[PATCH] kNN for btree

Started by Nikita Glukhovalmost 9 years ago49 messages
#1Nikita Glukhov
n.gluhov@postgrespro.ru
7 attachment(s)

Hi hackers,

I'd like to present a series of patches that implements k-Nearest
Neighbors (kNN)
search forbtree, which can be usedto speed up ORDER BY distance queries
like this:
SELECT * FROM eventsORDER BY date <-> '2000-01-01'::date ASC LIMIT 100;
Now only GiST supports kNN, but kNN on btree can be emulated using
contrib/btree_gist.

Scanning algorithm
==================

Algorithm is very simple: we use bidirectional B-tree index scan
starting at the point
from which we measure the distance(target point).At each step, we advance
this scan in the directionthat has the nearest point. Butwhen the
target point
does not fall into the scannedrange, we don't even need to
useabidirectional
scan here --- wecan use ordinaryunidirectional scaninthe right direction.

Performance results
===================

Test database istaken from original kNN-GiST presentation (PGCon2010).

Test query

SELECT * FROM events ORDER BY date <-> '1957-10-04'::date ASC LIMIT k;

can be optimizedto the next rather complicated UNION form, whichnolonger
requireskNN:

WITH
t1 AS (SELECT * FROM events WHERE date >= '1957-10-04'::date ORDER BY
date ASC LIMIT k),
t2 AS (SELECT * FROM events WHERE date < '1957-10-04'::date ORDER BY
date DESC LIMIT k),
t AS (SELECT * FROM t1 UNION SELECT * FROM t2)
SELECT * FROM t ORDER BY date <-> '1957-10-04'::date ASC LIMIT k;

In each cell of this table shown query execution time in milliseconds and
the number of accessed blocks:

k | kNN-btree | kNN-GiST| Opt. query | Seq.scan
| | (btree_gist) | withUNION | with sort
-------|--------------|--------------|---------------|------------
1 | 0.0414| 0.0794| 0.0608 |41.11824
10 | 0.0487 | 0.0919 | 0.09717 |41.81824
100 | 0.10747 | 0.192 52| 0.342104 |42.31824
1000 | 0.735573 | 0.913 650 | 2.9701160 |43.51824
10000 | 5.070 5622| 6.240 6760| 36.300 11031 | 54.1 1824
100000 | 49.600 51608| 61.900 64194 | 295.100 94980| 115.0 1824

As you can see, kNN-btree can be two times faster than kNN-GiST(btree_gist)
when k < 1000,but the number of blocks readis roughly the same.

Implementation details
======================

A brief description is given below for each of the patches:

1. Introduce amcanorderbyop() function

This patch transformsexisting boolean AMpropertyamcanorderbyop into a
method
(function pointer).This is necessarybecause, unlike GiST,kNN for
btreesupports
only a one ordering operator onthe first index column and we need a
different pathkey
matching logicfor btree (there was acorresponding comment
inmatch_pathkeys_to_index()).
GiST-specific logic has been moved from match_pathkeys_to_index() to
gistcanorderbyop().

2. Extract substructure BTScanState from BTScanOpaque

This refactoringis necessary for bidirectional kNN-scanimplementation. Now,
BTScanOpaque'ssubstructure BTScanState containing only thefields related
toscanpositionis passed to some functions where thewhole BTScanOpaque
waspassedpreviously.

3. Extract get_index_column_opclass(),
get_opclass_opfamily_and_input_type().

Extracted two simple common functions usedingistproperty() and
btproperty() (see the next patch).

4. Add kNN supportto btree

* Added additional optional BTScanState to BTScanOpaque for
bidirectional kNN scan.
* Implemented bidirectional kNN scan.
* Implemented logic for selecting kNN strategy
* Implemented btcanorderbyop(), updated btproperty() and btvalidate()

B-tree user interface functions have not been altered because ordering
operators
are used directly.

5. Add distance operators for sometypes

These operators for integer, float, date, time, timestamp, interval,
cash and oidtypes
havebeencopied fromcontrib/btree_gistand added to the existing btree
opclasses
as ordering operators. Their btree_gist duplicates areremoved in the
next patch.

6. Remove duplicate distance operators from contrib/btree_gist.

References to their own distance operators in btree_gist opclassesare
replaced
with references to the built-in operatorsand thanduplicate operators are
dropped.
But if the user is using somewhere these operators, upgrade of btree_gist
from 1.3 to 1.4 should fail.

7. Add regression tests for btree kNN.

Tests were added only after the built-in distance operators were added.

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-introduce-amcanorderby-function-v01.patchtext/x-patch; name=0001-introduce-amcanorderby-function-v01.patchDownload
diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 06077af..cb0f5f9 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -109,7 +109,6 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BLOOM_NSTRATEGIES;
 	amroutine->amsupport = BLOOM_NPROC;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -138,6 +137,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = blendscan;
 	amroutine->ammarkpos = NULL;
 	amroutine->amrestrpos = NULL;
+	amroutine->amcanorderbyop = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 3fce672..e85e224 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -82,7 +82,6 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = BRIN_LAST_OPTIONAL_PROCNUM;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -111,6 +110,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = brinendscan;
 	amroutine->ammarkpos = NULL;
 	amroutine->amrestrpos = NULL;
+	amroutine->amcanorderbyop = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 3909638..c7ff0fd 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -39,7 +39,6 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GINNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -68,6 +67,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = ginendscan;
 	amroutine->ammarkpos = NULL;
 	amroutine->amrestrpos = NULL;
+	amroutine->amcanorderbyop = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 4092a8b..504d9ee 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -59,7 +59,6 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GISTNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -88,6 +87,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = gistendscan;
 	amroutine->ammarkpos = NULL;
 	amroutine->amrestrpos = NULL;
+	amroutine->amcanorderbyop = gistcanorderbyop;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index f92baed..159987e 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -19,6 +19,7 @@
 #include "access/htup_details.h"
 #include "access/reloptions.h"
 #include "catalog/pg_opclass.h"
+#include "optimizer/paths.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -964,3 +965,31 @@ gistGetFakeLSN(Relation rel)
 		return GetFakeLSNForUnloggedRel();
 	}
 }
+
+/* gistcanorderbyop() */
+Expr *
+gistcanorderbyop(IndexOptInfo *index, PathKey *pathkey, int pathkeyno,
+				 Expr *orderby_clause, int *indexcol_p)
+{
+	int		indexcol;
+
+	/*
+	 * We allow any column of the GiST index to match each pathkey;
+	 * they don't have to match left-to-right as you might expect.
+	 */
+
+	for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
+	{
+		Expr	   *expr = match_clause_to_ordering_op(index,
+													   indexcol,
+													   orderby_clause,
+													   pathkey->pk_opfamily);
+		if (expr)
+		{
+			*indexcol_p = indexcol;
+			return expr;
+		}
+	}
+
+	return NULL;
+}
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0cbf6b0..8cdefac 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -56,7 +56,6 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = HTMaxStrategyNumber;
 	amroutine->amsupport = HASHNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -85,6 +84,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = hashendscan;
 	amroutine->ammarkpos = NULL;
 	amroutine->amrestrpos = NULL;
+	amroutine->amcanorderbyop = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index c6eed63..9e4384a 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -88,7 +88,6 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BTMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = true;
 	amroutine->amcanmulticol = true;
@@ -117,6 +116,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = btendscan;
 	amroutine->ammarkpos = btmarkpos;
 	amroutine->amrestrpos = btrestrpos;
+	amroutine->amcanorderbyop = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index b9e4940..f18b0bc 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -38,7 +38,6 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = SPGISTNProc;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -67,6 +66,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = spgendscan;
 	amroutine->ammarkpos = NULL;
 	amroutine->amrestrpos = NULL;
+	amroutine->amcanorderbyop = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 0a5c050..b9b4701 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -17,6 +17,7 @@
 
 #include <math.h>
 
+#include "access/amapi.h"
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_am.h"
@@ -166,8 +167,6 @@ static bool match_rowcompare_to_indexcol(IndexOptInfo *index,
 static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 						List **orderby_clauses_p,
 						List **clause_columns_p);
-static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
-							int indexcol, Expr *clause, Oid pk_opfamily);
 static bool ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
 						   EquivalenceClass *ec, EquivalenceMember *em,
 						   void *arg);
@@ -2473,12 +2472,15 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 	List	   *orderby_clauses = NIL;
 	List	   *clause_columns = NIL;
 	ListCell   *lc1;
+	int			pathkeyno = 1;
+	amcanorderbyop_function amcanorderbyop =
+			(amcanorderbyop_function) index->amcanorderbyop;
 
 	*orderby_clauses_p = NIL;	/* set default results */
 	*clause_columns_p = NIL;
 
-	/* Only indexes with the amcanorderbyop property are interesting here */
-	if (!index->amcanorderbyop)
+	/* Only indexes with the amcanorderbyop function are interesting here */
+	if (!amcanorderbyop)
 		return;
 
 	foreach(lc1, pathkeys)
@@ -2512,42 +2514,29 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 		foreach(lc2, pathkey->pk_eclass->ec_members)
 		{
 			EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
+			Expr	   *expr;
 			int			indexcol;
 
 			/* No possibility of match if it references other relations */
 			if (!bms_equal(member->em_relids, index->rel->relids))
 				continue;
 
-			/*
-			 * We allow any column of the index to match each pathkey; they
-			 * don't have to match left-to-right as you might expect.  This is
-			 * correct for GiST, which is the sole existing AM supporting
-			 * amcanorderbyop.  We might need different logic in future for
-			 * other implementations.
-			 */
-			for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
-			{
-				Expr	   *expr;
+			expr = amcanorderbyop(index, pathkey, pathkeyno, member->em_expr,
+								  &indexcol);
 
-				expr = match_clause_to_ordering_op(index,
-												   indexcol,
-												   member->em_expr,
-												   pathkey->pk_opfamily);
-				if (expr)
-				{
-					orderby_clauses = lappend(orderby_clauses, expr);
-					clause_columns = lappend_int(clause_columns, indexcol);
-					found = true;
-					break;
-				}
+			if (expr)
+			{
+				orderby_clauses = lappend(orderby_clauses, expr);
+				clause_columns = lappend_int(clause_columns, indexcol);
+				found = true;
+				break; /* don't want to look at remaining members */
 			}
-
-			if (found)			/* don't want to look at remaining members */
-				break;
 		}
 
 		if (!found)				/* fail if no match for this pathkey */
 			return;
+
+		pathkeyno++;
 	}
 
 	*orderby_clauses_p = orderby_clauses;		/* success! */
@@ -2579,7 +2568,7 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
  * If successful, return 'clause' as-is if the indexkey is on the left,
  * otherwise a commuted copy of 'clause'.  If no match, return NULL.
  */
-static Expr *
+Expr *
 match_clause_to_ordering_op(IndexOptInfo *index,
 							int indexcol,
 							Expr *clause,
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 7836e6b..93bda0f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -237,7 +237,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 			/* We copy just the fields we need, not all of rd_amroutine */
 			amroutine = indexRelation->rd_amroutine;
-			info->amcanorderbyop = amroutine->amcanorderbyop;
+			info->amcanorderbyop = (void (*)()) amroutine->amcanorderbyop;
 			info->amoptionalkey = amroutine->amoptionalkey;
 			info->amsearcharray = amroutine->amsearcharray;
 			info->amsearchnulls = amroutine->amsearchnulls;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 48b0cc0..436c5f8 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -21,6 +21,9 @@
  */
 struct PlannerInfo;
 struct IndexPath;
+struct IndexOptInfo;
+struct PathKey;
+struct Expr;
 
 /* Likewise, this file shouldn't depend on execnodes.h. */
 struct IndexInfo;
@@ -137,6 +140,12 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/* does AM support ORDER BY result of an operator on indexed column? */
+typedef struct Expr *(*amcanorderbyop_function) (struct IndexOptInfo *index,
+												 struct PathKey *pathkey,
+												 int pathkeyno,
+												 struct Expr *orderby_clause,
+												 int *indexcol_p);
 
 /*
  * API struct for an index AM.  Note this must be stored in a single palloc'd
@@ -155,8 +164,6 @@ typedef struct IndexAmRoutine
 	uint16		amsupport;
 	/* does AM support ORDER BY indexed column's value? */
 	bool		amcanorder;
-	/* does AM support ORDER BY result of an operator on indexed column? */
-	bool		amcanorderbyop;
 	/* does AM support backward scanning? */
 	bool		amcanbackward;
 	/* does AM support UNIQUE indexes? */
@@ -196,6 +203,7 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;		/* can be NULL */
 	amrestrpos_function amrestrpos;		/* can be NULL */
+	amcanorderbyop_function amcanorderbyop; /* can be NULL */
 } IndexAmRoutine;
 
 
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 72f52d4..0d35646 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -538,6 +538,11 @@ extern void gistMakeUnionKey(GISTSTATE *giststate, int attno,
 
 extern XLogRecPtr gistGetFakeLSN(Relation rel);
 
+extern struct Expr *gistcanorderbyop(struct IndexOptInfo *index,
+									 struct PathKey *pathkey,  int pathkeyno,
+									 struct Expr *orderby_clause,
+									 int *indexcol_p);
+
 /* gistvacuum.c */
 extern IndexBulkDeleteResult *gistbulkdelete(IndexVacuumInfo *info,
 			   IndexBulkDeleteResult *stats,
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index e1d31c7..ccf282a 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -616,7 +616,6 @@ typedef struct IndexOptInfo
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
 	bool		amoptionalkey;	/* can query omit key for the first column? */
 	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
 	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
@@ -624,6 +623,7 @@ typedef struct IndexOptInfo
 	bool		amhasgetbitmap; /* does AM have amgetbitmap interface? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
+	void		(*amcanorderbyop) ();	/* does AM support order by operator result? */
 } IndexOptInfo;
 
 /*
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 81a9be7..bb7611e 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -79,6 +79,10 @@ extern Expr *adjust_rowcompare_for_index(RowCompareExpr *clause,
 							int indexcol,
 							List **indexcolnos,
 							bool *var_on_left_p);
+extern Expr *match_clause_to_ordering_op(IndexOptInfo *index,
+							int indexcol,
+							Expr *clause,
+							Oid pk_opfamily);
 
 /*
  * tidpath.h
0002-extract-structure-BTScanState-v01.patchtext/x-patch; name=0002-extract-structure-BTScanState-v01.patchDownload
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 9e4384a..a05f0c2 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -295,6 +295,7 @@ bool
 btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 	bool		res;
 
 	/* btree indexes are never lossy */
@@ -305,7 +306,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(so->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
@@ -322,7 +323,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(so->currPos))
+		if (!BTScanPosIsValid(state->currPos))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -340,11 +341,11 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 				 * trying to optimize that, so we don't detect it, but instead
 				 * just forget any excess entries.
 				 */
-				if (so->killedItems == NULL)
-					so->killedItems = (int *)
+				if (state->killedItems == NULL)
+					state->killedItems = (int *)
 						palloc(MaxIndexTuplesPerPage * sizeof(int));
-				if (so->numKilled < MaxIndexTuplesPerPage)
-					so->killedItems[so->numKilled++] = so->currPos.itemIndex;
+				if (state->numKilled < MaxIndexTuplesPerPage)
+					state->killedItems[so->state.numKilled++] = state->currPos.itemIndex;
 			}
 
 			/*
@@ -369,6 +370,7 @@ int64
 btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	int64		ntids = 0;
 	ItemPointer heapTid;
 
@@ -401,7 +403,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				 * Advance to next tuple within page.  This is the same as the
 				 * easy case in _bt_next().
 				 */
-				if (++so->currPos.itemIndex > so->currPos.lastItem)
+				if (++currPos->itemIndex > currPos->lastItem)
 				{
 					/* let _bt_next do the heavy lifting */
 					if (!_bt_next(scan, ForwardScanDirection))
@@ -409,7 +411,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				}
 
 				/* Save tuple ID, and continue scanning */
-				heapTid = &so->currPos.items[so->currPos.itemIndex].heapTid;
+				heapTid = &currPos->items[currPos->itemIndex].heapTid;
 				tbm_add_tuples(tbm, heapTid, 1, false);
 				ntids++;
 			}
@@ -437,8 +439,8 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 
 	/* allocate private workspace */
 	so = (BTScanOpaque) palloc(sizeof(BTScanOpaqueData));
-	BTScanPosInvalidate(so->currPos);
-	BTScanPosInvalidate(so->markPos);
+	BTScanPosInvalidate(so->state.currPos);
+	BTScanPosInvalidate(so->state.markPos);
 	if (scan->numberOfKeys > 0)
 		so->keyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData));
 	else
@@ -449,15 +451,15 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	so->arrayKeys = NULL;
 	so->arrayContext = NULL;
 
-	so->killedItems = NULL;		/* until needed */
-	so->numKilled = 0;
+	so->state.killedItems = NULL;		/* until needed */
+	so->state.numKilled = 0;
 
 	/*
 	 * We don't know yet whether the scan will be index-only, so we do not
 	 * allocate the tuple workspace arrays until btrescan.  However, we set up
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
-	so->currTuples = so->markTuples = NULL;
+	so->state.currTuples = so->state.markTuples = NULL;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -466,6 +468,45 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	return scan;
 }
 
+static void
+_bt_release_current_position(BTScanState state, Relation indexRelation,
+							 bool invalidate)
+{
+	/* we aren't holding any read locks, but gotta drop the pins */
+	if (BTScanPosIsValid(state->currPos))
+	{
+		/* Before leaving current page, deal with any killed items */
+		if (state->numKilled > 0)
+			_bt_killitems(state, indexRelation);
+
+		BTScanPosUnpinIfPinned(state->currPos);
+
+		if (invalidate)
+			BTScanPosInvalidate(state->currPos);
+	}
+}
+
+static void
+_bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
+{
+	/* No need to invalidate positions, if the RAM is about to be freed. */
+	_bt_release_current_position(state, scan->indexRelation, !free);
+
+	state->markItemIndex = -1;
+	BTScanPosUnpinIfPinned(state->markPos);
+
+	if (free)
+	{
+		if (state->killedItems != NULL)
+			pfree(state->killedItems);
+		if (state->currTuples != NULL)
+			pfree(state->currTuples);
+		/* markTuples should not be pfree'd (_bt_allocate_tuple_workspaces) */
+	}
+	else
+		BTScanPosInvalidate(state->markPos);
+}
+
 /*
  *	btrescan() -- rescan an index relation
  */
@@ -474,20 +515,9 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 		 ScanKey orderbys, int norderbys)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-		BTScanPosInvalidate(so->currPos);
-	}
-
-	so->markItemIndex = -1;
-	BTScanPosUnpinIfPinned(so->markPos);
-	BTScanPosInvalidate(so->markPos);
+	_bt_release_scan_state(scan, state, false);
 
 	/*
 	 * Allocate tuple workspace arrays, if needed for an index-only scan and
@@ -505,11 +535,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 	 * a SIGSEGV is not possible.  Yeah, this is ugly as sin, but it beats
 	 * adding special-case treatment for name_ops elsewhere.
 	 */
-	if (scan->xs_want_itup && so->currTuples == NULL)
-	{
-		so->currTuples = (char *) palloc(BLCKSZ * 2);
-		so->markTuples = so->currTuples + BLCKSZ;
-	}
+	if (scan->xs_want_itup && state->currTuples == NULL)
+		_bt_allocate_tuple_workspaces(state);
 
 	/*
 	 * Reset the scan keys. Note that keys ordering stuff moved to _bt_first.
@@ -533,19 +560,7 @@ btendscan(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-	}
-
-	so->markItemIndex = -1;
-	BTScanPosUnpinIfPinned(so->markPos);
-
-	/* No need to invalidate positions, the RAM is about to be freed. */
+	_bt_release_scan_state(scan, &so->state, true);
 
 	/* Release storage */
 	if (so->keyData != NULL)
@@ -553,24 +568,15 @@ btendscan(IndexScanDesc scan)
 	/* so->arrayKeyData and so->arrayKeys are in arrayContext */
 	if (so->arrayContext != NULL)
 		MemoryContextDelete(so->arrayContext);
-	if (so->killedItems != NULL)
-		pfree(so->killedItems);
-	if (so->currTuples != NULL)
-		pfree(so->currTuples);
-	/* so->markTuples should not be pfree'd, see btrescan */
+
 	pfree(so);
 }
 
-/*
- *	btmarkpos() -- save current scan position
- */
-void
-btmarkpos(IndexScanDesc scan)
+static void
+_bt_mark_current_position(BTScanState state)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-
 	/* There may be an old mark with a pin (but no lock). */
-	BTScanPosUnpinIfPinned(so->markPos);
+	BTScanPosUnpinIfPinned(state->markPos);
 
 	/*
 	 * Just record the current itemIndex.  If we later step to next page
@@ -578,32 +584,34 @@ btmarkpos(IndexScanDesc scan)
 	 * the currPos struct in markPos.  If (as often happens) the mark is moved
 	 * before we leave the page, we don't have to do that work.
 	 */
-	if (BTScanPosIsValid(so->currPos))
-		so->markItemIndex = so->currPos.itemIndex;
+	if (BTScanPosIsValid(state->currPos))
+		state->markItemIndex = state->currPos.itemIndex;
 	else
 	{
-		BTScanPosInvalidate(so->markPos);
-		so->markItemIndex = -1;
+		BTScanPosInvalidate(state->markPos);
+		state->markItemIndex = -1;
 	}
-
-	/* Also record the current positions of any array keys */
-	if (so->numArrayKeys)
-		_bt_mark_array_keys(scan);
 }
 
 /*
- *	btrestrpos() -- restore scan to last saved position
+ *	btmarkpos() -- save current scan position
  */
 void
-btrestrpos(IndexScanDesc scan)
+btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* Restore the marked positions of any array keys */
+	_bt_mark_current_position(&so->state);
+
+	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
-		_bt_restore_array_keys(scan);
+		_bt_mark_array_keys(scan);
+}
 
-	if (so->markItemIndex >= 0)
+static void
+_bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
+{
+	if (state->markItemIndex >= 0)
 	{
 		/*
 		 * The scan has never moved to a new page since the last mark.  Just
@@ -612,7 +620,7 @@ btrestrpos(IndexScanDesc scan)
 		 * NB: In this case we can't count on anything in so->markPos to be
 		 * accurate.
 		 */
-		so->currPos.itemIndex = so->markItemIndex;
+		state->currPos.itemIndex = state->markItemIndex;
 	}
 	else
 	{
@@ -622,32 +630,40 @@ btrestrpos(IndexScanDesc scan)
 		 * locks, but if we're still holding the pin for the current position,
 		 * we must drop it.
 		 */
-		if (BTScanPosIsValid(so->currPos))
-		{
-			/* Before leaving current page, deal with any killed items */
-			if (so->numKilled > 0)
-				_bt_killitems(scan);
-			BTScanPosUnpinIfPinned(so->currPos);
-		}
+		_bt_release_current_position(state, scan->indexRelation,
+									 !BTScanPosIsValid(state->markPos));
 
-		if (BTScanPosIsValid(so->markPos))
+		if (BTScanPosIsValid(state->markPos))
 		{
 			/* bump pin on mark buffer for assignment to current buffer */
-			if (BTScanPosIsPinned(so->markPos))
-				IncrBufferRefCount(so->markPos.buf);
-			memcpy(&so->currPos, &so->markPos,
+			if (BTScanPosIsPinned(state->markPos))
+				IncrBufferRefCount(state->markPos.buf);
+			memcpy(&state->currPos, &state->markPos,
 				   offsetof(BTScanPosData, items[1]) +
-				   so->markPos.lastItem * sizeof(BTScanPosItem));
-			if (so->currTuples)
-				memcpy(so->currTuples, so->markTuples,
-					   so->markPos.nextTupleOffset);
+				   state->markPos.lastItem * sizeof(BTScanPosItem));
+			if (state->currTuples)
+				memcpy(state->currTuples, state->markTuples,
+					   state->markPos.nextTupleOffset);
 		}
-		else
-			BTScanPosInvalidate(so->currPos);
 	}
 }
 
 /*
+ *	btrestrpos() -- restore scan to last saved position
+ */
+void
+btrestrpos(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
+	/* Restore the marked positions of any array keys */
+	if (so->numArrayKeys)
+		_bt_restore_array_keys(scan);
+
+	_bt_restore_marked_position(scan, &so->state);
+}
+
+/*
  * Bulk deletion of all index entries pointing to a set of heap tuples.
  * The set of target tuples is specified via a callback routine that tells
  * whether any given heap tuple (identified by ItemPointer) is being deleted.
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 4fba75a..264d581 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -25,11 +25,11 @@
 #include "utils/tqual.h"
 
 
-static bool _bt_readpage(IndexScanDesc scan, ScanDirection dir,
+static bool _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 			 OffsetNumber offnum);
-static void _bt_saveitem(BTScanOpaque so, int itemIndex,
+static void _bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup);
-static bool _bt_steppage(IndexScanDesc scan, ScanDirection dir);
+static bool _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf, Snapshot snapshot);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
@@ -509,6 +509,58 @@ _bt_compare(Relation rel,
 }
 
 /*
+ * _bt_return_current_item() -- Prepare current scan state item for return.
+ *
+ * This function is used only in "return _bt_return_current_item();" statements
+ * and always returns true.
+ */
+static inline bool
+_bt_return_current_item(IndexScanDesc scan, BTScanState state)
+{
+	BTScanPosItem *currItem = &state->currPos.items[state->currPos.itemIndex];
+
+	scan->xs_ctup.t_self = currItem->heapTid;
+
+	if (scan->xs_want_itup)
+		scan->xs_itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+
+	return true;
+}
+
+/*
+ * _bt_load_first_page() -- Load data from the first page of the scan.
+ *
+ * Caller must have pinned and read-locked state->currPos.buf.
+ *
+ * On success exit, state->currPos is updated to contain data from the next
+ * interesting page.  For success on a scan using a non-MVCC snapshot we hold
+ * a pin, but not a read lock, on that page.  If we do not hold the pin, we
+ * set state->currPos.buf to InvalidBuffer.  We return true to indicate success.
+ *
+ * If there are no more matching records in the given direction at all,
+ * we drop all locks and pins, set state->currPos.buf to InvalidBuffer,
+ * and return false.
+ */
+static bool
+_bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+					OffsetNumber offnum)
+{
+	if (!_bt_readpage(scan, state, dir, offnum))
+	{
+		/*
+		 * There's no actually-matching data on this page.  Try to advance to
+		 * the next page.  Return false if there's no matching data at all.
+		 */
+		LockBuffer(state->currPos.buf, BUFFER_LOCK_UNLOCK);
+		return _bt_steppage(scan, state, dir);
+	}
+
+	/* Drop the lock, and maybe the pin, on the current page */
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
+	return true;
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -533,6 +585,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	BTStack		stack;
 	OffsetNumber offnum;
@@ -545,9 +598,8 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	int			keysCount = 0;
 	int			i;
 	StrategyNumber strat_total;
-	BTScanPosItem *currItem;
 
-	Assert(!BTScanPosIsValid(so->currPos));
+	Assert(!BTScanPosIsValid(*currPos));
 
 	pgstat_count_index_scan(rel);
 
@@ -1002,16 +1054,16 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	/* initialize moreLeft/moreRight appropriately for scan direction */
 	if (ScanDirectionIsForward(dir))
 	{
-		so->currPos.moreLeft = false;
-		so->currPos.moreRight = true;
+		currPos->moreLeft = false;
+		currPos->moreRight = true;
 	}
 	else
 	{
-		so->currPos.moreLeft = true;
-		so->currPos.moreRight = false;
+		currPos->moreLeft = true;
+		currPos->moreRight = false;
 	}
-	so->numKilled = 0;			/* just paranoia */
-	Assert(so->markItemIndex == -1);
+	so->state.numKilled = 0;	/* just paranoia */
+	Assert(so->state.markItemIndex == -1);
 
 	/* position to the precise item on the page */
 	offnum = _bt_binsrch(rel, buf, keysCount, scankeys, nextkey);
@@ -1038,35 +1090,35 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		offnum = OffsetNumberPrev(offnum);
 
 	/* remember which buffer we have pinned, if any */
-	Assert(!BTScanPosIsValid(so->currPos));
-	so->currPos.buf = buf;
+	Assert(!BTScanPosIsValid(*currPos));
+	currPos->buf = buf;
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, offnum))
+	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
+		return false;
+
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
+}
+
+/*
+ *	Advance to next tuple on current page; or if there's no more,
+ *	try to step to the next page with data.
+ */
+static bool
+_bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
+{
+	if (ScanDirectionIsForward(dir))
 	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
+		if (++state->currPos.itemIndex <= state->currPos.lastItem)
+			return true;
 	}
 	else
 	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+		if (--state->currPos.itemIndex >= state->currPos.firstItem)
+			return true;
 	}
 
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_steppage(scan, state, dir);
 }
 
 /*
@@ -1087,44 +1139,20 @@ bool
 _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-	BTScanPosItem *currItem;
 
-	/*
-	 * Advance to next tuple on current page; or if there's no more, try to
-	 * step to the next page with data.
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (++so->currPos.itemIndex > so->currPos.lastItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
-	else
-	{
-		if (--so->currPos.itemIndex < so->currPos.firstItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
+	if (!_bt_next_item(scan, &so->state, dir))
+		return false;
 
 	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
  *	_bt_readpage() -- Load data from current index page into so->currPos
  *
- * Caller must have pinned and read-locked so->currPos.buf; the buffer's state
- * is not changed here.  Also, currPos.moreLeft and moreRight must be valid;
- * they are updated as appropriate.  All other fields of so->currPos are
+ * Caller must have pinned and read-locked pos->buf; the buffer's state
+ * is not changed here.  Also, pos->moreLeft and moreRight must be valid;
+ * they are updated as appropriate.  All other fields of pos are
  * initialized from scratch here.
  *
  * We scan the current page starting at offnum and moving in the indicated
@@ -1135,9 +1163,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
  * Returns true if any matching items found on the page, false if none.
  */
 static bool
-_bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
+_bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+			 OffsetNumber offnum)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
@@ -1150,9 +1179,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We must have the buffer pinned and locked, but the usual macro can't be
 	 * used here; this function is what makes it good for currPos.
 	 */
-	Assert(BufferIsValid(so->currPos.buf));
+	Assert(BufferIsValid(pos->buf));
 
-	page = BufferGetPage(so->currPos.buf);
+	page = BufferGetPage(pos->buf);
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 	minoff = P_FIRSTDATAKEY(opaque);
 	maxoff = PageGetMaxOffsetNumber(page);
@@ -1161,30 +1190,30 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We note the buffer's block number so that we can release the pin later.
 	 * This allows us to re-read the buffer if it is needed again for hinting.
 	 */
-	so->currPos.currPage = BufferGetBlockNumber(so->currPos.buf);
+	pos->currPage = BufferGetBlockNumber(pos->buf);
 
 	/*
 	 * We save the LSN of the page as we read it, so that we know whether it
 	 * safe to apply LP_DEAD hints to the page later.  This allows us to drop
 	 * the pin for MVCC scans, which allows vacuum to avoid blocking.
 	 */
-	so->currPos.lsn = PageGetLSN(page);
+	pos->lsn = PageGetLSN(page);
 
 	/*
 	 * we must save the page's right-link while scanning it; this tells us
 	 * where to step right to after we're done with these items.  There is no
 	 * corresponding need for the left-link, since splits always go right.
 	 */
-	so->currPos.nextPage = opaque->btpo_next;
+	pos->nextPage = opaque->btpo_next;
 
 	/* initialize tuple workspace to empty */
-	so->currPos.nextTupleOffset = 0;
+	pos->nextTupleOffset = 0;
 
 	/*
 	 * Now that the current page has been made consistent, the macro should be
 	 * good.
 	 */
-	Assert(BTScanPosIsPinned(so->currPos));
+	Assert(BTScanPosIsPinned(*pos));
 
 	if (ScanDirectionIsForward(dir))
 	{
@@ -1199,13 +1228,13 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			if (itup != NULL)
 			{
 				/* tuple passes all scan key conditions, so remember it */
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 				itemIndex++;
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreRight = false;
+				pos->moreRight = false;
 				break;
 			}
 
@@ -1213,9 +1242,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex <= MaxIndexTuplesPerPage);
-		so->currPos.firstItem = 0;
-		so->currPos.lastItem = itemIndex - 1;
-		so->currPos.itemIndex = 0;
+		pos->firstItem = 0;
+		pos->lastItem = itemIndex - 1;
+		pos->itemIndex = 0;
 	}
 	else
 	{
@@ -1231,12 +1260,12 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			{
 				/* tuple passes all scan key conditions, so remember it */
 				itemIndex--;
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreLeft = false;
+				pos->moreLeft = false;
 				break;
 			}
 
@@ -1244,30 +1273,31 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex >= 0);
-		so->currPos.firstItem = itemIndex;
-		so->currPos.lastItem = MaxIndexTuplesPerPage - 1;
-		so->currPos.itemIndex = MaxIndexTuplesPerPage - 1;
+		pos->firstItem = itemIndex;
+		pos->lastItem = MaxIndexTuplesPerPage - 1;
+		pos->itemIndex = MaxIndexTuplesPerPage - 1;
 	}
 
-	return (so->currPos.firstItem <= so->currPos.lastItem);
+	return (pos->firstItem <= pos->lastItem);
 }
 
 /* Save an index item into so->currPos.items[itemIndex] */
 static void
-_bt_saveitem(BTScanOpaque so, int itemIndex,
+_bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup)
 {
-	BTScanPosItem *currItem = &so->currPos.items[itemIndex];
+	BTScanPosItem *currItem = &state->currPos.items[itemIndex];
 
 	currItem->heapTid = itup->t_tid;
 	currItem->indexOffset = offnum;
-	if (so->currTuples)
+	if (state->currTuples)
 	{
 		Size		itupsz = IndexTupleSize(itup);
 
-		currItem->tupleOffset = so->currPos.nextTupleOffset;
-		memcpy(so->currTuples + so->currPos.nextTupleOffset, itup, itupsz);
-		so->currPos.nextTupleOffset += MAXALIGN(itupsz);
+		currItem->tupleOffset = state->currPos.nextTupleOffset;
+		memcpy(state->currTuples + state->currPos.nextTupleOffset,
+			   itup, itupsz);
+		state->currPos.nextTupleOffset += MAXALIGN(itupsz);
 	}
 }
 
@@ -1287,66 +1317,64 @@ _bt_saveitem(BTScanOpaque so, int itemIndex,
  * locks and pins, set so->currPos.buf to InvalidBuffer, and return FALSE.
  */
 static bool
-_bt_steppage(IndexScanDesc scan, ScanDirection dir)
+_bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-	Relation	rel;
+	BTScanPos	currPos = &state->currPos;
+	Relation	rel = scan->indexRelation;
 	Page		page;
 	BTPageOpaque opaque;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(*currPos));
 
 	/* Before leaving current page, deal with any killed items */
-	if (so->numKilled > 0)
-		_bt_killitems(scan);
+	if (state->numKilled > 0)
+		_bt_killitems(state, rel);
 
 	/*
 	 * Before we modify currPos, make a copy of the page data if there was a
 	 * mark position that needs it.
 	 */
-	if (so->markItemIndex >= 0)
+	if (state->markItemIndex >= 0)
 	{
 		/* bump pin on current buffer for assignment to mark buffer */
-		if (BTScanPosIsPinned(so->currPos))
-			IncrBufferRefCount(so->currPos.buf);
-		memcpy(&so->markPos, &so->currPos,
+		if (BTScanPosIsPinned(*currPos))
+			IncrBufferRefCount(currPos->buf);
+		memcpy(&state->markPos, currPos,
 			   offsetof(BTScanPosData, items[1]) +
-			   so->currPos.lastItem * sizeof(BTScanPosItem));
-		if (so->markTuples)
-			memcpy(so->markTuples, so->currTuples,
-				   so->currPos.nextTupleOffset);
-		so->markPos.itemIndex = so->markItemIndex;
-		so->markItemIndex = -1;
+			   currPos->lastItem * sizeof(BTScanPosItem));
+		if (state->markTuples)
+			memcpy(state->markTuples, state->currTuples,
+				   currPos->nextTupleOffset);
+		state->markPos.itemIndex = state->markItemIndex;
+		state->markItemIndex = -1;
 	}
 
-	rel = scan->indexRelation;
-
 	if (ScanDirectionIsForward(dir))
 	{
 		/* Walk right to the next page with data */
 		/* We must rely on the previously saved nextPage link! */
-		BlockNumber blkno = so->currPos.nextPage;
+		BlockNumber blkno = currPos->nextPage;
 
 		/* Remember we left a page with data */
-		so->currPos.moreLeft = true;
+		currPos->moreLeft = true;
 
 		/* release the previous buffer, if pinned */
-		BTScanPosUnpinIfPinned(so->currPos);
+		BTScanPosUnpinIfPinned(*currPos);
 
 		for (;;)
 		{
 			/* if we're at end of scan, give up */
-			if (blkno == P_NONE || !so->currPos.moreRight)
+			if (blkno == P_NONE || !currPos->moreRight)
 			{
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 			/* check for interrupts while we're not holding any buffer lock */
 			CHECK_FOR_INTERRUPTS();
 			/* step right one page */
-			so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
+			currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
 			/* check for deleted page */
-			page = BufferGetPage(so->currPos.buf);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			if (!P_IGNORE(opaque))
@@ -1354,19 +1382,19 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 				PredicateLockPage(rel, blkno, scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreRight if we can stop */
-				if (_bt_readpage(scan, dir, P_FIRSTDATAKEY(opaque)))
+				if (_bt_readpage(scan, state, dir, P_FIRSTDATAKEY(opaque)))
 					break;
 			}
 
 			/* nope, keep going */
 			blkno = opaque->btpo_next;
-			_bt_relbuf(rel, so->currPos.buf);
+			_bt_relbuf(rel, currPos->buf);
 		}
 	}
 	else
 	{
 		/* Remember we left a page with data */
-		so->currPos.moreRight = true;
+		currPos->moreRight = true;
 
 		/*
 		 * Walk left to the next page with data.  This is much more complex
@@ -1390,29 +1418,28 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 		 * is MVCC the page cannot move past the half-dead state to fully
 		 * deleted.
 		 */
-		if (BTScanPosIsPinned(so->currPos))
-			LockBuffer(so->currPos.buf, BT_READ);
+		if (BTScanPosIsPinned(*currPos))
+			LockBuffer(currPos->buf, BT_READ);
 		else
-			so->currPos.buf = _bt_getbuf(rel, so->currPos.currPage, BT_READ);
+			currPos->buf = _bt_getbuf(rel, currPos->currPage, BT_READ);
 
 		for (;;)
 		{
 			/* Done if we know there are no matching keys to the left */
-			if (!so->currPos.moreLeft)
+			if (!currPos->moreLeft)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
-				BTScanPosInvalidate(so->currPos);
+				_bt_relbuf(rel, currPos->buf);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
 			/* Step to next physical page */
-			so->currPos.buf = _bt_walk_left(rel, so->currPos.buf,
-											scan->xs_snapshot);
+			currPos->buf = _bt_walk_left(rel, currPos->buf, scan->xs_snapshot);
 
 			/* if we're physically at end of index, return failure */
-			if (so->currPos.buf == InvalidBuffer)
+			if (currPos->buf == InvalidBuffer)
 			{
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
@@ -1421,22 +1448,22 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			 * it's not half-dead and contains matching tuples. Else loop back
 			 * and do it all again.
 			 */
-			page = BufferGetPage(so->currPos.buf);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			if (!P_IGNORE(opaque))
 			{
-				PredicateLockPage(rel, BufferGetBlockNumber(so->currPos.buf), scan->xs_snapshot);
+				PredicateLockPage(rel, BufferGetBlockNumber(currPos->buf), scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreLeft if we can stop */
-				if (_bt_readpage(scan, dir, PageGetMaxOffsetNumber(page)))
+				if (_bt_readpage(scan, state, dir, PageGetMaxOffsetNumber(page)))
 					break;
 			}
 		}
 	}
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, currPos);
 
 	return true;
 }
@@ -1661,11 +1688,11 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber start;
-	BTScanPosItem *currItem;
 
 	/*
 	 * Scan down to the leftmost or rightmost leaf page.  This is a simplified
@@ -1681,7 +1708,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 		 * exists.
 		 */
 		PredicateLockRelation(rel, scan->xs_snapshot);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 		return false;
 	}
 
@@ -1710,46 +1737,25 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 	}
 
 	/* remember which buffer we have pinned */
-	so->currPos.buf = buf;
+	currPos->buf = buf;
 
 	/* initialize moreLeft/moreRight appropriately for scan direction */
 	if (ScanDirectionIsForward(dir))
 	{
-		so->currPos.moreLeft = false;
-		so->currPos.moreRight = true;
+		currPos->moreLeft = false;
+		currPos->moreRight = true;
 	}
 	else
 	{
-		so->currPos.moreLeft = true;
-		so->currPos.moreRight = false;
+		currPos->moreLeft = true;
+		currPos->moreRight = false;
 	}
-	so->numKilled = 0;			/* just paranoia */
-	so->markItemIndex = -1;		/* ditto */
+	so->state.numKilled = 0;	/* just paranoia */
+	so->state.markItemIndex = -1;		/* ditto */
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, start))
-	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
-	}
-	else
-	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
-	}
-
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
+	if (!_bt_load_first_page(scan, &so->state, dir, start))
+		return false;
 
-	return true;
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index da0f330..ebcba7e 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -1725,26 +1725,26 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, TupleDesc tupdesc,
  * away and the TID was re-used by a completely different heap tuple.
  */
 void
-_bt_killitems(IndexScanDesc scan)
+_bt_killitems(BTScanState state, Relation indexRelation)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
 	OffsetNumber maxoff;
 	int			i;
-	int			numKilled = so->numKilled;
+	int			numKilled = state->numKilled;
 	bool		killedsomething = false;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(state->currPos));
 
 	/*
 	 * Always reset the scan state, so we don't look for same items on other
 	 * pages.
 	 */
-	so->numKilled = 0;
+	state->numKilled = 0;
 
-	if (BTScanPosIsPinned(so->currPos))
+	if (BTScanPosIsPinned(*pos))
 	{
 		/*
 		 * We have held the pin on this page since we read the index tuples,
@@ -1752,28 +1752,28 @@ _bt_killitems(IndexScanDesc scan)
 		 * re-use of any TID on the page, so there is no need to check the
 		 * LSN.
 		 */
-		LockBuffer(so->currPos.buf, BT_READ);
+		LockBuffer(pos->buf, BT_READ);
 
-		page = BufferGetPage(so->currPos.buf);
+		page = BufferGetPage(pos->buf);
 	}
 	else
 	{
 		Buffer		buf;
 
 		/* Attempt to re-read the buffer, getting pin and lock. */
-		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ);
+		buf = _bt_getbuf(indexRelation, pos->currPage, BT_READ);
 
 		/* It might not exist anymore; in which case we can't hint it. */
 		if (!BufferIsValid(buf))
 			return;
 
 		page = BufferGetPage(buf);
-		if (PageGetLSN(page) == so->currPos.lsn)
-			so->currPos.buf = buf;
+		if (PageGetLSN(page) == pos->lsn)
+			pos->buf = buf;
 		else
 		{
 			/* Modified while not pinned means hinting is not safe. */
-			_bt_relbuf(scan->indexRelation, buf);
+			_bt_relbuf(indexRelation, buf);
 			return;
 		}
 	}
@@ -1784,12 +1784,12 @@ _bt_killitems(IndexScanDesc scan)
 
 	for (i = 0; i < numKilled; i++)
 	{
-		int			itemIndex = so->killedItems[i];
-		BTScanPosItem *kitem = &so->currPos.items[itemIndex];
+		int			itemIndex = state->killedItems[i];
+		BTScanPosItem *kitem = &pos->items[itemIndex];
 		OffsetNumber offnum = kitem->indexOffset;
 
-		Assert(itemIndex >= so->currPos.firstItem &&
-			   itemIndex <= so->currPos.lastItem);
+		Assert(itemIndex >= pos->firstItem &&
+			   itemIndex <= pos->lastItem);
 		if (offnum < minoff)
 			continue;			/* pure paranoia */
 		while (offnum <= maxoff)
@@ -1817,10 +1817,10 @@ _bt_killitems(IndexScanDesc scan)
 	if (killedsomething)
 	{
 		opaque->btpo_flags |= BTP_HAS_GARBAGE;
-		MarkBufferDirtyHint(so->currPos.buf, true);
+		MarkBufferDirtyHint(pos->buf, true);
 	}
 
-	LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
+	LockBuffer(pos->buf, BUFFER_LOCK_UNLOCK);
 }
 
 
@@ -2065,3 +2065,14 @@ btproperty(Oid index_oid, int attno,
 			return false;		/* punt to generic code */
 	}
 }
+
+/*
+ * _bt_allocate_tuple_workspaces() -- Allocate buffers for saving index tuples
+ * 		in index-only scans.
+ */
+void
+_bt_allocate_tuple_workspaces(BTScanState state)
+{
+	state->currTuples = (char *) palloc(BLCKSZ * 2);
+	state->markTuples = state->currTuples + BLCKSZ;
+}
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 181f3ac..4427bd3 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -598,20 +598,8 @@ typedef struct BTArrayKeyInfo
 	Datum	   *elem_values;	/* array of num_elems Datums */
 } BTArrayKeyInfo;
 
-typedef struct BTScanOpaqueData
+typedef struct BTScanStateData
 {
-	/* these fields are set by _bt_preprocess_keys(): */
-	bool		qual_ok;		/* false if qual can never be satisfied */
-	int			numberOfKeys;	/* number of preprocessed scan keys */
-	ScanKey		keyData;		/* array of preprocessed scan keys */
-
-	/* workspace for SK_SEARCHARRAY support */
-	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
-	int			numArrayKeys;	/* number of equality-type array keys (-1 if
-								 * there are any unsatisfiable array keys) */
-	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
-	MemoryContext arrayContext; /* scan-lifespan context for array data */
-
 	/* info about killed items if any (killedItems is NULL if never used) */
 	int		   *killedItems;	/* currPos.items indexes of killed items */
 	int			numKilled;		/* number of currently stored items */
@@ -636,6 +624,23 @@ typedef struct BTScanOpaqueData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+}	BTScanStateData, *BTScanState;
+
+typedef struct BTScanOpaqueData
+{
+	/* these fields are set by _bt_preprocess_keys(): */
+	bool		qual_ok;		/* false if qual can never be satisfied */
+	int			numberOfKeys;	/* number of preprocessed scan keys */
+	ScanKey		keyData;		/* array of preprocessed scan keys */
+
+	/* workspace for SK_SEARCHARRAY support */
+	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
+	int			numArrayKeys;	/* number of equality-type array keys (-1 if
+								 * there are any unsatisfiable array keys) */
+	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
+	MemoryContext arrayContext; /* scan-lifespan context for array data */
+
+	BTScanStateData	state;
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -740,7 +745,7 @@ extern void _bt_preprocess_keys(IndexScanDesc scan);
 extern IndexTuple _bt_checkkeys(IndexScanDesc scan,
 			  Page page, OffsetNumber offnum,
 			  ScanDirection dir, bool *continuescan);
-extern void _bt_killitems(IndexScanDesc scan);
+extern void _bt_killitems(BTScanState state, Relation indexRelation);
 extern BTCycleId _bt_vacuum_cycleid(Relation rel);
 extern BTCycleId _bt_start_vacuum(Relation rel);
 extern void _bt_end_vacuum(Relation rel);
@@ -751,6 +756,7 @@ extern bytea *btoptions(Datum reloptions, bool validate);
 extern bool btproperty(Oid index_oid, int attno,
 		   IndexAMProperty prop, const char *propname,
 		   bool *res, bool *isnull);
+extern void _bt_allocate_tuple_workspaces(BTScanState state);
 
 /*
  * prototypes for functions in nbtvalidate.c
0003-extract-get_index_column_opclass-and-get_opclass_opfamily_and_input_type-v01.patchtext/x-patch; name=0003-extract-get_index_column_opclass-and-get_opclass_opfamily_and_input_type-v01.patchDownload
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 159987e..e1b1a18 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -24,6 +24,7 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/syscache.h"
+#include "utils/lsyscache.h"
 
 
 /*
@@ -852,12 +853,6 @@ gistproperty(Oid index_oid, int attno,
 			 IndexAMProperty prop, const char *propname,
 			 bool *res, bool *isnull)
 {
-	HeapTuple	tuple;
-	Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY;
-	Form_pg_opclass rd_opclass;
-	Datum		datum;
-	bool		disnull;
-	oidvector  *indclass;
 	Oid			opclass,
 				opfamily,
 				opcintype;
@@ -891,49 +886,28 @@ gistproperty(Oid index_oid, int attno,
 	}
 
 	/* First we need to know the column's opclass. */
-
-	tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
-	if (!HeapTupleIsValid(tuple))
+	opclass = get_index_column_opclass(index_oid, attno);
+	if (!OidIsValid(opclass))
 	{
 		*isnull = true;
 		return true;
 	}
-	rd_index = (Form_pg_index) GETSTRUCT(tuple);
-
-	/* caller is supposed to guarantee this */
-	Assert(attno > 0 && attno <= rd_index->indnatts);
-
-	datum = SysCacheGetAttr(INDEXRELID, tuple,
-							Anum_pg_index_indclass, &disnull);
-	Assert(!disnull);
-
-	indclass = ((oidvector *) DatumGetPointer(datum));
-	opclass = indclass->values[attno - 1];
-
-	ReleaseSysCache(tuple);
 
 	/* Now look up the opclass family and input datatype. */
-
-	tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
-	if (!HeapTupleIsValid(tuple))
+	if (!get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype))
 	{
 		*isnull = true;
 		return true;
 	}
-	rd_opclass = (Form_pg_opclass) GETSTRUCT(tuple);
-
-	opfamily = rd_opclass->opcfamily;
-	opcintype = rd_opclass->opcintype;
-
-	ReleaseSysCache(tuple);
 
 	/* And now we can check whether the function is provided. */
-
 	*res = SearchSysCacheExists4(AMPROCNUM,
 								 ObjectIdGetDatum(opfamily),
 								 ObjectIdGetDatum(opcintype),
 								 ObjectIdGetDatum(opcintype),
 								 Int16GetDatum(procno));
+	isnull = false;
+
 	return true;
 }
 
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index aff88a5..26c81c9 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1050,6 +1050,32 @@ get_opclass_input_type(Oid opclass)
 	return result;
 }
 
+/*
+ * get_opclass_family_and_input_type
+ *
+ *		Returns the OID of the operator family the opclass belongs to,
+ *				the OID of the datatype the opclass indexes
+ */
+bool
+get_opclass_opfamily_and_input_type(Oid opclass, Oid *opfamily, Oid *opcintype)
+{
+	HeapTuple	tp;
+	Form_pg_opclass cla_tup;
+
+	tp = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
+	if (!HeapTupleIsValid(tp))
+		return false;
+
+	cla_tup = (Form_pg_opclass) GETSTRUCT(tp);
+
+	*opfamily = cla_tup->opcfamily;
+	*opcintype = cla_tup->opcintype;
+
+	ReleaseSysCache(tp);
+
+	return true;
+}
+
 /*				---------- OPERATOR CACHE ----------					 */
 
 /*
@@ -3061,3 +3087,45 @@ get_range_subtype(Oid rangeOid)
 	else
 		return InvalidOid;
 }
+
+/*				---------- PG_INDEX CACHE ----------				 */
+
+/*
+ * get_index_column_opclass
+ *
+ *		Given the index OID and column number,
+ *		return opclass of the index column
+ *			or InvalidOid if the index was not found.
+ */
+Oid
+get_index_column_opclass(Oid index_oid, int attno)
+{
+	HeapTuple	tuple;
+	Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY;
+	Datum		datum;
+	bool		isnull;
+	oidvector  *indclass;
+	Oid			opclass;
+
+	/* First we need to know the column's opclass. */
+
+	tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
+	if (!HeapTupleIsValid(tuple))
+		return InvalidOid;
+
+	rd_index = (Form_pg_index) GETSTRUCT(tuple);
+
+	/* caller is supposed to guarantee this */
+	Assert(attno > 0 && attno <= rd_index->indnatts);
+
+	datum = SysCacheGetAttr(INDEXRELID, tuple,
+							Anum_pg_index_indclass, &isnull);
+	Assert(!isnull);
+
+	indclass = ((oidvector *) DatumGetPointer(datum));
+	opclass = indclass->values[attno - 1];
+
+	ReleaseSysCache(tuple);
+
+	return opclass;
+}
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b6d1fca..618c4e8 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -73,6 +73,8 @@ extern char *get_constraint_name(Oid conoid);
 extern char *get_language_name(Oid langoid, bool missing_ok);
 extern Oid	get_opclass_family(Oid opclass);
 extern Oid	get_opclass_input_type(Oid opclass);
+extern bool get_opclass_opfamily_and_input_type(Oid opclass,
+									Oid *opfamily, Oid *opcintype);
 extern RegProcedure get_opcode(Oid opno);
 extern char *get_opname(Oid opno);
 extern Oid	get_op_rettype(Oid opno);
@@ -159,6 +161,7 @@ extern void free_attstatsslot(Oid atttype,
 extern char *get_namespace_name(Oid nspid);
 extern char *get_namespace_name_or_temp(Oid nspid);
 extern Oid	get_range_subtype(Oid rangeOid);
+extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
0004-add-kNN-support-to-btree-v01.patchtext/x-patch; name=0004-add-kNN-support-to-btree-v01.patchDownload
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 271c135..480bfd0 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -248,7 +248,7 @@ CREATE INDEX <replaceable>name</replaceable> ON <replaceable>table</replaceable>
   </para>
 
   <para>
-   GiST indexes are also capable of optimizing <quote>nearest-neighbor</>
+   B-tree and GiST indexes are also capable of optimizing <quote>nearest-neighbor</>
    searches, such as
 <programlisting><![CDATA[
 SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 333a36c..6708653 100644
--- a/doc/src/sgml/xindex.sgml
+++ b/doc/src/sgml/xindex.sgml
@@ -1171,7 +1171,7 @@ ALTER OPERATOR FAMILY integer_ops USING btree ADD
   <title>Ordering Operators</title>
 
   <para>
-   Some index access methods (currently, only GiST) support the concept of
+   Some index access methods (currently, only B-tree and GiST) support the concept of
    <firstterm>ordering operators</>.  What we have been discussing so far
    are <firstterm>search operators</>.  A search operator is one for which
    the index can be searched to find all rows satisfying
diff --git a/src/backend/access/nbtree/README b/src/backend/access/nbtree/README
index a3f11da..764c0a9 100644
--- a/src/backend/access/nbtree/README
+++ b/src/backend/access/nbtree/README
@@ -624,6 +624,24 @@ item is irrelevant, and need not be stored at all.  This arrangement
 corresponds to the fact that an L&Y non-leaf page has one more pointer
 than key.
 
+Nearest-neighbor search
+-----------------------
+
+There is a special scan strategy for nearest-neighbor (kNN) search,
+that is used in queries with ORDER BY distance clauses like this:
+SELECT * FROM tab WHERE col > const1 ORDER BY col <-> const2 LIMIT k.
+But, unlike GiST, B-tree supports only a one ordering operator on the
+first index column.
+
+At the beginning of kNN scan, we need to determine which strategy we
+will use --- a special bidirectional or a ordinary unidirectional.
+If the point from which we measure the distance falls into the scan range,
+we use bidirectional scan starting from this point, else we use simple
+unidirectional scan in the right direction.  Algorithm of a bidirectional
+scan is very simple: at each step we advancing scan in that direction,
+which has the nearest point.
+
+
 Notes to Operator Class Implementors
 ------------------------------------
 
@@ -660,6 +678,18 @@ procedure, of course.)
 The other three operators are defined in terms of these two in the obvious way,
 and must act consistently with them.
 
+To implement the distance ordered (nearest-neighbor) search, we only need
+to define a distance operator (usually it called <->) with a correpsonding
+operator family for distance comparison in the index's operator class.
+These operators must satisfy the following assumptions for all non-null
+values A,B,C of the datatype:
+
+	A <-> B = B <-> A								symmetric law
+	if A = B, then A <-> C = B <-> C				distance equivalence
+	if (A <= B and B <= C) or (A >= B and B >= C),
+		then A <-> B <= A <-> C						monotonicity
+
+
 For an operator family supporting multiple datatypes, the above laws must hold
 when A,B,C are taken from any datatypes in the family.  The transitive laws
 are the trickiest to ensure, as in cross-type situations they represent
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index a05f0c2..74641d2 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -23,11 +23,15 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "commands/vacuum.h"
+#include "nodes/primnodes.h"
+#include "nodes/relation.h"
+#include "optimizer/paths.h"
 #include "storage/indexfsm.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"		/* pgrminclude ignore */
+#include "utils/datum.h"
 #include "utils/index_selfuncs.h"
 #include "utils/memutils.h"
 
@@ -75,6 +79,10 @@ static void btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 static void btvacuumpage(BTVacState *vstate, BlockNumber blkno,
 			 BlockNumber orig_blkno);
 
+static Expr *btcanorderbyop(IndexOptInfo *index,
+							PathKey *pathkey, int pathkeyno,
+							Expr *expr, int *indexcol_p);
+
 
 /*
  * Btree handler function: return IndexAmRoutine with access method parameters
@@ -85,7 +93,7 @@ bthandler(PG_FUNCTION_ARGS)
 {
 	IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
 
-	amroutine->amstrategies = BTMaxStrategyNumber;
+	amroutine->amstrategies = BtreeMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
 	amroutine->amcanbackward = true;
@@ -116,7 +124,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = btendscan;
 	amroutine->ammarkpos = btmarkpos;
 	amroutine->amrestrpos = btrestrpos;
-	amroutine->amcanorderbyop = NULL;
+	amroutine->amcanorderbyop = btcanorderbyop;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -300,6 +308,10 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 
 	/* btree indexes are never lossy */
 	scan->xs_recheck = false;
+	scan->xs_recheckorderby = false;
+
+	if (so->scanDirection != NoMovementScanDirection)
+		dir = so->scanDirection;
 
 	/*
 	 * If we have any array keys, initialize them during first call for a
@@ -323,7 +335,8 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(state->currPos))
+		if (!BTScanPosIsValid(state->currPos) &&
+			(!so->knnState || !BTScanPosIsValid(so->knnState->currPos)))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -431,9 +444,6 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	IndexScanDesc scan;
 	BTScanOpaque so;
 
-	/* no order by operators allowed */
-	Assert(norderbys == 0);
-
 	/* get the scan */
 	scan = RelationGetIndexScan(rel, nkeys, norderbys);
 
@@ -460,6 +470,9 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
 	so->state.currTuples = so->state.markTuples = NULL;
+	so->knnState = NULL;
+	so->distanceTypeByVal = true;
+	so->scanDirection = NoMovementScanDirection;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -489,6 +502,8 @@ _bt_release_current_position(BTScanState state, Relation indexRelation,
 static void
 _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	/* No need to invalidate positions, if the RAM is about to be freed. */
 	_bt_release_current_position(state, scan->indexRelation, !free);
 
@@ -505,6 +520,14 @@ _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 	}
 	else
 		BTScanPosInvalidate(state->markPos);
+
+	if (!so->distanceTypeByVal)
+	{
+		pfree(DatumGetPointer(state->currDistance));
+		state->currDistance = PointerGetDatum(NULL);
+		pfree(DatumGetPointer(state->markDistance));
+		state->markDistance = PointerGetDatum(NULL);
+	}
 }
 
 /*
@@ -519,6 +542,13 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 
 	_bt_release_scan_state(scan, state, false);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+		so->knnState = NULL;
+	}
+
 	/*
 	 * Allocate tuple workspace arrays, if needed for an index-only scan and
 	 * not already done in a previous rescan call.  To save on palloc
@@ -548,6 +578,14 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 				scan->numberOfKeys * sizeof(ScanKeyData));
 	so->numberOfKeys = 0;		/* until _bt_preprocess_keys sets it */
 
+	if (orderbys && scan->numberOfOrderBys > 0)
+		memmove(scan->orderByData,
+				orderbys,
+				scan->numberOfOrderBys * sizeof(ScanKeyData));
+
+	so->scanDirection = NoMovementScanDirection;
+	so->distanceTypeByVal = true;
+
 	/* If any keys are SK_SEARCHARRAY type, set up array-key info */
 	_bt_preprocess_array_keys(scan);
 }
@@ -562,6 +600,12 @@ btendscan(IndexScanDesc scan)
 
 	_bt_release_scan_state(scan, &so->state, true);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+	}
+
 	/* Release storage */
 	if (so->keyData != NULL)
 		pfree(so->keyData);
@@ -573,7 +617,7 @@ btendscan(IndexScanDesc scan)
 }
 
 static void
-_bt_mark_current_position(BTScanState state)
+_bt_mark_current_position(BTScanOpaque so, BTScanState state)
 {
 	/* There may be an old mark with a pin (but no lock). */
 	BTScanPosUnpinIfPinned(state->markPos);
@@ -591,6 +635,21 @@ _bt_mark_current_position(BTScanState state)
 		BTScanPosInvalidate(state->markPos);
 		state->markItemIndex = -1;
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->markDistance));
+
+		state->markIsNull = !BTScanPosIsValid(state->currPos) ||
+			state->currIsNull;
+
+		state->markDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->currDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -601,7 +660,13 @@ btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_mark_current_position(&so->state);
+	_bt_mark_current_position(so, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_mark_current_position(so, so->knnState);
+		so->markRightIsNearest = so->currRightIsNearest;
+	}
 
 	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
@@ -611,6 +676,8 @@ btmarkpos(IndexScanDesc scan)
 static void
 _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	if (state->markItemIndex >= 0)
 	{
 		/*
@@ -646,6 +713,19 @@ _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 					   state->markPos.nextTupleOffset);
 		}
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->currDistance));
+
+		state->currIsNull = state->markIsNull;
+		state->currDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->markDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -661,6 +741,12 @@ btrestrpos(IndexScanDesc scan)
 		_bt_restore_array_keys(scan);
 
 	_bt_restore_marked_position(scan, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_restore_marked_position(scan, so->knnState);
+		so->currRightIsNearest = so->markRightIsNearest;
+	}
 }
 
 /*
@@ -1148,3 +1234,26 @@ btcanreturn(Relation index, int attno)
 {
 	return true;
 }
+
+/*
+ *	btcanorderbyop() -- Check whether KNN-search strategy is applicable to
+ *		the given ORDER BY distance operator.
+ */
+static Expr *
+btcanorderbyop(IndexOptInfo *index, PathKey *pathkey, int pathkeyno,
+			   Expr *expr, int *indexcol_p)
+{
+	if (pathkeyno > 1)
+		return NULL; /* only one ORDER BY clause is supported */
+
+	expr = match_clause_to_ordering_op(index,
+									   0, /* ORDER BY distance to the first
+										   * index column is only supported */
+									   expr,
+									   pathkey->pk_opfamily);
+
+	if (expr)
+		*indexcol_p = 0;
+
+	return expr;
+}
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 264d581..4b08d8b 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -561,6 +561,127 @@ _bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 }
 
 /*
+ *  _bt_calc_current_dist() -- Calculate distance from the current item
+ *		of the scan state to the target order-by ScanKey argument.
+ */
+static void
+_bt_calc_current_dist(IndexScanDesc scan, BTScanState state)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanPosItem  *currItem = &state->currPos.items[state->currPos.itemIndex];
+	IndexTuple		itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+	ScanKey			scankey = &scan->orderByData[0];
+	Datum			value;
+
+	value = index_getattr(itup, 1, scan->xs_itupdesc, &state->currIsNull);
+
+	if (state->currIsNull)
+		return; /* NULL distance */
+
+	value = FunctionCall2Coll(&scankey->sk_func,
+							  scankey->sk_collation,
+							  value,
+							  scankey->sk_argument);
+
+	/* free previous distance value for by-ref types */
+	if (!so->distanceTypeByVal)
+		pfree(DatumGetPointer(state->currDistance));
+
+	state->currDistance = value;
+}
+
+/*
+ *  _bt_compare_current_dist() -- Compare current distances of the left and right scan states.
+ *
+ *  NULL distances are considered to be greater than any non-NULL distances.
+ *
+ *  Returns true if right distance is lesser than left, otherwise false.
+ */
+static bool
+_bt_compare_current_dist(BTScanOpaque so, BTScanState rstate, BTScanState lstate)
+{
+	if (lstate->currIsNull)
+		return true; /* non-NULL < NULL */
+
+	if (rstate->currIsNull)
+		return false; /* NULL > non-NULL */
+
+	return DatumGetBool(FunctionCall2Coll(&so->distanceCmpProc,
+										  InvalidOid, /* XXX collation for distance comparison */
+										  rstate->currDistance,
+										  lstate->currDistance));
+}
+
+/*
+ * _bt_init_knn_scan() -- Init additional scan state for KNN search.
+ *
+ * Caller must pin and read-lock scan->state.currPos.buf buffer.
+ *
+ * If empty result was found returned false.
+ * Otherwise prepared current item, and returned true.
+ */
+static bool
+_bt_init_knn_scan(IndexScanDesc scan, OffsetNumber offnum)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		rstate = &so->state; /* right (forward) main scan state */
+	BTScanState		lstate; /* additional left (backward) KNN scan state */
+	Buffer			buf = rstate->currPos.buf;
+	bool			left,
+					right;
+
+	lstate = so->knnState = (BTScanState) palloc(sizeof(BTScanStateData));
+	_bt_allocate_tuple_workspaces(lstate);
+
+	if (!scan->xs_want_itup)
+	{
+		/* We need to request index tuples for distance comparison. */
+		scan->xs_want_itup = true;
+		_bt_allocate_tuple_workspaces(rstate);
+	}
+
+	/* Bump pin and lock count before BTScanPosData copying. */
+	IncrBufferRefCount(buf);
+	LockBuffer(buf, BT_READ);
+
+	memcpy(&lstate->currPos, &rstate->currPos, sizeof(BTScanPosData));
+	lstate->currPos.moreLeft = true;
+	lstate->currPos.moreRight = false;
+
+	BTScanPosInvalidate(lstate->markPos);
+	lstate->markItemIndex = -1;
+	lstate->killedItems = NULL;
+	lstate->numKilled = 0;
+	lstate->currDistance = PointerGetDatum(NULL);
+	lstate->markDistance = PointerGetDatum(NULL);
+
+	/* Load first pages from the both scans. */
+	right = _bt_load_first_page(scan, rstate, ForwardScanDirection, offnum);
+	left = _bt_load_first_page(scan, lstate, BackwardScanDirection,
+							   OffsetNumberPrev(offnum));
+
+	if (!left && !right)
+		return false; /* empty result */
+
+	if (left && right)
+	{
+		/*
+		 * We have found items in both scan directions,
+		 * determine nearest item to return.
+		 */
+		_bt_calc_current_dist(scan, rstate);
+		_bt_calc_current_dist(scan, lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+
+		/* Reset right flag if the left item is nearer. */
+		right = so->currRightIsNearest;
+	}
+
+	/* Return current item of the selected scan direction. */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -663,7 +784,21 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 *----------
 	 */
 	strat_total = BTEqualStrategyNumber;
-	if (so->numberOfKeys > 0)
+
+	if (scan->numberOfOrderBys > 0)
+	{
+		if (_bt_process_orderings(scan, startKeys, &keysCount, notnullkeys))
+			/* use bidirectional KNN scan */
+			strat_total = BtreeKNNSearchStrategyNumber;
+
+		/* use selected KNN scan direction */
+		if (so->scanDirection != NoMovementScanDirection)
+			dir = so->scanDirection;
+	}
+
+	if (so->numberOfKeys > 0 &&
+	/* startKeys for KNN search already have been initialized */
+		strat_total != BtreeKNNSearchStrategyNumber)
 	{
 		AttrNumber	curattr;
 		ScanKey		chosen;
@@ -1003,6 +1138,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			break;
 
 		case BTGreaterEqualStrategyNumber:
+		case BtreeKNNSearchStrategyNumber:
 
 			/*
 			 * Find first item >= scankey.  (This is only used for forward
@@ -1093,16 +1229,21 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	Assert(!BTScanPosIsValid(*currPos));
 	currPos->buf = buf;
 
+	if (strat_total == BtreeKNNSearchStrategyNumber)
+		return _bt_init_knn_scan(scan, offnum);
+
 	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
-		return false;
+		return false; /* empty result */
 
 	/* OK, currPos->itemIndex says what to return */
 	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
- *	Advance to next tuple on current page; or if there's no more,
- *	try to step to the next page with data.
+ *	_bt_next_item() -- Advance to next tuple on current page;
+ *		or if there's no more, try to step to the next page with data.
+ *
+ *	If there are no more matching records in the given direction
  */
 static bool
 _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
@@ -1122,6 +1263,52 @@ _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 }
 
 /*
+ *	_bt_next_nearest() -- Return next nearest item from bidirectional KNN scan.
+ */
+static bool
+_bt_next_nearest(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState rstate = &so->state;
+	BTScanState lstate = so->knnState;
+	bool		right = BTScanPosIsValid(rstate->currPos);
+	bool		left = BTScanPosIsValid(lstate->currPos);
+	bool		advanceRight;
+
+	if (right && left)
+		advanceRight = so->currRightIsNearest;
+	else if (right)
+		advanceRight = true;
+	else if (left)
+		advanceRight = false;
+	else
+		return false; /* end of the scan */
+
+	*(advanceRight ? &right : &left) =
+		_bt_next_item(scan,
+					  advanceRight ? rstate : lstate,
+					  advanceRight ? ForwardScanDirection :
+					  BackwardScanDirection);
+
+	if (!left && !right)
+		return false; /* end of the scan */
+
+	if (left && right)
+	{
+		/*
+		 * If there are items in both scans we must recalculate distance
+		 * in the advanced scan.
+		 */
+		_bt_calc_current_dist(scan, advanceRight ? rstate : lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+		right = so->currRightIsNearest;
+	}
+
+	/* return nearest item */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
  *	_bt_next() -- Get the next item in a scan.
  *
  *		On entry, so->currPos describes the current page, which may be pinned
@@ -1140,6 +1327,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
+	if (so->knnState)
+		/* return next neareset item from KNN scan */
+		return _bt_next_nearest(scan);
+
 	if (!_bt_next_item(scan, &so->state, dir))
 		return false;
 
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index ebcba7e..e5e855e 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -20,11 +20,13 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "catalog/pg_amop.h"
 #include "miscadmin.h"
 #include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 
 
 typedef struct BTSortArrayContext
@@ -2061,6 +2063,34 @@ btproperty(Oid index_oid, int attno,
 			*res = true;
 			return true;
 
+		case AMPROP_DISTANCE_ORDERABLE:
+			{
+				Oid			opclass,
+							opfamily,
+							opcindtype;
+
+				/* answer only for columns, not AM or whole index */
+				if (attno == 0)
+					return false;
+
+				opclass = get_index_column_opclass(index_oid, attno);
+
+				if (!OidIsValid(opclass) ||
+					!get_opclass_opfamily_and_input_type(opclass,
+													 &opfamily, &opcindtype))
+				{
+					*isnull = true;
+					return true;
+				}
+
+				*res = SearchSysCacheExists(AMOPSTRATEGY,
+											ObjectIdGetDatum(opfamily),
+											ObjectIdGetDatum(opcindtype),
+											ObjectIdGetDatum(opcindtype),
+								   Int16GetDatum(BtreeKNNSearchStrategyNumber));
+				return true;
+			}
+
 		default:
 			return false;		/* punt to generic code */
 	}
@@ -2076,3 +2106,242 @@ _bt_allocate_tuple_workspaces(BTScanState state)
 	state->currTuples = (char *) palloc(BLCKSZ * 2);
 	state->markTuples = state->currTuples + BLCKSZ;
 }
+
+static Oid
+_bt_get_sortfamily_for_ordering_key(Relation rel, ScanKey skey)
+{
+	HeapTuple	tp;
+	Form_pg_amop amop_tup;
+	Oid			sortfamily;
+
+	tp = SearchSysCache4(AMOPSTRATEGY,
+						 ObjectIdGetDatum(rel->rd_opfamily[skey->sk_attno - 1]),
+						 ObjectIdGetDatum(rel->rd_opcintype[skey->sk_attno - 1]),
+						 ObjectIdGetDatum(skey->sk_subtype),
+						 Int16GetDatum(skey->sk_strategy));
+	if (!HeapTupleIsValid(tp))
+		return InvalidOid;
+	amop_tup = (Form_pg_amop) GETSTRUCT(tp);
+	sortfamily = amop_tup->amopsortfamily;
+	ReleaseSysCache(tp);
+
+	return sortfamily;
+}
+
+static bool
+_bt_compare_row_key_with_ordering_key(ScanKey row, ScanKey ord, bool *result)
+{
+	ScanKey		subkey = (ScanKey) DatumGetPointer(row->sk_argument);
+	int32		cmpresult;
+
+	Assert(subkey->sk_attno == 1);
+	Assert(subkey->sk_flags & SK_ROW_MEMBER);
+
+	if (subkey->sk_flags & SK_ISNULL)
+		return false;
+
+	/* Perform the test --- three-way comparison not bool operator */
+	cmpresult = DatumGetInt32(FunctionCall2Coll(&subkey->sk_func,
+												subkey->sk_collation,
+												ord->sk_argument,
+												subkey->sk_argument));
+
+	if (subkey->sk_flags & SK_BT_DESC)
+		cmpresult = -cmpresult;
+
+	/*
+	 * At this point cmpresult indicates the overall result of the row
+	 * comparison, and subkey points to the deciding column (or the last
+	 * column if the result is "=").
+	 */
+	switch (subkey->sk_strategy)
+	{
+			/* EQ and NE cases aren't allowed here */
+		case BTLessStrategyNumber:
+			*result = cmpresult < 0;
+			break;
+		case BTLessEqualStrategyNumber:
+			*result = cmpresult <= 0;
+			break;
+		case BTGreaterEqualStrategyNumber:
+			*result = cmpresult >= 0;
+			break;
+		case BTGreaterStrategyNumber:
+			*result = cmpresult > 0;
+			break;
+		default:
+			elog(ERROR, "unrecognized RowCompareType: %d",
+				 (int) subkey->sk_strategy);
+			*result = false;	/* keep compiler quiet */
+	}
+
+	return true;
+}
+
+/* _bt_select_knn_search_strategy() -- Determine which KNN scan strategy to use:
+ *		bidirectional or unidirectional. We are checking here if the
+ *		ordering scankey argument falls into the scan range: if it falls
+ *		we must use bidirectional scan, otherwise we use unidirectional.
+ *
+ *	Returns BtreeKNNSearchStrategyNumber for bidirectional scan or
+ *			strategy number of non-matched scankey for unidirectional.
+ */
+static StrategyNumber
+_bt_select_knn_search_strategy(IndexScanDesc scan, ScanKey ord)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	ScanKey		cond;
+
+	for (cond = so->keyData; cond < so->keyData + so->numberOfKeys; cond++)
+	{
+		bool		result;
+
+		if (cond->sk_attno != 1)
+			break; /* only interesting in the first index attribute */
+
+		if (cond->sk_strategy == BTEqualStrategyNumber)
+			/* always use simple unidirectional scan for equals operators */
+			return BTEqualStrategyNumber;
+
+		if (cond->sk_flags & SK_ROW_HEADER)
+		{
+			if (!_bt_compare_row_key_with_ordering_key(cond, ord, &result))
+				return BTEqualStrategyNumber; /* ROW(fist_index_attr, ...) IS NULL */
+		}
+		else
+		{
+			if (!_bt_compare_scankey_args(scan, cond, ord, cond, &result))
+				elog(ERROR, "could not compare ordering key");
+		}
+
+		if (!result)
+			/*
+			 * Ordering scankey argument is out of scan range,
+			 * use unidirectional scan.
+			 */
+			return cond->sk_strategy;
+	}
+
+	return BtreeKNNSearchStrategyNumber; /* use bidirectional scan */
+}
+
+/*
+ *  _bt_init_distance_comparison() -- Init distance typlen/typbyval and its
+ *  	comparison procedure.
+ */
+static void
+_bt_init_distance_comparison(IndexScanDesc scan, ScanKey ord)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	RegProcedure opcode;
+	Oid			sortfamily;
+	Oid			opno;
+	Oid			distanceType;
+
+	distanceType = get_func_rettype(ord->sk_func.fn_oid);
+
+	sortfamily = _bt_get_sortfamily_for_ordering_key(scan->indexRelation, ord);
+
+	if (!OidIsValid(sortfamily))
+		elog(ERROR, "could not find sort family for btree ordering operator");
+
+	opno = get_opfamily_member(sortfamily,
+							   distanceType,
+							   distanceType,
+							   BTLessEqualStrategyNumber);
+
+	if (!OidIsValid(opno))
+		elog(ERROR, "could not find operator for btree distance comparison");
+
+	opcode = get_opcode(opno);
+
+	if (!RegProcedureIsValid(opcode))
+		elog(ERROR,
+		  "could not find procedure for btree distance comparison operator");
+
+	fmgr_info(opcode, &so->distanceCmpProc);
+
+	get_typlenbyval(distanceType, &so->distanceTypeLen, &so->distanceTypeByVal);
+
+	if (!so->distanceTypeByVal)
+	{
+		so->state.currDistance = PointerGetDatum(NULL);
+		so->state.markDistance = PointerGetDatum(NULL);
+	}
+}
+
+/*
+ * _bt_process_orderings() -- Process ORDER BY distance scankeys and
+ *		select corresponding KNN strategy.
+ *
+ * If bidirectional scan is selected then one scankey is initialized
+ * using bufKeys and placed into startKeys/keysCount, true is returned.
+ *
+ * Otherwise, so->scanDirection is set and false is returned.
+ */
+bool
+_bt_process_orderings(IndexScanDesc scan, ScanKey *startKeys, int *keysCount,
+					  ScanKeyData bufKeys[])
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	ScanKey		ord = scan->orderByData;
+
+	if (scan->numberOfOrderBys > 1 || ord->sk_attno != 1)
+		/* it should not happen, see btcanorderbyop() */
+		elog(ERROR, "only one btree ordering operator "
+					"for the first index column is supported");
+
+	Assert(ord->sk_strategy == BtreeKNNSearchStrategyNumber);
+
+	switch (_bt_select_knn_search_strategy(scan, ord))
+	{
+		case BTLessStrategyNumber:
+		case BTLessEqualStrategyNumber:
+			/*
+			 * Ordering key argument is greater than all values in scan range.
+			 * select backward scan direction.
+			 */
+			so->scanDirection = BackwardScanDirection;
+			return false;
+
+		case BTEqualStrategyNumber:
+			/* Use default unidirectional scan direction. */
+			return false;
+
+		case BTGreaterEqualStrategyNumber:
+		case BTGreaterStrategyNumber:
+			/*
+			 * Ordering key argument is lesser than all values in scan range.
+			 * select forward scan direction.
+			 */
+			so->scanDirection = ForwardScanDirection;
+			return false;
+
+		case BtreeKNNSearchStrategyNumber:
+			/*
+			 * Ordering key argument falls into scan range,
+			 * use bidirectional scan.
+			 */
+			break;
+	}
+
+	_bt_init_distance_comparison(scan, ord);
+
+	/* Init btree search key with ordering key argument. */
+	ScanKeyEntryInitialize(&bufKeys[0],
+					 (scan->indexRelation->rd_indoption[ord->sk_attno - 1] <<
+					  SK_BT_INDOPTION_SHIFT) |
+						   SK_ORDER_BY |
+						   SK_SEARCHNULL /* only for invalid procedure oid, see
+										  * assert in ScanKeyEntryInitialize */,
+						   ord->sk_attno,
+						   BtreeKNNSearchStrategyNumber,
+						   ord->sk_subtype,
+						   ord->sk_collation,
+						   InvalidOid,
+						   ord->sk_argument);
+
+	startKeys[(*keysCount)++] = &bufKeys[0];
+
+	return true;
+}
diff --git a/src/backend/access/nbtree/nbtvalidate.c b/src/backend/access/nbtree/nbtvalidate.c
index 7b443f2..a06b697 100644
--- a/src/backend/access/nbtree/nbtvalidate.c
+++ b/src/backend/access/nbtree/nbtvalidate.c
@@ -22,8 +22,16 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_type.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
+#define BTRequiredOperatorSet \
+		((1 << BTLessStrategyNumber) | \
+		 (1 << BTLessEqualStrategyNumber) | \
+		 (1 << BTEqualStrategyNumber) | \
+		 (1 << BTGreaterEqualStrategyNumber) | \
+		 (1 << BTGreaterStrategyNumber))
+
 
 /*
  * Validator for a btree opclass.
@@ -122,10 +130,11 @@ btvalidate(Oid opclassoid)
 	{
 		HeapTuple	oprtup = &oprlist->members[i]->tuple;
 		Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
+		Oid			op_rettype;
 
 		/* Check that only allowed strategy numbers exist */
 		if (oprform->amopstrategy < 1 ||
-			oprform->amopstrategy > BTMaxStrategyNumber)
+			oprform->amopstrategy > BtreeMaxStrategyNumber)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -136,20 +145,29 @@ btvalidate(Oid opclassoid)
 			result = false;
 		}
 
-		/* btree doesn't support ORDER BY operators */
-		if (oprform->amoppurpose != AMOP_SEARCH ||
-			OidIsValid(oprform->amopsortfamily))
+		/* btree supports ORDER BY operators */
+		if (oprform->amoppurpose != AMOP_SEARCH)
 		{
-			ereport(INFO,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("btree operator family \"%s\" contains invalid ORDER BY specification for operator %s",
-							opfamilyname,
-							format_operator(oprform->amopopr))));
-			result = false;
+			/* ... and operator result must match the claimed btree opfamily */
+			op_rettype = get_op_rettype(oprform->amopopr);
+			if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype))
+			{
+				ereport(INFO,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("btree operator family %s contains invalid ORDER BY specification for operator %s",
+								opfamilyname,
+								format_operator(oprform->amopopr))));
+				result = false;
+			}
+		}
+		else
+		{
+			/* Search operators must always return bool */
+			op_rettype = BOOLOID;
 		}
 
 		/* Check operator signature --- same for all btree strategies */
-		if (!check_amop_signature(oprform->amopopr, BOOLOID,
+		if (!check_amop_signature(oprform->amopopr, op_rettype,
 								  oprform->amoplefttype,
 								  oprform->amoprighttype))
 		{
@@ -188,12 +206,8 @@ btvalidate(Oid opclassoid)
 		 * or support functions for this datatype pair.  The only thing that
 		 * is considered optional is the sortsupport function.
 		 */
-		if (thisgroup->operatorset !=
-			((1 << BTLessStrategyNumber) |
-			 (1 << BTLessEqualStrategyNumber) |
-			 (1 << BTEqualStrategyNumber) |
-			 (1 << BTGreaterEqualStrategyNumber) |
-			 (1 << BTGreaterStrategyNumber)))
+		if ((thisgroup->operatorset & BTRequiredOperatorSet) !=
+			BTRequiredOperatorSet)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index b9b4701..9ebe29b 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -982,6 +982,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	 * if we are only trying to build bitmap indexscans, nor if we have to
 	 * assume the scan is unordered.
 	 */
+	useful_pathkeys = NIL;
+	orderbyclauses = NIL;
+	orderbyclausecols = NIL;
+
 	pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN &&
 								!found_lower_saop_clause &&
 								has_useful_pathkeys(root, rel));
@@ -992,10 +996,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 											  ForwardScanDirection);
 		useful_pathkeys = truncate_useless_pathkeys(root, rel,
 													index_pathkeys);
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
 	}
-	else if (index->amcanorderbyop && pathkeys_possibly_useful)
+
+	if (useful_pathkeys == NIL &&
+		index->amcanorderbyop && pathkeys_possibly_useful)
 	{
 		/* see if we can generate ordering operators for query_pathkeys */
 		match_pathkeys_to_index(index, root->query_pathkeys,
@@ -1006,12 +1010,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		else
 			useful_pathkeys = NIL;
 	}
-	else
-	{
-		useful_pathkeys = NIL;
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
-	}
 
 	/*
 	 * 3. Check if an index-only scan is possible.  If we're not building
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 4427bd3..f2bd19f 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -624,6 +624,12 @@ typedef struct BTScanStateData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+
+	/* KNN-search fields: */
+	Datum		currDistance;	/* current distance */
+	Datum		markDistance;	/* marked distance */
+	bool		currIsNull;		/* current item is NULL */
+	bool		markIsNull;		/* marked item is NULL */
 }	BTScanStateData, *BTScanState;
 
 typedef struct BTScanOpaqueData
@@ -640,7 +646,17 @@ typedef struct BTScanOpaqueData
 	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
 	MemoryContext arrayContext; /* scan-lifespan context for array data */
 
-	BTScanStateData	state;
+	BTScanStateData state;		/* main scan state */
+
+	/* KNN-search fields: */
+	BTScanState knnState;			/* optional scan state for KNN search */
+	ScanDirection scanDirection;	/* selected scan direction for
+									 * unidirectional KNN scan */
+	FmgrInfo	distanceCmpProc;	/* distance comparison procedure */
+	int16		distanceTypeLen;	/* distance typlen */
+	bool		distanceTypeByVal;	/* distance typebyval */
+	bool		currRightIsNearest;	/* current right item is nearest */
+	bool		markRightIsNearest;	/* marked right item is nearest */
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -757,6 +773,8 @@ extern bool btproperty(Oid index_oid, int attno,
 		   IndexAMProperty prop, const char *propname,
 		   bool *res, bool *isnull);
 extern void _bt_allocate_tuple_workspaces(BTScanState state);
+extern bool _bt_process_orderings(IndexScanDesc scan,
+				  ScanKey *startKeys, int *keysCount, ScanKeyData bufKeys[]);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/include/access/stratnum.h b/src/include/access/stratnum.h
index 489e5c5..0852f8a 100644
--- a/src/include/access/stratnum.h
+++ b/src/include/access/stratnum.h
@@ -32,7 +32,10 @@ typedef uint16 StrategyNumber;
 #define BTGreaterEqualStrategyNumber	4
 #define BTGreaterStrategyNumber			5
 
-#define BTMaxStrategyNumber				5
+#define BTMaxStrategyNumber				5		/* number of canonical B-tree strategies */
+
+#define BtreeKNNSearchStrategyNumber	6		/* for <-> (distance) */
+#define BtreeMaxStrategyNumber			6		/* number of extended B-tree strategies */
 
 
 /*
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index b01be59..b03374e 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -347,10 +347,10 @@ ROLLBACK;
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
 ERROR:  access method "invalid_index_method" does not exist
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 6, must be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 0, must be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 7, must be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 0, must be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ERROR:  operator argument types must be specified in ALTER OPERATOR FAMILY
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -400,7 +400,6 @@ DROP OPERATOR FAMILY alt_opf9 USING gist;
 -- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
-ERROR:  access method "btree" does not support ordering operators
 DROP OPERATOR FAMILY alt_opf10 USING btree;
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index 74f7c9f..55296b7 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -24,7 +24,7 @@ select prop,
  nulls_first        |    |       | f
  nulls_last         |    |       | t
  orderable          |    |       | t
- distance_orderable |    |       | f
+ distance_orderable |    |       | t
  returnable         |    |       | t
  search_array       |    |       | t
  search_nulls       |    |       | t
@@ -97,7 +97,7 @@ select prop,
  nulls_first        | f     | f    | f    | f      | f   | f
  nulls_last         | t     | f    | f    | f      | f   | f
  orderable          | t     | f    | f    | f      | f   | f
- distance_orderable | f     | f    | t    | f      | f   | f
+ distance_orderable | t     | f    | t    | f      | f   | f
  returnable         | t     | f    | f    | t      | f   | f
  search_array       | t     | f    | f    | f      | f   | f
  search_nulls       | t     | f    | t    | t      | f   | t
diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql
index c9ea479..53cdda0 100644
--- a/src/test/regress/sql/alter_generic.sql
+++ b/src/test/regress/sql/alter_generic.sql
@@ -295,8 +295,8 @@ ROLLBACK;
 -- Should fail. Invalid values for ALTER OPERATOR FAMILY .. ADD / DROP
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 6 btint42cmp(int4, int2); -- function number should be between 1 and 5
0005-add-distance-operators-v01.patchtext/x-patch; name=0005-add-distance-operators-v01.patchDownload
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index a146b0a..1247d8e 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -30,6 +30,7 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+#define SAMESIGN(a,b)	(((a) < 0) == ((b) < 0))
 
 /*************************************************************************
  * Private routines
@@ -1157,3 +1158,23 @@ int8_cash(PG_FUNCTION_ARGS)
 
 	PG_RETURN_CASH(result);
 }
+
+Datum
+cash_dist(PG_FUNCTION_ARGS)
+{
+	Cash		a = PG_GETARG_CASH(0);
+	Cash		b = PG_GETARG_CASH(1);
+	Cash		r;
+	Cash		ra;
+
+	r = a - b;
+	ra = Abs(r);
+
+	/* Overflow check. */
+	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("money out of range")));
+
+	PG_RETURN_CASH(ra);
+}
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 96cfacd..a94a0df 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -561,6 +561,17 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+Datum
+date_dist(PG_FUNCTION_ARGS)
+{
+	/* we assume the difference can't overflow */
+	Datum		diff = DirectFunctionCall2(date_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
+}
+
 /*
  * Internal routines for promoting date to timestamp and timestamp with
  * time zone
@@ -2061,6 +2072,16 @@ time_part(PG_FUNCTION_ARGS)
 	PG_RETURN_FLOAT8(result);
 }
 
+Datum
+time_dist(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(time_mi_time,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
+
 
 /*****************************************************************************
  *	 Time With Time Zone ADT
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 86b46de..4c023f5 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -3585,6 +3585,32 @@ width_bucket_float8(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(result);
 }
 
+Datum
+float4_dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float4		r;
+
+	r = a - b;
+	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
+
+	PG_RETURN_FLOAT4(Abs(r));
+}
+
+Datum
+float8_dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r;
+
+	r = a - b;
+	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
 /* ========== PRIVATE ROUTINES ========== */
 
 #ifndef HAVE_CBRT
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index bd4422e..43dcf40 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -1395,3 +1395,43 @@ generate_series_step_int4(PG_FUNCTION_ARGS)
 		/* do when there is no more left */
 		SRF_RETURN_DONE(funcctx);
 }
+
+Datum
+int2_dist(PG_FUNCTION_ARGS)
+{
+	int16		a = PG_GETARG_INT16(0);
+	int16		b = PG_GETARG_INT16(1);
+	int16		r;
+	int16		ra;
+
+	r = a - b;
+	ra = Abs(r);
+
+	/* Overflow check. */
+	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("smallint out of range")));
+
+	PG_RETURN_INT16(ra);
+}
+
+Datum
+int4_dist(PG_FUNCTION_ARGS)
+{
+	int32		a = PG_GETARG_INT32(0);
+	int32		b = PG_GETARG_INT32(1);
+	int32		r;
+	int32		ra;
+
+	r = a - b;
+	ra = Abs(r);
+
+	/* Overflow check. */
+	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("integer out of range")));
+
+	PG_RETURN_INT32(ra);
+}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index b7aa0ad..f61d5a9 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1508,3 +1508,23 @@ generate_series_step_int8(PG_FUNCTION_ARGS)
 		/* do when there is no more left */
 		SRF_RETURN_DONE(funcctx);
 }
+
+Datum
+int8_dist(PG_FUNCTION_ARGS)
+{
+	int64		a = PG_GETARG_INT64(0);
+	int64		b = PG_GETARG_INT64(1);
+	int64		r;
+	int64		ra;
+
+	r = a - b;
+	ra = Abs(r);
+
+	/* Overflow check. */
+	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("bigint out of range")));
+
+	PG_RETURN_INT64(ra);
+}
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index fd12382..0640c65 100644
--- a/src/backend/utils/adt/oid.c
+++ b/src/backend/utils/adt/oid.c
@@ -452,3 +452,17 @@ oidvectorgt(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(cmp > 0);
 }
+
+Datum
+oid_dist(PG_FUNCTION_ARGS)
+{
+	Oid			a = PG_GETARG_OID(0);
+	Oid			b = PG_GETARG_OID(1);
+	Oid			res;
+
+	if (a < b)
+		res = b - a;
+	else
+		res = a - b;
+	PG_RETURN_OID(res);
+}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index a87f982..9ecaba6 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -2922,6 +2922,62 @@ timestamp_mi(PG_FUNCTION_ARGS)
 	PG_RETURN_INTERVAL_P(result);
 }
 
+Datum
+ts_dist(PG_FUNCTION_ARGS)
+{
+	Timestamp	a = PG_GETARG_TIMESTAMP(0);
+	Timestamp	b = PG_GETARG_TIMESTAMP(1);
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		Interval   *p = palloc(sizeof(Interval));
+
+		p->day = INT_MAX;
+		p->month = INT_MAX;
+#ifdef HAVE_INT64_TIMESTAMP
+		p->time = PG_INT64_MAX;
+#else
+		p->time = DBL_MAX;
+#endif
+		PG_RETURN_INTERVAL_P(p);
+	}
+	else
+		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+												  PG_GETARG_DATUM(0),
+												  PG_GETARG_DATUM(1)));
+	PG_RETURN_INTERVAL_P(abs_interval(r));
+}
+
+
+Datum
+tstz_dist(PG_FUNCTION_ARGS)
+{
+	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
+	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		Interval   *p = palloc(sizeof(Interval));
+
+		p->day = INT_MAX;
+		p->month = INT_MAX;
+#ifdef HAVE_INT64_TIMESTAMP
+		p->time = PG_INT64_MAX;
+#else
+		p->time = DBL_MAX;
+#endif
+		PG_RETURN_INTERVAL_P(p);
+	}
+
+	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+											  PG_GETARG_DATUM(0),
+											  PG_GETARG_DATUM(1)));
+	PG_RETURN_INTERVAL_P(abs_interval(r));
+}
+
+
 /*
  *	interval_justify_interval()
  *
@@ -3725,6 +3781,29 @@ interval_avg(PG_FUNCTION_ARGS)
 							   Float8GetDatum((double) N.time));
 }
 
+Interval *
+abs_interval(Interval *a)
+{
+	static Interval zero = {0, 0, 0};
+
+	if (DatumGetBool(DirectFunctionCall2(interval_lt,
+										 IntervalPGetDatum(a),
+										 IntervalPGetDatum(&zero))))
+		a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
+												  IntervalPGetDatum(a)));
+
+	return a;
+}
+
+Datum
+interval_dist(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(interval_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
 
 /* timestamp_age()
  * Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index 0251664..44e2caf 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -105,6 +105,7 @@ DATA(insert (	1976   21 21 2 s	522 403 0 ));
 DATA(insert (	1976   21 21 3 s	94	403 0 ));
 DATA(insert (	1976   21 21 4 s	524 403 0 ));
 DATA(insert (	1976   21 21 5 s	520 403 0 ));
+DATA(insert (	1976   21 21 6 o	3365 403 1976 ));
 /* crosstype operators int24 */
 DATA(insert (	1976   21 23 1 s	534 403 0 ));
 DATA(insert (	1976   21 23 2 s	540 403 0 ));
@@ -123,6 +124,7 @@ DATA(insert (	1976   23 23 2 s	523 403 0 ));
 DATA(insert (	1976   23 23 3 s	96	403 0 ));
 DATA(insert (	1976   23 23 4 s	525 403 0 ));
 DATA(insert (	1976   23 23 5 s	521 403 0 ));
+DATA(insert (	1976   23 23 6 o	3366 403 1976 ));
 /* crosstype operators int42 */
 DATA(insert (	1976   23 21 1 s	535 403 0 ));
 DATA(insert (	1976   23 21 2 s	541 403 0 ));
@@ -141,6 +143,7 @@ DATA(insert (	1976   20 20 2 s	414 403 0 ));
 DATA(insert (	1976   20 20 3 s	410 403 0 ));
 DATA(insert (	1976   20 20 4 s	415 403 0 ));
 DATA(insert (	1976   20 20 5 s	413 403 0 ));
+DATA(insert (	1976   20 20 6 o	3367 403 1976 ));
 /* crosstype operators int82 */
 DATA(insert (	1976   20 21 1 s	1870	403 0 ));
 DATA(insert (	1976   20 21 2 s	1872	403 0 ));
@@ -163,6 +166,7 @@ DATA(insert (	1989   26 26 2 s	611 403 0 ));
 DATA(insert (	1989   26 26 3 s	607 403 0 ));
 DATA(insert (	1989   26 26 4 s	612 403 0 ));
 DATA(insert (	1989   26 26 5 s	610 403 0 ));
+DATA(insert (	1989   26 26 6 o	3368 403 1989 ));
 
 /*
  * btree tid_ops
@@ -194,6 +198,7 @@ DATA(insert (	1970   700 700 2 s	624 403 0 ));
 DATA(insert (	1970   700 700 3 s	620 403 0 ));
 DATA(insert (	1970   700 700 4 s	625 403 0 ));
 DATA(insert (	1970   700 700 5 s	623 403 0 ));
+DATA(insert (	1970   700 700 6 o	3369 403 1970 ));
 /* crosstype operators float48 */
 DATA(insert (	1970   700 701 1 s	1122 403 0 ));
 DATA(insert (	1970   700 701 2 s	1124 403 0 ));
@@ -206,6 +211,7 @@ DATA(insert (	1970   701 701 2 s	673 403 0 ));
 DATA(insert (	1970   701 701 3 s	670 403 0 ));
 DATA(insert (	1970   701 701 4 s	675 403 0 ));
 DATA(insert (	1970   701 701 5 s	674 403 0 ));
+DATA(insert (	1970   701 701 6 o	3370 403 1970 ));
 /* crosstype operators float84 */
 DATA(insert (	1970   701 700 1 s	1132 403 0 ));
 DATA(insert (	1970   701 700 2 s	1134 403 0 ));
@@ -283,6 +289,7 @@ DATA(insert (	434   1082 1082 2 s 1096	403 0 ));
 DATA(insert (	434   1082 1082 3 s 1093	403 0 ));
 DATA(insert (	434   1082 1082 4 s 1098	403 0 ));
 DATA(insert (	434   1082 1082 5 s 1097	403 0 ));
+DATA(insert (	434   1082 1082 6 o 3372	403 1976 ));
 /* crosstype operators vs timestamp */
 DATA(insert (	434   1082 1114 1 s 2345	403 0 ));
 DATA(insert (	434   1082 1114 2 s 2346	403 0 ));
@@ -301,6 +308,7 @@ DATA(insert (	434   1114 1114 2 s 2063	403 0 ));
 DATA(insert (	434   1114 1114 3 s 2060	403 0 ));
 DATA(insert (	434   1114 1114 4 s 2065	403 0 ));
 DATA(insert (	434   1114 1114 5 s 2064	403 0 ));
+DATA(insert (	434   1114 1114 6 o 3374	403 1982 ));
 /* crosstype operators vs date */
 DATA(insert (	434   1114 1082 1 s 2371	403 0 ));
 DATA(insert (	434   1114 1082 2 s 2372	403 0 ));
@@ -319,6 +327,7 @@ DATA(insert (	434   1184 1184 2 s 1323	403 0 ));
 DATA(insert (	434   1184 1184 3 s 1320	403 0 ));
 DATA(insert (	434   1184 1184 4 s 1325	403 0 ));
 DATA(insert (	434   1184 1184 5 s 1324	403 0 ));
+DATA(insert (	434   1184 1184 6 o 3375	403 1982 ));
 /* crosstype operators vs date */
 DATA(insert (	434   1184 1082 1 s 2384	403 0 ));
 DATA(insert (	434   1184 1082 2 s 2385	403 0 ));
@@ -341,6 +350,7 @@ DATA(insert (	1996   1083 1083 2 s 1111 403 0 ));
 DATA(insert (	1996   1083 1083 3 s 1108 403 0 ));
 DATA(insert (	1996   1083 1083 4 s 1113 403 0 ));
 DATA(insert (	1996   1083 1083 5 s 1112 403 0 ));
+DATA(insert (	1996   1083 1083 6 o 3373 403 1982 ));
 
 /*
  *	btree timetz_ops
@@ -361,6 +371,7 @@ DATA(insert (	1982   1186 1186 2 s 1333 403 0 ));
 DATA(insert (	1982   1186 1186 3 s 1330 403 0 ));
 DATA(insert (	1982   1186 1186 4 s 1335 403 0 ));
 DATA(insert (	1982   1186 1186 5 s 1334 403 0 ));
+DATA(insert (	1982   1186 1186 6 o 3376 403 1982 ));
 
 /*
  *	btree macaddr
@@ -451,6 +462,7 @@ DATA(insert (	2099   790 790 2 s	904 403 0 ));
 DATA(insert (	2099   790 790 3 s	900 403 0 ));
 DATA(insert (	2099   790 790 4 s	905 403 0 ));
 DATA(insert (	2099   790 790 5 s	903 403 0 ));
+DATA(insert (	2099   790 790 6 o	3371 403 2099 ));
 
 /*
  *	btree reltime_ops
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index aeb7927..5a7e6b2 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1618,6 +1618,32 @@ DESCR("greater than or equal");
 DATA(insert OID = 3228 (  "-"	   PGNSP PGUID b f f 3220 3220 1700    0	0 pg_lsn_mi - - ));
 DESCR("minus");
 
+/* distance operators */
+DATA(insert OID = 3365 (  "<->"    PGNSP PGUID b f f   21	21	 21 3365 0 int2_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3366 (  "<->"    PGNSP PGUID b f f   23	23	 23 3366 0 int4_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3367 (  "<->"    PGNSP PGUID b f f   20	20	 20 3367 0 int8_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3368 ( "<->"	   PGNSP PGUID b f f   26	26	 26 3368 0 oid_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3369 (  "<->"    PGNSP PGUID b f f  700  700	700 3369 0 float4_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3370 (  "<->"    PGNSP PGUID b f f  701  701	701 3370 0 float8_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3371 ( "<->"	   PGNSP PGUID b f f  790  790	790 3371 0 cash_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3372 ( "<->"	   PGNSP PGUID b f f 1082 1082	 23 3372 0 date_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3373 ( "<->"	   PGNSP PGUID b f f 1083 1083 1186 3373 0 time_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3374 ( "<->"	   PGNSP PGUID b f f 1114 1114 1186 3374 0 ts_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3375 ( "<->"	   PGNSP PGUID b f f 1184 1184 1186 3375 0 tstz_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3376 ( "<->"	   PGNSP PGUID b f f 1186 1186 1186 3376 0 interval_dist - - ));
+DESCR("distance between");
+
 /* enum operators */
 DATA(insert OID = 3516 (  "="	   PGNSP PGUID b t t 3500 3500 16 3516 3517 enum_eq eqsel eqjoinsel ));
 DESCR("equal");
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 37e022d..9451b84 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5345,6 +5345,21 @@ DESCR("pg_controldata recovery state information as a function");
 DATA(insert OID = 3444 ( pg_control_init PGNSP PGUID 12 1 0 0 0 f f f f t f v s 0 0 2249 "" "{23,23,23,23,23,23,23,23,23,16,16,16,23}" "{o,o,o,o,o,o,o,o,o,o,o,o,o}" "{max_data_alignment,database_block_size,blocks_per_segment,wal_block_size,bytes_per_wal_segment,max_identifier_length,max_index_columns,max_toast_chunk_size,large_object_chunk_size,bigint_timestamps,float4_pass_by_value,float8_pass_by_value,data_page_checksum_version}" _null_ _null_ pg_control_init _null_ _null_ _null_ ));
 DESCR("pg_controldata init state information as a function");
 
+/* distance functions */
+DATA(insert OID = 3353 ( int2_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	 21 "21 21"		_null_ _null_ _null_ _null_ _null_	int2_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3354 ( int4_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	 23 "23 23"		_null_ _null_ _null_ _null_ _null_	int4_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3355 ( int8_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	 20 "20 20"		_null_ _null_ _null_ _null_ _null_	int8_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3356 ( oid_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	 26 "26 26"		_null_ _null_ _null_ _null_ _null_	oid_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3357 ( float4_dist	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	700 "700 700"	_null_ _null_ _null_ _null_ _null_	float4_dist _null_ _null_ _null_ ));
+DATA(insert OID = 3358 ( float8_dist	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	701 "701 701"	_null_ _null_ _null_ _null_ _null_	float8_dist _null_ _null_ _null_ ));
+DATA(insert OID = 3359 ( cash_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	790 "790 790"	_null_ _null_ _null_ _null_ _null_	cash_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3360 ( date_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	 23 "1082 1082" _null_ _null_ _null_ _null_ _null_	date_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3361 ( time_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 1186 "1083 1083" _null_ _null_ _null_ _null_ _null_	time_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3362 ( ts_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 1186 "1114 1114" _null_ _null_ _null_ _null_ _null_	ts_dist		_null_ _null_ _null_ ));
+DATA(insert OID = 3363 ( tstz_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 1186 "1184 1184" _null_ _null_ _null_ _null_ _null_	tstz_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3364 ( interval_dist	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_ _null_	interval_dist _null_ _null_ _null_ ));
+
+
 /*
  * Symbolic values for provolatile column: these indicate whether the result
  * of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index e1bb344..ed61bb8 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -253,6 +253,8 @@ extern Datum int2larger(PG_FUNCTION_ARGS);
 extern Datum int2smaller(PG_FUNCTION_ARGS);
 extern Datum int4larger(PG_FUNCTION_ARGS);
 extern Datum int4smaller(PG_FUNCTION_ARGS);
+extern Datum int2_dist(PG_FUNCTION_ARGS);
+extern Datum int4_dist(PG_FUNCTION_ARGS);
 
 extern Datum int4and(PG_FUNCTION_ARGS);
 extern Datum int4or(PG_FUNCTION_ARGS);
@@ -391,6 +393,8 @@ extern Datum float8lt(PG_FUNCTION_ARGS);
 extern Datum float8le(PG_FUNCTION_ARGS);
 extern Datum float8gt(PG_FUNCTION_ARGS);
 extern Datum float8ge(PG_FUNCTION_ARGS);
+extern Datum float4_dist(PG_FUNCTION_ARGS);
+extern Datum float8_dist(PG_FUNCTION_ARGS);
 extern Datum ftod(PG_FUNCTION_ARGS);
 extern Datum i4tod(PG_FUNCTION_ARGS);
 extern Datum i2tod(PG_FUNCTION_ARGS);
@@ -546,6 +550,7 @@ extern Datum oidvectorlt(PG_FUNCTION_ARGS);
 extern Datum oidvectorle(PG_FUNCTION_ARGS);
 extern Datum oidvectorge(PG_FUNCTION_ARGS);
 extern Datum oidvectorgt(PG_FUNCTION_ARGS);
+extern Datum oid_dist(PG_FUNCTION_ARGS);
 extern oidvector *buildoidvector(const Oid *oids, int n);
 extern Oid	oidparse(Node *node);
 
diff --git a/src/include/utils/cash.h b/src/include/utils/cash.h
index 3a491f9..9d609b2 100644
--- a/src/include/utils/cash.h
+++ b/src/include/utils/cash.h
@@ -70,4 +70,6 @@ extern Datum numeric_cash(PG_FUNCTION_ARGS);
 extern Datum int4_cash(PG_FUNCTION_ARGS);
 extern Datum int8_cash(PG_FUNCTION_ARGS);
 
+extern Datum cash_dist(PG_FUNCTION_ARGS);
+
 #endif   /* CASH_H */
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index b5cc6f3..d56c3c1 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -115,6 +115,7 @@ extern Datum date_smaller(PG_FUNCTION_ARGS);
 extern Datum date_mi(PG_FUNCTION_ARGS);
 extern Datum date_pli(PG_FUNCTION_ARGS);
 extern Datum date_mii(PG_FUNCTION_ARGS);
+extern Datum date_dist(PG_FUNCTION_ARGS);
 extern Datum date_eq_timestamp(PG_FUNCTION_ARGS);
 extern Datum date_ne_timestamp(PG_FUNCTION_ARGS);
 extern Datum date_lt_timestamp(PG_FUNCTION_ARGS);
@@ -180,6 +181,7 @@ extern Datum interval_time(PG_FUNCTION_ARGS);
 extern Datum time_pl_interval(PG_FUNCTION_ARGS);
 extern Datum time_mi_interval(PG_FUNCTION_ARGS);
 extern Datum time_part(PG_FUNCTION_ARGS);
+extern Datum time_dist(PG_FUNCTION_ARGS);
 
 extern Datum timetz_in(PG_FUNCTION_ARGS);
 extern Datum timetz_out(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index a7cc2c5..fff7e1d 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -349,4 +349,6 @@ extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 extern Datum pg_timezone_abbrevs(PG_FUNCTION_ARGS);
 extern Datum pg_timezone_names(PG_FUNCTION_ARGS);
 
+extern Interval *abs_interval(Interval *a);
+
 #endif   /* DATETIME_H */
diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h
index 627505e..163f0fd 100644
--- a/src/include/utils/int8.h
+++ b/src/include/utils/int8.h
@@ -126,4 +126,6 @@ extern Datum oidtoi8(PG_FUNCTION_ARGS);
 extern Datum generate_series_int8(PG_FUNCTION_ARGS);
 extern Datum generate_series_step_int8(PG_FUNCTION_ARGS);
 
+extern Datum int8_dist(PG_FUNCTION_ARGS);
+
 #endif   /* INT8_H */
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 3ec8ecd..32b8842 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -213,6 +213,10 @@ extern Datum pg_conf_load_time(PG_FUNCTION_ARGS);
 extern Datum generate_series_timestamp(PG_FUNCTION_ARGS);
 extern Datum generate_series_timestamptz(PG_FUNCTION_ARGS);
 
+extern Datum ts_dist(PG_FUNCTION_ARGS);
+extern Datum tstz_dist(PG_FUNCTION_ARGS);
+extern Datum interval_dist(PG_FUNCTION_ARGS);
+
 /* Internal routines (not fmgr-callable) */
 
 extern int32 anytimestamp_typmod_check(bool istz, int32 typmod);
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 0bcec13..fb44ae8 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1721,6 +1721,7 @@ ORDER BY 1, 2, 3;
         403 |            5 | *>
         403 |            5 | >
         403 |            5 | ~>~
+        403 |            6 | <->
         405 |            1 | =
         783 |            1 | <<
         783 |            1 | @@
@@ -1828,7 +1829,7 @@ ORDER BY 1, 2, 3;
        4000 |           25 | <<=
        4000 |           26 | >>
        4000 |           27 | >>=
-(121 rows)
+(122 rows)
 
 -- Check that all opclass search operators have selectivity estimators.
 -- This is not absolutely required, but it seems a reasonable thing
0006-remove-distance-operators-from-btree_gist-v01.patchtext/x-patch; name=0006-remove-distance-operators-from-btree_gist-v01.patchDownload
diff --git a/contrib/btree_gist/Makefile b/contrib/btree_gist/Makefile
index d36f517..c650581 100644
--- a/contrib/btree_gist/Makefile
+++ b/contrib/btree_gist/Makefile
@@ -10,7 +10,8 @@ OBJS =  btree_gist.o btree_utils_num.o btree_utils_var.o btree_int2.o \
 
 EXTENSION = btree_gist
 DATA = btree_gist--unpackaged--1.0.sql btree_gist--1.0--1.1.sql \
-       btree_gist--1.1--1.2.sql btree_gist--1.2.sql btree_gist--1.2--1.3.sql
+       btree_gist--1.1--1.2.sql btree_gist--1.2--1.3.sql \
+       btree_gist--1.3--1.4.sql btree_gist--1.4.sql
 PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes"
 
 REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \
diff --git a/contrib/btree_gist/btree_cash.c b/contrib/btree_gist/btree_cash.c
index aa14735..6ebeda1 100644
--- a/contrib/btree_gist/btree_cash.c
+++ b/contrib/btree_gist/btree_cash.c
@@ -90,27 +90,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(cash_dist);
-Datum
-cash_dist(PG_FUNCTION_ARGS)
-{
-	Cash		a = PG_GETARG_CASH(0);
-	Cash		b = PG_GETARG_CASH(1);
-	Cash		r;
-	Cash		ra;
-
-	r = a - b;
-	ra = Abs(r);
-
-	/* Overflow check. */
-	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("money out of range")));
-
-	PG_RETURN_CASH(ra);
-}
-
 /**************************************************
  * Cash ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_date.c b/contrib/btree_gist/btree_date.c
index bb516a9..e1bd413 100644
--- a/contrib/btree_gist/btree_date.c
+++ b/contrib/btree_gist/btree_date.c
@@ -108,19 +108,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(date_dist);
-Datum
-date_dist(PG_FUNCTION_ARGS)
-{
-	/* we assume the difference can't overflow */
-	Datum		diff = DirectFunctionCall2(date_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
-}
-
-
 /**************************************************
  * date ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index 13dc4a5..c798b42 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -89,21 +89,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(float4_dist);
-Datum
-float4_dist(PG_FUNCTION_ARGS)
-{
-	float4		a = PG_GETARG_FLOAT4(0);
-	float4		b = PG_GETARG_FLOAT4(1);
-	float4		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT4(Abs(r));
-}
-
-
 /**************************************************
  * float4 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index c3a2415..c332c2d 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -96,21 +96,6 @@ static const gbtree_ninfo tinfo =
 	gbt_float8_dist
 };
 
-
-PG_FUNCTION_INFO_V1(float8_dist);
-Datum
-float8_dist(PG_FUNCTION_ARGS)
-{
-	float8		a = PG_GETARG_FLOAT8(0);
-	float8		b = PG_GETARG_FLOAT8(1);
-	float8		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT8(Abs(r));
-}
-
 /**************************************************
  * float8 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_gist--1.2.sql b/contrib/btree_gist/btree_gist--1.2.sql
deleted file mode 100644
index 1efe753..0000000
--- a/contrib/btree_gist/btree_gist--1.2.sql
+++ /dev/null
@@ -1,1570 +0,0 @@
-/* contrib/btree_gist/btree_gist--1.2.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
-
-CREATE FUNCTION gbtreekey4_in(cstring)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey4_out(gbtreekey4)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey4 (
-	INTERNALLENGTH = 4,
-	INPUT  = gbtreekey4_in,
-	OUTPUT = gbtreekey4_out
-);
-
-CREATE FUNCTION gbtreekey8_in(cstring)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey8_out(gbtreekey8)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey8 (
-	INTERNALLENGTH = 8,
-	INPUT  = gbtreekey8_in,
-	OUTPUT = gbtreekey8_out
-);
-
-CREATE FUNCTION gbtreekey16_in(cstring)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey16_out(gbtreekey16)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey16 (
-	INTERNALLENGTH = 16,
-	INPUT  = gbtreekey16_in,
-	OUTPUT = gbtreekey16_out
-);
-
-CREATE FUNCTION gbtreekey32_in(cstring)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey32_out(gbtreekey32)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey32 (
-	INTERNALLENGTH = 32,
-	INPUT  = gbtreekey32_in,
-	OUTPUT = gbtreekey32_out
-);
-
-CREATE FUNCTION gbtreekey_var_in(cstring)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey_var (
-	INTERNALLENGTH = VARIABLE,
-	INPUT  = gbtreekey_var_in,
-	OUTPUT = gbtreekey_var_out,
-	STORAGE = EXTENDED
-);
-
---distance operators
-
-CREATE FUNCTION cash_dist(money, money)
-RETURNS money
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = money,
-	RIGHTARG = money,
-	PROCEDURE = cash_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION date_dist(date, date)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = date,
-	RIGHTARG = date,
-	PROCEDURE = date_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float4_dist(float4, float4)
-RETURNS float4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float4,
-	RIGHTARG = float4,
-	PROCEDURE = float4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float8_dist(float8, float8)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float8,
-	RIGHTARG = float8,
-	PROCEDURE = float8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int2_dist(int2, int2)
-RETURNS int2
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int2,
-	RIGHTARG = int2,
-	PROCEDURE = int2_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int4_dist(int4, int4)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int4,
-	RIGHTARG = int4,
-	PROCEDURE = int4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int8_dist(int8, int8)
-RETURNS int8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int8,
-	RIGHTARG = int8,
-	PROCEDURE = int8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION interval_dist(interval, interval)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = interval,
-	RIGHTARG = interval,
-	PROCEDURE = interval_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION oid_dist(oid, oid)
-RETURNS oid
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = oid,
-	RIGHTARG = oid,
-	PROCEDURE = oid_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION time_dist(time, time)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = time,
-	RIGHTARG = time,
-	PROCEDURE = time_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION ts_dist(timestamp, timestamp)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamp,
-	RIGHTARG = timestamp,
-	PROCEDURE = ts_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION tstz_dist(timestamptz, timestamptz)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamptz,
-	RIGHTARG = timestamptz,
-	PROCEDURE = tstz_dist,
-	COMMUTATOR = '<->'
-);
-
-
---
---
---
--- oid ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_oid_ops
-DEFAULT FOR TYPE oid USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
-	FUNCTION	2	gbt_oid_union (internal, internal),
-	FUNCTION	3	gbt_oid_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_oid_picksplit (internal, internal),
-	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
--- Add operators that are new in 9.1.  We do it like this, leaving them
--- "loose" in the operator family rather than bound into the opclass, because
--- that's the only state that can be reproduced during an upgrade from 9.0.
-ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
-	OPERATOR	6	<> (oid, oid) ,
-	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
-	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
-	-- Also add support function for index-only-scans, added in 9.5.
-	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
-
-
---
---
---
--- int2 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_union(internal, internal)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int2_ops
-DEFAULT FOR TYPE int2 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
-	FUNCTION	2	gbt_int2_union (internal, internal),
-	FUNCTION	3	gbt_int2_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int2_picksplit (internal, internal),
-	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
-	STORAGE		gbtreekey4;
-
-ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
-	OPERATOR	6	<> (int2, int2) ,
-	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
-	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
-
---
---
---
--- int4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int4_ops
-DEFAULT FOR TYPE int4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
-	FUNCTION	2	gbt_int4_union (internal, internal),
-	FUNCTION	3	gbt_int4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int4_picksplit (internal, internal),
-	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
-	OPERATOR	6	<> (int4, int4) ,
-	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
-	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
-
-
---
---
---
--- int8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int8_ops
-DEFAULT FOR TYPE int8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
-	FUNCTION	2	gbt_int8_union (internal, internal),
-	FUNCTION	3	gbt_int8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int8_picksplit (internal, internal),
-	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
-	OPERATOR	6	<> (int8, int8) ,
-	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
-	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
-
---
---
---
--- float4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float4_ops
-DEFAULT FOR TYPE float4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
-	FUNCTION	2	gbt_float4_union (internal, internal),
-	FUNCTION	3	gbt_float4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float4_picksplit (internal, internal),
-	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
-	OPERATOR	6	<> (float4, float4) ,
-	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
-	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
-
---
---
---
--- float8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float8_ops
-DEFAULT FOR TYPE float8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
-	FUNCTION	2	gbt_float8_union (internal, internal),
-	FUNCTION	3	gbt_float8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float8_picksplit (internal, internal),
-	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
-	OPERATOR	6	<> (float8, float8) ,
-	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
-	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
-
---
---
---
--- timestamp ops
---
---
---
-
-CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamp_ops
-DEFAULT FOR TYPE timestamp USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_ts_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
-	OPERATOR	6	<> (timestamp, timestamp) ,
-	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
-	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamptz_ops
-DEFAULT FOR TYPE timestamptz USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_tstz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
-	OPERATOR	6	<> (timestamptz, timestamptz) ,
-	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
-	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
-
---
---
---
--- time ops
---
---
---
-
-CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_time_ops
-DEFAULT FOR TYPE time USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_time_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
-	OPERATOR	6	<> (time, time) ,
-	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
-	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
-
-
-CREATE OPERATOR CLASS gist_timetz_ops
-DEFAULT FOR TYPE timetz USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_timetz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
-	OPERATOR	6	<> (timetz, timetz) ;
-	-- no 'fetch' function, as the compress function is lossy.
-
-
---
---
---
--- date ops
---
---
---
-
-CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_date_ops
-DEFAULT FOR TYPE date USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
-	FUNCTION	2	gbt_date_union (internal, internal),
-	FUNCTION	3	gbt_date_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_date_picksplit (internal, internal),
-	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
-	OPERATOR	6	<> (date, date) ,
-	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
-	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
-
-
---
---
---
--- interval ops
---
---
---
-
-CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_union(internal, internal)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_interval_ops
-DEFAULT FOR TYPE interval USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
-	FUNCTION	2	gbt_intv_union (internal, internal),
-	FUNCTION	3	gbt_intv_compress (internal),
-	FUNCTION	4	gbt_intv_decompress (internal),
-	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_intv_picksplit (internal, internal),
-	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
-	STORAGE		gbtreekey32;
-
-ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
-	OPERATOR	6	<> (interval, interval) ,
-	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
-	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
-
-
---
---
---
--- cash ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cash_ops
-DEFAULT FOR TYPE money USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
-	FUNCTION	2	gbt_cash_union (internal, internal),
-	FUNCTION	3	gbt_cash_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_cash_picksplit (internal, internal),
-	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
-	OPERATOR	6	<> (money, money) ,
-	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
-	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
-	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
-
-
---
---
---
--- macaddr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_macaddr_ops
-DEFAULT FOR TYPE macaddr USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
-	FUNCTION	2	gbt_macad_union (internal, internal),
-	FUNCTION	3	gbt_macad_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_macad_picksplit (internal, internal),
-	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
-	OPERATOR	6	<> (macaddr, macaddr) ,
-	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
-
-
---
---
---
--- text/ bpchar ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_text_ops
-DEFAULT FOR TYPE text USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_text_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
-	OPERATOR	6	<> (text, text) ,
-	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
-
-
----- Create the operator class
-CREATE OPERATOR CLASS gist_bpchar_ops
-DEFAULT FOR TYPE bpchar USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_bpchar_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
-	OPERATOR	6	<> (bpchar, bpchar) ,
-	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
-
---
---
--- bytea ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bytea_ops
-DEFAULT FOR TYPE bytea USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
-	FUNCTION	2	gbt_bytea_union (internal, internal),
-	FUNCTION	3	gbt_bytea_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
-	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
-	OPERATOR	6	<> (bytea, bytea) ,
-	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
-
-
---
---
---
--- numeric ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_numeric_ops
-DEFAULT FOR TYPE numeric USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
-	FUNCTION	2	gbt_numeric_union (internal, internal),
-	FUNCTION	3	gbt_numeric_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
-	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
-	OPERATOR	6	<> (numeric, numeric) ,
-	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
-
-
---
---
--- bit ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bit_ops
-DEFAULT FOR TYPE bit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
-	OPERATOR	6	<> (bit, bit) ,
-	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
-
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_vbit_ops
-DEFAULT FOR TYPE varbit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
-	OPERATOR	6	<> (varbit, varbit) ,
-	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
-
-
---
---
---
--- inet/cidr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_inet_ops
-DEFAULT FOR TYPE inet USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
-	OPERATOR	6	<>  (inet, inet) ;
-	-- no fetch support, the compress function is lossy
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cidr_ops
-DEFAULT FOR TYPE cidr USING gist
-AS
-	OPERATOR	1	<  (inet, inet)  ,
-	OPERATOR	2	<= (inet, inet)  ,
-	OPERATOR	3	=  (inet, inet)  ,
-	OPERATOR	4	>= (inet, inet)  ,
-	OPERATOR	5	>  (inet, inet)  ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
-	OPERATOR	6	<> (inet, inet) ;
-	-- no fetch support, the compress function is lossy
diff --git a/contrib/btree_gist/btree_gist--1.3--1.4.sql b/contrib/btree_gist/btree_gist--1.3--1.4.sql
new file mode 100644
index 0000000..4062aed
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.3--1.4.sql
@@ -0,0 +1,150 @@
+/* contrib/btree_gist/btree_gist--1.3--1.4.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.4'" to load this file. \quit
+
+-- update references to distance operators in pg_amop and pg_depend
+
+WITH
+btree_ops AS (
+	SELECT
+		amoplefttype, amoprighttype, amopopr
+	FROM
+		pg_amop
+		JOIN pg_am ON pg_am.oid = amopmethod
+		JOIN pg_opfamily ON pg_opfamily.oid = amopfamily
+		JOIN pg_namespace ON pg_namespace.oid = opfnamespace
+	WHERE
+		nspname = 'pg_catalog'
+		AND opfname IN (
+			'integer_ops',
+			'oid_ops',
+			'money_ops',
+			'float_ops',
+			'datetime_ops',
+			'time_ops',
+			'interval_ops'
+		)
+		AND amname = 'btree'
+		AND amoppurpose = 'o'
+		AND amopstrategy = 6
+	),
+gist_ops AS (
+	SELECT
+		pg_amop.oid AS oid, amoplefttype, amoprighttype, amopopr
+	FROM
+		pg_amop
+		JOIN pg_am ON amopmethod = pg_am.oid
+		JOIN pg_opfamily ON amopfamily = pg_opfamily.oid
+		JOIN pg_namespace ON pg_namespace.oid = opfnamespace
+	WHERE
+		nspname = current_schema()
+		AND opfname IN (
+			'gist_oid_ops',
+			'gist_int2_ops',
+			'gist_int4_ops',
+			'gist_int8_ops',
+			'gist_float4_ops',
+			'gist_float8_ops',
+			'gist_timestamp_ops',
+			'gist_timestamptz_ops',
+			'gist_time_ops',
+			'gist_date_ops',
+			'gist_interval_ops',
+			'gist_cash_ops'
+		)
+		AND amname = 'gist'
+		AND amoppurpose = 'o'
+		AND amopstrategy = 15
+	),
+depend_update_data(gist_amop, gist_amopopr, btree_amopopr) AS (
+	SELECT
+		gist_ops.oid, gist_ops.amopopr, btree_ops.amopopr
+	FROM
+		btree_ops JOIN gist_ops USING (amoplefttype, amoprighttype)
+),
+amop_update_data AS (
+	UPDATE
+		pg_depend
+	SET
+		refobjid = btree_amopopr
+	FROM
+		depend_update_data
+	WHERE
+		objid = gist_amop AND refobjid = gist_amopopr
+	RETURNING
+		depend_update_data.*
+)
+UPDATE
+	pg_amop
+SET
+	amopopr = btree_amopopr
+FROM
+	amop_update_data
+WHERE
+	pg_amop.oid = gist_amop;
+
+-- disable implicit pg_catalog search
+
+DO
+$$
+BEGIN
+	EXECUTE 'SET LOCAL search_path TO ' || current_schema() || ', pg_catalog';
+END
+$$;
+
+-- drop distance operators
+
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int2, int2);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int4, int4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int8, int8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float4, float4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float8, float8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (oid, oid);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (money, money);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (date, date);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (time, time);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (interval, interval);
+
+DROP OPERATOR <-> (int2, int2);
+DROP OPERATOR <-> (int4, int4);
+DROP OPERATOR <-> (int8, int8);
+DROP OPERATOR <-> (float4, float4);
+DROP OPERATOR <-> (float8, float8);
+DROP OPERATOR <-> (oid, oid);
+DROP OPERATOR <-> (money, money);
+DROP OPERATOR <-> (date, date);
+DROP OPERATOR <-> (time, time);
+DROP OPERATOR <-> (timestamp, timestamp);
+DROP OPERATOR <-> (timestamptz, timestamptz);
+DROP OPERATOR <-> (interval, interval);
+
+-- drop distance functions
+
+ALTER EXTENSION btree_gist DROP FUNCTION int2_dist(int2, int2);
+ALTER EXTENSION btree_gist DROP FUNCTION int4_dist(int4, int4);
+ALTER EXTENSION btree_gist DROP FUNCTION int8_dist(int8, int8);
+ALTER EXTENSION btree_gist DROP FUNCTION float4_dist(float4, float4);
+ALTER EXTENSION btree_gist DROP FUNCTION float8_dist(float8, float8);
+ALTER EXTENSION btree_gist DROP FUNCTION oid_dist(oid, oid);
+ALTER EXTENSION btree_gist DROP FUNCTION cash_dist(money, money);
+ALTER EXTENSION btree_gist DROP FUNCTION date_dist(date, date);
+ALTER EXTENSION btree_gist DROP FUNCTION time_dist(time, time);
+ALTER EXTENSION btree_gist DROP FUNCTION ts_dist(timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP FUNCTION interval_dist(interval, interval);
+
+DROP FUNCTION int2_dist(int2, int2);
+DROP FUNCTION int4_dist(int4, int4);
+DROP FUNCTION int8_dist(int8, int8);
+DROP FUNCTION float4_dist(float4, float4);
+DROP FUNCTION float8_dist(float8, float8);
+DROP FUNCTION oid_dist(oid, oid);
+DROP FUNCTION cash_dist(money, money);
+DROP FUNCTION date_dist(date, date);
+DROP FUNCTION time_dist(time, time);
+DROP FUNCTION ts_dist(timestamp, timestamp);
+DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+DROP FUNCTION interval_dist(interval, interval);
diff --git a/contrib/btree_gist/btree_gist--1.4.sql b/contrib/btree_gist/btree_gist--1.4.sql
new file mode 100644
index 0000000..1f1327d
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.4.sql
@@ -0,0 +1,1490 @@
+/* contrib/btree_gist/btree_gist--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
+
+CREATE FUNCTION gbtreekey4_in(cstring)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey4_out(gbtreekey4)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey4 (
+	INTERNALLENGTH = 4,
+	INPUT  = gbtreekey4_in,
+	OUTPUT = gbtreekey4_out
+);
+
+CREATE FUNCTION gbtreekey8_in(cstring)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey8_out(gbtreekey8)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey8 (
+	INTERNALLENGTH = 8,
+	INPUT  = gbtreekey8_in,
+	OUTPUT = gbtreekey8_out
+);
+
+CREATE FUNCTION gbtreekey16_in(cstring)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey16_out(gbtreekey16)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey16 (
+	INTERNALLENGTH = 16,
+	INPUT  = gbtreekey16_in,
+	OUTPUT = gbtreekey16_out
+);
+
+CREATE FUNCTION gbtreekey32_in(cstring)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey32_out(gbtreekey32)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey32 (
+	INTERNALLENGTH = 32,
+	INPUT  = gbtreekey32_in,
+	OUTPUT = gbtreekey32_out
+);
+
+CREATE FUNCTION gbtreekey_var_in(cstring)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey_var (
+	INTERNALLENGTH = VARIABLE,
+	INPUT  = gbtreekey_var_in,
+	OUTPUT = gbtreekey_var_out,
+	STORAGE = EXTENDED
+);
+
+
+--
+--
+--
+-- oid ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_oid_ops
+DEFAULT FOR TYPE oid USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
+	FUNCTION	2	gbt_oid_union (internal, internal),
+	FUNCTION	3	gbt_oid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_oid_picksplit (internal, internal),
+	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+-- Add operators that are new in 9.1.  We do it like this, leaving them
+-- "loose" in the operator family rather than bound into the opclass, because
+-- that's the only state that can be reproduced during an upgrade from 9.0.
+ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
+	OPERATOR	6	<> (oid, oid) ,
+	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
+	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
+	-- Also add support function for index-only-scans, added in 9.5.
+	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
+
+
+--
+--
+--
+-- int2 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_union(internal, internal)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int2_ops
+DEFAULT FOR TYPE int2 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
+	FUNCTION	2	gbt_int2_union (internal, internal),
+	FUNCTION	3	gbt_int2_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int2_picksplit (internal, internal),
+	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
+	STORAGE		gbtreekey4;
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
+	OPERATOR	6	<> (int2, int2) ,
+	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
+	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
+
+--
+--
+--
+-- int4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int4_ops
+DEFAULT FOR TYPE int4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
+	FUNCTION	2	gbt_int4_union (internal, internal),
+	FUNCTION	3	gbt_int4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int4_picksplit (internal, internal),
+	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
+	OPERATOR	6	<> (int4, int4) ,
+	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
+	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
+
+
+--
+--
+--
+-- int8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int8_ops
+DEFAULT FOR TYPE int8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
+	FUNCTION	2	gbt_int8_union (internal, internal),
+	FUNCTION	3	gbt_int8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int8_picksplit (internal, internal),
+	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
+	OPERATOR	6	<> (int8, int8) ,
+	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
+	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
+
+--
+--
+--
+-- float4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float4_ops
+DEFAULT FOR TYPE float4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
+	FUNCTION	2	gbt_float4_union (internal, internal),
+	FUNCTION	3	gbt_float4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float4_picksplit (internal, internal),
+	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
+	OPERATOR	6	<> (float4, float4) ,
+	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
+	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
+
+--
+--
+--
+-- float8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float8_ops
+DEFAULT FOR TYPE float8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
+	FUNCTION	2	gbt_float8_union (internal, internal),
+	FUNCTION	3	gbt_float8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float8_picksplit (internal, internal),
+	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
+	OPERATOR	6	<> (float8, float8) ,
+	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
+	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
+
+--
+--
+--
+-- timestamp ops
+--
+--
+--
+
+CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamp_ops
+DEFAULT FOR TYPE timestamp USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_ts_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
+	OPERATOR	6	<> (timestamp, timestamp) ,
+	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
+	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamptz_ops
+DEFAULT FOR TYPE timestamptz USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_tstz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
+	OPERATOR	6	<> (timestamptz, timestamptz) ,
+	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
+	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
+
+--
+--
+--
+-- time ops
+--
+--
+--
+
+CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_time_ops
+DEFAULT FOR TYPE time USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_time_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
+	OPERATOR	6	<> (time, time) ,
+	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
+	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
+
+
+CREATE OPERATOR CLASS gist_timetz_ops
+DEFAULT FOR TYPE timetz USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_timetz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
+	OPERATOR	6	<> (timetz, timetz) ;
+	-- no 'fetch' function, as the compress function is lossy.
+
+
+--
+--
+--
+-- date ops
+--
+--
+--
+
+CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_date_ops
+DEFAULT FOR TYPE date USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
+	FUNCTION	2	gbt_date_union (internal, internal),
+	FUNCTION	3	gbt_date_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_date_picksplit (internal, internal),
+	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
+	OPERATOR	6	<> (date, date) ,
+	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
+	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
+
+
+--
+--
+--
+-- interval ops
+--
+--
+--
+
+CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_interval_ops
+DEFAULT FOR TYPE interval USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
+	FUNCTION	2	gbt_intv_union (internal, internal),
+	FUNCTION	3	gbt_intv_compress (internal),
+	FUNCTION	4	gbt_intv_decompress (internal),
+	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_intv_picksplit (internal, internal),
+	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
+	OPERATOR	6	<> (interval, interval) ,
+	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
+	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
+
+
+--
+--
+--
+-- cash ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cash_ops
+DEFAULT FOR TYPE money USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
+	FUNCTION	2	gbt_cash_union (internal, internal),
+	FUNCTION	3	gbt_cash_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_cash_picksplit (internal, internal),
+	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
+	OPERATOR	6	<> (money, money) ,
+	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
+	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
+	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
+
+
+--
+--
+--
+-- macaddr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_macaddr_ops
+DEFAULT FOR TYPE macaddr USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
+	FUNCTION	2	gbt_macad_union (internal, internal),
+	FUNCTION	3	gbt_macad_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_macad_picksplit (internal, internal),
+	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
+	OPERATOR	6	<> (macaddr, macaddr) ,
+	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
+
+
+--
+--
+--
+-- text/ bpchar ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_text_ops
+DEFAULT FOR TYPE text USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_text_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
+	OPERATOR	6	<> (text, text) ,
+	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
+
+
+---- Create the operator class
+CREATE OPERATOR CLASS gist_bpchar_ops
+DEFAULT FOR TYPE bpchar USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_bpchar_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
+	OPERATOR	6	<> (bpchar, bpchar) ,
+	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
+
+--
+--
+-- bytea ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bytea_ops
+DEFAULT FOR TYPE bytea USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
+	FUNCTION	2	gbt_bytea_union (internal, internal),
+	FUNCTION	3	gbt_bytea_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
+	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
+	OPERATOR	6	<> (bytea, bytea) ,
+	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- numeric ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_numeric_ops
+DEFAULT FOR TYPE numeric USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
+	FUNCTION	2	gbt_numeric_union (internal, internal),
+	FUNCTION	3	gbt_numeric_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
+	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
+	OPERATOR	6	<> (numeric, numeric) ,
+	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
+
+
+--
+--
+-- bit ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bit_ops
+DEFAULT FOR TYPE bit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
+	OPERATOR	6	<> (bit, bit) ,
+	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
+
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_vbit_ops
+DEFAULT FOR TYPE varbit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
+	OPERATOR	6	<> (varbit, varbit) ,
+	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- inet/cidr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_inet_ops
+DEFAULT FOR TYPE inet USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
+	OPERATOR	6	<>  (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cidr_ops
+DEFAULT FOR TYPE cidr USING gist
+AS
+	OPERATOR	1	<  (inet, inet)  ,
+	OPERATOR	2	<= (inet, inet)  ,
+	OPERATOR	3	=  (inet, inet)  ,
+	OPERATOR	4	>= (inet, inet)  ,
+	OPERATOR	5	>  (inet, inet)  ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
+	OPERATOR	6	<> (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+--
+--
+--
+-- uuid ops
+--
+--
+---- define the GiST support methods
+CREATE FUNCTION gbt_uuid_consistent(internal,uuid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_uuid_ops
+DEFAULT FOR TYPE uuid USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_uuid_consistent (internal, uuid, int2, oid, internal),
+	FUNCTION	2	gbt_uuid_union (internal, internal),
+	FUNCTION	3	gbt_uuid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_uuid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_uuid_picksplit (internal, internal),
+	FUNCTION	7	gbt_uuid_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+-- These are "loose" in the opfamily for consistency with the rest of btree_gist
+ALTER OPERATOR FAMILY gist_uuid_ops USING gist ADD
+	OPERATOR	6	<>  (uuid, uuid) ,
+	FUNCTION	9 (uuid, uuid) gbt_uuid_fetch (internal) ;
+
diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control
index ddbf83d..fdf0e6a 100644
--- a/contrib/btree_gist/btree_gist.control
+++ b/contrib/btree_gist/btree_gist.control
@@ -1,5 +1,5 @@
 # btree_gist extension
 comment = 'support for indexing common datatypes in GiST'
-default_version = '1.3'
+default_version = '1.4'
 module_pathname = '$libdir/btree_gist'
 relocatable = true
diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c
index 54dc1cc..c3cee90 100644
--- a/contrib/btree_gist/btree_int2.c
+++ b/contrib/btree_gist/btree_int2.c
@@ -89,28 +89,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(int2_dist);
-Datum
-int2_dist(PG_FUNCTION_ARGS)
-{
-	int16		a = PG_GETARG_INT16(0);
-	int16		b = PG_GETARG_INT16(1);
-	int16		r;
-	int16		ra;
-
-	r = a - b;
-	ra = Abs(r);
-
-	/* Overflow check. */
-	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("smallint out of range")));
-
-	PG_RETURN_INT16(ra);
-}
-
-
 /**************************************************
  * int16 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c
index ddbcf52..e79ab3e 100644
--- a/contrib/btree_gist/btree_int4.c
+++ b/contrib/btree_gist/btree_int4.c
@@ -90,28 +90,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(int4_dist);
-Datum
-int4_dist(PG_FUNCTION_ARGS)
-{
-	int32		a = PG_GETARG_INT32(0);
-	int32		b = PG_GETARG_INT32(1);
-	int32		r;
-	int32		ra;
-
-	r = a - b;
-	ra = Abs(r);
-
-	/* Overflow check. */
-	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("integer out of range")));
-
-	PG_RETURN_INT32(ra);
-}
-
-
 /**************************************************
  * int32 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c
index 44bf69a..89b8430 100644
--- a/contrib/btree_gist/btree_int8.c
+++ b/contrib/btree_gist/btree_int8.c
@@ -90,28 +90,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(int8_dist);
-Datum
-int8_dist(PG_FUNCTION_ARGS)
-{
-	int64		a = PG_GETARG_INT64(0);
-	int64		b = PG_GETARG_INT64(1);
-	int64		r;
-	int64		ra;
-
-	r = a - b;
-	ra = Abs(r);
-
-	/* Overflow check. */
-	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("bigint out of range")));
-
-	PG_RETURN_INT64(ra);
-}
-
-
 /**************************************************
  * int64 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_interval.c b/contrib/btree_gist/btree_interval.c
index acccb8b..b95a963 100644
--- a/contrib/btree_gist/btree_interval.c
+++ b/contrib/btree_gist/btree_interval.c
@@ -109,32 +109,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-Interval *
-abs_interval(Interval *a)
-{
-	static Interval zero = {0, 0, 0};
-
-	if (DatumGetBool(DirectFunctionCall2(interval_lt,
-										 IntervalPGetDatum(a),
-										 IntervalPGetDatum(&zero))))
-		a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
-												  IntervalPGetDatum(a)));
-
-	return a;
-}
-
-PG_FUNCTION_INFO_V1(interval_dist);
-Datum
-interval_dist(PG_FUNCTION_ARGS)
-{
-	Datum		diff = DirectFunctionCall2(interval_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
-}
-
-
 /**************************************************
  * interval ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_oid.c b/contrib/btree_gist/btree_oid.c
index ac61a76..d389fe2 100644
--- a/contrib/btree_gist/btree_oid.c
+++ b/contrib/btree_gist/btree_oid.c
@@ -96,22 +96,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(oid_dist);
-Datum
-oid_dist(PG_FUNCTION_ARGS)
-{
-	Oid			a = PG_GETARG_OID(0);
-	Oid			b = PG_GETARG_OID(1);
-	Oid			res;
-
-	if (a < b)
-		res = b - a;
-	else
-		res = a - b;
-	PG_RETURN_OID(res);
-}
-
-
 /**************************************************
  * Oid ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_time.c b/contrib/btree_gist/btree_time.c
index 41d9959..fd95f7e 100644
--- a/contrib/btree_gist/btree_time.c
+++ b/contrib/btree_gist/btree_time.c
@@ -136,18 +136,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(time_dist);
-Datum
-time_dist(PG_FUNCTION_ARGS)
-{
-	Datum		diff = DirectFunctionCall2(time_mi_time,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
-}
-
-
 /**************************************************
  * time ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c
index ab22b27..899fbf4 100644
--- a/contrib/btree_gist/btree_ts.c
+++ b/contrib/btree_gist/btree_ts.c
@@ -139,63 +139,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(ts_dist);
-Datum
-ts_dist(PG_FUNCTION_ARGS)
-{
-	Timestamp	a = PG_GETARG_TIMESTAMP(0);
-	Timestamp	b = PG_GETARG_TIMESTAMP(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-#ifdef HAVE_INT64_TIMESTAMP
-		p->time = PG_INT64_MAX;
-#else
-		p->time = DBL_MAX;
-#endif
-		PG_RETURN_INTERVAL_P(p);
-	}
-	else
-		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-												  PG_GETARG_DATUM(0),
-												  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
-}
-
-PG_FUNCTION_INFO_V1(tstz_dist);
-Datum
-tstz_dist(PG_FUNCTION_ARGS)
-{
-	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
-	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-#ifdef HAVE_INT64_TIMESTAMP
-		p->time = PG_INT64_MAX;
-#else
-		p->time = DBL_MAX;
-#endif
-		PG_RETURN_INTERVAL_P(p);
-	}
-
-	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-											  PG_GETARG_DATUM(0),
-											  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
-}
-
-
 /**************************************************
  * timestamp ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_utils_num.h b/contrib/btree_gist/btree_utils_num.h
index a33491b..8f873a5 100644
--- a/contrib/btree_gist/btree_utils_num.h
+++ b/contrib/btree_gist/btree_utils_num.h
@@ -116,8 +116,6 @@ do {															\
 } while(0)
 
 
-extern Interval *abs_interval(Interval *a);
-
 extern bool gbt_num_consistent(const GBT_NUMKEY_R *key, const void *query,
 				   const StrategyNumber *strategy, bool is_leaf,
 				   const gbtree_ninfo *tinfo);
0007-add-regression-tests-for-kNN-btree-v01.patchtext/x-patch; name=0007-add-regression-tests-for-kNN-btree-v01.patchDownload
diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index 755cd17..3ab1ad7 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -150,3 +150,489 @@ vacuum btree_tall_tbl;
 -- need to insert some rows to cause the fast root page to split.
 insert into btree_tall_tbl (id, t)
   select g, repeat('x', 100) from generate_series(1, 500) g;
+---
+--- Test B-tree distance ordering
+---
+SET enable_bitmapscan = OFF;
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+          QUERY PLAN          
+------------------------------
+ Sort
+   Sort Key: ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+                   QUERY PLAN                   
+------------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((random <-> 1))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)))
+   Order By: (random <-> 4000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+        1 |     9001
+        1 |     8001
+        1 |     7001
+        1 |     6001
+        1 |     5001
+        1 |     4001
+        1 |     3001
+        1 |     2001
+        1 |     1001
+        1 |        1
+        0 |     9000
+        0 |     8000
+        0 |     7000
+        0 |     6000
+        0 |     5000
+        0 |     4000
+        0 |     3000
+        0 |     2000
+        0 |     1000
+        0 |        0
+          |        1
+          |        2
+          |        3
+(33 rows)
+
+DROP INDEX tenk3_idx;
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+        1 |        1
+        1 |     1001
+        1 |     2001
+        1 |     3001
+        1 |     4001
+        1 |     5001
+        1 |     6001
+        1 |     7001
+        1 |     8001
+        1 |     9001
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+        0 |        0
+        0 |     1000
+        0 |     2000
+        0 |     3000
+        0 |     4000
+        0 |     5000
+        0 |     6000
+        0 |     7000
+        0 |     8000
+        0 |     9000
+          |        3
+          |        2
+          |        1
+(33 rows)
+
+DROP INDEX tenk3_idx;
+DROP TABLE tenk3;
+RESET enable_bitmapscan;
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 65b08c8..b0559d7 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -92,3 +92,108 @@ vacuum btree_tall_tbl;
 -- need to insert some rows to cause the fast root page to split.
 insert into btree_tall_tbl (id, t)
   select g, repeat('x', 100) from generate_series(1, 500) g;
+
+---
+--- Test B-tree distance ordering
+---
+
+SET enable_bitmapscan = OFF;
+
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+
+
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+DROP INDEX tenk3_idx;
+
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+DROP INDEX tenk3_idx;
+
+DROP TABLE tenk3;
+
+RESET enable_bitmapscan;
#2Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Nikita Glukhov (#1)
Re: [PATCH] kNN for btree

Sorry for the broken formatting in my previous message.
Below is a corrected version of this message.

I'd like to present a series of patches that implements k-Nearest Neighbors
(kNN) search for btree, which can be used to speed up ORDER BY distance
queries like this:
SELECT * FROM events ORDER BY date <-> '2000-01-01'::date ASC LIMIT 100;

Now only GiST supports kNN, but kNN on btree can be emulated using
contrib/btree_gist.

Scanning algorithm
==================

Algorithm is very simple: we use bidirectional B-tree index scan starting at
the point from which we measure the distance (target point). At each step,
we advance this scan in the direction that has the nearest point. But when
the target point does not fall into the scanned range, we don't even need to
use a bidirectional scan here --- we can use ordinary unidirectional scan
in the right direction.

Performance results
===================

Test database is taken from original kNN-GiST presentation (PGCon 2010).

Test query

SELECT * FROM events ORDER BY date <-> '1957-10-04'::date ASC LIMIT k;

can be optimized to the next rather complicated UNION form,
which no longer requires kNN:

WITH
t1 AS (SELECT * FROM events WHERE date >= '1957-10-04'::date
ORDER BY date ASC LIMIT k),
t2 AS (SELECT * FROM events WHERE date < '1957-10-04'::date
ORDER BY date DESC LIMIT k),
t AS (SELECT * FROM t1 UNION SELECT * FROM t2)
SELECT * FROM t ORDER BY date <-> '1957-10-04'::date ASC LIMIT k;

In each cell of this table shown query execution time in milliseconds and
the number of accessed blocks:

k | kNN-btree | kNN-GiST | Opt. query | Seq. scan
| | (btree_gist) | with UNION | with sort
--------|--------------|--------------|---------------|------------
1 | 0.041 4 | 0.079 4 | 0.060 8 | 41.1 1824
10 | 0.048 7 | 0.091 9 | 0.097 17 | 41.8 1824
100 | 0.107 47 | 0.192 52 | 0.342 104 | 42.3 1824
1000 | 0.735 573 | 0.913 650 | 2.970 1160 | 43.5 1824
10000 | 5.070 5622 | 6.240 6760 | 36.300 11031 | 54.1 1824
100000 | 49.600 51608 | 61.900 64194 | 295.100 94980 | 115.0 1824

As you can see, kNN-btree can be two times faster than kNN-GiST (btree_gist)
when k < 1000, but the number of blocks read is roughly the same.

Implementation details
======================

A brief description is given below for each of the patches:

1. Introduce amcanorderbyop() function

This patch transforms existing boolean AM property amcanorderbyop into a method
(function pointer). This is necessary because, unlike GiST, kNN for btree
supports only a one ordering operator on the first index column and we need a
different pathkey matching logic for btree (there was a corresponding comment
in match_pathkeys_to_index()). GiST-specific logic has been moved from
match_pathkeys_to_index() to gistcanorderbyop().

2. Extract substructure BTScanState from BTScanOpaque

This refactoring is necessary for bidirectional kNN-scan implementation.
Now, BTScanOpaque's substructure BTScanState containing only the fields
related to scan position is passed to some functions where the whole
BTScanOpaque was passed previously.

3. Extract get_index_column_opclass(), get_opclass_opfamily_and_input_type().

Extracted two simple common functions used in gistproperty() and
btproperty() (see the next patch).

4. Add kNN support to btree

* Added additional optional BTScanState to BTScanOpaque for
bidirectional kNN scan.
* Implemented bidirectional kNN scan.
* Implemented logic for selecting kNN strategy
* Implemented btcanorderbyop(), updated btproperty() and btvalidate()

B-tree user interface functions have not been altered because ordering
operators are used directly.

5. Add distance operators for some types

These operators for integer, float, date, time, timestamp, interval, cash and
oid types have been copied from contrib/btree_gist and added to the existing
btree opclasses as ordering operators. Their btree_gist duplicates are removed
in the next patch.

6. Remove duplicate distance operators from contrib/btree_gist.

References to their own distance operators in btree_gist opclasses are
replaced with references to the built-in operators and than duplicate
operators are dropped. But if the user is using somewhere these operators,
upgrade of btree_gist from 1.3 to 1.4 would fail.

7. Add regression tests for btree kNN.

Tests were added only after the built-in distance operators were added.

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

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

#3Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Nikita Glukhov (#2)
7 attachment(s)
Re: [PATCH] kNN for btree

Attached v02 version of patches (rebased onto HEAD).

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-introduce-amcanorderby-function-v02.patchtext/x-patch; name=0001-introduce-amcanorderby-function-v02.patchDownload
diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 858798d..0b69a2a 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -109,7 +109,6 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BLOOM_NSTRATEGIES;
 	amroutine->amsupport = BLOOM_NPROC;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -141,6 +140,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amcanorderbyop = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index b2afdb7..22a2be7 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -83,7 +83,6 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = BRIN_LAST_OPTIONAL_PROCNUM;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -115,6 +114,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amcanorderbyop = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 02d920b..8c33f70 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -39,7 +39,6 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GINNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -71,6 +70,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amcanorderbyop = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index c2247ad..00a0c55 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -60,7 +60,6 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GISTNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -92,6 +91,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amcanorderbyop = gistcanorderbyop;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index f92baed..159987e 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -19,6 +19,7 @@
 #include "access/htup_details.h"
 #include "access/reloptions.h"
 #include "catalog/pg_opclass.h"
+#include "optimizer/paths.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
@@ -964,3 +965,31 @@ gistGetFakeLSN(Relation rel)
 		return GetFakeLSNForUnloggedRel();
 	}
 }
+
+/* gistcanorderbyop() */
+Expr *
+gistcanorderbyop(IndexOptInfo *index, PathKey *pathkey, int pathkeyno,
+				 Expr *orderby_clause, int *indexcol_p)
+{
+	int		indexcol;
+
+	/*
+	 * We allow any column of the GiST index to match each pathkey;
+	 * they don't have to match left-to-right as you might expect.
+	 */
+
+	for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
+	{
+		Expr	   *expr = match_clause_to_ordering_op(index,
+													   indexcol,
+													   orderby_clause,
+													   pathkey->pk_opfamily);
+		if (expr)
+		{
+			*indexcol_p = indexcol;
+			return expr;
+		}
+	}
+
+	return NULL;
+}
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index ec8ed33..e98edd7 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -57,7 +57,6 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = HTMaxStrategyNumber;
 	amroutine->amsupport = HASHNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -89,6 +88,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amcanorderbyop = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 469e7ab..b4e8d44 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -89,7 +89,6 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BTMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = true;
 	amroutine->amcanmulticol = true;
@@ -121,6 +120,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amcanorderbyop = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 78846be..675118e 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -39,7 +39,6 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = SPGISTNProc;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -71,6 +70,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->amcanorderbyop = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 5283468..21659a6 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -17,6 +17,7 @@
 
 #include <math.h>
 
+#include "access/amapi.h"
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_am.h"
@@ -166,8 +167,6 @@ static bool match_rowcompare_to_indexcol(IndexOptInfo *index,
 static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 						List **orderby_clauses_p,
 						List **clause_columns_p);
-static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
-							int indexcol, Expr *clause, Oid pk_opfamily);
 static bool ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
 						   EquivalenceClass *ec, EquivalenceMember *em,
 						   void *arg);
@@ -2481,12 +2480,15 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 	List	   *orderby_clauses = NIL;
 	List	   *clause_columns = NIL;
 	ListCell   *lc1;
+	int			pathkeyno = 1;
+	amcanorderbyop_function amcanorderbyop =
+			(amcanorderbyop_function) index->amcanorderbyop;
 
 	*orderby_clauses_p = NIL;	/* set default results */
 	*clause_columns_p = NIL;
 
-	/* Only indexes with the amcanorderbyop property are interesting here */
-	if (!index->amcanorderbyop)
+	/* Only indexes with the amcanorderbyop function are interesting here */
+	if (!amcanorderbyop)
 		return;
 
 	foreach(lc1, pathkeys)
@@ -2520,42 +2522,29 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 		foreach(lc2, pathkey->pk_eclass->ec_members)
 		{
 			EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
+			Expr	   *expr;
 			int			indexcol;
 
 			/* No possibility of match if it references other relations */
 			if (!bms_equal(member->em_relids, index->rel->relids))
 				continue;
 
-			/*
-			 * We allow any column of the index to match each pathkey; they
-			 * don't have to match left-to-right as you might expect.  This is
-			 * correct for GiST, which is the sole existing AM supporting
-			 * amcanorderbyop.  We might need different logic in future for
-			 * other implementations.
-			 */
-			for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
-			{
-				Expr	   *expr;
+			expr = amcanorderbyop(index, pathkey, pathkeyno, member->em_expr,
+								  &indexcol);
 
-				expr = match_clause_to_ordering_op(index,
-												   indexcol,
-												   member->em_expr,
-												   pathkey->pk_opfamily);
-				if (expr)
-				{
-					orderby_clauses = lappend(orderby_clauses, expr);
-					clause_columns = lappend_int(clause_columns, indexcol);
-					found = true;
-					break;
-				}
+			if (expr)
+			{
+				orderby_clauses = lappend(orderby_clauses, expr);
+				clause_columns = lappend_int(clause_columns, indexcol);
+				found = true;
+				break; /* don't want to look at remaining members */
 			}
-
-			if (found)			/* don't want to look at remaining members */
-				break;
 		}
 
 		if (!found)				/* fail if no match for this pathkey */
 			return;
+
+		pathkeyno++;
 	}
 
 	*orderby_clauses_p = orderby_clauses;		/* success! */
@@ -2587,7 +2576,7 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
  * If successful, return 'clause' as-is if the indexkey is on the left,
  * otherwise a commuted copy of 'clause'.  If no match, return NULL.
  */
-static Expr *
+Expr *
 match_clause_to_ordering_op(IndexOptInfo *index,
 							int indexcol,
 							Expr *clause,
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 7836e6b..93bda0f 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -237,7 +237,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 			/* We copy just the fields we need, not all of rd_amroutine */
 			amroutine = indexRelation->rd_amroutine;
-			info->amcanorderbyop = amroutine->amcanorderbyop;
+			info->amcanorderbyop = (void (*)()) amroutine->amcanorderbyop;
 			info->amoptionalkey = amroutine->amoptionalkey;
 			info->amsearcharray = amroutine->amsearcharray;
 			info->amsearchnulls = amroutine->amsearchnulls;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index e91e41d..aae6c36 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -21,6 +21,9 @@
  */
 struct PlannerInfo;
 struct IndexPath;
+struct IndexOptInfo;
+struct PathKey;
+struct Expr;
 
 /* Likewise, this file shouldn't depend on execnodes.h. */
 struct IndexInfo;
@@ -137,6 +140,13 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/* does AM support ORDER BY result of an operator on indexed column? */
+typedef struct Expr *(*amcanorderbyop_function) (struct IndexOptInfo *index,
+												 struct PathKey *pathkey,
+												 int pathkeyno,
+												 struct Expr *orderby_clause,
+												 int *indexcol_p);
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -167,8 +177,6 @@ typedef struct IndexAmRoutine
 	uint16		amsupport;
 	/* does AM support ORDER BY indexed column's value? */
 	bool		amcanorder;
-	/* does AM support ORDER BY result of an operator on indexed column? */
-	bool		amcanorderbyop;
 	/* does AM support backward scanning? */
 	bool		amcanbackward;
 	/* does AM support UNIQUE indexes? */
@@ -208,7 +216,7 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;		/* can be NULL */
 	amrestrpos_function amrestrpos;		/* can be NULL */
-
+	amcanorderbyop_function amcanorderbyop; /* can be NULL */
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan;		/* can be NULL */
 	aminitparallelscan_function aminitparallelscan;		/* can be NULL */
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 60a770a..87ecbc2 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -537,6 +537,11 @@ extern void gistMakeUnionKey(GISTSTATE *giststate, int attno,
 
 extern XLogRecPtr gistGetFakeLSN(Relation rel);
 
+extern struct Expr *gistcanorderbyop(struct IndexOptInfo *index,
+									 struct PathKey *pathkey,  int pathkeyno,
+									 struct Expr *orderby_clause,
+									 int *indexcol_p);
+
 /* gistvacuum.c */
 extern IndexBulkDeleteResult *gistbulkdelete(IndexVacuumInfo *info,
 			   IndexBulkDeleteResult *stats,
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 643be54..01501c9 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -623,7 +623,6 @@ typedef struct IndexOptInfo
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
 	bool		amoptionalkey;	/* can query omit key for the first column? */
 	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
 	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
@@ -631,6 +630,7 @@ typedef struct IndexOptInfo
 	bool		amhasgetbitmap; /* does AM have amgetbitmap interface? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
+	void		(*amcanorderbyop) ();	/* does AM support order by operator result? */
 } IndexOptInfo;
 
 /*
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 81a9be7..bb7611e 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -79,6 +79,10 @@ extern Expr *adjust_rowcompare_for_index(RowCompareExpr *clause,
 							int indexcol,
 							List **indexcolnos,
 							bool *var_on_left_p);
+extern Expr *match_clause_to_ordering_op(IndexOptInfo *index,
+							int indexcol,
+							Expr *clause,
+							Oid pk_opfamily);
 
 /*
  * tidpath.h
0002-extract-structure-BTScanState-v02.patchtext/x-patch; name=0002-extract-structure-BTScanState-v02.patchDownload
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index b4e8d44..c2da15e 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -299,6 +299,7 @@ bool
 btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 	bool		res;
 
 	/* btree indexes are never lossy */
@@ -309,7 +310,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(so->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
@@ -326,7 +327,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(so->currPos))
+		if (!BTScanPosIsValid(state->currPos))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -344,11 +345,11 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 				 * trying to optimize that, so we don't detect it, but instead
 				 * just forget any excess entries.
 				 */
-				if (so->killedItems == NULL)
-					so->killedItems = (int *)
+				if (state->killedItems == NULL)
+					state->killedItems = (int *)
 						palloc(MaxIndexTuplesPerPage * sizeof(int));
-				if (so->numKilled < MaxIndexTuplesPerPage)
-					so->killedItems[so->numKilled++] = so->currPos.itemIndex;
+				if (state->numKilled < MaxIndexTuplesPerPage)
+					state->killedItems[so->state.numKilled++] = state->currPos.itemIndex;
 			}
 
 			/*
@@ -373,6 +374,7 @@ int64
 btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	int64		ntids = 0;
 	ItemPointer heapTid;
 
@@ -405,7 +407,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				 * Advance to next tuple within page.  This is the same as the
 				 * easy case in _bt_next().
 				 */
-				if (++so->currPos.itemIndex > so->currPos.lastItem)
+				if (++currPos->itemIndex > currPos->lastItem)
 				{
 					/* let _bt_next do the heavy lifting */
 					if (!_bt_next(scan, ForwardScanDirection))
@@ -413,7 +415,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				}
 
 				/* Save tuple ID, and continue scanning */
-				heapTid = &so->currPos.items[so->currPos.itemIndex].heapTid;
+				heapTid = &currPos->items[currPos->itemIndex].heapTid;
 				tbm_add_tuples(tbm, heapTid, 1, false);
 				ntids++;
 			}
@@ -441,8 +443,8 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 
 	/* allocate private workspace */
 	so = (BTScanOpaque) palloc(sizeof(BTScanOpaqueData));
-	BTScanPosInvalidate(so->currPos);
-	BTScanPosInvalidate(so->markPos);
+	BTScanPosInvalidate(so->state.currPos);
+	BTScanPosInvalidate(so->state.markPos);
 	if (scan->numberOfKeys > 0)
 		so->keyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData));
 	else
@@ -453,15 +455,15 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	so->arrayKeys = NULL;
 	so->arrayContext = NULL;
 
-	so->killedItems = NULL;		/* until needed */
-	so->numKilled = 0;
+	so->state.killedItems = NULL;		/* until needed */
+	so->state.numKilled = 0;
 
 	/*
 	 * We don't know yet whether the scan will be index-only, so we do not
 	 * allocate the tuple workspace arrays until btrescan.  However, we set up
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
-	so->currTuples = so->markTuples = NULL;
+	so->state.currTuples = so->state.markTuples = NULL;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -470,6 +472,45 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	return scan;
 }
 
+static void
+_bt_release_current_position(BTScanState state, Relation indexRelation,
+							 bool invalidate)
+{
+	/* we aren't holding any read locks, but gotta drop the pins */
+	if (BTScanPosIsValid(state->currPos))
+	{
+		/* Before leaving current page, deal with any killed items */
+		if (state->numKilled > 0)
+			_bt_killitems(state, indexRelation);
+
+		BTScanPosUnpinIfPinned(state->currPos);
+
+		if (invalidate)
+			BTScanPosInvalidate(state->currPos);
+	}
+}
+
+static void
+_bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
+{
+	/* No need to invalidate positions, if the RAM is about to be freed. */
+	_bt_release_current_position(state, scan->indexRelation, !free);
+
+	state->markItemIndex = -1;
+	BTScanPosUnpinIfPinned(state->markPos);
+
+	if (free)
+	{
+		if (state->killedItems != NULL)
+			pfree(state->killedItems);
+		if (state->currTuples != NULL)
+			pfree(state->currTuples);
+		/* markTuples should not be pfree'd (_bt_allocate_tuple_workspaces) */
+	}
+	else
+		BTScanPosInvalidate(state->markPos);
+}
+
 /*
  *	btrescan() -- rescan an index relation
  */
@@ -478,20 +519,9 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 		 ScanKey orderbys, int norderbys)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-		BTScanPosInvalidate(so->currPos);
-	}
-
-	so->markItemIndex = -1;
-	BTScanPosUnpinIfPinned(so->markPos);
-	BTScanPosInvalidate(so->markPos);
+	_bt_release_scan_state(scan, state, false);
 
 	/*
 	 * Allocate tuple workspace arrays, if needed for an index-only scan and
@@ -509,11 +539,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 	 * a SIGSEGV is not possible.  Yeah, this is ugly as sin, but it beats
 	 * adding special-case treatment for name_ops elsewhere.
 	 */
-	if (scan->xs_want_itup && so->currTuples == NULL)
-	{
-		so->currTuples = (char *) palloc(BLCKSZ * 2);
-		so->markTuples = so->currTuples + BLCKSZ;
-	}
+	if (scan->xs_want_itup && state->currTuples == NULL)
+		_bt_allocate_tuple_workspaces(state);
 
 	/*
 	 * Reset the scan keys. Note that keys ordering stuff moved to _bt_first.
@@ -537,19 +564,7 @@ btendscan(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-	}
-
-	so->markItemIndex = -1;
-	BTScanPosUnpinIfPinned(so->markPos);
-
-	/* No need to invalidate positions, the RAM is about to be freed. */
+	_bt_release_scan_state(scan, &so->state, true);
 
 	/* Release storage */
 	if (so->keyData != NULL)
@@ -557,24 +572,15 @@ btendscan(IndexScanDesc scan)
 	/* so->arrayKeyData and so->arrayKeys are in arrayContext */
 	if (so->arrayContext != NULL)
 		MemoryContextDelete(so->arrayContext);
-	if (so->killedItems != NULL)
-		pfree(so->killedItems);
-	if (so->currTuples != NULL)
-		pfree(so->currTuples);
-	/* so->markTuples should not be pfree'd, see btrescan */
+
 	pfree(so);
 }
 
-/*
- *	btmarkpos() -- save current scan position
- */
-void
-btmarkpos(IndexScanDesc scan)
+static void
+_bt_mark_current_position(BTScanState state)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-
 	/* There may be an old mark with a pin (but no lock). */
-	BTScanPosUnpinIfPinned(so->markPos);
+	BTScanPosUnpinIfPinned(state->markPos);
 
 	/*
 	 * Just record the current itemIndex.  If we later step to next page
@@ -582,32 +588,34 @@ btmarkpos(IndexScanDesc scan)
 	 * the currPos struct in markPos.  If (as often happens) the mark is moved
 	 * before we leave the page, we don't have to do that work.
 	 */
-	if (BTScanPosIsValid(so->currPos))
-		so->markItemIndex = so->currPos.itemIndex;
+	if (BTScanPosIsValid(state->currPos))
+		state->markItemIndex = state->currPos.itemIndex;
 	else
 	{
-		BTScanPosInvalidate(so->markPos);
-		so->markItemIndex = -1;
+		BTScanPosInvalidate(state->markPos);
+		state->markItemIndex = -1;
 	}
-
-	/* Also record the current positions of any array keys */
-	if (so->numArrayKeys)
-		_bt_mark_array_keys(scan);
 }
 
 /*
- *	btrestrpos() -- restore scan to last saved position
+ *	btmarkpos() -- save current scan position
  */
 void
-btrestrpos(IndexScanDesc scan)
+btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* Restore the marked positions of any array keys */
+	_bt_mark_current_position(&so->state);
+
+	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
-		_bt_restore_array_keys(scan);
+		_bt_mark_array_keys(scan);
+}
 
-	if (so->markItemIndex >= 0)
+static void
+_bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
+{
+	if (state->markItemIndex >= 0)
 	{
 		/*
 		 * The scan has never moved to a new page since the last mark.  Just
@@ -616,7 +624,7 @@ btrestrpos(IndexScanDesc scan)
 		 * NB: In this case we can't count on anything in so->markPos to be
 		 * accurate.
 		 */
-		so->currPos.itemIndex = so->markItemIndex;
+		state->currPos.itemIndex = state->markItemIndex;
 	}
 	else
 	{
@@ -626,32 +634,40 @@ btrestrpos(IndexScanDesc scan)
 		 * locks, but if we're still holding the pin for the current position,
 		 * we must drop it.
 		 */
-		if (BTScanPosIsValid(so->currPos))
-		{
-			/* Before leaving current page, deal with any killed items */
-			if (so->numKilled > 0)
-				_bt_killitems(scan);
-			BTScanPosUnpinIfPinned(so->currPos);
-		}
+		_bt_release_current_position(state, scan->indexRelation,
+									 !BTScanPosIsValid(state->markPos));
 
-		if (BTScanPosIsValid(so->markPos))
+		if (BTScanPosIsValid(state->markPos))
 		{
 			/* bump pin on mark buffer for assignment to current buffer */
-			if (BTScanPosIsPinned(so->markPos))
-				IncrBufferRefCount(so->markPos.buf);
-			memcpy(&so->currPos, &so->markPos,
+			if (BTScanPosIsPinned(state->markPos))
+				IncrBufferRefCount(state->markPos.buf);
+			memcpy(&state->currPos, &state->markPos,
 				   offsetof(BTScanPosData, items[1]) +
-				   so->markPos.lastItem * sizeof(BTScanPosItem));
-			if (so->currTuples)
-				memcpy(so->currTuples, so->markTuples,
-					   so->markPos.nextTupleOffset);
+				   state->markPos.lastItem * sizeof(BTScanPosItem));
+			if (state->currTuples)
+				memcpy(state->currTuples, state->markTuples,
+					   state->markPos.nextTupleOffset);
 		}
-		else
-			BTScanPosInvalidate(so->currPos);
 	}
 }
 
 /*
+ *	btrestrpos() -- restore scan to last saved position
+ */
+void
+btrestrpos(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
+	/* Restore the marked positions of any array keys */
+	if (so->numArrayKeys)
+		_bt_restore_array_keys(scan);
+
+	_bt_restore_marked_position(scan, &so->state);
+}
+
+/*
  * Bulk deletion of all index entries pointing to a set of heap tuples.
  * The set of target tuples is specified via a callback routine that tells
  * whether any given heap tuple (identified by ItemPointer) is being deleted.
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index b6459d2..c041056 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -25,11 +25,11 @@
 #include "utils/tqual.h"
 
 
-static bool _bt_readpage(IndexScanDesc scan, ScanDirection dir,
+static bool _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 			 OffsetNumber offnum);
-static void _bt_saveitem(BTScanOpaque so, int itemIndex,
+static void _bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup);
-static bool _bt_steppage(IndexScanDesc scan, ScanDirection dir);
+static bool _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf, Snapshot snapshot);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
@@ -509,6 +509,58 @@ _bt_compare(Relation rel,
 }
 
 /*
+ * _bt_return_current_item() -- Prepare current scan state item for return.
+ *
+ * This function is used only in "return _bt_return_current_item();" statements
+ * and always returns true.
+ */
+static inline bool
+_bt_return_current_item(IndexScanDesc scan, BTScanState state)
+{
+	BTScanPosItem *currItem = &state->currPos.items[state->currPos.itemIndex];
+
+	scan->xs_ctup.t_self = currItem->heapTid;
+
+	if (scan->xs_want_itup)
+		scan->xs_itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+
+	return true;
+}
+
+/*
+ * _bt_load_first_page() -- Load data from the first page of the scan.
+ *
+ * Caller must have pinned and read-locked state->currPos.buf.
+ *
+ * On success exit, state->currPos is updated to contain data from the next
+ * interesting page.  For success on a scan using a non-MVCC snapshot we hold
+ * a pin, but not a read lock, on that page.  If we do not hold the pin, we
+ * set state->currPos.buf to InvalidBuffer.  We return true to indicate success.
+ *
+ * If there are no more matching records in the given direction at all,
+ * we drop all locks and pins, set state->currPos.buf to InvalidBuffer,
+ * and return false.
+ */
+static bool
+_bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+					OffsetNumber offnum)
+{
+	if (!_bt_readpage(scan, state, dir, offnum))
+	{
+		/*
+		 * There's no actually-matching data on this page.  Try to advance to
+		 * the next page.  Return false if there's no matching data at all.
+		 */
+		LockBuffer(state->currPos.buf, BUFFER_LOCK_UNLOCK);
+		return _bt_steppage(scan, state, dir);
+	}
+
+	/* Drop the lock, and maybe the pin, on the current page */
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
+	return true;
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -533,6 +585,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	BTStack		stack;
 	OffsetNumber offnum;
@@ -545,9 +598,8 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	int			keysCount = 0;
 	int			i;
 	StrategyNumber strat_total;
-	BTScanPosItem *currItem;
 
-	Assert(!BTScanPosIsValid(so->currPos));
+	Assert(!BTScanPosIsValid(*currPos));
 
 	pgstat_count_index_scan(rel);
 
@@ -1002,16 +1054,16 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	/* initialize moreLeft/moreRight appropriately for scan direction */
 	if (ScanDirectionIsForward(dir))
 	{
-		so->currPos.moreLeft = false;
-		so->currPos.moreRight = true;
+		currPos->moreLeft = false;
+		currPos->moreRight = true;
 	}
 	else
 	{
-		so->currPos.moreLeft = true;
-		so->currPos.moreRight = false;
+		currPos->moreLeft = true;
+		currPos->moreRight = false;
 	}
-	so->numKilled = 0;			/* just paranoia */
-	Assert(so->markItemIndex == -1);
+	so->state.numKilled = 0;	/* just paranoia */
+	Assert(so->state.markItemIndex == -1);
 
 	/* position to the precise item on the page */
 	offnum = _bt_binsrch(rel, buf, keysCount, scankeys, nextkey);
@@ -1038,35 +1090,35 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		offnum = OffsetNumberPrev(offnum);
 
 	/* remember which buffer we have pinned, if any */
-	Assert(!BTScanPosIsValid(so->currPos));
-	so->currPos.buf = buf;
+	Assert(!BTScanPosIsValid(*currPos));
+	currPos->buf = buf;
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, offnum))
+	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
+		return false;
+
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
+}
+
+/*
+ *	Advance to next tuple on current page; or if there's no more,
+ *	try to step to the next page with data.
+ */
+static bool
+_bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
+{
+	if (ScanDirectionIsForward(dir))
 	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
+		if (++state->currPos.itemIndex <= state->currPos.lastItem)
+			return true;
 	}
 	else
 	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+		if (--state->currPos.itemIndex >= state->currPos.firstItem)
+			return true;
 	}
 
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_steppage(scan, state, dir);
 }
 
 /*
@@ -1087,44 +1139,20 @@ bool
 _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-	BTScanPosItem *currItem;
 
-	/*
-	 * Advance to next tuple on current page; or if there's no more, try to
-	 * step to the next page with data.
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (++so->currPos.itemIndex > so->currPos.lastItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
-	else
-	{
-		if (--so->currPos.itemIndex < so->currPos.firstItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
+	if (!_bt_next_item(scan, &so->state, dir))
+		return false;
 
 	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
  *	_bt_readpage() -- Load data from current index page into so->currPos
  *
- * Caller must have pinned and read-locked so->currPos.buf; the buffer's state
- * is not changed here.  Also, currPos.moreLeft and moreRight must be valid;
- * they are updated as appropriate.  All other fields of so->currPos are
+ * Caller must have pinned and read-locked pos->buf; the buffer's state
+ * is not changed here.  Also, pos->moreLeft and moreRight must be valid;
+ * they are updated as appropriate.  All other fields of pos are
  * initialized from scratch here.
  *
  * We scan the current page starting at offnum and moving in the indicated
@@ -1135,9 +1163,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
  * Returns true if any matching items found on the page, false if none.
  */
 static bool
-_bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
+_bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+			 OffsetNumber offnum)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
@@ -1150,9 +1179,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We must have the buffer pinned and locked, but the usual macro can't be
 	 * used here; this function is what makes it good for currPos.
 	 */
-	Assert(BufferIsValid(so->currPos.buf));
+	Assert(BufferIsValid(pos->buf));
 
-	page = BufferGetPage(so->currPos.buf);
+	page = BufferGetPage(pos->buf);
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 	minoff = P_FIRSTDATAKEY(opaque);
 	maxoff = PageGetMaxOffsetNumber(page);
@@ -1161,30 +1190,30 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We note the buffer's block number so that we can release the pin later.
 	 * This allows us to re-read the buffer if it is needed again for hinting.
 	 */
-	so->currPos.currPage = BufferGetBlockNumber(so->currPos.buf);
+	pos->currPage = BufferGetBlockNumber(pos->buf);
 
 	/*
 	 * We save the LSN of the page as we read it, so that we know whether it
 	 * safe to apply LP_DEAD hints to the page later.  This allows us to drop
 	 * the pin for MVCC scans, which allows vacuum to avoid blocking.
 	 */
-	so->currPos.lsn = PageGetLSN(page);
+	pos->lsn = PageGetLSN(page);
 
 	/*
 	 * we must save the page's right-link while scanning it; this tells us
 	 * where to step right to after we're done with these items.  There is no
 	 * corresponding need for the left-link, since splits always go right.
 	 */
-	so->currPos.nextPage = opaque->btpo_next;
+	pos->nextPage = opaque->btpo_next;
 
 	/* initialize tuple workspace to empty */
-	so->currPos.nextTupleOffset = 0;
+	pos->nextTupleOffset = 0;
 
 	/*
 	 * Now that the current page has been made consistent, the macro should be
 	 * good.
 	 */
-	Assert(BTScanPosIsPinned(so->currPos));
+	Assert(BTScanPosIsPinned(*pos));
 
 	if (ScanDirectionIsForward(dir))
 	{
@@ -1199,13 +1228,13 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			if (itup != NULL)
 			{
 				/* tuple passes all scan key conditions, so remember it */
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 				itemIndex++;
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreRight = false;
+				pos->moreRight = false;
 				break;
 			}
 
@@ -1213,9 +1242,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex <= MaxIndexTuplesPerPage);
-		so->currPos.firstItem = 0;
-		so->currPos.lastItem = itemIndex - 1;
-		so->currPos.itemIndex = 0;
+		pos->firstItem = 0;
+		pos->lastItem = itemIndex - 1;
+		pos->itemIndex = 0;
 	}
 	else
 	{
@@ -1231,12 +1260,12 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			{
 				/* tuple passes all scan key conditions, so remember it */
 				itemIndex--;
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreLeft = false;
+				pos->moreLeft = false;
 				break;
 			}
 
@@ -1244,30 +1273,31 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex >= 0);
-		so->currPos.firstItem = itemIndex;
-		so->currPos.lastItem = MaxIndexTuplesPerPage - 1;
-		so->currPos.itemIndex = MaxIndexTuplesPerPage - 1;
+		pos->firstItem = itemIndex;
+		pos->lastItem = MaxIndexTuplesPerPage - 1;
+		pos->itemIndex = MaxIndexTuplesPerPage - 1;
 	}
 
-	return (so->currPos.firstItem <= so->currPos.lastItem);
+	return (pos->firstItem <= pos->lastItem);
 }
 
 /* Save an index item into so->currPos.items[itemIndex] */
 static void
-_bt_saveitem(BTScanOpaque so, int itemIndex,
+_bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup)
 {
-	BTScanPosItem *currItem = &so->currPos.items[itemIndex];
+	BTScanPosItem *currItem = &state->currPos.items[itemIndex];
 
 	currItem->heapTid = itup->t_tid;
 	currItem->indexOffset = offnum;
-	if (so->currTuples)
+	if (state->currTuples)
 	{
 		Size		itupsz = IndexTupleSize(itup);
 
-		currItem->tupleOffset = so->currPos.nextTupleOffset;
-		memcpy(so->currTuples + so->currPos.nextTupleOffset, itup, itupsz);
-		so->currPos.nextTupleOffset += MAXALIGN(itupsz);
+		currItem->tupleOffset = state->currPos.nextTupleOffset;
+		memcpy(state->currTuples + state->currPos.nextTupleOffset,
+			   itup, itupsz);
+		state->currPos.nextTupleOffset += MAXALIGN(itupsz);
 	}
 }
 
@@ -1287,65 +1317,63 @@ _bt_saveitem(BTScanOpaque so, int itemIndex,
  * locks and pins, set so->currPos.buf to InvalidBuffer, and return FALSE.
  */
 static bool
-_bt_steppage(IndexScanDesc scan, ScanDirection dir)
+_bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-	Relation	rel;
+	BTScanPos	currPos = &state->currPos;
+	Relation	rel = scan->indexRelation;
 	Page		page;
 	BTPageOpaque opaque;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(*currPos));
 
 	/* Before leaving current page, deal with any killed items */
-	if (so->numKilled > 0)
-		_bt_killitems(scan);
+	if (state->numKilled > 0)
+		_bt_killitems(state, rel);
 
 	/*
 	 * Before we modify currPos, make a copy of the page data if there was a
 	 * mark position that needs it.
 	 */
-	if (so->markItemIndex >= 0)
+	if (state->markItemIndex >= 0)
 	{
 		/* bump pin on current buffer for assignment to mark buffer */
-		if (BTScanPosIsPinned(so->currPos))
-			IncrBufferRefCount(so->currPos.buf);
-		memcpy(&so->markPos, &so->currPos,
+		if (BTScanPosIsPinned(*currPos))
+			IncrBufferRefCount(currPos->buf);
+		memcpy(&state->markPos, currPos,
 			   offsetof(BTScanPosData, items[1]) +
-			   so->currPos.lastItem * sizeof(BTScanPosItem));
-		if (so->markTuples)
-			memcpy(so->markTuples, so->currTuples,
-				   so->currPos.nextTupleOffset);
-		so->markPos.itemIndex = so->markItemIndex;
-		so->markItemIndex = -1;
+			   currPos->lastItem * sizeof(BTScanPosItem));
+		if (state->markTuples)
+			memcpy(state->markTuples, state->currTuples,
+				   currPos->nextTupleOffset);
+		state->markPos.itemIndex = state->markItemIndex;
+		state->markItemIndex = -1;
 	}
 
-	rel = scan->indexRelation;
-
 	if (ScanDirectionIsForward(dir))
 	{
 		/* Walk right to the next page with data */
 		/* We must rely on the previously saved nextPage link! */
-		BlockNumber blkno = so->currPos.nextPage;
+		BlockNumber blkno = currPos->nextPage;
 
 		/* Remember we left a page with data */
-		so->currPos.moreLeft = true;
+		currPos->moreLeft = true;
 
 		/* release the previous buffer, if pinned */
-		BTScanPosUnpinIfPinned(so->currPos);
+		BTScanPosUnpinIfPinned(*currPos);
 
 		for (;;)
 		{
 			/* if we're at end of scan, give up */
-			if (blkno == P_NONE || !so->currPos.moreRight)
+			if (blkno == P_NONE || !currPos->moreRight)
 			{
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 			/* check for interrupts while we're not holding any buffer lock */
 			CHECK_FOR_INTERRUPTS();
 			/* step right one page */
-			so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
-			page = BufferGetPage(so->currPos.buf);
+			currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			/* check for deleted page */
@@ -1354,19 +1382,19 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 				PredicateLockPage(rel, blkno, scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreRight if we can stop */
-				if (_bt_readpage(scan, dir, P_FIRSTDATAKEY(opaque)))
+				if (_bt_readpage(scan, state, dir, P_FIRSTDATAKEY(opaque)))
 					break;
 			}
 
 			/* nope, keep going */
 			blkno = opaque->btpo_next;
-			_bt_relbuf(rel, so->currPos.buf);
+			_bt_relbuf(rel, currPos->buf);
 		}
 	}
 	else
 	{
 		/* Remember we left a page with data */
-		so->currPos.moreRight = true;
+		currPos->moreRight = true;
 
 		/*
 		 * Walk left to the next page with data.  This is much more complex
@@ -1390,29 +1418,28 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 		 * is MVCC the page cannot move past the half-dead state to fully
 		 * deleted.
 		 */
-		if (BTScanPosIsPinned(so->currPos))
-			LockBuffer(so->currPos.buf, BT_READ);
+		if (BTScanPosIsPinned(*currPos))
+			LockBuffer(currPos->buf, BT_READ);
 		else
-			so->currPos.buf = _bt_getbuf(rel, so->currPos.currPage, BT_READ);
+			currPos->buf = _bt_getbuf(rel, currPos->currPage, BT_READ);
 
 		for (;;)
 		{
 			/* Done if we know there are no matching keys to the left */
-			if (!so->currPos.moreLeft)
+			if (!currPos->moreLeft)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
-				BTScanPosInvalidate(so->currPos);
+				_bt_relbuf(rel, currPos->buf);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
 			/* Step to next physical page */
-			so->currPos.buf = _bt_walk_left(rel, so->currPos.buf,
-											scan->xs_snapshot);
+			currPos->buf = _bt_walk_left(rel, currPos->buf, scan->xs_snapshot);
 
 			/* if we're physically at end of index, return failure */
-			if (so->currPos.buf == InvalidBuffer)
+			if (currPos->buf == InvalidBuffer)
 			{
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
@@ -1421,22 +1448,22 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			 * it's not half-dead and contains matching tuples. Else loop back
 			 * and do it all again.
 			 */
-			page = BufferGetPage(so->currPos.buf);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			if (!P_IGNORE(opaque))
 			{
-				PredicateLockPage(rel, BufferGetBlockNumber(so->currPos.buf), scan->xs_snapshot);
+				PredicateLockPage(rel, BufferGetBlockNumber(currPos->buf), scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreLeft if we can stop */
-				if (_bt_readpage(scan, dir, PageGetMaxOffsetNumber(page)))
+				if (_bt_readpage(scan, state, dir, PageGetMaxOffsetNumber(page)))
 					break;
 			}
 		}
 	}
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, currPos);
 
 	return true;
 }
@@ -1661,11 +1688,11 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber start;
-	BTScanPosItem *currItem;
 
 	/*
 	 * Scan down to the leftmost or rightmost leaf page.  This is a simplified
@@ -1681,7 +1708,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 		 * exists.
 		 */
 		PredicateLockRelation(rel, scan->xs_snapshot);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 		return false;
 	}
 
@@ -1710,46 +1737,25 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 	}
 
 	/* remember which buffer we have pinned */
-	so->currPos.buf = buf;
+	currPos->buf = buf;
 
 	/* initialize moreLeft/moreRight appropriately for scan direction */
 	if (ScanDirectionIsForward(dir))
 	{
-		so->currPos.moreLeft = false;
-		so->currPos.moreRight = true;
+		currPos->moreLeft = false;
+		currPos->moreRight = true;
 	}
 	else
 	{
-		so->currPos.moreLeft = true;
-		so->currPos.moreRight = false;
+		currPos->moreLeft = true;
+		currPos->moreRight = false;
 	}
-	so->numKilled = 0;			/* just paranoia */
-	so->markItemIndex = -1;		/* ditto */
+	so->state.numKilled = 0;	/* just paranoia */
+	so->state.markItemIndex = -1;		/* ditto */
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, start))
-	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
-	}
-	else
-	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
-	}
-
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
+	if (!_bt_load_first_page(scan, &so->state, dir, start))
+		return false;
 
-	return true;
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index da0f330..ebcba7e 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -1725,26 +1725,26 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, TupleDesc tupdesc,
  * away and the TID was re-used by a completely different heap tuple.
  */
 void
-_bt_killitems(IndexScanDesc scan)
+_bt_killitems(BTScanState state, Relation indexRelation)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
 	OffsetNumber maxoff;
 	int			i;
-	int			numKilled = so->numKilled;
+	int			numKilled = state->numKilled;
 	bool		killedsomething = false;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(state->currPos));
 
 	/*
 	 * Always reset the scan state, so we don't look for same items on other
 	 * pages.
 	 */
-	so->numKilled = 0;
+	state->numKilled = 0;
 
-	if (BTScanPosIsPinned(so->currPos))
+	if (BTScanPosIsPinned(*pos))
 	{
 		/*
 		 * We have held the pin on this page since we read the index tuples,
@@ -1752,28 +1752,28 @@ _bt_killitems(IndexScanDesc scan)
 		 * re-use of any TID on the page, so there is no need to check the
 		 * LSN.
 		 */
-		LockBuffer(so->currPos.buf, BT_READ);
+		LockBuffer(pos->buf, BT_READ);
 
-		page = BufferGetPage(so->currPos.buf);
+		page = BufferGetPage(pos->buf);
 	}
 	else
 	{
 		Buffer		buf;
 
 		/* Attempt to re-read the buffer, getting pin and lock. */
-		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ);
+		buf = _bt_getbuf(indexRelation, pos->currPage, BT_READ);
 
 		/* It might not exist anymore; in which case we can't hint it. */
 		if (!BufferIsValid(buf))
 			return;
 
 		page = BufferGetPage(buf);
-		if (PageGetLSN(page) == so->currPos.lsn)
-			so->currPos.buf = buf;
+		if (PageGetLSN(page) == pos->lsn)
+			pos->buf = buf;
 		else
 		{
 			/* Modified while not pinned means hinting is not safe. */
-			_bt_relbuf(scan->indexRelation, buf);
+			_bt_relbuf(indexRelation, buf);
 			return;
 		}
 	}
@@ -1784,12 +1784,12 @@ _bt_killitems(IndexScanDesc scan)
 
 	for (i = 0; i < numKilled; i++)
 	{
-		int			itemIndex = so->killedItems[i];
-		BTScanPosItem *kitem = &so->currPos.items[itemIndex];
+		int			itemIndex = state->killedItems[i];
+		BTScanPosItem *kitem = &pos->items[itemIndex];
 		OffsetNumber offnum = kitem->indexOffset;
 
-		Assert(itemIndex >= so->currPos.firstItem &&
-			   itemIndex <= so->currPos.lastItem);
+		Assert(itemIndex >= pos->firstItem &&
+			   itemIndex <= pos->lastItem);
 		if (offnum < minoff)
 			continue;			/* pure paranoia */
 		while (offnum <= maxoff)
@@ -1817,10 +1817,10 @@ _bt_killitems(IndexScanDesc scan)
 	if (killedsomething)
 	{
 		opaque->btpo_flags |= BTP_HAS_GARBAGE;
-		MarkBufferDirtyHint(so->currPos.buf, true);
+		MarkBufferDirtyHint(pos->buf, true);
 	}
 
-	LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
+	LockBuffer(pos->buf, BUFFER_LOCK_UNLOCK);
 }
 
 
@@ -2065,3 +2065,14 @@ btproperty(Oid index_oid, int attno,
 			return false;		/* punt to generic code */
 	}
 }
+
+/*
+ * _bt_allocate_tuple_workspaces() -- Allocate buffers for saving index tuples
+ * 		in index-only scans.
+ */
+void
+_bt_allocate_tuple_workspaces(BTScanState state)
+{
+	state->currTuples = (char *) palloc(BLCKSZ * 2);
+	state->markTuples = state->currTuples + BLCKSZ;
+}
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 011a72e..4124010 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -598,20 +598,8 @@ typedef struct BTArrayKeyInfo
 	Datum	   *elem_values;	/* array of num_elems Datums */
 } BTArrayKeyInfo;
 
-typedef struct BTScanOpaqueData
+typedef struct BTScanStateData
 {
-	/* these fields are set by _bt_preprocess_keys(): */
-	bool		qual_ok;		/* false if qual can never be satisfied */
-	int			numberOfKeys;	/* number of preprocessed scan keys */
-	ScanKey		keyData;		/* array of preprocessed scan keys */
-
-	/* workspace for SK_SEARCHARRAY support */
-	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
-	int			numArrayKeys;	/* number of equality-type array keys (-1 if
-								 * there are any unsatisfiable array keys) */
-	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
-	MemoryContext arrayContext; /* scan-lifespan context for array data */
-
 	/* info about killed items if any (killedItems is NULL if never used) */
 	int		   *killedItems;	/* currPos.items indexes of killed items */
 	int			numKilled;		/* number of currently stored items */
@@ -636,6 +624,23 @@ typedef struct BTScanOpaqueData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+}	BTScanStateData, *BTScanState;
+
+typedef struct BTScanOpaqueData
+{
+	/* these fields are set by _bt_preprocess_keys(): */
+	bool		qual_ok;		/* false if qual can never be satisfied */
+	int			numberOfKeys;	/* number of preprocessed scan keys */
+	ScanKey		keyData;		/* array of preprocessed scan keys */
+
+	/* workspace for SK_SEARCHARRAY support */
+	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
+	int			numArrayKeys;	/* number of equality-type array keys (-1 if
+								 * there are any unsatisfiable array keys) */
+	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
+	MemoryContext arrayContext; /* scan-lifespan context for array data */
+
+	BTScanStateData	state;
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -739,7 +744,7 @@ extern void _bt_preprocess_keys(IndexScanDesc scan);
 extern IndexTuple _bt_checkkeys(IndexScanDesc scan,
 			  Page page, OffsetNumber offnum,
 			  ScanDirection dir, bool *continuescan);
-extern void _bt_killitems(IndexScanDesc scan);
+extern void _bt_killitems(BTScanState state, Relation indexRelation);
 extern BTCycleId _bt_vacuum_cycleid(Relation rel);
 extern BTCycleId _bt_start_vacuum(Relation rel);
 extern void _bt_end_vacuum(Relation rel);
@@ -750,6 +755,7 @@ extern bytea *btoptions(Datum reloptions, bool validate);
 extern bool btproperty(Oid index_oid, int attno,
 		   IndexAMProperty prop, const char *propname,
 		   bool *res, bool *isnull);
+extern void _bt_allocate_tuple_workspaces(BTScanState state);
 
 /*
  * prototypes for functions in nbtvalidate.c
0003-extract-get_index_column_opclass-and-get_opclass_opfamily_and_input_type-v02.patchtext/x-patch; name=0003-extract-get_index_column_opclass-and-get_opclass_opfamily_and_input_type-v02.patchDownload
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 159987e..986d5b8 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -24,6 +24,7 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/syscache.h"
+#include "utils/lsyscache.h"
 
 
 /*
@@ -852,12 +853,6 @@ gistproperty(Oid index_oid, int attno,
 			 IndexAMProperty prop, const char *propname,
 			 bool *res, bool *isnull)
 {
-	HeapTuple	tuple;
-	Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY;
-	Form_pg_opclass rd_opclass;
-	Datum		datum;
-	bool		disnull;
-	oidvector  *indclass;
 	Oid			opclass,
 				opfamily,
 				opcintype;
@@ -891,49 +886,28 @@ gistproperty(Oid index_oid, int attno,
 	}
 
 	/* First we need to know the column's opclass. */
-
-	tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
-	if (!HeapTupleIsValid(tuple))
+	opclass = get_index_column_opclass(index_oid, attno);
+	if (!OidIsValid(opclass))
 	{
 		*isnull = true;
 		return true;
 	}
-	rd_index = (Form_pg_index) GETSTRUCT(tuple);
-
-	/* caller is supposed to guarantee this */
-	Assert(attno > 0 && attno <= rd_index->indnatts);
-
-	datum = SysCacheGetAttr(INDEXRELID, tuple,
-							Anum_pg_index_indclass, &disnull);
-	Assert(!disnull);
-
-	indclass = ((oidvector *) DatumGetPointer(datum));
-	opclass = indclass->values[attno - 1];
-
-	ReleaseSysCache(tuple);
 
 	/* Now look up the opclass family and input datatype. */
-
-	tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
-	if (!HeapTupleIsValid(tuple))
+	if (!get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype))
 	{
 		*isnull = true;
 		return true;
 	}
-	rd_opclass = (Form_pg_opclass) GETSTRUCT(tuple);
-
-	opfamily = rd_opclass->opcfamily;
-	opcintype = rd_opclass->opcintype;
-
-	ReleaseSysCache(tuple);
 
 	/* And now we can check whether the function is provided. */
-
 	*res = SearchSysCacheExists4(AMPROCNUM,
 								 ObjectIdGetDatum(opfamily),
 								 ObjectIdGetDatum(opcintype),
 								 ObjectIdGetDatum(opcintype),
 								 Int16GetDatum(procno));
+	*isnull = false;
+
 	return true;
 }
 
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1b04c09..1d479fe 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1050,6 +1050,32 @@ get_opclass_input_type(Oid opclass)
 	return result;
 }
 
+/*
+ * get_opclass_family_and_input_type
+ *
+ *		Returns the OID of the operator family the opclass belongs to,
+ *				the OID of the datatype the opclass indexes
+ */
+bool
+get_opclass_opfamily_and_input_type(Oid opclass, Oid *opfamily, Oid *opcintype)
+{
+	HeapTuple	tp;
+	Form_pg_opclass cla_tup;
+
+	tp = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
+	if (!HeapTupleIsValid(tp))
+		return false;
+
+	cla_tup = (Form_pg_opclass) GETSTRUCT(tp);
+
+	*opfamily = cla_tup->opcfamily;
+	*opcintype = cla_tup->opcintype;
+
+	ReleaseSysCache(tp);
+
+	return true;
+}
+
 /*				---------- OPERATOR CACHE ----------					 */
 
 /*
@@ -3061,3 +3087,45 @@ get_range_subtype(Oid rangeOid)
 	else
 		return InvalidOid;
 }
+
+/*				---------- PG_INDEX CACHE ----------				 */
+
+/*
+ * get_index_column_opclass
+ *
+ *		Given the index OID and column number,
+ *		return opclass of the index column
+ *			or InvalidOid if the index was not found.
+ */
+Oid
+get_index_column_opclass(Oid index_oid, int attno)
+{
+	HeapTuple	tuple;
+	Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY;
+	Datum		datum;
+	bool		isnull;
+	oidvector  *indclass;
+	Oid			opclass;
+
+	/* First we need to know the column's opclass. */
+
+	tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
+	if (!HeapTupleIsValid(tuple))
+		return InvalidOid;
+
+	rd_index = (Form_pg_index) GETSTRUCT(tuple);
+
+	/* caller is supposed to guarantee this */
+	Assert(attno > 0 && attno <= rd_index->indnatts);
+
+	datum = SysCacheGetAttr(INDEXRELID, tuple,
+							Anum_pg_index_indclass, &isnull);
+	Assert(!isnull);
+
+	indclass = ((oidvector *) DatumGetPointer(datum));
+	opclass = indclass->values[attno - 1];
+
+	ReleaseSysCache(tuple);
+
+	return opclass;
+}
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b6d1fca..618c4e8 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -73,6 +73,8 @@ extern char *get_constraint_name(Oid conoid);
 extern char *get_language_name(Oid langoid, bool missing_ok);
 extern Oid	get_opclass_family(Oid opclass);
 extern Oid	get_opclass_input_type(Oid opclass);
+extern bool get_opclass_opfamily_and_input_type(Oid opclass,
+									Oid *opfamily, Oid *opcintype);
 extern RegProcedure get_opcode(Oid opno);
 extern char *get_opname(Oid opno);
 extern Oid	get_op_rettype(Oid opno);
@@ -159,6 +161,7 @@ extern void free_attstatsslot(Oid atttype,
 extern char *get_namespace_name(Oid nspid);
 extern char *get_namespace_name_or_temp(Oid nspid);
 extern Oid	get_range_subtype(Oid rangeOid);
+extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
0004-add-kNN-support-to-btree-v02.patchtext/x-patch; name=0004-add-kNN-support-to-btree-v02.patchDownload
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 271c135..480bfd0 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -248,7 +248,7 @@ CREATE INDEX <replaceable>name</replaceable> ON <replaceable>table</replaceable>
   </para>
 
   <para>
-   GiST indexes are also capable of optimizing <quote>nearest-neighbor</>
+   B-tree and GiST indexes are also capable of optimizing <quote>nearest-neighbor</>
    searches, such as
 <programlisting><![CDATA[
 SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 333a36c..6708653 100644
--- a/doc/src/sgml/xindex.sgml
+++ b/doc/src/sgml/xindex.sgml
@@ -1171,7 +1171,7 @@ ALTER OPERATOR FAMILY integer_ops USING btree ADD
   <title>Ordering Operators</title>
 
   <para>
-   Some index access methods (currently, only GiST) support the concept of
+   Some index access methods (currently, only B-tree and GiST) support the concept of
    <firstterm>ordering operators</>.  What we have been discussing so far
    are <firstterm>search operators</>.  A search operator is one for which
    the index can be searched to find all rows satisfying
diff --git a/src/backend/access/nbtree/README b/src/backend/access/nbtree/README
index a3f11da..764c0a9 100644
--- a/src/backend/access/nbtree/README
+++ b/src/backend/access/nbtree/README
@@ -624,6 +624,24 @@ item is irrelevant, and need not be stored at all.  This arrangement
 corresponds to the fact that an L&Y non-leaf page has one more pointer
 than key.
 
+Nearest-neighbor search
+-----------------------
+
+There is a special scan strategy for nearest-neighbor (kNN) search,
+that is used in queries with ORDER BY distance clauses like this:
+SELECT * FROM tab WHERE col > const1 ORDER BY col <-> const2 LIMIT k.
+But, unlike GiST, B-tree supports only a one ordering operator on the
+first index column.
+
+At the beginning of kNN scan, we need to determine which strategy we
+will use --- a special bidirectional or a ordinary unidirectional.
+If the point from which we measure the distance falls into the scan range,
+we use bidirectional scan starting from this point, else we use simple
+unidirectional scan in the right direction.  Algorithm of a bidirectional
+scan is very simple: at each step we advancing scan in that direction,
+which has the nearest point.
+
+
 Notes to Operator Class Implementors
 ------------------------------------
 
@@ -660,6 +678,18 @@ procedure, of course.)
 The other three operators are defined in terms of these two in the obvious way,
 and must act consistently with them.
 
+To implement the distance ordered (nearest-neighbor) search, we only need
+to define a distance operator (usually it called <->) with a correpsonding
+operator family for distance comparison in the index's operator class.
+These operators must satisfy the following assumptions for all non-null
+values A,B,C of the datatype:
+
+	A <-> B = B <-> A								symmetric law
+	if A = B, then A <-> C = B <-> C				distance equivalence
+	if (A <= B and B <= C) or (A >= B and B >= C),
+		then A <-> B <= A <-> C						monotonicity
+
+
 For an operator family supporting multiple datatypes, the above laws must hold
 when A,B,C are taken from any datatypes in the family.  The transitive laws
 are the trickiest to ensure, as in cross-type situations they represent
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index c2da15e..2f476df 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -23,12 +23,16 @@
 #include "access/xlog.h"
 #include "catalog/index.h"
 #include "commands/vacuum.h"
+#include "nodes/primnodes.h"
+#include "nodes/relation.h"
+#include "optimizer/paths.h"
 #include "storage/indexfsm.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"		/* pgrminclude ignore */
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/index_selfuncs.h"
 #include "utils/memutils.h"
 
@@ -76,6 +80,10 @@ static void btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 static void btvacuumpage(BTVacState *vstate, BlockNumber blkno,
 			 BlockNumber orig_blkno);
 
+static Expr *btcanorderbyop(IndexOptInfo *index,
+							PathKey *pathkey, int pathkeyno,
+							Expr *expr, int *indexcol_p);
+
 
 /*
  * Btree handler function: return IndexAmRoutine with access method parameters
@@ -86,7 +94,7 @@ bthandler(PG_FUNCTION_ARGS)
 {
 	IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
 
-	amroutine->amstrategies = BTMaxStrategyNumber;
+	amroutine->amstrategies = BtreeMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
 	amroutine->amcanbackward = true;
@@ -117,10 +125,10 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amendscan = btendscan;
 	amroutine->ammarkpos = btmarkpos;
 	amroutine->amrestrpos = btrestrpos;
+	amroutine->amcanorderbyop = btcanorderbyop;
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
-	amroutine->amcanorderbyop = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -304,6 +312,10 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 
 	/* btree indexes are never lossy */
 	scan->xs_recheck = false;
+	scan->xs_recheckorderby = false;
+
+	if (so->scanDirection != NoMovementScanDirection)
+		dir = so->scanDirection;
 
 	/*
 	 * If we have any array keys, initialize them during first call for a
@@ -327,7 +339,8 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(state->currPos))
+		if (!BTScanPosIsValid(state->currPos) &&
+			(!so->knnState || !BTScanPosIsValid(so->knnState->currPos)))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -435,9 +448,6 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	IndexScanDesc scan;
 	BTScanOpaque so;
 
-	/* no order by operators allowed */
-	Assert(norderbys == 0);
-
 	/* get the scan */
 	scan = RelationGetIndexScan(rel, nkeys, norderbys);
 
@@ -464,6 +474,9 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
 	so->state.currTuples = so->state.markTuples = NULL;
+	so->knnState = NULL;
+	so->distanceTypeByVal = true;
+	so->scanDirection = NoMovementScanDirection;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -493,6 +506,8 @@ _bt_release_current_position(BTScanState state, Relation indexRelation,
 static void
 _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	/* No need to invalidate positions, if the RAM is about to be freed. */
 	_bt_release_current_position(state, scan->indexRelation, !free);
 
@@ -509,6 +524,14 @@ _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 	}
 	else
 		BTScanPosInvalidate(state->markPos);
+
+	if (!so->distanceTypeByVal)
+	{
+		pfree(DatumGetPointer(state->currDistance));
+		state->currDistance = PointerGetDatum(NULL);
+		pfree(DatumGetPointer(state->markDistance));
+		state->markDistance = PointerGetDatum(NULL);
+	}
 }
 
 /*
@@ -523,6 +546,13 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 
 	_bt_release_scan_state(scan, state, false);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+		so->knnState = NULL;
+	}
+
 	/*
 	 * Allocate tuple workspace arrays, if needed for an index-only scan and
 	 * not already done in a previous rescan call.  To save on palloc
@@ -552,6 +582,14 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 				scan->numberOfKeys * sizeof(ScanKeyData));
 	so->numberOfKeys = 0;		/* until _bt_preprocess_keys sets it */
 
+	if (orderbys && scan->numberOfOrderBys > 0)
+		memmove(scan->orderByData,
+				orderbys,
+				scan->numberOfOrderBys * sizeof(ScanKeyData));
+
+	so->scanDirection = NoMovementScanDirection;
+	so->distanceTypeByVal = true;
+
 	/* If any keys are SK_SEARCHARRAY type, set up array-key info */
 	_bt_preprocess_array_keys(scan);
 }
@@ -566,6 +604,12 @@ btendscan(IndexScanDesc scan)
 
 	_bt_release_scan_state(scan, &so->state, true);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+	}
+
 	/* Release storage */
 	if (so->keyData != NULL)
 		pfree(so->keyData);
@@ -577,7 +621,7 @@ btendscan(IndexScanDesc scan)
 }
 
 static void
-_bt_mark_current_position(BTScanState state)
+_bt_mark_current_position(BTScanOpaque so, BTScanState state)
 {
 	/* There may be an old mark with a pin (but no lock). */
 	BTScanPosUnpinIfPinned(state->markPos);
@@ -595,6 +639,21 @@ _bt_mark_current_position(BTScanState state)
 		BTScanPosInvalidate(state->markPos);
 		state->markItemIndex = -1;
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->markDistance));
+
+		state->markIsNull = !BTScanPosIsValid(state->currPos) ||
+			state->currIsNull;
+
+		state->markDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->currDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -605,7 +664,13 @@ btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_mark_current_position(&so->state);
+	_bt_mark_current_position(so, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_mark_current_position(so, so->knnState);
+		so->markRightIsNearest = so->currRightIsNearest;
+	}
 
 	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
@@ -615,6 +680,8 @@ btmarkpos(IndexScanDesc scan)
 static void
 _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	if (state->markItemIndex >= 0)
 	{
 		/*
@@ -650,6 +717,19 @@ _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 					   state->markPos.nextTupleOffset);
 		}
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->currDistance));
+
+		state->currIsNull = state->markIsNull;
+		state->currDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->markDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -665,6 +745,12 @@ btrestrpos(IndexScanDesc scan)
 		_bt_restore_array_keys(scan);
 
 	_bt_restore_marked_position(scan, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_restore_marked_position(scan, so->knnState);
+		so->currRightIsNearest = so->markRightIsNearest;
+	}
 }
 
 /*
@@ -1152,3 +1238,26 @@ btcanreturn(Relation index, int attno)
 {
 	return true;
 }
+
+/*
+ *	btcanorderbyop() -- Check whether KNN-search strategy is applicable to
+ *		the given ORDER BY distance operator.
+ */
+static Expr *
+btcanorderbyop(IndexOptInfo *index, PathKey *pathkey, int pathkeyno,
+			   Expr *expr, int *indexcol_p)
+{
+	if (pathkeyno > 1)
+		return NULL; /* only one ORDER BY clause is supported */
+
+	expr = match_clause_to_ordering_op(index,
+									   0, /* ORDER BY distance to the first
+										   * index column is only supported */
+									   expr,
+									   pathkey->pk_opfamily);
+
+	if (expr)
+		*indexcol_p = 0;
+
+	return expr;
+}
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index c041056..97a1061 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -561,6 +561,127 @@ _bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 }
 
 /*
+ *  _bt_calc_current_dist() -- Calculate distance from the current item
+ *		of the scan state to the target order-by ScanKey argument.
+ */
+static void
+_bt_calc_current_dist(IndexScanDesc scan, BTScanState state)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanPosItem  *currItem = &state->currPos.items[state->currPos.itemIndex];
+	IndexTuple		itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+	ScanKey			scankey = &scan->orderByData[0];
+	Datum			value;
+
+	value = index_getattr(itup, 1, scan->xs_itupdesc, &state->currIsNull);
+
+	if (state->currIsNull)
+		return; /* NULL distance */
+
+	value = FunctionCall2Coll(&scankey->sk_func,
+							  scankey->sk_collation,
+							  value,
+							  scankey->sk_argument);
+
+	/* free previous distance value for by-ref types */
+	if (!so->distanceTypeByVal)
+		pfree(DatumGetPointer(state->currDistance));
+
+	state->currDistance = value;
+}
+
+/*
+ *  _bt_compare_current_dist() -- Compare current distances of the left and right scan states.
+ *
+ *  NULL distances are considered to be greater than any non-NULL distances.
+ *
+ *  Returns true if right distance is lesser than left, otherwise false.
+ */
+static bool
+_bt_compare_current_dist(BTScanOpaque so, BTScanState rstate, BTScanState lstate)
+{
+	if (lstate->currIsNull)
+		return true; /* non-NULL < NULL */
+
+	if (rstate->currIsNull)
+		return false; /* NULL > non-NULL */
+
+	return DatumGetBool(FunctionCall2Coll(&so->distanceCmpProc,
+										  InvalidOid, /* XXX collation for distance comparison */
+										  rstate->currDistance,
+										  lstate->currDistance));
+}
+
+/*
+ * _bt_init_knn_scan() -- Init additional scan state for KNN search.
+ *
+ * Caller must pin and read-lock scan->state.currPos.buf buffer.
+ *
+ * If empty result was found returned false.
+ * Otherwise prepared current item, and returned true.
+ */
+static bool
+_bt_init_knn_scan(IndexScanDesc scan, OffsetNumber offnum)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		rstate = &so->state; /* right (forward) main scan state */
+	BTScanState		lstate; /* additional left (backward) KNN scan state */
+	Buffer			buf = rstate->currPos.buf;
+	bool			left,
+					right;
+
+	lstate = so->knnState = (BTScanState) palloc(sizeof(BTScanStateData));
+	_bt_allocate_tuple_workspaces(lstate);
+
+	if (!scan->xs_want_itup)
+	{
+		/* We need to request index tuples for distance comparison. */
+		scan->xs_want_itup = true;
+		_bt_allocate_tuple_workspaces(rstate);
+	}
+
+	/* Bump pin and lock count before BTScanPosData copying. */
+	IncrBufferRefCount(buf);
+	LockBuffer(buf, BT_READ);
+
+	memcpy(&lstate->currPos, &rstate->currPos, sizeof(BTScanPosData));
+	lstate->currPos.moreLeft = true;
+	lstate->currPos.moreRight = false;
+
+	BTScanPosInvalidate(lstate->markPos);
+	lstate->markItemIndex = -1;
+	lstate->killedItems = NULL;
+	lstate->numKilled = 0;
+	lstate->currDistance = PointerGetDatum(NULL);
+	lstate->markDistance = PointerGetDatum(NULL);
+
+	/* Load first pages from the both scans. */
+	right = _bt_load_first_page(scan, rstate, ForwardScanDirection, offnum);
+	left = _bt_load_first_page(scan, lstate, BackwardScanDirection,
+							   OffsetNumberPrev(offnum));
+
+	if (!left && !right)
+		return false; /* empty result */
+
+	if (left && right)
+	{
+		/*
+		 * We have found items in both scan directions,
+		 * determine nearest item to return.
+		 */
+		_bt_calc_current_dist(scan, rstate);
+		_bt_calc_current_dist(scan, lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+
+		/* Reset right flag if the left item is nearer. */
+		right = so->currRightIsNearest;
+	}
+
+	/* Return current item of the selected scan direction. */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -663,7 +784,21 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 *----------
 	 */
 	strat_total = BTEqualStrategyNumber;
-	if (so->numberOfKeys > 0)
+
+	if (scan->numberOfOrderBys > 0)
+	{
+		if (_bt_process_orderings(scan, startKeys, &keysCount, notnullkeys))
+			/* use bidirectional KNN scan */
+			strat_total = BtreeKNNSearchStrategyNumber;
+
+		/* use selected KNN scan direction */
+		if (so->scanDirection != NoMovementScanDirection)
+			dir = so->scanDirection;
+	}
+
+	if (so->numberOfKeys > 0 &&
+	/* startKeys for KNN search already have been initialized */
+		strat_total != BtreeKNNSearchStrategyNumber)
 	{
 		AttrNumber	curattr;
 		ScanKey		chosen;
@@ -1003,6 +1138,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			break;
 
 		case BTGreaterEqualStrategyNumber:
+		case BtreeKNNSearchStrategyNumber:
 
 			/*
 			 * Find first item >= scankey.  (This is only used for forward
@@ -1093,16 +1229,21 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	Assert(!BTScanPosIsValid(*currPos));
 	currPos->buf = buf;
 
+	if (strat_total == BtreeKNNSearchStrategyNumber)
+		return _bt_init_knn_scan(scan, offnum);
+
 	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
-		return false;
+		return false; /* empty result */
 
 	/* OK, currPos->itemIndex says what to return */
 	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
- *	Advance to next tuple on current page; or if there's no more,
- *	try to step to the next page with data.
+ *	_bt_next_item() -- Advance to next tuple on current page;
+ *		or if there's no more, try to step to the next page with data.
+ *
+ *	If there are no more matching records in the given direction
  */
 static bool
 _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
@@ -1122,6 +1263,52 @@ _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 }
 
 /*
+ *	_bt_next_nearest() -- Return next nearest item from bidirectional KNN scan.
+ */
+static bool
+_bt_next_nearest(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState rstate = &so->state;
+	BTScanState lstate = so->knnState;
+	bool		right = BTScanPosIsValid(rstate->currPos);
+	bool		left = BTScanPosIsValid(lstate->currPos);
+	bool		advanceRight;
+
+	if (right && left)
+		advanceRight = so->currRightIsNearest;
+	else if (right)
+		advanceRight = true;
+	else if (left)
+		advanceRight = false;
+	else
+		return false; /* end of the scan */
+
+	*(advanceRight ? &right : &left) =
+		_bt_next_item(scan,
+					  advanceRight ? rstate : lstate,
+					  advanceRight ? ForwardScanDirection :
+					  BackwardScanDirection);
+
+	if (!left && !right)
+		return false; /* end of the scan */
+
+	if (left && right)
+	{
+		/*
+		 * If there are items in both scans we must recalculate distance
+		 * in the advanced scan.
+		 */
+		_bt_calc_current_dist(scan, advanceRight ? rstate : lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+		right = so->currRightIsNearest;
+	}
+
+	/* return nearest item */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
  *	_bt_next() -- Get the next item in a scan.
  *
  *		On entry, so->currPos describes the current page, which may be pinned
@@ -1140,6 +1327,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
+	if (so->knnState)
+		/* return next neareset item from KNN scan */
+		return _bt_next_nearest(scan);
+
 	if (!_bt_next_item(scan, &so->state, dir))
 		return false;
 
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index ebcba7e..e5e855e 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -20,11 +20,13 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "catalog/pg_amop.h"
 #include "miscadmin.h"
 #include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 
 
 typedef struct BTSortArrayContext
@@ -2061,6 +2063,34 @@ btproperty(Oid index_oid, int attno,
 			*res = true;
 			return true;
 
+		case AMPROP_DISTANCE_ORDERABLE:
+			{
+				Oid			opclass,
+							opfamily,
+							opcindtype;
+
+				/* answer only for columns, not AM or whole index */
+				if (attno == 0)
+					return false;
+
+				opclass = get_index_column_opclass(index_oid, attno);
+
+				if (!OidIsValid(opclass) ||
+					!get_opclass_opfamily_and_input_type(opclass,
+													 &opfamily, &opcindtype))
+				{
+					*isnull = true;
+					return true;
+				}
+
+				*res = SearchSysCacheExists(AMOPSTRATEGY,
+											ObjectIdGetDatum(opfamily),
+											ObjectIdGetDatum(opcindtype),
+											ObjectIdGetDatum(opcindtype),
+								   Int16GetDatum(BtreeKNNSearchStrategyNumber));
+				return true;
+			}
+
 		default:
 			return false;		/* punt to generic code */
 	}
@@ -2076,3 +2106,242 @@ _bt_allocate_tuple_workspaces(BTScanState state)
 	state->currTuples = (char *) palloc(BLCKSZ * 2);
 	state->markTuples = state->currTuples + BLCKSZ;
 }
+
+static Oid
+_bt_get_sortfamily_for_ordering_key(Relation rel, ScanKey skey)
+{
+	HeapTuple	tp;
+	Form_pg_amop amop_tup;
+	Oid			sortfamily;
+
+	tp = SearchSysCache4(AMOPSTRATEGY,
+						 ObjectIdGetDatum(rel->rd_opfamily[skey->sk_attno - 1]),
+						 ObjectIdGetDatum(rel->rd_opcintype[skey->sk_attno - 1]),
+						 ObjectIdGetDatum(skey->sk_subtype),
+						 Int16GetDatum(skey->sk_strategy));
+	if (!HeapTupleIsValid(tp))
+		return InvalidOid;
+	amop_tup = (Form_pg_amop) GETSTRUCT(tp);
+	sortfamily = amop_tup->amopsortfamily;
+	ReleaseSysCache(tp);
+
+	return sortfamily;
+}
+
+static bool
+_bt_compare_row_key_with_ordering_key(ScanKey row, ScanKey ord, bool *result)
+{
+	ScanKey		subkey = (ScanKey) DatumGetPointer(row->sk_argument);
+	int32		cmpresult;
+
+	Assert(subkey->sk_attno == 1);
+	Assert(subkey->sk_flags & SK_ROW_MEMBER);
+
+	if (subkey->sk_flags & SK_ISNULL)
+		return false;
+
+	/* Perform the test --- three-way comparison not bool operator */
+	cmpresult = DatumGetInt32(FunctionCall2Coll(&subkey->sk_func,
+												subkey->sk_collation,
+												ord->sk_argument,
+												subkey->sk_argument));
+
+	if (subkey->sk_flags & SK_BT_DESC)
+		cmpresult = -cmpresult;
+
+	/*
+	 * At this point cmpresult indicates the overall result of the row
+	 * comparison, and subkey points to the deciding column (or the last
+	 * column if the result is "=").
+	 */
+	switch (subkey->sk_strategy)
+	{
+			/* EQ and NE cases aren't allowed here */
+		case BTLessStrategyNumber:
+			*result = cmpresult < 0;
+			break;
+		case BTLessEqualStrategyNumber:
+			*result = cmpresult <= 0;
+			break;
+		case BTGreaterEqualStrategyNumber:
+			*result = cmpresult >= 0;
+			break;
+		case BTGreaterStrategyNumber:
+			*result = cmpresult > 0;
+			break;
+		default:
+			elog(ERROR, "unrecognized RowCompareType: %d",
+				 (int) subkey->sk_strategy);
+			*result = false;	/* keep compiler quiet */
+	}
+
+	return true;
+}
+
+/* _bt_select_knn_search_strategy() -- Determine which KNN scan strategy to use:
+ *		bidirectional or unidirectional. We are checking here if the
+ *		ordering scankey argument falls into the scan range: if it falls
+ *		we must use bidirectional scan, otherwise we use unidirectional.
+ *
+ *	Returns BtreeKNNSearchStrategyNumber for bidirectional scan or
+ *			strategy number of non-matched scankey for unidirectional.
+ */
+static StrategyNumber
+_bt_select_knn_search_strategy(IndexScanDesc scan, ScanKey ord)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	ScanKey		cond;
+
+	for (cond = so->keyData; cond < so->keyData + so->numberOfKeys; cond++)
+	{
+		bool		result;
+
+		if (cond->sk_attno != 1)
+			break; /* only interesting in the first index attribute */
+
+		if (cond->sk_strategy == BTEqualStrategyNumber)
+			/* always use simple unidirectional scan for equals operators */
+			return BTEqualStrategyNumber;
+
+		if (cond->sk_flags & SK_ROW_HEADER)
+		{
+			if (!_bt_compare_row_key_with_ordering_key(cond, ord, &result))
+				return BTEqualStrategyNumber; /* ROW(fist_index_attr, ...) IS NULL */
+		}
+		else
+		{
+			if (!_bt_compare_scankey_args(scan, cond, ord, cond, &result))
+				elog(ERROR, "could not compare ordering key");
+		}
+
+		if (!result)
+			/*
+			 * Ordering scankey argument is out of scan range,
+			 * use unidirectional scan.
+			 */
+			return cond->sk_strategy;
+	}
+
+	return BtreeKNNSearchStrategyNumber; /* use bidirectional scan */
+}
+
+/*
+ *  _bt_init_distance_comparison() -- Init distance typlen/typbyval and its
+ *  	comparison procedure.
+ */
+static void
+_bt_init_distance_comparison(IndexScanDesc scan, ScanKey ord)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	RegProcedure opcode;
+	Oid			sortfamily;
+	Oid			opno;
+	Oid			distanceType;
+
+	distanceType = get_func_rettype(ord->sk_func.fn_oid);
+
+	sortfamily = _bt_get_sortfamily_for_ordering_key(scan->indexRelation, ord);
+
+	if (!OidIsValid(sortfamily))
+		elog(ERROR, "could not find sort family for btree ordering operator");
+
+	opno = get_opfamily_member(sortfamily,
+							   distanceType,
+							   distanceType,
+							   BTLessEqualStrategyNumber);
+
+	if (!OidIsValid(opno))
+		elog(ERROR, "could not find operator for btree distance comparison");
+
+	opcode = get_opcode(opno);
+
+	if (!RegProcedureIsValid(opcode))
+		elog(ERROR,
+		  "could not find procedure for btree distance comparison operator");
+
+	fmgr_info(opcode, &so->distanceCmpProc);
+
+	get_typlenbyval(distanceType, &so->distanceTypeLen, &so->distanceTypeByVal);
+
+	if (!so->distanceTypeByVal)
+	{
+		so->state.currDistance = PointerGetDatum(NULL);
+		so->state.markDistance = PointerGetDatum(NULL);
+	}
+}
+
+/*
+ * _bt_process_orderings() -- Process ORDER BY distance scankeys and
+ *		select corresponding KNN strategy.
+ *
+ * If bidirectional scan is selected then one scankey is initialized
+ * using bufKeys and placed into startKeys/keysCount, true is returned.
+ *
+ * Otherwise, so->scanDirection is set and false is returned.
+ */
+bool
+_bt_process_orderings(IndexScanDesc scan, ScanKey *startKeys, int *keysCount,
+					  ScanKeyData bufKeys[])
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	ScanKey		ord = scan->orderByData;
+
+	if (scan->numberOfOrderBys > 1 || ord->sk_attno != 1)
+		/* it should not happen, see btcanorderbyop() */
+		elog(ERROR, "only one btree ordering operator "
+					"for the first index column is supported");
+
+	Assert(ord->sk_strategy == BtreeKNNSearchStrategyNumber);
+
+	switch (_bt_select_knn_search_strategy(scan, ord))
+	{
+		case BTLessStrategyNumber:
+		case BTLessEqualStrategyNumber:
+			/*
+			 * Ordering key argument is greater than all values in scan range.
+			 * select backward scan direction.
+			 */
+			so->scanDirection = BackwardScanDirection;
+			return false;
+
+		case BTEqualStrategyNumber:
+			/* Use default unidirectional scan direction. */
+			return false;
+
+		case BTGreaterEqualStrategyNumber:
+		case BTGreaterStrategyNumber:
+			/*
+			 * Ordering key argument is lesser than all values in scan range.
+			 * select forward scan direction.
+			 */
+			so->scanDirection = ForwardScanDirection;
+			return false;
+
+		case BtreeKNNSearchStrategyNumber:
+			/*
+			 * Ordering key argument falls into scan range,
+			 * use bidirectional scan.
+			 */
+			break;
+	}
+
+	_bt_init_distance_comparison(scan, ord);
+
+	/* Init btree search key with ordering key argument. */
+	ScanKeyEntryInitialize(&bufKeys[0],
+					 (scan->indexRelation->rd_indoption[ord->sk_attno - 1] <<
+					  SK_BT_INDOPTION_SHIFT) |
+						   SK_ORDER_BY |
+						   SK_SEARCHNULL /* only for invalid procedure oid, see
+										  * assert in ScanKeyEntryInitialize */,
+						   ord->sk_attno,
+						   BtreeKNNSearchStrategyNumber,
+						   ord->sk_subtype,
+						   ord->sk_collation,
+						   InvalidOid,
+						   ord->sk_argument);
+
+	startKeys[(*keysCount)++] = &bufKeys[0];
+
+	return true;
+}
diff --git a/src/backend/access/nbtree/nbtvalidate.c b/src/backend/access/nbtree/nbtvalidate.c
index 88e33f5..c56f4e8 100644
--- a/src/backend/access/nbtree/nbtvalidate.c
+++ b/src/backend/access/nbtree/nbtvalidate.c
@@ -22,9 +22,17 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_type.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/regproc.h"
 #include "utils/syscache.h"
 
+#define BTRequiredOperatorSet \
+		((1 << BTLessStrategyNumber) | \
+		 (1 << BTLessEqualStrategyNumber) | \
+		 (1 << BTEqualStrategyNumber) | \
+		 (1 << BTGreaterEqualStrategyNumber) | \
+		 (1 << BTGreaterStrategyNumber))
+
 
 /*
  * Validator for a btree opclass.
@@ -123,10 +131,11 @@ btvalidate(Oid opclassoid)
 	{
 		HeapTuple	oprtup = &oprlist->members[i]->tuple;
 		Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
+		Oid			op_rettype;
 
 		/* Check that only allowed strategy numbers exist */
 		if (oprform->amopstrategy < 1 ||
-			oprform->amopstrategy > BTMaxStrategyNumber)
+			oprform->amopstrategy > BtreeMaxStrategyNumber)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -137,20 +146,29 @@ btvalidate(Oid opclassoid)
 			result = false;
 		}
 
-		/* btree doesn't support ORDER BY operators */
-		if (oprform->amoppurpose != AMOP_SEARCH ||
-			OidIsValid(oprform->amopsortfamily))
+		/* btree supports ORDER BY operators */
+		if (oprform->amoppurpose != AMOP_SEARCH)
 		{
-			ereport(INFO,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("btree operator family \"%s\" contains invalid ORDER BY specification for operator %s",
-							opfamilyname,
-							format_operator(oprform->amopopr))));
-			result = false;
+			/* ... and operator result must match the claimed btree opfamily */
+			op_rettype = get_op_rettype(oprform->amopopr);
+			if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype))
+			{
+				ereport(INFO,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("btree operator family %s contains invalid ORDER BY specification for operator %s",
+								opfamilyname,
+								format_operator(oprform->amopopr))));
+				result = false;
+			}
+		}
+		else
+		{
+			/* Search operators must always return bool */
+			op_rettype = BOOLOID;
 		}
 
 		/* Check operator signature --- same for all btree strategies */
-		if (!check_amop_signature(oprform->amopopr, BOOLOID,
+		if (!check_amop_signature(oprform->amopopr, op_rettype,
 								  oprform->amoplefttype,
 								  oprform->amoprighttype))
 		{
@@ -189,12 +207,8 @@ btvalidate(Oid opclassoid)
 		 * or support functions for this datatype pair.  The only thing that
 		 * is considered optional is the sortsupport function.
 		 */
-		if (thisgroup->operatorset !=
-			((1 << BTLessStrategyNumber) |
-			 (1 << BTLessEqualStrategyNumber) |
-			 (1 << BTEqualStrategyNumber) |
-			 (1 << BTGreaterEqualStrategyNumber) |
-			 (1 << BTGreaterStrategyNumber)))
+		if ((thisgroup->operatorset & BTRequiredOperatorSet) !=
+			BTRequiredOperatorSet)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 21659a6..9762e8d 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -982,6 +982,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	 * if we are only trying to build bitmap indexscans, nor if we have to
 	 * assume the scan is unordered.
 	 */
+	useful_pathkeys = NIL;
+	orderbyclauses = NIL;
+	orderbyclausecols = NIL;
+
 	pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN &&
 								!found_lower_saop_clause &&
 								has_useful_pathkeys(root, rel));
@@ -992,10 +996,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 											  ForwardScanDirection);
 		useful_pathkeys = truncate_useless_pathkeys(root, rel,
 													index_pathkeys);
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
 	}
-	else if (index->amcanorderbyop && pathkeys_possibly_useful)
+
+	if (useful_pathkeys == NIL &&
+		index->amcanorderbyop && pathkeys_possibly_useful)
 	{
 		/* see if we can generate ordering operators for query_pathkeys */
 		match_pathkeys_to_index(index, root->query_pathkeys,
@@ -1006,12 +1010,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		else
 			useful_pathkeys = NIL;
 	}
-	else
-	{
-		useful_pathkeys = NIL;
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
-	}
 
 	/*
 	 * 3. Check if an index-only scan is possible.  If we're not building
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 4124010..030993b 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -624,6 +624,12 @@ typedef struct BTScanStateData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+
+	/* KNN-search fields: */
+	Datum		currDistance;	/* current distance */
+	Datum		markDistance;	/* marked distance */
+	bool		currIsNull;		/* current item is NULL */
+	bool		markIsNull;		/* marked item is NULL */
 }	BTScanStateData, *BTScanState;
 
 typedef struct BTScanOpaqueData
@@ -640,7 +646,17 @@ typedef struct BTScanOpaqueData
 	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
 	MemoryContext arrayContext; /* scan-lifespan context for array data */
 
-	BTScanStateData	state;
+	BTScanStateData state;		/* main scan state */
+
+	/* KNN-search fields: */
+	BTScanState knnState;			/* optional scan state for KNN search */
+	ScanDirection scanDirection;	/* selected scan direction for
+									 * unidirectional KNN scan */
+	FmgrInfo	distanceCmpProc;	/* distance comparison procedure */
+	int16		distanceTypeLen;	/* distance typlen */
+	bool		distanceTypeByVal;	/* distance typebyval */
+	bool		currRightIsNearest;	/* current right item is nearest */
+	bool		markRightIsNearest;	/* marked right item is nearest */
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -756,6 +772,8 @@ extern bool btproperty(Oid index_oid, int attno,
 		   IndexAMProperty prop, const char *propname,
 		   bool *res, bool *isnull);
 extern void _bt_allocate_tuple_workspaces(BTScanState state);
+extern bool _bt_process_orderings(IndexScanDesc scan,
+				  ScanKey *startKeys, int *keysCount, ScanKeyData bufKeys[]);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/include/access/stratnum.h b/src/include/access/stratnum.h
index 489e5c5..0852f8a 100644
--- a/src/include/access/stratnum.h
+++ b/src/include/access/stratnum.h
@@ -32,7 +32,10 @@ typedef uint16 StrategyNumber;
 #define BTGreaterEqualStrategyNumber	4
 #define BTGreaterStrategyNumber			5
 
-#define BTMaxStrategyNumber				5
+#define BTMaxStrategyNumber				5		/* number of canonical B-tree strategies */
+
+#define BtreeKNNSearchStrategyNumber	6		/* for <-> (distance) */
+#define BtreeMaxStrategyNumber			6		/* number of extended B-tree strategies */
 
 
 /*
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index b01be59..778dae3 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -347,10 +347,10 @@ ROLLBACK;
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
 ERROR:  access method "invalid_index_method" does not exist
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 6, must be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 0, must be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 7, must be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 0, must be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ERROR:  operator argument types must be specified in ALTER OPERATOR FAMILY
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -397,11 +397,12 @@ DROP OPERATOR FAMILY alt_opf8 USING btree;
 CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
-ERROR:  access method "btree" does not support ordering operators
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
 ALTER OPERATOR FAMILY alt_opf11 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql
index c9ea479..bcf9cf6 100644
--- a/src/test/regress/sql/alter_generic.sql
+++ b/src/test/regress/sql/alter_generic.sql
@@ -295,8 +295,8 @@ ROLLBACK;
 -- Should fail. Invalid values for ALTER OPERATOR FAMILY .. ADD / DROP
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 6 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -340,10 +340,12 @@ CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
 
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
0005-add-distance-operators-v02.patchtext/x-patch; name=0005-add-distance-operators-v02.patchDownload
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index ac8f74f..debe1b3 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -30,6 +30,7 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+#define SAMESIGN(a,b)	(((a) < 0) == ((b) < 0))
 
 /*************************************************************************
  * Private routines
@@ -1157,3 +1158,23 @@ int8_cash(PG_FUNCTION_ARGS)
 
 	PG_RETURN_CASH(result);
 }
+
+Datum
+cash_dist(PG_FUNCTION_ARGS)
+{
+	Cash		a = PG_GETARG_CASH(0);
+	Cash		b = PG_GETARG_CASH(1);
+	Cash		r;
+	Cash		ra;
+
+	r = a - b;
+	ra = Abs(r);
+
+	/* Overflow check. */
+	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("money out of range")));
+
+	PG_RETURN_CASH(ra);
+}
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 0a100a3..88010d5 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -565,6 +565,17 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+Datum
+date_dist(PG_FUNCTION_ARGS)
+{
+	/* we assume the difference can't overflow */
+	Datum		diff = DirectFunctionCall2(date_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
+}
+
 /*
  * Internal routines for promoting date to timestamp and timestamp with
  * time zone
@@ -2065,6 +2076,16 @@ time_part(PG_FUNCTION_ARGS)
 	PG_RETURN_FLOAT8(result);
 }
 
+Datum
+time_dist(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(time_mi_time,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
+
 
 /*****************************************************************************
  *	 Time With Time Zone ADT
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 894f026..906e70a 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -3585,6 +3585,32 @@ width_bucket_float8(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(result);
 }
 
+Datum
+float4_dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float4		r;
+
+	r = a - b;
+	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
+
+	PG_RETURN_FLOAT4(Abs(r));
+}
+
+Datum
+float8_dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r;
+
+	r = a - b;
+	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
 /* ========== PRIVATE ROUTINES ========== */
 
 #ifndef HAVE_CBRT
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index bd4422e..43dcf40 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -1395,3 +1395,43 @@ generate_series_step_int4(PG_FUNCTION_ARGS)
 		/* do when there is no more left */
 		SRF_RETURN_DONE(funcctx);
 }
+
+Datum
+int2_dist(PG_FUNCTION_ARGS)
+{
+	int16		a = PG_GETARG_INT16(0);
+	int16		b = PG_GETARG_INT16(1);
+	int16		r;
+	int16		ra;
+
+	r = a - b;
+	ra = Abs(r);
+
+	/* Overflow check. */
+	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("smallint out of range")));
+
+	PG_RETURN_INT16(ra);
+}
+
+Datum
+int4_dist(PG_FUNCTION_ARGS)
+{
+	int32		a = PG_GETARG_INT32(0);
+	int32		b = PG_GETARG_INT32(1);
+	int32		r;
+	int32		ra;
+
+	r = a - b;
+	ra = Abs(r);
+
+	/* Overflow check. */
+	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("integer out of range")));
+
+	PG_RETURN_INT32(ra);
+}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 0c6a412..77938e0 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1508,3 +1508,23 @@ generate_series_step_int8(PG_FUNCTION_ARGS)
 		/* do when there is no more left */
 		SRF_RETURN_DONE(funcctx);
 }
+
+Datum
+int8_dist(PG_FUNCTION_ARGS)
+{
+	int64		a = PG_GETARG_INT64(0);
+	int64		b = PG_GETARG_INT64(1);
+	int64		r;
+	int64		ra;
+
+	r = a - b;
+	ra = Abs(r);
+
+	/* Overflow check. */
+	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("bigint out of range")));
+
+	PG_RETURN_INT64(ra);
+}
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index 12ef783..bab7ae7 100644
--- a/src/backend/utils/adt/oid.c
+++ b/src/backend/utils/adt/oid.c
@@ -455,3 +455,17 @@ oidvectorgt(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(cmp > 0);
 }
+
+Datum
+oid_dist(PG_FUNCTION_ARGS)
+{
+	Oid			a = PG_GETARG_OID(0);
+	Oid			b = PG_GETARG_OID(1);
+	Oid			res;
+
+	if (a < b)
+		res = b - a;
+	else
+		res = a - b;
+	PG_RETURN_OID(res);
+}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index f2784da..f665177 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -2922,6 +2922,62 @@ timestamp_mi(PG_FUNCTION_ARGS)
 	PG_RETURN_INTERVAL_P(result);
 }
 
+Datum
+ts_dist(PG_FUNCTION_ARGS)
+{
+	Timestamp	a = PG_GETARG_TIMESTAMP(0);
+	Timestamp	b = PG_GETARG_TIMESTAMP(1);
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		Interval   *p = palloc(sizeof(Interval));
+
+		p->day = INT_MAX;
+		p->month = INT_MAX;
+#ifdef HAVE_INT64_TIMESTAMP
+		p->time = PG_INT64_MAX;
+#else
+		p->time = DBL_MAX;
+#endif
+		PG_RETURN_INTERVAL_P(p);
+	}
+	else
+		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+												  PG_GETARG_DATUM(0),
+												  PG_GETARG_DATUM(1)));
+	PG_RETURN_INTERVAL_P(abs_interval(r));
+}
+
+
+Datum
+tstz_dist(PG_FUNCTION_ARGS)
+{
+	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
+	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		Interval   *p = palloc(sizeof(Interval));
+
+		p->day = INT_MAX;
+		p->month = INT_MAX;
+#ifdef HAVE_INT64_TIMESTAMP
+		p->time = PG_INT64_MAX;
+#else
+		p->time = DBL_MAX;
+#endif
+		PG_RETURN_INTERVAL_P(p);
+	}
+
+	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+											  PG_GETARG_DATUM(0),
+											  PG_GETARG_DATUM(1)));
+	PG_RETURN_INTERVAL_P(abs_interval(r));
+}
+
+
 /*
  *	interval_justify_interval()
  *
@@ -3725,6 +3781,29 @@ interval_avg(PG_FUNCTION_ARGS)
 							   Float8GetDatum((double) N.time));
 }
 
+Interval *
+abs_interval(Interval *a)
+{
+	static Interval zero = {0, 0, 0};
+
+	if (DatumGetBool(DirectFunctionCall2(interval_lt,
+										 IntervalPGetDatum(a),
+										 IntervalPGetDatum(&zero))))
+		a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
+												  IntervalPGetDatum(a)));
+
+	return a;
+}
+
+Datum
+interval_dist(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(interval_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
 
 /* timestamp_age()
  * Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index 0251664..44e2caf 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -105,6 +105,7 @@ DATA(insert (	1976   21 21 2 s	522 403 0 ));
 DATA(insert (	1976   21 21 3 s	94	403 0 ));
 DATA(insert (	1976   21 21 4 s	524 403 0 ));
 DATA(insert (	1976   21 21 5 s	520 403 0 ));
+DATA(insert (	1976   21 21 6 o	3365 403 1976 ));
 /* crosstype operators int24 */
 DATA(insert (	1976   21 23 1 s	534 403 0 ));
 DATA(insert (	1976   21 23 2 s	540 403 0 ));
@@ -123,6 +124,7 @@ DATA(insert (	1976   23 23 2 s	523 403 0 ));
 DATA(insert (	1976   23 23 3 s	96	403 0 ));
 DATA(insert (	1976   23 23 4 s	525 403 0 ));
 DATA(insert (	1976   23 23 5 s	521 403 0 ));
+DATA(insert (	1976   23 23 6 o	3366 403 1976 ));
 /* crosstype operators int42 */
 DATA(insert (	1976   23 21 1 s	535 403 0 ));
 DATA(insert (	1976   23 21 2 s	541 403 0 ));
@@ -141,6 +143,7 @@ DATA(insert (	1976   20 20 2 s	414 403 0 ));
 DATA(insert (	1976   20 20 3 s	410 403 0 ));
 DATA(insert (	1976   20 20 4 s	415 403 0 ));
 DATA(insert (	1976   20 20 5 s	413 403 0 ));
+DATA(insert (	1976   20 20 6 o	3367 403 1976 ));
 /* crosstype operators int82 */
 DATA(insert (	1976   20 21 1 s	1870	403 0 ));
 DATA(insert (	1976   20 21 2 s	1872	403 0 ));
@@ -163,6 +166,7 @@ DATA(insert (	1989   26 26 2 s	611 403 0 ));
 DATA(insert (	1989   26 26 3 s	607 403 0 ));
 DATA(insert (	1989   26 26 4 s	612 403 0 ));
 DATA(insert (	1989   26 26 5 s	610 403 0 ));
+DATA(insert (	1989   26 26 6 o	3368 403 1989 ));
 
 /*
  * btree tid_ops
@@ -194,6 +198,7 @@ DATA(insert (	1970   700 700 2 s	624 403 0 ));
 DATA(insert (	1970   700 700 3 s	620 403 0 ));
 DATA(insert (	1970   700 700 4 s	625 403 0 ));
 DATA(insert (	1970   700 700 5 s	623 403 0 ));
+DATA(insert (	1970   700 700 6 o	3369 403 1970 ));
 /* crosstype operators float48 */
 DATA(insert (	1970   700 701 1 s	1122 403 0 ));
 DATA(insert (	1970   700 701 2 s	1124 403 0 ));
@@ -206,6 +211,7 @@ DATA(insert (	1970   701 701 2 s	673 403 0 ));
 DATA(insert (	1970   701 701 3 s	670 403 0 ));
 DATA(insert (	1970   701 701 4 s	675 403 0 ));
 DATA(insert (	1970   701 701 5 s	674 403 0 ));
+DATA(insert (	1970   701 701 6 o	3370 403 1970 ));
 /* crosstype operators float84 */
 DATA(insert (	1970   701 700 1 s	1132 403 0 ));
 DATA(insert (	1970   701 700 2 s	1134 403 0 ));
@@ -283,6 +289,7 @@ DATA(insert (	434   1082 1082 2 s 1096	403 0 ));
 DATA(insert (	434   1082 1082 3 s 1093	403 0 ));
 DATA(insert (	434   1082 1082 4 s 1098	403 0 ));
 DATA(insert (	434   1082 1082 5 s 1097	403 0 ));
+DATA(insert (	434   1082 1082 6 o 3372	403 1976 ));
 /* crosstype operators vs timestamp */
 DATA(insert (	434   1082 1114 1 s 2345	403 0 ));
 DATA(insert (	434   1082 1114 2 s 2346	403 0 ));
@@ -301,6 +308,7 @@ DATA(insert (	434   1114 1114 2 s 2063	403 0 ));
 DATA(insert (	434   1114 1114 3 s 2060	403 0 ));
 DATA(insert (	434   1114 1114 4 s 2065	403 0 ));
 DATA(insert (	434   1114 1114 5 s 2064	403 0 ));
+DATA(insert (	434   1114 1114 6 o 3374	403 1982 ));
 /* crosstype operators vs date */
 DATA(insert (	434   1114 1082 1 s 2371	403 0 ));
 DATA(insert (	434   1114 1082 2 s 2372	403 0 ));
@@ -319,6 +327,7 @@ DATA(insert (	434   1184 1184 2 s 1323	403 0 ));
 DATA(insert (	434   1184 1184 3 s 1320	403 0 ));
 DATA(insert (	434   1184 1184 4 s 1325	403 0 ));
 DATA(insert (	434   1184 1184 5 s 1324	403 0 ));
+DATA(insert (	434   1184 1184 6 o 3375	403 1982 ));
 /* crosstype operators vs date */
 DATA(insert (	434   1184 1082 1 s 2384	403 0 ));
 DATA(insert (	434   1184 1082 2 s 2385	403 0 ));
@@ -341,6 +350,7 @@ DATA(insert (	1996   1083 1083 2 s 1111 403 0 ));
 DATA(insert (	1996   1083 1083 3 s 1108 403 0 ));
 DATA(insert (	1996   1083 1083 4 s 1113 403 0 ));
 DATA(insert (	1996   1083 1083 5 s 1112 403 0 ));
+DATA(insert (	1996   1083 1083 6 o 3373 403 1982 ));
 
 /*
  *	btree timetz_ops
@@ -361,6 +371,7 @@ DATA(insert (	1982   1186 1186 2 s 1333 403 0 ));
 DATA(insert (	1982   1186 1186 3 s 1330 403 0 ));
 DATA(insert (	1982   1186 1186 4 s 1335 403 0 ));
 DATA(insert (	1982   1186 1186 5 s 1334 403 0 ));
+DATA(insert (	1982   1186 1186 6 o 3376 403 1982 ));
 
 /*
  *	btree macaddr
@@ -451,6 +462,7 @@ DATA(insert (	2099   790 790 2 s	904 403 0 ));
 DATA(insert (	2099   790 790 3 s	900 403 0 ));
 DATA(insert (	2099   790 790 4 s	905 403 0 ));
 DATA(insert (	2099   790 790 5 s	903 403 0 ));
+DATA(insert (	2099   790 790 6 o	3371 403 2099 ));
 
 /*
  *	btree reltime_ops
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 45feb69..dca1706 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1624,6 +1624,32 @@ DESCR("greater than or equal");
 DATA(insert OID = 3228 (  "-"	   PGNSP PGUID b f f 3220 3220 1700    0	0 pg_lsn_mi - - ));
 DESCR("minus");
 
+/* distance operators */
+DATA(insert OID = 3365 (  "<->"    PGNSP PGUID b f f   21	21	 21 3365 0 int2_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3366 (  "<->"    PGNSP PGUID b f f   23	23	 23 3366 0 int4_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3367 (  "<->"    PGNSP PGUID b f f   20	20	 20 3367 0 int8_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3368 ( "<->"	   PGNSP PGUID b f f   26	26	 26 3368 0 oid_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3369 (  "<->"    PGNSP PGUID b f f  700  700	700 3369 0 float4_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3370 (  "<->"    PGNSP PGUID b f f  701  701	701 3370 0 float8_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3371 ( "<->"	   PGNSP PGUID b f f  790  790	790 3371 0 cash_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3372 ( "<->"	   PGNSP PGUID b f f 1082 1082	 23 3372 0 date_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3373 ( "<->"	   PGNSP PGUID b f f 1083 1083 1186 3373 0 time_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3374 ( "<->"	   PGNSP PGUID b f f 1114 1114 1186 3374 0 ts_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3375 ( "<->"	   PGNSP PGUID b f f 1184 1184 1186 3375 0 tstz_dist - - ));
+DESCR("distance between");
+DATA(insert OID = 3376 ( "<->"	   PGNSP PGUID b f f 1186 1186 1186 3376 0 interval_dist - - ));
+DESCR("distance between");
+
 /* enum operators */
 DATA(insert OID = 3516 (  "="	   PGNSP PGUID b t t 3500 3500 16 3516 3517 enum_eq eqsel eqjoinsel ));
 DESCR("equal");
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 05652e8..ca418fe 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5361,6 +5361,21 @@ DESCR("pg_controldata init state information as a function");
 DATA(insert OID = 3445 ( pg_import_system_collations PGNSP PGUID 12 100 0 0 0 f f f f t f v r 2 0 2278 "16 4089" _null_ _null_ "{if_not_exists,schema}" _null_ _null_ pg_import_system_collations _null_ _null_ _null_ ));
 DESCR("import collations from operating system");
 
+/* distance functions */
+DATA(insert OID = 3353 ( int2_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	 21 "21 21"		_null_ _null_ _null_ _null_ _null_	int2_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3354 ( int4_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	 23 "23 23"		_null_ _null_ _null_ _null_ _null_	int4_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3355 ( int8_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	 20 "20 20"		_null_ _null_ _null_ _null_ _null_	int8_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3356 ( oid_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	 26 "26 26"		_null_ _null_ _null_ _null_ _null_	oid_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3357 ( float4_dist	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	700 "700 700"	_null_ _null_ _null_ _null_ _null_	float4_dist _null_ _null_ _null_ ));
+DATA(insert OID = 3358 ( float8_dist	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	701 "701 701"	_null_ _null_ _null_ _null_ _null_	float8_dist _null_ _null_ _null_ ));
+DATA(insert OID = 3359 ( cash_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	790 "790 790"	_null_ _null_ _null_ _null_ _null_	cash_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3360 ( date_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0	 23 "1082 1082" _null_ _null_ _null_ _null_ _null_	date_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3361 ( time_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 1186 "1083 1083" _null_ _null_ _null_ _null_ _null_	time_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3362 ( ts_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 1186 "1114 1114" _null_ _null_ _null_ _null_ _null_	ts_dist		_null_ _null_ _null_ ));
+DATA(insert OID = 3363 ( tstz_dist		PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 1186 "1184 1184" _null_ _null_ _null_ _null_ _null_	tstz_dist	_null_ _null_ _null_ ));
+DATA(insert OID = 3364 ( interval_dist	PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 1186 "1186 1186" _null_ _null_ _null_ _null_ _null_	interval_dist _null_ _null_ _null_ ));
+
+
 /*
  * Symbolic values for provolatile column: these indicate whether the result
  * of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index f0e7798..4b4d02d 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -346,4 +346,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 					   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern Interval *abs_interval(Interval *a);
+
 #endif   /* DATETIME_H */
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index 74f7c9f..55296b7 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -24,7 +24,7 @@ select prop,
  nulls_first        |    |       | f
  nulls_last         |    |       | t
  orderable          |    |       | t
- distance_orderable |    |       | f
+ distance_orderable |    |       | t
  returnable         |    |       | t
  search_array       |    |       | t
  search_nulls       |    |       | t
@@ -97,7 +97,7 @@ select prop,
  nulls_first        | f     | f    | f    | f      | f   | f
  nulls_last         | t     | f    | f    | f      | f   | f
  orderable          | t     | f    | f    | f      | f   | f
- distance_orderable | f     | f    | t    | f      | f   | f
+ distance_orderable | t     | f    | t    | f      | f   | f
  returnable         | t     | f    | f    | t      | f   | f
  search_array       | t     | f    | f    | f      | f   | f
  search_nulls       | t     | f    | t    | t      | f   | t
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 0bcec13..fb44ae8 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1721,6 +1721,7 @@ ORDER BY 1, 2, 3;
         403 |            5 | *>
         403 |            5 | >
         403 |            5 | ~>~
+        403 |            6 | <->
         405 |            1 | =
         783 |            1 | <<
         783 |            1 | @@
@@ -1828,7 +1829,7 @@ ORDER BY 1, 2, 3;
        4000 |           25 | <<=
        4000 |           26 | >>
        4000 |           27 | >>=
-(121 rows)
+(122 rows)
 
 -- Check that all opclass search operators have selectivity estimators.
 -- This is not absolutely required, but it seems a reasonable thing
0006-remove-distance-operators-from-btree_gist-v02.patchtext/x-patch; name=0006-remove-distance-operators-from-btree_gist-v02.patchDownload
diff --git a/contrib/btree_gist/Makefile b/contrib/btree_gist/Makefile
index d36f517..c650581 100644
--- a/contrib/btree_gist/Makefile
+++ b/contrib/btree_gist/Makefile
@@ -10,7 +10,8 @@ OBJS =  btree_gist.o btree_utils_num.o btree_utils_var.o btree_int2.o \
 
 EXTENSION = btree_gist
 DATA = btree_gist--unpackaged--1.0.sql btree_gist--1.0--1.1.sql \
-       btree_gist--1.1--1.2.sql btree_gist--1.2.sql btree_gist--1.2--1.3.sql
+       btree_gist--1.1--1.2.sql btree_gist--1.2--1.3.sql \
+       btree_gist--1.3--1.4.sql btree_gist--1.4.sql
 PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes"
 
 REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \
diff --git a/contrib/btree_gist/btree_cash.c b/contrib/btree_gist/btree_cash.c
index aa14735..6ebeda1 100644
--- a/contrib/btree_gist/btree_cash.c
+++ b/contrib/btree_gist/btree_cash.c
@@ -90,27 +90,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(cash_dist);
-Datum
-cash_dist(PG_FUNCTION_ARGS)
-{
-	Cash		a = PG_GETARG_CASH(0);
-	Cash		b = PG_GETARG_CASH(1);
-	Cash		r;
-	Cash		ra;
-
-	r = a - b;
-	ra = Abs(r);
-
-	/* Overflow check. */
-	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("money out of range")));
-
-	PG_RETURN_CASH(ra);
-}
-
 /**************************************************
  * Cash ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_date.c b/contrib/btree_gist/btree_date.c
index 56031d4..96d8821 100644
--- a/contrib/btree_gist/btree_date.c
+++ b/contrib/btree_gist/btree_date.c
@@ -109,19 +109,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(date_dist);
-Datum
-date_dist(PG_FUNCTION_ARGS)
-{
-	/* we assume the difference can't overflow */
-	Datum		diff = DirectFunctionCall2(date_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
-}
-
-
 /**************************************************
  * date ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index 13dc4a5..c798b42 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -89,21 +89,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(float4_dist);
-Datum
-float4_dist(PG_FUNCTION_ARGS)
-{
-	float4		a = PG_GETARG_FLOAT4(0);
-	float4		b = PG_GETARG_FLOAT4(1);
-	float4		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT4(Abs(r));
-}
-
-
 /**************************************************
  * float4 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index c3a2415..c332c2d 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -96,21 +96,6 @@ static const gbtree_ninfo tinfo =
 	gbt_float8_dist
 };
 
-
-PG_FUNCTION_INFO_V1(float8_dist);
-Datum
-float8_dist(PG_FUNCTION_ARGS)
-{
-	float8		a = PG_GETARG_FLOAT8(0);
-	float8		b = PG_GETARG_FLOAT8(1);
-	float8		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT8(Abs(r));
-}
-
 /**************************************************
  * float8 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_gist--1.2.sql b/contrib/btree_gist/btree_gist--1.2.sql
deleted file mode 100644
index 1efe753..0000000
--- a/contrib/btree_gist/btree_gist--1.2.sql
+++ /dev/null
@@ -1,1570 +0,0 @@
-/* contrib/btree_gist/btree_gist--1.2.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
-
-CREATE FUNCTION gbtreekey4_in(cstring)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey4_out(gbtreekey4)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey4 (
-	INTERNALLENGTH = 4,
-	INPUT  = gbtreekey4_in,
-	OUTPUT = gbtreekey4_out
-);
-
-CREATE FUNCTION gbtreekey8_in(cstring)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey8_out(gbtreekey8)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey8 (
-	INTERNALLENGTH = 8,
-	INPUT  = gbtreekey8_in,
-	OUTPUT = gbtreekey8_out
-);
-
-CREATE FUNCTION gbtreekey16_in(cstring)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey16_out(gbtreekey16)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey16 (
-	INTERNALLENGTH = 16,
-	INPUT  = gbtreekey16_in,
-	OUTPUT = gbtreekey16_out
-);
-
-CREATE FUNCTION gbtreekey32_in(cstring)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey32_out(gbtreekey32)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey32 (
-	INTERNALLENGTH = 32,
-	INPUT  = gbtreekey32_in,
-	OUTPUT = gbtreekey32_out
-);
-
-CREATE FUNCTION gbtreekey_var_in(cstring)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey_var (
-	INTERNALLENGTH = VARIABLE,
-	INPUT  = gbtreekey_var_in,
-	OUTPUT = gbtreekey_var_out,
-	STORAGE = EXTENDED
-);
-
---distance operators
-
-CREATE FUNCTION cash_dist(money, money)
-RETURNS money
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = money,
-	RIGHTARG = money,
-	PROCEDURE = cash_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION date_dist(date, date)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = date,
-	RIGHTARG = date,
-	PROCEDURE = date_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float4_dist(float4, float4)
-RETURNS float4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float4,
-	RIGHTARG = float4,
-	PROCEDURE = float4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float8_dist(float8, float8)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float8,
-	RIGHTARG = float8,
-	PROCEDURE = float8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int2_dist(int2, int2)
-RETURNS int2
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int2,
-	RIGHTARG = int2,
-	PROCEDURE = int2_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int4_dist(int4, int4)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int4,
-	RIGHTARG = int4,
-	PROCEDURE = int4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int8_dist(int8, int8)
-RETURNS int8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int8,
-	RIGHTARG = int8,
-	PROCEDURE = int8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION interval_dist(interval, interval)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = interval,
-	RIGHTARG = interval,
-	PROCEDURE = interval_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION oid_dist(oid, oid)
-RETURNS oid
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = oid,
-	RIGHTARG = oid,
-	PROCEDURE = oid_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION time_dist(time, time)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = time,
-	RIGHTARG = time,
-	PROCEDURE = time_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION ts_dist(timestamp, timestamp)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamp,
-	RIGHTARG = timestamp,
-	PROCEDURE = ts_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION tstz_dist(timestamptz, timestamptz)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamptz,
-	RIGHTARG = timestamptz,
-	PROCEDURE = tstz_dist,
-	COMMUTATOR = '<->'
-);
-
-
---
---
---
--- oid ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_oid_ops
-DEFAULT FOR TYPE oid USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
-	FUNCTION	2	gbt_oid_union (internal, internal),
-	FUNCTION	3	gbt_oid_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_oid_picksplit (internal, internal),
-	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
--- Add operators that are new in 9.1.  We do it like this, leaving them
--- "loose" in the operator family rather than bound into the opclass, because
--- that's the only state that can be reproduced during an upgrade from 9.0.
-ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
-	OPERATOR	6	<> (oid, oid) ,
-	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
-	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
-	-- Also add support function for index-only-scans, added in 9.5.
-	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
-
-
---
---
---
--- int2 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_union(internal, internal)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int2_ops
-DEFAULT FOR TYPE int2 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
-	FUNCTION	2	gbt_int2_union (internal, internal),
-	FUNCTION	3	gbt_int2_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int2_picksplit (internal, internal),
-	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
-	STORAGE		gbtreekey4;
-
-ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
-	OPERATOR	6	<> (int2, int2) ,
-	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
-	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
-
---
---
---
--- int4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int4_ops
-DEFAULT FOR TYPE int4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
-	FUNCTION	2	gbt_int4_union (internal, internal),
-	FUNCTION	3	gbt_int4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int4_picksplit (internal, internal),
-	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
-	OPERATOR	6	<> (int4, int4) ,
-	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
-	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
-
-
---
---
---
--- int8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int8_ops
-DEFAULT FOR TYPE int8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
-	FUNCTION	2	gbt_int8_union (internal, internal),
-	FUNCTION	3	gbt_int8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int8_picksplit (internal, internal),
-	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
-	OPERATOR	6	<> (int8, int8) ,
-	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
-	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
-
---
---
---
--- float4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float4_ops
-DEFAULT FOR TYPE float4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
-	FUNCTION	2	gbt_float4_union (internal, internal),
-	FUNCTION	3	gbt_float4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float4_picksplit (internal, internal),
-	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
-	OPERATOR	6	<> (float4, float4) ,
-	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
-	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
-
---
---
---
--- float8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float8_ops
-DEFAULT FOR TYPE float8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
-	FUNCTION	2	gbt_float8_union (internal, internal),
-	FUNCTION	3	gbt_float8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float8_picksplit (internal, internal),
-	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
-	OPERATOR	6	<> (float8, float8) ,
-	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
-	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
-
---
---
---
--- timestamp ops
---
---
---
-
-CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamp_ops
-DEFAULT FOR TYPE timestamp USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_ts_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
-	OPERATOR	6	<> (timestamp, timestamp) ,
-	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
-	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamptz_ops
-DEFAULT FOR TYPE timestamptz USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_tstz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
-	OPERATOR	6	<> (timestamptz, timestamptz) ,
-	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
-	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
-
---
---
---
--- time ops
---
---
---
-
-CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_time_ops
-DEFAULT FOR TYPE time USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_time_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
-	OPERATOR	6	<> (time, time) ,
-	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
-	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
-
-
-CREATE OPERATOR CLASS gist_timetz_ops
-DEFAULT FOR TYPE timetz USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_timetz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
-	OPERATOR	6	<> (timetz, timetz) ;
-	-- no 'fetch' function, as the compress function is lossy.
-
-
---
---
---
--- date ops
---
---
---
-
-CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_date_ops
-DEFAULT FOR TYPE date USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
-	FUNCTION	2	gbt_date_union (internal, internal),
-	FUNCTION	3	gbt_date_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_date_picksplit (internal, internal),
-	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
-	OPERATOR	6	<> (date, date) ,
-	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
-	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
-
-
---
---
---
--- interval ops
---
---
---
-
-CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_union(internal, internal)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_interval_ops
-DEFAULT FOR TYPE interval USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
-	FUNCTION	2	gbt_intv_union (internal, internal),
-	FUNCTION	3	gbt_intv_compress (internal),
-	FUNCTION	4	gbt_intv_decompress (internal),
-	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_intv_picksplit (internal, internal),
-	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
-	STORAGE		gbtreekey32;
-
-ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
-	OPERATOR	6	<> (interval, interval) ,
-	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
-	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
-
-
---
---
---
--- cash ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cash_ops
-DEFAULT FOR TYPE money USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
-	FUNCTION	2	gbt_cash_union (internal, internal),
-	FUNCTION	3	gbt_cash_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_cash_picksplit (internal, internal),
-	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
-	OPERATOR	6	<> (money, money) ,
-	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
-	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
-	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
-
-
---
---
---
--- macaddr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_macaddr_ops
-DEFAULT FOR TYPE macaddr USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
-	FUNCTION	2	gbt_macad_union (internal, internal),
-	FUNCTION	3	gbt_macad_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_macad_picksplit (internal, internal),
-	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
-	OPERATOR	6	<> (macaddr, macaddr) ,
-	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
-
-
---
---
---
--- text/ bpchar ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_text_ops
-DEFAULT FOR TYPE text USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_text_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
-	OPERATOR	6	<> (text, text) ,
-	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
-
-
----- Create the operator class
-CREATE OPERATOR CLASS gist_bpchar_ops
-DEFAULT FOR TYPE bpchar USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_bpchar_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
-	OPERATOR	6	<> (bpchar, bpchar) ,
-	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
-
---
---
--- bytea ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bytea_ops
-DEFAULT FOR TYPE bytea USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
-	FUNCTION	2	gbt_bytea_union (internal, internal),
-	FUNCTION	3	gbt_bytea_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
-	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
-	OPERATOR	6	<> (bytea, bytea) ,
-	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
-
-
---
---
---
--- numeric ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_numeric_ops
-DEFAULT FOR TYPE numeric USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
-	FUNCTION	2	gbt_numeric_union (internal, internal),
-	FUNCTION	3	gbt_numeric_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
-	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
-	OPERATOR	6	<> (numeric, numeric) ,
-	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
-
-
---
---
--- bit ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bit_ops
-DEFAULT FOR TYPE bit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
-	OPERATOR	6	<> (bit, bit) ,
-	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
-
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_vbit_ops
-DEFAULT FOR TYPE varbit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
-	OPERATOR	6	<> (varbit, varbit) ,
-	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
-
-
---
---
---
--- inet/cidr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_inet_ops
-DEFAULT FOR TYPE inet USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
-	OPERATOR	6	<>  (inet, inet) ;
-	-- no fetch support, the compress function is lossy
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cidr_ops
-DEFAULT FOR TYPE cidr USING gist
-AS
-	OPERATOR	1	<  (inet, inet)  ,
-	OPERATOR	2	<= (inet, inet)  ,
-	OPERATOR	3	=  (inet, inet)  ,
-	OPERATOR	4	>= (inet, inet)  ,
-	OPERATOR	5	>  (inet, inet)  ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
-	OPERATOR	6	<> (inet, inet) ;
-	-- no fetch support, the compress function is lossy
diff --git a/contrib/btree_gist/btree_gist--1.3--1.4.sql b/contrib/btree_gist/btree_gist--1.3--1.4.sql
new file mode 100644
index 0000000..4062aed
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.3--1.4.sql
@@ -0,0 +1,150 @@
+/* contrib/btree_gist/btree_gist--1.3--1.4.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.4'" to load this file. \quit
+
+-- update references to distance operators in pg_amop and pg_depend
+
+WITH
+btree_ops AS (
+	SELECT
+		amoplefttype, amoprighttype, amopopr
+	FROM
+		pg_amop
+		JOIN pg_am ON pg_am.oid = amopmethod
+		JOIN pg_opfamily ON pg_opfamily.oid = amopfamily
+		JOIN pg_namespace ON pg_namespace.oid = opfnamespace
+	WHERE
+		nspname = 'pg_catalog'
+		AND opfname IN (
+			'integer_ops',
+			'oid_ops',
+			'money_ops',
+			'float_ops',
+			'datetime_ops',
+			'time_ops',
+			'interval_ops'
+		)
+		AND amname = 'btree'
+		AND amoppurpose = 'o'
+		AND amopstrategy = 6
+	),
+gist_ops AS (
+	SELECT
+		pg_amop.oid AS oid, amoplefttype, amoprighttype, amopopr
+	FROM
+		pg_amop
+		JOIN pg_am ON amopmethod = pg_am.oid
+		JOIN pg_opfamily ON amopfamily = pg_opfamily.oid
+		JOIN pg_namespace ON pg_namespace.oid = opfnamespace
+	WHERE
+		nspname = current_schema()
+		AND opfname IN (
+			'gist_oid_ops',
+			'gist_int2_ops',
+			'gist_int4_ops',
+			'gist_int8_ops',
+			'gist_float4_ops',
+			'gist_float8_ops',
+			'gist_timestamp_ops',
+			'gist_timestamptz_ops',
+			'gist_time_ops',
+			'gist_date_ops',
+			'gist_interval_ops',
+			'gist_cash_ops'
+		)
+		AND amname = 'gist'
+		AND amoppurpose = 'o'
+		AND amopstrategy = 15
+	),
+depend_update_data(gist_amop, gist_amopopr, btree_amopopr) AS (
+	SELECT
+		gist_ops.oid, gist_ops.amopopr, btree_ops.amopopr
+	FROM
+		btree_ops JOIN gist_ops USING (amoplefttype, amoprighttype)
+),
+amop_update_data AS (
+	UPDATE
+		pg_depend
+	SET
+		refobjid = btree_amopopr
+	FROM
+		depend_update_data
+	WHERE
+		objid = gist_amop AND refobjid = gist_amopopr
+	RETURNING
+		depend_update_data.*
+)
+UPDATE
+	pg_amop
+SET
+	amopopr = btree_amopopr
+FROM
+	amop_update_data
+WHERE
+	pg_amop.oid = gist_amop;
+
+-- disable implicit pg_catalog search
+
+DO
+$$
+BEGIN
+	EXECUTE 'SET LOCAL search_path TO ' || current_schema() || ', pg_catalog';
+END
+$$;
+
+-- drop distance operators
+
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int2, int2);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int4, int4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int8, int8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float4, float4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float8, float8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (oid, oid);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (money, money);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (date, date);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (time, time);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (interval, interval);
+
+DROP OPERATOR <-> (int2, int2);
+DROP OPERATOR <-> (int4, int4);
+DROP OPERATOR <-> (int8, int8);
+DROP OPERATOR <-> (float4, float4);
+DROP OPERATOR <-> (float8, float8);
+DROP OPERATOR <-> (oid, oid);
+DROP OPERATOR <-> (money, money);
+DROP OPERATOR <-> (date, date);
+DROP OPERATOR <-> (time, time);
+DROP OPERATOR <-> (timestamp, timestamp);
+DROP OPERATOR <-> (timestamptz, timestamptz);
+DROP OPERATOR <-> (interval, interval);
+
+-- drop distance functions
+
+ALTER EXTENSION btree_gist DROP FUNCTION int2_dist(int2, int2);
+ALTER EXTENSION btree_gist DROP FUNCTION int4_dist(int4, int4);
+ALTER EXTENSION btree_gist DROP FUNCTION int8_dist(int8, int8);
+ALTER EXTENSION btree_gist DROP FUNCTION float4_dist(float4, float4);
+ALTER EXTENSION btree_gist DROP FUNCTION float8_dist(float8, float8);
+ALTER EXTENSION btree_gist DROP FUNCTION oid_dist(oid, oid);
+ALTER EXTENSION btree_gist DROP FUNCTION cash_dist(money, money);
+ALTER EXTENSION btree_gist DROP FUNCTION date_dist(date, date);
+ALTER EXTENSION btree_gist DROP FUNCTION time_dist(time, time);
+ALTER EXTENSION btree_gist DROP FUNCTION ts_dist(timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP FUNCTION interval_dist(interval, interval);
+
+DROP FUNCTION int2_dist(int2, int2);
+DROP FUNCTION int4_dist(int4, int4);
+DROP FUNCTION int8_dist(int8, int8);
+DROP FUNCTION float4_dist(float4, float4);
+DROP FUNCTION float8_dist(float8, float8);
+DROP FUNCTION oid_dist(oid, oid);
+DROP FUNCTION cash_dist(money, money);
+DROP FUNCTION date_dist(date, date);
+DROP FUNCTION time_dist(time, time);
+DROP FUNCTION ts_dist(timestamp, timestamp);
+DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+DROP FUNCTION interval_dist(interval, interval);
diff --git a/contrib/btree_gist/btree_gist--1.4.sql b/contrib/btree_gist/btree_gist--1.4.sql
new file mode 100644
index 0000000..1f1327d
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.4.sql
@@ -0,0 +1,1490 @@
+/* contrib/btree_gist/btree_gist--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
+
+CREATE FUNCTION gbtreekey4_in(cstring)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey4_out(gbtreekey4)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey4 (
+	INTERNALLENGTH = 4,
+	INPUT  = gbtreekey4_in,
+	OUTPUT = gbtreekey4_out
+);
+
+CREATE FUNCTION gbtreekey8_in(cstring)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey8_out(gbtreekey8)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey8 (
+	INTERNALLENGTH = 8,
+	INPUT  = gbtreekey8_in,
+	OUTPUT = gbtreekey8_out
+);
+
+CREATE FUNCTION gbtreekey16_in(cstring)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey16_out(gbtreekey16)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey16 (
+	INTERNALLENGTH = 16,
+	INPUT  = gbtreekey16_in,
+	OUTPUT = gbtreekey16_out
+);
+
+CREATE FUNCTION gbtreekey32_in(cstring)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey32_out(gbtreekey32)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey32 (
+	INTERNALLENGTH = 32,
+	INPUT  = gbtreekey32_in,
+	OUTPUT = gbtreekey32_out
+);
+
+CREATE FUNCTION gbtreekey_var_in(cstring)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey_var (
+	INTERNALLENGTH = VARIABLE,
+	INPUT  = gbtreekey_var_in,
+	OUTPUT = gbtreekey_var_out,
+	STORAGE = EXTENDED
+);
+
+
+--
+--
+--
+-- oid ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_oid_ops
+DEFAULT FOR TYPE oid USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
+	FUNCTION	2	gbt_oid_union (internal, internal),
+	FUNCTION	3	gbt_oid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_oid_picksplit (internal, internal),
+	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+-- Add operators that are new in 9.1.  We do it like this, leaving them
+-- "loose" in the operator family rather than bound into the opclass, because
+-- that's the only state that can be reproduced during an upgrade from 9.0.
+ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
+	OPERATOR	6	<> (oid, oid) ,
+	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
+	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
+	-- Also add support function for index-only-scans, added in 9.5.
+	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
+
+
+--
+--
+--
+-- int2 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_union(internal, internal)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int2_ops
+DEFAULT FOR TYPE int2 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
+	FUNCTION	2	gbt_int2_union (internal, internal),
+	FUNCTION	3	gbt_int2_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int2_picksplit (internal, internal),
+	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
+	STORAGE		gbtreekey4;
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
+	OPERATOR	6	<> (int2, int2) ,
+	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
+	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
+
+--
+--
+--
+-- int4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int4_ops
+DEFAULT FOR TYPE int4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
+	FUNCTION	2	gbt_int4_union (internal, internal),
+	FUNCTION	3	gbt_int4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int4_picksplit (internal, internal),
+	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
+	OPERATOR	6	<> (int4, int4) ,
+	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
+	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
+
+
+--
+--
+--
+-- int8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int8_ops
+DEFAULT FOR TYPE int8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
+	FUNCTION	2	gbt_int8_union (internal, internal),
+	FUNCTION	3	gbt_int8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int8_picksplit (internal, internal),
+	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
+	OPERATOR	6	<> (int8, int8) ,
+	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
+	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
+
+--
+--
+--
+-- float4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float4_ops
+DEFAULT FOR TYPE float4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
+	FUNCTION	2	gbt_float4_union (internal, internal),
+	FUNCTION	3	gbt_float4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float4_picksplit (internal, internal),
+	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
+	OPERATOR	6	<> (float4, float4) ,
+	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
+	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
+
+--
+--
+--
+-- float8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float8_ops
+DEFAULT FOR TYPE float8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
+	FUNCTION	2	gbt_float8_union (internal, internal),
+	FUNCTION	3	gbt_float8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float8_picksplit (internal, internal),
+	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
+	OPERATOR	6	<> (float8, float8) ,
+	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
+	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
+
+--
+--
+--
+-- timestamp ops
+--
+--
+--
+
+CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamp_ops
+DEFAULT FOR TYPE timestamp USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_ts_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
+	OPERATOR	6	<> (timestamp, timestamp) ,
+	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
+	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamptz_ops
+DEFAULT FOR TYPE timestamptz USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_tstz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
+	OPERATOR	6	<> (timestamptz, timestamptz) ,
+	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
+	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
+
+--
+--
+--
+-- time ops
+--
+--
+--
+
+CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_time_ops
+DEFAULT FOR TYPE time USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_time_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
+	OPERATOR	6	<> (time, time) ,
+	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
+	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
+
+
+CREATE OPERATOR CLASS gist_timetz_ops
+DEFAULT FOR TYPE timetz USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_timetz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
+	OPERATOR	6	<> (timetz, timetz) ;
+	-- no 'fetch' function, as the compress function is lossy.
+
+
+--
+--
+--
+-- date ops
+--
+--
+--
+
+CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_date_ops
+DEFAULT FOR TYPE date USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
+	FUNCTION	2	gbt_date_union (internal, internal),
+	FUNCTION	3	gbt_date_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_date_picksplit (internal, internal),
+	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
+	OPERATOR	6	<> (date, date) ,
+	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
+	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
+
+
+--
+--
+--
+-- interval ops
+--
+--
+--
+
+CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_interval_ops
+DEFAULT FOR TYPE interval USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
+	FUNCTION	2	gbt_intv_union (internal, internal),
+	FUNCTION	3	gbt_intv_compress (internal),
+	FUNCTION	4	gbt_intv_decompress (internal),
+	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_intv_picksplit (internal, internal),
+	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
+	OPERATOR	6	<> (interval, interval) ,
+	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
+	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
+
+
+--
+--
+--
+-- cash ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cash_ops
+DEFAULT FOR TYPE money USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
+	FUNCTION	2	gbt_cash_union (internal, internal),
+	FUNCTION	3	gbt_cash_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_cash_picksplit (internal, internal),
+	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
+	OPERATOR	6	<> (money, money) ,
+	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
+	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
+	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
+
+
+--
+--
+--
+-- macaddr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_macaddr_ops
+DEFAULT FOR TYPE macaddr USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
+	FUNCTION	2	gbt_macad_union (internal, internal),
+	FUNCTION	3	gbt_macad_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_macad_picksplit (internal, internal),
+	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
+	OPERATOR	6	<> (macaddr, macaddr) ,
+	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
+
+
+--
+--
+--
+-- text/ bpchar ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_text_ops
+DEFAULT FOR TYPE text USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_text_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
+	OPERATOR	6	<> (text, text) ,
+	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
+
+
+---- Create the operator class
+CREATE OPERATOR CLASS gist_bpchar_ops
+DEFAULT FOR TYPE bpchar USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_bpchar_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
+	OPERATOR	6	<> (bpchar, bpchar) ,
+	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
+
+--
+--
+-- bytea ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bytea_ops
+DEFAULT FOR TYPE bytea USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
+	FUNCTION	2	gbt_bytea_union (internal, internal),
+	FUNCTION	3	gbt_bytea_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
+	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
+	OPERATOR	6	<> (bytea, bytea) ,
+	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- numeric ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_numeric_ops
+DEFAULT FOR TYPE numeric USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
+	FUNCTION	2	gbt_numeric_union (internal, internal),
+	FUNCTION	3	gbt_numeric_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
+	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
+	OPERATOR	6	<> (numeric, numeric) ,
+	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
+
+
+--
+--
+-- bit ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bit_ops
+DEFAULT FOR TYPE bit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
+	OPERATOR	6	<> (bit, bit) ,
+	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
+
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_vbit_ops
+DEFAULT FOR TYPE varbit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
+	OPERATOR	6	<> (varbit, varbit) ,
+	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- inet/cidr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_inet_ops
+DEFAULT FOR TYPE inet USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
+	OPERATOR	6	<>  (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cidr_ops
+DEFAULT FOR TYPE cidr USING gist
+AS
+	OPERATOR	1	<  (inet, inet)  ,
+	OPERATOR	2	<= (inet, inet)  ,
+	OPERATOR	3	=  (inet, inet)  ,
+	OPERATOR	4	>= (inet, inet)  ,
+	OPERATOR	5	>  (inet, inet)  ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
+	OPERATOR	6	<> (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+--
+--
+--
+-- uuid ops
+--
+--
+---- define the GiST support methods
+CREATE FUNCTION gbt_uuid_consistent(internal,uuid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_uuid_ops
+DEFAULT FOR TYPE uuid USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_uuid_consistent (internal, uuid, int2, oid, internal),
+	FUNCTION	2	gbt_uuid_union (internal, internal),
+	FUNCTION	3	gbt_uuid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_uuid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_uuid_picksplit (internal, internal),
+	FUNCTION	7	gbt_uuid_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+-- These are "loose" in the opfamily for consistency with the rest of btree_gist
+ALTER OPERATOR FAMILY gist_uuid_ops USING gist ADD
+	OPERATOR	6	<>  (uuid, uuid) ,
+	FUNCTION	9 (uuid, uuid) gbt_uuid_fetch (internal) ;
+
diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control
index ddbf83d..fdf0e6a 100644
--- a/contrib/btree_gist/btree_gist.control
+++ b/contrib/btree_gist/btree_gist.control
@@ -1,5 +1,5 @@
 # btree_gist extension
 comment = 'support for indexing common datatypes in GiST'
-default_version = '1.3'
+default_version = '1.4'
 module_pathname = '$libdir/btree_gist'
 relocatable = true
diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c
index 54dc1cc..c3cee90 100644
--- a/contrib/btree_gist/btree_int2.c
+++ b/contrib/btree_gist/btree_int2.c
@@ -89,28 +89,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(int2_dist);
-Datum
-int2_dist(PG_FUNCTION_ARGS)
-{
-	int16		a = PG_GETARG_INT16(0);
-	int16		b = PG_GETARG_INT16(1);
-	int16		r;
-	int16		ra;
-
-	r = a - b;
-	ra = Abs(r);
-
-	/* Overflow check. */
-	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("smallint out of range")));
-
-	PG_RETURN_INT16(ra);
-}
-
-
 /**************************************************
  * int16 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c
index ddbcf52..e79ab3e 100644
--- a/contrib/btree_gist/btree_int4.c
+++ b/contrib/btree_gist/btree_int4.c
@@ -90,28 +90,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(int4_dist);
-Datum
-int4_dist(PG_FUNCTION_ARGS)
-{
-	int32		a = PG_GETARG_INT32(0);
-	int32		b = PG_GETARG_INT32(1);
-	int32		r;
-	int32		ra;
-
-	r = a - b;
-	ra = Abs(r);
-
-	/* Overflow check. */
-	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("integer out of range")));
-
-	PG_RETURN_INT32(ra);
-}
-
-
 /**************************************************
  * int32 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c
index 44bf69a..89b8430 100644
--- a/contrib/btree_gist/btree_int8.c
+++ b/contrib/btree_gist/btree_int8.c
@@ -90,28 +90,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(int8_dist);
-Datum
-int8_dist(PG_FUNCTION_ARGS)
-{
-	int64		a = PG_GETARG_INT64(0);
-	int64		b = PG_GETARG_INT64(1);
-	int64		r;
-	int64		ra;
-
-	r = a - b;
-	ra = Abs(r);
-
-	/* Overflow check. */
-	if (ra < 0 || (!SAMESIGN(a, b) && !SAMESIGN(r, a)))
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("bigint out of range")));
-
-	PG_RETURN_INT64(ra);
-}
-
-
 /**************************************************
  * int64 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_interval.c b/contrib/btree_gist/btree_interval.c
index e5cd0a2..af48540 100644
--- a/contrib/btree_gist/btree_interval.c
+++ b/contrib/btree_gist/btree_interval.c
@@ -110,32 +110,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-Interval *
-abs_interval(Interval *a)
-{
-	static Interval zero = {0, 0, 0};
-
-	if (DatumGetBool(DirectFunctionCall2(interval_lt,
-										 IntervalPGetDatum(a),
-										 IntervalPGetDatum(&zero))))
-		a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
-												  IntervalPGetDatum(a)));
-
-	return a;
-}
-
-PG_FUNCTION_INFO_V1(interval_dist);
-Datum
-interval_dist(PG_FUNCTION_ARGS)
-{
-	Datum		diff = DirectFunctionCall2(interval_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
-}
-
-
 /**************************************************
  * interval ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_oid.c b/contrib/btree_gist/btree_oid.c
index ac61a76..d389fe2 100644
--- a/contrib/btree_gist/btree_oid.c
+++ b/contrib/btree_gist/btree_oid.c
@@ -96,22 +96,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(oid_dist);
-Datum
-oid_dist(PG_FUNCTION_ARGS)
-{
-	Oid			a = PG_GETARG_OID(0);
-	Oid			b = PG_GETARG_OID(1);
-	Oid			res;
-
-	if (a < b)
-		res = b - a;
-	else
-		res = a - b;
-	PG_RETURN_OID(res);
-}
-
-
 /**************************************************
  * Oid ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_time.c b/contrib/btree_gist/btree_time.c
index 27f30bc..c515168 100644
--- a/contrib/btree_gist/btree_time.c
+++ b/contrib/btree_gist/btree_time.c
@@ -137,18 +137,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(time_dist);
-Datum
-time_dist(PG_FUNCTION_ARGS)
-{
-	Datum		diff = DirectFunctionCall2(time_mi_time,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
-}
-
-
 /**************************************************
  * time ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c
index ab22b27..899fbf4 100644
--- a/contrib/btree_gist/btree_ts.c
+++ b/contrib/btree_gist/btree_ts.c
@@ -139,63 +139,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(ts_dist);
-Datum
-ts_dist(PG_FUNCTION_ARGS)
-{
-	Timestamp	a = PG_GETARG_TIMESTAMP(0);
-	Timestamp	b = PG_GETARG_TIMESTAMP(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-#ifdef HAVE_INT64_TIMESTAMP
-		p->time = PG_INT64_MAX;
-#else
-		p->time = DBL_MAX;
-#endif
-		PG_RETURN_INTERVAL_P(p);
-	}
-	else
-		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-												  PG_GETARG_DATUM(0),
-												  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
-}
-
-PG_FUNCTION_INFO_V1(tstz_dist);
-Datum
-tstz_dist(PG_FUNCTION_ARGS)
-{
-	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
-	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-#ifdef HAVE_INT64_TIMESTAMP
-		p->time = PG_INT64_MAX;
-#else
-		p->time = DBL_MAX;
-#endif
-		PG_RETURN_INTERVAL_P(p);
-	}
-
-	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-											  PG_GETARG_DATUM(0),
-											  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
-}
-
-
 /**************************************************
  * timestamp ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_utils_num.h b/contrib/btree_gist/btree_utils_num.h
index a33491b..8f873a5 100644
--- a/contrib/btree_gist/btree_utils_num.h
+++ b/contrib/btree_gist/btree_utils_num.h
@@ -116,8 +116,6 @@ do {															\
 } while(0)
 
 
-extern Interval *abs_interval(Interval *a);
-
 extern bool gbt_num_consistent(const GBT_NUMKEY_R *key, const void *query,
 				   const StrategyNumber *strategy, bool is_leaf,
 				   const gbtree_ninfo *tinfo);
0007-add-regression-tests-for-kNN-btree-v02.patchtext/x-patch; name=0007-add-regression-tests-for-kNN-btree-v02.patchDownload
diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index 755cd17..3ab1ad7 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -150,3 +150,489 @@ vacuum btree_tall_tbl;
 -- need to insert some rows to cause the fast root page to split.
 insert into btree_tall_tbl (id, t)
   select g, repeat('x', 100) from generate_series(1, 500) g;
+---
+--- Test B-tree distance ordering
+---
+SET enable_bitmapscan = OFF;
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+          QUERY PLAN          
+------------------------------
+ Sort
+   Sort Key: ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+                   QUERY PLAN                   
+------------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((random <-> 1))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)))
+   Order By: (random <-> 4000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+        1 |     9001
+        1 |     8001
+        1 |     7001
+        1 |     6001
+        1 |     5001
+        1 |     4001
+        1 |     3001
+        1 |     2001
+        1 |     1001
+        1 |        1
+        0 |     9000
+        0 |     8000
+        0 |     7000
+        0 |     6000
+        0 |     5000
+        0 |     4000
+        0 |     3000
+        0 |     2000
+        0 |     1000
+        0 |        0
+          |        1
+          |        2
+          |        3
+(33 rows)
+
+DROP INDEX tenk3_idx;
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+        1 |        1
+        1 |     1001
+        1 |     2001
+        1 |     3001
+        1 |     4001
+        1 |     5001
+        1 |     6001
+        1 |     7001
+        1 |     8001
+        1 |     9001
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+        0 |        0
+        0 |     1000
+        0 |     2000
+        0 |     3000
+        0 |     4000
+        0 |     5000
+        0 |     6000
+        0 |     7000
+        0 |     8000
+        0 |     9000
+          |        3
+          |        2
+          |        1
+(33 rows)
+
+DROP INDEX tenk3_idx;
+DROP TABLE tenk3;
+RESET enable_bitmapscan;
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 65b08c8..b0559d7 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -92,3 +92,108 @@ vacuum btree_tall_tbl;
 -- need to insert some rows to cause the fast root page to split.
 insert into btree_tall_tbl (id, t)
   select g, repeat('x', 100) from generate_series(1, 500) g;
+
+---
+--- Test B-tree distance ordering
+---
+
+SET enable_bitmapscan = OFF;
+
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+
+
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+DROP INDEX tenk3_idx;
+
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+DROP INDEX tenk3_idx;
+
+DROP TABLE tenk3;
+
+RESET enable_bitmapscan;
#4Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Nikita Glukhov (#3)
Re: [PATCH] kNN for btree

Hi, Nikita!

I have assigned as a reviewer for this patchset. I took a fist look to
these patches.
At first, I'd like to notice that it's very cool that you picked up this
work. I frequently hear people complains about lack of this feature.

k | kNN-btree | kNN-GiST | Opt. query | Seq. scan

| | (btree_gist) | with UNION | with sort
--------|--------------|--------------|---------------|------------
1 | 0.041 4 | 0.079 4 | 0.060 8 | 41.1 1824
10 | 0.048 7 | 0.091 9 | 0.097 17 | 41.8 1824
100 | 0.107 47 | 0.192 52 | 0.342 104 | 42.3 1824
1000 | 0.735 573 | 0.913 650 | 2.970 1160 | 43.5 1824
10000 | 5.070 5622 | 6.240 6760 | 36.300 11031 | 54.1 1824
100000 | 49.600 51608 | 61.900 64194 | 295.100 94980 | 115.0 1824

These results looks quite expected. KNN-btree uses about half of blocks in
comparison with UNION query, and it's more than twice faster. In
comparison with kNN-GiST there is still some win.

1. Introduce amcanorderbyop() function

This patch transforms existing boolean AM property amcanorderbyop into a
method
(function pointer). This is necessary because, unlike GiST, kNN for btree
supports only a one ordering operator on the first index column and we
need a
different pathkey matching logic for btree (there was a corresponding
comment
in match_pathkeys_to_index()). GiST-specific logic has been moved from
match_pathkeys_to_index() to gistcanorderbyop().

I'm not very excited about this design of amcanorderbyop callback.
Introducing new callback from index access method to the planner should
imply quite good flexibility to the future. In this particular signature
of callback I see no potential future use-cases than your implementation
for btree. We could just add amcanorderbyonlyfisrtop property and that
would give us same level of flexibility I think.
With existing index types, we could cover much more orderings that we
currently do. Some of possible cases:
1) "ORDER BY col" for btree_gist, SP-GiST text_ops
2) "ORDER BY col1, col2 <-> const" for btree_gist
3) "ORDER BY col1, col2 <-> const" for btree

I understand that #3 is quite hard task and I don't ask you to implement it
now. But it would be nice if some day we decide to add #3, we wouldn't
have to change IndexAmRoutine definition.

My idea is that we need more general redesign of specifying ordering which
index can produce. Ideally, we should replace amcanorder, amcanbackward
and amcanorderbyop with single callback. Such callback should take a list
of pathkeys and return number of leading pathkeys index could satisfy (with
corresponding information for index scan). I'm not sure that other hackers
would agree with such design, but I'm very convinced that we need something
of this level of extendability. Otherwise we would have to hack our
planner <-> index_access_method interface each time we decide to cover
another index produced ordering.

6. Remove duplicate distance operators from contrib/btree_gist.

References to their own distance operators in btree_gist opclasses are
replaced with references to the built-in operators and than duplicate
operators are dropped. But if the user is using somewhere these operators,
upgrade of btree_gist from 1.3 to 1.4 would fail.

The query in "btree_gist--1.3--1.4.sql" which directly touches system
catalogue to update opfamilies looks too hackery. I think we shouldn't use
such queries until we have no other choice.
In this particular case we can update opfamilies using legal mechanism
"ALTER OPERATOR FAMILY name USING index_method ADD/DROP ... " (note that
operator name could be schema-qualified if needed). This way wouldn't be
that brief, but it is much more correct.

Also this like catch my eyes.

info->amcanorderbyop = (void (*)()) amroutine->amcanorderbyop;

It's not necessary to use cast here. For instance, we don't use cast
for amcostestimate.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#5Robert Haas
robertmhaas@gmail.com
In reply to: Alexander Korotkov (#4)
Re: [PATCH] kNN for btree

On Thu, Feb 16, 2017 at 8:05 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

My idea is that we need more general redesign of specifying ordering which
index can produce. Ideally, we should replace amcanorder, amcanbackward and
amcanorderbyop with single callback. Such callback should take a list of
pathkeys and return number of leading pathkeys index could satisfy (with
corresponding information for index scan). I'm not sure that other hackers
would agree with such design, but I'm very convinced that we need something
of this level of extendability. Otherwise we would have to hack our planner
<-> index_access_method interface each time we decide to cover another index
produced ordering.

Yeah. I'm not sure if that's exactly the right idea. But it seems
like we need something.

info->amcanorderbyop = (void (*)()) amroutine->amcanorderbyop;

It's not necessary to use cast here. For instance, we don't use cast for
amcostestimate.

In fact, it's bad to use the cast here, because if in future the
signature of one of amroutine->amcanorderbyop is changed and
info->amcanorderbyop is not changed to match, then the cast will
prevent a compiler warning, but at runtime you may crash.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

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

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#5)
Re: [PATCH] kNN for btree

Robert Haas <robertmhaas@gmail.com> writes:

On Thu, Feb 16, 2017 at 8:05 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

My idea is that we need more general redesign of specifying ordering which
index can produce. Ideally, we should replace amcanorder, amcanbackward and
amcanorderbyop with single callback. Such callback should take a list of
pathkeys and return number of leading pathkeys index could satisfy (with
corresponding information for index scan). I'm not sure that other hackers
would agree with such design, but I'm very convinced that we need something
of this level of extendability. Otherwise we would have to hack our planner
<-> index_access_method interface each time we decide to cover another index
produced ordering.

Yeah. I'm not sure if that's exactly the right idea. But it seems
like we need something.

That's definitely not exactly the right idea, because using it would
require the core planner to play twenty-questions trying to guess which
pathkeys the index can satisfy. ("Can you satisfy some prefix of this
pathkey list? How about that one?") It could be sensible to have a
callback that's called once per index and hands back a list of pathkey
lists that represent interesting orders the index could produce, which
could be informed by looking aside at the PlannerInfo contents to see
what is likely to be relevant to the query.

But even so, I'm not convinced that that is a better design or more
maintainable than the current approach. I fear that it will lead to
duplicating substantial amounts of code and knowledge into each index AM,
which is not an improvement; and if anything, that increases the risk of
breaking every index AM anytime you want to introduce some fundamentally
new capability in the area. Now that it's actually practical to have
out-of-core index AMs, that's a bigger concern than it might once have
been.

Also see the discussion that led up to commit ed0097e4f. Users objected
the last time we tried to make index capabilities opaque at the SQL level,
so they're not going to like a design that tries to hide that information
even from the core C code.

regards, tom lane

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

#7Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#6)
Re: [PATCH] kNN for btree

On Thu, Feb 16, 2017 at 10:59 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Thu, Feb 16, 2017 at 8:05 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

My idea is that we need more general redesign of specifying ordering which
index can produce. Ideally, we should replace amcanorder, amcanbackward and
amcanorderbyop with single callback. Such callback should take a list of
pathkeys and return number of leading pathkeys index could satisfy (with
corresponding information for index scan). I'm not sure that other hackers
would agree with such design, but I'm very convinced that we need something
of this level of extendability. Otherwise we would have to hack our planner
<-> index_access_method interface each time we decide to cover another index
produced ordering.

Yeah. I'm not sure if that's exactly the right idea. But it seems
like we need something.

That's definitely not exactly the right idea, because using it would
require the core planner to play twenty-questions trying to guess which
pathkeys the index can satisfy. ("Can you satisfy some prefix of this
pathkey list? How about that one?") It could be sensible to have a
callback that's called once per index and hands back a list of pathkey
lists that represent interesting orders the index could produce, which
could be informed by looking aside at the PlannerInfo contents to see
what is likely to be relevant to the query.

But even so, I'm not convinced that that is a better design or more
maintainable than the current approach. I fear that it will lead to
duplicating substantial amounts of code and knowledge into each index AM,
which is not an improvement; and if anything, that increases the risk of
breaking every index AM anytime you want to introduce some fundamentally
new capability in the area. Now that it's actually practical to have
out-of-core index AMs, that's a bigger concern than it might once have
been.

Yeah, that's all true. But I think Alexander is right that just
adding amcandoblah flags ad infinitum doesn't feel good either. The
interface isn't really arm's-length if every new thing somebody wants
to do something new requires another flag.

Also see the discussion that led up to commit ed0097e4f. Users objected
the last time we tried to make index capabilities opaque at the SQL level,
so they're not going to like a design that tries to hide that information
even from the core C code.

Discoverability is definitely important, but first we have to figure
out how we're going to make it work, and then we can work out how to
let users see how it works.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

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

#8David Steele
david@pgmasters.net
In reply to: Robert Haas (#7)
Re: [PATCH] kNN for btree

Hi Alexander,

On 2/16/17 11:20 AM, Robert Haas wrote:

On Thu, Feb 16, 2017 at 10:59 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Thu, Feb 16, 2017 at 8:05 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

My idea is that we need more general redesign of specifying ordering which
index can produce. Ideally, we should replace amcanorder, amcanbackward and
amcanorderbyop with single callback. Such callback should take a list of
pathkeys and return number of leading pathkeys index could satisfy (with
corresponding information for index scan). I'm not sure that other hackers
would agree with such design, but I'm very convinced that we need something
of this level of extendability. Otherwise we would have to hack our planner
<-> index_access_method interface each time we decide to cover another index
produced ordering.

Yeah. I'm not sure if that's exactly the right idea. But it seems
like we need something.

That's definitely not exactly the right idea, because using it would
require the core planner to play twenty-questions trying to guess which
pathkeys the index can satisfy. ("Can you satisfy some prefix of this
pathkey list? How about that one?") It could be sensible to have a
callback that's called once per index and hands back a list of pathkey
lists that represent interesting orders the index could produce, which
could be informed by looking aside at the PlannerInfo contents to see
what is likely to be relevant to the query.

But even so, I'm not convinced that that is a better design or more
maintainable than the current approach. I fear that it will lead to
duplicating substantial amounts of code and knowledge into each index AM,
which is not an improvement; and if anything, that increases the risk of
breaking every index AM anytime you want to introduce some fundamentally
new capability in the area. Now that it's actually practical to have
out-of-core index AMs, that's a bigger concern than it might once have
been.

Yeah, that's all true. But I think Alexander is right that just
adding amcandoblah flags ad infinitum doesn't feel good either. The
interface isn't really arm's-length if every new thing somebody wants
to do something new requires another flag.

Also see the discussion that led up to commit ed0097e4f. Users objected
the last time we tried to make index capabilities opaque at the SQL level,
so they're not going to like a design that tries to hide that information
even from the core C code.

Discoverability is definitely important, but first we have to figure
out how we're going to make it work, and then we can work out how to
let users see how it works.

Reading through this thread I'm concerned that this appears to be a big
change making its first appearance in the last CF. There is also the
need for a new patch and a general consensus of how to proceed.

I recommend moving this patch to 2017-07 or marking it RWF.

Thanks,
--
-David
david@pgmasters.net

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

#9Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: David Steele (#8)
Re: [PATCH] kNN for btree

On Thu, Mar 2, 2017 at 5:57 PM, David Steele <david@pgmasters.net> wrote:

Hi Alexander,

On 2/16/17 11:20 AM, Robert Haas wrote:

On Thu, Feb 16, 2017 at 10:59 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Thu, Feb 16, 2017 at 8:05 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

My idea is that we need more general redesign of specifying ordering

which

index can produce. Ideally, we should replace amcanorder,

amcanbackward and

amcanorderbyop with single callback. Such callback should take a

list of

pathkeys and return number of leading pathkeys index could satisfy

(with

corresponding information for index scan). I'm not sure that other

hackers

would agree with such design, but I'm very convinced that we need

something

of this level of extendability. Otherwise we would have to hack our

planner

<-> index_access_method interface each time we decide to cover

another index

produced ordering.

Yeah. I'm not sure if that's exactly the right idea. But it seems
like we need something.

That's definitely not exactly the right idea, because using it would
require the core planner to play twenty-questions trying to guess which
pathkeys the index can satisfy. ("Can you satisfy some prefix of this
pathkey list? How about that one?") It could be sensible to have a
callback that's called once per index and hands back a list of pathkey
lists that represent interesting orders the index could produce, which
could be informed by looking aside at the PlannerInfo contents to see
what is likely to be relevant to the query.

But even so, I'm not convinced that that is a better design or more
maintainable than the current approach. I fear that it will lead to
duplicating substantial amounts of code and knowledge into each index

AM,

which is not an improvement; and if anything, that increases the risk of
breaking every index AM anytime you want to introduce some fundamentally
new capability in the area. Now that it's actually practical to have
out-of-core index AMs, that's a bigger concern than it might once have
been.

Yeah, that's all true. But I think Alexander is right that just
adding amcandoblah flags ad infinitum doesn't feel good either. The
interface isn't really arm's-length if every new thing somebody wants
to do something new requires another flag.

Also see the discussion that led up to commit ed0097e4f. Users objected
the last time we tried to make index capabilities opaque at the SQL

level,

so they're not going to like a design that tries to hide that

information

even from the core C code.

Discoverability is definitely important, but first we have to figure
out how we're going to make it work, and then we can work out how to
let users see how it works.

Reading through this thread I'm concerned that this appears to be a big
change making its first appearance in the last CF. There is also the
need for a new patch and a general consensus of how to proceed.

Yes, refactoring of amcanorder/amcanorderbyop should be very thoughtful.

I recommend moving this patch to 2017-07 or marking it RWF.

I agree. Done.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#10Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Alexander Korotkov (#9)
7 attachment(s)
Re: [PATCH] kNN for btree

Attached 3rd version of the patches rebased onto the current master.

Changes from the previous version:
- Added support of INCLUDE columns to get_index_column_opclass() (1st patch).
- Added parallel kNN scan support.
- amcanorderbyop() was transformed into ammatchorderby() which takes a List of
PathKeys and checks each of them with new function match_orderbyop_pathkey()
extracted from match_pathkeys_to_index(). I think that this design can be
used in the future to support a mix of ordinary and order-by-op PathKeys,
but I am not sure.

On 09.03.2017 15:00, Alexander Korotkov wrote:

On Thu, Mar 2, 2017 at 5:57 PM, David Steele <david@pgmasters.net
<mailto:david@pgmasters.net>> wrote:

Hi Alexander,

On 2/16/17 11:20 AM, Robert Haas wrote:

On Thu, Feb 16, 2017 at 10:59 AM, Tom Lane <tgl@sss.pgh.pa.us

<mailto:tgl@sss.pgh.pa.us>> wrote:

Robert Haas <robertmhaas@gmail.com

<mailto:robertmhaas@gmail.com>> writes:

On Thu, Feb 16, 2017 at 8:05 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru <mailto:a.korotkov@postgrespro.ru>>

wrote:

My idea is that we need more general redesign of specifying

ordering which

index can produce.  Ideally, we should replace amcanorder,

amcanbackward and

amcanorderbyop with single callback. Such callback should

take a list of

pathkeys and return number of leading pathkeys index could

satisfy (with

corresponding information for index scan).  I'm not sure that

other hackers

would agree with such design, but I'm very convinced that we

need something

of this level of extendability. Otherwise we would have to

hack our planner

<-> index_access_method interface each time we decide to

cover another index

produced ordering.

Yeah.  I'm not sure if that's exactly the right idea.  But it

seems

like we need something.

That's definitely not exactly the right idea, because using it

would

require the core planner to play twenty-questions trying to

guess which

pathkeys the index can satisfy.  ("Can you satisfy some prefix

of this

pathkey list?  How about that one?")  It could be sensible to

have a

callback that's called once per index and hands back a list of

pathkey

lists that represent interesting orders the index could

produce, which

could be informed by looking aside at the PlannerInfo contents

to see

what is likely to be relevant to the query.

But even so, I'm not convinced that that is a better design or more
maintainable than the current approach.  I fear that it will

lead to

duplicating substantial amounts of code and knowledge into each

index AM,

which is not an improvement; and if anything, that increases

the risk of

breaking every index AM anytime you want to introduce some

fundamentally

new capability in the area.  Now that it's actually practical

to have

out-of-core index AMs, that's a bigger concern than it might

once have

been.

Yeah, that's all true.  But I think Alexander is right that just
adding amcandoblah flags ad infinitum doesn't feel good either.  The
interface isn't really arm's-length if every new thing somebody

wants

to do something new requires another flag.

Also see the discussion that led up to commit ed0097e4f.  Users

objected

the last time we tried to make index capabilities opaque at the

SQL level,

so they're not going to like a design that tries to hide that

information

even from the core C code.

Discoverability is definitely important, but first we have to figure
out how we're going to make it work, and then we can work out how to
let users see how it works.

Reading through this thread I'm concerned that this appears to be
a big
change making its first appearance in the last CF.  There is also the
need for a new patch and a general consensus of how to proceed.

Yes, refactoring of amcanorder/amcanorderbyop should be very thoughtful.

I recommend moving this patch to 2017-07 or marking it RWF.

I agree. Done.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
<http://www.postgrespro.com/&gt;
The Russian Postgres Company

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Fix-get_index_column_opclass-v03.patchtext/x-patch; name=0001-Fix-get_index_column_opclass-v03.patchDownload
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 0c116b3..a488c72 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -3147,9 +3147,6 @@ get_index_column_opclass(Oid index_oid, int attno)
 {
 	HeapTuple	tuple;
 	Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY;
-	Datum		datum;
-	bool		isnull;
-	oidvector  *indclass;
 	Oid			opclass;
 
 	/* First we need to know the column's opclass. */
@@ -3163,12 +3160,22 @@ get_index_column_opclass(Oid index_oid, int attno)
 	/* caller is supposed to guarantee this */
 	Assert(attno > 0 && attno <= rd_index->indnatts);
 
-	datum = SysCacheGetAttr(INDEXRELID, tuple,
-							Anum_pg_index_indclass, &isnull);
-	Assert(!isnull);
+	if (attno >= 1 && attno <= rd_index->indnkeyatts)
+	{
+		oidvector  *indclass;
+		bool		isnull;
+		Datum		datum = SysCacheGetAttr(INDEXRELID, tuple,
+											Anum_pg_index_indclass,
+											&isnull);
+		Assert(!isnull);
 
-	indclass = ((oidvector *) DatumGetPointer(datum));
-	opclass = indclass->values[attno - 1];
+		indclass = ((oidvector *) DatumGetPointer(datum));
+		opclass = indclass->values[attno - 1];
+	}
+	else
+	{
+		opclass = InvalidOid;
+	}
 
 	ReleaseSysCache(tuple);
 
0002-Introduce-ammatchorderby-function-v03.patchtext/x-patch; name=0002-Introduce-ammatchorderby-function-v03.patchDownload
diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 6b2b9e3..71636ae 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -109,7 +109,6 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BLOOM_NSTRATEGIES;
 	amroutine->amsupport = BLOOM_NPROC;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -143,6 +142,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index e95fbbc..ffb6de0 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -86,7 +86,6 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = BRIN_LAST_OPTIONAL_PROCNUM;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -120,6 +119,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 0a32182..c142d09 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -41,7 +41,6 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GINNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -75,6 +74,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 8a42eff..f60bb45 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -18,6 +18,7 @@
 #include "access/gistscan.h"
 #include "catalog/pg_collation.h"
 #include "miscadmin.h"
+#include "optimizer/paths.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "nodes/execnodes.h"
@@ -63,7 +64,6 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GISTNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -97,6 +97,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = match_orderbyop_pathkeys;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0002df3..39f7f45 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -59,7 +59,6 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = HTMaxStrategyNumber;
 	amroutine->amsupport = HASHNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -93,6 +92,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index e8725fb..3e47c37 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -110,7 +110,6 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BTMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = true;
 	amroutine->amcanmulticol = true;
@@ -144,6 +143,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = btestimateparallelscan;
 	amroutine->aminitparallelscan = btinitparallelscan;
 	amroutine->amparallelrescan = btparallelrescan;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 9919e6f..a843650 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -32,10 +32,6 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
-extern Expr *spgcanorderbyop(IndexOptInfo *index,
-				PathKey *pathkey, int pathkeyno,
-				Expr *orderby_clause, int *indexcol_p);
-
 /*
  * SP-GiST handler function: return IndexAmRoutine with access method parameters
  * and callbacks.
@@ -48,7 +44,6 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = SPGISTNProc;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -82,6 +77,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = match_orderbyop_pathkeys;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 3b5c90e..6c763ef 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -1083,7 +1083,7 @@ assignOperTypes(OpFamilyMember *member, Oid amoid, Oid typeoid)
 		 */
 		IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid, false);
 
-		if (!amroutine->amcanorderbyop)
+		if (!amroutine->ammatchorderby)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("access method \"%s\" does not support ordering operators",
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 6285a21..b7262e3 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -199,7 +199,7 @@ IndexNextWithReorder(IndexScanState *node)
 	 * with just Asserting here because the system will not try to run the
 	 * plan backwards if ExecSupportsBackwardScan() says it won't work.
 	 * Currently, that is guaranteed because no index AMs support both
-	 * amcanorderbyop and amcanbackward; if any ever do,
+	 * ammatchorderby and amcanbackward; if any ever do,
 	 * ExecSupportsBackwardScan() will need to consider indexorderbys
 	 * explicitly.
 	 */
@@ -1145,7 +1145,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
  * 5. NullTest ("indexkey IS NULL/IS NOT NULL").  We just fill in the
  * ScanKey properly.
  *
- * This code is also used to prepare ORDER BY expressions for amcanorderbyop
+ * This code is also used to prepare ORDER BY expressions for ammatchorderby
  * indexes.  The behavior is exactly the same, except that we have to look up
  * the operator differently.  Note that only cases 1 and 2 are currently
  * possible for ORDER BY.
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index f295558..e043664 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -17,6 +17,7 @@
 
 #include <math.h>
 
+#include "access/amapi.h"
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_am.h"
@@ -154,6 +155,10 @@ static void match_clause_to_index(IndexOptInfo *index,
 static bool match_clause_to_indexcol(IndexOptInfo *index,
 						 int indexcol,
 						 RestrictInfo *rinfo);
+static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
+							int indexcol,
+							Expr *clause,
+							Oid pk_opfamily);
 static bool is_indexable_operator(Oid expr_op, Oid opfamily,
 					  bool indexkey_on_left);
 static bool match_rowcompare_to_indexcol(IndexOptInfo *index,
@@ -164,8 +169,6 @@ static bool match_rowcompare_to_indexcol(IndexOptInfo *index,
 static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 						List **orderby_clauses_p,
 						List **clause_columns_p);
-static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
-							int indexcol, Expr *clause, Oid pk_opfamily);
 static bool ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
 						   EquivalenceClass *ec, EquivalenceMember *em,
 						   void *arg);
@@ -998,7 +1001,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		orderbyclauses = NIL;
 		orderbyclausecols = NIL;
 	}
-	else if (index->amcanorderbyop && pathkeys_possibly_useful)
+	else if (index->ammatchorderby && pathkeys_possibly_useful)
 	{
 		/* see if we can generate ordering operators for query_pathkeys */
 		match_pathkeys_to_index(index, root->query_pathkeys,
@@ -2545,6 +2548,99 @@ match_rowcompare_to_indexcol(IndexOptInfo *index,
 	return false;
 }
 
+Expr *
+match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey, int *indexcol_p)
+{
+	ListCell   *lc;
+
+	/* Pathkey must request default sort order for the target opfamily */
+	if (pathkey->pk_strategy != BTLessStrategyNumber ||
+		pathkey->pk_nulls_first)
+		return NULL;
+
+	/* If eclass is volatile, no hope of using an indexscan */
+	if (pathkey->pk_eclass->ec_has_volatile)
+		return NULL;
+
+	/*
+	 * Try to match eclass member expression(s) to index.  Note that child
+	 * EC members are considered, but only when they belong to the target
+	 * relation.  (Unlike regular members, the same expression could be a
+	 * child member of more than one EC.  Therefore, the same index could
+	 * be considered to match more than one pathkey list, which is OK
+	 * here.  See also get_eclass_for_sort_expr.)
+	 */
+	foreach(lc, pathkey->pk_eclass->ec_members)
+	{
+		EquivalenceMember *member = castNode(EquivalenceMember, lfirst(lc));
+		int			indexcol;
+		int			indexcol_min;
+		int			indexcol_max;
+
+		/* No possibility of match if it references other relations */
+		if (!bms_equal(member->em_relids, index->rel->relids))
+			continue;
+
+		/* If *indexcol_p is non-negative then try to match only to it */
+		if (*indexcol_p >= 0)
+		{
+			indexcol_min = *indexcol_p;
+			indexcol_max = *indexcol_p + 1;
+		}
+		else	/* try to match all columns */
+		{
+			indexcol_min = 0;
+			indexcol_max = index->ncolumns;
+		}
+
+		/*
+		 * We allow any column of the GiST index to match each pathkey;
+		 * they don't have to match left-to-right as you might expect.
+		 */
+		for (indexcol = indexcol_min; indexcol < indexcol_max; indexcol++)
+		{
+			Expr	   *expr = match_clause_to_ordering_op(index,
+														   indexcol,
+														   member->em_expr,
+														   pathkey->pk_opfamily);
+			if (expr)
+			{
+				*indexcol_p = indexcol;
+				return expr;	/* don't want to look at remaining members */
+			}
+		}
+	}
+
+	return NULL;
+}
+
+bool
+match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
+						 List **orderby_clauses_p, List **clause_columns_p)
+{
+	ListCell   *lc;
+
+	foreach(lc, pathkeys)
+	{
+		PathKey	   *pathkey = castNode(PathKey, lfirst(lc));
+		Expr	   *expr;
+		int			indexcol = -1;	/* match all index columns */
+
+		expr = match_orderbyop_pathkey(index, pathkey, &indexcol);
+
+		/*
+		 * Note: for any failure to match, we just return NIL immediately.
+		 * There is no value in matching just some of the pathkeys.
+		 */
+		if (!expr)
+			return false;
+
+		*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+		*clause_columns_p = lappend_int(*clause_columns_p, indexcol);
+	}
+
+	return true;	/* success */
+}
 
 /****************************************************************************
  *				----  ROUTINES TO CHECK ORDERING OPERATORS	----
@@ -2570,86 +2666,24 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 {
 	List	   *orderby_clauses = NIL;
 	List	   *clause_columns = NIL;
-	ListCell   *lc1;
+	ammatchorderby_function ammatchorderby =
+			(ammatchorderby_function) index->ammatchorderby;
 
-	*orderby_clauses_p = NIL;	/* set default results */
-	*clause_columns_p = NIL;
-
-	/* Only indexes with the amcanorderbyop property are interesting here */
-	if (!index->amcanorderbyop)
-		return;
-
-	foreach(lc1, pathkeys)
+	/* Only indexes with the ammatchorderby function are interesting here */
+	if (ammatchorderby &&
+		ammatchorderby(index, pathkeys, &orderby_clauses, &clause_columns))
 	{
-		PathKey    *pathkey = (PathKey *) lfirst(lc1);
-		bool		found = false;
-		ListCell   *lc2;
-
-		/*
-		 * Note: for any failure to match, we just return NIL immediately.
-		 * There is no value in matching just some of the pathkeys.
-		 */
-
-		/* Pathkey must request default sort order for the target opfamily */
-		if (pathkey->pk_strategy != BTLessStrategyNumber ||
-			pathkey->pk_nulls_first)
-			return;
+		Assert(list_length(pathkeys) == list_length(orderby_clauses));
+		Assert(list_length(pathkeys) == list_length(clause_columns));
 
-		/* If eclass is volatile, no hope of using an indexscan */
-		if (pathkey->pk_eclass->ec_has_volatile)
-			return;
-
-		/*
-		 * Try to match eclass member expression(s) to index.  Note that child
-		 * EC members are considered, but only when they belong to the target
-		 * relation.  (Unlike regular members, the same expression could be a
-		 * child member of more than one EC.  Therefore, the same index could
-		 * be considered to match more than one pathkey list, which is OK
-		 * here.  See also get_eclass_for_sort_expr.)
-		 */
-		foreach(lc2, pathkey->pk_eclass->ec_members)
-		{
-			EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
-			int			indexcol;
-
-			/* No possibility of match if it references other relations */
-			if (!bms_equal(member->em_relids, index->rel->relids))
-				continue;
-
-			/*
-			 * We allow any column of the index to match each pathkey; they
-			 * don't have to match left-to-right as you might expect.  This is
-			 * correct for GiST, which is the sole existing AM supporting
-			 * amcanorderbyop.  We might need different logic in future for
-			 * other implementations.
-			 */
-			for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
-			{
-				Expr	   *expr;
-
-				expr = match_clause_to_ordering_op(index,
-												   indexcol,
-												   member->em_expr,
-												   pathkey->pk_opfamily);
-				if (expr)
-				{
-					orderby_clauses = lappend(orderby_clauses, expr);
-					clause_columns = lappend_int(clause_columns, indexcol);
-					found = true;
-					break;
-				}
-			}
-
-			if (found)			/* don't want to look at remaining members */
-				break;
-		}
-
-		if (!found)				/* fail if no match for this pathkey */
-			return;
+		*orderby_clauses_p = orderby_clauses;	/* success! */
+		*clause_columns_p = clause_columns;
+	}
+	else
+	{
+		*orderby_clauses_p = NIL;	/* set default results */
+		*clause_columns_p = NIL;
 	}
-
-	*orderby_clauses_p = orderby_clauses;	/* success! */
-	*clause_columns_p = clause_columns;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8369e3a..6c26d26 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -266,7 +266,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 			/* We copy just the fields we need, not all of rd_amroutine */
 			amroutine = indexRelation->rd_amroutine;
-			info->amcanorderbyop = amroutine->amcanorderbyop;
+			info->ammatchorderby = amroutine->ammatchorderby;
 			info->amoptionalkey = amroutine->amoptionalkey;
 			info->amsearcharray = amroutine->amsearcharray;
 			info->amsearchnulls = amroutine->amsearchnulls;
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c
index dc04148..02879e3 100644
--- a/src/backend/utils/adt/amutils.c
+++ b/src/backend/utils/adt/amutils.c
@@ -298,7 +298,7 @@ indexam_property(FunctionCallInfo fcinfo,
 				 * a nonkey column, and null otherwise (meaning we don't
 				 * know).
 				 */
-				if (!iskey || !routine->amcanorderbyop)
+				if (!iskey || !routine->ammatchorderby)
 				{
 					res = false;
 					isnull = false;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 14526a6..a293b38 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -21,6 +21,9 @@
  */
 struct PlannerInfo;
 struct IndexPath;
+struct IndexOptInfo;
+struct PathKey;
+struct Expr;
 
 /* Likewise, this file shouldn't depend on execnodes.h. */
 struct IndexInfo;
@@ -140,6 +143,12 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/* does AM support ORDER BY result of an operator on indexed column? */
+typedef bool (*ammatchorderby_function) (struct IndexOptInfo *index,
+										 List *pathkeys,
+										 List **orderby_clauses_p,
+										 List **clause_columns_p);
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -170,8 +179,6 @@ typedef struct IndexAmRoutine
 	uint16		amsupport;
 	/* does AM support ORDER BY indexed column's value? */
 	bool		amcanorder;
-	/* does AM support ORDER BY result of an operator on indexed column? */
-	bool		amcanorderbyop;
 	/* does AM support backward scanning? */
 	bool		amcanbackward;
 	/* does AM support UNIQUE indexes? */
@@ -221,7 +228,8 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;	/* can be NULL */
 	amrestrpos_function amrestrpos; /* can be NULL */
-
+	ammatchorderby_function ammatchorderby; /* can be NULL */
+	
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
 	aminitparallelscan_function aminitparallelscan; /* can be NULL */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 7c2abbd..5bbfc5a 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -395,7 +395,7 @@ typedef struct SampleScan
  * indexorderbyops is a list of the OIDs of the operators used to sort the
  * ORDER BY expressions.  This is used together with indexorderbyorig to
  * recheck ordering at run time.  (Note that indexorderby, indexorderbyorig,
- * and indexorderbyops are used for amcanorderbyop cases, not amcanorder.)
+ * and indexorderbyops are used for ammatchorderby cases, not amcanorder.)
  *
  * indexorderdir specifies the scan ordering, for indexscans on amcanorder
  * indexes (for other indexes it should be "don't care").
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index adb4265..75ee8d6 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -805,7 +805,6 @@ typedef struct IndexOptInfo
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
 	bool		amoptionalkey;	/* can query omit key for the first column? */
 	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
 	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
@@ -814,6 +813,11 @@ typedef struct IndexOptInfo
 	bool		amcanparallel;	/* does AM support parallel scan? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
+								/* AM order-by match function */
+	bool		(*ammatchorderby) (struct IndexOptInfo *index,
+								   List *pathkeys,
+								   List **orderby_clauses_p,
+								   List **clause_columns_p);
 } IndexOptInfo;
 
 /*
@@ -1137,7 +1141,7 @@ typedef struct Path
  * (The order of multiple quals for the same index column is unspecified.)
  *
  * 'indexorderbys', if not NIL, is a list of ORDER BY expressions that have
- * been found to be usable as ordering operators for an amcanorderbyop index.
+ * been found to be usable as ordering operators for an ammatchorderby index.
  * The list must match the path's pathkeys, ie, one expression per pathkey
  * in the same order.  These are not RestrictInfos, just bare expressions,
  * since they generally won't yield booleans.  Also, unlike the case for
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index cafde30..21677eb 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -87,6 +87,10 @@ extern Expr *adjust_rowcompare_for_index(RowCompareExpr *clause,
 							int indexcol,
 							List **indexcolnos,
 							bool *var_on_left_p);
+extern Expr *match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey,
+									 int *indexcol_p);
+extern bool match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
+						 List **orderby_clauses_p, List **clause_columns_p);
 
 /*
  * tidpath.h
0003-Extract-structure-BTScanState-v03.patchtext/x-patch; name=0003-Extract-structure-BTScanState-v03.patchDownload
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 3e47c37..55c7833 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -214,6 +214,7 @@ bool
 btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 	bool		res;
 
 	/* btree indexes are never lossy */
@@ -224,7 +225,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(so->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
@@ -241,7 +242,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(so->currPos))
+		if (!BTScanPosIsValid(state->currPos))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -259,11 +260,11 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 				 * trying to optimize that, so we don't detect it, but instead
 				 * just forget any excess entries.
 				 */
-				if (so->killedItems == NULL)
-					so->killedItems = (int *)
+				if (state->killedItems == NULL)
+					state->killedItems = (int *)
 						palloc(MaxIndexTuplesPerPage * sizeof(int));
-				if (so->numKilled < MaxIndexTuplesPerPage)
-					so->killedItems[so->numKilled++] = so->currPos.itemIndex;
+				if (state->numKilled < MaxIndexTuplesPerPage)
+					state->killedItems[so->state.numKilled++] = state->currPos.itemIndex;
 			}
 
 			/*
@@ -288,6 +289,7 @@ int64
 btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	int64		ntids = 0;
 	ItemPointer heapTid;
 
@@ -320,7 +322,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				 * Advance to next tuple within page.  This is the same as the
 				 * easy case in _bt_next().
 				 */
-				if (++so->currPos.itemIndex > so->currPos.lastItem)
+				if (++currPos->itemIndex > currPos->lastItem)
 				{
 					/* let _bt_next do the heavy lifting */
 					if (!_bt_next(scan, ForwardScanDirection))
@@ -328,7 +330,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				}
 
 				/* Save tuple ID, and continue scanning */
-				heapTid = &so->currPos.items[so->currPos.itemIndex].heapTid;
+				heapTid = &currPos->items[currPos->itemIndex].heapTid;
 				tbm_add_tuples(tbm, heapTid, 1, false);
 				ntids++;
 			}
@@ -356,8 +358,8 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 
 	/* allocate private workspace */
 	so = (BTScanOpaque) palloc(sizeof(BTScanOpaqueData));
-	BTScanPosInvalidate(so->currPos);
-	BTScanPosInvalidate(so->markPos);
+	BTScanPosInvalidate(so->state.currPos);
+	BTScanPosInvalidate(so->state.markPos);
 	if (scan->numberOfKeys > 0)
 		so->keyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData));
 	else
@@ -368,15 +370,15 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	so->arrayKeys = NULL;
 	so->arrayContext = NULL;
 
-	so->killedItems = NULL;		/* until needed */
-	so->numKilled = 0;
+	so->state.killedItems = NULL;		/* until needed */
+	so->state.numKilled = 0;
 
 	/*
 	 * We don't know yet whether the scan will be index-only, so we do not
 	 * allocate the tuple workspace arrays until btrescan.  However, we set up
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
-	so->currTuples = so->markTuples = NULL;
+	so->state.currTuples = so->state.markTuples = NULL;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -385,6 +387,45 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	return scan;
 }
 
+static void
+_bt_release_current_position(BTScanState state, Relation indexRelation,
+							 bool invalidate)
+{
+	/* we aren't holding any read locks, but gotta drop the pins */
+	if (BTScanPosIsValid(state->currPos))
+	{
+		/* Before leaving current page, deal with any killed items */
+		if (state->numKilled > 0)
+			_bt_killitems(state, indexRelation);
+
+		BTScanPosUnpinIfPinned(state->currPos);
+
+		if (invalidate)
+			BTScanPosInvalidate(state->currPos);
+	}
+}
+
+static void
+_bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
+{
+	/* No need to invalidate positions, if the RAM is about to be freed. */
+	_bt_release_current_position(state, scan->indexRelation, !free);
+
+	state->markItemIndex = -1;
+	BTScanPosUnpinIfPinned(state->markPos);
+
+	if (free)
+	{
+		if (state->killedItems != NULL)
+			pfree(state->killedItems);
+		if (state->currTuples != NULL)
+			pfree(state->currTuples);
+		/* markTuples should not be pfree'd (_bt_allocate_tuple_workspaces) */
+	}
+	else
+		BTScanPosInvalidate(state->markPos);
+}
+
 /*
  *	btrescan() -- rescan an index relation
  */
@@ -393,21 +434,11 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 		 ScanKey orderbys, int norderbys)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-		BTScanPosInvalidate(so->currPos);
-	}
+	_bt_release_scan_state(scan, state, false);
 
-	so->markItemIndex = -1;
-	so->arrayKeyCount = 0;
-	BTScanPosUnpinIfPinned(so->markPos);
-	BTScanPosInvalidate(so->markPos);
+	so->arrayKeyCount = 0; /* FIXME in _bt_release_scan_state */
 
 	/*
 	 * Allocate tuple workspace arrays, if needed for an index-only scan and
@@ -425,11 +456,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 	 * a SIGSEGV is not possible.  Yeah, this is ugly as sin, but it beats
 	 * adding special-case treatment for name_ops elsewhere.
 	 */
-	if (scan->xs_want_itup && so->currTuples == NULL)
-	{
-		so->currTuples = (char *) palloc(BLCKSZ * 2);
-		so->markTuples = so->currTuples + BLCKSZ;
-	}
+	if (scan->xs_want_itup && state->currTuples == NULL)
+		_bt_allocate_tuple_workspaces(state);
 
 	/*
 	 * Reset the scan keys. Note that keys ordering stuff moved to _bt_first.
@@ -453,19 +481,7 @@ btendscan(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-	}
-
-	so->markItemIndex = -1;
-	BTScanPosUnpinIfPinned(so->markPos);
-
-	/* No need to invalidate positions, the RAM is about to be freed. */
+	_bt_release_scan_state(scan, &so->state, true);
 
 	/* Release storage */
 	if (so->keyData != NULL)
@@ -473,24 +489,15 @@ btendscan(IndexScanDesc scan)
 	/* so->arrayKeyData and so->arrayKeys are in arrayContext */
 	if (so->arrayContext != NULL)
 		MemoryContextDelete(so->arrayContext);
-	if (so->killedItems != NULL)
-		pfree(so->killedItems);
-	if (so->currTuples != NULL)
-		pfree(so->currTuples);
-	/* so->markTuples should not be pfree'd, see btrescan */
+
 	pfree(so);
 }
 
-/*
- *	btmarkpos() -- save current scan position
- */
-void
-btmarkpos(IndexScanDesc scan)
+static void
+_bt_mark_current_position(BTScanState state)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-
 	/* There may be an old mark with a pin (but no lock). */
-	BTScanPosUnpinIfPinned(so->markPos);
+	BTScanPosUnpinIfPinned(state->markPos);
 
 	/*
 	 * Just record the current itemIndex.  If we later step to next page
@@ -498,32 +505,34 @@ btmarkpos(IndexScanDesc scan)
 	 * the currPos struct in markPos.  If (as often happens) the mark is moved
 	 * before we leave the page, we don't have to do that work.
 	 */
-	if (BTScanPosIsValid(so->currPos))
-		so->markItemIndex = so->currPos.itemIndex;
+	if (BTScanPosIsValid(state->currPos))
+		state->markItemIndex = state->currPos.itemIndex;
 	else
 	{
-		BTScanPosInvalidate(so->markPos);
-		so->markItemIndex = -1;
+		BTScanPosInvalidate(state->markPos);
+		state->markItemIndex = -1;
 	}
-
-	/* Also record the current positions of any array keys */
-	if (so->numArrayKeys)
-		_bt_mark_array_keys(scan);
 }
 
 /*
- *	btrestrpos() -- restore scan to last saved position
+ *	btmarkpos() -- save current scan position
  */
 void
-btrestrpos(IndexScanDesc scan)
+btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* Restore the marked positions of any array keys */
+	_bt_mark_current_position(&so->state);
+
+	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
-		_bt_restore_array_keys(scan);
+		_bt_mark_array_keys(scan);
+}
 
-	if (so->markItemIndex >= 0)
+static void
+_bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
+{
+	if (state->markItemIndex >= 0)
 	{
 		/*
 		 * The scan has never moved to a new page since the last mark.  Just
@@ -532,7 +541,7 @@ btrestrpos(IndexScanDesc scan)
 		 * NB: In this case we can't count on anything in so->markPos to be
 		 * accurate.
 		 */
-		so->currPos.itemIndex = so->markItemIndex;
+		state->currPos.itemIndex = state->markItemIndex;
 	}
 	else
 	{
@@ -542,28 +551,21 @@ btrestrpos(IndexScanDesc scan)
 		 * locks, but if we're still holding the pin for the current position,
 		 * we must drop it.
 		 */
-		if (BTScanPosIsValid(so->currPos))
-		{
-			/* Before leaving current page, deal with any killed items */
-			if (so->numKilled > 0)
-				_bt_killitems(scan);
-			BTScanPosUnpinIfPinned(so->currPos);
-		}
+		_bt_release_current_position(state, scan->indexRelation,
+									 !BTScanPosIsValid(state->markPos));
 
-		if (BTScanPosIsValid(so->markPos))
+		if (BTScanPosIsValid(state->markPos))
 		{
 			/* bump pin on mark buffer for assignment to current buffer */
-			if (BTScanPosIsPinned(so->markPos))
-				IncrBufferRefCount(so->markPos.buf);
-			memcpy(&so->currPos, &so->markPos,
+			if (BTScanPosIsPinned(state->markPos))
+				IncrBufferRefCount(state->markPos.buf);
+			memcpy(&state->currPos, &state->markPos,
 				   offsetof(BTScanPosData, items[1]) +
-				   so->markPos.lastItem * sizeof(BTScanPosItem));
-			if (so->currTuples)
-				memcpy(so->currTuples, so->markTuples,
-					   so->markPos.nextTupleOffset);
+				   state->markPos.lastItem * sizeof(BTScanPosItem));
+			if (state->currTuples)
+				memcpy(state->currTuples, state->markTuples,
+					   state->markPos.nextTupleOffset);
 		}
-		else
-			BTScanPosInvalidate(so->currPos);
 	}
 }
 
@@ -779,9 +781,10 @@ _bt_parallel_advance_array_keys(IndexScanDesc scan)
 }
 
 /*
- * _bt_vacuum_needs_cleanup() -- Checks if index needs cleanup assuming that
- *			btbulkdelete() wasn't called.
- */
+- * _bt_vacuum_needs_cleanup() -- Checks if index needs cleanup assuming that
+- *			btbulkdelete() wasn't called.
++ *	btrestrpos() -- restore scan to last saved position
+  */
 static bool
 _bt_vacuum_needs_cleanup(IndexVacuumInfo *info)
 {
@@ -844,6 +847,21 @@ _bt_vacuum_needs_cleanup(IndexVacuumInfo *info)
 }
 
 /*
+ *	btrestrpos() -- restore scan to last saved position
+ */
+void
+btrestrpos(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
+	/* Restore the marked positions of any array keys */
+	if (so->numArrayKeys)
+		_bt_restore_array_keys(scan);
+
+	_bt_restore_marked_position(scan, &so->state);
+}
+
+/*
  * Bulk deletion of all index entries pointing to a set of heap tuples.
  * The set of target tuples is specified via a callback routine that tells
  * whether any given heap tuple (identified by ItemPointer) is being deleted.
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index d3700bd..2b63e0c 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -25,18 +25,19 @@
 #include "utils/tqual.h"
 
 
-static bool _bt_readpage(IndexScanDesc scan, ScanDirection dir,
+static bool _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 			 OffsetNumber offnum);
-static void _bt_saveitem(BTScanOpaque so, int itemIndex,
+static void _bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup);
-static bool _bt_steppage(IndexScanDesc scan, ScanDirection dir);
-static bool _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir);
+static bool _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir);
+static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
+				 BlockNumber blkno, ScanDirection dir);
 static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
 					  ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf, Snapshot snapshot);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
-static inline void _bt_initialize_more_data(BTScanOpaque so, ScanDirection dir);
+static inline void _bt_initialize_more_data(BTScanState state, ScanDirection dir);
 
 
 /*
@@ -545,6 +546,58 @@ _bt_compare(Relation rel,
 }
 
 /*
+ * _bt_return_current_item() -- Prepare current scan state item for return.
+ *
+ * This function is used only in "return _bt_return_current_item();" statements
+ * and always returns true.
+ */
+static inline bool
+_bt_return_current_item(IndexScanDesc scan, BTScanState state)
+{
+	BTScanPosItem *currItem = &state->currPos.items[state->currPos.itemIndex];
+
+	scan->xs_ctup.t_self = currItem->heapTid;
+
+	if (scan->xs_want_itup)
+		scan->xs_itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+
+	return true;
+}
+
+/*
+ * _bt_load_first_page() -- Load data from the first page of the scan.
+ *
+ * Caller must have pinned and read-locked state->currPos.buf.
+ *
+ * On success exit, state->currPos is updated to contain data from the next
+ * interesting page.  For success on a scan using a non-MVCC snapshot we hold
+ * a pin, but not a read lock, on that page.  If we do not hold the pin, we
+ * set state->currPos.buf to InvalidBuffer.  We return true to indicate success.
+ *
+ * If there are no more matching records in the given direction at all,
+ * we drop all locks and pins, set state->currPos.buf to InvalidBuffer,
+ * and return false.
+ */
+static bool
+_bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+					OffsetNumber offnum)
+{
+	if (!_bt_readpage(scan, state, dir, offnum))
+	{
+		/*
+		 * There's no actually-matching data on this page.  Try to advance to
+		 * the next page.  Return false if there's no matching data at all.
+		 */
+		LockBuffer(state->currPos.buf, BUFFER_LOCK_UNLOCK);
+		return _bt_steppage(scan, state, dir);
+	}
+
+	/* Drop the lock, and maybe the pin, on the current page */
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
+	return true;
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -569,6 +622,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	BTStack		stack;
 	OffsetNumber offnum;
@@ -582,10 +636,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	int			i;
 	bool		status = true;
 	StrategyNumber strat_total;
-	BTScanPosItem *currItem;
 	BlockNumber blkno;
 
-	Assert(!BTScanPosIsValid(so->currPos));
+	Assert(!BTScanPosIsValid(*currPos));
 
 	pgstat_count_index_scan(rel);
 
@@ -1076,7 +1129,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 * their scan
 		 */
 		_bt_parallel_done(scan);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 
 		return false;
 	}
@@ -1084,7 +1137,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		PredicateLockPage(rel, BufferGetBlockNumber(buf),
 						  scan->xs_snapshot);
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
 	/* position to the precise item on the page */
 	offnum = _bt_binsrch(rel, buf, keysCount, scankeys, nextkey);
@@ -1111,36 +1164,36 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		offnum = OffsetNumberPrev(offnum);
 
 	/* remember which buffer we have pinned, if any */
-	Assert(!BTScanPosIsValid(so->currPos));
-	so->currPos.buf = buf;
+	Assert(!BTScanPosIsValid(*currPos));
+	currPos->buf = buf;
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, offnum))
+	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
+		return false;
+
+readcomplete:
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
+}
+
+/*
+ *	Advance to next tuple on current page; or if there's no more,
+ *	try to step to the next page with data.
+ */
+static bool
+_bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
+{
+	if (ScanDirectionIsForward(dir))
 	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
+		if (++state->currPos.itemIndex <= state->currPos.lastItem)
+			return true;
 	}
 	else
 	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+		if (--state->currPos.itemIndex >= state->currPos.firstItem)
+			return true;
 	}
 
-readcomplete:
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_steppage(scan, state, dir);
 }
 
 /*
@@ -1161,44 +1214,20 @@ bool
 _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-	BTScanPosItem *currItem;
 
-	/*
-	 * Advance to next tuple on current page; or if there's no more, try to
-	 * step to the next page with data.
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (++so->currPos.itemIndex > so->currPos.lastItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
-	else
-	{
-		if (--so->currPos.itemIndex < so->currPos.firstItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
+	if (!_bt_next_item(scan, &so->state, dir))
+		return false;
 
 	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
  *	_bt_readpage() -- Load data from current index page into so->currPos
  *
- * Caller must have pinned and read-locked so->currPos.buf; the buffer's state
- * is not changed here.  Also, currPos.moreLeft and moreRight must be valid;
- * they are updated as appropriate.  All other fields of so->currPos are
+ * Caller must have pinned and read-locked pos->buf; the buffer's state
+ * is not changed here.  Also, pos->moreLeft and moreRight must be valid;
+ * they are updated as appropriate.  All other fields of pos are
  * initialized from scratch here.
  *
  * We scan the current page starting at offnum and moving in the indicated
@@ -1213,9 +1242,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
  * Returns true if any matching items found on the page, false if none.
  */
 static bool
-_bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
+_bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+			 OffsetNumber offnum)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
@@ -1228,9 +1258,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We must have the buffer pinned and locked, but the usual macro can't be
 	 * used here; this function is what makes it good for currPos.
 	 */
-	Assert(BufferIsValid(so->currPos.buf));
+	Assert(BufferIsValid(pos->buf));
 
-	page = BufferGetPage(so->currPos.buf);
+	page = BufferGetPage(pos->buf);
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 
 	/* allow next page be processed by parallel worker */
@@ -1239,7 +1269,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		if (ScanDirectionIsForward(dir))
 			_bt_parallel_release(scan, opaque->btpo_next);
 		else
-			_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+			_bt_parallel_release(scan, BufferGetBlockNumber(pos->buf));
 	}
 
 	minoff = P_FIRSTDATAKEY(opaque);
@@ -1249,30 +1279,30 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We note the buffer's block number so that we can release the pin later.
 	 * This allows us to re-read the buffer if it is needed again for hinting.
 	 */
-	so->currPos.currPage = BufferGetBlockNumber(so->currPos.buf);
+	pos->currPage = BufferGetBlockNumber(pos->buf);
 
 	/*
 	 * We save the LSN of the page as we read it, so that we know whether it
 	 * safe to apply LP_DEAD hints to the page later.  This allows us to drop
 	 * the pin for MVCC scans, which allows vacuum to avoid blocking.
 	 */
-	so->currPos.lsn = BufferGetLSNAtomic(so->currPos.buf);
+	pos->lsn = BufferGetLSNAtomic(pos->buf);
 
 	/*
 	 * we must save the page's right-link while scanning it; this tells us
 	 * where to step right to after we're done with these items.  There is no
 	 * corresponding need for the left-link, since splits always go right.
 	 */
-	so->currPos.nextPage = opaque->btpo_next;
+	pos->nextPage = opaque->btpo_next;
 
 	/* initialize tuple workspace to empty */
-	so->currPos.nextTupleOffset = 0;
+	pos->nextTupleOffset = 0;
 
 	/*
 	 * Now that the current page has been made consistent, the macro should be
 	 * good.
 	 */
-	Assert(BTScanPosIsPinned(so->currPos));
+	Assert(BTScanPosIsPinned(*pos));
 
 	if (ScanDirectionIsForward(dir))
 	{
@@ -1287,13 +1317,13 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			if (itup != NULL)
 			{
 				/* tuple passes all scan key conditions, so remember it */
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 				itemIndex++;
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreRight = false;
+				pos->moreRight = false;
 				break;
 			}
 
@@ -1301,9 +1331,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex <= MaxIndexTuplesPerPage);
-		so->currPos.firstItem = 0;
-		so->currPos.lastItem = itemIndex - 1;
-		so->currPos.itemIndex = 0;
+		pos->firstItem = 0;
+		pos->lastItem = itemIndex - 1;
+		pos->itemIndex = 0;
 	}
 	else
 	{
@@ -1319,12 +1349,12 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			{
 				/* tuple passes all scan key conditions, so remember it */
 				itemIndex--;
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreLeft = false;
+				pos->moreLeft = false;
 				break;
 			}
 
@@ -1332,30 +1362,31 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex >= 0);
-		so->currPos.firstItem = itemIndex;
-		so->currPos.lastItem = MaxIndexTuplesPerPage - 1;
-		so->currPos.itemIndex = MaxIndexTuplesPerPage - 1;
+		pos->firstItem = itemIndex;
+		pos->lastItem = MaxIndexTuplesPerPage - 1;
+		pos->itemIndex = MaxIndexTuplesPerPage - 1;
 	}
 
-	return (so->currPos.firstItem <= so->currPos.lastItem);
+	return (pos->firstItem <= pos->lastItem);
 }
 
 /* Save an index item into so->currPos.items[itemIndex] */
 static void
-_bt_saveitem(BTScanOpaque so, int itemIndex,
+_bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup)
 {
-	BTScanPosItem *currItem = &so->currPos.items[itemIndex];
+	BTScanPosItem *currItem = &state->currPos.items[itemIndex];
 
 	currItem->heapTid = itup->t_tid;
 	currItem->indexOffset = offnum;
-	if (so->currTuples)
+	if (state->currTuples)
 	{
 		Size		itupsz = IndexTupleSize(itup);
 
-		currItem->tupleOffset = so->currPos.nextTupleOffset;
-		memcpy(so->currTuples + so->currPos.nextTupleOffset, itup, itupsz);
-		so->currPos.nextTupleOffset += MAXALIGN(itupsz);
+		currItem->tupleOffset = state->currPos.nextTupleOffset;
+		memcpy(state->currTuples + state->currPos.nextTupleOffset,
+			   itup, itupsz);
+		state->currPos.nextTupleOffset += MAXALIGN(itupsz);
 	}
 }
 
@@ -1371,35 +1402,36 @@ _bt_saveitem(BTScanOpaque so, int itemIndex,
  * to InvalidBuffer.  We return true to indicate success.
  */
 static bool
-_bt_steppage(IndexScanDesc scan, ScanDirection dir)
+_bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
+	Relation	rel = scan->indexRelation;
 	BlockNumber blkno = InvalidBlockNumber;
 	bool		status = true;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(*currPos));
 
 	/* Before leaving current page, deal with any killed items */
-	if (so->numKilled > 0)
-		_bt_killitems(scan);
+	if (state->numKilled > 0)
+		_bt_killitems(state, rel);
 
 	/*
 	 * Before we modify currPos, make a copy of the page data if there was a
 	 * mark position that needs it.
 	 */
-	if (so->markItemIndex >= 0)
+	if (state->markItemIndex >= 0)
 	{
 		/* bump pin on current buffer for assignment to mark buffer */
-		if (BTScanPosIsPinned(so->currPos))
-			IncrBufferRefCount(so->currPos.buf);
-		memcpy(&so->markPos, &so->currPos,
+		if (BTScanPosIsPinned(*currPos))
+			IncrBufferRefCount(currPos->buf);
+		memcpy(&state->markPos, currPos,
 			   offsetof(BTScanPosData, items[1]) +
-			   so->currPos.lastItem * sizeof(BTScanPosItem));
-		if (so->markTuples)
-			memcpy(so->markTuples, so->currTuples,
-				   so->currPos.nextTupleOffset);
-		so->markPos.itemIndex = so->markItemIndex;
-		so->markItemIndex = -1;
+			   currPos->lastItem * sizeof(BTScanPosItem));
+		if (state->markTuples)
+			memcpy(state->markTuples, state->currTuples,
+				   currPos->nextTupleOffset);
+		state->markPos.itemIndex = state->markItemIndex;
+		state->markItemIndex = -1;
 	}
 
 	if (ScanDirectionIsForward(dir))
@@ -1415,27 +1447,27 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
-				BTScanPosUnpinIfPinned(so->currPos);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosUnpinIfPinned(*currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so use the previously-saved nextPage link. */
-			blkno = so->currPos.nextPage;
+			blkno = currPos->nextPage;
 		}
 
 		/* Remember we left a page with data */
-		so->currPos.moreLeft = true;
+		currPos->moreLeft = true;
 
 		/* release the previous buffer, if pinned */
-		BTScanPosUnpinIfPinned(so->currPos);
+		BTScanPosUnpinIfPinned(*currPos);
 	}
 	else
 	{
 		/* Remember we left a page with data */
-		so->currPos.moreRight = true;
+		currPos->moreRight = true;
 
 		if (scan->parallel_scan != NULL)
 		{
@@ -1444,25 +1476,25 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			 * ended already, bail out.
 			 */
 			status = _bt_parallel_seize(scan, &blkno);
-			BTScanPosUnpinIfPinned(so->currPos);
+			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so just use our own notion of the current page */
-			blkno = so->currPos.currPage;
+			blkno = currPos->currPage;
 		}
 	}
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, currPos);
 
 	return true;
 }
@@ -1478,9 +1510,10 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
  * locks and pins, set so->currPos.buf to InvalidBuffer, and return false.
  */
 static bool
-_bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+				 ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
 	Relation	rel;
 	Page		page;
 	BTPageOpaque opaque;
@@ -1496,17 +1529,17 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * if we're at end of scan, give up and mark parallel scan as
 			 * done, so that all the workers can finish their scan
 			 */
-			if (blkno == P_NONE || !so->currPos.moreRight)
+			if (blkno == P_NONE || !currPos->moreRight)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 			/* check for interrupts while we're not holding any buffer lock */
 			CHECK_FOR_INTERRUPTS();
 			/* step right one page */
-			so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
-			page = BufferGetPage(so->currPos.buf);
+			currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			/* check for deleted page */
@@ -1515,7 +1548,7 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 				PredicateLockPage(rel, blkno, scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreRight if we can stop */
-				if (_bt_readpage(scan, dir, P_FIRSTDATAKEY(opaque)))
+				if (_bt_readpage(scan, state, dir, P_FIRSTDATAKEY(opaque)))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
@@ -1527,18 +1560,18 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
 			}
 			else
 			{
 				blkno = opaque->btpo_next;
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 			}
 		}
 	}
@@ -1548,10 +1581,10 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * Should only happen in parallel cases, when some other backend
 		 * advanced the scan.
 		 */
-		if (so->currPos.currPage != blkno)
+		if (currPos->currPage != blkno)
 		{
-			BTScanPosUnpinIfPinned(so->currPos);
-			so->currPos.currPage = blkno;
+			BTScanPosUnpinIfPinned(*currPos);
+			currPos->currPage = blkno;
 		}
 
 		/*
@@ -1576,31 +1609,30 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * is MVCC the page cannot move past the half-dead state to fully
 		 * deleted.
 		 */
-		if (BTScanPosIsPinned(so->currPos))
-			LockBuffer(so->currPos.buf, BT_READ);
+		if (BTScanPosIsPinned(*currPos))
+			LockBuffer(currPos->buf, BT_READ);
 		else
-			so->currPos.buf = _bt_getbuf(rel, so->currPos.currPage, BT_READ);
+			currPos->buf = _bt_getbuf(rel, currPos->currPage, BT_READ);
 
 		for (;;)
 		{
 			/* Done if we know there are no matching keys to the left */
-			if (!so->currPos.moreLeft)
+			if (!currPos->moreLeft)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
 			/* Step to next physical page */
-			so->currPos.buf = _bt_walk_left(rel, so->currPos.buf,
-											scan->xs_snapshot);
+			currPos->buf = _bt_walk_left(rel, currPos->buf, scan->xs_snapshot);
 
 			/* if we're physically at end of index, return failure */
-			if (so->currPos.buf == InvalidBuffer)
+			if (currPos->buf == InvalidBuffer)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
@@ -1609,21 +1641,21 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * it's not half-dead and contains matching tuples. Else loop back
 			 * and do it all again.
 			 */
-			page = BufferGetPage(so->currPos.buf);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			if (!P_IGNORE(opaque))
 			{
-				PredicateLockPage(rel, BufferGetBlockNumber(so->currPos.buf), scan->xs_snapshot);
+				PredicateLockPage(rel, BufferGetBlockNumber(currPos->buf), scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreLeft if we can stop */
-				if (_bt_readpage(scan, dir, PageGetMaxOffsetNumber(page)))
+				if (_bt_readpage(scan, state, dir, PageGetMaxOffsetNumber(page)))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -1634,14 +1666,14 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
-				so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
+				currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
 			}
 		}
 	}
@@ -1660,13 +1692,13 @@ _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
 
 	return true;
 }
@@ -1891,11 +1923,11 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber start;
-	BTScanPosItem *currItem;
 
 	/*
 	 * Scan down to the leftmost or rightmost leaf page.  This is a simplified
@@ -1911,7 +1943,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 		 * exists.
 		 */
 		PredicateLockRelation(rel, scan->xs_snapshot);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 		return false;
 	}
 
@@ -1940,36 +1972,15 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 	}
 
 	/* remember which buffer we have pinned */
-	so->currPos.buf = buf;
+	currPos->buf = buf;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, start))
-	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
-	}
-	else
-	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
-	}
-
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
+	if (!_bt_load_first_page(scan, &so->state, dir, start))
+		return false;
 
-	return true;
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
@@ -1977,19 +1988,19 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
  * for scan direction
  */
 static inline void
-_bt_initialize_more_data(BTScanOpaque so, ScanDirection dir)
+_bt_initialize_more_data(BTScanState state, ScanDirection dir)
 {
 	/* initialize moreLeft/moreRight appropriately for scan direction */
 	if (ScanDirectionIsForward(dir))
 	{
-		so->currPos.moreLeft = false;
-		so->currPos.moreRight = true;
+		state->currPos.moreLeft = false;
+		state->currPos.moreRight = true;
 	}
 	else
 	{
-		so->currPos.moreLeft = true;
-		so->currPos.moreRight = false;
+		state->currPos.moreLeft = true;
+		state->currPos.moreRight = false;
 	}
-	so->numKilled = 0;			/* just paranoia */
-	so->markItemIndex = -1;		/* ditto */
+	state->numKilled = 0;		/* just paranoia */
+	state->markItemIndex = -1;	/* ditto */
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 4528e87..9bf453c 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -1741,26 +1741,26 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, TupleDesc tupdesc,
  * away and the TID was re-used by a completely different heap tuple.
  */
 void
-_bt_killitems(IndexScanDesc scan)
+_bt_killitems(BTScanState state, Relation indexRelation)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
 	OffsetNumber maxoff;
 	int			i;
-	int			numKilled = so->numKilled;
+	int			numKilled = state->numKilled;
 	bool		killedsomething = false;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(state->currPos));
 
 	/*
 	 * Always reset the scan state, so we don't look for same items on other
 	 * pages.
 	 */
-	so->numKilled = 0;
+	state->numKilled = 0;
 
-	if (BTScanPosIsPinned(so->currPos))
+	if (BTScanPosIsPinned(*pos))
 	{
 		/*
 		 * We have held the pin on this page since we read the index tuples,
@@ -1768,44 +1768,42 @@ _bt_killitems(IndexScanDesc scan)
 		 * re-use of any TID on the page, so there is no need to check the
 		 * LSN.
 		 */
-		LockBuffer(so->currPos.buf, BT_READ);
-
-		page = BufferGetPage(so->currPos.buf);
+		LockBuffer(pos->buf, BT_READ);
 	}
 	else
 	{
 		Buffer		buf;
 
 		/* Attempt to re-read the buffer, getting pin and lock. */
-		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ);
+		buf = _bt_getbuf(indexRelation, pos->currPage, BT_READ);
 
 		/* It might not exist anymore; in which case we can't hint it. */
 		if (!BufferIsValid(buf))
 			return;
 
-		page = BufferGetPage(buf);
-		if (BufferGetLSNAtomic(buf) == so->currPos.lsn)
-			so->currPos.buf = buf;
+		if (BufferGetLSNAtomic(buf) == pos->lsn)
+			pos->buf = buf;
 		else
 		{
 			/* Modified while not pinned means hinting is not safe. */
-			_bt_relbuf(scan->indexRelation, buf);
+			_bt_relbuf(indexRelation, buf);
 			return;
 		}
 	}
 
+	page = BufferGetPage(pos->buf);
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 	minoff = P_FIRSTDATAKEY(opaque);
 	maxoff = PageGetMaxOffsetNumber(page);
 
 	for (i = 0; i < numKilled; i++)
 	{
-		int			itemIndex = so->killedItems[i];
-		BTScanPosItem *kitem = &so->currPos.items[itemIndex];
+		int			itemIndex = state->killedItems[i];
+		BTScanPosItem *kitem = &pos->items[itemIndex];
 		OffsetNumber offnum = kitem->indexOffset;
 
-		Assert(itemIndex >= so->currPos.firstItem &&
-			   itemIndex <= so->currPos.lastItem);
+		Assert(itemIndex >= pos->firstItem &&
+			   itemIndex <= pos->lastItem);
 		if (offnum < minoff)
 			continue;			/* pure paranoia */
 		while (offnum <= maxoff)
@@ -1833,10 +1831,10 @@ _bt_killitems(IndexScanDesc scan)
 	if (killedsomething)
 	{
 		opaque->btpo_flags |= BTP_HAS_GARBAGE;
-		MarkBufferDirtyHint(so->currPos.buf, true);
+		MarkBufferDirtyHint(pos->buf, true);
 	}
 
-	LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
+	LockBuffer(pos->buf, BUFFER_LOCK_UNLOCK);
 }
 
 
@@ -2214,3 +2212,14 @@ _bt_check_natts(Relation rel, Page page, OffsetNumber offnum)
 
 	}
 }
+
+/*
+ * _bt_allocate_tuple_workspaces() -- Allocate buffers for saving index tuples
+ * 		in index-only scans.
+ */
+void
+_bt_allocate_tuple_workspaces(BTScanState state)
+{
+	state->currTuples = (char *) palloc(BLCKSZ * 2);
+	state->markTuples = state->currTuples + BLCKSZ;
+}
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 04ecb4c..388b311 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -434,22 +434,8 @@ typedef struct BTArrayKeyInfo
 	Datum	   *elem_values;	/* array of num_elems Datums */
 } BTArrayKeyInfo;
 
-typedef struct BTScanOpaqueData
+typedef struct BTScanStateData
 {
-	/* these fields are set by _bt_preprocess_keys(): */
-	bool		qual_ok;		/* false if qual can never be satisfied */
-	int			numberOfKeys;	/* number of preprocessed scan keys */
-	ScanKey		keyData;		/* array of preprocessed scan keys */
-
-	/* workspace for SK_SEARCHARRAY support */
-	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
-	int			numArrayKeys;	/* number of equality-type array keys (-1 if
-								 * there are any unsatisfiable array keys) */
-	int			arrayKeyCount;	/* count indicating number of array scan keys
-								 * processed */
-	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
-	MemoryContext arrayContext; /* scan-lifespan context for array data */
-
 	/* info about killed items if any (killedItems is NULL if never used) */
 	int		   *killedItems;	/* currPos.items indexes of killed items */
 	int			numKilled;		/* number of currently stored items */
@@ -474,6 +460,25 @@ typedef struct BTScanOpaqueData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+}	BTScanStateData, *BTScanState;
+
+typedef struct BTScanOpaqueData
+{
+	/* these fields are set by _bt_preprocess_keys(): */
+	bool		qual_ok;		/* false if qual can never be satisfied */
+	int			numberOfKeys;	/* number of preprocessed scan keys */
+	ScanKey		keyData;		/* array of preprocessed scan keys */
+
+	/* workspace for SK_SEARCHARRAY support */
+	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
+	int			numArrayKeys;	/* number of equality-type array keys (-1 if
+								 * there are any unsatisfiable array keys) */
+	int			arrayKeyCount;	/* count indicating number of array scan keys
+								 * processed */
+	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
+	MemoryContext arrayContext; /* scan-lifespan context for array data */
+
+	BTScanStateData	state;
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -590,7 +595,7 @@ extern void _bt_preprocess_keys(IndexScanDesc scan);
 extern IndexTuple _bt_checkkeys(IndexScanDesc scan,
 			  Page page, OffsetNumber offnum,
 			  ScanDirection dir, bool *continuescan);
-extern void _bt_killitems(IndexScanDesc scan);
+extern void _bt_killitems(BTScanState state, Relation indexRelation);
 extern BTCycleId _bt_vacuum_cycleid(Relation rel);
 extern BTCycleId _bt_start_vacuum(Relation rel);
 extern void _bt_end_vacuum(Relation rel);
@@ -603,6 +608,7 @@ extern bool btproperty(Oid index_oid, int attno,
 		   bool *res, bool *isnull);
 extern IndexTuple _bt_nonkey_truncate(Relation rel, IndexTuple itup);
 extern bool _bt_check_natts(Relation rel, Page page, OffsetNumber offnum);
+extern void _bt_allocate_tuple_workspaces(BTScanState state);
 
 /*
  * prototypes for functions in nbtvalidate.c
0004-Add-kNN-support-to-btree-v03.patchtext/x-patch; name=0004-Add-kNN-support-to-btree-v03.patchDownload
diff --git a/doc/src/sgml/btree.sgml b/doc/src/sgml/btree.sgml
index 8bd0bad..ed9625e 100644
--- a/doc/src/sgml/btree.sgml
+++ b/doc/src/sgml/btree.sgml
@@ -200,6 +200,20 @@
   planner relies on them for optimization purposes.
  </para>
 
+ <para>
+  FIXME!!!
+  To implement the distance ordered (nearest-neighbor) search, we only need
+  to define a distance operator (usually it called &lt;-&gt;) with a correpsonding
+  operator family for distance comparison in the index's operator class.
+  These operators must satisfy the following assumptions for all non-null
+  values A,B,C of the datatype:
+
+  A &lt;-&gt; B = B &lt;-&gt; A						symmetric law
+  if A = B, then A &lt;-&gt; C = B &lt;-&gt; C		distance equivalence
+  if (A &lt;= B and B &lt;= C) or (A &gt;= B and B &gt;= C),
+  then A &lt;-&gt; B &lt;= A &lt;-&gt; C			monotonicity
+ </para>
+
 </sect1>
 
 <sect1 id="btree-support-funcs">
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index df7d16f..9015557 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -175,6 +175,17 @@ CREATE INDEX test1_id_index ON test1 (id);
   </para>
 
   <para>
+   B-tree indexes are also capable of optimizing <quote>nearest-neighbor</>
+   searches, such as
+<programlisting><![CDATA[
+SELECT * FROM events ORDER BY event_date <-> date '2017-05-05' LIMIT 10;
+]]>
+</programlisting>
+   which finds the ten events closest to a given target date.  The ability
+   to do this is again dependent on the particular operator class being used.
+  </para>
+
+  <para>
    <indexterm>
     <primary>index</primary>
     <secondary>hash</secondary>
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 9446f8b..93094bc 100644
--- a/doc/src/sgml/xindex.sgml
+++ b/doc/src/sgml/xindex.sgml
@@ -1242,7 +1242,8 @@ SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
   <title>Ordering Operators</title>
 
   <para>
-   Some index access methods (currently, only GiST and SP-GiST) support the concept of
+   Some index access methods (currently, only B-tree, GiST and SP-GiST) 
+   support the concept of
    <firstterm>ordering operators</firstterm>.  What we have been discussing so far
    are <firstterm>search operators</firstterm>.  A search operator is one for which
    the index can be searched to find all rows satisfying
diff --git a/src/backend/access/nbtree/README b/src/backend/access/nbtree/README
index 3680e69..3f7e1b1 100644
--- a/src/backend/access/nbtree/README
+++ b/src/backend/access/nbtree/README
@@ -659,3 +659,20 @@ routines must treat it accordingly.  The actual key stored in the
 item is irrelevant, and need not be stored at all.  This arrangement
 corresponds to the fact that an L&Y non-leaf page has one more pointer
 than key.
+
+Nearest-neighbor search
+-----------------------
+
+There is a special scan strategy for nearest-neighbor (kNN) search,
+that is used in queries with ORDER BY distance clauses like this:
+SELECT * FROM tab WHERE col > const1 ORDER BY col <-> const2 LIMIT k.
+But, unlike GiST, B-tree supports only a one ordering operator on the
+first index column.
+
+At the beginning of kNN scan, we need to determine which strategy we
+will use --- a special bidirectional or a ordinary unidirectional.
+If the point from which we measure the distance falls into the scan range,
+we use bidirectional scan starting from this point, else we use simple
+unidirectional scan in the right direction.  Algorithm of a bidirectional
+scan is very simple: at each step we advancing scan in that direction,
+which has the nearest point.
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 55c7833..9270a85 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -25,6 +25,9 @@
 #include "commands/vacuum.h"
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
+#include "nodes/primnodes.h"
+#include "nodes/relation.h"
+#include "optimizer/paths.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "storage/condition_variable.h"
@@ -33,6 +36,7 @@
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/index_selfuncs.h"
 #include "utils/memutils.h"
 
@@ -79,6 +83,7 @@ typedef enum
 typedef struct BTParallelScanDescData
 {
 	BlockNumber btps_scanPage;	/* latest or next page to be scanned */
+	BlockNumber btps_knnScanPage;	/* secondary latest or next page to be scanned */
 	BTPS_State	btps_pageStatus;	/* indicates whether next page is
 									 * available for scan. see above for
 									 * possible states of parallel scan. */
@@ -97,6 +102,9 @@ static void btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 static void btvacuumpage(BTVacState *vstate, BlockNumber blkno,
 			 BlockNumber orig_blkno);
 
+static bool btmatchorderby(IndexOptInfo *index, List *pathkeys,
+			   List **orderby_clauses_p, List **clause_columns_p);
+
 
 /*
  * Btree handler function: return IndexAmRoutine with access method parameters
@@ -107,7 +115,7 @@ bthandler(PG_FUNCTION_ARGS)
 {
 	IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
 
-	amroutine->amstrategies = BTMaxStrategyNumber;
+	amroutine->amstrategies = BtreeMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
 	amroutine->amcanbackward = true;
@@ -143,7 +151,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = btestimateparallelscan;
 	amroutine->aminitparallelscan = btinitparallelscan;
 	amroutine->amparallelrescan = btparallelrescan;
-	amroutine->ammatchorderby = NULL;
+	amroutine->ammatchorderby = btmatchorderby;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -215,23 +223,30 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTScanState state = &so->state;
+	ScanDirection arraydir =
+		scan->numberOfOrderBys > 0 ? ForwardScanDirection : dir;
 	bool		res;
 
 	/* btree indexes are never lossy */
 	scan->xs_recheck = false;
+	scan->xs_recheckorderby = false;
+
+	if (so->scanDirection != NoMovementScanDirection)
+		dir = so->scanDirection;
 
 	/*
 	 * If we have any array keys, initialize them during first call for a
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos) &&
+		(!so->knnState || !BTScanPosIsValid(so->knnState->currPos)))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
 			return false;
 
-		_bt_start_array_keys(scan, dir);
+		_bt_start_array_keys(scan, arraydir);
 	}
 
 	/* This loop handles advancing to the next array elements, if any */
@@ -242,7 +257,8 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(state->currPos))
+		if (!BTScanPosIsValid(state->currPos) &&
+			(!so->knnState || !BTScanPosIsValid(so->knnState->currPos)))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -277,7 +293,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		if (res)
 			break;
 		/* ... otherwise see if we have more array keys to deal with */
-	} while (so->numArrayKeys && _bt_advance_array_keys(scan, dir));
+	} while (so->numArrayKeys && _bt_advance_array_keys(scan, arraydir));
 
 	return res;
 }
@@ -350,9 +366,6 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	IndexScanDesc scan;
 	BTScanOpaque so;
 
-	/* no order by operators allowed */
-	Assert(norderbys == 0);
-
 	/* get the scan */
 	scan = RelationGetIndexScan(rel, nkeys, norderbys);
 
@@ -379,6 +392,9 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
 	so->state.currTuples = so->state.markTuples = NULL;
+	so->knnState = NULL;
+	so->distanceTypeByVal = true;
+	so->scanDirection = NoMovementScanDirection;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -408,6 +424,8 @@ _bt_release_current_position(BTScanState state, Relation indexRelation,
 static void
 _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	/* No need to invalidate positions, if the RAM is about to be freed. */
 	_bt_release_current_position(state, scan->indexRelation, !free);
 
@@ -424,6 +442,17 @@ _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 	}
 	else
 		BTScanPosInvalidate(state->markPos);
+
+	if (!so->distanceTypeByVal)
+	{
+		if (DatumGetPointer(state->currDistance))
+			pfree(DatumGetPointer(state->currDistance));
+		state->currDistance = PointerGetDatum(NULL);
+
+		if (DatumGetPointer(state->markDistance))
+			pfree(DatumGetPointer(state->markDistance));
+		state->markDistance = PointerGetDatum(NULL);
+	}
 }
 
 /*
@@ -438,6 +467,13 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 
 	_bt_release_scan_state(scan, state, false);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+		so->knnState = NULL;
+	}
+
 	so->arrayKeyCount = 0; /* FIXME in _bt_release_scan_state */
 
 	/*
@@ -469,6 +505,14 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 				scan->numberOfKeys * sizeof(ScanKeyData));
 	so->numberOfKeys = 0;		/* until _bt_preprocess_keys sets it */
 
+	if (orderbys && scan->numberOfOrderBys > 0)
+		memmove(scan->orderByData,
+				orderbys,
+				scan->numberOfOrderBys * sizeof(ScanKeyData));
+
+	so->scanDirection = NoMovementScanDirection;
+	so->distanceTypeByVal = true;
+
 	/* If any keys are SK_SEARCHARRAY type, set up array-key info */
 	_bt_preprocess_array_keys(scan);
 }
@@ -483,6 +527,12 @@ btendscan(IndexScanDesc scan)
 
 	_bt_release_scan_state(scan, &so->state, true);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+	}
+
 	/* Release storage */
 	if (so->keyData != NULL)
 		pfree(so->keyData);
@@ -494,7 +544,7 @@ btendscan(IndexScanDesc scan)
 }
 
 static void
-_bt_mark_current_position(BTScanState state)
+_bt_mark_current_position(BTScanOpaque so, BTScanState state)
 {
 	/* There may be an old mark with a pin (but no lock). */
 	BTScanPosUnpinIfPinned(state->markPos);
@@ -512,6 +562,21 @@ _bt_mark_current_position(BTScanState state)
 		BTScanPosInvalidate(state->markPos);
 		state->markItemIndex = -1;
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->markDistance));
+
+		state->markIsNull = !BTScanPosIsValid(state->currPos) ||
+			state->currIsNull;
+
+		state->markDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->currDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -522,7 +587,13 @@ btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_mark_current_position(&so->state);
+	_bt_mark_current_position(so, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_mark_current_position(so, so->knnState);
+		so->markRightIsNearest = so->currRightIsNearest;
+	}
 
 	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
@@ -532,6 +603,8 @@ btmarkpos(IndexScanDesc scan)
 static void
 _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	if (state->markItemIndex >= 0)
 	{
 		/*
@@ -567,6 +640,19 @@ _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 					   state->markPos.nextTupleOffset);
 		}
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->currDistance));
+
+		state->currIsNull = state->markIsNull;
+		state->currDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->markDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -588,6 +674,7 @@ btinitparallelscan(void *target)
 
 	SpinLockInit(&bt_target->btps_mutex);
 	bt_target->btps_scanPage = InvalidBlockNumber;
+	bt_target->btps_knnScanPage = InvalidBlockNumber;
 	bt_target->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	bt_target->btps_arrayKeyCount = 0;
 	ConditionVariableInit(&bt_target->btps_cv);
@@ -614,6 +701,7 @@ btparallelrescan(IndexScanDesc scan)
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
 	btscan->btps_scanPage = InvalidBlockNumber;
+	btscan->btps_knnScanPage = InvalidBlockNumber;
 	btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	btscan->btps_arrayKeyCount = 0;
 	SpinLockRelease(&btscan->btps_mutex);
@@ -638,7 +726,7 @@ btparallelrescan(IndexScanDesc scan)
  * Callers should ignore the value of pageno if the return value is false.
  */
 bool
-_bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
+_bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTPS_State	pageStatus;
@@ -646,12 +734,17 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
 	bool		status = true;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
 
 	*pageno = P_NONE;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	scanPage = state == &so->state
+		? &btscan->btps_scanPage
+		: &btscan->btps_knnScanPage;
+
 	while (1)
 	{
 		SpinLockAcquire(&btscan->btps_mutex);
@@ -677,7 +770,7 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
 			 * of advancing it to a new page!
 			 */
 			btscan->btps_pageStatus = BTPARALLEL_ADVANCING;
-			*pageno = btscan->btps_scanPage;
+			*pageno = *scanPage;
 			exit_loop = true;
 		}
 		SpinLockRelease(&btscan->btps_mutex);
@@ -696,19 +789,42 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
  *		can now begin advancing the scan.
  */
 void
-_bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
+_bt_parallel_release(IndexScanDesc scan, BTScanState state,
+					 BlockNumber scan_page)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
+	bool		status_changed = false;
+	bool		knnScan = so->knnState != NULL;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
+	if (!state || state == &so->state)
+	{
+		scanPage = &btscan->btps_scanPage;
+		otherScanPage = &btscan->btps_knnScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_knnScanPage;
+		otherScanPage = &btscan->btps_scanPage;
+	}
 
 	SpinLockAcquire(&btscan->btps_mutex);
-	btscan->btps_scanPage = scan_page;
-	btscan->btps_pageStatus = BTPARALLEL_IDLE;
+	*scanPage = scan_page;
+	/* switch to idle state only if both KNN pages are initialized */
+	if (!knnScan || *otherScanPage != InvalidBlockNumber)
+	{
+		btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		status_changed = true;
+	}
 	SpinLockRelease(&btscan->btps_mutex);
-	ConditionVariableSignal(&btscan->btps_cv);
+
+	if (status_changed)
+		ConditionVariableSignal(&btscan->btps_cv);
 }
 
 /*
@@ -719,12 +835,15 @@ _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
  * advance to the next page.
  */
 void
-_bt_parallel_done(IndexScanDesc scan)
+_bt_parallel_done(IndexScanDesc scan, BTScanState state)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
 	bool		status_changed = false;
+	bool		knnScan = so->knnState != NULL;
 
 	/* Do nothing, for non-parallel scans */
 	if (parallel_scan == NULL)
@@ -733,18 +852,41 @@ _bt_parallel_done(IndexScanDesc scan)
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	if (!state || state == &so->state)
+	{
+		scanPage = &btscan->btps_scanPage;
+		otherScanPage = &btscan->btps_knnScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_knnScanPage;
+		otherScanPage = &btscan->btps_scanPage;
+	}
+
 	/*
 	 * Mark the parallel scan as done for this combination of scan keys,
 	 * unless some other process already did so.  See also
 	 * _bt_advance_array_keys.
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
-	if (so->arrayKeyCount >= btscan->btps_arrayKeyCount &&
-		btscan->btps_pageStatus != BTPARALLEL_DONE)
+
+	Assert(btscan->btps_pageStatus == BTPARALLEL_ADVANCING);
+
+	if (so->arrayKeyCount >= btscan->btps_arrayKeyCount)
 	{
-		btscan->btps_pageStatus = BTPARALLEL_DONE;
+		*scanPage = P_NONE;
 		status_changed = true;
+
+		/* switch to "done" state only if both KNN scans are done */
+		if (!knnScan || *otherScanPage == P_NONE)
+			btscan->btps_pageStatus = BTPARALLEL_DONE;
+		/* else switch to "idle" state only if both KNN scans are initialized */
+		else if (*otherScanPage != InvalidBlockNumber)
+			btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		else
+			status_changed = false;
 	}
+
 	SpinLockRelease(&btscan->btps_mutex);
 
 	/* wake up all the workers associated with this parallel scan */
@@ -774,6 +916,7 @@ _bt_parallel_advance_array_keys(IndexScanDesc scan)
 	if (btscan->btps_pageStatus == BTPARALLEL_DONE)
 	{
 		btscan->btps_scanPage = InvalidBlockNumber;
+		btscan->btps_knnScanPage = InvalidBlockNumber;
 		btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 		btscan->btps_arrayKeyCount++;
 	}
@@ -859,6 +1002,12 @@ btrestrpos(IndexScanDesc scan)
 		_bt_restore_array_keys(scan);
 
 	_bt_restore_marked_position(scan, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_restore_marked_position(scan, so->knnState);
+		so->currRightIsNearest = so->markRightIsNearest;
+	}
 }
 
 /*
@@ -1394,3 +1543,30 @@ btcanreturn(Relation index, int attno)
 {
 	return true;
 }
+
+/*
+ *	btmatchorderby() -- Check whether KNN-search strategy is applicable to
+ *		the given ORDER BY distance operator.
+ */
+static bool
+btmatchorderby(IndexOptInfo *index, List *pathkeys,
+			   List **orderby_clauses_p, List **clause_columns_p)
+{
+	Expr	   *expr;
+	/* ORDER BY distance to the first index column is only supported */
+	int			indexcol = 0;
+
+	if (list_length(pathkeys) != 1)
+		return false; /* only one ORDER BY clause is supported */
+
+	expr = match_orderbyop_pathkey(index, castNode(PathKey, linitial(pathkeys)),
+								   &indexcol);
+
+	if (!expr)
+		return false;
+
+	*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+	*clause_columns_p = lappend_int(*clause_columns_p, 0);
+
+	return true;
+}
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 2b63e0c..6b58dd52 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -32,12 +32,14 @@ static void _bt_saveitem(BTScanState state, int itemIndex,
 static bool _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir);
 static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
 				 BlockNumber blkno, ScanDirection dir);
-static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
-					  ScanDirection dir);
+static bool _bt_parallel_readpage(IndexScanDesc scan, BTScanState state,
+					  BlockNumber blkno, ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf, Snapshot snapshot);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
 static inline void _bt_initialize_more_data(BTScanState state, ScanDirection dir);
+static BTScanState _bt_alloc_knn_scan(IndexScanDesc scan);
+static bool _bt_start_knn_scan(IndexScanDesc scan, bool left, bool right);
 
 
 /*
@@ -598,6 +600,156 @@ _bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 }
 
 /*
+ *  _bt_calc_current_dist() -- Calculate distance from the current item
+ *		of the scan state to the target order-by ScanKey argument.
+ */
+static void
+_bt_calc_current_dist(IndexScanDesc scan, BTScanState state)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanPosItem  *currItem = &state->currPos.items[state->currPos.itemIndex];
+	IndexTuple		itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+	ScanKey			scankey = &scan->orderByData[0];
+	Datum			value;
+
+	value = index_getattr(itup, 1, scan->xs_itupdesc, &state->currIsNull);
+
+	if (state->currIsNull)
+		return; /* NULL distance */
+
+	value = FunctionCall2Coll(&scankey->sk_func,
+							  scankey->sk_collation,
+							  value,
+							  scankey->sk_argument);
+
+	/* free previous distance value for by-ref types */
+	if (!so->distanceTypeByVal && DatumGetPointer(state->currDistance))
+		pfree(DatumGetPointer(state->currDistance));
+
+	state->currDistance = value;
+}
+
+/*
+ *  _bt_compare_current_dist() -- Compare current distances of the left and right scan states.
+ *
+ *  NULL distances are considered to be greater than any non-NULL distances.
+ *
+ *  Returns true if right distance is lesser than left, otherwise false.
+ */
+static bool
+_bt_compare_current_dist(BTScanOpaque so, BTScanState rstate, BTScanState lstate)
+{
+	if (lstate->currIsNull)
+		return true; /* non-NULL < NULL */
+
+	if (rstate->currIsNull)
+		return false; /* NULL > non-NULL */
+
+	return DatumGetBool(FunctionCall2Coll(&so->distanceCmpProc,
+										  InvalidOid, /* XXX collation for distance comparison */
+										  rstate->currDistance,
+										  lstate->currDistance));
+}
+
+/*
+ * _bt_alloc_knn_scan() -- Allocate additional backward scan state for KNN.
+ */
+static BTScanState
+_bt_alloc_knn_scan(IndexScanDesc scan)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		lstate = (BTScanState) palloc(sizeof(BTScanStateData));
+
+	_bt_allocate_tuple_workspaces(lstate);
+
+	if (!scan->xs_want_itup)
+	{
+		/* We need to request index tuples for distance comparison. */
+		scan->xs_want_itup = true;
+		_bt_allocate_tuple_workspaces(&so->state);
+	}
+
+	BTScanPosInvalidate(lstate->currPos);
+	lstate->currPos.moreLeft = false;
+	lstate->currPos.moreRight = false;
+	BTScanPosInvalidate(lstate->markPos);
+	lstate->markItemIndex = -1;
+	lstate->killedItems = NULL;
+	lstate->numKilled = 0;
+	lstate->currDistance = PointerGetDatum(NULL);
+	lstate->markDistance = PointerGetDatum(NULL);
+
+	return so->knnState = lstate;
+}
+
+static bool
+_bt_start_knn_scan(IndexScanDesc scan, bool left, bool right)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		rstate; /* right (forward) main scan state */
+	BTScanState		lstate; /* additional left (backward) KNN scan state */
+
+	if (!left && !right)
+		return false; /* empty result */
+
+	rstate = &so->state;
+	lstate = so->knnState;
+
+	if (left && right)
+	{
+		/*
+		 * We have found items in both scan directions,
+		 * determine nearest item to return.
+		 */
+		_bt_calc_current_dist(scan, rstate);
+		_bt_calc_current_dist(scan, lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+
+		/* Reset right flag if the left item is nearer. */
+		right = so->currRightIsNearest;
+	}
+
+	/* Return current item of the selected scan direction. */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
+ * _bt_init_knn_scan() -- Init additional scan state for KNN search.
+ *
+ * Caller must pin and read-lock scan->state.currPos.buf buffer.
+ *
+ * If empty result was found returned false.
+ * Otherwise prepared current item, and returned true.
+ */
+static bool
+_bt_init_knn_scan(IndexScanDesc scan, OffsetNumber offnum)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		rstate = &so->state; /* right (forward) main scan state */
+	BTScanState		lstate; /* additional left (backward) KNN scan state */
+	Buffer			buf = rstate->currPos.buf;
+	bool			left,
+					right;
+
+	lstate = _bt_alloc_knn_scan(scan);
+
+	/* Bump pin and lock count before BTScanPosData copying. */
+	IncrBufferRefCount(buf);
+	LockBuffer(buf, BT_READ);
+
+	memcpy(&lstate->currPos, &rstate->currPos, sizeof(BTScanPosData));
+	lstate->currPos.moreLeft = true;
+	lstate->currPos.moreRight = false;
+
+	/* Load first pages from the both scans. */
+	right = _bt_load_first_page(scan, rstate, ForwardScanDirection, offnum);
+	left = _bt_load_first_page(scan, lstate, BackwardScanDirection,
+							   OffsetNumberPrev(offnum));
+
+	return _bt_start_knn_scan(scan, left, right);
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -655,6 +807,19 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	if (!so->qual_ok)
 		return false;
 
+	strat_total = BTEqualStrategyNumber;
+
+	if (scan->numberOfOrderBys > 0)
+	{
+		if (_bt_process_orderings(scan, startKeys, &keysCount, notnullkeys))
+			/* use bidirectional KNN scan */
+			strat_total = BtreeKNNSearchStrategyNumber;
+
+		/* use selected KNN scan direction */
+		if (so->scanDirection != NoMovementScanDirection)
+			dir = so->scanDirection;
+	}
+
 	/*
 	 * For parallel scans, get the starting page from shared state. If the
 	 * scan has not started, proceed to find out first leaf page in the usual
@@ -663,19 +828,50 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 */
 	if (scan->parallel_scan != NULL)
 	{
-		status = _bt_parallel_seize(scan, &blkno);
+		status = _bt_parallel_seize(scan, &so->state, &blkno);
 		if (!status)
 			return false;
-		else if (blkno == P_NONE)
-		{
-			_bt_parallel_done(scan);
-			return false;
-		}
 		else if (blkno != InvalidBlockNumber)
 		{
-			if (!_bt_parallel_readpage(scan, blkno, dir))
-				return false;
-			goto readcomplete;
+			bool		knn = strat_total == BtreeKNNSearchStrategyNumber;
+			bool		right;
+			bool		left;
+
+			if (knn)
+				_bt_alloc_knn_scan(scan);
+
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, &so->state);
+				right = false;
+			}
+			else
+				right = _bt_parallel_readpage(scan, &so->state, blkno,
+											  knn ? ForwardScanDirection : dir);
+
+			if (!knn)
+				return right && _bt_return_current_item(scan, &so->state);
+
+			/* seize additional backward KNN scan */
+			left = _bt_parallel_seize(scan, so->knnState, &blkno);
+
+			if (left)
+			{
+				if (blkno == P_NONE)
+				{
+					_bt_parallel_done(scan, so->knnState);
+					left = false;
+				}
+				else
+				{
+					/* backward scan should be already initialized */
+					Assert(blkno != InvalidBlockNumber);
+					left = _bt_parallel_readpage(scan, so->knnState, blkno,
+												 BackwardScanDirection);
+				}
+			}
+
+			return _bt_start_knn_scan(scan, left, right);
 		}
 	}
 
@@ -725,8 +921,10 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 * storing their addresses into the local startKeys[] array.
 	 *----------
 	 */
-	strat_total = BTEqualStrategyNumber;
-	if (so->numberOfKeys > 0)
+
+	if (so->numberOfKeys > 0 &&
+	/* startKeys for KNN search already have been initialized */
+		strat_total != BtreeKNNSearchStrategyNumber)
 	{
 		AttrNumber	curattr;
 		ScanKey		chosen;
@@ -866,7 +1064,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		if (!match)
 		{
 			/* No match, so mark (parallel) scan finished */
-			_bt_parallel_done(scan);
+			_bt_parallel_done(scan, NULL);
 		}
 
 		return match;
@@ -901,7 +1099,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			Assert(subkey->sk_flags & SK_ROW_MEMBER);
 			if (subkey->sk_flags & SK_ISNULL)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, NULL);
 				return false;
 			}
 			memcpy(scankeys + i, subkey, sizeof(ScanKeyData));
@@ -1081,6 +1279,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			break;
 
 		case BTGreaterEqualStrategyNumber:
+		case BtreeKNNSearchStrategyNumber:
 
 			/*
 			 * Find first item >= scankey.  (This is only used for forward
@@ -1128,7 +1327,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 * mark parallel scan as done, so that all the workers can finish
 		 * their scan
 		 */
-		_bt_parallel_done(scan);
+		_bt_parallel_done(scan, NULL);
 		BTScanPosInvalidate(*currPos);
 
 		return false;
@@ -1167,17 +1366,21 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	Assert(!BTScanPosIsValid(*currPos));
 	currPos->buf = buf;
 
+	if (strat_total == BtreeKNNSearchStrategyNumber)
+		return _bt_init_knn_scan(scan, offnum);
+
 	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
-		return false;
+		return false; /* empty result */
 
-readcomplete:
 	/* OK, currPos->itemIndex says what to return */
 	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
- *	Advance to next tuple on current page; or if there's no more,
- *	try to step to the next page with data.
+ *	_bt_next_item() -- Advance to next tuple on current page;
+ *		or if there's no more, try to step to the next page with data.
+ *
+ *	If there are no more matching records in the given direction
  */
 static bool
 _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
@@ -1197,6 +1400,51 @@ _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 }
 
 /*
+ *	_bt_next_nearest() -- Return next nearest item from bidirectional KNN scan.
+ */
+static bool
+_bt_next_nearest(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState rstate = &so->state;
+	BTScanState lstate = so->knnState;
+	bool		right = BTScanPosIsValid(rstate->currPos);
+	bool		left = BTScanPosIsValid(lstate->currPos);
+	bool		advanceRight;
+
+	if (right && left)
+		advanceRight = so->currRightIsNearest;
+	else if (right)
+		advanceRight = true;
+	else if (left)
+		advanceRight = false;
+	else
+		return false; /* end of the scan */
+
+	if (advanceRight)
+		right = _bt_next_item(scan, rstate, ForwardScanDirection);
+	else
+		left = _bt_next_item(scan, lstate, BackwardScanDirection);
+
+	if (!left && !right)
+		return false; /* end of the scan */
+
+	if (left && right)
+	{
+		/*
+		 * If there are items in both scans we must recalculate distance
+		 * in the advanced scan.
+		 */
+		_bt_calc_current_dist(scan, advanceRight ? rstate : lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+		right = so->currRightIsNearest;
+	}
+
+	/* return nearest item */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
  *	_bt_next() -- Get the next item in a scan.
  *
  *		On entry, so->currPos describes the current page, which may be pinned
@@ -1215,6 +1463,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
+	if (so->knnState)
+		/* return next neareset item from KNN scan */
+		return _bt_next_nearest(scan);
+
 	if (!_bt_next_item(scan, &so->state, dir))
 		return false;
 
@@ -1267,9 +1519,9 @@ _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 	if (scan->parallel_scan)
 	{
 		if (ScanDirectionIsForward(dir))
-			_bt_parallel_release(scan, opaque->btpo_next);
+			_bt_parallel_release(scan, state, opaque->btpo_next);
 		else
-			_bt_parallel_release(scan, BufferGetBlockNumber(pos->buf));
+			_bt_parallel_release(scan, state, BufferGetBlockNumber(pos->buf));
 	}
 
 	minoff = P_FIRSTDATAKEY(opaque);
@@ -1443,7 +1695,7 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the next block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno);
+			status = _bt_parallel_seize(scan, state, &blkno);
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
@@ -1475,13 +1727,19 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the current block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno);
+			status = _bt_parallel_seize(scan, state, &blkno);
 			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, state);
+				BTScanPosInvalidate(*currPos);
+				return false;
+			}
 		}
 		else
 		{
@@ -1531,7 +1789,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			 */
 			if (blkno == P_NONE || !currPos->moreRight)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1554,14 +1812,14 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, opaque->btpo_next);
+				_bt_parallel_release(scan, state, opaque->btpo_next);
 			}
 
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno);
+				status = _bt_parallel_seize(scan, state, &blkno);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -1620,7 +1878,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (!currPos->moreLeft)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1631,7 +1889,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			/* if we're physically at end of index, return failure */
 			if (currPos->buf == InvalidBuffer)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1655,7 +1913,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
+				_bt_parallel_release(scan, state, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -1667,7 +1925,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno);
+				status = _bt_parallel_seize(scan, state, &blkno);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -1688,17 +1946,16 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
  * indicate success.
  */
 static bool
-_bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_parallel_readpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+					  ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	_bt_initialize_more_data(state, dir);
 
-	_bt_initialize_more_data(&so->state, dir);
-
-	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
 
 	return true;
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 9bf453c..cd81490 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -20,16 +20,21 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "catalog/pg_amop.h"
 #include "miscadmin.h"
 #include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 
 
 typedef struct BTSortArrayContext
 {
 	FmgrInfo	flinfo;
+	FmgrInfo	distflinfo;
+	FmgrInfo	distcmpflinfo;
+	ScanKey		distkey;
 	Oid			collation;
 	bool		reverse;
 } BTSortArrayContext;
@@ -49,6 +54,9 @@ static void _bt_mark_scankey_required(ScanKey skey);
 static bool _bt_check_rowcompare(ScanKey skey,
 					 IndexTuple tuple, TupleDesc tupdesc,
 					 ScanDirection dir, bool *continuescan);
+static void _bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype,
+						  FmgrInfo *finfo, int16 *typlen, bool *typbyval);
+
 
 
 /*
@@ -445,6 +453,7 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 {
 	Relation	rel = scan->indexRelation;
 	Oid			elemtype;
+	Oid			opfamily;
 	RegProcedure cmp_proc;
 	BTSortArrayContext cxt;
 	int			last_non_dup;
@@ -462,6 +471,53 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 	if (elemtype == InvalidOid)
 		elemtype = rel->rd_opcintype[skey->sk_attno - 1];
 
+	opfamily = rel->rd_opfamily[skey->sk_attno - 1];
+
+	if (scan->numberOfOrderBys <= 0)
+	{
+		cxt.distkey = NULL;
+		cxt.reverse = reverse;
+	}
+	else
+	{
+		/* Init procedures for distance calculation and comparison. */
+		ScanKey		distkey = &scan->orderByData[0];
+		ScanKeyData	distkey2;
+		Oid			disttype = distkey->sk_subtype;
+		Oid			distopr;
+		RegProcedure distproc;
+
+		if (!OidIsValid(disttype))
+			disttype = rel->rd_opcintype[skey->sk_attno - 1];
+
+		/* Lookup distance operator in index column's operator family. */
+		distopr = get_opfamily_member(opfamily,
+									  elemtype,
+									  disttype,
+									  distkey->sk_strategy);
+
+		if (!OidIsValid(distopr))
+			elog(ERROR, "missing operator (%u,%u) for strategy %d in opfamily %u",
+				 elemtype, disttype, BtreeKNNSearchStrategyNumber, opfamily);
+
+		distproc = get_opcode(distopr);
+
+		if (!RegProcedureIsValid(distproc))
+			elog(ERROR, "missing code for operator %u", distopr);
+
+		fmgr_info(distproc, &cxt.distflinfo);
+
+		distkey2 = *distkey;
+		fmgr_info_copy(&distkey2.sk_func, &cxt.distflinfo, CurrentMemoryContext);
+		distkey2.sk_subtype = disttype;
+
+		_bt_get_distance_cmp_proc(&distkey2, opfamily, elemtype,
+								  &cxt.distcmpflinfo, NULL, NULL);
+
+		cxt.distkey = distkey;
+		cxt.reverse = false;	/* supported only ascending ordering */
+	}
+
 	/*
 	 * Look up the appropriate comparison function in the opfamily.
 	 *
@@ -470,19 +526,17 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 	 * non-cross-type support functions for any datatype that it supports at
 	 * all.
 	 */
-	cmp_proc = get_opfamily_proc(rel->rd_opfamily[skey->sk_attno - 1],
+	cmp_proc = get_opfamily_proc(opfamily,
 								 elemtype,
 								 elemtype,
 								 BTORDER_PROC);
 	if (!RegProcedureIsValid(cmp_proc))
 		elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
-			 BTORDER_PROC, elemtype, elemtype,
-			 rel->rd_opfamily[skey->sk_attno - 1]);
+			 BTORDER_PROC, elemtype, elemtype, opfamily);
 
 	/* Sort the array elements */
 	fmgr_info(cmp_proc, &cxt.flinfo);
 	cxt.collation = skey->sk_collation;
-	cxt.reverse = reverse;
 	qsort_arg((void *) elems, nelems, sizeof(Datum),
 			  _bt_compare_array_elements, (void *) &cxt);
 
@@ -514,6 +568,23 @@ _bt_compare_array_elements(const void *a, const void *b, void *arg)
 	BTSortArrayContext *cxt = (BTSortArrayContext *) arg;
 	int32		compare;
 
+	if (cxt->distkey)
+	{
+		Datum		dista = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  da,
+											  cxt->distkey->sk_argument);
+		Datum		distb = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  db,
+											  cxt->distkey->sk_argument);
+		bool		cmp = DatumGetBool(FunctionCall2Coll(&cxt->distcmpflinfo,
+														 cxt->collation,
+														 dista,
+														 distb));
+		return cmp ? -1 : 1;
+	}
+
 	compare = DatumGetInt32(FunctionCall2Coll(&cxt->flinfo,
 											  cxt->collation,
 											  da, db));
@@ -2075,6 +2146,39 @@ btproperty(Oid index_oid, int attno,
 			*res = true;
 			return true;
 
+		case AMPROP_DISTANCE_ORDERABLE:
+			{
+				Oid			opclass,
+							opfamily,
+							opcindtype;
+
+				/* answer only for columns, not AM or whole index */
+				if (attno == 0)
+					return false;
+
+				opclass = get_index_column_opclass(index_oid, attno);
+
+				if (!OidIsValid(opclass))
+				{
+					*res = false;	/* non-key attribute */
+					return true;
+				}
+
+				if (!get_opclass_opfamily_and_input_type(opclass,
+													 &opfamily, &opcindtype))
+				{
+					*isnull = true;
+					return true;
+				}
+
+				*res = SearchSysCacheExists(AMOPSTRATEGY,
+											ObjectIdGetDatum(opfamily),
+											ObjectIdGetDatum(opcindtype),
+											ObjectIdGetDatum(opcindtype),
+								   Int16GetDatum(BtreeKNNSearchStrategyNumber));
+				return true;
+			}
+
 		default:
 			return false;		/* punt to generic code */
 	}
@@ -2223,3 +2327,264 @@ _bt_allocate_tuple_workspaces(BTScanState state)
 	state->currTuples = (char *) palloc(BLCKSZ * 2);
 	state->markTuples = state->currTuples + BLCKSZ;
 }
+
+static bool
+_bt_compare_row_key_with_ordering_key(ScanKey row, ScanKey ord, bool *result)
+{
+	ScanKey		subkey = (ScanKey) DatumGetPointer(row->sk_argument);
+	int32		cmpresult;
+
+	Assert(subkey->sk_attno == 1);
+	Assert(subkey->sk_flags & SK_ROW_MEMBER);
+
+	if (subkey->sk_flags & SK_ISNULL)
+		return false;
+
+	/* Perform the test --- three-way comparison not bool operator */
+	cmpresult = DatumGetInt32(FunctionCall2Coll(&subkey->sk_func,
+												subkey->sk_collation,
+												ord->sk_argument,
+												subkey->sk_argument));
+
+	if (subkey->sk_flags & SK_BT_DESC)
+		cmpresult = -cmpresult;
+
+	/*
+	 * At this point cmpresult indicates the overall result of the row
+	 * comparison, and subkey points to the deciding column (or the last
+	 * column if the result is "=").
+	 */
+	switch (subkey->sk_strategy)
+	{
+			/* EQ and NE cases aren't allowed here */
+		case BTLessStrategyNumber:
+			*result = cmpresult < 0;
+			break;
+		case BTLessEqualStrategyNumber:
+			*result = cmpresult <= 0;
+			break;
+		case BTGreaterEqualStrategyNumber:
+			*result = cmpresult >= 0;
+			break;
+		case BTGreaterStrategyNumber:
+			*result = cmpresult > 0;
+			break;
+		default:
+			elog(ERROR, "unrecognized RowCompareType: %d",
+				 (int) subkey->sk_strategy);
+			*result = false;	/* keep compiler quiet */
+	}
+
+	return true;
+}
+
+/* _bt_select_knn_search_strategy() -- Determine which KNN scan strategy to use:
+ *		bidirectional or unidirectional. We are checking here if the
+ *		ordering scankey argument falls into the scan range: if it falls
+ *		we must use bidirectional scan, otherwise we use unidirectional.
+ *
+ *	Returns BtreeKNNSearchStrategyNumber for bidirectional scan or
+ *			strategy number of non-matched scankey for unidirectional.
+ */
+static StrategyNumber
+_bt_select_knn_search_strategy(IndexScanDesc scan, ScanKey ord)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	ScanKey		cond;
+
+	for (cond = so->keyData; cond < so->keyData + so->numberOfKeys; cond++)
+	{
+		bool		result;
+
+		if (cond->sk_attno != 1)
+			break; /* only interesting in the first index attribute */
+
+		if (cond->sk_strategy == BTEqualStrategyNumber)
+			/* always use simple unidirectional scan for equals operators */
+			return BTEqualStrategyNumber;
+
+		if (cond->sk_flags & SK_ROW_HEADER)
+		{
+			if (!_bt_compare_row_key_with_ordering_key(cond, ord, &result))
+				return BTEqualStrategyNumber; /* ROW(fist_index_attr, ...) IS NULL */
+		}
+		else
+		{
+			if (!_bt_compare_scankey_args(scan, cond, ord, cond, &result))
+				elog(ERROR, "could not compare ordering key");
+		}
+
+		if (!result)
+			/*
+			 * Ordering scankey argument is out of scan range,
+			 * use unidirectional scan.
+			 */
+			return cond->sk_strategy;
+	}
+
+	return BtreeKNNSearchStrategyNumber; /* use bidirectional scan */
+}
+
+static Oid
+_bt_get_sortfamily_for_opfamily_op(Oid opfamily, Oid lefttype, Oid righttype,
+								   StrategyNumber strategy)
+{
+	HeapTuple	tp;
+	Form_pg_amop amop_tup;
+	Oid			sortfamily;
+
+	tp = SearchSysCache4(AMOPSTRATEGY,
+						 ObjectIdGetDatum(opfamily),
+						 ObjectIdGetDatum(lefttype),
+						 ObjectIdGetDatum(righttype),
+						 Int16GetDatum(strategy));
+	if (!HeapTupleIsValid(tp))
+		return InvalidOid;
+	amop_tup = (Form_pg_amop) GETSTRUCT(tp);
+	sortfamily = amop_tup->amopsortfamily;
+	ReleaseSysCache(tp);
+
+	return sortfamily;
+}
+
+/*
+ * _bt_get_distance_cmp_proc() -- Init procedure for comparsion of distances
+ *		between "leftargtype" and "distkey".
+ */
+static void
+_bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype,
+						  FmgrInfo *finfo, int16 *typlen, bool *typbyval)
+{
+	RegProcedure opcode;
+	Oid			sortfamily;
+	Oid			opno;
+	Oid			distanceType;
+
+	distanceType = get_func_rettype(distkey->sk_func.fn_oid);
+
+	sortfamily = _bt_get_sortfamily_for_opfamily_op(opfamily, leftargtype,
+													distkey->sk_subtype,
+													distkey->sk_strategy);
+
+	if (!OidIsValid(sortfamily))
+		elog(ERROR, "could not find sort family for btree ordering operator");
+
+	opno = get_opfamily_member(sortfamily,
+							   distanceType,
+							   distanceType,
+							   BTLessEqualStrategyNumber);
+
+	if (!OidIsValid(opno))
+		elog(ERROR, "could not find operator for btree distance comparison");
+
+	opcode = get_opcode(opno);
+
+	if (!RegProcedureIsValid(opcode))
+		elog(ERROR,
+			"could not find procedure for btree distance comparison operator");
+
+	fmgr_info(opcode, finfo);
+
+	if (typlen)
+		get_typlenbyval(distanceType, typlen, typbyval);
+}
+
+/*
+ *  _bt_init_distance_comparison() -- Init distance typlen/typbyval and its
+ *  	comparison procedure.
+ */
+static void
+_bt_init_distance_comparison(IndexScanDesc scan, ScanKey ord)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	Relation	rel = scan->indexRelation;
+
+	_bt_get_distance_cmp_proc(ord,
+							  rel->rd_opfamily[ord->sk_attno - 1],
+							  rel->rd_opcintype[ord->sk_attno - 1],
+							  &so->distanceCmpProc,
+							  &so->distanceTypeLen,
+							  &so->distanceTypeByVal);
+
+	if (!so->distanceTypeByVal)
+	{
+		so->state.currDistance = PointerGetDatum(NULL);
+		so->state.markDistance = PointerGetDatum(NULL);
+	}
+}
+
+/*
+ * _bt_process_orderings() -- Process ORDER BY distance scankeys and
+ *		select corresponding KNN strategy.
+ *
+ * If bidirectional scan is selected then one scankey is initialized
+ * using bufKeys and placed into startKeys/keysCount, true is returned.
+ *
+ * Otherwise, so->scanDirection is set and false is returned.
+ */
+bool
+_bt_process_orderings(IndexScanDesc scan, ScanKey *startKeys, int *keysCount,
+					  ScanKeyData bufKeys[])
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	ScanKey		ord = scan->orderByData;
+
+	if (scan->numberOfOrderBys > 1 || ord->sk_attno != 1)
+		/* it should not happen, see btmatchorderby() */
+		elog(ERROR, "only one btree ordering operator "
+					"for the first index column is supported");
+
+	Assert(ord->sk_strategy == BtreeKNNSearchStrategyNumber);
+
+	switch (_bt_select_knn_search_strategy(scan, ord))
+	{
+		case BTLessStrategyNumber:
+		case BTLessEqualStrategyNumber:
+			/*
+			 * Ordering key argument is greater than all values in scan range.
+			 * select backward scan direction.
+			 */
+			so->scanDirection = BackwardScanDirection;
+			return false;
+
+		case BTEqualStrategyNumber:
+			/* Use default unidirectional scan direction. */
+			return false;
+
+		case BTGreaterEqualStrategyNumber:
+		case BTGreaterStrategyNumber:
+			/*
+			 * Ordering key argument is lesser than all values in scan range.
+			 * select forward scan direction.
+			 */
+			so->scanDirection = ForwardScanDirection;
+			return false;
+
+		case BtreeKNNSearchStrategyNumber:
+			/*
+			 * Ordering key argument falls into scan range,
+			 * use bidirectional scan.
+			 */
+			break;
+	}
+
+	_bt_init_distance_comparison(scan, ord);
+
+	/* Init btree search key with ordering key argument. */
+	ScanKeyEntryInitialize(&bufKeys[0],
+					 (scan->indexRelation->rd_indoption[ord->sk_attno - 1] <<
+					  SK_BT_INDOPTION_SHIFT) |
+						   SK_ORDER_BY |
+						   SK_SEARCHNULL /* only for invalid procedure oid, see
+										  * assert in ScanKeyEntryInitialize */,
+						   ord->sk_attno,
+						   BtreeKNNSearchStrategyNumber,
+						   ord->sk_subtype,
+						   ord->sk_collation,
+						   InvalidOid,
+						   ord->sk_argument);
+
+	startKeys[(*keysCount)++] = &bufKeys[0];
+
+	return true;
+}
diff --git a/src/backend/access/nbtree/nbtvalidate.c b/src/backend/access/nbtree/nbtvalidate.c
index f24091c..be4e843 100644
--- a/src/backend/access/nbtree/nbtvalidate.c
+++ b/src/backend/access/nbtree/nbtvalidate.c
@@ -22,9 +22,17 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_type.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/regproc.h"
 #include "utils/syscache.h"
 
+#define BTRequiredOperatorSet \
+		((1 << BTLessStrategyNumber) | \
+		 (1 << BTLessEqualStrategyNumber) | \
+		 (1 << BTEqualStrategyNumber) | \
+		 (1 << BTGreaterEqualStrategyNumber) | \
+		 (1 << BTGreaterStrategyNumber))
+
 
 /*
  * Validator for a btree opclass.
@@ -132,10 +140,11 @@ btvalidate(Oid opclassoid)
 	{
 		HeapTuple	oprtup = &oprlist->members[i]->tuple;
 		Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
+		Oid			op_rettype;
 
 		/* Check that only allowed strategy numbers exist */
 		if (oprform->amopstrategy < 1 ||
-			oprform->amopstrategy > BTMaxStrategyNumber)
+			oprform->amopstrategy > BtreeMaxStrategyNumber)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -146,20 +155,29 @@ btvalidate(Oid opclassoid)
 			result = false;
 		}
 
-		/* btree doesn't support ORDER BY operators */
-		if (oprform->amoppurpose != AMOP_SEARCH ||
-			OidIsValid(oprform->amopsortfamily))
+		/* btree supports ORDER BY operators */
+		if (oprform->amoppurpose != AMOP_SEARCH)
 		{
-			ereport(INFO,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
-							opfamilyname, "btree",
-							format_operator(oprform->amopopr))));
-			result = false;
+			/* ... and operator result must match the claimed btree opfamily */
+			op_rettype = get_op_rettype(oprform->amopopr);
+			if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype))
+			{
+				ereport(INFO,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
+								opfamilyname, "btree",
+								format_operator(oprform->amopopr))));
+				result = false;
+			}
+		}
+		else
+		{
+			/* Search operators must always return bool */
+			op_rettype = BOOLOID;
 		}
 
 		/* Check operator signature --- same for all btree strategies */
-		if (!check_amop_signature(oprform->amopopr, BOOLOID,
+		if (!check_amop_signature(oprform->amopopr, op_rettype,
 								  oprform->amoplefttype,
 								  oprform->amoprighttype))
 		{
@@ -214,12 +232,8 @@ btvalidate(Oid opclassoid)
 		 * or support functions for this datatype pair.  The only things
 		 * considered optional are the sortsupport and in_range functions.
 		 */
-		if (thisgroup->operatorset !=
-			((1 << BTLessStrategyNumber) |
-			 (1 << BTLessEqualStrategyNumber) |
-			 (1 << BTEqualStrategyNumber) |
-			 (1 << BTGreaterEqualStrategyNumber) |
-			 (1 << BTGreaterStrategyNumber)))
+		if ((thisgroup->operatorset & BTRequiredOperatorSet) !=
+			BTRequiredOperatorSet)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index e043664..7c9b97b 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -988,6 +988,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	 * if we are only trying to build bitmap indexscans, nor if we have to
 	 * assume the scan is unordered.
 	 */
+	useful_pathkeys = NIL;
+	orderbyclauses = NIL;
+	orderbyclausecols = NIL;
+
 	pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN &&
 								!found_lower_saop_clause &&
 								has_useful_pathkeys(root, rel));
@@ -998,10 +1002,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 											  ForwardScanDirection);
 		useful_pathkeys = truncate_useless_pathkeys(root, rel,
 													index_pathkeys);
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
 	}
-	else if (index->ammatchorderby && pathkeys_possibly_useful)
+
+	if (useful_pathkeys == NIL &&
+		index->ammatchorderby && pathkeys_possibly_useful)
 	{
 		/* see if we can generate ordering operators for query_pathkeys */
 		match_pathkeys_to_index(index, root->query_pathkeys,
@@ -1012,12 +1016,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		else
 			useful_pathkeys = NIL;
 	}
-	else
-	{
-		useful_pathkeys = NIL;
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
-	}
 
 	/*
 	 * 3. Check if an index-only scan is possible.  If we're not building
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 388b311..9c8a384 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -460,6 +460,12 @@ typedef struct BTScanStateData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+
+	/* KNN-search fields: */
+	Datum		currDistance;	/* current distance */
+	Datum		markDistance;	/* marked distance */
+	bool		currIsNull;		/* current item is NULL */
+	bool		markIsNull;		/* marked item is NULL */
 }	BTScanStateData, *BTScanState;
 
 typedef struct BTScanOpaqueData
@@ -478,7 +484,17 @@ typedef struct BTScanOpaqueData
 	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
 	MemoryContext arrayContext; /* scan-lifespan context for array data */
 
-	BTScanStateData	state;
+	BTScanStateData state;		/* main scan state */
+
+	/* KNN-search fields: */
+	BTScanState knnState;			/* optional scan state for KNN search */
+	ScanDirection scanDirection;	/* selected scan direction for
+									 * unidirectional KNN scan */
+	FmgrInfo	distanceCmpProc;	/* distance comparison procedure */
+	int16		distanceTypeLen;	/* distance typlen */
+	bool		distanceTypeByVal;	/* distance typebyval */
+	bool		currRightIsNearest;	/* current right item is nearest */
+	bool		markRightIsNearest;	/* marked right item is nearest */
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -524,11 +540,12 @@ extern bool btcanreturn(Relation index, int attno);
 /*
  * prototypes for internal functions in nbtree.c
  */
-extern bool _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno);
-extern void _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page);
-extern void _bt_parallel_done(IndexScanDesc scan);
+extern bool _bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno);
+extern void _bt_parallel_release(IndexScanDesc scan, BTScanState state, BlockNumber scan_page);
+extern void _bt_parallel_done(IndexScanDesc scan, BTScanState state);
 extern void _bt_parallel_advance_array_keys(IndexScanDesc scan);
 
+
 /*
  * prototypes for functions in nbtinsert.c
  */
@@ -609,6 +626,8 @@ extern bool btproperty(Oid index_oid, int attno,
 extern IndexTuple _bt_nonkey_truncate(Relation rel, IndexTuple itup);
 extern bool _bt_check_natts(Relation rel, Page page, OffsetNumber offnum);
 extern void _bt_allocate_tuple_workspaces(BTScanState state);
+extern bool _bt_process_orderings(IndexScanDesc scan,
+				  ScanKey *startKeys, int *keysCount, ScanKeyData bufKeys[]);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/include/access/stratnum.h b/src/include/access/stratnum.h
index 0db11a1..f44b41a 100644
--- a/src/include/access/stratnum.h
+++ b/src/include/access/stratnum.h
@@ -32,7 +32,10 @@ typedef uint16 StrategyNumber;
 #define BTGreaterEqualStrategyNumber	4
 #define BTGreaterStrategyNumber			5
 
-#define BTMaxStrategyNumber				5
+#define BTMaxStrategyNumber				5		/* number of canonical B-tree strategies */
+
+#define BtreeKNNSearchStrategyNumber	6		/* for <-> (distance) */
+#define BtreeMaxStrategyNumber			6		/* number of extended B-tree strategies */
 
 
 /*
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index 6faa9d7..c75ef39 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -347,10 +347,10 @@ ROLLBACK;
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
 ERROR:  access method "invalid_index_method" does not exist
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 6, must be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 0, must be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 7, must be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 0, must be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ERROR:  operator argument types must be specified in ALTER OPERATOR FAMILY
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -397,11 +397,12 @@ DROP OPERATOR FAMILY alt_opf8 USING btree;
 CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
-ERROR:  access method "btree" does not support ordering operators
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
 ALTER OPERATOR FAMILY alt_opf11 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql
index 84fd900..73e6e206 100644
--- a/src/test/regress/sql/alter_generic.sql
+++ b/src/test/regress/sql/alter_generic.sql
@@ -295,8 +295,8 @@ ROLLBACK;
 -- Should fail. Invalid values for ALTER OPERATOR FAMILY .. ADD / DROP
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 6 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -340,10 +340,12 @@ CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
 
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
0005-Add-btree-distance-operators-v03.patchtext/x-patch; name=0005-Add-btree-distance-operators-v03.patchDownload
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 9015557..a72f45a 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -183,6 +183,12 @@ SELECT * FROM events ORDER BY event_date <-> date '2017-05-05' LIMIT 10;
 </programlisting>
    which finds the ten events closest to a given target date.  The ability
    to do this is again dependent on the particular operator class being used.
+   Built-in B-tree operator classes support distance ordering for data types
+   <type>int2</>, <type>int4</>, <type>int8</>,
+   <type>float4</>, <type>float8</>, <type>numeric</>,
+   <type>timestamp with time zone</>, <type>timestamp without time zone</>,
+   <type>time with time zone</>, <type>time without time zone</>,
+   <type>date</>, <type>interval</>, <type>oid</>, <type>money</>.
   </para>
 
   <para>
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index c787dd3..3c07ced 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -30,6 +30,7 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+#define SAMESIGN(a,b)	(((a) < 0) == ((b) < 0))
 
 /*************************************************************************
  * Private routines
@@ -1157,3 +1158,22 @@ int8_cash(PG_FUNCTION_ARGS)
 
 	PG_RETURN_CASH(result);
 }
+
+Datum
+cash_dist(PG_FUNCTION_ARGS)
+{
+	Cash		a = PG_GETARG_CASH(0);
+	Cash		b = PG_GETARG_CASH(1);
+	Cash		r;
+	Cash		ra;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("money out of range")));
+
+	ra = Abs(r);
+
+	PG_RETURN_CASH(ra);
+}
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 87146a2..789d277 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -563,6 +563,17 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+Datum
+date_dist(PG_FUNCTION_ARGS)
+{
+	/* we assume the difference can't overflow */
+	Datum		diff = DirectFunctionCall2(date_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
+}
+
 /*
  * Internal routines for promoting date to timestamp and timestamp with
  * time zone
@@ -760,6 +771,29 @@ date_cmp_timestamp(PG_FUNCTION_ARGS)
 }
 
 Datum
+date_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+	Timestamp	dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
+Datum
 date_eq_timestamptz(PG_FUNCTION_ARGS)
 {
 	DateADT		dateVal = PG_GETARG_DATEADT(0);
@@ -844,6 +878,30 @@ date_cmp_timestamptz(PG_FUNCTION_ARGS)
 }
 
 Datum
+date_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	TimestampTz	dt2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz	dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
+
+
+Datum
 timestamp_eq_date(PG_FUNCTION_ARGS)
 {
 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
@@ -928,6 +986,29 @@ timestamp_cmp_date(PG_FUNCTION_ARGS)
 }
 
 Datum
+timestamp_dist_date(PG_FUNCTION_ARGS)
+{
+	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	Timestamp	dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
+Datum
 timestamptz_eq_date(PG_FUNCTION_ARGS)
 {
 	TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
@@ -1039,6 +1120,28 @@ in_range_date_interval(PG_FUNCTION_ARGS)
 							   BoolGetDatum(less));
 }
 
+Datum
+timestamptz_dist_date(PG_FUNCTION_ARGS)
+{
+	TimestampTz	dt1 = PG_GETARG_TIMESTAMPTZ(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	TimestampTz	dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
 
 /* Add an interval to a date, giving a new date.
  * Must handle both positive and negative intervals.
@@ -1996,6 +2099,16 @@ time_part(PG_FUNCTION_ARGS)
 	PG_RETURN_FLOAT8(result);
 }
 
+Datum
+time_dist(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(time_mi_time,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
+
 
 /*****************************************************************************
  *	 Time With Time Zone ADT
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index df35557..24078b3 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -3416,6 +3416,47 @@ width_bucket_float8(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(result);
 }
 
+Datum
+float4_dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float4		r = float4_mi(a, b);
+
+	PG_RETURN_FLOAT4(Abs(r));
+}
+
+Datum
+float8_dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
+
+Datum
+float48_dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
+Datum
+float84_dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
 /* ========== PRIVATE ROUTINES ========== */
 
 #ifndef HAVE_CBRT
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 8149dc1..9335746 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -1427,3 +1427,55 @@ generate_series_step_int4(PG_FUNCTION_ARGS)
 		/* do when there is no more left */
 		SRF_RETURN_DONE(funcctx);
 }
+
+Datum
+int2_dist(PG_FUNCTION_ARGS)
+{
+	int16		a = PG_GETARG_INT16(0);
+	int16		b = PG_GETARG_INT16(1);
+	int16		r;
+	int16		ra;
+
+	if (pg_sub_s16_overflow(a, b, &r) ||
+		r == PG_INT16_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("smallint out of range")));
+
+	ra = Abs(r);
+
+	PG_RETURN_INT16(ra);
+}
+
+static int32
+int44_dist(int32 a, int32 b)
+{
+	int32		r;
+
+	if (pg_sub_s32_overflow(a, b, &r) ||
+		r == PG_INT32_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("integer out of range")));
+
+	return Abs(r);
+}
+
+
+Datum
+int4_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int24_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT16(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int42_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT16(1)));
+}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 3c595e8..a114319 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1357,3 +1357,47 @@ generate_series_step_int8(PG_FUNCTION_ARGS)
 		/* do when there is no more left */
 		SRF_RETURN_DONE(funcctx);
 }
+
+static int64
+int88_dist(int64 a, int64 b)
+{
+	int64		r;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("bigint out of range")));
+
+	return Abs(r);
+}
+
+Datum
+int8_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int82_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT16(1)));
+}
+
+Datum
+int84_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT32(1)));
+}
+
+Datum
+int28_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT16(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int48_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT32(0), PG_GETARG_INT64(1)));
+}
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index b0670e0..536507b 100644
--- a/src/backend/utils/adt/oid.c
+++ b/src/backend/utils/adt/oid.c
@@ -469,3 +469,17 @@ oidvectorgt(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(cmp > 0);
 }
+
+Datum
+oid_dist(PG_FUNCTION_ARGS)
+{
+	Oid			a = PG_GETARG_OID(0);
+	Oid			b = PG_GETARG_OID(1);
+	Oid			res;
+
+	if (a < b)
+		res = b - a;
+	else
+		res = a - b;
+	PG_RETURN_OID(res);
+}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 284e14d..e9a4a0c 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -2649,6 +2649,86 @@ timestamp_mi(PG_FUNCTION_ARGS)
 	PG_RETURN_INTERVAL_P(result);
 }
 
+Datum
+timestamp_dist(PG_FUNCTION_ARGS)
+{
+	Timestamp	a = PG_GETARG_TIMESTAMP(0);
+	Timestamp	b = PG_GETARG_TIMESTAMP(1);
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		Interval   *p = palloc(sizeof(Interval));
+
+		p->day = INT_MAX;
+		p->month = INT_MAX;
+		p->time = PG_INT64_MAX;
+		PG_RETURN_INTERVAL_P(p);
+	}
+	else
+		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+												  PG_GETARG_DATUM(0),
+												  PG_GETARG_DATUM(1)));
+	PG_RETURN_INTERVAL_P(abs_interval(r));
+}
+
+Interval *
+timestamp_dist_internal(Timestamp a, Timestamp b)
+{
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		return r;
+	}
+
+	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+											  TimestampGetDatum(a),
+											  TimestampGetDatum(b)));
+
+	return abs_interval(r);
+}
+
+Datum
+timestamptz_dist(PG_FUNCTION_ARGS)
+{
+	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
+	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(a, b));
+}
+
+Datum
+timestamp_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	Timestamp	ts1 = PG_GETARG_TIMESTAMP(0);
+	TimestampTz	tstz2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz	tstz1;
+
+	tstz1 = timestamp2timestamptz(ts1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+Datum
+timestamptz_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	TimestampTz	tstz1 = PG_GETARG_TIMESTAMPTZ(0);
+	Timestamp	ts2 = PG_GETARG_TIMESTAMP(1);
+	TimestampTz	tstz2;
+
+	tstz2 = timestamp2timestamptz(ts2);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+
 /*
  *	interval_justify_interval()
  *
@@ -3518,6 +3598,29 @@ interval_avg(PG_FUNCTION_ARGS)
 							   Float8GetDatum((double) N.time));
 }
 
+Interval *
+abs_interval(Interval *a)
+{
+	static Interval zero = {0, 0, 0};
+
+	if (DatumGetBool(DirectFunctionCall2(interval_lt,
+										 IntervalPGetDatum(a),
+										 IntervalPGetDatum(&zero))))
+		a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
+												  IntervalPGetDatum(a)));
+
+	return a;
+}
+
+Datum
+interval_dist(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(interval_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
 
 /* timestamp_age()
  * Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index a1e57d7..3fe49a6 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -30,6 +30,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int2,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int24
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -47,6 +51,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int2,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int28
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -64,6 +72,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int2,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int4
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -81,6 +93,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int4,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int42
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -98,6 +114,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int4,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int48
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -115,6 +135,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int4,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int8
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -132,6 +156,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int8,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int82
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -149,6 +177,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int8,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int84
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -166,6 +198,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int8,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # btree oid_ops
 
@@ -179,6 +215,10 @@
   amopstrategy => '4', amopopr => '>=(oid,oid)', amopmethod => 'btree' },
 { amopfamily => 'btree/oid_ops', amoplefttype => 'oid', amoprighttype => 'oid',
   amopstrategy => '5', amopopr => '>(oid,oid)', amopmethod => 'btree' },
+{ amopfamily => 'btree/oid_ops', amoplefttype => 'oid',
+  amoprighttype => 'oid', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(oid,oid)', amopmethod => 'btree',
+  amopsortfamily => 'btree/oid_ops' },
 
 # btree tid_ops
 
@@ -229,6 +269,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float4,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float48
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
@@ -246,6 +290,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float4,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # default operators float8
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -263,6 +311,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float8,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float84
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -280,6 +332,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float8,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # btree char_ops
 
@@ -407,6 +463,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(date,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -424,6 +484,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(date,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -441,6 +505,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(date,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -458,6 +526,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamp,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -475,6 +547,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(timestamp,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -492,6 +568,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamp,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -509,6 +589,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -526,6 +610,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'date', amopstrategy => '5',
   amopopr => '>(timestamptz,date)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -543,6 +631,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree time_ops
 
@@ -561,6 +653,10 @@
 { amopfamily => 'btree/time_ops', amoplefttype => 'time',
   amoprighttype => 'time', amopstrategy => '5', amopopr => '>(time,time)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/time_ops', amoplefttype => 'time',
+  amoprighttype => 'time', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(time,time)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree timetz_ops
 
@@ -597,6 +693,10 @@
 { amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
   amoprighttype => 'interval', amopstrategy => '5',
   amopopr => '>(interval,interval)', amopmethod => 'btree' },
+{ amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
+  amoprighttype => 'interval', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(interval,interval)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree macaddr
 
@@ -772,6 +872,10 @@
 { amopfamily => 'btree/money_ops', amoplefttype => 'money',
   amoprighttype => 'money', amopstrategy => '5', amopopr => '>(money,money)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/money_ops', amoplefttype => 'money',
+  amoprighttype => 'money', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(money,money)', amopmethod => 'btree',
+  amopsortfamily => 'btree/money_ops' },
 
 # btree reltime_ops
 
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index d9b6bad..6be94f3 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2932,6 +2932,114 @@
   oprname => '-', oprleft => 'pg_lsn', oprright => 'pg_lsn',
   oprresult => 'numeric', oprcode => 'pg_lsn_mi' },
 
+# distance operators
+{ oid => '4217', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int2', oprresult => 'int2', oprcom => '<->(int2,int2)',
+  oprcode => 'int2_dist'},
+{ oid => '4218', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int4)',
+  oprcode => 'int4_dist'},
+{ oid => '4219', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int8)',
+  oprcode => 'int8_dist'},
+{ oid => '4220', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'oid',
+  oprright => 'oid', oprresult => 'oid', oprcom => '<->(oid,oid)',
+  oprcode => 'oid_dist'},
+{ oid => '4221', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float4', oprresult => 'float4', oprcom => '<->(float4,float4)',
+  oprcode => 'float4_dist'},
+{ oid => '4222', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float8)',
+  oprcode => 'float8_dist'},
+{ oid => '4223', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'money',
+  oprright => 'money', oprresult => 'money', oprcom => '<->(money,money)',
+  oprcode => 'cash_dist'},
+{ oid => '4224', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'date', oprresult => 'int4', oprcom => '<->(date,date)',
+  oprcode => 'date_dist'},
+{ oid => '4225', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'time',
+  oprright => 'time', oprresult => 'interval', oprcom => '<->(time,time)',
+  oprcode => 'time_dist'},
+{ oid => '4226', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamp)',
+  oprcode => 'timestamp_dist'},
+{ oid => '4227', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamptz)',
+  oprcode => 'timestamptz_dist'},
+{ oid => '4228', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'interval',
+  oprright => 'interval', oprresult => 'interval', oprcom => '<->(interval,interval)',
+  oprcode => 'interval_dist'},
+
+# cross-type distance operators
+{ oid => '4229', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int2)',
+  oprcode => 'int24_dist'},
+{ oid => '4230', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int2', oprresult => 'int4', oprcom => '<->(int2,int4)',
+  oprcode => 'int42_dist'},
+{ oid => '4231', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int2)',
+  oprcode => 'int28_dist'},
+{ oid => '4232', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int2', oprresult => 'int8', oprcom => '<->(int2,int8)',
+  oprcode => 'int82_dist'},
+{ oid => '4233', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int4)',
+  oprcode => 'int48_dist'},
+{ oid => '4234', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int4', oprresult => 'int8', oprcom => '<->(int4,int8)',
+  oprcode => 'int84_dist'},
+{ oid => '4235', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float4)',
+  oprcode => 'float48_dist'},
+{ oid => '4236', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float4', oprresult => 'float8', oprcom => '<->(float4,float8)',
+  oprcode => 'float84_dist'},
+{ oid => '4237', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,date)',
+  oprcode => 'date_dist_timestamp'},
+{ oid => '4238', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamp)',
+  oprcode => 'timestamp_dist_date'},
+{ oid => '4239', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,date)',
+  oprcode => 'date_dist_timestamptz'},
+{ oid => '4240', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamptz)',
+  oprcode => 'timestamptz_dist_date'},
+{ oid => '4241', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamp)',
+  oprcode => 'timestamp_dist_timestamptz'},
+{ oid => '4242', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamptz)',
+  oprcode => 'timestamptz_dist_timestamp'},
+
 # enum operators
 { oid => '3516', descr => 'equal',
   oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'anyenum',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 8e4145f..ed3a50f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10206,4 +10206,86 @@
   proisstrict => 'f', prorettype => 'bool', proargtypes => 'oid int4 int4 any',
   proargmodes => '{i,i,i,v}', prosrc => 'satisfies_hash_partition' },
 
+# distance functions
+{ oid => '4243',
+  proname => 'int2_dist', prorettype => 'int2',
+  proargtypes => 'int2 int2', prosrc => 'int2_dist' },
+{ oid => '4244',
+  proname => 'int4_dist', prorettype => 'int4',
+  proargtypes => 'int4 int4', prosrc => 'int4_dist' },
+{ oid => '4245',
+  proname => 'int8_dist', prorettype => 'int8',
+  proargtypes => 'int8 int8', prosrc => 'int8_dist' },
+{ oid => '4246',
+  proname => 'oid_dist', prorettype => 'oid',
+  proargtypes => 'oid oid', prosrc => 'oid_dist' },
+{ oid => '4247',
+  proname => 'float4_dist', prorettype => 'float4',
+  proargtypes => 'float4 float4', prosrc => 'float4_dist' },
+{ oid => '4248',
+  proname => 'float8_dist', prorettype => 'float8',
+  proargtypes => 'float8 float8', prosrc => 'float8_dist' },
+{ oid => '4249',
+  proname => 'cash_dist', prorettype => 'money',
+  proargtypes => 'money money', prosrc => 'cash_dist' },
+{ oid => '4250',
+  proname => 'date_dist', prorettype => 'int4',
+  proargtypes => 'date date', prosrc => 'date_dist' },
+{ oid => '4251',
+  proname => 'time_dist', prorettype => 'interval',
+  proargtypes => 'time time', prosrc => 'time_dist' },
+{ oid => '4252',
+  proname => 'timestamp_dist', prorettype => 'interval',
+  proargtypes => 'timestamp timestamp', prosrc => 'timestamp_dist' },
+{ oid => '4253',
+  proname => 'timestamptz_dist', prorettype => 'interval',
+  proargtypes => 'timestamptz timestamptz', prosrc => 'timestamptz_dist' },
+{ oid => '4254',
+  proname => 'interval_dist', prorettype => 'interval',
+  proargtypes => 'interval interval', prosrc => 'interval_dist' },
+
+# cross-type distance functions
+{ oid => '4255',
+  proname => 'int24_dist', prorettype => 'int4',
+  proargtypes => 'int2 int4', prosrc => 'int24_dist' },
+{ oid => '4256',
+  proname => 'int28_dist', prorettype => 'int8',
+  proargtypes => 'int2 int8', prosrc => 'int28_dist' },
+{ oid => '4257',
+  proname => 'int42_dist', prorettype => 'int4',
+  proargtypes => 'int4 int2', prosrc => 'int42_dist' },
+{ oid => '4258',
+  proname => 'int48_dist', prorettype => 'int8',
+  proargtypes => 'int4 int8', prosrc => 'int48_dist' },
+{ oid => '4259',
+  proname => 'int82_dist', prorettype => 'int8',
+  proargtypes => 'int8 int2', prosrc => 'int82_dist' },
+{ oid => '4260',
+  proname => 'int84_dist', prorettype => 'int8',
+  proargtypes => 'int8 int4', prosrc => 'int84_dist' },
+{ oid => '4261',
+  proname => 'float48_dist', prorettype => 'float8',
+  proargtypes => 'float4 float8', prosrc => 'float48_dist' },
+{ oid => '4262',
+  proname => 'float84_dist', prorettype => 'float8',
+  proargtypes => 'float8 float4', prosrc => 'float84_dist' },
+{ oid => '4263',
+  proname => 'date_dist_timestamp', prorettype => 'interval',
+  proargtypes => 'date timestamp', prosrc => 'date_dist_timestamp' },
+{ oid => '4264',
+  proname => 'date_dist_timestamptz', prorettype => 'interval',
+  proargtypes => 'date timestamptz', prosrc => 'date_dist_timestamptz' },
+{ oid => '4265',
+  proname => 'timestamp_dist_date', prorettype => 'interval',
+  proargtypes => 'timestamp date', prosrc => 'timestamp_dist_date' },
+{ oid => '4266',
+  proname => 'timestamp_dist_timestamptz', prorettype => 'interval',
+  proargtypes => 'timestamp timestamptz', prosrc => 'timestamp_dist_timestamptz' },
+{ oid => '4267',
+  proname => 'timestamptz_dist_date', prorettype => 'interval',
+  proargtypes => 'timestamptz date', prosrc => 'timestamptz_dist_date' },
+{ oid => '4268',
+  proname => 'timestamptz_dist_timestamp', prorettype => 'interval',
+  proargtypes => 'timestamptz timestamp', prosrc => 'timestamptz_dist_timestamp' },
+
 ]
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index d66582b..9a79369 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -338,4 +338,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 					   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern Interval *abs_interval(Interval *a);
+
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 2b3b357..1fa56e5 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -93,9 +93,11 @@ extern Timestamp SetEpochTimestamp(void);
 extern void GetEpochTime(struct pg_tm *tm);
 
 extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern Interval *timestamp_dist_internal(Timestamp a, Timestamp b);
 
-/* timestamp comparison works for timestamptz also */
+/* timestamp comparison and distance works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
+#define timestamptz_dist_internal(dt1,dt2)	timestamp_dist_internal(dt1, dt2)
 
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index 4570a39..630dc6b 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -24,7 +24,7 @@ select prop,
  nulls_first        |    |       | f
  nulls_last         |    |       | t
  orderable          |    |       | t
- distance_orderable |    |       | f
+ distance_orderable |    |       | t
  returnable         |    |       | t
  search_array       |    |       | t
  search_nulls       |    |       | t
@@ -100,7 +100,7 @@ select prop,
  nulls_first        | f     | f    | f    | f            | f           | f   | f
  nulls_last         | t     | f    | f    | f            | f           | f   | f
  orderable          | t     | f    | f    | f            | f           | f   | f
- distance_orderable | f     | f    | t    | f            | t           | f   | f
+ distance_orderable | t     | f    | t    | f            | t           | f   | f
  returnable         | t     | f    | f    | t            | t           | f   | f
  search_array       | t     | f    | f    | f            | f           | f   | f
  search_nulls       | t     | f    | t    | t            | t           | f   | t
@@ -231,7 +231,7 @@ select col, prop, pg_index_column_has_property(o, col, prop)
    1 | desc               | f
    1 | nulls_first        | f
    1 | nulls_last         | t
-   1 | distance_orderable | f
+   1 | distance_orderable | t
    1 | returnable         | t
    1 | bogus              | 
    2 | orderable          | f
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
index 1bcc946..b9b819c 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -1477,3 +1477,64 @@ select make_time(10, 55, 100.1);
 ERROR:  time field value out of range: 10:55:100.1
 select make_time(24, 0, 2.1);
 ERROR:  time field value out of range: 24:00:2.1
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+ Fifteen | Distance 
+---------+----------
+         |    16006
+         |    15941
+         |     1802
+         |     1801
+         |     1800
+         |     1799
+         |     1436
+         |     1435
+         |     1434
+         |      308
+         |      307
+         |      306
+         |    13578
+         |    13944
+         |    14311
+(15 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+ Fifteen |               Distance                
+---------+---------------------------------------
+         | @ 16006 days 1 hour 23 mins 45 secs
+         | @ 15941 days 1 hour 23 mins 45 secs
+         | @ 1802 days 1 hour 23 mins 45 secs
+         | @ 1801 days 1 hour 23 mins 45 secs
+         | @ 1800 days 1 hour 23 mins 45 secs
+         | @ 1799 days 1 hour 23 mins 45 secs
+         | @ 1436 days 1 hour 23 mins 45 secs
+         | @ 1435 days 1 hour 23 mins 45 secs
+         | @ 1434 days 1 hour 23 mins 45 secs
+         | @ 308 days 1 hour 23 mins 45 secs
+         | @ 307 days 1 hour 23 mins 45 secs
+         | @ 306 days 1 hour 23 mins 45 secs
+         | @ 13577 days 22 hours 36 mins 15 secs
+         | @ 13943 days 22 hours 36 mins 15 secs
+         | @ 14310 days 22 hours 36 mins 15 secs
+(15 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
+ Fifteen |               Distance                
+---------+---------------------------------------
+         | @ 16005 days 14 hours 23 mins 45 secs
+         | @ 15940 days 14 hours 23 mins 45 secs
+         | @ 1801 days 14 hours 23 mins 45 secs
+         | @ 1800 days 14 hours 23 mins 45 secs
+         | @ 1799 days 14 hours 23 mins 45 secs
+         | @ 1798 days 14 hours 23 mins 45 secs
+         | @ 1435 days 14 hours 23 mins 45 secs
+         | @ 1434 days 14 hours 23 mins 45 secs
+         | @ 1433 days 14 hours 23 mins 45 secs
+         | @ 307 days 14 hours 23 mins 45 secs
+         | @ 306 days 14 hours 23 mins 45 secs
+         | @ 305 days 15 hours 23 mins 45 secs
+         | @ 13578 days 8 hours 36 mins 15 secs
+         | @ 13944 days 8 hours 36 mins 15 secs
+         | @ 14311 days 8 hours 36 mins 15 secs
+(15 rows)
+
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index fd46a4a..51f82ea 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -257,3 +257,23 @@ SELECT '' AS five, * FROM FLOAT4_TBL;
       | -1.23457e-20
 (5 rows)
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+ five |      f1      |    dist     
+------+--------------+-------------
+      |            0 |      1004.3
+      |       -34.84 |     1039.14
+      |      -1004.3 |      2008.6
+      | -1.23457e+20 | 1.23457e+20
+      | -1.23457e-20 |      1004.3
+(5 rows)
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+ five |      f1      |         dist         
+------+--------------+----------------------
+      |            0 |               1004.3
+      |       -34.84 |     1039.14000015259
+      |      -1004.3 |     2008.59998779297
+      | -1.23457e+20 | 1.23456789557014e+20
+      | -1.23457e-20 |               1004.3
+(5 rows)
+
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
index b05831d..ff3dff6 100644
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -404,6 +404,27 @@ SELECT '' AS five, f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
       | 1.2345678901234e-200 |  2.3112042409018e-67
 (5 rows)
 
+-- distance
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+ five |          f1          |         dist         
+------+----------------------+----------------------
+      |                    0 |               1004.3
+      |               1004.3 |                    0
+      |               -34.84 |              1039.14
+      | 1.2345678901234e+200 | 1.2345678901234e+200
+      | 1.2345678901234e-200 |               1004.3
+(5 rows)
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
+ five |          f1          |         dist         
+------+----------------------+----------------------
+      |                    0 |     1004.29998779297
+      |               1004.3 | 1.22070312045253e-05
+      |               -34.84 |     1039.13998779297
+      | 1.2345678901234e+200 | 1.2345678901234e+200
+      | 1.2345678901234e-200 |     1004.29998779297
+(5 rows)
+
 SELECT '' AS five, * FROM FLOAT8_TBL;
  five |          f1          
 ------+----------------------
diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out
index 8c255b9..0edc57e 100644
--- a/src/test/regress/expected/int2.out
+++ b/src/test/regress/expected/int2.out
@@ -242,6 +242,39 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
       | -32767 | -16383
 (5 rows)
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+ERROR:  smallint out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+ four |  f1   |   x   
+------+-------+-------
+      |     0 |     2
+      |  1234 |  1232
+      | -1234 |  1236
+      | 32767 | 32765
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
   text  
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
index bda7a8d..3735dbc 100644
--- a/src/test/regress/expected/int4.out
+++ b/src/test/regress/expected/int4.out
@@ -247,6 +247,38 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
       | -2147483647 | -1073741823
 (5 rows)
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+ERROR:  integer out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+ five |     f1      |     x      
+------+-------------+------------
+      |           0 |          2
+      |      123456 |     123454
+      |     -123456 |     123458
+      |  2147483647 | 2147483645
+      | -2147483647 | 2147483649
+(5 rows)
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index 35e3b3f..8940e81 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -432,6 +432,37 @@ SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 A
  4567890123457035 | -4567890123456543 | 1123700970370370094 |     0
 (5 rows)
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
 SELECT q2, abs(q2) FROM INT8_TBL;
         q2         |       abs        
 -------------------+------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index f88f345..cb95adf 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -207,6 +207,21 @@ SELECT '' AS fortyfive, r1.*, r2.*
            | 34 years        | 6 years
 (45 rows)
 
+SELECT '' AS ten, f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+ ten |          ?column?          
+-----+----------------------------
+     | 2 days 02:59:00
+     | 2 days -02:00:00
+     | 8 days -03:00:00
+     | 34 years -2 days -03:00:00
+     | 3 mons -2 days -03:00:00
+     | 2 days 03:00:14
+     | 1 day 00:56:56
+     | 6 years -2 days -03:00:00
+     | 5 mons -2 days -03:00:00
+     | 5 mons -2 days +09:00:00
+(10 rows)
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index ab86595..fb2a489 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -123,6 +123,12 @@ SELECT m / 2::float4 FROM money_data;
    $61.50
 (1 row)
 
+SELECT m <-> '$123.45' FROM money_data;
+ ?column? 
+----------
+    $0.45
+(1 row)
+
 -- All true
 SELECT m = '$123.00' FROM money_data;
  ?column? 
diff --git a/src/test/regress/expected/oid.out b/src/test/regress/expected/oid.out
index 1eab9cc..5339a48 100644
--- a/src/test/regress/expected/oid.out
+++ b/src/test/regress/expected/oid.out
@@ -119,4 +119,17 @@ SELECT '' AS three, o.* FROM OID_TBL o WHERE o.f1 > '1234';
        |   99999999
 (3 rows)
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+ eight |     f1     |  ?column?  
+-------+------------+------------
+       |       1234 |       1111
+       |       1235 |       1112
+       |        987 |        864
+       | 4294966256 | 4294966133
+       |   99999999 |   99999876
+       |          5 |        118
+       |         10 |        113
+       |         15 |        108
+(8 rows)
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 7bcc03b..15ed87a 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1816,6 +1816,7 @@ ORDER BY 1, 2, 3;
         403 |            5 | *>
         403 |            5 | >
         403 |            5 | ~>~
+        403 |            6 | <->
         405 |            1 | =
         783 |            1 | <<
         783 |            1 | @@
@@ -1925,7 +1926,7 @@ ORDER BY 1, 2, 3;
        4000 |           26 | >>
        4000 |           27 | >>=
        4000 |           28 | ^@
-(123 rows)
+(124 rows)
 
 -- Check that all opclass search operators have selectivity estimators.
 -- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out
index 8e0afe6..ee74faa 100644
--- a/src/test/regress/expected/time.out
+++ b/src/test/regress/expected/time.out
@@ -86,3 +86,19 @@ ERROR:  operator is not unique: time without time zone + time without time zone
 LINE 1: SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
                   ^
 HINT:  Could not choose a best candidate operator. You might need to add explicit type casts.
+-- distance
+SELECT f1 AS "Ten", f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
+     Ten     |           Distance            
+-------------+-------------------------------
+ 00:00:00    | @ 1 hour 23 mins 45 secs
+ 01:00:00    | @ 23 mins 45 secs
+ 02:03:00    | @ 39 mins 15 secs
+ 11:59:00    | @ 10 hours 35 mins 15 secs
+ 12:00:00    | @ 10 hours 36 mins 15 secs
+ 12:01:00    | @ 10 hours 37 mins 15 secs
+ 23:59:00    | @ 22 hours 35 mins 15 secs
+ 23:59:59.99 | @ 22 hours 36 mins 14.99 secs
+ 15:36:39    | @ 14 hours 12 mins 54 secs
+ 15:36:39    | @ 14 hours 12 mins 54 secs
+(10 rows)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 4a2fabd..dcb4205 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1604,3 +1604,214 @@ SELECT make_timestamp(2014,12,28,6,30,45.887);
  Sun Dec 28 06:30:45.887 2014
 (1 row)
 
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 58 secs
+    | @ 1453 days 6 hours 27 mins 58.6 secs
+    | @ 1453 days 6 hours 27 mins 58.5 secs
+    | @ 1453 days 6 hours 27 mins 58.4 secs
+    | @ 1493 days
+    | @ 1492 days 20 hours 55 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 6 hours 27 mins 59 secs
+    | @ 231 days 18 hours 19 mins 20 secs
+    | @ 324 days 15 hours 45 mins 59 secs
+    | @ 324 days 10 hours 45 mins 58 secs
+    | @ 324 days 11 hours 45 mins 57 secs
+    | @ 324 days 20 hours 45 mins 56 secs
+    | @ 324 days 21 hours 45 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 28 mins
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 5 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1452 days 6 hours 27 mins 59 secs
+    | @ 1451 days 6 hours 27 mins 59 secs
+    | @ 1450 days 6 hours 27 mins 59 secs
+    | @ 1449 days 6 hours 27 mins 59 secs
+    | @ 1448 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 765901 days 6 hours 27 mins 59 secs
+    | @ 695407 days 6 hours 27 mins 59 secs
+    | @ 512786 days 6 hours 27 mins 59 secs
+    | @ 330165 days 6 hours 27 mins 59 secs
+    | @ 111019 days 6 hours 27 mins 59 secs
+    | @ 74495 days 6 hours 27 mins 59 secs
+    | @ 37971 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 35077 days 17 hours 32 mins 1 sec
+    | @ 1801 days 6 hours 27 mins 59 secs
+    | @ 1800 days 6 hours 27 mins 59 secs
+    | @ 1799 days 6 hours 27 mins 59 secs
+    | @ 1495 days 6 hours 27 mins 59 secs
+    | @ 1494 days 6 hours 27 mins 59 secs
+    | @ 1493 days 6 hours 27 mins 59 secs
+    | @ 1435 days 6 hours 27 mins 59 secs
+    | @ 1434 days 6 hours 27 mins 59 secs
+    | @ 1130 days 6 hours 27 mins 59 secs
+    | @ 1129 days 6 hours 27 mins 59 secs
+    | @ 399 days 6 hours 27 mins 59 secs
+    | @ 398 days 6 hours 27 mins 59 secs
+    | @ 33 days 6 hours 27 mins 59 secs
+    | @ 32 days 6 hours 27 mins 59 secs
+(63 rows)
+
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 1 hour 23 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 43 secs
+    | @ 1453 days 7 hours 51 mins 43.6 secs
+    | @ 1453 days 7 hours 51 mins 43.5 secs
+    | @ 1453 days 7 hours 51 mins 43.4 secs
+    | @ 1493 days 1 hour 23 mins 45 secs
+    | @ 1492 days 22 hours 19 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 7 hours 51 mins 44 secs
+    | @ 231 days 16 hours 55 mins 35 secs
+    | @ 324 days 17 hours 9 mins 44 secs
+    | @ 324 days 12 hours 9 mins 43 secs
+    | @ 324 days 13 hours 9 mins 42 secs
+    | @ 324 days 22 hours 9 mins 41 secs
+    | @ 324 days 23 hours 9 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 6 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1452 days 7 hours 51 mins 44 secs
+    | @ 1451 days 7 hours 51 mins 44 secs
+    | @ 1450 days 7 hours 51 mins 44 secs
+    | @ 1449 days 7 hours 51 mins 44 secs
+    | @ 1448 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 765901 days 7 hours 51 mins 44 secs
+    | @ 695407 days 7 hours 51 mins 44 secs
+    | @ 512786 days 7 hours 51 mins 44 secs
+    | @ 330165 days 7 hours 51 mins 44 secs
+    | @ 111019 days 7 hours 51 mins 44 secs
+    | @ 74495 days 7 hours 51 mins 44 secs
+    | @ 37971 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 35077 days 16 hours 8 mins 16 secs
+    | @ 1801 days 7 hours 51 mins 44 secs
+    | @ 1800 days 7 hours 51 mins 44 secs
+    | @ 1799 days 7 hours 51 mins 44 secs
+    | @ 1495 days 7 hours 51 mins 44 secs
+    | @ 1494 days 7 hours 51 mins 44 secs
+    | @ 1493 days 7 hours 51 mins 44 secs
+    | @ 1435 days 7 hours 51 mins 44 secs
+    | @ 1434 days 7 hours 51 mins 44 secs
+    | @ 1130 days 7 hours 51 mins 44 secs
+    | @ 1129 days 7 hours 51 mins 44 secs
+    | @ 399 days 7 hours 51 mins 44 secs
+    | @ 398 days 7 hours 51 mins 44 secs
+    | @ 33 days 7 hours 51 mins 44 secs
+    | @ 32 days 7 hours 51 mins 44 secs
+(63 rows)
+
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |                Distance                
+----+----------------------------------------
+    | @ 11355 days 14 hours 23 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 43 secs
+    | @ 1452 days 20 hours 51 mins 43.6 secs
+    | @ 1452 days 20 hours 51 mins 43.5 secs
+    | @ 1452 days 20 hours 51 mins 43.4 secs
+    | @ 1492 days 14 hours 23 mins 45 secs
+    | @ 1492 days 11 hours 19 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 21 hours 51 mins 44 secs
+    | @ 232 days 2 hours 55 mins 35 secs
+    | @ 324 days 6 hours 9 mins 44 secs
+    | @ 324 days 1 hour 9 mins 43 secs
+    | @ 324 days 2 hours 9 mins 42 secs
+    | @ 324 days 11 hours 9 mins 41 secs
+    | @ 324 days 12 hours 9 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1451 days 20 hours 51 mins 44 secs
+    | @ 1450 days 20 hours 51 mins 44 secs
+    | @ 1449 days 20 hours 51 mins 44 secs
+    | @ 1448 days 20 hours 51 mins 44 secs
+    | @ 1447 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 765900 days 20 hours 51 mins 44 secs
+    | @ 695406 days 20 hours 51 mins 44 secs
+    | @ 512785 days 20 hours 51 mins 44 secs
+    | @ 330164 days 20 hours 51 mins 44 secs
+    | @ 111018 days 20 hours 51 mins 44 secs
+    | @ 74494 days 20 hours 51 mins 44 secs
+    | @ 37970 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 35078 days 3 hours 8 mins 16 secs
+    | @ 1800 days 20 hours 51 mins 44 secs
+    | @ 1799 days 20 hours 51 mins 44 secs
+    | @ 1798 days 20 hours 51 mins 44 secs
+    | @ 1494 days 20 hours 51 mins 44 secs
+    | @ 1493 days 20 hours 51 mins 44 secs
+    | @ 1492 days 20 hours 51 mins 44 secs
+    | @ 1434 days 20 hours 51 mins 44 secs
+    | @ 1433 days 20 hours 51 mins 44 secs
+    | @ 1129 days 20 hours 51 mins 44 secs
+    | @ 1128 days 20 hours 51 mins 44 secs
+    | @ 398 days 20 hours 51 mins 44 secs
+    | @ 397 days 20 hours 51 mins 44 secs
+    | @ 32 days 20 hours 51 mins 44 secs
+    | @ 31 days 20 hours 51 mins 44 secs
+(63 rows)
+
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 2340f30..4018fc8 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2526,3 +2526,217 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
  Tue Jan 17 16:00:00 2017 PST
 (1 row)
 
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 8 hours
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 58 secs
+    | @ 1453 days 6 hours 27 mins 58.6 secs
+    | @ 1453 days 6 hours 27 mins 58.5 secs
+    | @ 1453 days 6 hours 27 mins 58.4 secs
+    | @ 1493 days
+    | @ 1492 days 20 hours 55 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 7 hours 27 mins 59 secs
+    | @ 231 days 17 hours 19 mins 20 secs
+    | @ 324 days 15 hours 45 mins 59 secs
+    | @ 324 days 19 hours 45 mins 58 secs
+    | @ 324 days 21 hours 45 mins 57 secs
+    | @ 324 days 20 hours 45 mins 56 secs
+    | @ 324 days 22 hours 45 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 28 mins
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 9 hours 27 mins 59 secs
+    | @ 1303 days 10 hours 27 mins 59 secs
+    | @ 1333 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1452 days 6 hours 27 mins 59 secs
+    | @ 1451 days 6 hours 27 mins 59 secs
+    | @ 1450 days 6 hours 27 mins 59 secs
+    | @ 1449 days 6 hours 27 mins 59 secs
+    | @ 1448 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 765901 days 6 hours 27 mins 59 secs
+    | @ 695407 days 6 hours 27 mins 59 secs
+    | @ 512786 days 6 hours 27 mins 59 secs
+    | @ 330165 days 6 hours 27 mins 59 secs
+    | @ 111019 days 6 hours 27 mins 59 secs
+    | @ 74495 days 6 hours 27 mins 59 secs
+    | @ 37971 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 35077 days 17 hours 32 mins 1 sec
+    | @ 1801 days 6 hours 27 mins 59 secs
+    | @ 1800 days 6 hours 27 mins 59 secs
+    | @ 1799 days 6 hours 27 mins 59 secs
+    | @ 1495 days 6 hours 27 mins 59 secs
+    | @ 1494 days 6 hours 27 mins 59 secs
+    | @ 1493 days 6 hours 27 mins 59 secs
+    | @ 1435 days 6 hours 27 mins 59 secs
+    | @ 1434 days 6 hours 27 mins 59 secs
+    | @ 1130 days 6 hours 27 mins 59 secs
+    | @ 1129 days 6 hours 27 mins 59 secs
+    | @ 399 days 6 hours 27 mins 59 secs
+    | @ 398 days 6 hours 27 mins 59 secs
+    | @ 33 days 6 hours 27 mins 59 secs
+    | @ 32 days 6 hours 27 mins 59 secs
+(64 rows)
+
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 9 hours 23 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 43 secs
+    | @ 1453 days 7 hours 51 mins 43.6 secs
+    | @ 1453 days 7 hours 51 mins 43.5 secs
+    | @ 1453 days 7 hours 51 mins 43.4 secs
+    | @ 1493 days 1 hour 23 mins 45 secs
+    | @ 1492 days 22 hours 19 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 8 hours 51 mins 44 secs
+    | @ 231 days 15 hours 55 mins 35 secs
+    | @ 324 days 17 hours 9 mins 44 secs
+    | @ 324 days 21 hours 9 mins 43 secs
+    | @ 324 days 23 hours 9 mins 42 secs
+    | @ 324 days 22 hours 9 mins 41 secs
+    | @ 325 days 9 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 10 hours 51 mins 44 secs
+    | @ 1303 days 11 hours 51 mins 44 secs
+    | @ 1333 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1452 days 7 hours 51 mins 44 secs
+    | @ 1451 days 7 hours 51 mins 44 secs
+    | @ 1450 days 7 hours 51 mins 44 secs
+    | @ 1449 days 7 hours 51 mins 44 secs
+    | @ 1448 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 765901 days 7 hours 51 mins 44 secs
+    | @ 695407 days 7 hours 51 mins 44 secs
+    | @ 512786 days 7 hours 51 mins 44 secs
+    | @ 330165 days 7 hours 51 mins 44 secs
+    | @ 111019 days 7 hours 51 mins 44 secs
+    | @ 74495 days 7 hours 51 mins 44 secs
+    | @ 37971 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 35077 days 16 hours 8 mins 16 secs
+    | @ 1801 days 7 hours 51 mins 44 secs
+    | @ 1800 days 7 hours 51 mins 44 secs
+    | @ 1799 days 7 hours 51 mins 44 secs
+    | @ 1495 days 7 hours 51 mins 44 secs
+    | @ 1494 days 7 hours 51 mins 44 secs
+    | @ 1493 days 7 hours 51 mins 44 secs
+    | @ 1435 days 7 hours 51 mins 44 secs
+    | @ 1434 days 7 hours 51 mins 44 secs
+    | @ 1130 days 7 hours 51 mins 44 secs
+    | @ 1129 days 7 hours 51 mins 44 secs
+    | @ 399 days 7 hours 51 mins 44 secs
+    | @ 398 days 7 hours 51 mins 44 secs
+    | @ 33 days 7 hours 51 mins 44 secs
+    | @ 32 days 7 hours 51 mins 44 secs
+(64 rows)
+
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |                Distance                
+----+----------------------------------------
+    | @ 11355 days 22 hours 23 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 43 secs
+    | @ 1452 days 20 hours 51 mins 43.6 secs
+    | @ 1452 days 20 hours 51 mins 43.5 secs
+    | @ 1452 days 20 hours 51 mins 43.4 secs
+    | @ 1492 days 14 hours 23 mins 45 secs
+    | @ 1492 days 11 hours 19 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 21 hours 51 mins 44 secs
+    | @ 232 days 2 hours 55 mins 35 secs
+    | @ 324 days 6 hours 9 mins 44 secs
+    | @ 324 days 10 hours 9 mins 43 secs
+    | @ 324 days 12 hours 9 mins 42 secs
+    | @ 324 days 11 hours 9 mins 41 secs
+    | @ 324 days 13 hours 9 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1452 days 23 hours 51 mins 44 secs
+    | @ 1303 days 51 mins 44 secs
+    | @ 1332 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1451 days 20 hours 51 mins 44 secs
+    | @ 1450 days 20 hours 51 mins 44 secs
+    | @ 1449 days 20 hours 51 mins 44 secs
+    | @ 1448 days 20 hours 51 mins 44 secs
+    | @ 1447 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 765900 days 20 hours 51 mins 44 secs
+    | @ 695406 days 20 hours 51 mins 44 secs
+    | @ 512785 days 20 hours 51 mins 44 secs
+    | @ 330164 days 20 hours 51 mins 44 secs
+    | @ 111018 days 20 hours 51 mins 44 secs
+    | @ 74494 days 20 hours 51 mins 44 secs
+    | @ 37970 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 35078 days 3 hours 8 mins 16 secs
+    | @ 1800 days 20 hours 51 mins 44 secs
+    | @ 1799 days 20 hours 51 mins 44 secs
+    | @ 1798 days 20 hours 51 mins 44 secs
+    | @ 1494 days 20 hours 51 mins 44 secs
+    | @ 1493 days 20 hours 51 mins 44 secs
+    | @ 1492 days 20 hours 51 mins 44 secs
+    | @ 1434 days 20 hours 51 mins 44 secs
+    | @ 1433 days 20 hours 51 mins 44 secs
+    | @ 1129 days 20 hours 51 mins 44 secs
+    | @ 1128 days 20 hours 51 mins 44 secs
+    | @ 398 days 20 hours 51 mins 44 secs
+    | @ 397 days 20 hours 51 mins 44 secs
+    | @ 32 days 20 hours 51 mins 44 secs
+    | @ 31 days 20 hours 51 mins 44 secs
+(64 rows)
+
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
index 22f80f2..24be476 100644
--- a/src/test/regress/sql/date.sql
+++ b/src/test/regress/sql/date.sql
@@ -346,3 +346,8 @@ select make_date(2013, 13, 1);
 select make_date(2013, 11, -1);
 select make_time(10, 55, 100.1);
 select make_time(24, 0, 2.1);
+
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
diff --git a/src/test/regress/sql/float4.sql b/src/test/regress/sql/float4.sql
index 3b363f9..da4af1a 100644
--- a/src/test/regress/sql/float4.sql
+++ b/src/test/regress/sql/float4.sql
@@ -81,3 +81,6 @@ UPDATE FLOAT4_TBL
    WHERE FLOAT4_TBL.f1 > '0.0';
 
 SELECT '' AS five, * FROM FLOAT4_TBL;
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
index eeebddd..c770237 100644
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -125,6 +125,9 @@ SELECT ||/ float8 '27' AS three;
 
 SELECT '' AS five, f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
 
+-- distance
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
 
 SELECT '' AS five, * FROM FLOAT8_TBL;
 
diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql
index 7dbafb6..16dd5d8 100644
--- a/src/test/regress/sql/int2.sql
+++ b/src/test/regress/sql/int2.sql
@@ -84,6 +84,16 @@ SELECT '' AS five, i.f1, i.f1 / int2 '2' AS x FROM INT2_TBL i;
 
 SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
 SELECT ((-1::int2<<15)+1::int2)::text;
diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql
index f014cb2..cff32946 100644
--- a/src/test/regress/sql/int4.sql
+++ b/src/test/regress/sql/int4.sql
@@ -93,6 +93,16 @@ SELECT '' AS five, i.f1, i.f1 / int2 '2' AS x FROM INT4_TBL i;
 
 SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql
index e890452..d7f5bde 100644
--- a/src/test/regress/sql/int8.sql
+++ b/src/test/regress/sql/int8.sql
@@ -89,6 +89,11 @@ SELECT q1 + 42::int2 AS "8plus2", q1 - 42::int2 AS "8minus2", q1 * 42::int2 AS "
 -- int2 op int8
 SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 AS "2mul8", 246::int2 / q1 AS "2div8" FROM INT8_TBL;
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+
 SELECT q2, abs(q2) FROM INT8_TBL;
 SELECT min(q1), min(q2) FROM INT8_TBL;
 SELECT max(q1), max(q2) FROM INT8_TBL;
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index bc5537d..d51c866 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -59,6 +59,8 @@ SELECT '' AS fortyfive, r1.*, r2.*
    WHERE r1.f1 > r2.f1
    ORDER BY r1.f1, r2.f1;
 
+SELECT '' AS ten, f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql
index 37b9ecc..8428d59 100644
--- a/src/test/regress/sql/money.sql
+++ b/src/test/regress/sql/money.sql
@@ -25,6 +25,7 @@ SELECT m / 2::float8 FROM money_data;
 SELECT m * 2::float4 FROM money_data;
 SELECT 2::float4 * m FROM money_data;
 SELECT m / 2::float4 FROM money_data;
+SELECT m <-> '$123.45' FROM money_data;
 
 -- All true
 SELECT m = '$123.00' FROM money_data;
diff --git a/src/test/regress/sql/oid.sql b/src/test/regress/sql/oid.sql
index 4a09689..9f54f92 100644
--- a/src/test/regress/sql/oid.sql
+++ b/src/test/regress/sql/oid.sql
@@ -40,4 +40,6 @@ SELECT '' AS four, o.* FROM OID_TBL o WHERE o.f1 >= '1234';
 
 SELECT '' AS three, o.* FROM OID_TBL o WHERE o.f1 > '1234';
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/sql/time.sql b/src/test/regress/sql/time.sql
index 99a1562..31f0330 100644
--- a/src/test/regress/sql/time.sql
+++ b/src/test/regress/sql/time.sql
@@ -40,3 +40,6 @@ SELECT f1 AS "Eight" FROM TIME_TBL WHERE f1 >= '00:00';
 -- where we do mixed-type arithmetic. - thomas 2000-12-02
 
 SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
+
+-- distance
+SELECT f1 AS "Ten", f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b7957cb..5d023dd 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -230,3 +230,11 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
 
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
+
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index f17d153..92c3388 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -460,3 +460,11 @@ insert into tmptz values ('2017-01-18 00:00+00');
 explain (costs off)
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
0006-Remove-distance-operators-from-btree_gist-v03.patchtext/x-patch; name=0006-Remove-distance-operators-from-btree_gist-v03.patchDownload
diff --git a/contrib/btree_gist/Makefile b/contrib/btree_gist/Makefile
index af65120..46ab241 100644
--- a/contrib/btree_gist/Makefile
+++ b/contrib/btree_gist/Makefile
@@ -11,8 +11,9 @@ OBJS =  btree_gist.o btree_utils_num.o btree_utils_var.o btree_int2.o \
 
 EXTENSION = btree_gist
 DATA = btree_gist--unpackaged--1.0.sql btree_gist--1.0--1.1.sql \
-       btree_gist--1.1--1.2.sql btree_gist--1.2.sql btree_gist--1.2--1.3.sql \
-       btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql
+       btree_gist--1.1--1.2.sql btree_gist--1.2--1.3.sql \
+       btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql \
+       btree_gist--1.5--1.6.sql btree_gist--1.6.sql
 PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes"
 
 REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \
diff --git a/contrib/btree_gist/btree_cash.c b/contrib/btree_gist/btree_cash.c
index 894d0a2..86e319d 100644
--- a/contrib/btree_gist/btree_cash.c
+++ b/contrib/btree_gist/btree_cash.c
@@ -91,26 +91,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(cash_dist);
-Datum
-cash_dist(PG_FUNCTION_ARGS)
-{
-	Cash		a = PG_GETARG_CASH(0);
-	Cash		b = PG_GETARG_CASH(1);
-	Cash		r;
-	Cash		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("money out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_CASH(ra);
-}
-
 /**************************************************
  * Cash ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_date.c b/contrib/btree_gist/btree_date.c
index 992ce57..05a4909 100644
--- a/contrib/btree_gist/btree_date.c
+++ b/contrib/btree_gist/btree_date.c
@@ -109,19 +109,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(date_dist);
-Datum
-date_dist(PG_FUNCTION_ARGS)
-{
-	/* we assume the difference can't overflow */
-	Datum		diff = DirectFunctionCall2(date_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
-}
-
-
 /**************************************************
  * date ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index 6b20f44..26fa79b 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -89,21 +89,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(float4_dist);
-Datum
-float4_dist(PG_FUNCTION_ARGS)
-{
-	float4		a = PG_GETARG_FLOAT4(0);
-	float4		b = PG_GETARG_FLOAT4(1);
-	float4		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT4(Abs(r));
-}
-
-
 /**************************************************
  * float4 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index ee114cb..63e5597 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -96,21 +96,6 @@ static const gbtree_ninfo tinfo =
 	gbt_float8_dist
 };
 
-
-PG_FUNCTION_INFO_V1(float8_dist);
-Datum
-float8_dist(PG_FUNCTION_ARGS)
-{
-	float8		a = PG_GETARG_FLOAT8(0);
-	float8		b = PG_GETARG_FLOAT8(1);
-	float8		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT8(Abs(r));
-}
-
 /**************************************************
  * float8 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_gist--1.2.sql b/contrib/btree_gist/btree_gist--1.2.sql
deleted file mode 100644
index 1efe753..0000000
--- a/contrib/btree_gist/btree_gist--1.2.sql
+++ /dev/null
@@ -1,1570 +0,0 @@
-/* contrib/btree_gist/btree_gist--1.2.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
-
-CREATE FUNCTION gbtreekey4_in(cstring)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey4_out(gbtreekey4)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey4 (
-	INTERNALLENGTH = 4,
-	INPUT  = gbtreekey4_in,
-	OUTPUT = gbtreekey4_out
-);
-
-CREATE FUNCTION gbtreekey8_in(cstring)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey8_out(gbtreekey8)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey8 (
-	INTERNALLENGTH = 8,
-	INPUT  = gbtreekey8_in,
-	OUTPUT = gbtreekey8_out
-);
-
-CREATE FUNCTION gbtreekey16_in(cstring)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey16_out(gbtreekey16)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey16 (
-	INTERNALLENGTH = 16,
-	INPUT  = gbtreekey16_in,
-	OUTPUT = gbtreekey16_out
-);
-
-CREATE FUNCTION gbtreekey32_in(cstring)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey32_out(gbtreekey32)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey32 (
-	INTERNALLENGTH = 32,
-	INPUT  = gbtreekey32_in,
-	OUTPUT = gbtreekey32_out
-);
-
-CREATE FUNCTION gbtreekey_var_in(cstring)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey_var (
-	INTERNALLENGTH = VARIABLE,
-	INPUT  = gbtreekey_var_in,
-	OUTPUT = gbtreekey_var_out,
-	STORAGE = EXTENDED
-);
-
---distance operators
-
-CREATE FUNCTION cash_dist(money, money)
-RETURNS money
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = money,
-	RIGHTARG = money,
-	PROCEDURE = cash_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION date_dist(date, date)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = date,
-	RIGHTARG = date,
-	PROCEDURE = date_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float4_dist(float4, float4)
-RETURNS float4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float4,
-	RIGHTARG = float4,
-	PROCEDURE = float4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float8_dist(float8, float8)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float8,
-	RIGHTARG = float8,
-	PROCEDURE = float8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int2_dist(int2, int2)
-RETURNS int2
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int2,
-	RIGHTARG = int2,
-	PROCEDURE = int2_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int4_dist(int4, int4)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int4,
-	RIGHTARG = int4,
-	PROCEDURE = int4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int8_dist(int8, int8)
-RETURNS int8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int8,
-	RIGHTARG = int8,
-	PROCEDURE = int8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION interval_dist(interval, interval)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = interval,
-	RIGHTARG = interval,
-	PROCEDURE = interval_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION oid_dist(oid, oid)
-RETURNS oid
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = oid,
-	RIGHTARG = oid,
-	PROCEDURE = oid_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION time_dist(time, time)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = time,
-	RIGHTARG = time,
-	PROCEDURE = time_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION ts_dist(timestamp, timestamp)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamp,
-	RIGHTARG = timestamp,
-	PROCEDURE = ts_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION tstz_dist(timestamptz, timestamptz)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamptz,
-	RIGHTARG = timestamptz,
-	PROCEDURE = tstz_dist,
-	COMMUTATOR = '<->'
-);
-
-
---
---
---
--- oid ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_oid_ops
-DEFAULT FOR TYPE oid USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
-	FUNCTION	2	gbt_oid_union (internal, internal),
-	FUNCTION	3	gbt_oid_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_oid_picksplit (internal, internal),
-	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
--- Add operators that are new in 9.1.  We do it like this, leaving them
--- "loose" in the operator family rather than bound into the opclass, because
--- that's the only state that can be reproduced during an upgrade from 9.0.
-ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
-	OPERATOR	6	<> (oid, oid) ,
-	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
-	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
-	-- Also add support function for index-only-scans, added in 9.5.
-	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
-
-
---
---
---
--- int2 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_union(internal, internal)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int2_ops
-DEFAULT FOR TYPE int2 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
-	FUNCTION	2	gbt_int2_union (internal, internal),
-	FUNCTION	3	gbt_int2_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int2_picksplit (internal, internal),
-	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
-	STORAGE		gbtreekey4;
-
-ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
-	OPERATOR	6	<> (int2, int2) ,
-	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
-	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
-
---
---
---
--- int4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int4_ops
-DEFAULT FOR TYPE int4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
-	FUNCTION	2	gbt_int4_union (internal, internal),
-	FUNCTION	3	gbt_int4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int4_picksplit (internal, internal),
-	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
-	OPERATOR	6	<> (int4, int4) ,
-	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
-	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
-
-
---
---
---
--- int8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int8_ops
-DEFAULT FOR TYPE int8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
-	FUNCTION	2	gbt_int8_union (internal, internal),
-	FUNCTION	3	gbt_int8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int8_picksplit (internal, internal),
-	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
-	OPERATOR	6	<> (int8, int8) ,
-	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
-	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
-
---
---
---
--- float4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float4_ops
-DEFAULT FOR TYPE float4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
-	FUNCTION	2	gbt_float4_union (internal, internal),
-	FUNCTION	3	gbt_float4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float4_picksplit (internal, internal),
-	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
-	OPERATOR	6	<> (float4, float4) ,
-	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
-	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
-
---
---
---
--- float8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float8_ops
-DEFAULT FOR TYPE float8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
-	FUNCTION	2	gbt_float8_union (internal, internal),
-	FUNCTION	3	gbt_float8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float8_picksplit (internal, internal),
-	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
-	OPERATOR	6	<> (float8, float8) ,
-	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
-	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
-
---
---
---
--- timestamp ops
---
---
---
-
-CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamp_ops
-DEFAULT FOR TYPE timestamp USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_ts_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
-	OPERATOR	6	<> (timestamp, timestamp) ,
-	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
-	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamptz_ops
-DEFAULT FOR TYPE timestamptz USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_tstz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
-	OPERATOR	6	<> (timestamptz, timestamptz) ,
-	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
-	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
-
---
---
---
--- time ops
---
---
---
-
-CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_time_ops
-DEFAULT FOR TYPE time USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_time_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
-	OPERATOR	6	<> (time, time) ,
-	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
-	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
-
-
-CREATE OPERATOR CLASS gist_timetz_ops
-DEFAULT FOR TYPE timetz USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_timetz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
-	OPERATOR	6	<> (timetz, timetz) ;
-	-- no 'fetch' function, as the compress function is lossy.
-
-
---
---
---
--- date ops
---
---
---
-
-CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_date_ops
-DEFAULT FOR TYPE date USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
-	FUNCTION	2	gbt_date_union (internal, internal),
-	FUNCTION	3	gbt_date_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_date_picksplit (internal, internal),
-	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
-	OPERATOR	6	<> (date, date) ,
-	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
-	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
-
-
---
---
---
--- interval ops
---
---
---
-
-CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_union(internal, internal)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_interval_ops
-DEFAULT FOR TYPE interval USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
-	FUNCTION	2	gbt_intv_union (internal, internal),
-	FUNCTION	3	gbt_intv_compress (internal),
-	FUNCTION	4	gbt_intv_decompress (internal),
-	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_intv_picksplit (internal, internal),
-	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
-	STORAGE		gbtreekey32;
-
-ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
-	OPERATOR	6	<> (interval, interval) ,
-	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
-	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
-
-
---
---
---
--- cash ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cash_ops
-DEFAULT FOR TYPE money USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
-	FUNCTION	2	gbt_cash_union (internal, internal),
-	FUNCTION	3	gbt_cash_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_cash_picksplit (internal, internal),
-	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
-	OPERATOR	6	<> (money, money) ,
-	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
-	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
-	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
-
-
---
---
---
--- macaddr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_macaddr_ops
-DEFAULT FOR TYPE macaddr USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
-	FUNCTION	2	gbt_macad_union (internal, internal),
-	FUNCTION	3	gbt_macad_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_macad_picksplit (internal, internal),
-	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
-	OPERATOR	6	<> (macaddr, macaddr) ,
-	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
-
-
---
---
---
--- text/ bpchar ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_text_ops
-DEFAULT FOR TYPE text USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_text_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
-	OPERATOR	6	<> (text, text) ,
-	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
-
-
----- Create the operator class
-CREATE OPERATOR CLASS gist_bpchar_ops
-DEFAULT FOR TYPE bpchar USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_bpchar_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
-	OPERATOR	6	<> (bpchar, bpchar) ,
-	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
-
---
---
--- bytea ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bytea_ops
-DEFAULT FOR TYPE bytea USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
-	FUNCTION	2	gbt_bytea_union (internal, internal),
-	FUNCTION	3	gbt_bytea_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
-	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
-	OPERATOR	6	<> (bytea, bytea) ,
-	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
-
-
---
---
---
--- numeric ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_numeric_ops
-DEFAULT FOR TYPE numeric USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
-	FUNCTION	2	gbt_numeric_union (internal, internal),
-	FUNCTION	3	gbt_numeric_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
-	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
-	OPERATOR	6	<> (numeric, numeric) ,
-	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
-
-
---
---
--- bit ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bit_ops
-DEFAULT FOR TYPE bit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
-	OPERATOR	6	<> (bit, bit) ,
-	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
-
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_vbit_ops
-DEFAULT FOR TYPE varbit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
-	OPERATOR	6	<> (varbit, varbit) ,
-	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
-
-
---
---
---
--- inet/cidr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_inet_ops
-DEFAULT FOR TYPE inet USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
-	OPERATOR	6	<>  (inet, inet) ;
-	-- no fetch support, the compress function is lossy
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cidr_ops
-DEFAULT FOR TYPE cidr USING gist
-AS
-	OPERATOR	1	<  (inet, inet)  ,
-	OPERATOR	2	<= (inet, inet)  ,
-	OPERATOR	3	=  (inet, inet)  ,
-	OPERATOR	4	>= (inet, inet)  ,
-	OPERATOR	5	>  (inet, inet)  ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
-	OPERATOR	6	<> (inet, inet) ;
-	-- no fetch support, the compress function is lossy
diff --git a/contrib/btree_gist/btree_gist--1.5--1.6.sql b/contrib/btree_gist/btree_gist--1.5--1.6.sql
new file mode 100644
index 0000000..fcd10f5
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.5--1.6.sql
@@ -0,0 +1,150 @@
+/* contrib/btree_gist/btree_gist--1.5--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.6'" to load this file. \quit
+
+-- update references to distance operators in pg_amop and pg_depend
+
+WITH
+btree_ops AS (
+	SELECT
+		amoplefttype, amoprighttype, amopopr
+	FROM
+		pg_amop
+		JOIN pg_am ON pg_am.oid = amopmethod
+		JOIN pg_opfamily ON pg_opfamily.oid = amopfamily
+		JOIN pg_namespace ON pg_namespace.oid = opfnamespace
+	WHERE
+		nspname = 'pg_catalog'
+		AND opfname IN (
+			'integer_ops',
+			'oid_ops',
+			'money_ops',
+			'float_ops',
+			'datetime_ops',
+			'time_ops',
+			'interval_ops'
+		)
+		AND amname = 'btree'
+		AND amoppurpose = 'o'
+		AND amopstrategy = 6
+	),
+gist_ops AS (
+	SELECT
+		pg_amop.oid AS oid, amoplefttype, amoprighttype, amopopr
+	FROM
+		pg_amop
+		JOIN pg_am ON amopmethod = pg_am.oid
+		JOIN pg_opfamily ON amopfamily = pg_opfamily.oid
+		JOIN pg_namespace ON pg_namespace.oid = opfnamespace
+	WHERE
+		nspname = current_schema()
+		AND opfname IN (
+			'gist_oid_ops',
+			'gist_int2_ops',
+			'gist_int4_ops',
+			'gist_int8_ops',
+			'gist_float4_ops',
+			'gist_float8_ops',
+			'gist_timestamp_ops',
+			'gist_timestamptz_ops',
+			'gist_time_ops',
+			'gist_date_ops',
+			'gist_interval_ops',
+			'gist_cash_ops'
+		)
+		AND amname = 'gist'
+		AND amoppurpose = 'o'
+		AND amopstrategy = 15
+	),
+depend_update_data(gist_amop, gist_amopopr, btree_amopopr) AS (
+	SELECT
+		gist_ops.oid, gist_ops.amopopr, btree_ops.amopopr
+	FROM
+		btree_ops JOIN gist_ops USING (amoplefttype, amoprighttype)
+),
+amop_update_data AS (
+	UPDATE
+		pg_depend
+	SET
+		refobjid = btree_amopopr
+	FROM
+		depend_update_data
+	WHERE
+		objid = gist_amop AND refobjid = gist_amopopr
+	RETURNING
+		depend_update_data.*
+)
+UPDATE
+	pg_amop
+SET
+	amopopr = btree_amopopr
+FROM
+	amop_update_data
+WHERE
+	pg_amop.oid = gist_amop;
+
+-- disable implicit pg_catalog search
+
+DO
+$$
+BEGIN
+	EXECUTE 'SET LOCAL search_path TO ' || current_schema() || ', pg_catalog';
+END
+$$;
+
+-- drop distance operators
+
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int2, int2);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int4, int4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int8, int8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float4, float4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float8, float8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (oid, oid);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (money, money);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (date, date);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (time, time);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (interval, interval);
+
+DROP OPERATOR <-> (int2, int2);
+DROP OPERATOR <-> (int4, int4);
+DROP OPERATOR <-> (int8, int8);
+DROP OPERATOR <-> (float4, float4);
+DROP OPERATOR <-> (float8, float8);
+DROP OPERATOR <-> (oid, oid);
+DROP OPERATOR <-> (money, money);
+DROP OPERATOR <-> (date, date);
+DROP OPERATOR <-> (time, time);
+DROP OPERATOR <-> (timestamp, timestamp);
+DROP OPERATOR <-> (timestamptz, timestamptz);
+DROP OPERATOR <-> (interval, interval);
+
+-- drop distance functions
+
+ALTER EXTENSION btree_gist DROP FUNCTION int2_dist(int2, int2);
+ALTER EXTENSION btree_gist DROP FUNCTION int4_dist(int4, int4);
+ALTER EXTENSION btree_gist DROP FUNCTION int8_dist(int8, int8);
+ALTER EXTENSION btree_gist DROP FUNCTION float4_dist(float4, float4);
+ALTER EXTENSION btree_gist DROP FUNCTION float8_dist(float8, float8);
+ALTER EXTENSION btree_gist DROP FUNCTION oid_dist(oid, oid);
+ALTER EXTENSION btree_gist DROP FUNCTION cash_dist(money, money);
+ALTER EXTENSION btree_gist DROP FUNCTION date_dist(date, date);
+ALTER EXTENSION btree_gist DROP FUNCTION time_dist(time, time);
+ALTER EXTENSION btree_gist DROP FUNCTION ts_dist(timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP FUNCTION interval_dist(interval, interval);
+
+DROP FUNCTION int2_dist(int2, int2);
+DROP FUNCTION int4_dist(int4, int4);
+DROP FUNCTION int8_dist(int8, int8);
+DROP FUNCTION float4_dist(float4, float4);
+DROP FUNCTION float8_dist(float8, float8);
+DROP FUNCTION oid_dist(oid, oid);
+DROP FUNCTION cash_dist(money, money);
+DROP FUNCTION date_dist(date, date);
+DROP FUNCTION time_dist(time, time);
+DROP FUNCTION ts_dist(timestamp, timestamp);
+DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+DROP FUNCTION interval_dist(interval, interval);
diff --git a/contrib/btree_gist/btree_gist--1.6.sql b/contrib/btree_gist/btree_gist--1.6.sql
new file mode 100644
index 0000000..8ff8eb5
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.6.sql
@@ -0,0 +1,1615 @@
+/* contrib/btree_gist/btree_gist--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
+
+CREATE FUNCTION gbtreekey4_in(cstring)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey4_out(gbtreekey4)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey4 (
+	INTERNALLENGTH = 4,
+	INPUT  = gbtreekey4_in,
+	OUTPUT = gbtreekey4_out
+);
+
+CREATE FUNCTION gbtreekey8_in(cstring)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey8_out(gbtreekey8)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey8 (
+	INTERNALLENGTH = 8,
+	INPUT  = gbtreekey8_in,
+	OUTPUT = gbtreekey8_out
+);
+
+CREATE FUNCTION gbtreekey16_in(cstring)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey16_out(gbtreekey16)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey16 (
+	INTERNALLENGTH = 16,
+	INPUT  = gbtreekey16_in,
+	OUTPUT = gbtreekey16_out
+);
+
+CREATE FUNCTION gbtreekey32_in(cstring)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey32_out(gbtreekey32)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey32 (
+	INTERNALLENGTH = 32,
+	INPUT  = gbtreekey32_in,
+	OUTPUT = gbtreekey32_out
+);
+
+CREATE FUNCTION gbtreekey_var_in(cstring)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey_var (
+	INTERNALLENGTH = VARIABLE,
+	INPUT  = gbtreekey_var_in,
+	OUTPUT = gbtreekey_var_out,
+	STORAGE = EXTENDED
+);
+
+
+--
+--
+--
+-- oid ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_oid_ops
+DEFAULT FOR TYPE oid USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
+	FUNCTION	2	gbt_oid_union (internal, internal),
+	FUNCTION	3	gbt_oid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_oid_picksplit (internal, internal),
+	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+-- Add operators that are new in 9.1.  We do it like this, leaving them
+-- "loose" in the operator family rather than bound into the opclass, because
+-- that's the only state that can be reproduced during an upgrade from 9.0.
+ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
+	OPERATOR	6	<> (oid, oid) ,
+	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
+	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
+	-- Also add support function for index-only-scans, added in 9.5.
+	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
+
+
+--
+--
+--
+-- int2 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_union(internal, internal)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int2_ops
+DEFAULT FOR TYPE int2 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
+	FUNCTION	2	gbt_int2_union (internal, internal),
+	FUNCTION	3	gbt_int2_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int2_picksplit (internal, internal),
+	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
+	STORAGE		gbtreekey4;
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
+	OPERATOR	6	<> (int2, int2) ,
+	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
+	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
+
+--
+--
+--
+-- int4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int4_ops
+DEFAULT FOR TYPE int4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
+	FUNCTION	2	gbt_int4_union (internal, internal),
+	FUNCTION	3	gbt_int4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int4_picksplit (internal, internal),
+	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
+	OPERATOR	6	<> (int4, int4) ,
+	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
+	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
+
+
+--
+--
+--
+-- int8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int8_ops
+DEFAULT FOR TYPE int8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
+	FUNCTION	2	gbt_int8_union (internal, internal),
+	FUNCTION	3	gbt_int8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int8_picksplit (internal, internal),
+	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
+	OPERATOR	6	<> (int8, int8) ,
+	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
+	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
+
+--
+--
+--
+-- float4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float4_ops
+DEFAULT FOR TYPE float4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
+	FUNCTION	2	gbt_float4_union (internal, internal),
+	FUNCTION	3	gbt_float4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float4_picksplit (internal, internal),
+	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
+	OPERATOR	6	<> (float4, float4) ,
+	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
+	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
+
+--
+--
+--
+-- float8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float8_ops
+DEFAULT FOR TYPE float8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
+	FUNCTION	2	gbt_float8_union (internal, internal),
+	FUNCTION	3	gbt_float8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float8_picksplit (internal, internal),
+	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
+	OPERATOR	6	<> (float8, float8) ,
+	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
+	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
+
+--
+--
+--
+-- timestamp ops
+--
+--
+--
+
+CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamp_ops
+DEFAULT FOR TYPE timestamp USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_ts_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
+	OPERATOR	6	<> (timestamp, timestamp) ,
+	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
+	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamptz_ops
+DEFAULT FOR TYPE timestamptz USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_tstz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
+	OPERATOR	6	<> (timestamptz, timestamptz) ,
+	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
+	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
+
+--
+--
+--
+-- time ops
+--
+--
+--
+
+CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_time_ops
+DEFAULT FOR TYPE time USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_time_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
+	OPERATOR	6	<> (time, time) ,
+	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
+	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
+
+
+CREATE OPERATOR CLASS gist_timetz_ops
+DEFAULT FOR TYPE timetz USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_timetz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
+	OPERATOR	6	<> (timetz, timetz) ;
+	-- no 'fetch' function, as the compress function is lossy.
+
+
+--
+--
+--
+-- date ops
+--
+--
+--
+
+CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_date_ops
+DEFAULT FOR TYPE date USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
+	FUNCTION	2	gbt_date_union (internal, internal),
+	FUNCTION	3	gbt_date_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_date_picksplit (internal, internal),
+	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
+	OPERATOR	6	<> (date, date) ,
+	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
+	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
+
+
+--
+--
+--
+-- interval ops
+--
+--
+--
+
+CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_interval_ops
+DEFAULT FOR TYPE interval USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
+	FUNCTION	2	gbt_intv_union (internal, internal),
+	FUNCTION	3	gbt_intv_compress (internal),
+	FUNCTION	4	gbt_intv_decompress (internal),
+	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_intv_picksplit (internal, internal),
+	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
+	OPERATOR	6	<> (interval, interval) ,
+	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
+	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
+
+
+--
+--
+--
+-- cash ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cash_ops
+DEFAULT FOR TYPE money USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
+	FUNCTION	2	gbt_cash_union (internal, internal),
+	FUNCTION	3	gbt_cash_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_cash_picksplit (internal, internal),
+	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
+	OPERATOR	6	<> (money, money) ,
+	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
+	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
+	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
+
+
+--
+--
+--
+-- macaddr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_macaddr_ops
+DEFAULT FOR TYPE macaddr USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
+	FUNCTION	2	gbt_macad_union (internal, internal),
+	FUNCTION	3	gbt_macad_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_macad_picksplit (internal, internal),
+	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
+	OPERATOR	6	<> (macaddr, macaddr) ,
+	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
+
+
+--
+--
+--
+-- text/ bpchar ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_text_ops
+DEFAULT FOR TYPE text USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_text_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
+	OPERATOR	6	<> (text, text) ,
+	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
+
+
+---- Create the operator class
+CREATE OPERATOR CLASS gist_bpchar_ops
+DEFAULT FOR TYPE bpchar USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_bpchar_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
+	OPERATOR	6	<> (bpchar, bpchar) ,
+	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
+
+--
+--
+-- bytea ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bytea_ops
+DEFAULT FOR TYPE bytea USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
+	FUNCTION	2	gbt_bytea_union (internal, internal),
+	FUNCTION	3	gbt_bytea_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
+	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
+	OPERATOR	6	<> (bytea, bytea) ,
+	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- numeric ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_numeric_ops
+DEFAULT FOR TYPE numeric USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
+	FUNCTION	2	gbt_numeric_union (internal, internal),
+	FUNCTION	3	gbt_numeric_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
+	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
+	OPERATOR	6	<> (numeric, numeric) ,
+	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
+
+
+--
+--
+-- bit ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bit_ops
+DEFAULT FOR TYPE bit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
+	OPERATOR	6	<> (bit, bit) ,
+	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
+
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_vbit_ops
+DEFAULT FOR TYPE varbit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
+	OPERATOR	6	<> (varbit, varbit) ,
+	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- inet/cidr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_inet_ops
+DEFAULT FOR TYPE inet USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
+	OPERATOR	6	<>  (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cidr_ops
+DEFAULT FOR TYPE cidr USING gist
+AS
+	OPERATOR	1	<  (inet, inet)  ,
+	OPERATOR	2	<= (inet, inet)  ,
+	OPERATOR	3	=  (inet, inet)  ,
+	OPERATOR	4	>= (inet, inet)  ,
+	OPERATOR	5	>  (inet, inet)  ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
+	OPERATOR	6	<> (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+--
+--
+--
+-- uuid ops
+--
+--
+---- define the GiST support methods
+CREATE FUNCTION gbt_uuid_consistent(internal,uuid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_uuid_ops
+DEFAULT FOR TYPE uuid USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_uuid_consistent (internal, uuid, int2, oid, internal),
+	FUNCTION	2	gbt_uuid_union (internal, internal),
+	FUNCTION	3	gbt_uuid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_uuid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_uuid_picksplit (internal, internal),
+	FUNCTION	7	gbt_uuid_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+-- These are "loose" in the opfamily for consistency with the rest of btree_gist
+ALTER OPERATOR FAMILY gist_uuid_ops USING gist ADD
+	OPERATOR	6	<>  (uuid, uuid) ,
+	FUNCTION	9 (uuid, uuid) gbt_uuid_fetch (internal) ;
+
+
+-- Add support for indexing macaddr8 columns
+
+-- define the GiST support methods
+CREATE FUNCTION gbt_macad8_consistent(internal,macaddr8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_macaddr8_ops
+DEFAULT FOR TYPE macaddr8 USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_macad8_consistent (internal, macaddr8, int2, oid, internal),
+	FUNCTION	2	gbt_macad8_union (internal, internal),
+	FUNCTION	3	gbt_macad8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_macad8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_macad8_picksplit (internal, internal),
+	FUNCTION	7	gbt_macad8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_macaddr8_ops USING gist ADD
+	OPERATOR	6	<> (macaddr8, macaddr8) ,
+	FUNCTION	9 (macaddr8, macaddr8) gbt_macad8_fetch (internal);
+
+--
+--
+--
+-- enum ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_enum_consistent(internal,anyenum,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_enum_ops
+DEFAULT FOR TYPE anyenum USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_enum_consistent (internal, anyenum, int2, oid, internal),
+	FUNCTION	2	gbt_enum_union (internal, internal),
+	FUNCTION	3	gbt_enum_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_enum_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_enum_picksplit (internal, internal),
+	FUNCTION	7	gbt_enum_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_enum_ops USING gist ADD
+	OPERATOR	6	<> (anyenum, anyenum) ,
+	FUNCTION	9 (anyenum, anyenum) gbt_enum_fetch (internal) ;
diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control
index 81c8509..9ced3bc 100644
--- a/contrib/btree_gist/btree_gist.control
+++ b/contrib/btree_gist/btree_gist.control
@@ -1,5 +1,5 @@
 # btree_gist extension
 comment = 'support for indexing common datatypes in GiST'
-default_version = '1.5'
+default_version = '1.6'
 module_pathname = '$libdir/btree_gist'
 relocatable = true
diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c
index 7674e2d..3c402c0 100644
--- a/contrib/btree_gist/btree_int2.c
+++ b/contrib/btree_gist/btree_int2.c
@@ -90,26 +90,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(int2_dist);
-Datum
-int2_dist(PG_FUNCTION_ARGS)
-{
-	int16		a = PG_GETARG_INT16(0);
-	int16		b = PG_GETARG_INT16(1);
-	int16		r;
-	int16		ra;
-
-	if (pg_sub_s16_overflow(a, b, &r) ||
-		r == PG_INT16_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("smallint out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT16(ra);
-}
-
 
 /**************************************************
  * int16 ops
diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c
index 80005ab..a4f1968 100644
--- a/contrib/btree_gist/btree_int4.c
+++ b/contrib/btree_gist/btree_int4.c
@@ -91,27 +91,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(int4_dist);
-Datum
-int4_dist(PG_FUNCTION_ARGS)
-{
-	int32		a = PG_GETARG_INT32(0);
-	int32		b = PG_GETARG_INT32(1);
-	int32		r;
-	int32		ra;
-
-	if (pg_sub_s32_overflow(a, b, &r) ||
-		r == PG_INT32_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("integer out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT32(ra);
-}
-
-
 /**************************************************
  * int32 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c
index b0fd3e1..efec64c 100644
--- a/contrib/btree_gist/btree_int8.c
+++ b/contrib/btree_gist/btree_int8.c
@@ -91,27 +91,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(int8_dist);
-Datum
-int8_dist(PG_FUNCTION_ARGS)
-{
-	int64		a = PG_GETARG_INT64(0);
-	int64		b = PG_GETARG_INT64(1);
-	int64		r;
-	int64		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("bigint out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT64(ra);
-}
-
-
 /**************************************************
  * int64 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_interval.c b/contrib/btree_gist/btree_interval.c
index 3a527a7..b244fa4 100644
--- a/contrib/btree_gist/btree_interval.c
+++ b/contrib/btree_gist/btree_interval.c
@@ -110,32 +110,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-Interval *
-abs_interval(Interval *a)
-{
-	static Interval zero = {0, 0, 0};
-
-	if (DatumGetBool(DirectFunctionCall2(interval_lt,
-										 IntervalPGetDatum(a),
-										 IntervalPGetDatum(&zero))))
-		a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
-												  IntervalPGetDatum(a)));
-
-	return a;
-}
-
-PG_FUNCTION_INFO_V1(interval_dist);
-Datum
-interval_dist(PG_FUNCTION_ARGS)
-{
-	Datum		diff = DirectFunctionCall2(interval_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
-}
-
-
 /**************************************************
  * interval ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_oid.c b/contrib/btree_gist/btree_oid.c
index 00e7019..3a0bd73 100644
--- a/contrib/btree_gist/btree_oid.c
+++ b/contrib/btree_gist/btree_oid.c
@@ -96,22 +96,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(oid_dist);
-Datum
-oid_dist(PG_FUNCTION_ARGS)
-{
-	Oid			a = PG_GETARG_OID(0);
-	Oid			b = PG_GETARG_OID(1);
-	Oid			res;
-
-	if (a < b)
-		res = b - a;
-	else
-		res = a - b;
-	PG_RETURN_OID(res);
-}
-
-
 /**************************************************
  * Oid ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_time.c b/contrib/btree_gist/btree_time.c
index 90cf655..b6e7e4a 100644
--- a/contrib/btree_gist/btree_time.c
+++ b/contrib/btree_gist/btree_time.c
@@ -137,18 +137,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(time_dist);
-Datum
-time_dist(PG_FUNCTION_ARGS)
-{
-	Datum		diff = DirectFunctionCall2(time_mi_time,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
-}
-
-
 /**************************************************
  * time ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c
index 49d1849..b0271a6 100644
--- a/contrib/btree_gist/btree_ts.c
+++ b/contrib/btree_gist/btree_ts.c
@@ -142,55 +142,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(ts_dist);
-Datum
-ts_dist(PG_FUNCTION_ARGS)
-{
-	Timestamp	a = PG_GETARG_TIMESTAMP(0);
-	Timestamp	b = PG_GETARG_TIMESTAMP(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-	else
-		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-												  PG_GETARG_DATUM(0),
-												  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
-}
-
-PG_FUNCTION_INFO_V1(tstz_dist);
-Datum
-tstz_dist(PG_FUNCTION_ARGS)
-{
-	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
-	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-
-	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-											  PG_GETARG_DATUM(0),
-											  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
-}
-
-
 /**************************************************
  * timestamp ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_utils_num.h b/contrib/btree_gist/btree_utils_num.h
index d7945f8..794d92b 100644
--- a/contrib/btree_gist/btree_utils_num.h
+++ b/contrib/btree_gist/btree_utils_num.h
@@ -107,8 +107,6 @@ do {															\
 } while(0)
 
 
-extern Interval *abs_interval(Interval *a);
-
 extern bool gbt_num_consistent(const GBT_NUMKEY_R *key, const void *query,
 				   const StrategyNumber *strategy, bool is_leaf,
 				   const gbtree_ninfo *tinfo, FmgrInfo *flinfo);
0007-Add-regression-tests-for-kNN-btree-v03.patchtext/x-patch; name=0007-Add-regression-tests-for-kNN-btree-v03.patchDownload
diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index 0bd48dc..a8ed9da 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -179,3 +179,782 @@ select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass;
  {vacuum_cleanup_index_scale_factor=70.0}
 (1 row)
 
+---
+--- Test B-tree distance ordering
+---
+SET enable_bitmapscan = OFF;
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+          QUERY PLAN          
+------------------------------
+ Sort
+   Sort Key: ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+                   QUERY PLAN                   
+------------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((random <-> 1))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)))
+   Order By: (random <-> 4000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+                                                                                               QUERY PLAN                                                                                               
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)) AND (random = ANY ('{1809552,1919087,2321799,2648497,3000193,3013326,4157193,4488889,5257716,5593978,NULL}'::integer[])))
+   Order By: (random <-> 3000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+ seqno | random  
+-------+---------
+  5380 | 3000193
+  6262 | 3013326
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+  6448 | 4157193
+   210 | 1809552
+  4408 | 4488889
+  6320 | 5257716
+  1836 | 5593978
+(10 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+-- test parallel KNN scan
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+RESET enable_indexscan;
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, 1000000) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+EXPLAIN (COSTS OFF)
+SELECT i FROM bt_knn_test WHERE i > 8000000;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Gather
+   Workers Planned: 4
+   ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+         Index Cond: (i > 8000000)
+(4 rows)
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> 4000003) AS n, i * 10 AS i
+	FROM generate_series(1, 1000000) i;
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (t2.n = t1.n)
+   Join Filter: (t1.i <> t2.i)
+   CTE bt_knn_test1
+     ->  WindowAgg
+           ->  Gather Merge
+                 Workers Planned: 4
+                 ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                       Order By: (i <-> 4000003)
+   ->  Seq Scan on bt_knn_test2 t2
+   ->  Hash
+         ->  CTE Scan on bt_knn_test1 t1
+(12 rows)
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+DROP TABLE bt_knn_test;
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, 100000) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (t2.n = t1.n)
+   Join Filter: (t1.i <> t2.i)
+   CTE t1
+     ->  WindowAgg
+           ->  Gather Merge
+                 Workers Planned: 4
+                 ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                       Index Cond: (i = ANY ('{3,4,7,8,2}'::integer[]))
+                       Order By: (i <-> 4)
+   CTE t2
+     ->  Nested Loop
+           ->  Function Scan on generate_series i
+           ->  Function Scan on generate_series j
+   ->  CTE Scan on t2
+   ->  Hash
+         ->  CTE Scan on t1
+(17 rows)
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+ROLLBACK;
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: (ROW(thousand, tenthous) >= ROW(997, 5000))
+   Order By: (thousand <-> 998)
+(3 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+        1 |     9001
+        1 |     8001
+        1 |     7001
+        1 |     6001
+        1 |     5001
+        1 |     4001
+        1 |     3001
+        1 |     2001
+        1 |     1001
+        1 |        1
+        0 |     9000
+        0 |     8000
+        0 |     7000
+        0 |     6000
+        0 |     5000
+        0 |     4000
+        0 |     3000
+        0 |     2000
+        0 |     1000
+        0 |        0
+          |        1
+          |        2
+          |        3
+(33 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: ((thousand > 100) AND (thousand < 800) AND (thousand = ANY ('{0,123,234,345,456,678,901,NULL}'::smallint[])))
+   Order By: (thousand <-> '300'::bigint)
+(3 rows)
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+ thousand | tenthous 
+----------+----------
+      345 |      345
+      345 |     1345
+      345 |     2345
+      345 |     3345
+      345 |     4345
+      345 |     5345
+      345 |     6345
+      345 |     7345
+      345 |     8345
+      345 |     9345
+      234 |      234
+      234 |     1234
+      234 |     2234
+      234 |     3234
+      234 |     4234
+      234 |     5234
+      234 |     6234
+      234 |     7234
+      234 |     8234
+      234 |     9234
+      456 |      456
+      456 |     1456
+      456 |     2456
+      456 |     3456
+      456 |     4456
+      456 |     5456
+      456 |     6456
+      456 |     7456
+      456 |     8456
+      456 |     9456
+      123 |      123
+      123 |     1123
+      123 |     2123
+      123 |     3123
+      123 |     4123
+      123 |     5123
+      123 |     6123
+      123 |     7123
+      123 |     8123
+      123 |     9123
+      678 |      678
+      678 |     1678
+      678 |     2678
+      678 |     3678
+      678 |     4678
+      678 |     5678
+      678 |     6678
+      678 |     7678
+      678 |     8678
+      678 |     9678
+(50 rows)
+
+DROP INDEX tenk3_idx;
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+        1 |        1
+        1 |     1001
+        1 |     2001
+        1 |     3001
+        1 |     4001
+        1 |     5001
+        1 |     6001
+        1 |     7001
+        1 |     8001
+        1 |     9001
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+        0 |        0
+        0 |     1000
+        0 |     2000
+        0 |     3000
+        0 |     4000
+        0 |     5000
+        0 |     6000
+        0 |     7000
+        0 |     8000
+        0 |     9000
+          |        3
+          |        2
+          |        1
+(33 rows)
+
+DROP INDEX tenk3_idx;
+DROP TABLE tenk3;
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |     ?column?      
+--------------------------+-------------------
+ Wed May 03 00:00:00 2017 | @ 2 days
+ Wed May 03 01:00:00 2017 | @ 2 days 1 hour
+ Wed May 03 02:00:00 2017 | @ 2 days 2 hours
+ Wed May 03 03:00:00 2017 | @ 2 days 3 hours
+ Wed May 03 04:00:00 2017 | @ 2 days 4 hours
+ Wed May 03 05:00:00 2017 | @ 2 days 5 hours
+ Wed May 03 06:00:00 2017 | @ 2 days 6 hours
+ Wed May 03 07:00:00 2017 | @ 2 days 7 hours
+ Wed May 03 08:00:00 2017 | @ 2 days 8 hours
+ Wed May 03 09:00:00 2017 | @ 2 days 9 hours
+ Wed May 03 10:00:00 2017 | @ 2 days 10 hours
+ Wed May 03 11:00:00 2017 | @ 2 days 11 hours
+ Wed May 03 12:00:00 2017 | @ 2 days 12 hours
+ Wed May 03 13:00:00 2017 | @ 2 days 13 hours
+ Wed May 03 14:00:00 2017 | @ 2 days 14 hours
+ Wed May 03 15:00:00 2017 | @ 2 days 15 hours
+ Wed May 03 16:00:00 2017 | @ 2 days 16 hours
+ Wed May 03 17:00:00 2017 | @ 2 days 17 hours
+ Wed May 03 18:00:00 2017 | @ 2 days 18 hours
+ Wed May 03 19:00:00 2017 | @ 2 days 19 hours
+(20 rows)
+
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |  ?column?  
+--------------------------+------------
+ Mon Jan 01 00:00:00 2018 | @ 0
+ Mon Jan 01 01:00:00 2018 | @ 1 hour
+ Sun Dec 31 23:00:00 2017 | @ 1 hour
+ Mon Jan 01 02:00:00 2018 | @ 2 hours
+ Sun Dec 31 22:00:00 2017 | @ 2 hours
+ Mon Jan 01 03:00:00 2018 | @ 3 hours
+ Sun Dec 31 21:00:00 2017 | @ 3 hours
+ Mon Jan 01 04:00:00 2018 | @ 4 hours
+ Sun Dec 31 20:00:00 2017 | @ 4 hours
+ Mon Jan 01 05:00:00 2018 | @ 5 hours
+ Sun Dec 31 19:00:00 2017 | @ 5 hours
+ Mon Jan 01 06:00:00 2018 | @ 6 hours
+ Sun Dec 31 18:00:00 2017 | @ 6 hours
+ Mon Jan 01 07:00:00 2018 | @ 7 hours
+ Sun Dec 31 17:00:00 2017 | @ 7 hours
+ Mon Jan 01 08:00:00 2018 | @ 8 hours
+ Sun Dec 31 16:00:00 2017 | @ 8 hours
+ Mon Jan 01 09:00:00 2018 | @ 9 hours
+ Sun Dec 31 15:00:00 2017 | @ 9 hours
+ Mon Jan 01 10:00:00 2018 | @ 10 hours
+(20 rows)
+
+DROP TABLE knn_btree_ts;
+RESET enable_bitmapscan;
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 21171f7..307f2f5 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -111,3 +111,235 @@ create index btree_idx_err on btree_test(a) with (vacuum_cleanup_index_scale_fac
 -- Simple ALTER INDEX
 alter index btree_idx1 set (vacuum_cleanup_index_scale_factor = 70.0);
 select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass;
+
+---
+--- Test B-tree distance ordering
+---
+
+SET enable_bitmapscan = OFF;
+
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+-- test parallel KNN scan
+
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+
+RESET enable_indexscan;
+
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, 1000000) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+EXPLAIN (COSTS OFF)
+SELECT i FROM bt_knn_test WHERE i > 8000000;
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> 4000003) AS n, i * 10 AS i
+	FROM generate_series(1, 1000000) i;
+
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+DROP TABLE bt_knn_test;
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, 100000) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+
+ROLLBACK;
+
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+
+
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+DROP INDEX tenk3_idx;
+
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+DROP INDEX tenk3_idx;
+
+DROP TABLE tenk3;
+
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+
+DROP TABLE knn_btree_ts;
+
+RESET enable_bitmapscan;
#11Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Nikita Glukhov (#10)
Re: [PATCH] kNN for btree

On Wed, Sep 26, 2018 at 5:41 PM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

Attached 3rd version of the patches rebased onto the current master.

Changes from the previous version:
- Added support of INCLUDE columns to get_index_column_opclass() (1st patch).
- Added parallel kNN scan support.
- amcanorderbyop() was transformed into ammatchorderby() which takes a List of
PathKeys and checks each of them with new function match_orderbyop_pathkey()
extracted from match_pathkeys_to_index(). I think that this design can be
used in the future to support a mix of ordinary and order-by-op PathKeys,
but I am not sure.

Hi,

Unfortunately, the patch has some conflicts, could you rebase it? In the
meantime I'll move it to the next CF, hoping to have more reviewers for this
item.

#12Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Dmitry Dolgov (#11)
7 attachment(s)
Re: [PATCH] kNN for btree

On 29.11.2018 18:24, Dmitry Dolgov wrote:

On Wed, Sep 26, 2018 at 5:41 PM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

Attached 3rd version of the patches rebased onto the current master.

Changes from the previous version:
- Added support of INCLUDE columns to get_index_column_opclass() (1st patch).
- Added parallel kNN scan support.
- amcanorderbyop() was transformed into ammatchorderby() which takes a List of
PathKeys and checks each of them with new function match_orderbyop_pathkey()
extracted from match_pathkeys_to_index(). I think that this design can be
used in the future to support a mix of ordinary and order-by-op PathKeys,
but I am not sure.

Hi,

Unfortunately, the patch has some conflicts, could you rebase it? In the
meantime I'll move it to the next CF, hoping to have more reviewers for this
item.

Attached 4th version of the patches rebased onto the current master.

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Fix-get_index_column_opclass-v04.patchtext/x-patch; name=0001-Fix-get_index_column_opclass-v04.patchDownload
From f26de4505d217a40bd4dd88cb4de71c82bd40ab0 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 30 Nov 2018 14:56:54 +0300
Subject: [PATCH 1/7] Fix get_index_column_opclass

---
 src/backend/utils/cache/lsyscache.c | 23 +++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)

diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 7a263cc..0ae7639 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -3112,9 +3112,6 @@ get_index_column_opclass(Oid index_oid, int attno)
 {
 	HeapTuple	tuple;
 	Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY;
-	Datum		datum;
-	bool		isnull;
-	oidvector  *indclass;
 	Oid			opclass;
 
 	/* First we need to know the column's opclass. */
@@ -3128,12 +3125,22 @@ get_index_column_opclass(Oid index_oid, int attno)
 	/* caller is supposed to guarantee this */
 	Assert(attno > 0 && attno <= rd_index->indnatts);
 
-	datum = SysCacheGetAttr(INDEXRELID, tuple,
-							Anum_pg_index_indclass, &isnull);
-	Assert(!isnull);
+	if (attno >= 1 && attno <= rd_index->indnkeyatts)
+	{
+		oidvector  *indclass;
+		bool		isnull;
+		Datum		datum = SysCacheGetAttr(INDEXRELID, tuple,
+											Anum_pg_index_indclass,
+											&isnull);
+		Assert(!isnull);
 
-	indclass = ((oidvector *) DatumGetPointer(datum));
-	opclass = indclass->values[attno - 1];
+		indclass = ((oidvector *) DatumGetPointer(datum));
+		opclass = indclass->values[attno - 1];
+	}
+	else
+	{
+		opclass = InvalidOid;
+	}
 
 	ReleaseSysCache(tuple);
 
-- 
2.7.4

0002-Introduce-ammatchorderby-function-v04.patchtext/x-patch; name=0002-Introduce-ammatchorderby-function-v04.patchDownload
From 0f6e09819203e7b5d7fea224a7173b9fde2014b6 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 30 Nov 2018 14:56:54 +0300
Subject: [PATCH 2/7] Introduce ammatchorderby function

---
 contrib/bloom/blutils.c               |   2 +-
 src/backend/access/brin/brin.c        |   2 +-
 src/backend/access/gin/ginutil.c      |   2 +-
 src/backend/access/gist/gist.c        |   3 +-
 src/backend/access/hash/hash.c        |   2 +-
 src/backend/access/nbtree/nbtree.c    |   2 +-
 src/backend/access/spgist/spgutils.c  |   6 +-
 src/backend/commands/opclasscmds.c    |   2 +-
 src/backend/executor/nodeIndexscan.c  |   4 +-
 src/backend/optimizer/path/indxpath.c | 192 ++++++++++++++++++++--------------
 src/backend/optimizer/util/plancat.c  |   2 +-
 src/backend/utils/adt/amutils.c       |   2 +-
 src/include/access/amapi.h            |  14 ++-
 src/include/nodes/plannodes.h         |   2 +-
 src/include/nodes/relation.h          |   8 +-
 src/include/optimizer/paths.h         |   4 +
 16 files changed, 148 insertions(+), 101 deletions(-)

diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 6b2b9e3..71636ae 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -109,7 +109,6 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BLOOM_NSTRATEGIES;
 	amroutine->amsupport = BLOOM_NPROC;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -143,6 +142,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index e95fbbc..ffb6de0 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -86,7 +86,6 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = BRIN_LAST_OPTIONAL_PROCNUM;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -120,6 +119,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index d7696a1..0fe76d6 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -41,7 +41,6 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GINNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -75,6 +74,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 8a42eff..f60bb45 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -18,6 +18,7 @@
 #include "access/gistscan.h"
 #include "catalog/pg_collation.h"
 #include "miscadmin.h"
+#include "optimizer/paths.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "nodes/execnodes.h"
@@ -63,7 +64,6 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GISTNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -97,6 +97,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = match_orderbyop_pathkeys;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0002df3..39f7f45 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -59,7 +59,6 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = HTMaxStrategyNumber;
 	amroutine->amsupport = HASHNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -93,6 +92,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index e8725fb..3e47c37 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -110,7 +110,6 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BTMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = true;
 	amroutine->amcanmulticol = true;
@@ -144,6 +143,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = btestimateparallelscan;
 	amroutine->aminitparallelscan = btinitparallelscan;
 	amroutine->amparallelrescan = btparallelrescan;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 9919e6f..a843650 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -32,10 +32,6 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
-extern Expr *spgcanorderbyop(IndexOptInfo *index,
-				PathKey *pathkey, int pathkeyno,
-				Expr *orderby_clause, int *indexcol_p);
-
 /*
  * SP-GiST handler function: return IndexAmRoutine with access method parameters
  * and callbacks.
@@ -48,7 +44,6 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = SPGISTNProc;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -82,6 +77,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = match_orderbyop_pathkeys;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 93ef3bd..cd1c3f8 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -1098,7 +1098,7 @@ assignOperTypes(OpFamilyMember *member, Oid amoid, Oid typeoid)
 		 */
 		IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid, false);
 
-		if (!amroutine->amcanorderbyop)
+		if (!amroutine->ammatchorderby)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("access method \"%s\" does not support ordering operators",
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index f209173..909d4d6 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -199,7 +199,7 @@ IndexNextWithReorder(IndexScanState *node)
 	 * with just Asserting here because the system will not try to run the
 	 * plan backwards if ExecSupportsBackwardScan() says it won't work.
 	 * Currently, that is guaranteed because no index AMs support both
-	 * amcanorderbyop and amcanbackward; if any ever do,
+	 * ammatchorderby and amcanbackward; if any ever do,
 	 * ExecSupportsBackwardScan() will need to consider indexorderbys
 	 * explicitly.
 	 */
@@ -1149,7 +1149,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
  * 5. NullTest ("indexkey IS NULL/IS NOT NULL").  We just fill in the
  * ScanKey properly.
  *
- * This code is also used to prepare ORDER BY expressions for amcanorderbyop
+ * This code is also used to prepare ORDER BY expressions for ammatchorderby
  * indexes.  The behavior is exactly the same, except that we have to look up
  * the operator differently.  Note that only cases 1 and 2 are currently
  * possible for ORDER BY.
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 5f46415..2c21eae 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -17,6 +17,7 @@
 
 #include <math.h>
 
+#include "access/amapi.h"
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_am.h"
@@ -155,6 +156,10 @@ static void match_clause_to_index(IndexOptInfo *index,
 static bool match_clause_to_indexcol(IndexOptInfo *index,
 						 int indexcol,
 						 RestrictInfo *rinfo);
+static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
+							int indexcol,
+							Expr *clause,
+							Oid pk_opfamily);
 static bool is_indexable_operator(Oid expr_op, Oid opfamily,
 					  bool indexkey_on_left);
 static bool match_rowcompare_to_indexcol(IndexOptInfo *index,
@@ -165,8 +170,6 @@ static bool match_rowcompare_to_indexcol(IndexOptInfo *index,
 static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 						List **orderby_clauses_p,
 						List **clause_columns_p);
-static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
-							int indexcol, Expr *clause, Oid pk_opfamily);
 static bool ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
 						   EquivalenceClass *ec, EquivalenceMember *em,
 						   void *arg);
@@ -999,7 +1002,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		orderbyclauses = NIL;
 		orderbyclausecols = NIL;
 	}
-	else if (index->amcanorderbyop && pathkeys_possibly_useful)
+	else if (index->ammatchorderby && pathkeys_possibly_useful)
 	{
 		/* see if we can generate ordering operators for query_pathkeys */
 		match_pathkeys_to_index(index, root->query_pathkeys,
@@ -2575,6 +2578,99 @@ match_rowcompare_to_indexcol(IndexOptInfo *index,
 	return false;
 }
 
+Expr *
+match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey, int *indexcol_p)
+{
+	ListCell   *lc;
+
+	/* Pathkey must request default sort order for the target opfamily */
+	if (pathkey->pk_strategy != BTLessStrategyNumber ||
+		pathkey->pk_nulls_first)
+		return NULL;
+
+	/* If eclass is volatile, no hope of using an indexscan */
+	if (pathkey->pk_eclass->ec_has_volatile)
+		return NULL;
+
+	/*
+	 * Try to match eclass member expression(s) to index.  Note that child
+	 * EC members are considered, but only when they belong to the target
+	 * relation.  (Unlike regular members, the same expression could be a
+	 * child member of more than one EC.  Therefore, the same index could
+	 * be considered to match more than one pathkey list, which is OK
+	 * here.  See also get_eclass_for_sort_expr.)
+	 */
+	foreach(lc, pathkey->pk_eclass->ec_members)
+	{
+		EquivalenceMember *member = castNode(EquivalenceMember, lfirst(lc));
+		int			indexcol;
+		int			indexcol_min;
+		int			indexcol_max;
+
+		/* No possibility of match if it references other relations */
+		if (!bms_equal(member->em_relids, index->rel->relids))
+			continue;
+
+		/* If *indexcol_p is non-negative then try to match only to it */
+		if (*indexcol_p >= 0)
+		{
+			indexcol_min = *indexcol_p;
+			indexcol_max = *indexcol_p + 1;
+		}
+		else	/* try to match all columns */
+		{
+			indexcol_min = 0;
+			indexcol_max = index->ncolumns;
+		}
+
+		/*
+		 * We allow any column of the GiST index to match each pathkey;
+		 * they don't have to match left-to-right as you might expect.
+		 */
+		for (indexcol = indexcol_min; indexcol < indexcol_max; indexcol++)
+		{
+			Expr	   *expr = match_clause_to_ordering_op(index,
+														   indexcol,
+														   member->em_expr,
+														   pathkey->pk_opfamily);
+			if (expr)
+			{
+				*indexcol_p = indexcol;
+				return expr;	/* don't want to look at remaining members */
+			}
+		}
+	}
+
+	return NULL;
+}
+
+bool
+match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
+						 List **orderby_clauses_p, List **clause_columns_p)
+{
+	ListCell   *lc;
+
+	foreach(lc, pathkeys)
+	{
+		PathKey	   *pathkey = castNode(PathKey, lfirst(lc));
+		Expr	   *expr;
+		int			indexcol = -1;	/* match all index columns */
+
+		expr = match_orderbyop_pathkey(index, pathkey, &indexcol);
+
+		/*
+		 * Note: for any failure to match, we just return NIL immediately.
+		 * There is no value in matching just some of the pathkeys.
+		 */
+		if (!expr)
+			return false;
+
+		*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+		*clause_columns_p = lappend_int(*clause_columns_p, indexcol);
+	}
+
+	return true;	/* success */
+}
 
 /****************************************************************************
  *				----  ROUTINES TO CHECK ORDERING OPERATORS	----
@@ -2600,86 +2696,24 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 {
 	List	   *orderby_clauses = NIL;
 	List	   *clause_columns = NIL;
-	ListCell   *lc1;
+	ammatchorderby_function ammatchorderby =
+			(ammatchorderby_function) index->ammatchorderby;
 
-	*orderby_clauses_p = NIL;	/* set default results */
-	*clause_columns_p = NIL;
-
-	/* Only indexes with the amcanorderbyop property are interesting here */
-	if (!index->amcanorderbyop)
-		return;
-
-	foreach(lc1, pathkeys)
+	/* Only indexes with the ammatchorderby function are interesting here */
+	if (ammatchorderby &&
+		ammatchorderby(index, pathkeys, &orderby_clauses, &clause_columns))
 	{
-		PathKey    *pathkey = (PathKey *) lfirst(lc1);
-		bool		found = false;
-		ListCell   *lc2;
-
-		/*
-		 * Note: for any failure to match, we just return NIL immediately.
-		 * There is no value in matching just some of the pathkeys.
-		 */
-
-		/* Pathkey must request default sort order for the target opfamily */
-		if (pathkey->pk_strategy != BTLessStrategyNumber ||
-			pathkey->pk_nulls_first)
-			return;
+		Assert(list_length(pathkeys) == list_length(orderby_clauses));
+		Assert(list_length(pathkeys) == list_length(clause_columns));
 
-		/* If eclass is volatile, no hope of using an indexscan */
-		if (pathkey->pk_eclass->ec_has_volatile)
-			return;
-
-		/*
-		 * Try to match eclass member expression(s) to index.  Note that child
-		 * EC members are considered, but only when they belong to the target
-		 * relation.  (Unlike regular members, the same expression could be a
-		 * child member of more than one EC.  Therefore, the same index could
-		 * be considered to match more than one pathkey list, which is OK
-		 * here.  See also get_eclass_for_sort_expr.)
-		 */
-		foreach(lc2, pathkey->pk_eclass->ec_members)
-		{
-			EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
-			int			indexcol;
-
-			/* No possibility of match if it references other relations */
-			if (!bms_equal(member->em_relids, index->rel->relids))
-				continue;
-
-			/*
-			 * We allow any column of the index to match each pathkey; they
-			 * don't have to match left-to-right as you might expect.  This is
-			 * correct for GiST, which is the sole existing AM supporting
-			 * amcanorderbyop.  We might need different logic in future for
-			 * other implementations.
-			 */
-			for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
-			{
-				Expr	   *expr;
-
-				expr = match_clause_to_ordering_op(index,
-												   indexcol,
-												   member->em_expr,
-												   pathkey->pk_opfamily);
-				if (expr)
-				{
-					orderby_clauses = lappend(orderby_clauses, expr);
-					clause_columns = lappend_int(clause_columns, indexcol);
-					found = true;
-					break;
-				}
-			}
-
-			if (found)			/* don't want to look at remaining members */
-				break;
-		}
-
-		if (!found)				/* fail if no match for this pathkey */
-			return;
+		*orderby_clauses_p = orderby_clauses;	/* success! */
+		*clause_columns_p = clause_columns;
+	}
+	else
+	{
+		*orderby_clauses_p = NIL;	/* set default results */
+		*clause_columns_p = NIL;
 	}
-
-	*orderby_clauses_p = orderby_clauses;	/* success! */
-	*clause_columns_p = clause_columns;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a570ac0..bf3912d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -265,7 +265,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 			/* We copy just the fields we need, not all of rd_amroutine */
 			amroutine = indexRelation->rd_amroutine;
-			info->amcanorderbyop = amroutine->amcanorderbyop;
+			info->ammatchorderby = amroutine->ammatchorderby;
 			info->amoptionalkey = amroutine->amoptionalkey;
 			info->amsearcharray = amroutine->amsearcharray;
 			info->amsearchnulls = amroutine->amsearchnulls;
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c
index dc04148..02879e3 100644
--- a/src/backend/utils/adt/amutils.c
+++ b/src/backend/utils/adt/amutils.c
@@ -298,7 +298,7 @@ indexam_property(FunctionCallInfo fcinfo,
 				 * a nonkey column, and null otherwise (meaning we don't
 				 * know).
 				 */
-				if (!iskey || !routine->amcanorderbyop)
+				if (!iskey || !routine->ammatchorderby)
 				{
 					res = false;
 					isnull = false;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 14526a6..a293b38 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -21,6 +21,9 @@
  */
 struct PlannerInfo;
 struct IndexPath;
+struct IndexOptInfo;
+struct PathKey;
+struct Expr;
 
 /* Likewise, this file shouldn't depend on execnodes.h. */
 struct IndexInfo;
@@ -140,6 +143,12 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/* does AM support ORDER BY result of an operator on indexed column? */
+typedef bool (*ammatchorderby_function) (struct IndexOptInfo *index,
+										 List *pathkeys,
+										 List **orderby_clauses_p,
+										 List **clause_columns_p);
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -170,8 +179,6 @@ typedef struct IndexAmRoutine
 	uint16		amsupport;
 	/* does AM support ORDER BY indexed column's value? */
 	bool		amcanorder;
-	/* does AM support ORDER BY result of an operator on indexed column? */
-	bool		amcanorderbyop;
 	/* does AM support backward scanning? */
 	bool		amcanbackward;
 	/* does AM support UNIQUE indexes? */
@@ -221,7 +228,8 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;	/* can be NULL */
 	amrestrpos_function amrestrpos; /* can be NULL */
-
+	ammatchorderby_function ammatchorderby; /* can be NULL */
+	
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
 	aminitparallelscan_function aminitparallelscan; /* can be NULL */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index f116bc2..ad9530e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -388,7 +388,7 @@ typedef struct SampleScan
  * indexorderbyops is a list of the OIDs of the operators used to sort the
  * ORDER BY expressions.  This is used together with indexorderbyorig to
  * recheck ordering at run time.  (Note that indexorderby, indexorderbyorig,
- * and indexorderbyops are used for amcanorderbyop cases, not amcanorder.)
+ * and indexorderbyops are used for ammatchorderby cases, not amcanorder.)
  *
  * indexorderdir specifies the scan ordering, for indexscans on amcanorder
  * indexes (for other indexes it should be "don't care").
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 6fd2420..0f6f07c 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -804,7 +804,6 @@ typedef struct IndexOptInfo
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
 	bool		amoptionalkey;	/* can query omit key for the first column? */
 	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
 	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
@@ -813,6 +812,11 @@ typedef struct IndexOptInfo
 	bool		amcanparallel;	/* does AM support parallel scan? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
+								/* AM order-by match function */
+	bool		(*ammatchorderby) (struct IndexOptInfo *index,
+								   List *pathkeys,
+								   List **orderby_clauses_p,
+								   List **clause_columns_p);
 } IndexOptInfo;
 
 /*
@@ -1136,7 +1140,7 @@ typedef struct Path
  * (The order of multiple quals for the same index column is unspecified.)
  *
  * 'indexorderbys', if not NIL, is a list of ORDER BY expressions that have
- * been found to be usable as ordering operators for an amcanorderbyop index.
+ * been found to be usable as ordering operators for an ammatchorderby index.
  * The list must match the path's pathkeys, ie, one expression per pathkey
  * in the same order.  These are not RestrictInfos, just bare expressions,
  * since they generally won't yield booleans.  Also, unlike the case for
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index cafde30..21677eb 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -87,6 +87,10 @@ extern Expr *adjust_rowcompare_for_index(RowCompareExpr *clause,
 							int indexcol,
 							List **indexcolnos,
 							bool *var_on_left_p);
+extern Expr *match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey,
+									 int *indexcol_p);
+extern bool match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
+						 List **orderby_clauses_p, List **clause_columns_p);
 
 /*
  * tidpath.h
-- 
2.7.4

0003-Extract-structure-BTScanState-v04.patchtext/x-patch; name=0003-Extract-structure-BTScanState-v04.patchDownload
From 46c11340c1e697eafca07a4452c94e0a63d6c3fd Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 30 Nov 2018 14:56:54 +0300
Subject: [PATCH 3/7] Extract structure BTScanState

---
 src/backend/access/nbtree/nbtree.c    | 200 ++++++++++--------
 src/backend/access/nbtree/nbtsearch.c | 379 +++++++++++++++++-----------------
 src/backend/access/nbtree/nbtutils.c  |  49 +++--
 src/include/access/nbtree.h           |  38 ++--
 4 files changed, 355 insertions(+), 311 deletions(-)

diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 3e47c37..55c7833 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -214,6 +214,7 @@ bool
 btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 	bool		res;
 
 	/* btree indexes are never lossy */
@@ -224,7 +225,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(so->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
@@ -241,7 +242,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(so->currPos))
+		if (!BTScanPosIsValid(state->currPos))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -259,11 +260,11 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 				 * trying to optimize that, so we don't detect it, but instead
 				 * just forget any excess entries.
 				 */
-				if (so->killedItems == NULL)
-					so->killedItems = (int *)
+				if (state->killedItems == NULL)
+					state->killedItems = (int *)
 						palloc(MaxIndexTuplesPerPage * sizeof(int));
-				if (so->numKilled < MaxIndexTuplesPerPage)
-					so->killedItems[so->numKilled++] = so->currPos.itemIndex;
+				if (state->numKilled < MaxIndexTuplesPerPage)
+					state->killedItems[so->state.numKilled++] = state->currPos.itemIndex;
 			}
 
 			/*
@@ -288,6 +289,7 @@ int64
 btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	int64		ntids = 0;
 	ItemPointer heapTid;
 
@@ -320,7 +322,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				 * Advance to next tuple within page.  This is the same as the
 				 * easy case in _bt_next().
 				 */
-				if (++so->currPos.itemIndex > so->currPos.lastItem)
+				if (++currPos->itemIndex > currPos->lastItem)
 				{
 					/* let _bt_next do the heavy lifting */
 					if (!_bt_next(scan, ForwardScanDirection))
@@ -328,7 +330,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				}
 
 				/* Save tuple ID, and continue scanning */
-				heapTid = &so->currPos.items[so->currPos.itemIndex].heapTid;
+				heapTid = &currPos->items[currPos->itemIndex].heapTid;
 				tbm_add_tuples(tbm, heapTid, 1, false);
 				ntids++;
 			}
@@ -356,8 +358,8 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 
 	/* allocate private workspace */
 	so = (BTScanOpaque) palloc(sizeof(BTScanOpaqueData));
-	BTScanPosInvalidate(so->currPos);
-	BTScanPosInvalidate(so->markPos);
+	BTScanPosInvalidate(so->state.currPos);
+	BTScanPosInvalidate(so->state.markPos);
 	if (scan->numberOfKeys > 0)
 		so->keyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData));
 	else
@@ -368,15 +370,15 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	so->arrayKeys = NULL;
 	so->arrayContext = NULL;
 
-	so->killedItems = NULL;		/* until needed */
-	so->numKilled = 0;
+	so->state.killedItems = NULL;		/* until needed */
+	so->state.numKilled = 0;
 
 	/*
 	 * We don't know yet whether the scan will be index-only, so we do not
 	 * allocate the tuple workspace arrays until btrescan.  However, we set up
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
-	so->currTuples = so->markTuples = NULL;
+	so->state.currTuples = so->state.markTuples = NULL;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -385,6 +387,45 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	return scan;
 }
 
+static void
+_bt_release_current_position(BTScanState state, Relation indexRelation,
+							 bool invalidate)
+{
+	/* we aren't holding any read locks, but gotta drop the pins */
+	if (BTScanPosIsValid(state->currPos))
+	{
+		/* Before leaving current page, deal with any killed items */
+		if (state->numKilled > 0)
+			_bt_killitems(state, indexRelation);
+
+		BTScanPosUnpinIfPinned(state->currPos);
+
+		if (invalidate)
+			BTScanPosInvalidate(state->currPos);
+	}
+}
+
+static void
+_bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
+{
+	/* No need to invalidate positions, if the RAM is about to be freed. */
+	_bt_release_current_position(state, scan->indexRelation, !free);
+
+	state->markItemIndex = -1;
+	BTScanPosUnpinIfPinned(state->markPos);
+
+	if (free)
+	{
+		if (state->killedItems != NULL)
+			pfree(state->killedItems);
+		if (state->currTuples != NULL)
+			pfree(state->currTuples);
+		/* markTuples should not be pfree'd (_bt_allocate_tuple_workspaces) */
+	}
+	else
+		BTScanPosInvalidate(state->markPos);
+}
+
 /*
  *	btrescan() -- rescan an index relation
  */
@@ -393,21 +434,11 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 		 ScanKey orderbys, int norderbys)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-		BTScanPosInvalidate(so->currPos);
-	}
+	_bt_release_scan_state(scan, state, false);
 
-	so->markItemIndex = -1;
-	so->arrayKeyCount = 0;
-	BTScanPosUnpinIfPinned(so->markPos);
-	BTScanPosInvalidate(so->markPos);
+	so->arrayKeyCount = 0; /* FIXME in _bt_release_scan_state */
 
 	/*
 	 * Allocate tuple workspace arrays, if needed for an index-only scan and
@@ -425,11 +456,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 	 * a SIGSEGV is not possible.  Yeah, this is ugly as sin, but it beats
 	 * adding special-case treatment for name_ops elsewhere.
 	 */
-	if (scan->xs_want_itup && so->currTuples == NULL)
-	{
-		so->currTuples = (char *) palloc(BLCKSZ * 2);
-		so->markTuples = so->currTuples + BLCKSZ;
-	}
+	if (scan->xs_want_itup && state->currTuples == NULL)
+		_bt_allocate_tuple_workspaces(state);
 
 	/*
 	 * Reset the scan keys. Note that keys ordering stuff moved to _bt_first.
@@ -453,19 +481,7 @@ btendscan(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-	}
-
-	so->markItemIndex = -1;
-	BTScanPosUnpinIfPinned(so->markPos);
-
-	/* No need to invalidate positions, the RAM is about to be freed. */
+	_bt_release_scan_state(scan, &so->state, true);
 
 	/* Release storage */
 	if (so->keyData != NULL)
@@ -473,24 +489,15 @@ btendscan(IndexScanDesc scan)
 	/* so->arrayKeyData and so->arrayKeys are in arrayContext */
 	if (so->arrayContext != NULL)
 		MemoryContextDelete(so->arrayContext);
-	if (so->killedItems != NULL)
-		pfree(so->killedItems);
-	if (so->currTuples != NULL)
-		pfree(so->currTuples);
-	/* so->markTuples should not be pfree'd, see btrescan */
+
 	pfree(so);
 }
 
-/*
- *	btmarkpos() -- save current scan position
- */
-void
-btmarkpos(IndexScanDesc scan)
+static void
+_bt_mark_current_position(BTScanState state)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-
 	/* There may be an old mark with a pin (but no lock). */
-	BTScanPosUnpinIfPinned(so->markPos);
+	BTScanPosUnpinIfPinned(state->markPos);
 
 	/*
 	 * Just record the current itemIndex.  If we later step to next page
@@ -498,32 +505,34 @@ btmarkpos(IndexScanDesc scan)
 	 * the currPos struct in markPos.  If (as often happens) the mark is moved
 	 * before we leave the page, we don't have to do that work.
 	 */
-	if (BTScanPosIsValid(so->currPos))
-		so->markItemIndex = so->currPos.itemIndex;
+	if (BTScanPosIsValid(state->currPos))
+		state->markItemIndex = state->currPos.itemIndex;
 	else
 	{
-		BTScanPosInvalidate(so->markPos);
-		so->markItemIndex = -1;
+		BTScanPosInvalidate(state->markPos);
+		state->markItemIndex = -1;
 	}
-
-	/* Also record the current positions of any array keys */
-	if (so->numArrayKeys)
-		_bt_mark_array_keys(scan);
 }
 
 /*
- *	btrestrpos() -- restore scan to last saved position
+ *	btmarkpos() -- save current scan position
  */
 void
-btrestrpos(IndexScanDesc scan)
+btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* Restore the marked positions of any array keys */
+	_bt_mark_current_position(&so->state);
+
+	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
-		_bt_restore_array_keys(scan);
+		_bt_mark_array_keys(scan);
+}
 
-	if (so->markItemIndex >= 0)
+static void
+_bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
+{
+	if (state->markItemIndex >= 0)
 	{
 		/*
 		 * The scan has never moved to a new page since the last mark.  Just
@@ -532,7 +541,7 @@ btrestrpos(IndexScanDesc scan)
 		 * NB: In this case we can't count on anything in so->markPos to be
 		 * accurate.
 		 */
-		so->currPos.itemIndex = so->markItemIndex;
+		state->currPos.itemIndex = state->markItemIndex;
 	}
 	else
 	{
@@ -542,28 +551,21 @@ btrestrpos(IndexScanDesc scan)
 		 * locks, but if we're still holding the pin for the current position,
 		 * we must drop it.
 		 */
-		if (BTScanPosIsValid(so->currPos))
-		{
-			/* Before leaving current page, deal with any killed items */
-			if (so->numKilled > 0)
-				_bt_killitems(scan);
-			BTScanPosUnpinIfPinned(so->currPos);
-		}
+		_bt_release_current_position(state, scan->indexRelation,
+									 !BTScanPosIsValid(state->markPos));
 
-		if (BTScanPosIsValid(so->markPos))
+		if (BTScanPosIsValid(state->markPos))
 		{
 			/* bump pin on mark buffer for assignment to current buffer */
-			if (BTScanPosIsPinned(so->markPos))
-				IncrBufferRefCount(so->markPos.buf);
-			memcpy(&so->currPos, &so->markPos,
+			if (BTScanPosIsPinned(state->markPos))
+				IncrBufferRefCount(state->markPos.buf);
+			memcpy(&state->currPos, &state->markPos,
 				   offsetof(BTScanPosData, items[1]) +
-				   so->markPos.lastItem * sizeof(BTScanPosItem));
-			if (so->currTuples)
-				memcpy(so->currTuples, so->markTuples,
-					   so->markPos.nextTupleOffset);
+				   state->markPos.lastItem * sizeof(BTScanPosItem));
+			if (state->currTuples)
+				memcpy(state->currTuples, state->markTuples,
+					   state->markPos.nextTupleOffset);
 		}
-		else
-			BTScanPosInvalidate(so->currPos);
 	}
 }
 
@@ -779,9 +781,10 @@ _bt_parallel_advance_array_keys(IndexScanDesc scan)
 }
 
 /*
- * _bt_vacuum_needs_cleanup() -- Checks if index needs cleanup assuming that
- *			btbulkdelete() wasn't called.
- */
+- * _bt_vacuum_needs_cleanup() -- Checks if index needs cleanup assuming that
+- *			btbulkdelete() wasn't called.
++ *	btrestrpos() -- restore scan to last saved position
+  */
 static bool
 _bt_vacuum_needs_cleanup(IndexVacuumInfo *info)
 {
@@ -844,6 +847,21 @@ _bt_vacuum_needs_cleanup(IndexVacuumInfo *info)
 }
 
 /*
+ *	btrestrpos() -- restore scan to last saved position
+ */
+void
+btrestrpos(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
+	/* Restore the marked positions of any array keys */
+	if (so->numArrayKeys)
+		_bt_restore_array_keys(scan);
+
+	_bt_restore_marked_position(scan, &so->state);
+}
+
+/*
  * Bulk deletion of all index entries pointing to a set of heap tuples.
  * The set of target tuples is specified via a callback routine that tells
  * whether any given heap tuple (identified by ItemPointer) is being deleted.
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 16223d0..8c9df32 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -25,18 +25,19 @@
 #include "utils/tqual.h"
 
 
-static bool _bt_readpage(IndexScanDesc scan, ScanDirection dir,
+static bool _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 			 OffsetNumber offnum);
-static void _bt_saveitem(BTScanOpaque so, int itemIndex,
+static void _bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup);
-static bool _bt_steppage(IndexScanDesc scan, ScanDirection dir);
-static bool _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir);
+static bool _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir);
+static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
+				 BlockNumber blkno, ScanDirection dir);
 static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
 					  ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf, Snapshot snapshot);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
-static inline void _bt_initialize_more_data(BTScanOpaque so, ScanDirection dir);
+static inline void _bt_initialize_more_data(BTScanState state, ScanDirection dir);
 
 
 /*
@@ -545,6 +546,58 @@ _bt_compare(Relation rel,
 }
 
 /*
+ * _bt_return_current_item() -- Prepare current scan state item for return.
+ *
+ * This function is used only in "return _bt_return_current_item();" statements
+ * and always returns true.
+ */
+static inline bool
+_bt_return_current_item(IndexScanDesc scan, BTScanState state)
+{
+	BTScanPosItem *currItem = &state->currPos.items[state->currPos.itemIndex];
+
+	scan->xs_ctup.t_self = currItem->heapTid;
+
+	if (scan->xs_want_itup)
+		scan->xs_itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+
+	return true;
+}
+
+/*
+ * _bt_load_first_page() -- Load data from the first page of the scan.
+ *
+ * Caller must have pinned and read-locked state->currPos.buf.
+ *
+ * On success exit, state->currPos is updated to contain data from the next
+ * interesting page.  For success on a scan using a non-MVCC snapshot we hold
+ * a pin, but not a read lock, on that page.  If we do not hold the pin, we
+ * set state->currPos.buf to InvalidBuffer.  We return true to indicate success.
+ *
+ * If there are no more matching records in the given direction at all,
+ * we drop all locks and pins, set state->currPos.buf to InvalidBuffer,
+ * and return false.
+ */
+static bool
+_bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+					OffsetNumber offnum)
+{
+	if (!_bt_readpage(scan, state, dir, offnum))
+	{
+		/*
+		 * There's no actually-matching data on this page.  Try to advance to
+		 * the next page.  Return false if there's no matching data at all.
+		 */
+		LockBuffer(state->currPos.buf, BUFFER_LOCK_UNLOCK);
+		return _bt_steppage(scan, state, dir);
+	}
+
+	/* Drop the lock, and maybe the pin, on the current page */
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
+	return true;
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -569,6 +622,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	BTStack		stack;
 	OffsetNumber offnum;
@@ -582,10 +636,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	int			i;
 	bool		status = true;
 	StrategyNumber strat_total;
-	BTScanPosItem *currItem;
 	BlockNumber blkno;
 
-	Assert(!BTScanPosIsValid(so->currPos));
+	Assert(!BTScanPosIsValid(*currPos));
 
 	pgstat_count_index_scan(rel);
 
@@ -1076,7 +1129,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 * their scan
 		 */
 		_bt_parallel_done(scan);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 
 		return false;
 	}
@@ -1084,7 +1137,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		PredicateLockPage(rel, BufferGetBlockNumber(buf),
 						  scan->xs_snapshot);
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
 	/* position to the precise item on the page */
 	offnum = _bt_binsrch(rel, buf, keysCount, scankeys, nextkey);
@@ -1111,36 +1164,36 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		offnum = OffsetNumberPrev(offnum);
 
 	/* remember which buffer we have pinned, if any */
-	Assert(!BTScanPosIsValid(so->currPos));
-	so->currPos.buf = buf;
+	Assert(!BTScanPosIsValid(*currPos));
+	currPos->buf = buf;
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, offnum))
+	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
+		return false;
+
+readcomplete:
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
+}
+
+/*
+ *	Advance to next tuple on current page; or if there's no more,
+ *	try to step to the next page with data.
+ */
+static bool
+_bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
+{
+	if (ScanDirectionIsForward(dir))
 	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
+		if (++state->currPos.itemIndex <= state->currPos.lastItem)
+			return true;
 	}
 	else
 	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+		if (--state->currPos.itemIndex >= state->currPos.firstItem)
+			return true;
 	}
 
-readcomplete:
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_steppage(scan, state, dir);
 }
 
 /*
@@ -1161,44 +1214,20 @@ bool
 _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-	BTScanPosItem *currItem;
 
-	/*
-	 * Advance to next tuple on current page; or if there's no more, try to
-	 * step to the next page with data.
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (++so->currPos.itemIndex > so->currPos.lastItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
-	else
-	{
-		if (--so->currPos.itemIndex < so->currPos.firstItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
+	if (!_bt_next_item(scan, &so->state, dir))
+		return false;
 
 	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
  *	_bt_readpage() -- Load data from current index page into so->currPos
  *
- * Caller must have pinned and read-locked so->currPos.buf; the buffer's state
- * is not changed here.  Also, currPos.moreLeft and moreRight must be valid;
- * they are updated as appropriate.  All other fields of so->currPos are
+ * Caller must have pinned and read-locked pos->buf; the buffer's state
+ * is not changed here.  Also, pos->moreLeft and moreRight must be valid;
+ * they are updated as appropriate.  All other fields of pos are
  * initialized from scratch here.
  *
  * We scan the current page starting at offnum and moving in the indicated
@@ -1213,9 +1242,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
  * Returns true if any matching items found on the page, false if none.
  */
 static bool
-_bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
+_bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+			 OffsetNumber offnum)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
@@ -1228,9 +1258,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We must have the buffer pinned and locked, but the usual macro can't be
 	 * used here; this function is what makes it good for currPos.
 	 */
-	Assert(BufferIsValid(so->currPos.buf));
+	Assert(BufferIsValid(pos->buf));
 
-	page = BufferGetPage(so->currPos.buf);
+	page = BufferGetPage(pos->buf);
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 
 	/* allow next page be processed by parallel worker */
@@ -1239,7 +1269,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		if (ScanDirectionIsForward(dir))
 			_bt_parallel_release(scan, opaque->btpo_next);
 		else
-			_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+			_bt_parallel_release(scan, BufferGetBlockNumber(pos->buf));
 	}
 
 	minoff = P_FIRSTDATAKEY(opaque);
@@ -1249,30 +1279,30 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We note the buffer's block number so that we can release the pin later.
 	 * This allows us to re-read the buffer if it is needed again for hinting.
 	 */
-	so->currPos.currPage = BufferGetBlockNumber(so->currPos.buf);
+	pos->currPage = BufferGetBlockNumber(pos->buf);
 
 	/*
 	 * We save the LSN of the page as we read it, so that we know whether it
 	 * safe to apply LP_DEAD hints to the page later.  This allows us to drop
 	 * the pin for MVCC scans, which allows vacuum to avoid blocking.
 	 */
-	so->currPos.lsn = BufferGetLSNAtomic(so->currPos.buf);
+	pos->lsn = BufferGetLSNAtomic(pos->buf);
 
 	/*
 	 * we must save the page's right-link while scanning it; this tells us
 	 * where to step right to after we're done with these items.  There is no
 	 * corresponding need for the left-link, since splits always go right.
 	 */
-	so->currPos.nextPage = opaque->btpo_next;
+	pos->nextPage = opaque->btpo_next;
 
 	/* initialize tuple workspace to empty */
-	so->currPos.nextTupleOffset = 0;
+	pos->nextTupleOffset = 0;
 
 	/*
 	 * Now that the current page has been made consistent, the macro should be
 	 * good.
 	 */
-	Assert(BTScanPosIsPinned(so->currPos));
+	Assert(BTScanPosIsPinned(*pos));
 
 	if (ScanDirectionIsForward(dir))
 	{
@@ -1287,13 +1317,13 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			if (itup != NULL)
 			{
 				/* tuple passes all scan key conditions, so remember it */
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 				itemIndex++;
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreRight = false;
+				pos->moreRight = false;
 				break;
 			}
 
@@ -1301,9 +1331,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex <= MaxIndexTuplesPerPage);
-		so->currPos.firstItem = 0;
-		so->currPos.lastItem = itemIndex - 1;
-		so->currPos.itemIndex = 0;
+		pos->firstItem = 0;
+		pos->lastItem = itemIndex - 1;
+		pos->itemIndex = 0;
 	}
 	else
 	{
@@ -1319,12 +1349,12 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			{
 				/* tuple passes all scan key conditions, so remember it */
 				itemIndex--;
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreLeft = false;
+				pos->moreLeft = false;
 				break;
 			}
 
@@ -1332,30 +1362,31 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex >= 0);
-		so->currPos.firstItem = itemIndex;
-		so->currPos.lastItem = MaxIndexTuplesPerPage - 1;
-		so->currPos.itemIndex = MaxIndexTuplesPerPage - 1;
+		pos->firstItem = itemIndex;
+		pos->lastItem = MaxIndexTuplesPerPage - 1;
+		pos->itemIndex = MaxIndexTuplesPerPage - 1;
 	}
 
-	return (so->currPos.firstItem <= so->currPos.lastItem);
+	return (pos->firstItem <= pos->lastItem);
 }
 
 /* Save an index item into so->currPos.items[itemIndex] */
 static void
-_bt_saveitem(BTScanOpaque so, int itemIndex,
+_bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup)
 {
-	BTScanPosItem *currItem = &so->currPos.items[itemIndex];
+	BTScanPosItem *currItem = &state->currPos.items[itemIndex];
 
 	currItem->heapTid = itup->t_tid;
 	currItem->indexOffset = offnum;
-	if (so->currTuples)
+	if (state->currTuples)
 	{
 		Size		itupsz = IndexTupleSize(itup);
 
-		currItem->tupleOffset = so->currPos.nextTupleOffset;
-		memcpy(so->currTuples + so->currPos.nextTupleOffset, itup, itupsz);
-		so->currPos.nextTupleOffset += MAXALIGN(itupsz);
+		currItem->tupleOffset = state->currPos.nextTupleOffset;
+		memcpy(state->currTuples + state->currPos.nextTupleOffset,
+			   itup, itupsz);
+		state->currPos.nextTupleOffset += MAXALIGN(itupsz);
 	}
 }
 
@@ -1371,35 +1402,36 @@ _bt_saveitem(BTScanOpaque so, int itemIndex,
  * to InvalidBuffer.  We return true to indicate success.
  */
 static bool
-_bt_steppage(IndexScanDesc scan, ScanDirection dir)
+_bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
+	Relation	rel = scan->indexRelation;
 	BlockNumber blkno = InvalidBlockNumber;
 	bool		status = true;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(*currPos));
 
 	/* Before leaving current page, deal with any killed items */
-	if (so->numKilled > 0)
-		_bt_killitems(scan);
+	if (state->numKilled > 0)
+		_bt_killitems(state, rel);
 
 	/*
 	 * Before we modify currPos, make a copy of the page data if there was a
 	 * mark position that needs it.
 	 */
-	if (so->markItemIndex >= 0)
+	if (state->markItemIndex >= 0)
 	{
 		/* bump pin on current buffer for assignment to mark buffer */
-		if (BTScanPosIsPinned(so->currPos))
-			IncrBufferRefCount(so->currPos.buf);
-		memcpy(&so->markPos, &so->currPos,
+		if (BTScanPosIsPinned(*currPos))
+			IncrBufferRefCount(currPos->buf);
+		memcpy(&state->markPos, currPos,
 			   offsetof(BTScanPosData, items[1]) +
-			   so->currPos.lastItem * sizeof(BTScanPosItem));
-		if (so->markTuples)
-			memcpy(so->markTuples, so->currTuples,
-				   so->currPos.nextTupleOffset);
-		so->markPos.itemIndex = so->markItemIndex;
-		so->markItemIndex = -1;
+			   currPos->lastItem * sizeof(BTScanPosItem));
+		if (state->markTuples)
+			memcpy(state->markTuples, state->currTuples,
+				   currPos->nextTupleOffset);
+		state->markPos.itemIndex = state->markItemIndex;
+		state->markItemIndex = -1;
 	}
 
 	if (ScanDirectionIsForward(dir))
@@ -1415,27 +1447,27 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
-				BTScanPosUnpinIfPinned(so->currPos);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosUnpinIfPinned(*currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so use the previously-saved nextPage link. */
-			blkno = so->currPos.nextPage;
+			blkno = currPos->nextPage;
 		}
 
 		/* Remember we left a page with data */
-		so->currPos.moreLeft = true;
+		currPos->moreLeft = true;
 
 		/* release the previous buffer, if pinned */
-		BTScanPosUnpinIfPinned(so->currPos);
+		BTScanPosUnpinIfPinned(*currPos);
 	}
 	else
 	{
 		/* Remember we left a page with data */
-		so->currPos.moreRight = true;
+		currPos->moreRight = true;
 
 		if (scan->parallel_scan != NULL)
 		{
@@ -1444,25 +1476,25 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			 * ended already, bail out.
 			 */
 			status = _bt_parallel_seize(scan, &blkno);
-			BTScanPosUnpinIfPinned(so->currPos);
+			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so just use our own notion of the current page */
-			blkno = so->currPos.currPage;
+			blkno = currPos->currPage;
 		}
 	}
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, currPos);
 
 	return true;
 }
@@ -1478,9 +1510,10 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
  * locks and pins, set so->currPos.buf to InvalidBuffer, and return false.
  */
 static bool
-_bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+				 ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
 	Relation	rel;
 	Page		page;
 	BTPageOpaque opaque;
@@ -1496,17 +1529,17 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * if we're at end of scan, give up and mark parallel scan as
 			 * done, so that all the workers can finish their scan
 			 */
-			if (blkno == P_NONE || !so->currPos.moreRight)
+			if (blkno == P_NONE || !currPos->moreRight)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 			/* check for interrupts while we're not holding any buffer lock */
 			CHECK_FOR_INTERRUPTS();
 			/* step right one page */
-			so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
-			page = BufferGetPage(so->currPos.buf);
+			currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			/* check for deleted page */
@@ -1515,7 +1548,7 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 				PredicateLockPage(rel, blkno, scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreRight if we can stop */
-				if (_bt_readpage(scan, dir, P_FIRSTDATAKEY(opaque)))
+				if (_bt_readpage(scan, state, dir, P_FIRSTDATAKEY(opaque)))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
@@ -1527,18 +1560,18 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
 			}
 			else
 			{
 				blkno = opaque->btpo_next;
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 			}
 		}
 	}
@@ -1548,10 +1581,10 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * Should only happen in parallel cases, when some other backend
 		 * advanced the scan.
 		 */
-		if (so->currPos.currPage != blkno)
+		if (currPos->currPage != blkno)
 		{
-			BTScanPosUnpinIfPinned(so->currPos);
-			so->currPos.currPage = blkno;
+			BTScanPosUnpinIfPinned(*currPos);
+			currPos->currPage = blkno;
 		}
 
 		/*
@@ -1576,31 +1609,30 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * is MVCC the page cannot move past the half-dead state to fully
 		 * deleted.
 		 */
-		if (BTScanPosIsPinned(so->currPos))
-			LockBuffer(so->currPos.buf, BT_READ);
+		if (BTScanPosIsPinned(*currPos))
+			LockBuffer(currPos->buf, BT_READ);
 		else
-			so->currPos.buf = _bt_getbuf(rel, so->currPos.currPage, BT_READ);
+			currPos->buf = _bt_getbuf(rel, currPos->currPage, BT_READ);
 
 		for (;;)
 		{
 			/* Done if we know there are no matching keys to the left */
-			if (!so->currPos.moreLeft)
+			if (!currPos->moreLeft)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
 			/* Step to next physical page */
-			so->currPos.buf = _bt_walk_left(rel, so->currPos.buf,
-											scan->xs_snapshot);
+			currPos->buf = _bt_walk_left(rel, currPos->buf, scan->xs_snapshot);
 
 			/* if we're physically at end of index, return failure */
-			if (so->currPos.buf == InvalidBuffer)
+			if (currPos->buf == InvalidBuffer)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
@@ -1609,21 +1641,21 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * it's not half-dead and contains matching tuples. Else loop back
 			 * and do it all again.
 			 */
-			page = BufferGetPage(so->currPos.buf);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			if (!P_IGNORE(opaque))
 			{
-				PredicateLockPage(rel, BufferGetBlockNumber(so->currPos.buf), scan->xs_snapshot);
+				PredicateLockPage(rel, BufferGetBlockNumber(currPos->buf), scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreLeft if we can stop */
-				if (_bt_readpage(scan, dir, PageGetMaxOffsetNumber(page)))
+				if (_bt_readpage(scan, state, dir, PageGetMaxOffsetNumber(page)))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -1634,14 +1666,14 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
-				so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
+				currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
 			}
 		}
 	}
@@ -1660,13 +1692,13 @@ _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
 
 	return true;
 }
@@ -1891,11 +1923,11 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber start;
-	BTScanPosItem *currItem;
 
 	/*
 	 * Scan down to the leftmost or rightmost leaf page.  This is a simplified
@@ -1911,7 +1943,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 		 * exists.
 		 */
 		PredicateLockRelation(rel, scan->xs_snapshot);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 		return false;
 	}
 
@@ -1940,36 +1972,15 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 	}
 
 	/* remember which buffer we have pinned */
-	so->currPos.buf = buf;
+	currPos->buf = buf;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, start))
-	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
-	}
-	else
-	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
-	}
-
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
+	if (!_bt_load_first_page(scan, &so->state, dir, start))
+		return false;
 
-	return true;
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
@@ -1977,19 +1988,19 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
  * for scan direction
  */
 static inline void
-_bt_initialize_more_data(BTScanOpaque so, ScanDirection dir)
+_bt_initialize_more_data(BTScanState state, ScanDirection dir)
 {
 	/* initialize moreLeft/moreRight appropriately for scan direction */
 	if (ScanDirectionIsForward(dir))
 	{
-		so->currPos.moreLeft = false;
-		so->currPos.moreRight = true;
+		state->currPos.moreLeft = false;
+		state->currPos.moreRight = true;
 	}
 	else
 	{
-		so->currPos.moreLeft = true;
-		so->currPos.moreRight = false;
+		state->currPos.moreLeft = true;
+		state->currPos.moreRight = false;
 	}
-	so->numKilled = 0;			/* just paranoia */
-	so->markItemIndex = -1;		/* ditto */
+	state->numKilled = 0;		/* just paranoia */
+	state->markItemIndex = -1;	/* ditto */
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 205457e..619f96ce 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -1741,26 +1741,26 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, TupleDesc tupdesc,
  * away and the TID was re-used by a completely different heap tuple.
  */
 void
-_bt_killitems(IndexScanDesc scan)
+_bt_killitems(BTScanState state, Relation indexRelation)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
 	OffsetNumber maxoff;
 	int			i;
-	int			numKilled = so->numKilled;
+	int			numKilled = state->numKilled;
 	bool		killedsomething = false;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(state->currPos));
 
 	/*
 	 * Always reset the scan state, so we don't look for same items on other
 	 * pages.
 	 */
-	so->numKilled = 0;
+	state->numKilled = 0;
 
-	if (BTScanPosIsPinned(so->currPos))
+	if (BTScanPosIsPinned(*pos))
 	{
 		/*
 		 * We have held the pin on this page since we read the index tuples,
@@ -1768,44 +1768,42 @@ _bt_killitems(IndexScanDesc scan)
 		 * re-use of any TID on the page, so there is no need to check the
 		 * LSN.
 		 */
-		LockBuffer(so->currPos.buf, BT_READ);
-
-		page = BufferGetPage(so->currPos.buf);
+		LockBuffer(pos->buf, BT_READ);
 	}
 	else
 	{
 		Buffer		buf;
 
 		/* Attempt to re-read the buffer, getting pin and lock. */
-		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ);
+		buf = _bt_getbuf(indexRelation, pos->currPage, BT_READ);
 
 		/* It might not exist anymore; in which case we can't hint it. */
 		if (!BufferIsValid(buf))
 			return;
 
-		page = BufferGetPage(buf);
-		if (BufferGetLSNAtomic(buf) == so->currPos.lsn)
-			so->currPos.buf = buf;
+		if (BufferGetLSNAtomic(buf) == pos->lsn)
+			pos->buf = buf;
 		else
 		{
 			/* Modified while not pinned means hinting is not safe. */
-			_bt_relbuf(scan->indexRelation, buf);
+			_bt_relbuf(indexRelation, buf);
 			return;
 		}
 	}
 
+	page = BufferGetPage(pos->buf);
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 	minoff = P_FIRSTDATAKEY(opaque);
 	maxoff = PageGetMaxOffsetNumber(page);
 
 	for (i = 0; i < numKilled; i++)
 	{
-		int			itemIndex = so->killedItems[i];
-		BTScanPosItem *kitem = &so->currPos.items[itemIndex];
+		int			itemIndex = state->killedItems[i];
+		BTScanPosItem *kitem = &pos->items[itemIndex];
 		OffsetNumber offnum = kitem->indexOffset;
 
-		Assert(itemIndex >= so->currPos.firstItem &&
-			   itemIndex <= so->currPos.lastItem);
+		Assert(itemIndex >= pos->firstItem &&
+			   itemIndex <= pos->lastItem);
 		if (offnum < minoff)
 			continue;			/* pure paranoia */
 		while (offnum <= maxoff)
@@ -1833,10 +1831,10 @@ _bt_killitems(IndexScanDesc scan)
 	if (killedsomething)
 	{
 		opaque->btpo_flags |= BTP_HAS_GARBAGE;
-		MarkBufferDirtyHint(so->currPos.buf, true);
+		MarkBufferDirtyHint(pos->buf, true);
 	}
 
-	LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
+	LockBuffer(pos->buf, BUFFER_LOCK_UNLOCK);
 }
 
 
@@ -2214,3 +2212,14 @@ _bt_check_natts(Relation rel, Page page, OffsetNumber offnum)
 
 	}
 }
+
+/*
+ * _bt_allocate_tuple_workspaces() -- Allocate buffers for saving index tuples
+ * 		in index-only scans.
+ */
+void
+_bt_allocate_tuple_workspaces(BTScanState state)
+{
+	state->currTuples = (char *) palloc(BLCKSZ * 2);
+	state->markTuples = state->currTuples + BLCKSZ;
+}
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index ea495f1..1bfee58 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -433,22 +433,8 @@ typedef struct BTArrayKeyInfo
 	Datum	   *elem_values;	/* array of num_elems Datums */
 } BTArrayKeyInfo;
 
-typedef struct BTScanOpaqueData
+typedef struct BTScanStateData
 {
-	/* these fields are set by _bt_preprocess_keys(): */
-	bool		qual_ok;		/* false if qual can never be satisfied */
-	int			numberOfKeys;	/* number of preprocessed scan keys */
-	ScanKey		keyData;		/* array of preprocessed scan keys */
-
-	/* workspace for SK_SEARCHARRAY support */
-	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
-	int			numArrayKeys;	/* number of equality-type array keys (-1 if
-								 * there are any unsatisfiable array keys) */
-	int			arrayKeyCount;	/* count indicating number of array scan keys
-								 * processed */
-	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
-	MemoryContext arrayContext; /* scan-lifespan context for array data */
-
 	/* info about killed items if any (killedItems is NULL if never used) */
 	int		   *killedItems;	/* currPos.items indexes of killed items */
 	int			numKilled;		/* number of currently stored items */
@@ -473,6 +459,25 @@ typedef struct BTScanOpaqueData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+}	BTScanStateData, *BTScanState;
+
+typedef struct BTScanOpaqueData
+{
+	/* these fields are set by _bt_preprocess_keys(): */
+	bool		qual_ok;		/* false if qual can never be satisfied */
+	int			numberOfKeys;	/* number of preprocessed scan keys */
+	ScanKey		keyData;		/* array of preprocessed scan keys */
+
+	/* workspace for SK_SEARCHARRAY support */
+	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
+	int			numArrayKeys;	/* number of equality-type array keys (-1 if
+								 * there are any unsatisfiable array keys) */
+	int			arrayKeyCount;	/* count indicating number of array scan keys
+								 * processed */
+	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
+	MemoryContext arrayContext; /* scan-lifespan context for array data */
+
+	BTScanStateData	state;
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -589,7 +594,7 @@ extern void _bt_preprocess_keys(IndexScanDesc scan);
 extern IndexTuple _bt_checkkeys(IndexScanDesc scan,
 			  Page page, OffsetNumber offnum,
 			  ScanDirection dir, bool *continuescan);
-extern void _bt_killitems(IndexScanDesc scan);
+extern void _bt_killitems(BTScanState state, Relation indexRelation);
 extern BTCycleId _bt_vacuum_cycleid(Relation rel);
 extern BTCycleId _bt_start_vacuum(Relation rel);
 extern void _bt_end_vacuum(Relation rel);
@@ -602,6 +607,7 @@ extern bool btproperty(Oid index_oid, int attno,
 		   bool *res, bool *isnull);
 extern IndexTuple _bt_nonkey_truncate(Relation rel, IndexTuple itup);
 extern bool _bt_check_natts(Relation rel, Page page, OffsetNumber offnum);
+extern void _bt_allocate_tuple_workspaces(BTScanState state);
 
 /*
  * prototypes for functions in nbtvalidate.c
-- 
2.7.4

0004-Add-kNN-support-to-btree-v04.patchtext/x-patch; name=0004-Add-kNN-support-to-btree-v04.patchDownload
From 096a442eefaf3227da92880aad841eae02285d34 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 30 Nov 2018 14:56:54 +0300
Subject: [PATCH 4/7] Add kNN support to btree

---
 doc/src/sgml/btree.sgml                     |  14 ++
 doc/src/sgml/indices.sgml                   |  11 +
 doc/src/sgml/xindex.sgml                    |   3 +-
 src/backend/access/nbtree/README            |  17 ++
 src/backend/access/nbtree/nbtree.c          | 218 ++++++++++++++--
 src/backend/access/nbtree/nbtsearch.c       | 331 +++++++++++++++++++++---
 src/backend/access/nbtree/nbtutils.c        | 373 +++++++++++++++++++++++++++-
 src/backend/access/nbtree/nbtvalidate.c     |  48 ++--
 src/backend/optimizer/path/indxpath.c       |  16 +-
 src/include/access/nbtree.h                 |  27 +-
 src/include/access/stratnum.h               |   5 +-
 src/test/regress/expected/alter_generic.out |  13 +-
 src/test/regress/sql/alter_generic.sql      |   8 +-
 13 files changed, 981 insertions(+), 103 deletions(-)

diff --git a/doc/src/sgml/btree.sgml b/doc/src/sgml/btree.sgml
index c16825e..e508615 100644
--- a/doc/src/sgml/btree.sgml
+++ b/doc/src/sgml/btree.sgml
@@ -200,6 +200,20 @@
   planner relies on them for optimization purposes.
  </para>
 
+ <para>
+  FIXME!!!
+  To implement the distance ordered (nearest-neighbor) search, we only need
+  to define a distance operator (usually it called &lt;-&gt;) with a correpsonding
+  operator family for distance comparison in the index's operator class.
+  These operators must satisfy the following assumptions for all non-null
+  values A,B,C of the datatype:
+
+  A &lt;-&gt; B = B &lt;-&gt; A						symmetric law
+  if A = B, then A &lt;-&gt; C = B &lt;-&gt; C		distance equivalence
+  if (A &lt;= B and B &lt;= C) or (A &gt;= B and B &gt;= C),
+  then A &lt;-&gt; B &lt;= A &lt;-&gt; C			monotonicity
+ </para>
+
 </sect1>
 
 <sect1 id="btree-support-funcs">
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 46f427b..caec484 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -175,6 +175,17 @@ CREATE INDEX test1_id_index ON test1 (id);
   </para>
 
   <para>
+   B-tree indexes are also capable of optimizing <quote>nearest-neighbor</>
+   searches, such as
+<programlisting><![CDATA[
+SELECT * FROM events ORDER BY event_date <-> date '2017-05-05' LIMIT 10;
+]]>
+</programlisting>
+   which finds the ten events closest to a given target date.  The ability
+   to do this is again dependent on the particular operator class being used.
+  </para>
+
+  <para>
    <indexterm>
     <primary>index</primary>
     <secondary>hash</secondary>
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 9446f8b..93094bc 100644
--- a/doc/src/sgml/xindex.sgml
+++ b/doc/src/sgml/xindex.sgml
@@ -1242,7 +1242,8 @@ SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
   <title>Ordering Operators</title>
 
   <para>
-   Some index access methods (currently, only GiST and SP-GiST) support the concept of
+   Some index access methods (currently, only B-tree, GiST and SP-GiST) 
+   support the concept of
    <firstterm>ordering operators</firstterm>.  What we have been discussing so far
    are <firstterm>search operators</firstterm>.  A search operator is one for which
    the index can be searched to find all rows satisfying
diff --git a/src/backend/access/nbtree/README b/src/backend/access/nbtree/README
index 3680e69..3f7e1b1 100644
--- a/src/backend/access/nbtree/README
+++ b/src/backend/access/nbtree/README
@@ -659,3 +659,20 @@ routines must treat it accordingly.  The actual key stored in the
 item is irrelevant, and need not be stored at all.  This arrangement
 corresponds to the fact that an L&Y non-leaf page has one more pointer
 than key.
+
+Nearest-neighbor search
+-----------------------
+
+There is a special scan strategy for nearest-neighbor (kNN) search,
+that is used in queries with ORDER BY distance clauses like this:
+SELECT * FROM tab WHERE col > const1 ORDER BY col <-> const2 LIMIT k.
+But, unlike GiST, B-tree supports only a one ordering operator on the
+first index column.
+
+At the beginning of kNN scan, we need to determine which strategy we
+will use --- a special bidirectional or a ordinary unidirectional.
+If the point from which we measure the distance falls into the scan range,
+we use bidirectional scan starting from this point, else we use simple
+unidirectional scan in the right direction.  Algorithm of a bidirectional
+scan is very simple: at each step we advancing scan in that direction,
+which has the nearest point.
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 55c7833..9270a85 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -25,6 +25,9 @@
 #include "commands/vacuum.h"
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
+#include "nodes/primnodes.h"
+#include "nodes/relation.h"
+#include "optimizer/paths.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "storage/condition_variable.h"
@@ -33,6 +36,7 @@
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/index_selfuncs.h"
 #include "utils/memutils.h"
 
@@ -79,6 +83,7 @@ typedef enum
 typedef struct BTParallelScanDescData
 {
 	BlockNumber btps_scanPage;	/* latest or next page to be scanned */
+	BlockNumber btps_knnScanPage;	/* secondary latest or next page to be scanned */
 	BTPS_State	btps_pageStatus;	/* indicates whether next page is
 									 * available for scan. see above for
 									 * possible states of parallel scan. */
@@ -97,6 +102,9 @@ static void btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 static void btvacuumpage(BTVacState *vstate, BlockNumber blkno,
 			 BlockNumber orig_blkno);
 
+static bool btmatchorderby(IndexOptInfo *index, List *pathkeys,
+			   List **orderby_clauses_p, List **clause_columns_p);
+
 
 /*
  * Btree handler function: return IndexAmRoutine with access method parameters
@@ -107,7 +115,7 @@ bthandler(PG_FUNCTION_ARGS)
 {
 	IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
 
-	amroutine->amstrategies = BTMaxStrategyNumber;
+	amroutine->amstrategies = BtreeMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
 	amroutine->amcanbackward = true;
@@ -143,7 +151,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = btestimateparallelscan;
 	amroutine->aminitparallelscan = btinitparallelscan;
 	amroutine->amparallelrescan = btparallelrescan;
-	amroutine->ammatchorderby = NULL;
+	amroutine->ammatchorderby = btmatchorderby;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -215,23 +223,30 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTScanState state = &so->state;
+	ScanDirection arraydir =
+		scan->numberOfOrderBys > 0 ? ForwardScanDirection : dir;
 	bool		res;
 
 	/* btree indexes are never lossy */
 	scan->xs_recheck = false;
+	scan->xs_recheckorderby = false;
+
+	if (so->scanDirection != NoMovementScanDirection)
+		dir = so->scanDirection;
 
 	/*
 	 * If we have any array keys, initialize them during first call for a
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos) &&
+		(!so->knnState || !BTScanPosIsValid(so->knnState->currPos)))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
 			return false;
 
-		_bt_start_array_keys(scan, dir);
+		_bt_start_array_keys(scan, arraydir);
 	}
 
 	/* This loop handles advancing to the next array elements, if any */
@@ -242,7 +257,8 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(state->currPos))
+		if (!BTScanPosIsValid(state->currPos) &&
+			(!so->knnState || !BTScanPosIsValid(so->knnState->currPos)))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -277,7 +293,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		if (res)
 			break;
 		/* ... otherwise see if we have more array keys to deal with */
-	} while (so->numArrayKeys && _bt_advance_array_keys(scan, dir));
+	} while (so->numArrayKeys && _bt_advance_array_keys(scan, arraydir));
 
 	return res;
 }
@@ -350,9 +366,6 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	IndexScanDesc scan;
 	BTScanOpaque so;
 
-	/* no order by operators allowed */
-	Assert(norderbys == 0);
-
 	/* get the scan */
 	scan = RelationGetIndexScan(rel, nkeys, norderbys);
 
@@ -379,6 +392,9 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
 	so->state.currTuples = so->state.markTuples = NULL;
+	so->knnState = NULL;
+	so->distanceTypeByVal = true;
+	so->scanDirection = NoMovementScanDirection;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -408,6 +424,8 @@ _bt_release_current_position(BTScanState state, Relation indexRelation,
 static void
 _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	/* No need to invalidate positions, if the RAM is about to be freed. */
 	_bt_release_current_position(state, scan->indexRelation, !free);
 
@@ -424,6 +442,17 @@ _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 	}
 	else
 		BTScanPosInvalidate(state->markPos);
+
+	if (!so->distanceTypeByVal)
+	{
+		if (DatumGetPointer(state->currDistance))
+			pfree(DatumGetPointer(state->currDistance));
+		state->currDistance = PointerGetDatum(NULL);
+
+		if (DatumGetPointer(state->markDistance))
+			pfree(DatumGetPointer(state->markDistance));
+		state->markDistance = PointerGetDatum(NULL);
+	}
 }
 
 /*
@@ -438,6 +467,13 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 
 	_bt_release_scan_state(scan, state, false);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+		so->knnState = NULL;
+	}
+
 	so->arrayKeyCount = 0; /* FIXME in _bt_release_scan_state */
 
 	/*
@@ -469,6 +505,14 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 				scan->numberOfKeys * sizeof(ScanKeyData));
 	so->numberOfKeys = 0;		/* until _bt_preprocess_keys sets it */
 
+	if (orderbys && scan->numberOfOrderBys > 0)
+		memmove(scan->orderByData,
+				orderbys,
+				scan->numberOfOrderBys * sizeof(ScanKeyData));
+
+	so->scanDirection = NoMovementScanDirection;
+	so->distanceTypeByVal = true;
+
 	/* If any keys are SK_SEARCHARRAY type, set up array-key info */
 	_bt_preprocess_array_keys(scan);
 }
@@ -483,6 +527,12 @@ btendscan(IndexScanDesc scan)
 
 	_bt_release_scan_state(scan, &so->state, true);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+	}
+
 	/* Release storage */
 	if (so->keyData != NULL)
 		pfree(so->keyData);
@@ -494,7 +544,7 @@ btendscan(IndexScanDesc scan)
 }
 
 static void
-_bt_mark_current_position(BTScanState state)
+_bt_mark_current_position(BTScanOpaque so, BTScanState state)
 {
 	/* There may be an old mark with a pin (but no lock). */
 	BTScanPosUnpinIfPinned(state->markPos);
@@ -512,6 +562,21 @@ _bt_mark_current_position(BTScanState state)
 		BTScanPosInvalidate(state->markPos);
 		state->markItemIndex = -1;
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->markDistance));
+
+		state->markIsNull = !BTScanPosIsValid(state->currPos) ||
+			state->currIsNull;
+
+		state->markDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->currDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -522,7 +587,13 @@ btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_mark_current_position(&so->state);
+	_bt_mark_current_position(so, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_mark_current_position(so, so->knnState);
+		so->markRightIsNearest = so->currRightIsNearest;
+	}
 
 	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
@@ -532,6 +603,8 @@ btmarkpos(IndexScanDesc scan)
 static void
 _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	if (state->markItemIndex >= 0)
 	{
 		/*
@@ -567,6 +640,19 @@ _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 					   state->markPos.nextTupleOffset);
 		}
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->currDistance));
+
+		state->currIsNull = state->markIsNull;
+		state->currDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->markDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -588,6 +674,7 @@ btinitparallelscan(void *target)
 
 	SpinLockInit(&bt_target->btps_mutex);
 	bt_target->btps_scanPage = InvalidBlockNumber;
+	bt_target->btps_knnScanPage = InvalidBlockNumber;
 	bt_target->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	bt_target->btps_arrayKeyCount = 0;
 	ConditionVariableInit(&bt_target->btps_cv);
@@ -614,6 +701,7 @@ btparallelrescan(IndexScanDesc scan)
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
 	btscan->btps_scanPage = InvalidBlockNumber;
+	btscan->btps_knnScanPage = InvalidBlockNumber;
 	btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	btscan->btps_arrayKeyCount = 0;
 	SpinLockRelease(&btscan->btps_mutex);
@@ -638,7 +726,7 @@ btparallelrescan(IndexScanDesc scan)
  * Callers should ignore the value of pageno if the return value is false.
  */
 bool
-_bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
+_bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTPS_State	pageStatus;
@@ -646,12 +734,17 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
 	bool		status = true;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
 
 	*pageno = P_NONE;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	scanPage = state == &so->state
+		? &btscan->btps_scanPage
+		: &btscan->btps_knnScanPage;
+
 	while (1)
 	{
 		SpinLockAcquire(&btscan->btps_mutex);
@@ -677,7 +770,7 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
 			 * of advancing it to a new page!
 			 */
 			btscan->btps_pageStatus = BTPARALLEL_ADVANCING;
-			*pageno = btscan->btps_scanPage;
+			*pageno = *scanPage;
 			exit_loop = true;
 		}
 		SpinLockRelease(&btscan->btps_mutex);
@@ -696,19 +789,42 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
  *		can now begin advancing the scan.
  */
 void
-_bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
+_bt_parallel_release(IndexScanDesc scan, BTScanState state,
+					 BlockNumber scan_page)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
+	bool		status_changed = false;
+	bool		knnScan = so->knnState != NULL;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
+	if (!state || state == &so->state)
+	{
+		scanPage = &btscan->btps_scanPage;
+		otherScanPage = &btscan->btps_knnScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_knnScanPage;
+		otherScanPage = &btscan->btps_scanPage;
+	}
 
 	SpinLockAcquire(&btscan->btps_mutex);
-	btscan->btps_scanPage = scan_page;
-	btscan->btps_pageStatus = BTPARALLEL_IDLE;
+	*scanPage = scan_page;
+	/* switch to idle state only if both KNN pages are initialized */
+	if (!knnScan || *otherScanPage != InvalidBlockNumber)
+	{
+		btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		status_changed = true;
+	}
 	SpinLockRelease(&btscan->btps_mutex);
-	ConditionVariableSignal(&btscan->btps_cv);
+
+	if (status_changed)
+		ConditionVariableSignal(&btscan->btps_cv);
 }
 
 /*
@@ -719,12 +835,15 @@ _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
  * advance to the next page.
  */
 void
-_bt_parallel_done(IndexScanDesc scan)
+_bt_parallel_done(IndexScanDesc scan, BTScanState state)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
 	bool		status_changed = false;
+	bool		knnScan = so->knnState != NULL;
 
 	/* Do nothing, for non-parallel scans */
 	if (parallel_scan == NULL)
@@ -733,18 +852,41 @@ _bt_parallel_done(IndexScanDesc scan)
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	if (!state || state == &so->state)
+	{
+		scanPage = &btscan->btps_scanPage;
+		otherScanPage = &btscan->btps_knnScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_knnScanPage;
+		otherScanPage = &btscan->btps_scanPage;
+	}
+
 	/*
 	 * Mark the parallel scan as done for this combination of scan keys,
 	 * unless some other process already did so.  See also
 	 * _bt_advance_array_keys.
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
-	if (so->arrayKeyCount >= btscan->btps_arrayKeyCount &&
-		btscan->btps_pageStatus != BTPARALLEL_DONE)
+
+	Assert(btscan->btps_pageStatus == BTPARALLEL_ADVANCING);
+
+	if (so->arrayKeyCount >= btscan->btps_arrayKeyCount)
 	{
-		btscan->btps_pageStatus = BTPARALLEL_DONE;
+		*scanPage = P_NONE;
 		status_changed = true;
+
+		/* switch to "done" state only if both KNN scans are done */
+		if (!knnScan || *otherScanPage == P_NONE)
+			btscan->btps_pageStatus = BTPARALLEL_DONE;
+		/* else switch to "idle" state only if both KNN scans are initialized */
+		else if (*otherScanPage != InvalidBlockNumber)
+			btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		else
+			status_changed = false;
 	}
+
 	SpinLockRelease(&btscan->btps_mutex);
 
 	/* wake up all the workers associated with this parallel scan */
@@ -774,6 +916,7 @@ _bt_parallel_advance_array_keys(IndexScanDesc scan)
 	if (btscan->btps_pageStatus == BTPARALLEL_DONE)
 	{
 		btscan->btps_scanPage = InvalidBlockNumber;
+		btscan->btps_knnScanPage = InvalidBlockNumber;
 		btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 		btscan->btps_arrayKeyCount++;
 	}
@@ -859,6 +1002,12 @@ btrestrpos(IndexScanDesc scan)
 		_bt_restore_array_keys(scan);
 
 	_bt_restore_marked_position(scan, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_restore_marked_position(scan, so->knnState);
+		so->currRightIsNearest = so->markRightIsNearest;
+	}
 }
 
 /*
@@ -1394,3 +1543,30 @@ btcanreturn(Relation index, int attno)
 {
 	return true;
 }
+
+/*
+ *	btmatchorderby() -- Check whether KNN-search strategy is applicable to
+ *		the given ORDER BY distance operator.
+ */
+static bool
+btmatchorderby(IndexOptInfo *index, List *pathkeys,
+			   List **orderby_clauses_p, List **clause_columns_p)
+{
+	Expr	   *expr;
+	/* ORDER BY distance to the first index column is only supported */
+	int			indexcol = 0;
+
+	if (list_length(pathkeys) != 1)
+		return false; /* only one ORDER BY clause is supported */
+
+	expr = match_orderbyop_pathkey(index, castNode(PathKey, linitial(pathkeys)),
+								   &indexcol);
+
+	if (!expr)
+		return false;
+
+	*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+	*clause_columns_p = lappend_int(*clause_columns_p, 0);
+
+	return true;
+}
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 8c9df32..36f8886 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -32,12 +32,14 @@ static void _bt_saveitem(BTScanState state, int itemIndex,
 static bool _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir);
 static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
 				 BlockNumber blkno, ScanDirection dir);
-static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
-					  ScanDirection dir);
+static bool _bt_parallel_readpage(IndexScanDesc scan, BTScanState state,
+					  BlockNumber blkno, ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf, Snapshot snapshot);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
 static inline void _bt_initialize_more_data(BTScanState state, ScanDirection dir);
+static BTScanState _bt_alloc_knn_scan(IndexScanDesc scan);
+static bool _bt_start_knn_scan(IndexScanDesc scan, bool left, bool right);
 
 
 /*
@@ -598,6 +600,156 @@ _bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 }
 
 /*
+ *  _bt_calc_current_dist() -- Calculate distance from the current item
+ *		of the scan state to the target order-by ScanKey argument.
+ */
+static void
+_bt_calc_current_dist(IndexScanDesc scan, BTScanState state)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanPosItem  *currItem = &state->currPos.items[state->currPos.itemIndex];
+	IndexTuple		itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+	ScanKey			scankey = &scan->orderByData[0];
+	Datum			value;
+
+	value = index_getattr(itup, 1, scan->xs_itupdesc, &state->currIsNull);
+
+	if (state->currIsNull)
+		return; /* NULL distance */
+
+	value = FunctionCall2Coll(&scankey->sk_func,
+							  scankey->sk_collation,
+							  value,
+							  scankey->sk_argument);
+
+	/* free previous distance value for by-ref types */
+	if (!so->distanceTypeByVal && DatumGetPointer(state->currDistance))
+		pfree(DatumGetPointer(state->currDistance));
+
+	state->currDistance = value;
+}
+
+/*
+ *  _bt_compare_current_dist() -- Compare current distances of the left and right scan states.
+ *
+ *  NULL distances are considered to be greater than any non-NULL distances.
+ *
+ *  Returns true if right distance is lesser than left, otherwise false.
+ */
+static bool
+_bt_compare_current_dist(BTScanOpaque so, BTScanState rstate, BTScanState lstate)
+{
+	if (lstate->currIsNull)
+		return true; /* non-NULL < NULL */
+
+	if (rstate->currIsNull)
+		return false; /* NULL > non-NULL */
+
+	return DatumGetBool(FunctionCall2Coll(&so->distanceCmpProc,
+										  InvalidOid, /* XXX collation for distance comparison */
+										  rstate->currDistance,
+										  lstate->currDistance));
+}
+
+/*
+ * _bt_alloc_knn_scan() -- Allocate additional backward scan state for KNN.
+ */
+static BTScanState
+_bt_alloc_knn_scan(IndexScanDesc scan)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		lstate = (BTScanState) palloc(sizeof(BTScanStateData));
+
+	_bt_allocate_tuple_workspaces(lstate);
+
+	if (!scan->xs_want_itup)
+	{
+		/* We need to request index tuples for distance comparison. */
+		scan->xs_want_itup = true;
+		_bt_allocate_tuple_workspaces(&so->state);
+	}
+
+	BTScanPosInvalidate(lstate->currPos);
+	lstate->currPos.moreLeft = false;
+	lstate->currPos.moreRight = false;
+	BTScanPosInvalidate(lstate->markPos);
+	lstate->markItemIndex = -1;
+	lstate->killedItems = NULL;
+	lstate->numKilled = 0;
+	lstate->currDistance = PointerGetDatum(NULL);
+	lstate->markDistance = PointerGetDatum(NULL);
+
+	return so->knnState = lstate;
+}
+
+static bool
+_bt_start_knn_scan(IndexScanDesc scan, bool left, bool right)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		rstate; /* right (forward) main scan state */
+	BTScanState		lstate; /* additional left (backward) KNN scan state */
+
+	if (!left && !right)
+		return false; /* empty result */
+
+	rstate = &so->state;
+	lstate = so->knnState;
+
+	if (left && right)
+	{
+		/*
+		 * We have found items in both scan directions,
+		 * determine nearest item to return.
+		 */
+		_bt_calc_current_dist(scan, rstate);
+		_bt_calc_current_dist(scan, lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+
+		/* Reset right flag if the left item is nearer. */
+		right = so->currRightIsNearest;
+	}
+
+	/* Return current item of the selected scan direction. */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
+ * _bt_init_knn_scan() -- Init additional scan state for KNN search.
+ *
+ * Caller must pin and read-lock scan->state.currPos.buf buffer.
+ *
+ * If empty result was found returned false.
+ * Otherwise prepared current item, and returned true.
+ */
+static bool
+_bt_init_knn_scan(IndexScanDesc scan, OffsetNumber offnum)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		rstate = &so->state; /* right (forward) main scan state */
+	BTScanState		lstate; /* additional left (backward) KNN scan state */
+	Buffer			buf = rstate->currPos.buf;
+	bool			left,
+					right;
+
+	lstate = _bt_alloc_knn_scan(scan);
+
+	/* Bump pin and lock count before BTScanPosData copying. */
+	IncrBufferRefCount(buf);
+	LockBuffer(buf, BT_READ);
+
+	memcpy(&lstate->currPos, &rstate->currPos, sizeof(BTScanPosData));
+	lstate->currPos.moreLeft = true;
+	lstate->currPos.moreRight = false;
+
+	/* Load first pages from the both scans. */
+	right = _bt_load_first_page(scan, rstate, ForwardScanDirection, offnum);
+	left = _bt_load_first_page(scan, lstate, BackwardScanDirection,
+							   OffsetNumberPrev(offnum));
+
+	return _bt_start_knn_scan(scan, left, right);
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -655,6 +807,19 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	if (!so->qual_ok)
 		return false;
 
+	strat_total = BTEqualStrategyNumber;
+
+	if (scan->numberOfOrderBys > 0)
+	{
+		if (_bt_process_orderings(scan, startKeys, &keysCount, notnullkeys))
+			/* use bidirectional KNN scan */
+			strat_total = BtreeKNNSearchStrategyNumber;
+
+		/* use selected KNN scan direction */
+		if (so->scanDirection != NoMovementScanDirection)
+			dir = so->scanDirection;
+	}
+
 	/*
 	 * For parallel scans, get the starting page from shared state. If the
 	 * scan has not started, proceed to find out first leaf page in the usual
@@ -663,19 +828,50 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 */
 	if (scan->parallel_scan != NULL)
 	{
-		status = _bt_parallel_seize(scan, &blkno);
+		status = _bt_parallel_seize(scan, &so->state, &blkno);
 		if (!status)
 			return false;
-		else if (blkno == P_NONE)
-		{
-			_bt_parallel_done(scan);
-			return false;
-		}
 		else if (blkno != InvalidBlockNumber)
 		{
-			if (!_bt_parallel_readpage(scan, blkno, dir))
-				return false;
-			goto readcomplete;
+			bool		knn = strat_total == BtreeKNNSearchStrategyNumber;
+			bool		right;
+			bool		left;
+
+			if (knn)
+				_bt_alloc_knn_scan(scan);
+
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, &so->state);
+				right = false;
+			}
+			else
+				right = _bt_parallel_readpage(scan, &so->state, blkno,
+											  knn ? ForwardScanDirection : dir);
+
+			if (!knn)
+				return right && _bt_return_current_item(scan, &so->state);
+
+			/* seize additional backward KNN scan */
+			left = _bt_parallel_seize(scan, so->knnState, &blkno);
+
+			if (left)
+			{
+				if (blkno == P_NONE)
+				{
+					_bt_parallel_done(scan, so->knnState);
+					left = false;
+				}
+				else
+				{
+					/* backward scan should be already initialized */
+					Assert(blkno != InvalidBlockNumber);
+					left = _bt_parallel_readpage(scan, so->knnState, blkno,
+												 BackwardScanDirection);
+				}
+			}
+
+			return _bt_start_knn_scan(scan, left, right);
 		}
 	}
 
@@ -725,8 +921,10 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 * storing their addresses into the local startKeys[] array.
 	 *----------
 	 */
-	strat_total = BTEqualStrategyNumber;
-	if (so->numberOfKeys > 0)
+
+	if (so->numberOfKeys > 0 &&
+	/* startKeys for KNN search already have been initialized */
+		strat_total != BtreeKNNSearchStrategyNumber)
 	{
 		AttrNumber	curattr;
 		ScanKey		chosen;
@@ -866,7 +1064,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		if (!match)
 		{
 			/* No match, so mark (parallel) scan finished */
-			_bt_parallel_done(scan);
+			_bt_parallel_done(scan, NULL);
 		}
 
 		return match;
@@ -901,7 +1099,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			Assert(subkey->sk_flags & SK_ROW_MEMBER);
 			if (subkey->sk_flags & SK_ISNULL)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, NULL);
 				return false;
 			}
 			memcpy(scankeys + i, subkey, sizeof(ScanKeyData));
@@ -1081,6 +1279,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			break;
 
 		case BTGreaterEqualStrategyNumber:
+		case BtreeKNNSearchStrategyNumber:
 
 			/*
 			 * Find first item >= scankey.  (This is only used for forward
@@ -1128,7 +1327,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 * mark parallel scan as done, so that all the workers can finish
 		 * their scan
 		 */
-		_bt_parallel_done(scan);
+		_bt_parallel_done(scan, NULL);
 		BTScanPosInvalidate(*currPos);
 
 		return false;
@@ -1167,17 +1366,21 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	Assert(!BTScanPosIsValid(*currPos));
 	currPos->buf = buf;
 
+	if (strat_total == BtreeKNNSearchStrategyNumber)
+		return _bt_init_knn_scan(scan, offnum);
+
 	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
-		return false;
+		return false; /* empty result */
 
-readcomplete:
 	/* OK, currPos->itemIndex says what to return */
 	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
- *	Advance to next tuple on current page; or if there's no more,
- *	try to step to the next page with data.
+ *	_bt_next_item() -- Advance to next tuple on current page;
+ *		or if there's no more, try to step to the next page with data.
+ *
+ *	If there are no more matching records in the given direction
  */
 static bool
 _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
@@ -1197,6 +1400,51 @@ _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 }
 
 /*
+ *	_bt_next_nearest() -- Return next nearest item from bidirectional KNN scan.
+ */
+static bool
+_bt_next_nearest(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState rstate = &so->state;
+	BTScanState lstate = so->knnState;
+	bool		right = BTScanPosIsValid(rstate->currPos);
+	bool		left = BTScanPosIsValid(lstate->currPos);
+	bool		advanceRight;
+
+	if (right && left)
+		advanceRight = so->currRightIsNearest;
+	else if (right)
+		advanceRight = true;
+	else if (left)
+		advanceRight = false;
+	else
+		return false; /* end of the scan */
+
+	if (advanceRight)
+		right = _bt_next_item(scan, rstate, ForwardScanDirection);
+	else
+		left = _bt_next_item(scan, lstate, BackwardScanDirection);
+
+	if (!left && !right)
+		return false; /* end of the scan */
+
+	if (left && right)
+	{
+		/*
+		 * If there are items in both scans we must recalculate distance
+		 * in the advanced scan.
+		 */
+		_bt_calc_current_dist(scan, advanceRight ? rstate : lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+		right = so->currRightIsNearest;
+	}
+
+	/* return nearest item */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
  *	_bt_next() -- Get the next item in a scan.
  *
  *		On entry, so->currPos describes the current page, which may be pinned
@@ -1215,6 +1463,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
+	if (so->knnState)
+		/* return next neareset item from KNN scan */
+		return _bt_next_nearest(scan);
+
 	if (!_bt_next_item(scan, &so->state, dir))
 		return false;
 
@@ -1267,9 +1519,9 @@ _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 	if (scan->parallel_scan)
 	{
 		if (ScanDirectionIsForward(dir))
-			_bt_parallel_release(scan, opaque->btpo_next);
+			_bt_parallel_release(scan, state, opaque->btpo_next);
 		else
-			_bt_parallel_release(scan, BufferGetBlockNumber(pos->buf));
+			_bt_parallel_release(scan, state, BufferGetBlockNumber(pos->buf));
 	}
 
 	minoff = P_FIRSTDATAKEY(opaque);
@@ -1443,7 +1695,7 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the next block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno);
+			status = _bt_parallel_seize(scan, state, &blkno);
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
@@ -1475,13 +1727,19 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the current block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno);
+			status = _bt_parallel_seize(scan, state, &blkno);
 			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, state);
+				BTScanPosInvalidate(*currPos);
+				return false;
+			}
 		}
 		else
 		{
@@ -1531,7 +1789,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			 */
 			if (blkno == P_NONE || !currPos->moreRight)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1554,14 +1812,14 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, opaque->btpo_next);
+				_bt_parallel_release(scan, state, opaque->btpo_next);
 			}
 
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno);
+				status = _bt_parallel_seize(scan, state, &blkno);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -1620,7 +1878,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (!currPos->moreLeft)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1631,7 +1889,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			/* if we're physically at end of index, return failure */
 			if (currPos->buf == InvalidBuffer)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1655,7 +1913,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
+				_bt_parallel_release(scan, state, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -1667,7 +1925,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno);
+				status = _bt_parallel_seize(scan, state, &blkno);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -1688,17 +1946,16 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
  * indicate success.
  */
 static bool
-_bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_parallel_readpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+					  ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	_bt_initialize_more_data(state, dir);
 
-	_bt_initialize_more_data(&so->state, dir);
-
-	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
 
 	return true;
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 619f96ce..3e615c2 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -20,16 +20,21 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "catalog/pg_amop.h"
 #include "miscadmin.h"
 #include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 
 
 typedef struct BTSortArrayContext
 {
 	FmgrInfo	flinfo;
+	FmgrInfo	distflinfo;
+	FmgrInfo	distcmpflinfo;
+	ScanKey		distkey;
 	Oid			collation;
 	bool		reverse;
 } BTSortArrayContext;
@@ -49,6 +54,9 @@ static void _bt_mark_scankey_required(ScanKey skey);
 static bool _bt_check_rowcompare(ScanKey skey,
 					 IndexTuple tuple, TupleDesc tupdesc,
 					 ScanDirection dir, bool *continuescan);
+static void _bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype,
+						  FmgrInfo *finfo, int16 *typlen, bool *typbyval);
+
 
 
 /*
@@ -445,6 +453,7 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 {
 	Relation	rel = scan->indexRelation;
 	Oid			elemtype;
+	Oid			opfamily;
 	RegProcedure cmp_proc;
 	BTSortArrayContext cxt;
 	int			last_non_dup;
@@ -462,6 +471,53 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 	if (elemtype == InvalidOid)
 		elemtype = rel->rd_opcintype[skey->sk_attno - 1];
 
+	opfamily = rel->rd_opfamily[skey->sk_attno - 1];
+
+	if (scan->numberOfOrderBys <= 0)
+	{
+		cxt.distkey = NULL;
+		cxt.reverse = reverse;
+	}
+	else
+	{
+		/* Init procedures for distance calculation and comparison. */
+		ScanKey		distkey = &scan->orderByData[0];
+		ScanKeyData	distkey2;
+		Oid			disttype = distkey->sk_subtype;
+		Oid			distopr;
+		RegProcedure distproc;
+
+		if (!OidIsValid(disttype))
+			disttype = rel->rd_opcintype[skey->sk_attno - 1];
+
+		/* Lookup distance operator in index column's operator family. */
+		distopr = get_opfamily_member(opfamily,
+									  elemtype,
+									  disttype,
+									  distkey->sk_strategy);
+
+		if (!OidIsValid(distopr))
+			elog(ERROR, "missing operator (%u,%u) for strategy %d in opfamily %u",
+				 elemtype, disttype, BtreeKNNSearchStrategyNumber, opfamily);
+
+		distproc = get_opcode(distopr);
+
+		if (!RegProcedureIsValid(distproc))
+			elog(ERROR, "missing code for operator %u", distopr);
+
+		fmgr_info(distproc, &cxt.distflinfo);
+
+		distkey2 = *distkey;
+		fmgr_info_copy(&distkey2.sk_func, &cxt.distflinfo, CurrentMemoryContext);
+		distkey2.sk_subtype = disttype;
+
+		_bt_get_distance_cmp_proc(&distkey2, opfamily, elemtype,
+								  &cxt.distcmpflinfo, NULL, NULL);
+
+		cxt.distkey = distkey;
+		cxt.reverse = false;	/* supported only ascending ordering */
+	}
+
 	/*
 	 * Look up the appropriate comparison function in the opfamily.
 	 *
@@ -470,19 +526,17 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 	 * non-cross-type support functions for any datatype that it supports at
 	 * all.
 	 */
-	cmp_proc = get_opfamily_proc(rel->rd_opfamily[skey->sk_attno - 1],
+	cmp_proc = get_opfamily_proc(opfamily,
 								 elemtype,
 								 elemtype,
 								 BTORDER_PROC);
 	if (!RegProcedureIsValid(cmp_proc))
 		elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
-			 BTORDER_PROC, elemtype, elemtype,
-			 rel->rd_opfamily[skey->sk_attno - 1]);
+			 BTORDER_PROC, elemtype, elemtype, opfamily);
 
 	/* Sort the array elements */
 	fmgr_info(cmp_proc, &cxt.flinfo);
 	cxt.collation = skey->sk_collation;
-	cxt.reverse = reverse;
 	qsort_arg((void *) elems, nelems, sizeof(Datum),
 			  _bt_compare_array_elements, (void *) &cxt);
 
@@ -514,6 +568,23 @@ _bt_compare_array_elements(const void *a, const void *b, void *arg)
 	BTSortArrayContext *cxt = (BTSortArrayContext *) arg;
 	int32		compare;
 
+	if (cxt->distkey)
+	{
+		Datum		dista = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  da,
+											  cxt->distkey->sk_argument);
+		Datum		distb = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  db,
+											  cxt->distkey->sk_argument);
+		bool		cmp = DatumGetBool(FunctionCall2Coll(&cxt->distcmpflinfo,
+														 cxt->collation,
+														 dista,
+														 distb));
+		return cmp ? -1 : 1;
+	}
+
 	compare = DatumGetInt32(FunctionCall2Coll(&cxt->flinfo,
 											  cxt->collation,
 											  da, db));
@@ -2075,6 +2146,39 @@ btproperty(Oid index_oid, int attno,
 			*res = true;
 			return true;
 
+		case AMPROP_DISTANCE_ORDERABLE:
+			{
+				Oid			opclass,
+							opfamily,
+							opcindtype;
+
+				/* answer only for columns, not AM or whole index */
+				if (attno == 0)
+					return false;
+
+				opclass = get_index_column_opclass(index_oid, attno);
+
+				if (!OidIsValid(opclass))
+				{
+					*res = false;	/* non-key attribute */
+					return true;
+				}
+
+				if (!get_opclass_opfamily_and_input_type(opclass,
+													 &opfamily, &opcindtype))
+				{
+					*isnull = true;
+					return true;
+				}
+
+				*res = SearchSysCacheExists(AMOPSTRATEGY,
+											ObjectIdGetDatum(opfamily),
+											ObjectIdGetDatum(opcindtype),
+											ObjectIdGetDatum(opcindtype),
+								   Int16GetDatum(BtreeKNNSearchStrategyNumber));
+				return true;
+			}
+
 		default:
 			return false;		/* punt to generic code */
 	}
@@ -2223,3 +2327,264 @@ _bt_allocate_tuple_workspaces(BTScanState state)
 	state->currTuples = (char *) palloc(BLCKSZ * 2);
 	state->markTuples = state->currTuples + BLCKSZ;
 }
+
+static bool
+_bt_compare_row_key_with_ordering_key(ScanKey row, ScanKey ord, bool *result)
+{
+	ScanKey		subkey = (ScanKey) DatumGetPointer(row->sk_argument);
+	int32		cmpresult;
+
+	Assert(subkey->sk_attno == 1);
+	Assert(subkey->sk_flags & SK_ROW_MEMBER);
+
+	if (subkey->sk_flags & SK_ISNULL)
+		return false;
+
+	/* Perform the test --- three-way comparison not bool operator */
+	cmpresult = DatumGetInt32(FunctionCall2Coll(&subkey->sk_func,
+												subkey->sk_collation,
+												ord->sk_argument,
+												subkey->sk_argument));
+
+	if (subkey->sk_flags & SK_BT_DESC)
+		cmpresult = -cmpresult;
+
+	/*
+	 * At this point cmpresult indicates the overall result of the row
+	 * comparison, and subkey points to the deciding column (or the last
+	 * column if the result is "=").
+	 */
+	switch (subkey->sk_strategy)
+	{
+			/* EQ and NE cases aren't allowed here */
+		case BTLessStrategyNumber:
+			*result = cmpresult < 0;
+			break;
+		case BTLessEqualStrategyNumber:
+			*result = cmpresult <= 0;
+			break;
+		case BTGreaterEqualStrategyNumber:
+			*result = cmpresult >= 0;
+			break;
+		case BTGreaterStrategyNumber:
+			*result = cmpresult > 0;
+			break;
+		default:
+			elog(ERROR, "unrecognized RowCompareType: %d",
+				 (int) subkey->sk_strategy);
+			*result = false;	/* keep compiler quiet */
+	}
+
+	return true;
+}
+
+/* _bt_select_knn_search_strategy() -- Determine which KNN scan strategy to use:
+ *		bidirectional or unidirectional. We are checking here if the
+ *		ordering scankey argument falls into the scan range: if it falls
+ *		we must use bidirectional scan, otherwise we use unidirectional.
+ *
+ *	Returns BtreeKNNSearchStrategyNumber for bidirectional scan or
+ *			strategy number of non-matched scankey for unidirectional.
+ */
+static StrategyNumber
+_bt_select_knn_search_strategy(IndexScanDesc scan, ScanKey ord)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	ScanKey		cond;
+
+	for (cond = so->keyData; cond < so->keyData + so->numberOfKeys; cond++)
+	{
+		bool		result;
+
+		if (cond->sk_attno != 1)
+			break; /* only interesting in the first index attribute */
+
+		if (cond->sk_strategy == BTEqualStrategyNumber)
+			/* always use simple unidirectional scan for equals operators */
+			return BTEqualStrategyNumber;
+
+		if (cond->sk_flags & SK_ROW_HEADER)
+		{
+			if (!_bt_compare_row_key_with_ordering_key(cond, ord, &result))
+				return BTEqualStrategyNumber; /* ROW(fist_index_attr, ...) IS NULL */
+		}
+		else
+		{
+			if (!_bt_compare_scankey_args(scan, cond, ord, cond, &result))
+				elog(ERROR, "could not compare ordering key");
+		}
+
+		if (!result)
+			/*
+			 * Ordering scankey argument is out of scan range,
+			 * use unidirectional scan.
+			 */
+			return cond->sk_strategy;
+	}
+
+	return BtreeKNNSearchStrategyNumber; /* use bidirectional scan */
+}
+
+static Oid
+_bt_get_sortfamily_for_opfamily_op(Oid opfamily, Oid lefttype, Oid righttype,
+								   StrategyNumber strategy)
+{
+	HeapTuple	tp;
+	Form_pg_amop amop_tup;
+	Oid			sortfamily;
+
+	tp = SearchSysCache4(AMOPSTRATEGY,
+						 ObjectIdGetDatum(opfamily),
+						 ObjectIdGetDatum(lefttype),
+						 ObjectIdGetDatum(righttype),
+						 Int16GetDatum(strategy));
+	if (!HeapTupleIsValid(tp))
+		return InvalidOid;
+	amop_tup = (Form_pg_amop) GETSTRUCT(tp);
+	sortfamily = amop_tup->amopsortfamily;
+	ReleaseSysCache(tp);
+
+	return sortfamily;
+}
+
+/*
+ * _bt_get_distance_cmp_proc() -- Init procedure for comparsion of distances
+ *		between "leftargtype" and "distkey".
+ */
+static void
+_bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype,
+						  FmgrInfo *finfo, int16 *typlen, bool *typbyval)
+{
+	RegProcedure opcode;
+	Oid			sortfamily;
+	Oid			opno;
+	Oid			distanceType;
+
+	distanceType = get_func_rettype(distkey->sk_func.fn_oid);
+
+	sortfamily = _bt_get_sortfamily_for_opfamily_op(opfamily, leftargtype,
+													distkey->sk_subtype,
+													distkey->sk_strategy);
+
+	if (!OidIsValid(sortfamily))
+		elog(ERROR, "could not find sort family for btree ordering operator");
+
+	opno = get_opfamily_member(sortfamily,
+							   distanceType,
+							   distanceType,
+							   BTLessEqualStrategyNumber);
+
+	if (!OidIsValid(opno))
+		elog(ERROR, "could not find operator for btree distance comparison");
+
+	opcode = get_opcode(opno);
+
+	if (!RegProcedureIsValid(opcode))
+		elog(ERROR,
+			"could not find procedure for btree distance comparison operator");
+
+	fmgr_info(opcode, finfo);
+
+	if (typlen)
+		get_typlenbyval(distanceType, typlen, typbyval);
+}
+
+/*
+ *  _bt_init_distance_comparison() -- Init distance typlen/typbyval and its
+ *  	comparison procedure.
+ */
+static void
+_bt_init_distance_comparison(IndexScanDesc scan, ScanKey ord)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	Relation	rel = scan->indexRelation;
+
+	_bt_get_distance_cmp_proc(ord,
+							  rel->rd_opfamily[ord->sk_attno - 1],
+							  rel->rd_opcintype[ord->sk_attno - 1],
+							  &so->distanceCmpProc,
+							  &so->distanceTypeLen,
+							  &so->distanceTypeByVal);
+
+	if (!so->distanceTypeByVal)
+	{
+		so->state.currDistance = PointerGetDatum(NULL);
+		so->state.markDistance = PointerGetDatum(NULL);
+	}
+}
+
+/*
+ * _bt_process_orderings() -- Process ORDER BY distance scankeys and
+ *		select corresponding KNN strategy.
+ *
+ * If bidirectional scan is selected then one scankey is initialized
+ * using bufKeys and placed into startKeys/keysCount, true is returned.
+ *
+ * Otherwise, so->scanDirection is set and false is returned.
+ */
+bool
+_bt_process_orderings(IndexScanDesc scan, ScanKey *startKeys, int *keysCount,
+					  ScanKeyData bufKeys[])
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	ScanKey		ord = scan->orderByData;
+
+	if (scan->numberOfOrderBys > 1 || ord->sk_attno != 1)
+		/* it should not happen, see btmatchorderby() */
+		elog(ERROR, "only one btree ordering operator "
+					"for the first index column is supported");
+
+	Assert(ord->sk_strategy == BtreeKNNSearchStrategyNumber);
+
+	switch (_bt_select_knn_search_strategy(scan, ord))
+	{
+		case BTLessStrategyNumber:
+		case BTLessEqualStrategyNumber:
+			/*
+			 * Ordering key argument is greater than all values in scan range.
+			 * select backward scan direction.
+			 */
+			so->scanDirection = BackwardScanDirection;
+			return false;
+
+		case BTEqualStrategyNumber:
+			/* Use default unidirectional scan direction. */
+			return false;
+
+		case BTGreaterEqualStrategyNumber:
+		case BTGreaterStrategyNumber:
+			/*
+			 * Ordering key argument is lesser than all values in scan range.
+			 * select forward scan direction.
+			 */
+			so->scanDirection = ForwardScanDirection;
+			return false;
+
+		case BtreeKNNSearchStrategyNumber:
+			/*
+			 * Ordering key argument falls into scan range,
+			 * use bidirectional scan.
+			 */
+			break;
+	}
+
+	_bt_init_distance_comparison(scan, ord);
+
+	/* Init btree search key with ordering key argument. */
+	ScanKeyEntryInitialize(&bufKeys[0],
+					 (scan->indexRelation->rd_indoption[ord->sk_attno - 1] <<
+					  SK_BT_INDOPTION_SHIFT) |
+						   SK_ORDER_BY |
+						   SK_SEARCHNULL /* only for invalid procedure oid, see
+										  * assert in ScanKeyEntryInitialize */,
+						   ord->sk_attno,
+						   BtreeKNNSearchStrategyNumber,
+						   ord->sk_subtype,
+						   ord->sk_collation,
+						   InvalidOid,
+						   ord->sk_argument);
+
+	startKeys[(*keysCount)++] = &bufKeys[0];
+
+	return true;
+}
diff --git a/src/backend/access/nbtree/nbtvalidate.c b/src/backend/access/nbtree/nbtvalidate.c
index f24091c..be4e843 100644
--- a/src/backend/access/nbtree/nbtvalidate.c
+++ b/src/backend/access/nbtree/nbtvalidate.c
@@ -22,9 +22,17 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_type.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/regproc.h"
 #include "utils/syscache.h"
 
+#define BTRequiredOperatorSet \
+		((1 << BTLessStrategyNumber) | \
+		 (1 << BTLessEqualStrategyNumber) | \
+		 (1 << BTEqualStrategyNumber) | \
+		 (1 << BTGreaterEqualStrategyNumber) | \
+		 (1 << BTGreaterStrategyNumber))
+
 
 /*
  * Validator for a btree opclass.
@@ -132,10 +140,11 @@ btvalidate(Oid opclassoid)
 	{
 		HeapTuple	oprtup = &oprlist->members[i]->tuple;
 		Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
+		Oid			op_rettype;
 
 		/* Check that only allowed strategy numbers exist */
 		if (oprform->amopstrategy < 1 ||
-			oprform->amopstrategy > BTMaxStrategyNumber)
+			oprform->amopstrategy > BtreeMaxStrategyNumber)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -146,20 +155,29 @@ btvalidate(Oid opclassoid)
 			result = false;
 		}
 
-		/* btree doesn't support ORDER BY operators */
-		if (oprform->amoppurpose != AMOP_SEARCH ||
-			OidIsValid(oprform->amopsortfamily))
+		/* btree supports ORDER BY operators */
+		if (oprform->amoppurpose != AMOP_SEARCH)
 		{
-			ereport(INFO,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
-							opfamilyname, "btree",
-							format_operator(oprform->amopopr))));
-			result = false;
+			/* ... and operator result must match the claimed btree opfamily */
+			op_rettype = get_op_rettype(oprform->amopopr);
+			if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype))
+			{
+				ereport(INFO,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
+								opfamilyname, "btree",
+								format_operator(oprform->amopopr))));
+				result = false;
+			}
+		}
+		else
+		{
+			/* Search operators must always return bool */
+			op_rettype = BOOLOID;
 		}
 
 		/* Check operator signature --- same for all btree strategies */
-		if (!check_amop_signature(oprform->amopopr, BOOLOID,
+		if (!check_amop_signature(oprform->amopopr, op_rettype,
 								  oprform->amoplefttype,
 								  oprform->amoprighttype))
 		{
@@ -214,12 +232,8 @@ btvalidate(Oid opclassoid)
 		 * or support functions for this datatype pair.  The only things
 		 * considered optional are the sortsupport and in_range functions.
 		 */
-		if (thisgroup->operatorset !=
-			((1 << BTLessStrategyNumber) |
-			 (1 << BTLessEqualStrategyNumber) |
-			 (1 << BTEqualStrategyNumber) |
-			 (1 << BTGreaterEqualStrategyNumber) |
-			 (1 << BTGreaterStrategyNumber)))
+		if ((thisgroup->operatorset & BTRequiredOperatorSet) !=
+			BTRequiredOperatorSet)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 2c21eae..24dc877 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -989,6 +989,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	 * if we are only trying to build bitmap indexscans, nor if we have to
 	 * assume the scan is unordered.
 	 */
+	useful_pathkeys = NIL;
+	orderbyclauses = NIL;
+	orderbyclausecols = NIL;
+
 	pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN &&
 								!found_lower_saop_clause &&
 								has_useful_pathkeys(root, rel));
@@ -999,10 +1003,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 											  ForwardScanDirection);
 		useful_pathkeys = truncate_useless_pathkeys(root, rel,
 													index_pathkeys);
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
 	}
-	else if (index->ammatchorderby && pathkeys_possibly_useful)
+
+	if (useful_pathkeys == NIL &&
+		index->ammatchorderby && pathkeys_possibly_useful)
 	{
 		/* see if we can generate ordering operators for query_pathkeys */
 		match_pathkeys_to_index(index, root->query_pathkeys,
@@ -1013,12 +1017,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		else
 			useful_pathkeys = NIL;
 	}
-	else
-	{
-		useful_pathkeys = NIL;
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
-	}
 
 	/*
 	 * 3. Check if an index-only scan is possible.  If we're not building
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 1bfee58..a5207f1 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -459,6 +459,12 @@ typedef struct BTScanStateData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+
+	/* KNN-search fields: */
+	Datum		currDistance;	/* current distance */
+	Datum		markDistance;	/* marked distance */
+	bool		currIsNull;		/* current item is NULL */
+	bool		markIsNull;		/* marked item is NULL */
 }	BTScanStateData, *BTScanState;
 
 typedef struct BTScanOpaqueData
@@ -477,7 +483,17 @@ typedef struct BTScanOpaqueData
 	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
 	MemoryContext arrayContext; /* scan-lifespan context for array data */
 
-	BTScanStateData	state;
+	BTScanStateData state;		/* main scan state */
+
+	/* KNN-search fields: */
+	BTScanState knnState;			/* optional scan state for KNN search */
+	ScanDirection scanDirection;	/* selected scan direction for
+									 * unidirectional KNN scan */
+	FmgrInfo	distanceCmpProc;	/* distance comparison procedure */
+	int16		distanceTypeLen;	/* distance typlen */
+	bool		distanceTypeByVal;	/* distance typebyval */
+	bool		currRightIsNearest;	/* current right item is nearest */
+	bool		markRightIsNearest;	/* marked right item is nearest */
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -523,11 +539,12 @@ extern bool btcanreturn(Relation index, int attno);
 /*
  * prototypes for internal functions in nbtree.c
  */
-extern bool _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno);
-extern void _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page);
-extern void _bt_parallel_done(IndexScanDesc scan);
+extern bool _bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno);
+extern void _bt_parallel_release(IndexScanDesc scan, BTScanState state, BlockNumber scan_page);
+extern void _bt_parallel_done(IndexScanDesc scan, BTScanState state);
 extern void _bt_parallel_advance_array_keys(IndexScanDesc scan);
 
+
 /*
  * prototypes for functions in nbtinsert.c
  */
@@ -608,6 +625,8 @@ extern bool btproperty(Oid index_oid, int attno,
 extern IndexTuple _bt_nonkey_truncate(Relation rel, IndexTuple itup);
 extern bool _bt_check_natts(Relation rel, Page page, OffsetNumber offnum);
 extern void _bt_allocate_tuple_workspaces(BTScanState state);
+extern bool _bt_process_orderings(IndexScanDesc scan,
+				  ScanKey *startKeys, int *keysCount, ScanKeyData bufKeys[]);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/include/access/stratnum.h b/src/include/access/stratnum.h
index 0db11a1..f44b41a 100644
--- a/src/include/access/stratnum.h
+++ b/src/include/access/stratnum.h
@@ -32,7 +32,10 @@ typedef uint16 StrategyNumber;
 #define BTGreaterEqualStrategyNumber	4
 #define BTGreaterStrategyNumber			5
 
-#define BTMaxStrategyNumber				5
+#define BTMaxStrategyNumber				5		/* number of canonical B-tree strategies */
+
+#define BtreeKNNSearchStrategyNumber	6		/* for <-> (distance) */
+#define BtreeMaxStrategyNumber			6		/* number of extended B-tree strategies */
 
 
 /*
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index 6faa9d7..c75ef39 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -347,10 +347,10 @@ ROLLBACK;
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
 ERROR:  access method "invalid_index_method" does not exist
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 6, must be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 0, must be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 7, must be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 0, must be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ERROR:  operator argument types must be specified in ALTER OPERATOR FAMILY
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -397,11 +397,12 @@ DROP OPERATOR FAMILY alt_opf8 USING btree;
 CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
-ERROR:  access method "btree" does not support ordering operators
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
 ALTER OPERATOR FAMILY alt_opf11 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql
index 84fd900..73e6e206 100644
--- a/src/test/regress/sql/alter_generic.sql
+++ b/src/test/regress/sql/alter_generic.sql
@@ -295,8 +295,8 @@ ROLLBACK;
 -- Should fail. Invalid values for ALTER OPERATOR FAMILY .. ADD / DROP
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 6 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -340,10 +340,12 @@ CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
 
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
-- 
2.7.4

0005-Add-btree-distance-operators-v04.patchtext/x-patch; name=0005-Add-btree-distance-operators-v04.patchDownload
From 1084ec189c0f17fa89c0ff4fd45ec9a2bde593fe Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 30 Nov 2018 14:56:54 +0300
Subject: [PATCH 5/7] Add btree distance operators

---
 doc/src/sgml/indices.sgml                 |   6 +
 src/backend/utils/adt/cash.c              |  20 +++
 src/backend/utils/adt/date.c              | 113 ++++++++++++++++
 src/backend/utils/adt/float.c             |  41 ++++++
 src/backend/utils/adt/int.c               |  52 ++++++++
 src/backend/utils/adt/int8.c              |  44 ++++++
 src/backend/utils/adt/oid.c               |  14 ++
 src/backend/utils/adt/timestamp.c         | 103 ++++++++++++++
 src/include/catalog/pg_amop.dat           | 104 +++++++++++++++
 src/include/catalog/pg_operator.dat       | 108 +++++++++++++++
 src/include/catalog/pg_proc.dat           |  82 ++++++++++++
 src/include/utils/datetime.h              |   2 +
 src/include/utils/timestamp.h             |   4 +-
 src/test/regress/expected/amutils.out     |   6 +-
 src/test/regress/expected/date.out        |  61 +++++++++
 src/test/regress/expected/float4.out      |  20 +++
 src/test/regress/expected/float8.out      |  21 +++
 src/test/regress/expected/int2.out        |  33 +++++
 src/test/regress/expected/int4.out        |  32 +++++
 src/test/regress/expected/int8.out        |  31 +++++
 src/test/regress/expected/interval.out    |  15 +++
 src/test/regress/expected/money.out       |   6 +
 src/test/regress/expected/oid.out         |  13 ++
 src/test/regress/expected/opr_sanity.out  |   3 +-
 src/test/regress/expected/time.out        |  16 +++
 src/test/regress/expected/timestamp.out   | 211 +++++++++++++++++++++++++++++
 src/test/regress/expected/timestamptz.out | 214 ++++++++++++++++++++++++++++++
 src/test/regress/sql/date.sql             |   5 +
 src/test/regress/sql/float4.sql           |   3 +
 src/test/regress/sql/float8.sql           |   3 +
 src/test/regress/sql/int2.sql             |  10 ++
 src/test/regress/sql/int4.sql             |  10 ++
 src/test/regress/sql/int8.sql             |   5 +
 src/test/regress/sql/interval.sql         |   2 +
 src/test/regress/sql/money.sql            |   1 +
 src/test/regress/sql/oid.sql              |   2 +
 src/test/regress/sql/time.sql             |   3 +
 src/test/regress/sql/timestamp.sql        |   8 ++
 src/test/regress/sql/timestamptz.sql      |   8 ++
 39 files changed, 1430 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index caec484..e656a8a 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -183,6 +183,12 @@ SELECT * FROM events ORDER BY event_date <-> date '2017-05-05' LIMIT 10;
 </programlisting>
    which finds the ten events closest to a given target date.  The ability
    to do this is again dependent on the particular operator class being used.
+   Built-in B-tree operator classes support distance ordering for data types
+   <type>int2</>, <type>int4</>, <type>int8</>,
+   <type>float4</>, <type>float8</>, <type>numeric</>,
+   <type>timestamp with time zone</>, <type>timestamp without time zone</>,
+   <type>time with time zone</>, <type>time without time zone</>,
+   <type>date</>, <type>interval</>, <type>oid</>, <type>money</>.
   </para>
 
   <para>
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index c92e9d5..b6bfbff 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -30,6 +30,7 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+#define SAMESIGN(a,b)	(((a) < 0) == ((b) < 0))
 
 /*************************************************************************
  * Private routines
@@ -1157,3 +1158,22 @@ int8_cash(PG_FUNCTION_ARGS)
 
 	PG_RETURN_CASH(result);
 }
+
+Datum
+cash_dist(PG_FUNCTION_ARGS)
+{
+	Cash		a = PG_GETARG_CASH(0);
+	Cash		b = PG_GETARG_CASH(1);
+	Cash		r;
+	Cash		ra;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("money out of range")));
+
+	ra = Abs(r);
+
+	PG_RETURN_CASH(ra);
+}
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index cb6b5e55..237c3cb 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -562,6 +562,17 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+Datum
+date_dist(PG_FUNCTION_ARGS)
+{
+	/* we assume the difference can't overflow */
+	Datum		diff = DirectFunctionCall2(date_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
+}
+
 /*
  * Internal routines for promoting date to timestamp and timestamp with
  * time zone
@@ -759,6 +770,29 @@ date_cmp_timestamp(PG_FUNCTION_ARGS)
 }
 
 Datum
+date_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+	Timestamp	dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
+Datum
 date_eq_timestamptz(PG_FUNCTION_ARGS)
 {
 	DateADT		dateVal = PG_GETARG_DATEADT(0);
@@ -843,6 +877,30 @@ date_cmp_timestamptz(PG_FUNCTION_ARGS)
 }
 
 Datum
+date_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	TimestampTz	dt2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz	dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
+
+
+Datum
 timestamp_eq_date(PG_FUNCTION_ARGS)
 {
 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
@@ -927,6 +985,29 @@ timestamp_cmp_date(PG_FUNCTION_ARGS)
 }
 
 Datum
+timestamp_dist_date(PG_FUNCTION_ARGS)
+{
+	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	Timestamp	dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
+Datum
 timestamptz_eq_date(PG_FUNCTION_ARGS)
 {
 	TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
@@ -1038,6 +1119,28 @@ in_range_date_interval(PG_FUNCTION_ARGS)
 							   BoolGetDatum(less));
 }
 
+Datum
+timestamptz_dist_date(PG_FUNCTION_ARGS)
+{
+	TimestampTz	dt1 = PG_GETARG_TIMESTAMPTZ(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	TimestampTz	dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
 
 /* Add an interval to a date, giving a new date.
  * Must handle both positive and negative intervals.
@@ -1946,6 +2049,16 @@ time_part(PG_FUNCTION_ARGS)
 	PG_RETURN_FLOAT8(result);
 }
 
+Datum
+time_dist(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(time_mi_time,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
+
 
 /*****************************************************************************
  *	 Time With Time Zone ADT
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index cf9327f..f8c2702 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -3641,6 +3641,47 @@ width_bucket_float8(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(result);
 }
 
+Datum
+float4_dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float4		r = float4_mi(a, b);
+
+	PG_RETURN_FLOAT4(Abs(r));
+}
+
+Datum
+float8_dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
+
+Datum
+float48_dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
+Datum
+float84_dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
 /* ========== PRIVATE ROUTINES ========== */
 
 #ifndef HAVE_CBRT
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 8149dc1..9335746 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -1427,3 +1427,55 @@ generate_series_step_int4(PG_FUNCTION_ARGS)
 		/* do when there is no more left */
 		SRF_RETURN_DONE(funcctx);
 }
+
+Datum
+int2_dist(PG_FUNCTION_ARGS)
+{
+	int16		a = PG_GETARG_INT16(0);
+	int16		b = PG_GETARG_INT16(1);
+	int16		r;
+	int16		ra;
+
+	if (pg_sub_s16_overflow(a, b, &r) ||
+		r == PG_INT16_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("smallint out of range")));
+
+	ra = Abs(r);
+
+	PG_RETURN_INT16(ra);
+}
+
+static int32
+int44_dist(int32 a, int32 b)
+{
+	int32		r;
+
+	if (pg_sub_s32_overflow(a, b, &r) ||
+		r == PG_INT32_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("integer out of range")));
+
+	return Abs(r);
+}
+
+
+Datum
+int4_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int24_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT16(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int42_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT16(1)));
+}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 437d418..8bf8eda 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1373,3 +1373,47 @@ generate_series_step_int8(PG_FUNCTION_ARGS)
 		/* do when there is no more left */
 		SRF_RETURN_DONE(funcctx);
 }
+
+static int64
+int88_dist(int64 a, int64 b)
+{
+	int64		r;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("bigint out of range")));
+
+	return Abs(r);
+}
+
+Datum
+int8_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int82_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT16(1)));
+}
+
+Datum
+int84_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT32(1)));
+}
+
+Datum
+int28_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT16(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int48_dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT32(0), PG_GETARG_INT64(1)));
+}
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index b0670e0..536507b 100644
--- a/src/backend/utils/adt/oid.c
+++ b/src/backend/utils/adt/oid.c
@@ -469,3 +469,17 @@ oidvectorgt(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(cmp > 0);
 }
+
+Datum
+oid_dist(PG_FUNCTION_ARGS)
+{
+	Oid			a = PG_GETARG_OID(0);
+	Oid			b = PG_GETARG_OID(1);
+	Oid			res;
+
+	if (a < b)
+		res = b - a;
+	else
+		res = a - b;
+	PG_RETURN_OID(res);
+}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index b377c38..b536645 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -2672,6 +2672,86 @@ timestamp_mi(PG_FUNCTION_ARGS)
 	PG_RETURN_INTERVAL_P(result);
 }
 
+Datum
+timestamp_dist(PG_FUNCTION_ARGS)
+{
+	Timestamp	a = PG_GETARG_TIMESTAMP(0);
+	Timestamp	b = PG_GETARG_TIMESTAMP(1);
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		Interval   *p = palloc(sizeof(Interval));
+
+		p->day = INT_MAX;
+		p->month = INT_MAX;
+		p->time = PG_INT64_MAX;
+		PG_RETURN_INTERVAL_P(p);
+	}
+	else
+		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+												  PG_GETARG_DATUM(0),
+												  PG_GETARG_DATUM(1)));
+	PG_RETURN_INTERVAL_P(abs_interval(r));
+}
+
+Interval *
+timestamp_dist_internal(Timestamp a, Timestamp b)
+{
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		return r;
+	}
+
+	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+											  TimestampGetDatum(a),
+											  TimestampGetDatum(b)));
+
+	return abs_interval(r);
+}
+
+Datum
+timestamptz_dist(PG_FUNCTION_ARGS)
+{
+	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
+	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(a, b));
+}
+
+Datum
+timestamp_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	Timestamp	ts1 = PG_GETARG_TIMESTAMP(0);
+	TimestampTz	tstz2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz	tstz1;
+
+	tstz1 = timestamp2timestamptz(ts1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+Datum
+timestamptz_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	TimestampTz	tstz1 = PG_GETARG_TIMESTAMPTZ(0);
+	Timestamp	ts2 = PG_GETARG_TIMESTAMP(1);
+	TimestampTz	tstz2;
+
+	tstz2 = timestamp2timestamptz(ts2);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+
 /*
  *	interval_justify_interval()
  *
@@ -3541,6 +3621,29 @@ interval_avg(PG_FUNCTION_ARGS)
 							   Float8GetDatum((double) N.time));
 }
 
+Interval *
+abs_interval(Interval *a)
+{
+	static Interval zero = {0, 0, 0};
+
+	if (DatumGetBool(DirectFunctionCall2(interval_lt,
+										 IntervalPGetDatum(a),
+										 IntervalPGetDatum(&zero))))
+		a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
+												  IntervalPGetDatum(a)));
+
+	return a;
+}
+
+Datum
+interval_dist(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(interval_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
 
 /* timestamp_age()
  * Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 075a54c..ded5951 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -30,6 +30,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int2,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int24
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -47,6 +51,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int2,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int28
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -64,6 +72,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int2,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int4
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -81,6 +93,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int4,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int42
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -98,6 +114,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int4,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int48
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -115,6 +135,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int4,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int8
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -132,6 +156,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int8,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int82
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -149,6 +177,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int8,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int84
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -166,6 +198,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int8,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # btree oid_ops
 
@@ -179,6 +215,10 @@
   amopstrategy => '4', amopopr => '>=(oid,oid)', amopmethod => 'btree' },
 { amopfamily => 'btree/oid_ops', amoplefttype => 'oid', amoprighttype => 'oid',
   amopstrategy => '5', amopopr => '>(oid,oid)', amopmethod => 'btree' },
+{ amopfamily => 'btree/oid_ops', amoplefttype => 'oid',
+  amoprighttype => 'oid', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(oid,oid)', amopmethod => 'btree',
+  amopsortfamily => 'btree/oid_ops' },
 
 # btree tid_ops
 
@@ -229,6 +269,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float4,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float48
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
@@ -246,6 +290,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float4,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # default operators float8
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -263,6 +311,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float8,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float84
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -280,6 +332,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float8,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # btree char_ops
 
@@ -389,6 +445,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(date,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -406,6 +466,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(date,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -423,6 +487,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(date,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -440,6 +508,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamp,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -457,6 +529,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(timestamp,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -474,6 +550,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamp,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -491,6 +571,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -508,6 +592,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'date', amopstrategy => '5',
   amopopr => '>(timestamptz,date)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -525,6 +613,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree time_ops
 
@@ -543,6 +635,10 @@
 { amopfamily => 'btree/time_ops', amoplefttype => 'time',
   amoprighttype => 'time', amopstrategy => '5', amopopr => '>(time,time)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/time_ops', amoplefttype => 'time',
+  amoprighttype => 'time', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(time,time)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree timetz_ops
 
@@ -579,6 +675,10 @@
 { amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
   amoprighttype => 'interval', amopstrategy => '5',
   amopopr => '>(interval,interval)', amopmethod => 'btree' },
+{ amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
+  amoprighttype => 'interval', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(interval,interval)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree macaddr
 
@@ -754,6 +854,10 @@
 { amopfamily => 'btree/money_ops', amoplefttype => 'money',
   amoprighttype => 'money', amopstrategy => '5', amopopr => '>(money,money)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/money_ops', amoplefttype => 'money',
+  amoprighttype => 'money', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(money,money)', amopmethod => 'btree',
+  amopsortfamily => 'btree/money_ops' },
 
 # btree array_ops
 
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index ce23c2f..a2ae8cb 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2791,6 +2791,114 @@
   oprname => '-', oprleft => 'pg_lsn', oprright => 'pg_lsn',
   oprresult => 'numeric', oprcode => 'pg_lsn_mi' },
 
+# distance operators
+{ oid => '4217', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int2', oprresult => 'int2', oprcom => '<->(int2,int2)',
+  oprcode => 'int2_dist'},
+{ oid => '4218', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int4)',
+  oprcode => 'int4_dist'},
+{ oid => '4219', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int8)',
+  oprcode => 'int8_dist'},
+{ oid => '4220', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'oid',
+  oprright => 'oid', oprresult => 'oid', oprcom => '<->(oid,oid)',
+  oprcode => 'oid_dist'},
+{ oid => '4221', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float4', oprresult => 'float4', oprcom => '<->(float4,float4)',
+  oprcode => 'float4_dist'},
+{ oid => '4222', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float8)',
+  oprcode => 'float8_dist'},
+{ oid => '4223', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'money',
+  oprright => 'money', oprresult => 'money', oprcom => '<->(money,money)',
+  oprcode => 'cash_dist'},
+{ oid => '4224', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'date', oprresult => 'int4', oprcom => '<->(date,date)',
+  oprcode => 'date_dist'},
+{ oid => '4225', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'time',
+  oprright => 'time', oprresult => 'interval', oprcom => '<->(time,time)',
+  oprcode => 'time_dist'},
+{ oid => '4226', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamp)',
+  oprcode => 'timestamp_dist'},
+{ oid => '4227', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamptz)',
+  oprcode => 'timestamptz_dist'},
+{ oid => '4228', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'interval',
+  oprright => 'interval', oprresult => 'interval', oprcom => '<->(interval,interval)',
+  oprcode => 'interval_dist'},
+
+# cross-type distance operators
+{ oid => '4229', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int2)',
+  oprcode => 'int24_dist'},
+{ oid => '4230', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int2', oprresult => 'int4', oprcom => '<->(int2,int4)',
+  oprcode => 'int42_dist'},
+{ oid => '4231', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int2)',
+  oprcode => 'int28_dist'},
+{ oid => '4232', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int2', oprresult => 'int8', oprcom => '<->(int2,int8)',
+  oprcode => 'int82_dist'},
+{ oid => '4233', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int4)',
+  oprcode => 'int48_dist'},
+{ oid => '4234', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int4', oprresult => 'int8', oprcom => '<->(int4,int8)',
+  oprcode => 'int84_dist'},
+{ oid => '4235', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float4)',
+  oprcode => 'float48_dist'},
+{ oid => '4236', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float4', oprresult => 'float8', oprcom => '<->(float4,float8)',
+  oprcode => 'float84_dist'},
+{ oid => '4237', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,date)',
+  oprcode => 'date_dist_timestamp'},
+{ oid => '4238', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamp)',
+  oprcode => 'timestamp_dist_date'},
+{ oid => '4239', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,date)',
+  oprcode => 'date_dist_timestamptz'},
+{ oid => '4240', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamptz)',
+  oprcode => 'timestamptz_dist_date'},
+{ oid => '4241', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamp)',
+  oprcode => 'timestamp_dist_timestamptz'},
+{ oid => '4242', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamptz)',
+  oprcode => 'timestamptz_dist_timestamp'},
+
 # enum operators
 { oid => '3516', descr => 'equal',
   oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'anyenum',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 034a41e..ec9ff16 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10048,4 +10048,86 @@
   proargnames => '{rootrelid,relid,parentrelid,isleaf,level}',
   prosrc => 'pg_partition_tree' },
 
+# distance functions
+{ oid => '4243',
+  proname => 'int2_dist', prorettype => 'int2',
+  proargtypes => 'int2 int2', prosrc => 'int2_dist' },
+{ oid => '4244',
+  proname => 'int4_dist', prorettype => 'int4',
+  proargtypes => 'int4 int4', prosrc => 'int4_dist' },
+{ oid => '4245',
+  proname => 'int8_dist', prorettype => 'int8',
+  proargtypes => 'int8 int8', prosrc => 'int8_dist' },
+{ oid => '4246',
+  proname => 'oid_dist', prorettype => 'oid',
+  proargtypes => 'oid oid', prosrc => 'oid_dist' },
+{ oid => '4247',
+  proname => 'float4_dist', prorettype => 'float4',
+  proargtypes => 'float4 float4', prosrc => 'float4_dist' },
+{ oid => '4248',
+  proname => 'float8_dist', prorettype => 'float8',
+  proargtypes => 'float8 float8', prosrc => 'float8_dist' },
+{ oid => '4249',
+  proname => 'cash_dist', prorettype => 'money',
+  proargtypes => 'money money', prosrc => 'cash_dist' },
+{ oid => '4250',
+  proname => 'date_dist', prorettype => 'int4',
+  proargtypes => 'date date', prosrc => 'date_dist' },
+{ oid => '4251',
+  proname => 'time_dist', prorettype => 'interval',
+  proargtypes => 'time time', prosrc => 'time_dist' },
+{ oid => '4252',
+  proname => 'timestamp_dist', prorettype => 'interval',
+  proargtypes => 'timestamp timestamp', prosrc => 'timestamp_dist' },
+{ oid => '4253',
+  proname => 'timestamptz_dist', prorettype => 'interval',
+  proargtypes => 'timestamptz timestamptz', prosrc => 'timestamptz_dist' },
+{ oid => '4254',
+  proname => 'interval_dist', prorettype => 'interval',
+  proargtypes => 'interval interval', prosrc => 'interval_dist' },
+
+# cross-type distance functions
+{ oid => '4255',
+  proname => 'int24_dist', prorettype => 'int4',
+  proargtypes => 'int2 int4', prosrc => 'int24_dist' },
+{ oid => '4256',
+  proname => 'int28_dist', prorettype => 'int8',
+  proargtypes => 'int2 int8', prosrc => 'int28_dist' },
+{ oid => '4257',
+  proname => 'int42_dist', prorettype => 'int4',
+  proargtypes => 'int4 int2', prosrc => 'int42_dist' },
+{ oid => '4258',
+  proname => 'int48_dist', prorettype => 'int8',
+  proargtypes => 'int4 int8', prosrc => 'int48_dist' },
+{ oid => '4259',
+  proname => 'int82_dist', prorettype => 'int8',
+  proargtypes => 'int8 int2', prosrc => 'int82_dist' },
+{ oid => '4260',
+  proname => 'int84_dist', prorettype => 'int8',
+  proargtypes => 'int8 int4', prosrc => 'int84_dist' },
+{ oid => '4261',
+  proname => 'float48_dist', prorettype => 'float8',
+  proargtypes => 'float4 float8', prosrc => 'float48_dist' },
+{ oid => '4262',
+  proname => 'float84_dist', prorettype => 'float8',
+  proargtypes => 'float8 float4', prosrc => 'float84_dist' },
+{ oid => '4263',
+  proname => 'date_dist_timestamp', prorettype => 'interval',
+  proargtypes => 'date timestamp', prosrc => 'date_dist_timestamp' },
+{ oid => '4264',
+  proname => 'date_dist_timestamptz', prorettype => 'interval',
+  proargtypes => 'date timestamptz', prosrc => 'date_dist_timestamptz' },
+{ oid => '4265',
+  proname => 'timestamp_dist_date', prorettype => 'interval',
+  proargtypes => 'timestamp date', prosrc => 'timestamp_dist_date' },
+{ oid => '4266',
+  proname => 'timestamp_dist_timestamptz', prorettype => 'interval',
+  proargtypes => 'timestamp timestamptz', prosrc => 'timestamp_dist_timestamptz' },
+{ oid => '4267',
+  proname => 'timestamptz_dist_date', prorettype => 'interval',
+  proargtypes => 'timestamptz date', prosrc => 'timestamptz_dist_date' },
+{ oid => '4268',
+  proname => 'timestamptz_dist_timestamp', prorettype => 'interval',
+  proargtypes => 'timestamptz timestamp', prosrc => 'timestamptz_dist_timestamp' },
+
 ]
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index de9e9ad..6f8062d 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -338,4 +338,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 					   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern Interval *abs_interval(Interval *a);
+
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 2b3b357..1fa56e5 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -93,9 +93,11 @@ extern Timestamp SetEpochTimestamp(void);
 extern void GetEpochTime(struct pg_tm *tm);
 
 extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern Interval *timestamp_dist_internal(Timestamp a, Timestamp b);
 
-/* timestamp comparison works for timestamptz also */
+/* timestamp comparison and distance works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
+#define timestamptz_dist_internal(dt1,dt2)	timestamp_dist_internal(dt1, dt2)
 
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index 4570a39..630dc6b 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -24,7 +24,7 @@ select prop,
  nulls_first        |    |       | f
  nulls_last         |    |       | t
  orderable          |    |       | t
- distance_orderable |    |       | f
+ distance_orderable |    |       | t
  returnable         |    |       | t
  search_array       |    |       | t
  search_nulls       |    |       | t
@@ -100,7 +100,7 @@ select prop,
  nulls_first        | f     | f    | f    | f            | f           | f   | f
  nulls_last         | t     | f    | f    | f            | f           | f   | f
  orderable          | t     | f    | f    | f            | f           | f   | f
- distance_orderable | f     | f    | t    | f            | t           | f   | f
+ distance_orderable | t     | f    | t    | f            | t           | f   | f
  returnable         | t     | f    | f    | t            | t           | f   | f
  search_array       | t     | f    | f    | f            | f           | f   | f
  search_nulls       | t     | f    | t    | t            | t           | f   | t
@@ -231,7 +231,7 @@ select col, prop, pg_index_column_has_property(o, col, prop)
    1 | desc               | f
    1 | nulls_first        | f
    1 | nulls_last         | t
-   1 | distance_orderable | f
+   1 | distance_orderable | t
    1 | returnable         | t
    1 | bogus              | 
    2 | orderable          | f
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
index 1bcc946..b9b819c 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -1477,3 +1477,64 @@ select make_time(10, 55, 100.1);
 ERROR:  time field value out of range: 10:55:100.1
 select make_time(24, 0, 2.1);
 ERROR:  time field value out of range: 24:00:2.1
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+ Fifteen | Distance 
+---------+----------
+         |    16006
+         |    15941
+         |     1802
+         |     1801
+         |     1800
+         |     1799
+         |     1436
+         |     1435
+         |     1434
+         |      308
+         |      307
+         |      306
+         |    13578
+         |    13944
+         |    14311
+(15 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+ Fifteen |               Distance                
+---------+---------------------------------------
+         | @ 16006 days 1 hour 23 mins 45 secs
+         | @ 15941 days 1 hour 23 mins 45 secs
+         | @ 1802 days 1 hour 23 mins 45 secs
+         | @ 1801 days 1 hour 23 mins 45 secs
+         | @ 1800 days 1 hour 23 mins 45 secs
+         | @ 1799 days 1 hour 23 mins 45 secs
+         | @ 1436 days 1 hour 23 mins 45 secs
+         | @ 1435 days 1 hour 23 mins 45 secs
+         | @ 1434 days 1 hour 23 mins 45 secs
+         | @ 308 days 1 hour 23 mins 45 secs
+         | @ 307 days 1 hour 23 mins 45 secs
+         | @ 306 days 1 hour 23 mins 45 secs
+         | @ 13577 days 22 hours 36 mins 15 secs
+         | @ 13943 days 22 hours 36 mins 15 secs
+         | @ 14310 days 22 hours 36 mins 15 secs
+(15 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
+ Fifteen |               Distance                
+---------+---------------------------------------
+         | @ 16005 days 14 hours 23 mins 45 secs
+         | @ 15940 days 14 hours 23 mins 45 secs
+         | @ 1801 days 14 hours 23 mins 45 secs
+         | @ 1800 days 14 hours 23 mins 45 secs
+         | @ 1799 days 14 hours 23 mins 45 secs
+         | @ 1798 days 14 hours 23 mins 45 secs
+         | @ 1435 days 14 hours 23 mins 45 secs
+         | @ 1434 days 14 hours 23 mins 45 secs
+         | @ 1433 days 14 hours 23 mins 45 secs
+         | @ 307 days 14 hours 23 mins 45 secs
+         | @ 306 days 14 hours 23 mins 45 secs
+         | @ 305 days 15 hours 23 mins 45 secs
+         | @ 13578 days 8 hours 36 mins 15 secs
+         | @ 13944 days 8 hours 36 mins 15 secs
+         | @ 14311 days 8 hours 36 mins 15 secs
+(15 rows)
+
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index 2f47e1c..a9dddc4 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -257,6 +257,26 @@ SELECT '' AS five, * FROM FLOAT4_TBL;
       | -1.23457e-20
 (5 rows)
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+ five |      f1      |    dist     
+------+--------------+-------------
+      |            0 |      1004.3
+      |       -34.84 |     1039.14
+      |      -1004.3 |      2008.6
+      | -1.23457e+20 | 1.23457e+20
+      | -1.23457e-20 |      1004.3
+(5 rows)
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+ five |      f1      |         dist         
+------+--------------+----------------------
+      |            0 |               1004.3
+      |       -34.84 |     1039.14000015259
+      |      -1004.3 |     2008.59998779297
+      | -1.23457e+20 | 1.23456789557014e+20
+      | -1.23457e-20 |               1004.3
+(5 rows)
+
 -- test edge-case coercions to integer
 SELECT '32767.4'::float4::int2;
  int2  
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
index 75c0bf3..abf9ef1 100644
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -404,6 +404,27 @@ SELECT '' AS five, f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
       | 1.2345678901234e-200 |  2.3112042409018e-67
 (5 rows)
 
+-- distance
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+ five |          f1          |         dist         
+------+----------------------+----------------------
+      |                    0 |               1004.3
+      |               1004.3 |                    0
+      |               -34.84 |              1039.14
+      | 1.2345678901234e+200 | 1.2345678901234e+200
+      | 1.2345678901234e-200 |               1004.3
+(5 rows)
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
+ five |          f1          |         dist         
+------+----------------------+----------------------
+      |                    0 |     1004.29998779297
+      |               1004.3 | 1.22070312045253e-05
+      |               -34.84 |     1039.13998779297
+      | 1.2345678901234e+200 | 1.2345678901234e+200
+      | 1.2345678901234e-200 |     1004.29998779297
+(5 rows)
+
 SELECT '' AS five, * FROM FLOAT8_TBL;
  five |          f1          
 ------+----------------------
diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out
index 8c255b9..0edc57e 100644
--- a/src/test/regress/expected/int2.out
+++ b/src/test/regress/expected/int2.out
@@ -242,6 +242,39 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
       | -32767 | -16383
 (5 rows)
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+ERROR:  smallint out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+ four |  f1   |   x   
+------+-------+-------
+      |     0 |     2
+      |  1234 |  1232
+      | -1234 |  1236
+      | 32767 | 32765
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
   text  
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
index bda7a8d..3735dbc 100644
--- a/src/test/regress/expected/int4.out
+++ b/src/test/regress/expected/int4.out
@@ -247,6 +247,38 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
       | -2147483647 | -1073741823
 (5 rows)
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+ERROR:  integer out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+ five |     f1      |     x      
+------+-------------+------------
+      |           0 |          2
+      |      123456 |     123454
+      |     -123456 |     123458
+      |  2147483647 | 2147483645
+      | -2147483647 | 2147483649
+(5 rows)
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index 35e3b3f..8940e81 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -432,6 +432,37 @@ SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 A
  4567890123457035 | -4567890123456543 | 1123700970370370094 |     0
 (5 rows)
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
 SELECT q2, abs(q2) FROM INT8_TBL;
         q2         |       abs        
 -------------------+------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index f88f345..cb95adf 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -207,6 +207,21 @@ SELECT '' AS fortyfive, r1.*, r2.*
            | 34 years        | 6 years
 (45 rows)
 
+SELECT '' AS ten, f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+ ten |          ?column?          
+-----+----------------------------
+     | 2 days 02:59:00
+     | 2 days -02:00:00
+     | 8 days -03:00:00
+     | 34 years -2 days -03:00:00
+     | 3 mons -2 days -03:00:00
+     | 2 days 03:00:14
+     | 1 day 00:56:56
+     | 6 years -2 days -03:00:00
+     | 5 mons -2 days -03:00:00
+     | 5 mons -2 days +09:00:00
+(10 rows)
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index ab86595..fb2a489 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -123,6 +123,12 @@ SELECT m / 2::float4 FROM money_data;
    $61.50
 (1 row)
 
+SELECT m <-> '$123.45' FROM money_data;
+ ?column? 
+----------
+    $0.45
+(1 row)
+
 -- All true
 SELECT m = '$123.00' FROM money_data;
  ?column? 
diff --git a/src/test/regress/expected/oid.out b/src/test/regress/expected/oid.out
index 1eab9cc..5339a48 100644
--- a/src/test/regress/expected/oid.out
+++ b/src/test/regress/expected/oid.out
@@ -119,4 +119,17 @@ SELECT '' AS three, o.* FROM OID_TBL o WHERE o.f1 > '1234';
        |   99999999
 (3 rows)
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+ eight |     f1     |  ?column?  
+-------+------------+------------
+       |       1234 |       1111
+       |       1235 |       1112
+       |        987 |        864
+       | 4294966256 | 4294966133
+       |   99999999 |   99999876
+       |          5 |        118
+       |         10 |        113
+       |         15 |        108
+(8 rows)
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 6072f6b..b951b75 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1801,6 +1801,7 @@ ORDER BY 1, 2, 3;
         403 |            5 | *>
         403 |            5 | >
         403 |            5 | ~>~
+        403 |            6 | <->
         405 |            1 | =
         783 |            1 | <<
         783 |            1 | @@
@@ -1910,7 +1911,7 @@ ORDER BY 1, 2, 3;
        4000 |           26 | >>
        4000 |           27 | >>=
        4000 |           28 | ^@
-(123 rows)
+(124 rows)
 
 -- Check that all opclass search operators have selectivity estimators.
 -- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out
index 8e0afe6..ee74faa 100644
--- a/src/test/regress/expected/time.out
+++ b/src/test/regress/expected/time.out
@@ -86,3 +86,19 @@ ERROR:  operator is not unique: time without time zone + time without time zone
 LINE 1: SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
                   ^
 HINT:  Could not choose a best candidate operator. You might need to add explicit type casts.
+-- distance
+SELECT f1 AS "Ten", f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
+     Ten     |           Distance            
+-------------+-------------------------------
+ 00:00:00    | @ 1 hour 23 mins 45 secs
+ 01:00:00    | @ 23 mins 45 secs
+ 02:03:00    | @ 39 mins 15 secs
+ 11:59:00    | @ 10 hours 35 mins 15 secs
+ 12:00:00    | @ 10 hours 36 mins 15 secs
+ 12:01:00    | @ 10 hours 37 mins 15 secs
+ 23:59:00    | @ 22 hours 35 mins 15 secs
+ 23:59:59.99 | @ 22 hours 36 mins 14.99 secs
+ 15:36:39    | @ 14 hours 12 mins 54 secs
+ 15:36:39    | @ 14 hours 12 mins 54 secs
+(10 rows)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 4a2fabd..dcb4205 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1604,3 +1604,214 @@ SELECT make_timestamp(2014,12,28,6,30,45.887);
  Sun Dec 28 06:30:45.887 2014
 (1 row)
 
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 58 secs
+    | @ 1453 days 6 hours 27 mins 58.6 secs
+    | @ 1453 days 6 hours 27 mins 58.5 secs
+    | @ 1453 days 6 hours 27 mins 58.4 secs
+    | @ 1493 days
+    | @ 1492 days 20 hours 55 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 6 hours 27 mins 59 secs
+    | @ 231 days 18 hours 19 mins 20 secs
+    | @ 324 days 15 hours 45 mins 59 secs
+    | @ 324 days 10 hours 45 mins 58 secs
+    | @ 324 days 11 hours 45 mins 57 secs
+    | @ 324 days 20 hours 45 mins 56 secs
+    | @ 324 days 21 hours 45 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 28 mins
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 5 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1452 days 6 hours 27 mins 59 secs
+    | @ 1451 days 6 hours 27 mins 59 secs
+    | @ 1450 days 6 hours 27 mins 59 secs
+    | @ 1449 days 6 hours 27 mins 59 secs
+    | @ 1448 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 765901 days 6 hours 27 mins 59 secs
+    | @ 695407 days 6 hours 27 mins 59 secs
+    | @ 512786 days 6 hours 27 mins 59 secs
+    | @ 330165 days 6 hours 27 mins 59 secs
+    | @ 111019 days 6 hours 27 mins 59 secs
+    | @ 74495 days 6 hours 27 mins 59 secs
+    | @ 37971 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 35077 days 17 hours 32 mins 1 sec
+    | @ 1801 days 6 hours 27 mins 59 secs
+    | @ 1800 days 6 hours 27 mins 59 secs
+    | @ 1799 days 6 hours 27 mins 59 secs
+    | @ 1495 days 6 hours 27 mins 59 secs
+    | @ 1494 days 6 hours 27 mins 59 secs
+    | @ 1493 days 6 hours 27 mins 59 secs
+    | @ 1435 days 6 hours 27 mins 59 secs
+    | @ 1434 days 6 hours 27 mins 59 secs
+    | @ 1130 days 6 hours 27 mins 59 secs
+    | @ 1129 days 6 hours 27 mins 59 secs
+    | @ 399 days 6 hours 27 mins 59 secs
+    | @ 398 days 6 hours 27 mins 59 secs
+    | @ 33 days 6 hours 27 mins 59 secs
+    | @ 32 days 6 hours 27 mins 59 secs
+(63 rows)
+
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 1 hour 23 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 43 secs
+    | @ 1453 days 7 hours 51 mins 43.6 secs
+    | @ 1453 days 7 hours 51 mins 43.5 secs
+    | @ 1453 days 7 hours 51 mins 43.4 secs
+    | @ 1493 days 1 hour 23 mins 45 secs
+    | @ 1492 days 22 hours 19 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 7 hours 51 mins 44 secs
+    | @ 231 days 16 hours 55 mins 35 secs
+    | @ 324 days 17 hours 9 mins 44 secs
+    | @ 324 days 12 hours 9 mins 43 secs
+    | @ 324 days 13 hours 9 mins 42 secs
+    | @ 324 days 22 hours 9 mins 41 secs
+    | @ 324 days 23 hours 9 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 6 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1452 days 7 hours 51 mins 44 secs
+    | @ 1451 days 7 hours 51 mins 44 secs
+    | @ 1450 days 7 hours 51 mins 44 secs
+    | @ 1449 days 7 hours 51 mins 44 secs
+    | @ 1448 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 765901 days 7 hours 51 mins 44 secs
+    | @ 695407 days 7 hours 51 mins 44 secs
+    | @ 512786 days 7 hours 51 mins 44 secs
+    | @ 330165 days 7 hours 51 mins 44 secs
+    | @ 111019 days 7 hours 51 mins 44 secs
+    | @ 74495 days 7 hours 51 mins 44 secs
+    | @ 37971 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 35077 days 16 hours 8 mins 16 secs
+    | @ 1801 days 7 hours 51 mins 44 secs
+    | @ 1800 days 7 hours 51 mins 44 secs
+    | @ 1799 days 7 hours 51 mins 44 secs
+    | @ 1495 days 7 hours 51 mins 44 secs
+    | @ 1494 days 7 hours 51 mins 44 secs
+    | @ 1493 days 7 hours 51 mins 44 secs
+    | @ 1435 days 7 hours 51 mins 44 secs
+    | @ 1434 days 7 hours 51 mins 44 secs
+    | @ 1130 days 7 hours 51 mins 44 secs
+    | @ 1129 days 7 hours 51 mins 44 secs
+    | @ 399 days 7 hours 51 mins 44 secs
+    | @ 398 days 7 hours 51 mins 44 secs
+    | @ 33 days 7 hours 51 mins 44 secs
+    | @ 32 days 7 hours 51 mins 44 secs
+(63 rows)
+
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |                Distance                
+----+----------------------------------------
+    | @ 11355 days 14 hours 23 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 43 secs
+    | @ 1452 days 20 hours 51 mins 43.6 secs
+    | @ 1452 days 20 hours 51 mins 43.5 secs
+    | @ 1452 days 20 hours 51 mins 43.4 secs
+    | @ 1492 days 14 hours 23 mins 45 secs
+    | @ 1492 days 11 hours 19 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 21 hours 51 mins 44 secs
+    | @ 232 days 2 hours 55 mins 35 secs
+    | @ 324 days 6 hours 9 mins 44 secs
+    | @ 324 days 1 hour 9 mins 43 secs
+    | @ 324 days 2 hours 9 mins 42 secs
+    | @ 324 days 11 hours 9 mins 41 secs
+    | @ 324 days 12 hours 9 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1451 days 20 hours 51 mins 44 secs
+    | @ 1450 days 20 hours 51 mins 44 secs
+    | @ 1449 days 20 hours 51 mins 44 secs
+    | @ 1448 days 20 hours 51 mins 44 secs
+    | @ 1447 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 765900 days 20 hours 51 mins 44 secs
+    | @ 695406 days 20 hours 51 mins 44 secs
+    | @ 512785 days 20 hours 51 mins 44 secs
+    | @ 330164 days 20 hours 51 mins 44 secs
+    | @ 111018 days 20 hours 51 mins 44 secs
+    | @ 74494 days 20 hours 51 mins 44 secs
+    | @ 37970 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 35078 days 3 hours 8 mins 16 secs
+    | @ 1800 days 20 hours 51 mins 44 secs
+    | @ 1799 days 20 hours 51 mins 44 secs
+    | @ 1798 days 20 hours 51 mins 44 secs
+    | @ 1494 days 20 hours 51 mins 44 secs
+    | @ 1493 days 20 hours 51 mins 44 secs
+    | @ 1492 days 20 hours 51 mins 44 secs
+    | @ 1434 days 20 hours 51 mins 44 secs
+    | @ 1433 days 20 hours 51 mins 44 secs
+    | @ 1129 days 20 hours 51 mins 44 secs
+    | @ 1128 days 20 hours 51 mins 44 secs
+    | @ 398 days 20 hours 51 mins 44 secs
+    | @ 397 days 20 hours 51 mins 44 secs
+    | @ 32 days 20 hours 51 mins 44 secs
+    | @ 31 days 20 hours 51 mins 44 secs
+(63 rows)
+
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 8a4c719..0a05e37 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2544,3 +2544,217 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
  Tue Jan 17 16:00:00 2017 PST
 (1 row)
 
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 8 hours
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 58 secs
+    | @ 1453 days 6 hours 27 mins 58.6 secs
+    | @ 1453 days 6 hours 27 mins 58.5 secs
+    | @ 1453 days 6 hours 27 mins 58.4 secs
+    | @ 1493 days
+    | @ 1492 days 20 hours 55 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 7 hours 27 mins 59 secs
+    | @ 231 days 17 hours 19 mins 20 secs
+    | @ 324 days 15 hours 45 mins 59 secs
+    | @ 324 days 19 hours 45 mins 58 secs
+    | @ 324 days 21 hours 45 mins 57 secs
+    | @ 324 days 20 hours 45 mins 56 secs
+    | @ 324 days 22 hours 45 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 28 mins
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 9 hours 27 mins 59 secs
+    | @ 1303 days 10 hours 27 mins 59 secs
+    | @ 1333 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1452 days 6 hours 27 mins 59 secs
+    | @ 1451 days 6 hours 27 mins 59 secs
+    | @ 1450 days 6 hours 27 mins 59 secs
+    | @ 1449 days 6 hours 27 mins 59 secs
+    | @ 1448 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 765901 days 6 hours 27 mins 59 secs
+    | @ 695407 days 6 hours 27 mins 59 secs
+    | @ 512786 days 6 hours 27 mins 59 secs
+    | @ 330165 days 6 hours 27 mins 59 secs
+    | @ 111019 days 6 hours 27 mins 59 secs
+    | @ 74495 days 6 hours 27 mins 59 secs
+    | @ 37971 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 35077 days 17 hours 32 mins 1 sec
+    | @ 1801 days 6 hours 27 mins 59 secs
+    | @ 1800 days 6 hours 27 mins 59 secs
+    | @ 1799 days 6 hours 27 mins 59 secs
+    | @ 1495 days 6 hours 27 mins 59 secs
+    | @ 1494 days 6 hours 27 mins 59 secs
+    | @ 1493 days 6 hours 27 mins 59 secs
+    | @ 1435 days 6 hours 27 mins 59 secs
+    | @ 1434 days 6 hours 27 mins 59 secs
+    | @ 1130 days 6 hours 27 mins 59 secs
+    | @ 1129 days 6 hours 27 mins 59 secs
+    | @ 399 days 6 hours 27 mins 59 secs
+    | @ 398 days 6 hours 27 mins 59 secs
+    | @ 33 days 6 hours 27 mins 59 secs
+    | @ 32 days 6 hours 27 mins 59 secs
+(64 rows)
+
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 9 hours 23 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 43 secs
+    | @ 1453 days 7 hours 51 mins 43.6 secs
+    | @ 1453 days 7 hours 51 mins 43.5 secs
+    | @ 1453 days 7 hours 51 mins 43.4 secs
+    | @ 1493 days 1 hour 23 mins 45 secs
+    | @ 1492 days 22 hours 19 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 8 hours 51 mins 44 secs
+    | @ 231 days 15 hours 55 mins 35 secs
+    | @ 324 days 17 hours 9 mins 44 secs
+    | @ 324 days 21 hours 9 mins 43 secs
+    | @ 324 days 23 hours 9 mins 42 secs
+    | @ 324 days 22 hours 9 mins 41 secs
+    | @ 325 days 9 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 10 hours 51 mins 44 secs
+    | @ 1303 days 11 hours 51 mins 44 secs
+    | @ 1333 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1452 days 7 hours 51 mins 44 secs
+    | @ 1451 days 7 hours 51 mins 44 secs
+    | @ 1450 days 7 hours 51 mins 44 secs
+    | @ 1449 days 7 hours 51 mins 44 secs
+    | @ 1448 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 765901 days 7 hours 51 mins 44 secs
+    | @ 695407 days 7 hours 51 mins 44 secs
+    | @ 512786 days 7 hours 51 mins 44 secs
+    | @ 330165 days 7 hours 51 mins 44 secs
+    | @ 111019 days 7 hours 51 mins 44 secs
+    | @ 74495 days 7 hours 51 mins 44 secs
+    | @ 37971 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 35077 days 16 hours 8 mins 16 secs
+    | @ 1801 days 7 hours 51 mins 44 secs
+    | @ 1800 days 7 hours 51 mins 44 secs
+    | @ 1799 days 7 hours 51 mins 44 secs
+    | @ 1495 days 7 hours 51 mins 44 secs
+    | @ 1494 days 7 hours 51 mins 44 secs
+    | @ 1493 days 7 hours 51 mins 44 secs
+    | @ 1435 days 7 hours 51 mins 44 secs
+    | @ 1434 days 7 hours 51 mins 44 secs
+    | @ 1130 days 7 hours 51 mins 44 secs
+    | @ 1129 days 7 hours 51 mins 44 secs
+    | @ 399 days 7 hours 51 mins 44 secs
+    | @ 398 days 7 hours 51 mins 44 secs
+    | @ 33 days 7 hours 51 mins 44 secs
+    | @ 32 days 7 hours 51 mins 44 secs
+(64 rows)
+
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |                Distance                
+----+----------------------------------------
+    | @ 11355 days 22 hours 23 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 43 secs
+    | @ 1452 days 20 hours 51 mins 43.6 secs
+    | @ 1452 days 20 hours 51 mins 43.5 secs
+    | @ 1452 days 20 hours 51 mins 43.4 secs
+    | @ 1492 days 14 hours 23 mins 45 secs
+    | @ 1492 days 11 hours 19 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 21 hours 51 mins 44 secs
+    | @ 232 days 2 hours 55 mins 35 secs
+    | @ 324 days 6 hours 9 mins 44 secs
+    | @ 324 days 10 hours 9 mins 43 secs
+    | @ 324 days 12 hours 9 mins 42 secs
+    | @ 324 days 11 hours 9 mins 41 secs
+    | @ 324 days 13 hours 9 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1452 days 23 hours 51 mins 44 secs
+    | @ 1303 days 51 mins 44 secs
+    | @ 1332 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1451 days 20 hours 51 mins 44 secs
+    | @ 1450 days 20 hours 51 mins 44 secs
+    | @ 1449 days 20 hours 51 mins 44 secs
+    | @ 1448 days 20 hours 51 mins 44 secs
+    | @ 1447 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 765900 days 20 hours 51 mins 44 secs
+    | @ 695406 days 20 hours 51 mins 44 secs
+    | @ 512785 days 20 hours 51 mins 44 secs
+    | @ 330164 days 20 hours 51 mins 44 secs
+    | @ 111018 days 20 hours 51 mins 44 secs
+    | @ 74494 days 20 hours 51 mins 44 secs
+    | @ 37970 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 35078 days 3 hours 8 mins 16 secs
+    | @ 1800 days 20 hours 51 mins 44 secs
+    | @ 1799 days 20 hours 51 mins 44 secs
+    | @ 1798 days 20 hours 51 mins 44 secs
+    | @ 1494 days 20 hours 51 mins 44 secs
+    | @ 1493 days 20 hours 51 mins 44 secs
+    | @ 1492 days 20 hours 51 mins 44 secs
+    | @ 1434 days 20 hours 51 mins 44 secs
+    | @ 1433 days 20 hours 51 mins 44 secs
+    | @ 1129 days 20 hours 51 mins 44 secs
+    | @ 1128 days 20 hours 51 mins 44 secs
+    | @ 398 days 20 hours 51 mins 44 secs
+    | @ 397 days 20 hours 51 mins 44 secs
+    | @ 32 days 20 hours 51 mins 44 secs
+    | @ 31 days 20 hours 51 mins 44 secs
+(64 rows)
+
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
index 22f80f2..24be476 100644
--- a/src/test/regress/sql/date.sql
+++ b/src/test/regress/sql/date.sql
@@ -346,3 +346,8 @@ select make_date(2013, 13, 1);
 select make_date(2013, 11, -1);
 select make_time(10, 55, 100.1);
 select make_time(24, 0, 2.1);
+
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
diff --git a/src/test/regress/sql/float4.sql b/src/test/regress/sql/float4.sql
index 46a9166..f97f80a 100644
--- a/src/test/regress/sql/float4.sql
+++ b/src/test/regress/sql/float4.sql
@@ -82,6 +82,9 @@ UPDATE FLOAT4_TBL
 
 SELECT '' AS five, * FROM FLOAT4_TBL;
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+
 -- test edge-case coercions to integer
 SELECT '32767.4'::float4::int2;
 SELECT '32767.6'::float4::int2;
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
index 6595fd2..9b50210 100644
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -125,6 +125,9 @@ SELECT ||/ float8 '27' AS three;
 
 SELECT '' AS five, f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
 
+-- distance
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
 
 SELECT '' AS five, * FROM FLOAT8_TBL;
 
diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql
index 7dbafb6..16dd5d8 100644
--- a/src/test/regress/sql/int2.sql
+++ b/src/test/regress/sql/int2.sql
@@ -84,6 +84,16 @@ SELECT '' AS five, i.f1, i.f1 / int2 '2' AS x FROM INT2_TBL i;
 
 SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
 SELECT ((-1::int2<<15)+1::int2)::text;
diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql
index f014cb2..cff32946 100644
--- a/src/test/regress/sql/int4.sql
+++ b/src/test/regress/sql/int4.sql
@@ -93,6 +93,16 @@ SELECT '' AS five, i.f1, i.f1 / int2 '2' AS x FROM INT4_TBL i;
 
 SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql
index e890452..d7f5bde 100644
--- a/src/test/regress/sql/int8.sql
+++ b/src/test/regress/sql/int8.sql
@@ -89,6 +89,11 @@ SELECT q1 + 42::int2 AS "8plus2", q1 - 42::int2 AS "8minus2", q1 * 42::int2 AS "
 -- int2 op int8
 SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 AS "2mul8", 246::int2 / q1 AS "2div8" FROM INT8_TBL;
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+
 SELECT q2, abs(q2) FROM INT8_TBL;
 SELECT min(q1), min(q2) FROM INT8_TBL;
 SELECT max(q1), max(q2) FROM INT8_TBL;
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index bc5537d..d51c866 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -59,6 +59,8 @@ SELECT '' AS fortyfive, r1.*, r2.*
    WHERE r1.f1 > r2.f1
    ORDER BY r1.f1, r2.f1;
 
+SELECT '' AS ten, f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql
index 37b9ecc..8428d59 100644
--- a/src/test/regress/sql/money.sql
+++ b/src/test/regress/sql/money.sql
@@ -25,6 +25,7 @@ SELECT m / 2::float8 FROM money_data;
 SELECT m * 2::float4 FROM money_data;
 SELECT 2::float4 * m FROM money_data;
 SELECT m / 2::float4 FROM money_data;
+SELECT m <-> '$123.45' FROM money_data;
 
 -- All true
 SELECT m = '$123.00' FROM money_data;
diff --git a/src/test/regress/sql/oid.sql b/src/test/regress/sql/oid.sql
index 4a09689..9f54f92 100644
--- a/src/test/regress/sql/oid.sql
+++ b/src/test/regress/sql/oid.sql
@@ -40,4 +40,6 @@ SELECT '' AS four, o.* FROM OID_TBL o WHERE o.f1 >= '1234';
 
 SELECT '' AS three, o.* FROM OID_TBL o WHERE o.f1 > '1234';
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/sql/time.sql b/src/test/regress/sql/time.sql
index 99a1562..31f0330 100644
--- a/src/test/regress/sql/time.sql
+++ b/src/test/regress/sql/time.sql
@@ -40,3 +40,6 @@ SELECT f1 AS "Eight" FROM TIME_TBL WHERE f1 >= '00:00';
 -- where we do mixed-type arithmetic. - thomas 2000-12-02
 
 SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
+
+-- distance
+SELECT f1 AS "Ten", f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b7957cb..5d023dd 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -230,3 +230,11 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
 
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
+
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index c3bd46c..7f0525d 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -464,3 +464,11 @@ insert into tmptz values ('2017-01-18 00:00+00');
 explain (costs off)
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
-- 
2.7.4

0006-Remove-distance-operators-from-btree_gist-v04.patchtext/x-patch; name=0006-Remove-distance-operators-from-btree_gist-v04.patchDownload
From 34f82a6c4e759af2b43d21af1eadaedfee9e7bd3 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 30 Nov 2018 14:56:54 +0300
Subject: [PATCH 6/7] Remove distance operators from btree_gist

---
 contrib/btree_gist/Makefile                 |    5 +-
 contrib/btree_gist/btree_cash.c             |   20 -
 contrib/btree_gist/btree_date.c             |   13 -
 contrib/btree_gist/btree_float4.c           |   15 -
 contrib/btree_gist/btree_float8.c           |   15 -
 contrib/btree_gist/btree_gist--1.2.sql      | 1570 --------------------------
 contrib/btree_gist/btree_gist--1.5--1.6.sql |  150 +++
 contrib/btree_gist/btree_gist--1.6.sql      | 1615 +++++++++++++++++++++++++++
 contrib/btree_gist/btree_gist.control       |    2 +-
 contrib/btree_gist/btree_int2.c             |   20 -
 contrib/btree_gist/btree_int4.c             |   21 -
 contrib/btree_gist/btree_int8.c             |   21 -
 contrib/btree_gist/btree_interval.c         |   26 -
 contrib/btree_gist/btree_oid.c              |   16 -
 contrib/btree_gist/btree_time.c             |   12 -
 contrib/btree_gist/btree_ts.c               |   49 -
 contrib/btree_gist/btree_utils_num.h        |    2 -
 17 files changed, 1769 insertions(+), 1803 deletions(-)
 delete mode 100644 contrib/btree_gist/btree_gist--1.2.sql
 create mode 100644 contrib/btree_gist/btree_gist--1.5--1.6.sql
 create mode 100644 contrib/btree_gist/btree_gist--1.6.sql

diff --git a/contrib/btree_gist/Makefile b/contrib/btree_gist/Makefile
index af65120..46ab241 100644
--- a/contrib/btree_gist/Makefile
+++ b/contrib/btree_gist/Makefile
@@ -11,8 +11,9 @@ OBJS =  btree_gist.o btree_utils_num.o btree_utils_var.o btree_int2.o \
 
 EXTENSION = btree_gist
 DATA = btree_gist--unpackaged--1.0.sql btree_gist--1.0--1.1.sql \
-       btree_gist--1.1--1.2.sql btree_gist--1.2.sql btree_gist--1.2--1.3.sql \
-       btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql
+       btree_gist--1.1--1.2.sql btree_gist--1.2--1.3.sql \
+       btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql \
+       btree_gist--1.5--1.6.sql btree_gist--1.6.sql
 PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes"
 
 REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \
diff --git a/contrib/btree_gist/btree_cash.c b/contrib/btree_gist/btree_cash.c
index 894d0a2..86e319d 100644
--- a/contrib/btree_gist/btree_cash.c
+++ b/contrib/btree_gist/btree_cash.c
@@ -91,26 +91,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(cash_dist);
-Datum
-cash_dist(PG_FUNCTION_ARGS)
-{
-	Cash		a = PG_GETARG_CASH(0);
-	Cash		b = PG_GETARG_CASH(1);
-	Cash		r;
-	Cash		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("money out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_CASH(ra);
-}
-
 /**************************************************
  * Cash ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_date.c b/contrib/btree_gist/btree_date.c
index 992ce57..05a4909 100644
--- a/contrib/btree_gist/btree_date.c
+++ b/contrib/btree_gist/btree_date.c
@@ -109,19 +109,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(date_dist);
-Datum
-date_dist(PG_FUNCTION_ARGS)
-{
-	/* we assume the difference can't overflow */
-	Datum		diff = DirectFunctionCall2(date_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
-}
-
-
 /**************************************************
  * date ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index 6b20f44..26fa79b 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -89,21 +89,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(float4_dist);
-Datum
-float4_dist(PG_FUNCTION_ARGS)
-{
-	float4		a = PG_GETARG_FLOAT4(0);
-	float4		b = PG_GETARG_FLOAT4(1);
-	float4		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT4(Abs(r));
-}
-
-
 /**************************************************
  * float4 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index ee114cb..63e5597 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -96,21 +96,6 @@ static const gbtree_ninfo tinfo =
 	gbt_float8_dist
 };
 
-
-PG_FUNCTION_INFO_V1(float8_dist);
-Datum
-float8_dist(PG_FUNCTION_ARGS)
-{
-	float8		a = PG_GETARG_FLOAT8(0);
-	float8		b = PG_GETARG_FLOAT8(1);
-	float8		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT8(Abs(r));
-}
-
 /**************************************************
  * float8 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_gist--1.2.sql b/contrib/btree_gist/btree_gist--1.2.sql
deleted file mode 100644
index 1efe753..0000000
--- a/contrib/btree_gist/btree_gist--1.2.sql
+++ /dev/null
@@ -1,1570 +0,0 @@
-/* contrib/btree_gist/btree_gist--1.2.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
-
-CREATE FUNCTION gbtreekey4_in(cstring)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey4_out(gbtreekey4)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey4 (
-	INTERNALLENGTH = 4,
-	INPUT  = gbtreekey4_in,
-	OUTPUT = gbtreekey4_out
-);
-
-CREATE FUNCTION gbtreekey8_in(cstring)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey8_out(gbtreekey8)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey8 (
-	INTERNALLENGTH = 8,
-	INPUT  = gbtreekey8_in,
-	OUTPUT = gbtreekey8_out
-);
-
-CREATE FUNCTION gbtreekey16_in(cstring)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey16_out(gbtreekey16)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey16 (
-	INTERNALLENGTH = 16,
-	INPUT  = gbtreekey16_in,
-	OUTPUT = gbtreekey16_out
-);
-
-CREATE FUNCTION gbtreekey32_in(cstring)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey32_out(gbtreekey32)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey32 (
-	INTERNALLENGTH = 32,
-	INPUT  = gbtreekey32_in,
-	OUTPUT = gbtreekey32_out
-);
-
-CREATE FUNCTION gbtreekey_var_in(cstring)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey_var (
-	INTERNALLENGTH = VARIABLE,
-	INPUT  = gbtreekey_var_in,
-	OUTPUT = gbtreekey_var_out,
-	STORAGE = EXTENDED
-);
-
---distance operators
-
-CREATE FUNCTION cash_dist(money, money)
-RETURNS money
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = money,
-	RIGHTARG = money,
-	PROCEDURE = cash_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION date_dist(date, date)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = date,
-	RIGHTARG = date,
-	PROCEDURE = date_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float4_dist(float4, float4)
-RETURNS float4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float4,
-	RIGHTARG = float4,
-	PROCEDURE = float4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float8_dist(float8, float8)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float8,
-	RIGHTARG = float8,
-	PROCEDURE = float8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int2_dist(int2, int2)
-RETURNS int2
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int2,
-	RIGHTARG = int2,
-	PROCEDURE = int2_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int4_dist(int4, int4)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int4,
-	RIGHTARG = int4,
-	PROCEDURE = int4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int8_dist(int8, int8)
-RETURNS int8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int8,
-	RIGHTARG = int8,
-	PROCEDURE = int8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION interval_dist(interval, interval)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = interval,
-	RIGHTARG = interval,
-	PROCEDURE = interval_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION oid_dist(oid, oid)
-RETURNS oid
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = oid,
-	RIGHTARG = oid,
-	PROCEDURE = oid_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION time_dist(time, time)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = time,
-	RIGHTARG = time,
-	PROCEDURE = time_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION ts_dist(timestamp, timestamp)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamp,
-	RIGHTARG = timestamp,
-	PROCEDURE = ts_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION tstz_dist(timestamptz, timestamptz)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamptz,
-	RIGHTARG = timestamptz,
-	PROCEDURE = tstz_dist,
-	COMMUTATOR = '<->'
-);
-
-
---
---
---
--- oid ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_oid_ops
-DEFAULT FOR TYPE oid USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
-	FUNCTION	2	gbt_oid_union (internal, internal),
-	FUNCTION	3	gbt_oid_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_oid_picksplit (internal, internal),
-	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
--- Add operators that are new in 9.1.  We do it like this, leaving them
--- "loose" in the operator family rather than bound into the opclass, because
--- that's the only state that can be reproduced during an upgrade from 9.0.
-ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
-	OPERATOR	6	<> (oid, oid) ,
-	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
-	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
-	-- Also add support function for index-only-scans, added in 9.5.
-	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
-
-
---
---
---
--- int2 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_union(internal, internal)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int2_ops
-DEFAULT FOR TYPE int2 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
-	FUNCTION	2	gbt_int2_union (internal, internal),
-	FUNCTION	3	gbt_int2_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int2_picksplit (internal, internal),
-	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
-	STORAGE		gbtreekey4;
-
-ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
-	OPERATOR	6	<> (int2, int2) ,
-	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
-	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
-
---
---
---
--- int4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int4_ops
-DEFAULT FOR TYPE int4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
-	FUNCTION	2	gbt_int4_union (internal, internal),
-	FUNCTION	3	gbt_int4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int4_picksplit (internal, internal),
-	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
-	OPERATOR	6	<> (int4, int4) ,
-	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
-	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
-
-
---
---
---
--- int8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int8_ops
-DEFAULT FOR TYPE int8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
-	FUNCTION	2	gbt_int8_union (internal, internal),
-	FUNCTION	3	gbt_int8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int8_picksplit (internal, internal),
-	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
-	OPERATOR	6	<> (int8, int8) ,
-	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
-	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
-
---
---
---
--- float4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float4_ops
-DEFAULT FOR TYPE float4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
-	FUNCTION	2	gbt_float4_union (internal, internal),
-	FUNCTION	3	gbt_float4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float4_picksplit (internal, internal),
-	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
-	OPERATOR	6	<> (float4, float4) ,
-	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
-	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
-
---
---
---
--- float8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float8_ops
-DEFAULT FOR TYPE float8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
-	FUNCTION	2	gbt_float8_union (internal, internal),
-	FUNCTION	3	gbt_float8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float8_picksplit (internal, internal),
-	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
-	OPERATOR	6	<> (float8, float8) ,
-	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
-	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
-
---
---
---
--- timestamp ops
---
---
---
-
-CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamp_ops
-DEFAULT FOR TYPE timestamp USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_ts_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
-	OPERATOR	6	<> (timestamp, timestamp) ,
-	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
-	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamptz_ops
-DEFAULT FOR TYPE timestamptz USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_tstz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
-	OPERATOR	6	<> (timestamptz, timestamptz) ,
-	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
-	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
-
---
---
---
--- time ops
---
---
---
-
-CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_time_ops
-DEFAULT FOR TYPE time USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_time_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
-	OPERATOR	6	<> (time, time) ,
-	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
-	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
-
-
-CREATE OPERATOR CLASS gist_timetz_ops
-DEFAULT FOR TYPE timetz USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_timetz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
-	OPERATOR	6	<> (timetz, timetz) ;
-	-- no 'fetch' function, as the compress function is lossy.
-
-
---
---
---
--- date ops
---
---
---
-
-CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_date_ops
-DEFAULT FOR TYPE date USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
-	FUNCTION	2	gbt_date_union (internal, internal),
-	FUNCTION	3	gbt_date_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_date_picksplit (internal, internal),
-	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
-	OPERATOR	6	<> (date, date) ,
-	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
-	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
-
-
---
---
---
--- interval ops
---
---
---
-
-CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_union(internal, internal)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_interval_ops
-DEFAULT FOR TYPE interval USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
-	FUNCTION	2	gbt_intv_union (internal, internal),
-	FUNCTION	3	gbt_intv_compress (internal),
-	FUNCTION	4	gbt_intv_decompress (internal),
-	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_intv_picksplit (internal, internal),
-	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
-	STORAGE		gbtreekey32;
-
-ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
-	OPERATOR	6	<> (interval, interval) ,
-	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
-	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
-
-
---
---
---
--- cash ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cash_ops
-DEFAULT FOR TYPE money USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
-	FUNCTION	2	gbt_cash_union (internal, internal),
-	FUNCTION	3	gbt_cash_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_cash_picksplit (internal, internal),
-	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
-	OPERATOR	6	<> (money, money) ,
-	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
-	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
-	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
-
-
---
---
---
--- macaddr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_macaddr_ops
-DEFAULT FOR TYPE macaddr USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
-	FUNCTION	2	gbt_macad_union (internal, internal),
-	FUNCTION	3	gbt_macad_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_macad_picksplit (internal, internal),
-	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
-	OPERATOR	6	<> (macaddr, macaddr) ,
-	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
-
-
---
---
---
--- text/ bpchar ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_text_ops
-DEFAULT FOR TYPE text USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_text_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
-	OPERATOR	6	<> (text, text) ,
-	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
-
-
----- Create the operator class
-CREATE OPERATOR CLASS gist_bpchar_ops
-DEFAULT FOR TYPE bpchar USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_bpchar_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
-	OPERATOR	6	<> (bpchar, bpchar) ,
-	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
-
---
---
--- bytea ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bytea_ops
-DEFAULT FOR TYPE bytea USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
-	FUNCTION	2	gbt_bytea_union (internal, internal),
-	FUNCTION	3	gbt_bytea_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
-	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
-	OPERATOR	6	<> (bytea, bytea) ,
-	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
-
-
---
---
---
--- numeric ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_numeric_ops
-DEFAULT FOR TYPE numeric USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
-	FUNCTION	2	gbt_numeric_union (internal, internal),
-	FUNCTION	3	gbt_numeric_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
-	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
-	OPERATOR	6	<> (numeric, numeric) ,
-	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
-
-
---
---
--- bit ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bit_ops
-DEFAULT FOR TYPE bit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
-	OPERATOR	6	<> (bit, bit) ,
-	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
-
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_vbit_ops
-DEFAULT FOR TYPE varbit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
-	OPERATOR	6	<> (varbit, varbit) ,
-	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
-
-
---
---
---
--- inet/cidr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_inet_ops
-DEFAULT FOR TYPE inet USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
-	OPERATOR	6	<>  (inet, inet) ;
-	-- no fetch support, the compress function is lossy
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cidr_ops
-DEFAULT FOR TYPE cidr USING gist
-AS
-	OPERATOR	1	<  (inet, inet)  ,
-	OPERATOR	2	<= (inet, inet)  ,
-	OPERATOR	3	=  (inet, inet)  ,
-	OPERATOR	4	>= (inet, inet)  ,
-	OPERATOR	5	>  (inet, inet)  ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
-	OPERATOR	6	<> (inet, inet) ;
-	-- no fetch support, the compress function is lossy
diff --git a/contrib/btree_gist/btree_gist--1.5--1.6.sql b/contrib/btree_gist/btree_gist--1.5--1.6.sql
new file mode 100644
index 0000000..fcd10f5
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.5--1.6.sql
@@ -0,0 +1,150 @@
+/* contrib/btree_gist/btree_gist--1.5--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.6'" to load this file. \quit
+
+-- update references to distance operators in pg_amop and pg_depend
+
+WITH
+btree_ops AS (
+	SELECT
+		amoplefttype, amoprighttype, amopopr
+	FROM
+		pg_amop
+		JOIN pg_am ON pg_am.oid = amopmethod
+		JOIN pg_opfamily ON pg_opfamily.oid = amopfamily
+		JOIN pg_namespace ON pg_namespace.oid = opfnamespace
+	WHERE
+		nspname = 'pg_catalog'
+		AND opfname IN (
+			'integer_ops',
+			'oid_ops',
+			'money_ops',
+			'float_ops',
+			'datetime_ops',
+			'time_ops',
+			'interval_ops'
+		)
+		AND amname = 'btree'
+		AND amoppurpose = 'o'
+		AND amopstrategy = 6
+	),
+gist_ops AS (
+	SELECT
+		pg_amop.oid AS oid, amoplefttype, amoprighttype, amopopr
+	FROM
+		pg_amop
+		JOIN pg_am ON amopmethod = pg_am.oid
+		JOIN pg_opfamily ON amopfamily = pg_opfamily.oid
+		JOIN pg_namespace ON pg_namespace.oid = opfnamespace
+	WHERE
+		nspname = current_schema()
+		AND opfname IN (
+			'gist_oid_ops',
+			'gist_int2_ops',
+			'gist_int4_ops',
+			'gist_int8_ops',
+			'gist_float4_ops',
+			'gist_float8_ops',
+			'gist_timestamp_ops',
+			'gist_timestamptz_ops',
+			'gist_time_ops',
+			'gist_date_ops',
+			'gist_interval_ops',
+			'gist_cash_ops'
+		)
+		AND amname = 'gist'
+		AND amoppurpose = 'o'
+		AND amopstrategy = 15
+	),
+depend_update_data(gist_amop, gist_amopopr, btree_amopopr) AS (
+	SELECT
+		gist_ops.oid, gist_ops.amopopr, btree_ops.amopopr
+	FROM
+		btree_ops JOIN gist_ops USING (amoplefttype, amoprighttype)
+),
+amop_update_data AS (
+	UPDATE
+		pg_depend
+	SET
+		refobjid = btree_amopopr
+	FROM
+		depend_update_data
+	WHERE
+		objid = gist_amop AND refobjid = gist_amopopr
+	RETURNING
+		depend_update_data.*
+)
+UPDATE
+	pg_amop
+SET
+	amopopr = btree_amopopr
+FROM
+	amop_update_data
+WHERE
+	pg_amop.oid = gist_amop;
+
+-- disable implicit pg_catalog search
+
+DO
+$$
+BEGIN
+	EXECUTE 'SET LOCAL search_path TO ' || current_schema() || ', pg_catalog';
+END
+$$;
+
+-- drop distance operators
+
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int2, int2);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int4, int4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int8, int8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float4, float4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float8, float8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (oid, oid);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (money, money);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (date, date);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (time, time);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (interval, interval);
+
+DROP OPERATOR <-> (int2, int2);
+DROP OPERATOR <-> (int4, int4);
+DROP OPERATOR <-> (int8, int8);
+DROP OPERATOR <-> (float4, float4);
+DROP OPERATOR <-> (float8, float8);
+DROP OPERATOR <-> (oid, oid);
+DROP OPERATOR <-> (money, money);
+DROP OPERATOR <-> (date, date);
+DROP OPERATOR <-> (time, time);
+DROP OPERATOR <-> (timestamp, timestamp);
+DROP OPERATOR <-> (timestamptz, timestamptz);
+DROP OPERATOR <-> (interval, interval);
+
+-- drop distance functions
+
+ALTER EXTENSION btree_gist DROP FUNCTION int2_dist(int2, int2);
+ALTER EXTENSION btree_gist DROP FUNCTION int4_dist(int4, int4);
+ALTER EXTENSION btree_gist DROP FUNCTION int8_dist(int8, int8);
+ALTER EXTENSION btree_gist DROP FUNCTION float4_dist(float4, float4);
+ALTER EXTENSION btree_gist DROP FUNCTION float8_dist(float8, float8);
+ALTER EXTENSION btree_gist DROP FUNCTION oid_dist(oid, oid);
+ALTER EXTENSION btree_gist DROP FUNCTION cash_dist(money, money);
+ALTER EXTENSION btree_gist DROP FUNCTION date_dist(date, date);
+ALTER EXTENSION btree_gist DROP FUNCTION time_dist(time, time);
+ALTER EXTENSION btree_gist DROP FUNCTION ts_dist(timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP FUNCTION interval_dist(interval, interval);
+
+DROP FUNCTION int2_dist(int2, int2);
+DROP FUNCTION int4_dist(int4, int4);
+DROP FUNCTION int8_dist(int8, int8);
+DROP FUNCTION float4_dist(float4, float4);
+DROP FUNCTION float8_dist(float8, float8);
+DROP FUNCTION oid_dist(oid, oid);
+DROP FUNCTION cash_dist(money, money);
+DROP FUNCTION date_dist(date, date);
+DROP FUNCTION time_dist(time, time);
+DROP FUNCTION ts_dist(timestamp, timestamp);
+DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+DROP FUNCTION interval_dist(interval, interval);
diff --git a/contrib/btree_gist/btree_gist--1.6.sql b/contrib/btree_gist/btree_gist--1.6.sql
new file mode 100644
index 0000000..8ff8eb5
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.6.sql
@@ -0,0 +1,1615 @@
+/* contrib/btree_gist/btree_gist--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
+
+CREATE FUNCTION gbtreekey4_in(cstring)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey4_out(gbtreekey4)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey4 (
+	INTERNALLENGTH = 4,
+	INPUT  = gbtreekey4_in,
+	OUTPUT = gbtreekey4_out
+);
+
+CREATE FUNCTION gbtreekey8_in(cstring)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey8_out(gbtreekey8)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey8 (
+	INTERNALLENGTH = 8,
+	INPUT  = gbtreekey8_in,
+	OUTPUT = gbtreekey8_out
+);
+
+CREATE FUNCTION gbtreekey16_in(cstring)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey16_out(gbtreekey16)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey16 (
+	INTERNALLENGTH = 16,
+	INPUT  = gbtreekey16_in,
+	OUTPUT = gbtreekey16_out
+);
+
+CREATE FUNCTION gbtreekey32_in(cstring)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey32_out(gbtreekey32)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey32 (
+	INTERNALLENGTH = 32,
+	INPUT  = gbtreekey32_in,
+	OUTPUT = gbtreekey32_out
+);
+
+CREATE FUNCTION gbtreekey_var_in(cstring)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey_var (
+	INTERNALLENGTH = VARIABLE,
+	INPUT  = gbtreekey_var_in,
+	OUTPUT = gbtreekey_var_out,
+	STORAGE = EXTENDED
+);
+
+
+--
+--
+--
+-- oid ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_oid_ops
+DEFAULT FOR TYPE oid USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
+	FUNCTION	2	gbt_oid_union (internal, internal),
+	FUNCTION	3	gbt_oid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_oid_picksplit (internal, internal),
+	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+-- Add operators that are new in 9.1.  We do it like this, leaving them
+-- "loose" in the operator family rather than bound into the opclass, because
+-- that's the only state that can be reproduced during an upgrade from 9.0.
+ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
+	OPERATOR	6	<> (oid, oid) ,
+	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
+	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
+	-- Also add support function for index-only-scans, added in 9.5.
+	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
+
+
+--
+--
+--
+-- int2 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_union(internal, internal)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int2_ops
+DEFAULT FOR TYPE int2 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
+	FUNCTION	2	gbt_int2_union (internal, internal),
+	FUNCTION	3	gbt_int2_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int2_picksplit (internal, internal),
+	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
+	STORAGE		gbtreekey4;
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
+	OPERATOR	6	<> (int2, int2) ,
+	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
+	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
+
+--
+--
+--
+-- int4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int4_ops
+DEFAULT FOR TYPE int4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
+	FUNCTION	2	gbt_int4_union (internal, internal),
+	FUNCTION	3	gbt_int4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int4_picksplit (internal, internal),
+	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
+	OPERATOR	6	<> (int4, int4) ,
+	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
+	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
+
+
+--
+--
+--
+-- int8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int8_ops
+DEFAULT FOR TYPE int8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
+	FUNCTION	2	gbt_int8_union (internal, internal),
+	FUNCTION	3	gbt_int8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int8_picksplit (internal, internal),
+	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
+	OPERATOR	6	<> (int8, int8) ,
+	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
+	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
+
+--
+--
+--
+-- float4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float4_ops
+DEFAULT FOR TYPE float4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
+	FUNCTION	2	gbt_float4_union (internal, internal),
+	FUNCTION	3	gbt_float4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float4_picksplit (internal, internal),
+	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
+	OPERATOR	6	<> (float4, float4) ,
+	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
+	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
+
+--
+--
+--
+-- float8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float8_ops
+DEFAULT FOR TYPE float8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
+	FUNCTION	2	gbt_float8_union (internal, internal),
+	FUNCTION	3	gbt_float8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float8_picksplit (internal, internal),
+	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
+	OPERATOR	6	<> (float8, float8) ,
+	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
+	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
+
+--
+--
+--
+-- timestamp ops
+--
+--
+--
+
+CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamp_ops
+DEFAULT FOR TYPE timestamp USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_ts_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
+	OPERATOR	6	<> (timestamp, timestamp) ,
+	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
+	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamptz_ops
+DEFAULT FOR TYPE timestamptz USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_tstz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
+	OPERATOR	6	<> (timestamptz, timestamptz) ,
+	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
+	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
+
+--
+--
+--
+-- time ops
+--
+--
+--
+
+CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_time_ops
+DEFAULT FOR TYPE time USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_time_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
+	OPERATOR	6	<> (time, time) ,
+	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
+	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
+
+
+CREATE OPERATOR CLASS gist_timetz_ops
+DEFAULT FOR TYPE timetz USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_timetz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
+	OPERATOR	6	<> (timetz, timetz) ;
+	-- no 'fetch' function, as the compress function is lossy.
+
+
+--
+--
+--
+-- date ops
+--
+--
+--
+
+CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_date_ops
+DEFAULT FOR TYPE date USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
+	FUNCTION	2	gbt_date_union (internal, internal),
+	FUNCTION	3	gbt_date_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_date_picksplit (internal, internal),
+	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
+	OPERATOR	6	<> (date, date) ,
+	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
+	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
+
+
+--
+--
+--
+-- interval ops
+--
+--
+--
+
+CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_interval_ops
+DEFAULT FOR TYPE interval USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
+	FUNCTION	2	gbt_intv_union (internal, internal),
+	FUNCTION	3	gbt_intv_compress (internal),
+	FUNCTION	4	gbt_intv_decompress (internal),
+	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_intv_picksplit (internal, internal),
+	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
+	OPERATOR	6	<> (interval, interval) ,
+	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
+	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
+
+
+--
+--
+--
+-- cash ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cash_ops
+DEFAULT FOR TYPE money USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
+	FUNCTION	2	gbt_cash_union (internal, internal),
+	FUNCTION	3	gbt_cash_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_cash_picksplit (internal, internal),
+	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
+	OPERATOR	6	<> (money, money) ,
+	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
+	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
+	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
+
+
+--
+--
+--
+-- macaddr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_macaddr_ops
+DEFAULT FOR TYPE macaddr USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
+	FUNCTION	2	gbt_macad_union (internal, internal),
+	FUNCTION	3	gbt_macad_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_macad_picksplit (internal, internal),
+	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
+	OPERATOR	6	<> (macaddr, macaddr) ,
+	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
+
+
+--
+--
+--
+-- text/ bpchar ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_text_ops
+DEFAULT FOR TYPE text USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_text_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
+	OPERATOR	6	<> (text, text) ,
+	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
+
+
+---- Create the operator class
+CREATE OPERATOR CLASS gist_bpchar_ops
+DEFAULT FOR TYPE bpchar USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_bpchar_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
+	OPERATOR	6	<> (bpchar, bpchar) ,
+	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
+
+--
+--
+-- bytea ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bytea_ops
+DEFAULT FOR TYPE bytea USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
+	FUNCTION	2	gbt_bytea_union (internal, internal),
+	FUNCTION	3	gbt_bytea_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
+	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
+	OPERATOR	6	<> (bytea, bytea) ,
+	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- numeric ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_numeric_ops
+DEFAULT FOR TYPE numeric USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
+	FUNCTION	2	gbt_numeric_union (internal, internal),
+	FUNCTION	3	gbt_numeric_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
+	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
+	OPERATOR	6	<> (numeric, numeric) ,
+	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
+
+
+--
+--
+-- bit ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bit_ops
+DEFAULT FOR TYPE bit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
+	OPERATOR	6	<> (bit, bit) ,
+	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
+
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_vbit_ops
+DEFAULT FOR TYPE varbit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
+	OPERATOR	6	<> (varbit, varbit) ,
+	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- inet/cidr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_inet_ops
+DEFAULT FOR TYPE inet USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
+	OPERATOR	6	<>  (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cidr_ops
+DEFAULT FOR TYPE cidr USING gist
+AS
+	OPERATOR	1	<  (inet, inet)  ,
+	OPERATOR	2	<= (inet, inet)  ,
+	OPERATOR	3	=  (inet, inet)  ,
+	OPERATOR	4	>= (inet, inet)  ,
+	OPERATOR	5	>  (inet, inet)  ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
+	OPERATOR	6	<> (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+--
+--
+--
+-- uuid ops
+--
+--
+---- define the GiST support methods
+CREATE FUNCTION gbt_uuid_consistent(internal,uuid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_uuid_ops
+DEFAULT FOR TYPE uuid USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_uuid_consistent (internal, uuid, int2, oid, internal),
+	FUNCTION	2	gbt_uuid_union (internal, internal),
+	FUNCTION	3	gbt_uuid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_uuid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_uuid_picksplit (internal, internal),
+	FUNCTION	7	gbt_uuid_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+-- These are "loose" in the opfamily for consistency with the rest of btree_gist
+ALTER OPERATOR FAMILY gist_uuid_ops USING gist ADD
+	OPERATOR	6	<>  (uuid, uuid) ,
+	FUNCTION	9 (uuid, uuid) gbt_uuid_fetch (internal) ;
+
+
+-- Add support for indexing macaddr8 columns
+
+-- define the GiST support methods
+CREATE FUNCTION gbt_macad8_consistent(internal,macaddr8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_macaddr8_ops
+DEFAULT FOR TYPE macaddr8 USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_macad8_consistent (internal, macaddr8, int2, oid, internal),
+	FUNCTION	2	gbt_macad8_union (internal, internal),
+	FUNCTION	3	gbt_macad8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_macad8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_macad8_picksplit (internal, internal),
+	FUNCTION	7	gbt_macad8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_macaddr8_ops USING gist ADD
+	OPERATOR	6	<> (macaddr8, macaddr8) ,
+	FUNCTION	9 (macaddr8, macaddr8) gbt_macad8_fetch (internal);
+
+--
+--
+--
+-- enum ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_enum_consistent(internal,anyenum,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_enum_ops
+DEFAULT FOR TYPE anyenum USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_enum_consistent (internal, anyenum, int2, oid, internal),
+	FUNCTION	2	gbt_enum_union (internal, internal),
+	FUNCTION	3	gbt_enum_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_enum_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_enum_picksplit (internal, internal),
+	FUNCTION	7	gbt_enum_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_enum_ops USING gist ADD
+	OPERATOR	6	<> (anyenum, anyenum) ,
+	FUNCTION	9 (anyenum, anyenum) gbt_enum_fetch (internal) ;
diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control
index 81c8509..9ced3bc 100644
--- a/contrib/btree_gist/btree_gist.control
+++ b/contrib/btree_gist/btree_gist.control
@@ -1,5 +1,5 @@
 # btree_gist extension
 comment = 'support for indexing common datatypes in GiST'
-default_version = '1.5'
+default_version = '1.6'
 module_pathname = '$libdir/btree_gist'
 relocatable = true
diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c
index 7674e2d..3c402c0 100644
--- a/contrib/btree_gist/btree_int2.c
+++ b/contrib/btree_gist/btree_int2.c
@@ -90,26 +90,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(int2_dist);
-Datum
-int2_dist(PG_FUNCTION_ARGS)
-{
-	int16		a = PG_GETARG_INT16(0);
-	int16		b = PG_GETARG_INT16(1);
-	int16		r;
-	int16		ra;
-
-	if (pg_sub_s16_overflow(a, b, &r) ||
-		r == PG_INT16_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("smallint out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT16(ra);
-}
-
 
 /**************************************************
  * int16 ops
diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c
index 80005ab..a4f1968 100644
--- a/contrib/btree_gist/btree_int4.c
+++ b/contrib/btree_gist/btree_int4.c
@@ -91,27 +91,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(int4_dist);
-Datum
-int4_dist(PG_FUNCTION_ARGS)
-{
-	int32		a = PG_GETARG_INT32(0);
-	int32		b = PG_GETARG_INT32(1);
-	int32		r;
-	int32		ra;
-
-	if (pg_sub_s32_overflow(a, b, &r) ||
-		r == PG_INT32_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("integer out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT32(ra);
-}
-
-
 /**************************************************
  * int32 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c
index b0fd3e1..efec64c 100644
--- a/contrib/btree_gist/btree_int8.c
+++ b/contrib/btree_gist/btree_int8.c
@@ -91,27 +91,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(int8_dist);
-Datum
-int8_dist(PG_FUNCTION_ARGS)
-{
-	int64		a = PG_GETARG_INT64(0);
-	int64		b = PG_GETARG_INT64(1);
-	int64		r;
-	int64		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("bigint out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT64(ra);
-}
-
-
 /**************************************************
  * int64 ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_interval.c b/contrib/btree_gist/btree_interval.c
index 3a527a7..b244fa4 100644
--- a/contrib/btree_gist/btree_interval.c
+++ b/contrib/btree_gist/btree_interval.c
@@ -110,32 +110,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-Interval *
-abs_interval(Interval *a)
-{
-	static Interval zero = {0, 0, 0};
-
-	if (DatumGetBool(DirectFunctionCall2(interval_lt,
-										 IntervalPGetDatum(a),
-										 IntervalPGetDatum(&zero))))
-		a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
-												  IntervalPGetDatum(a)));
-
-	return a;
-}
-
-PG_FUNCTION_INFO_V1(interval_dist);
-Datum
-interval_dist(PG_FUNCTION_ARGS)
-{
-	Datum		diff = DirectFunctionCall2(interval_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
-}
-
-
 /**************************************************
  * interval ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_oid.c b/contrib/btree_gist/btree_oid.c
index 00e7019..3a0bd73 100644
--- a/contrib/btree_gist/btree_oid.c
+++ b/contrib/btree_gist/btree_oid.c
@@ -96,22 +96,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(oid_dist);
-Datum
-oid_dist(PG_FUNCTION_ARGS)
-{
-	Oid			a = PG_GETARG_OID(0);
-	Oid			b = PG_GETARG_OID(1);
-	Oid			res;
-
-	if (a < b)
-		res = b - a;
-	else
-		res = a - b;
-	PG_RETURN_OID(res);
-}
-
-
 /**************************************************
  * Oid ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_time.c b/contrib/btree_gist/btree_time.c
index 90cf655..b6e7e4a 100644
--- a/contrib/btree_gist/btree_time.c
+++ b/contrib/btree_gist/btree_time.c
@@ -137,18 +137,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(time_dist);
-Datum
-time_dist(PG_FUNCTION_ARGS)
-{
-	Datum		diff = DirectFunctionCall2(time_mi_time,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
-}
-
-
 /**************************************************
  * time ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c
index 49d1849..b0271a6 100644
--- a/contrib/btree_gist/btree_ts.c
+++ b/contrib/btree_gist/btree_ts.c
@@ -142,55 +142,6 @@ static const gbtree_ninfo tinfo =
 };
 
 
-PG_FUNCTION_INFO_V1(ts_dist);
-Datum
-ts_dist(PG_FUNCTION_ARGS)
-{
-	Timestamp	a = PG_GETARG_TIMESTAMP(0);
-	Timestamp	b = PG_GETARG_TIMESTAMP(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-	else
-		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-												  PG_GETARG_DATUM(0),
-												  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
-}
-
-PG_FUNCTION_INFO_V1(tstz_dist);
-Datum
-tstz_dist(PG_FUNCTION_ARGS)
-{
-	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
-	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-
-	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-											  PG_GETARG_DATUM(0),
-											  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
-}
-
-
 /**************************************************
  * timestamp ops
  **************************************************/
diff --git a/contrib/btree_gist/btree_utils_num.h b/contrib/btree_gist/btree_utils_num.h
index d7945f8..794d92b 100644
--- a/contrib/btree_gist/btree_utils_num.h
+++ b/contrib/btree_gist/btree_utils_num.h
@@ -107,8 +107,6 @@ do {															\
 } while(0)
 
 
-extern Interval *abs_interval(Interval *a);
-
 extern bool gbt_num_consistent(const GBT_NUMKEY_R *key, const void *query,
 				   const StrategyNumber *strategy, bool is_leaf,
 				   const gbtree_ninfo *tinfo, FmgrInfo *flinfo);
-- 
2.7.4

0007-Add-regression-tests-for-kNN-btree-v04.patchtext/x-patch; name=0007-Add-regression-tests-for-kNN-btree-v04.patchDownload
From 7255d3ff7753d97337098a0967dcb4ffe3886864 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 30 Nov 2018 14:56:54 +0300
Subject: [PATCH 7/7] Add regression tests for kNN btree

---
 src/test/regress/expected/btree_index.out | 779 ++++++++++++++++++++++++++++++
 src/test/regress/sql/btree_index.sql      | 232 +++++++++
 2 files changed, 1011 insertions(+)

diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index 0bd48dc..a8ed9da 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -179,3 +179,782 @@ select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass;
  {vacuum_cleanup_index_scale_factor=70.0}
 (1 row)
 
+---
+--- Test B-tree distance ordering
+---
+SET enable_bitmapscan = OFF;
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+          QUERY PLAN          
+------------------------------
+ Sort
+   Sort Key: ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+                   QUERY PLAN                   
+------------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((random <-> 1))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)))
+   Order By: (random <-> 4000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+                                                                                               QUERY PLAN                                                                                               
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)) AND (random = ANY ('{1809552,1919087,2321799,2648497,3000193,3013326,4157193,4488889,5257716,5593978,NULL}'::integer[])))
+   Order By: (random <-> 3000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+ seqno | random  
+-------+---------
+  5380 | 3000193
+  6262 | 3013326
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+  6448 | 4157193
+   210 | 1809552
+  4408 | 4488889
+  6320 | 5257716
+  1836 | 5593978
+(10 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+-- test parallel KNN scan
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+RESET enable_indexscan;
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, 1000000) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+EXPLAIN (COSTS OFF)
+SELECT i FROM bt_knn_test WHERE i > 8000000;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Gather
+   Workers Planned: 4
+   ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+         Index Cond: (i > 8000000)
+(4 rows)
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> 4000003) AS n, i * 10 AS i
+	FROM generate_series(1, 1000000) i;
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (t2.n = t1.n)
+   Join Filter: (t1.i <> t2.i)
+   CTE bt_knn_test1
+     ->  WindowAgg
+           ->  Gather Merge
+                 Workers Planned: 4
+                 ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                       Order By: (i <-> 4000003)
+   ->  Seq Scan on bt_knn_test2 t2
+   ->  Hash
+         ->  CTE Scan on bt_knn_test1 t1
+(12 rows)
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+DROP TABLE bt_knn_test;
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, 100000) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (t2.n = t1.n)
+   Join Filter: (t1.i <> t2.i)
+   CTE t1
+     ->  WindowAgg
+           ->  Gather Merge
+                 Workers Planned: 4
+                 ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                       Index Cond: (i = ANY ('{3,4,7,8,2}'::integer[]))
+                       Order By: (i <-> 4)
+   CTE t2
+     ->  Nested Loop
+           ->  Function Scan on generate_series i
+           ->  Function Scan on generate_series j
+   ->  CTE Scan on t2
+   ->  Hash
+         ->  CTE Scan on t1
+(17 rows)
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+ROLLBACK;
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: (ROW(thousand, tenthous) >= ROW(997, 5000))
+   Order By: (thousand <-> 998)
+(3 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+        1 |     9001
+        1 |     8001
+        1 |     7001
+        1 |     6001
+        1 |     5001
+        1 |     4001
+        1 |     3001
+        1 |     2001
+        1 |     1001
+        1 |        1
+        0 |     9000
+        0 |     8000
+        0 |     7000
+        0 |     6000
+        0 |     5000
+        0 |     4000
+        0 |     3000
+        0 |     2000
+        0 |     1000
+        0 |        0
+          |        1
+          |        2
+          |        3
+(33 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: ((thousand > 100) AND (thousand < 800) AND (thousand = ANY ('{0,123,234,345,456,678,901,NULL}'::smallint[])))
+   Order By: (thousand <-> '300'::bigint)
+(3 rows)
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+ thousand | tenthous 
+----------+----------
+      345 |      345
+      345 |     1345
+      345 |     2345
+      345 |     3345
+      345 |     4345
+      345 |     5345
+      345 |     6345
+      345 |     7345
+      345 |     8345
+      345 |     9345
+      234 |      234
+      234 |     1234
+      234 |     2234
+      234 |     3234
+      234 |     4234
+      234 |     5234
+      234 |     6234
+      234 |     7234
+      234 |     8234
+      234 |     9234
+      456 |      456
+      456 |     1456
+      456 |     2456
+      456 |     3456
+      456 |     4456
+      456 |     5456
+      456 |     6456
+      456 |     7456
+      456 |     8456
+      456 |     9456
+      123 |      123
+      123 |     1123
+      123 |     2123
+      123 |     3123
+      123 |     4123
+      123 |     5123
+      123 |     6123
+      123 |     7123
+      123 |     8123
+      123 |     9123
+      678 |      678
+      678 |     1678
+      678 |     2678
+      678 |     3678
+      678 |     4678
+      678 |     5678
+      678 |     6678
+      678 |     7678
+      678 |     8678
+      678 |     9678
+(50 rows)
+
+DROP INDEX tenk3_idx;
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+        1 |        1
+        1 |     1001
+        1 |     2001
+        1 |     3001
+        1 |     4001
+        1 |     5001
+        1 |     6001
+        1 |     7001
+        1 |     8001
+        1 |     9001
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+        0 |        0
+        0 |     1000
+        0 |     2000
+        0 |     3000
+        0 |     4000
+        0 |     5000
+        0 |     6000
+        0 |     7000
+        0 |     8000
+        0 |     9000
+          |        3
+          |        2
+          |        1
+(33 rows)
+
+DROP INDEX tenk3_idx;
+DROP TABLE tenk3;
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |     ?column?      
+--------------------------+-------------------
+ Wed May 03 00:00:00 2017 | @ 2 days
+ Wed May 03 01:00:00 2017 | @ 2 days 1 hour
+ Wed May 03 02:00:00 2017 | @ 2 days 2 hours
+ Wed May 03 03:00:00 2017 | @ 2 days 3 hours
+ Wed May 03 04:00:00 2017 | @ 2 days 4 hours
+ Wed May 03 05:00:00 2017 | @ 2 days 5 hours
+ Wed May 03 06:00:00 2017 | @ 2 days 6 hours
+ Wed May 03 07:00:00 2017 | @ 2 days 7 hours
+ Wed May 03 08:00:00 2017 | @ 2 days 8 hours
+ Wed May 03 09:00:00 2017 | @ 2 days 9 hours
+ Wed May 03 10:00:00 2017 | @ 2 days 10 hours
+ Wed May 03 11:00:00 2017 | @ 2 days 11 hours
+ Wed May 03 12:00:00 2017 | @ 2 days 12 hours
+ Wed May 03 13:00:00 2017 | @ 2 days 13 hours
+ Wed May 03 14:00:00 2017 | @ 2 days 14 hours
+ Wed May 03 15:00:00 2017 | @ 2 days 15 hours
+ Wed May 03 16:00:00 2017 | @ 2 days 16 hours
+ Wed May 03 17:00:00 2017 | @ 2 days 17 hours
+ Wed May 03 18:00:00 2017 | @ 2 days 18 hours
+ Wed May 03 19:00:00 2017 | @ 2 days 19 hours
+(20 rows)
+
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |  ?column?  
+--------------------------+------------
+ Mon Jan 01 00:00:00 2018 | @ 0
+ Mon Jan 01 01:00:00 2018 | @ 1 hour
+ Sun Dec 31 23:00:00 2017 | @ 1 hour
+ Mon Jan 01 02:00:00 2018 | @ 2 hours
+ Sun Dec 31 22:00:00 2017 | @ 2 hours
+ Mon Jan 01 03:00:00 2018 | @ 3 hours
+ Sun Dec 31 21:00:00 2017 | @ 3 hours
+ Mon Jan 01 04:00:00 2018 | @ 4 hours
+ Sun Dec 31 20:00:00 2017 | @ 4 hours
+ Mon Jan 01 05:00:00 2018 | @ 5 hours
+ Sun Dec 31 19:00:00 2017 | @ 5 hours
+ Mon Jan 01 06:00:00 2018 | @ 6 hours
+ Sun Dec 31 18:00:00 2017 | @ 6 hours
+ Mon Jan 01 07:00:00 2018 | @ 7 hours
+ Sun Dec 31 17:00:00 2017 | @ 7 hours
+ Mon Jan 01 08:00:00 2018 | @ 8 hours
+ Sun Dec 31 16:00:00 2017 | @ 8 hours
+ Mon Jan 01 09:00:00 2018 | @ 9 hours
+ Sun Dec 31 15:00:00 2017 | @ 9 hours
+ Mon Jan 01 10:00:00 2018 | @ 10 hours
+(20 rows)
+
+DROP TABLE knn_btree_ts;
+RESET enable_bitmapscan;
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 21171f7..307f2f5 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -111,3 +111,235 @@ create index btree_idx_err on btree_test(a) with (vacuum_cleanup_index_scale_fac
 -- Simple ALTER INDEX
 alter index btree_idx1 set (vacuum_cleanup_index_scale_factor = 70.0);
 select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass;
+
+---
+--- Test B-tree distance ordering
+---
+
+SET enable_bitmapscan = OFF;
+
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+-- test parallel KNN scan
+
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+
+RESET enable_indexscan;
+
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, 1000000) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+EXPLAIN (COSTS OFF)
+SELECT i FROM bt_knn_test WHERE i > 8000000;
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> 4000003) AS n, i * 10 AS i
+	FROM generate_series(1, 1000000) i;
+
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+DROP TABLE bt_knn_test;
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, 100000) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+
+ROLLBACK;
+
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+
+
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+DROP INDEX tenk3_idx;
+
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+DROP INDEX tenk3_idx;
+
+DROP TABLE tenk3;
+
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+
+DROP TABLE knn_btree_ts;
+
+RESET enable_bitmapscan;
-- 
2.7.4

#13Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Nikita Glukhov (#12)
Re: [PATCH] kNN for btree

Hi!

On Fri, Nov 30, 2018 at 3:02 PM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

On 29.11.2018 18:24, Dmitry Dolgov wrote:

On Wed, Sep 26, 2018 at 5:41 PM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

Attached 3rd version of the patches rebased onto the current master.

Changes from the previous version:
- Added support of INCLUDE columns to get_index_column_opclass() (1st patch).
- Added parallel kNN scan support.
- amcanorderbyop() was transformed into ammatchorderby() which takes a List of
PathKeys and checks each of them with new function match_orderbyop_pathkey()
extracted from match_pathkeys_to_index(). I think that this design can be
used in the future to support a mix of ordinary and order-by-op PathKeys,
but I am not sure.

Hi,

Unfortunately, the patch has some conflicts, could you rebase it? In the
meantime I'll move it to the next CF, hoping to have more reviewers for this
item.

Attached 4th version of the patches rebased onto the current master.

I think this patchset in general has a good shape. After some rounds
of review, it might be committed during January commitfest.

For now, I have following notes.

* 0002-Introduce-ammatchorderby-function-v04.patch

I think match_orderbyop_pathkey() and match_orderbyop_pathkeys()
deserve some high-level commends describing what these functions are
expected to do.

* 0004-Add-kNN-support-to-btree-v04.patch

+ <para>
+  FIXME!!!
+  To implement the distance ordered (nearest-neighbor) search, we only need
+  to define a distance operator (usually it called &lt;-&gt;) with a
correpsonding
+  operator family for distance comparison in the index's operator class.
+  These operators must satisfy the following assumptions for all non-null
+  values A,B,C of the datatype:
+
+  A &lt;-&gt; B = B &lt;-&gt; A symmetric law
+  if A = B, then A &lt;-&gt; C = B &lt;-&gt; C distance equivalence
+  if (A &lt;= B and B &lt;= C) or (A &gt;= B and B &gt;= C),
+  then A &lt;-&gt; B &lt;= A &lt;-&gt; C monotonicity
+ </para>

What exactly you're going to fix here? I think you at least should
provide a proper formatting to this paragraph....

* 0006-Remove-distance-operators-from-btree_gist-v04.patch

I see you provide btree_gist--1.6.sql and remove btree_gist--1.2.sql.
Note, that in order to better checking of extension migrations, we're
now providing just migration script to new version. So, everybody
installing new version will go through the migration. However, in
this particular case we've mass deletion of former extension objects.
So, I think this case should be an exception to the rules. And it's
good to provide new version of extension script in this case. Other
opinions?

A see btree_gist--1.5--1.6.sql contains a sophisticated query
updating extension operators to builtin operators. However, what do
you think about just long sequence of ALTER OPERATOR FAMILY commands
removing old operators and adding new operators? It would be longer,
but more predictable and easier for understanding.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#14Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#13)
Re: [PATCH] kNN for btree

On Thu, Dec 27, 2018 at 5:46 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

* 0006-Remove-distance-operators-from-btree_gist-v04.patch

I see you provide btree_gist--1.6.sql and remove btree_gist--1.2.sql.
Note, that in order to better checking of extension migrations, we're
now providing just migration script to new version. So, everybody
installing new version will go through the migration. However, in
this particular case we've mass deletion of former extension objects.
So, I think this case should be an exception to the rules. And it's
good to provide new version of extension script in this case. Other
opinions?

I also note that you've removed implementation of distance functions
from btree_gist. But during pg_upgrade extensions are moved "as is".
Not just CREATE EXTENSION command is dumped, but the whole extension
content. pg_upgrade'd instances would have old version of extension
metadata with new .so until ALTER EXTENSION UPDATE. So, user would get
errors about missed function in .so until updates the extension.

We're typically evade this by inclusion of old functions into new .so.
Then user can work normally before extension update. In this
particular case, we can leave the distance functions in the .so, but
make them just wrappers over core functions.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#15Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#14)
Re: [PATCH] kNN for btree

On Sun, Dec 30, 2018 at 1:19 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Thu, Dec 27, 2018 at 5:46 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

* 0006-Remove-distance-operators-from-btree_gist-v04.patch

I see you provide btree_gist--1.6.sql and remove btree_gist--1.2.sql.
Note, that in order to better checking of extension migrations, we're
now providing just migration script to new version. So, everybody
installing new version will go through the migration. However, in
this particular case we've mass deletion of former extension objects.
So, I think this case should be an exception to the rules. And it's
good to provide new version of extension script in this case. Other
opinions?

I also note that you've removed implementation of distance functions
from btree_gist. But during pg_upgrade extensions are moved "as is".
Not just CREATE EXTENSION command is dumped, but the whole extension
content. pg_upgrade'd instances would have old version of extension
metadata with new .so until ALTER EXTENSION UPDATE. So, user would get
errors about missed function in .so until updates the extension.

We're typically evade this by inclusion of old functions into new .so.
Then user can work normally before extension update. In this
particular case, we can leave the distance functions in the .so, but
make them just wrappers over core functions.

I've run regression tests with patch applied and opr_sanity showed some errors:

1) date_dist_timestamptz(), timestamp_dist_timestamptz(),
timestamptz_dist_date(), timestamptz_dist_timestamp() should be
stable, not immutable. These functions use timezone during
conversion.

2) date_dist_timestamp(), date_dist_timestamptz(),
timestamp_dist_date(), timestamp_dist_timestamptz(),
timestamptz_dist_date(), timestamptz_dist_timestamp() should be not
leafproof. These functions perform conversion, which might fail in
corner case. So, this error should be considered as a leak.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#16Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#15)
Re: [PATCH] kNN for btree

Hi!

I've couple more notes regarding this patch.
1) There are two loops over scan key determining scan strategy:
existing loop in _bt_first(), and in new function
_bt_select_knn_search_strategy(). It's kind of redundant that we've
to process scan keys twice for knn searches. I think scan keys
processing should be merged into one loop.
2) We're not supporting knn ordering only using the first key.
Supporting multiple knn keys would require significant reword of
B-tree traversal algorithm making it closer to GiST and SP-GiST. So,
I think supporting only one knn key is reasonable restriction, at
least for now. But we could support single-key knn ordering in more
cases. For instance, knn search in "SELECT * FROM tbl WHERE a =
const1 ORDER BY b <-> const2" could benefit from (a, b) B-tree index.
So, we can support knn search on n-th key if there are equality scan
keys for [1, n-1] index keys.

I've already discussed these issues with Nikita personally.
Hopefully, new version will be published soon.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#17Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Alexander Korotkov (#16)
7 attachment(s)
Re: [PATCH] kNN for btree

Attached 5th version of the patches.

On 11.01.2019 2:21, Alexander Korotkov wrote:

Hi!

I've couple more notes regarding this patch.
1) There are two loops over scan key determining scan strategy:
existing loop in _bt_first(), and in new function
_bt_select_knn_search_strategy(). It's kind of redundant that we've
to process scan keys twice for knn searches. I think scan keys
processing should be merged into one loop.

This redundant loop was removed and code from _bt_select_knn_search_strategy()
was moved into the new function _bt_emit_scan_key() extracted from
_bt_preprocess_keys().

2) We're not supporting knn ordering only using the first key.
Supporting multiple knn keys would require significant reword of
B-tree traversal algorithm making it closer to GiST and SP-GiST. So,
I think supporting only one knn key is reasonable restriction, at
least for now. But we could support single-key knn ordering in more
cases. For instance, knn search in "SELECT * FROM tbl WHERE a =
const1 ORDER BY b <-> const2" could benefit from (a, b) B-tree index.
So, we can support knn search on n-th key if there are equality scan
keys for [1, n-1] index keys.

I will try to implement this in the next version of the patch.

I also note that you've removed implementation of distance functions
from btree_gist. But during pg_upgrade extensions are moved "as is".
Not just CREATE EXTENSION command is dumped, but the whole extension
content. pg_upgrade'd instances would have old version of extension
metadata with new .so until ALTER EXTENSION UPDATE. So, user would get
errors about missed function in .so until updates the extension.

We're typically evade this by inclusion of old functions into new .so.
Then user can work normally before extension update. In this
particular case, we can leave the distance functions in the .so, but
make them just wrappers over core functions.

Wrappers over core functions were left in btree_gist.

I've run regression tests with patch applied and opr_sanity showed some errors:

1) date_dist_timestamptz(), timestamp_dist_timestamptz(),
timestamptz_dist_date(), timestamptz_dist_timestamp() should be
stable, not immutable. These functions use timezone during
conversion.

Fixed.

2) date_dist_timestamp(), date_dist_timestamptz(),
timestamp_dist_date(), timestamp_dist_timestamptz(),
timestamptz_dist_date(), timestamptz_dist_timestamp() should be not
leafproof. These functions perform conversion, which might fail in
corner case. So, this error should be considered as a leak.

All new distance functions except oiddist() are not leakproof,
so I had to relax condition in opr_sanity.sql test:

- pp.proleakproof != po.proleakproof
+ (NOT pp.proleakproof AND po.proleakproof))

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Fix-get_index_column_opclass-v05.patchtext/x-patch; name=0001-Fix-get_index_column_opclass-v05.patchDownload
From 9632809b67531dd3d04a5b478ed39b8b55063284 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 11 Jan 2019 15:51:28 +0300
Subject: [PATCH 1/7] Fix get_index_column_opclass

---
 src/backend/utils/cache/lsyscache.c | 23 +++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)

diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index fba0ee8..c97c8bd 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -3131,9 +3131,6 @@ get_index_column_opclass(Oid index_oid, int attno)
 {
 	HeapTuple	tuple;
 	Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY;
-	Datum		datum;
-	bool		isnull;
-	oidvector  *indclass;
 	Oid			opclass;
 
 	/* First we need to know the column's opclass. */
@@ -3147,12 +3144,22 @@ get_index_column_opclass(Oid index_oid, int attno)
 	/* caller is supposed to guarantee this */
 	Assert(attno > 0 && attno <= rd_index->indnatts);
 
-	datum = SysCacheGetAttr(INDEXRELID, tuple,
-							Anum_pg_index_indclass, &isnull);
-	Assert(!isnull);
+	if (attno >= 1 && attno <= rd_index->indnkeyatts)
+	{
+		oidvector  *indclass;
+		bool		isnull;
+		Datum		datum = SysCacheGetAttr(INDEXRELID, tuple,
+											Anum_pg_index_indclass,
+											&isnull);
+		Assert(!isnull);
 
-	indclass = ((oidvector *) DatumGetPointer(datum));
-	opclass = indclass->values[attno - 1];
+		indclass = ((oidvector *) DatumGetPointer(datum));
+		opclass = indclass->values[attno - 1];
+	}
+	else
+	{
+		opclass = InvalidOid;
+	}
 
 	ReleaseSysCache(tuple);
 
-- 
2.7.4

0002-Introduce-ammatchorderby-function-v05.patchtext/x-patch; name=0002-Introduce-ammatchorderby-function-v05.patchDownload
From 2cf1738c6c7a614783fbdb411c09063ed13f7e01 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 11 Jan 2019 15:51:28 +0300
Subject: [PATCH 2/7] Introduce ammatchorderby function

---
 contrib/bloom/blutils.c               |   2 +-
 src/backend/access/brin/brin.c        |   2 +-
 src/backend/access/gin/ginutil.c      |   2 +-
 src/backend/access/gist/gist.c        |   3 +-
 src/backend/access/hash/hash.c        |   2 +-
 src/backend/access/nbtree/nbtree.c    |   2 +-
 src/backend/access/spgist/spgutils.c  |   6 +-
 src/backend/commands/opclasscmds.c    |   2 +-
 src/backend/executor/nodeIndexscan.c  |   4 +-
 src/backend/optimizer/path/indxpath.c | 192 ++++++++++++++++++++--------------
 src/backend/optimizer/util/plancat.c  |   2 +-
 src/backend/utils/adt/amutils.c       |   2 +-
 src/include/access/amapi.h            |  14 ++-
 src/include/nodes/plannodes.h         |   2 +-
 src/include/nodes/relation.h          |   8 +-
 src/include/optimizer/paths.h         |   4 +
 16 files changed, 148 insertions(+), 101 deletions(-)

diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 6458376..9bff793 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -109,7 +109,6 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BLOOM_NSTRATEGIES;
 	amroutine->amsupport = BLOOM_NPROC;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -143,6 +142,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 03c8a6b..d1d2b0f 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -86,7 +86,6 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = BRIN_LAST_OPTIONAL_PROCNUM;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -120,6 +119,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index afc2023..0f6714d 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -41,7 +41,6 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GINNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -75,6 +74,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index b75b3a8..77ca187 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -18,6 +18,7 @@
 #include "access/gistscan.h"
 #include "catalog/pg_collation.h"
 #include "miscadmin.h"
+#include "optimizer/paths.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "nodes/execnodes.h"
@@ -64,7 +65,6 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GISTNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -98,6 +98,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = match_orderbyop_pathkeys;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index f1f01a0..bb5c6a1 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -59,7 +59,6 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = HTMaxStrategyNumber;
 	amroutine->amsupport = HASHNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -93,6 +92,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 98917de..c56ee5e 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -110,7 +110,6 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BTMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = true;
 	amroutine->amcanmulticol = true;
@@ -144,6 +143,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = btestimateparallelscan;
 	amroutine->aminitparallelscan = btinitparallelscan;
 	amroutine->amparallelrescan = btparallelrescan;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index de147d7..37de33c 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -32,10 +32,6 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
-extern Expr *spgcanorderbyop(IndexOptInfo *index,
-				PathKey *pathkey, int pathkeyno,
-				Expr *orderby_clause, int *indexcol_p);
-
 /*
  * SP-GiST handler function: return IndexAmRoutine with access method parameters
  * and callbacks.
@@ -48,7 +44,6 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = SPGISTNProc;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -82,6 +77,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = match_orderbyop_pathkeys;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 5a16045..4f670e7 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -1098,7 +1098,7 @@ assignOperTypes(OpFamilyMember *member, Oid amoid, Oid typeoid)
 		 */
 		IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid, false);
 
-		if (!amroutine->amcanorderbyop)
+		if (!amroutine->ammatchorderby)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("access method \"%s\" does not support ordering operators",
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index f255551..9115c16 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -199,7 +199,7 @@ IndexNextWithReorder(IndexScanState *node)
 	 * with just Asserting here because the system will not try to run the
 	 * plan backwards if ExecSupportsBackwardScan() says it won't work.
 	 * Currently, that is guaranteed because no index AMs support both
-	 * amcanorderbyop and amcanbackward; if any ever do,
+	 * ammatchorderby and amcanbackward; if any ever do,
 	 * ExecSupportsBackwardScan() will need to consider indexorderbys
 	 * explicitly.
 	 */
@@ -1149,7 +1149,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
  * 5. NullTest ("indexkey IS NULL/IS NOT NULL").  We just fill in the
  * ScanKey properly.
  *
- * This code is also used to prepare ORDER BY expressions for amcanorderbyop
+ * This code is also used to prepare ORDER BY expressions for ammatchorderby
  * indexes.  The behavior is exactly the same, except that we have to look up
  * the operator differently.  Note that only cases 1 and 2 are currently
  * possible for ORDER BY.
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index f8e674c..2bf87ad 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -17,6 +17,7 @@
 
 #include <math.h>
 
+#include "access/amapi.h"
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_am.h"
@@ -156,6 +157,10 @@ static void match_clause_to_index(IndexOptInfo *index,
 static bool match_clause_to_indexcol(IndexOptInfo *index,
 						 int indexcol,
 						 RestrictInfo *rinfo);
+static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
+							int indexcol,
+							Expr *clause,
+							Oid pk_opfamily);
 static bool is_indexable_operator(Oid expr_op, Oid opfamily,
 					  bool indexkey_on_left);
 static bool match_rowcompare_to_indexcol(IndexOptInfo *index,
@@ -166,8 +171,6 @@ static bool match_rowcompare_to_indexcol(IndexOptInfo *index,
 static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 						List **orderby_clauses_p,
 						List **clause_columns_p);
-static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
-							int indexcol, Expr *clause, Oid pk_opfamily);
 static bool ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
 						   EquivalenceClass *ec, EquivalenceMember *em,
 						   void *arg);
@@ -1000,7 +1003,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		orderbyclauses = NIL;
 		orderbyclausecols = NIL;
 	}
-	else if (index->amcanorderbyop && pathkeys_possibly_useful)
+	else if (index->ammatchorderby && pathkeys_possibly_useful)
 	{
 		/* see if we can generate ordering operators for query_pathkeys */
 		match_pathkeys_to_index(index, root->query_pathkeys,
@@ -2576,6 +2579,99 @@ match_rowcompare_to_indexcol(IndexOptInfo *index,
 	return false;
 }
 
+Expr *
+match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey, int *indexcol_p)
+{
+	ListCell   *lc;
+
+	/* Pathkey must request default sort order for the target opfamily */
+	if (pathkey->pk_strategy != BTLessStrategyNumber ||
+		pathkey->pk_nulls_first)
+		return NULL;
+
+	/* If eclass is volatile, no hope of using an indexscan */
+	if (pathkey->pk_eclass->ec_has_volatile)
+		return NULL;
+
+	/*
+	 * Try to match eclass member expression(s) to index.  Note that child
+	 * EC members are considered, but only when they belong to the target
+	 * relation.  (Unlike regular members, the same expression could be a
+	 * child member of more than one EC.  Therefore, the same index could
+	 * be considered to match more than one pathkey list, which is OK
+	 * here.  See also get_eclass_for_sort_expr.)
+	 */
+	foreach(lc, pathkey->pk_eclass->ec_members)
+	{
+		EquivalenceMember *member = castNode(EquivalenceMember, lfirst(lc));
+		int			indexcol;
+		int			indexcol_min;
+		int			indexcol_max;
+
+		/* No possibility of match if it references other relations */
+		if (!bms_equal(member->em_relids, index->rel->relids))
+			continue;
+
+		/* If *indexcol_p is non-negative then try to match only to it */
+		if (*indexcol_p >= 0)
+		{
+			indexcol_min = *indexcol_p;
+			indexcol_max = *indexcol_p + 1;
+		}
+		else	/* try to match all columns */
+		{
+			indexcol_min = 0;
+			indexcol_max = index->ncolumns;
+		}
+
+		/*
+		 * We allow any column of the GiST index to match each pathkey;
+		 * they don't have to match left-to-right as you might expect.
+		 */
+		for (indexcol = indexcol_min; indexcol < indexcol_max; indexcol++)
+		{
+			Expr	   *expr = match_clause_to_ordering_op(index,
+														   indexcol,
+														   member->em_expr,
+														   pathkey->pk_opfamily);
+			if (expr)
+			{
+				*indexcol_p = indexcol;
+				return expr;	/* don't want to look at remaining members */
+			}
+		}
+	}
+
+	return NULL;
+}
+
+bool
+match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
+						 List **orderby_clauses_p, List **clause_columns_p)
+{
+	ListCell   *lc;
+
+	foreach(lc, pathkeys)
+	{
+		PathKey	   *pathkey = castNode(PathKey, lfirst(lc));
+		Expr	   *expr;
+		int			indexcol = -1;	/* match all index columns */
+
+		expr = match_orderbyop_pathkey(index, pathkey, &indexcol);
+
+		/*
+		 * Note: for any failure to match, we just return NIL immediately.
+		 * There is no value in matching just some of the pathkeys.
+		 */
+		if (!expr)
+			return false;
+
+		*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+		*clause_columns_p = lappend_int(*clause_columns_p, indexcol);
+	}
+
+	return true;	/* success */
+}
 
 /****************************************************************************
  *				----  ROUTINES TO CHECK ORDERING OPERATORS	----
@@ -2601,86 +2697,24 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 {
 	List	   *orderby_clauses = NIL;
 	List	   *clause_columns = NIL;
-	ListCell   *lc1;
+	ammatchorderby_function ammatchorderby =
+			(ammatchorderby_function) index->ammatchorderby;
 
-	*orderby_clauses_p = NIL;	/* set default results */
-	*clause_columns_p = NIL;
-
-	/* Only indexes with the amcanorderbyop property are interesting here */
-	if (!index->amcanorderbyop)
-		return;
-
-	foreach(lc1, pathkeys)
+	/* Only indexes with the ammatchorderby function are interesting here */
+	if (ammatchorderby &&
+		ammatchorderby(index, pathkeys, &orderby_clauses, &clause_columns))
 	{
-		PathKey    *pathkey = (PathKey *) lfirst(lc1);
-		bool		found = false;
-		ListCell   *lc2;
-
-		/*
-		 * Note: for any failure to match, we just return NIL immediately.
-		 * There is no value in matching just some of the pathkeys.
-		 */
-
-		/* Pathkey must request default sort order for the target opfamily */
-		if (pathkey->pk_strategy != BTLessStrategyNumber ||
-			pathkey->pk_nulls_first)
-			return;
+		Assert(list_length(pathkeys) == list_length(orderby_clauses));
+		Assert(list_length(pathkeys) == list_length(clause_columns));
 
-		/* If eclass is volatile, no hope of using an indexscan */
-		if (pathkey->pk_eclass->ec_has_volatile)
-			return;
-
-		/*
-		 * Try to match eclass member expression(s) to index.  Note that child
-		 * EC members are considered, but only when they belong to the target
-		 * relation.  (Unlike regular members, the same expression could be a
-		 * child member of more than one EC.  Therefore, the same index could
-		 * be considered to match more than one pathkey list, which is OK
-		 * here.  See also get_eclass_for_sort_expr.)
-		 */
-		foreach(lc2, pathkey->pk_eclass->ec_members)
-		{
-			EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
-			int			indexcol;
-
-			/* No possibility of match if it references other relations */
-			if (!bms_equal(member->em_relids, index->rel->relids))
-				continue;
-
-			/*
-			 * We allow any column of the index to match each pathkey; they
-			 * don't have to match left-to-right as you might expect.  This is
-			 * correct for GiST, which is the sole existing AM supporting
-			 * amcanorderbyop.  We might need different logic in future for
-			 * other implementations.
-			 */
-			for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
-			{
-				Expr	   *expr;
-
-				expr = match_clause_to_ordering_op(index,
-												   indexcol,
-												   member->em_expr,
-												   pathkey->pk_opfamily);
-				if (expr)
-				{
-					orderby_clauses = lappend(orderby_clauses, expr);
-					clause_columns = lappend_int(clause_columns, indexcol);
-					found = true;
-					break;
-				}
-			}
-
-			if (found)			/* don't want to look at remaining members */
-				break;
-		}
-
-		if (!found)				/* fail if no match for this pathkey */
-			return;
+		*orderby_clauses_p = orderby_clauses;	/* success! */
+		*clause_columns_p = clause_columns;
+	}
+	else
+	{
+		*orderby_clauses_p = NIL;	/* set default results */
+		*clause_columns_p = NIL;
 	}
-
-	*orderby_clauses_p = orderby_clauses;	/* success! */
-	*clause_columns_p = clause_columns;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 48ffc5f..9c30709 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -265,7 +265,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 			/* We copy just the fields we need, not all of rd_amroutine */
 			amroutine = indexRelation->rd_amroutine;
-			info->amcanorderbyop = amroutine->amcanorderbyop;
+			info->ammatchorderby = amroutine->ammatchorderby;
 			info->amoptionalkey = amroutine->amoptionalkey;
 			info->amsearcharray = amroutine->amsearcharray;
 			info->amsearchnulls = amroutine->amsearchnulls;
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c
index 060ffe5..3907065 100644
--- a/src/backend/utils/adt/amutils.c
+++ b/src/backend/utils/adt/amutils.c
@@ -298,7 +298,7 @@ indexam_property(FunctionCallInfo fcinfo,
 				 * a nonkey column, and null otherwise (meaning we don't
 				 * know).
 				 */
-				if (!iskey || !routine->amcanorderbyop)
+				if (!iskey || !routine->ammatchorderby)
 				{
 					res = false;
 					isnull = false;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 653ddc9..09411a7 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -21,6 +21,9 @@
  */
 struct PlannerInfo;
 struct IndexPath;
+struct IndexOptInfo;
+struct PathKey;
+struct Expr;
 
 /* Likewise, this file shouldn't depend on execnodes.h. */
 struct IndexInfo;
@@ -140,6 +143,12 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/* does AM support ORDER BY result of an operator on indexed column? */
+typedef bool (*ammatchorderby_function) (struct IndexOptInfo *index,
+										 List *pathkeys,
+										 List **orderby_clauses_p,
+										 List **clause_columns_p);
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -170,8 +179,6 @@ typedef struct IndexAmRoutine
 	uint16		amsupport;
 	/* does AM support ORDER BY indexed column's value? */
 	bool		amcanorder;
-	/* does AM support ORDER BY result of an operator on indexed column? */
-	bool		amcanorderbyop;
 	/* does AM support backward scanning? */
 	bool		amcanbackward;
 	/* does AM support UNIQUE indexes? */
@@ -221,7 +228,8 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;	/* can be NULL */
 	amrestrpos_function amrestrpos; /* can be NULL */
-
+	ammatchorderby_function ammatchorderby; /* can be NULL */
+	
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
 	aminitparallelscan_function aminitparallelscan; /* can be NULL */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 6d087c2..bd6ce97 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -388,7 +388,7 @@ typedef struct SampleScan
  * indexorderbyops is a list of the OIDs of the operators used to sort the
  * ORDER BY expressions.  This is used together with indexorderbyorig to
  * recheck ordering at run time.  (Note that indexorderby, indexorderbyorig,
- * and indexorderbyops are used for amcanorderbyop cases, not amcanorder.)
+ * and indexorderbyops are used for ammatchorderby cases, not amcanorder.)
  *
  * indexorderdir specifies the scan ordering, for indexscans on amcanorder
  * indexes (for other indexes it should be "don't care").
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 3430061..af6963f 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -804,7 +804,6 @@ typedef struct IndexOptInfo
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
 	bool		amoptionalkey;	/* can query omit key for the first column? */
 	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
 	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
@@ -813,6 +812,11 @@ typedef struct IndexOptInfo
 	bool		amcanparallel;	/* does AM support parallel scan? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
+								/* AM order-by match function */
+	bool		(*ammatchorderby) (struct IndexOptInfo *index,
+								   List *pathkeys,
+								   List **orderby_clauses_p,
+								   List **clause_columns_p);
 } IndexOptInfo;
 
 /*
@@ -1136,7 +1140,7 @@ typedef struct Path
  * (The order of multiple quals for the same index column is unspecified.)
  *
  * 'indexorderbys', if not NIL, is a list of ORDER BY expressions that have
- * been found to be usable as ordering operators for an amcanorderbyop index.
+ * been found to be usable as ordering operators for an ammatchorderby index.
  * The list must match the path's pathkeys, ie, one expression per pathkey
  * in the same order.  These are not RestrictInfos, just bare expressions,
  * since they generally won't yield booleans.  Also, unlike the case for
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 666217c..718182b 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -87,6 +87,10 @@ extern Expr *adjust_rowcompare_for_index(RowCompareExpr *clause,
 							int indexcol,
 							List **indexcolnos,
 							bool *var_on_left_p);
+extern Expr *match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey,
+									 int *indexcol_p);
+extern bool match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
+						 List **orderby_clauses_p, List **clause_columns_p);
 
 /*
  * tidpath.h
-- 
2.7.4

0003-Extract-structure-BTScanState-v05.patchtext/x-patch; name=0003-Extract-structure-BTScanState-v05.patchDownload
From 8823f18e84c4d6e381a38f23e775043c184ee794 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 11 Jan 2019 15:51:28 +0300
Subject: [PATCH 3/7] Extract structure BTScanState

---
 src/backend/access/nbtree/nbtree.c    | 200 ++++++++++--------
 src/backend/access/nbtree/nbtsearch.c | 379 +++++++++++++++++-----------------
 src/backend/access/nbtree/nbtutils.c  |  49 +++--
 src/include/access/nbtree.h           |  38 ++--
 4 files changed, 355 insertions(+), 311 deletions(-)

diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index c56ee5e..27d9c6f 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -214,6 +214,7 @@ bool
 btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 	bool		res;
 
 	/* btree indexes are never lossy */
@@ -224,7 +225,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(so->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
@@ -241,7 +242,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(so->currPos))
+		if (!BTScanPosIsValid(state->currPos))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -259,11 +260,11 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 				 * trying to optimize that, so we don't detect it, but instead
 				 * just forget any excess entries.
 				 */
-				if (so->killedItems == NULL)
-					so->killedItems = (int *)
+				if (state->killedItems == NULL)
+					state->killedItems = (int *)
 						palloc(MaxIndexTuplesPerPage * sizeof(int));
-				if (so->numKilled < MaxIndexTuplesPerPage)
-					so->killedItems[so->numKilled++] = so->currPos.itemIndex;
+				if (state->numKilled < MaxIndexTuplesPerPage)
+					state->killedItems[so->state.numKilled++] = state->currPos.itemIndex;
 			}
 
 			/*
@@ -288,6 +289,7 @@ int64
 btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	int64		ntids = 0;
 	ItemPointer heapTid;
 
@@ -320,7 +322,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				 * Advance to next tuple within page.  This is the same as the
 				 * easy case in _bt_next().
 				 */
-				if (++so->currPos.itemIndex > so->currPos.lastItem)
+				if (++currPos->itemIndex > currPos->lastItem)
 				{
 					/* let _bt_next do the heavy lifting */
 					if (!_bt_next(scan, ForwardScanDirection))
@@ -328,7 +330,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				}
 
 				/* Save tuple ID, and continue scanning */
-				heapTid = &so->currPos.items[so->currPos.itemIndex].heapTid;
+				heapTid = &currPos->items[currPos->itemIndex].heapTid;
 				tbm_add_tuples(tbm, heapTid, 1, false);
 				ntids++;
 			}
@@ -356,8 +358,8 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 
 	/* allocate private workspace */
 	so = (BTScanOpaque) palloc(sizeof(BTScanOpaqueData));
-	BTScanPosInvalidate(so->currPos);
-	BTScanPosInvalidate(so->markPos);
+	BTScanPosInvalidate(so->state.currPos);
+	BTScanPosInvalidate(so->state.markPos);
 	if (scan->numberOfKeys > 0)
 		so->keyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData));
 	else
@@ -368,15 +370,15 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	so->arrayKeys = NULL;
 	so->arrayContext = NULL;
 
-	so->killedItems = NULL;		/* until needed */
-	so->numKilled = 0;
+	so->state.killedItems = NULL;		/* until needed */
+	so->state.numKilled = 0;
 
 	/*
 	 * We don't know yet whether the scan will be index-only, so we do not
 	 * allocate the tuple workspace arrays until btrescan.  However, we set up
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
-	so->currTuples = so->markTuples = NULL;
+	so->state.currTuples = so->state.markTuples = NULL;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -385,6 +387,45 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	return scan;
 }
 
+static void
+_bt_release_current_position(BTScanState state, Relation indexRelation,
+							 bool invalidate)
+{
+	/* we aren't holding any read locks, but gotta drop the pins */
+	if (BTScanPosIsValid(state->currPos))
+	{
+		/* Before leaving current page, deal with any killed items */
+		if (state->numKilled > 0)
+			_bt_killitems(state, indexRelation);
+
+		BTScanPosUnpinIfPinned(state->currPos);
+
+		if (invalidate)
+			BTScanPosInvalidate(state->currPos);
+	}
+}
+
+static void
+_bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
+{
+	/* No need to invalidate positions, if the RAM is about to be freed. */
+	_bt_release_current_position(state, scan->indexRelation, !free);
+
+	state->markItemIndex = -1;
+	BTScanPosUnpinIfPinned(state->markPos);
+
+	if (free)
+	{
+		if (state->killedItems != NULL)
+			pfree(state->killedItems);
+		if (state->currTuples != NULL)
+			pfree(state->currTuples);
+		/* markTuples should not be pfree'd (_bt_allocate_tuple_workspaces) */
+	}
+	else
+		BTScanPosInvalidate(state->markPos);
+}
+
 /*
  *	btrescan() -- rescan an index relation
  */
@@ -393,21 +434,11 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 		 ScanKey orderbys, int norderbys)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-		BTScanPosInvalidate(so->currPos);
-	}
+	_bt_release_scan_state(scan, state, false);
 
-	so->markItemIndex = -1;
-	so->arrayKeyCount = 0;
-	BTScanPosUnpinIfPinned(so->markPos);
-	BTScanPosInvalidate(so->markPos);
+	so->arrayKeyCount = 0; /* FIXME in _bt_release_scan_state */
 
 	/*
 	 * Allocate tuple workspace arrays, if needed for an index-only scan and
@@ -425,11 +456,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 	 * a SIGSEGV is not possible.  Yeah, this is ugly as sin, but it beats
 	 * adding special-case treatment for name_ops elsewhere.
 	 */
-	if (scan->xs_want_itup && so->currTuples == NULL)
-	{
-		so->currTuples = (char *) palloc(BLCKSZ * 2);
-		so->markTuples = so->currTuples + BLCKSZ;
-	}
+	if (scan->xs_want_itup && state->currTuples == NULL)
+		_bt_allocate_tuple_workspaces(state);
 
 	/*
 	 * Reset the scan keys. Note that keys ordering stuff moved to _bt_first.
@@ -453,19 +481,7 @@ btendscan(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-	}
-
-	so->markItemIndex = -1;
-	BTScanPosUnpinIfPinned(so->markPos);
-
-	/* No need to invalidate positions, the RAM is about to be freed. */
+	_bt_release_scan_state(scan, &so->state, true);
 
 	/* Release storage */
 	if (so->keyData != NULL)
@@ -473,24 +489,15 @@ btendscan(IndexScanDesc scan)
 	/* so->arrayKeyData and so->arrayKeys are in arrayContext */
 	if (so->arrayContext != NULL)
 		MemoryContextDelete(so->arrayContext);
-	if (so->killedItems != NULL)
-		pfree(so->killedItems);
-	if (so->currTuples != NULL)
-		pfree(so->currTuples);
-	/* so->markTuples should not be pfree'd, see btrescan */
+
 	pfree(so);
 }
 
-/*
- *	btmarkpos() -- save current scan position
- */
-void
-btmarkpos(IndexScanDesc scan)
+static void
+_bt_mark_current_position(BTScanState state)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-
 	/* There may be an old mark with a pin (but no lock). */
-	BTScanPosUnpinIfPinned(so->markPos);
+	BTScanPosUnpinIfPinned(state->markPos);
 
 	/*
 	 * Just record the current itemIndex.  If we later step to next page
@@ -498,32 +505,34 @@ btmarkpos(IndexScanDesc scan)
 	 * the currPos struct in markPos.  If (as often happens) the mark is moved
 	 * before we leave the page, we don't have to do that work.
 	 */
-	if (BTScanPosIsValid(so->currPos))
-		so->markItemIndex = so->currPos.itemIndex;
+	if (BTScanPosIsValid(state->currPos))
+		state->markItemIndex = state->currPos.itemIndex;
 	else
 	{
-		BTScanPosInvalidate(so->markPos);
-		so->markItemIndex = -1;
+		BTScanPosInvalidate(state->markPos);
+		state->markItemIndex = -1;
 	}
-
-	/* Also record the current positions of any array keys */
-	if (so->numArrayKeys)
-		_bt_mark_array_keys(scan);
 }
 
 /*
- *	btrestrpos() -- restore scan to last saved position
+ *	btmarkpos() -- save current scan position
  */
 void
-btrestrpos(IndexScanDesc scan)
+btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* Restore the marked positions of any array keys */
+	_bt_mark_current_position(&so->state);
+
+	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
-		_bt_restore_array_keys(scan);
+		_bt_mark_array_keys(scan);
+}
 
-	if (so->markItemIndex >= 0)
+static void
+_bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
+{
+	if (state->markItemIndex >= 0)
 	{
 		/*
 		 * The scan has never moved to a new page since the last mark.  Just
@@ -532,7 +541,7 @@ btrestrpos(IndexScanDesc scan)
 		 * NB: In this case we can't count on anything in so->markPos to be
 		 * accurate.
 		 */
-		so->currPos.itemIndex = so->markItemIndex;
+		state->currPos.itemIndex = state->markItemIndex;
 	}
 	else
 	{
@@ -542,28 +551,21 @@ btrestrpos(IndexScanDesc scan)
 		 * locks, but if we're still holding the pin for the current position,
 		 * we must drop it.
 		 */
-		if (BTScanPosIsValid(so->currPos))
-		{
-			/* Before leaving current page, deal with any killed items */
-			if (so->numKilled > 0)
-				_bt_killitems(scan);
-			BTScanPosUnpinIfPinned(so->currPos);
-		}
+		_bt_release_current_position(state, scan->indexRelation,
+									 !BTScanPosIsValid(state->markPos));
 
-		if (BTScanPosIsValid(so->markPos))
+		if (BTScanPosIsValid(state->markPos))
 		{
 			/* bump pin on mark buffer for assignment to current buffer */
-			if (BTScanPosIsPinned(so->markPos))
-				IncrBufferRefCount(so->markPos.buf);
-			memcpy(&so->currPos, &so->markPos,
+			if (BTScanPosIsPinned(state->markPos))
+				IncrBufferRefCount(state->markPos.buf);
+			memcpy(&state->currPos, &state->markPos,
 				   offsetof(BTScanPosData, items[1]) +
-				   so->markPos.lastItem * sizeof(BTScanPosItem));
-			if (so->currTuples)
-				memcpy(so->currTuples, so->markTuples,
-					   so->markPos.nextTupleOffset);
+				   state->markPos.lastItem * sizeof(BTScanPosItem));
+			if (state->currTuples)
+				memcpy(state->currTuples, state->markTuples,
+					   state->markPos.nextTupleOffset);
 		}
-		else
-			BTScanPosInvalidate(so->currPos);
 	}
 }
 
@@ -779,9 +781,10 @@ _bt_parallel_advance_array_keys(IndexScanDesc scan)
 }
 
 /*
- * _bt_vacuum_needs_cleanup() -- Checks if index needs cleanup assuming that
- *			btbulkdelete() wasn't called.
- */
+- * _bt_vacuum_needs_cleanup() -- Checks if index needs cleanup assuming that
+- *			btbulkdelete() wasn't called.
++ *	btrestrpos() -- restore scan to last saved position
+  */
 static bool
 _bt_vacuum_needs_cleanup(IndexVacuumInfo *info)
 {
@@ -844,6 +847,21 @@ _bt_vacuum_needs_cleanup(IndexVacuumInfo *info)
 }
 
 /*
+ *	btrestrpos() -- restore scan to last saved position
+ */
+void
+btrestrpos(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
+	/* Restore the marked positions of any array keys */
+	if (so->numArrayKeys)
+		_bt_restore_array_keys(scan);
+
+	_bt_restore_marked_position(scan, &so->state);
+}
+
+/*
  * Bulk deletion of all index entries pointing to a set of heap tuples.
  * The set of target tuples is specified via a callback routine that tells
  * whether any given heap tuple (identified by ItemPointer) is being deleted.
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index b5244aa..9b7374e 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -25,18 +25,19 @@
 #include "utils/tqual.h"
 
 
-static bool _bt_readpage(IndexScanDesc scan, ScanDirection dir,
+static bool _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 			 OffsetNumber offnum);
-static void _bt_saveitem(BTScanOpaque so, int itemIndex,
+static void _bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup);
-static bool _bt_steppage(IndexScanDesc scan, ScanDirection dir);
-static bool _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir);
+static bool _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir);
+static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
+				 BlockNumber blkno, ScanDirection dir);
 static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
 					  ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf, Snapshot snapshot);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
-static inline void _bt_initialize_more_data(BTScanOpaque so, ScanDirection dir);
+static inline void _bt_initialize_more_data(BTScanState state, ScanDirection dir);
 
 
 /*
@@ -545,6 +546,58 @@ _bt_compare(Relation rel,
 }
 
 /*
+ * _bt_return_current_item() -- Prepare current scan state item for return.
+ *
+ * This function is used only in "return _bt_return_current_item();" statements
+ * and always returns true.
+ */
+static inline bool
+_bt_return_current_item(IndexScanDesc scan, BTScanState state)
+{
+	BTScanPosItem *currItem = &state->currPos.items[state->currPos.itemIndex];
+
+	scan->xs_ctup.t_self = currItem->heapTid;
+
+	if (scan->xs_want_itup)
+		scan->xs_itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+
+	return true;
+}
+
+/*
+ * _bt_load_first_page() -- Load data from the first page of the scan.
+ *
+ * Caller must have pinned and read-locked state->currPos.buf.
+ *
+ * On success exit, state->currPos is updated to contain data from the next
+ * interesting page.  For success on a scan using a non-MVCC snapshot we hold
+ * a pin, but not a read lock, on that page.  If we do not hold the pin, we
+ * set state->currPos.buf to InvalidBuffer.  We return true to indicate success.
+ *
+ * If there are no more matching records in the given direction at all,
+ * we drop all locks and pins, set state->currPos.buf to InvalidBuffer,
+ * and return false.
+ */
+static bool
+_bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+					OffsetNumber offnum)
+{
+	if (!_bt_readpage(scan, state, dir, offnum))
+	{
+		/*
+		 * There's no actually-matching data on this page.  Try to advance to
+		 * the next page.  Return false if there's no matching data at all.
+		 */
+		LockBuffer(state->currPos.buf, BUFFER_LOCK_UNLOCK);
+		return _bt_steppage(scan, state, dir);
+	}
+
+	/* Drop the lock, and maybe the pin, on the current page */
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
+	return true;
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -569,6 +622,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	BTStack		stack;
 	OffsetNumber offnum;
@@ -582,10 +636,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	int			i;
 	bool		status = true;
 	StrategyNumber strat_total;
-	BTScanPosItem *currItem;
 	BlockNumber blkno;
 
-	Assert(!BTScanPosIsValid(so->currPos));
+	Assert(!BTScanPosIsValid(*currPos));
 
 	pgstat_count_index_scan(rel);
 
@@ -1076,7 +1129,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 * their scan
 		 */
 		_bt_parallel_done(scan);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 
 		return false;
 	}
@@ -1084,7 +1137,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		PredicateLockPage(rel, BufferGetBlockNumber(buf),
 						  scan->xs_snapshot);
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
 	/* position to the precise item on the page */
 	offnum = _bt_binsrch(rel, buf, keysCount, scankeys, nextkey);
@@ -1111,36 +1164,36 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		offnum = OffsetNumberPrev(offnum);
 
 	/* remember which buffer we have pinned, if any */
-	Assert(!BTScanPosIsValid(so->currPos));
-	so->currPos.buf = buf;
+	Assert(!BTScanPosIsValid(*currPos));
+	currPos->buf = buf;
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, offnum))
+	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
+		return false;
+
+readcomplete:
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
+}
+
+/*
+ *	Advance to next tuple on current page; or if there's no more,
+ *	try to step to the next page with data.
+ */
+static bool
+_bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
+{
+	if (ScanDirectionIsForward(dir))
 	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
+		if (++state->currPos.itemIndex <= state->currPos.lastItem)
+			return true;
 	}
 	else
 	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+		if (--state->currPos.itemIndex >= state->currPos.firstItem)
+			return true;
 	}
 
-readcomplete:
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_steppage(scan, state, dir);
 }
 
 /*
@@ -1161,44 +1214,20 @@ bool
 _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-	BTScanPosItem *currItem;
 
-	/*
-	 * Advance to next tuple on current page; or if there's no more, try to
-	 * step to the next page with data.
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (++so->currPos.itemIndex > so->currPos.lastItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
-	else
-	{
-		if (--so->currPos.itemIndex < so->currPos.firstItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
+	if (!_bt_next_item(scan, &so->state, dir))
+		return false;
 
 	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
  *	_bt_readpage() -- Load data from current index page into so->currPos
  *
- * Caller must have pinned and read-locked so->currPos.buf; the buffer's state
- * is not changed here.  Also, currPos.moreLeft and moreRight must be valid;
- * they are updated as appropriate.  All other fields of so->currPos are
+ * Caller must have pinned and read-locked pos->buf; the buffer's state
+ * is not changed here.  Also, pos->moreLeft and moreRight must be valid;
+ * they are updated as appropriate.  All other fields of pos are
  * initialized from scratch here.
  *
  * We scan the current page starting at offnum and moving in the indicated
@@ -1213,9 +1242,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
  * Returns true if any matching items found on the page, false if none.
  */
 static bool
-_bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
+_bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+			 OffsetNumber offnum)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
@@ -1228,9 +1258,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We must have the buffer pinned and locked, but the usual macro can't be
 	 * used here; this function is what makes it good for currPos.
 	 */
-	Assert(BufferIsValid(so->currPos.buf));
+	Assert(BufferIsValid(pos->buf));
 
-	page = BufferGetPage(so->currPos.buf);
+	page = BufferGetPage(pos->buf);
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 
 	/* allow next page be processed by parallel worker */
@@ -1239,7 +1269,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		if (ScanDirectionIsForward(dir))
 			_bt_parallel_release(scan, opaque->btpo_next);
 		else
-			_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+			_bt_parallel_release(scan, BufferGetBlockNumber(pos->buf));
 	}
 
 	minoff = P_FIRSTDATAKEY(opaque);
@@ -1249,30 +1279,30 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We note the buffer's block number so that we can release the pin later.
 	 * This allows us to re-read the buffer if it is needed again for hinting.
 	 */
-	so->currPos.currPage = BufferGetBlockNumber(so->currPos.buf);
+	pos->currPage = BufferGetBlockNumber(pos->buf);
 
 	/*
 	 * We save the LSN of the page as we read it, so that we know whether it
 	 * safe to apply LP_DEAD hints to the page later.  This allows us to drop
 	 * the pin for MVCC scans, which allows vacuum to avoid blocking.
 	 */
-	so->currPos.lsn = BufferGetLSNAtomic(so->currPos.buf);
+	pos->lsn = BufferGetLSNAtomic(pos->buf);
 
 	/*
 	 * we must save the page's right-link while scanning it; this tells us
 	 * where to step right to after we're done with these items.  There is no
 	 * corresponding need for the left-link, since splits always go right.
 	 */
-	so->currPos.nextPage = opaque->btpo_next;
+	pos->nextPage = opaque->btpo_next;
 
 	/* initialize tuple workspace to empty */
-	so->currPos.nextTupleOffset = 0;
+	pos->nextTupleOffset = 0;
 
 	/*
 	 * Now that the current page has been made consistent, the macro should be
 	 * good.
 	 */
-	Assert(BTScanPosIsPinned(so->currPos));
+	Assert(BTScanPosIsPinned(*pos));
 
 	if (ScanDirectionIsForward(dir))
 	{
@@ -1287,13 +1317,13 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			if (itup != NULL)
 			{
 				/* tuple passes all scan key conditions, so remember it */
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 				itemIndex++;
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreRight = false;
+				pos->moreRight = false;
 				break;
 			}
 
@@ -1301,9 +1331,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex <= MaxIndexTuplesPerPage);
-		so->currPos.firstItem = 0;
-		so->currPos.lastItem = itemIndex - 1;
-		so->currPos.itemIndex = 0;
+		pos->firstItem = 0;
+		pos->lastItem = itemIndex - 1;
+		pos->itemIndex = 0;
 	}
 	else
 	{
@@ -1319,12 +1349,12 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			{
 				/* tuple passes all scan key conditions, so remember it */
 				itemIndex--;
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreLeft = false;
+				pos->moreLeft = false;
 				break;
 			}
 
@@ -1332,30 +1362,31 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex >= 0);
-		so->currPos.firstItem = itemIndex;
-		so->currPos.lastItem = MaxIndexTuplesPerPage - 1;
-		so->currPos.itemIndex = MaxIndexTuplesPerPage - 1;
+		pos->firstItem = itemIndex;
+		pos->lastItem = MaxIndexTuplesPerPage - 1;
+		pos->itemIndex = MaxIndexTuplesPerPage - 1;
 	}
 
-	return (so->currPos.firstItem <= so->currPos.lastItem);
+	return (pos->firstItem <= pos->lastItem);
 }
 
 /* Save an index item into so->currPos.items[itemIndex] */
 static void
-_bt_saveitem(BTScanOpaque so, int itemIndex,
+_bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup)
 {
-	BTScanPosItem *currItem = &so->currPos.items[itemIndex];
+	BTScanPosItem *currItem = &state->currPos.items[itemIndex];
 
 	currItem->heapTid = itup->t_tid;
 	currItem->indexOffset = offnum;
-	if (so->currTuples)
+	if (state->currTuples)
 	{
 		Size		itupsz = IndexTupleSize(itup);
 
-		currItem->tupleOffset = so->currPos.nextTupleOffset;
-		memcpy(so->currTuples + so->currPos.nextTupleOffset, itup, itupsz);
-		so->currPos.nextTupleOffset += MAXALIGN(itupsz);
+		currItem->tupleOffset = state->currPos.nextTupleOffset;
+		memcpy(state->currTuples + state->currPos.nextTupleOffset,
+			   itup, itupsz);
+		state->currPos.nextTupleOffset += MAXALIGN(itupsz);
 	}
 }
 
@@ -1371,35 +1402,36 @@ _bt_saveitem(BTScanOpaque so, int itemIndex,
  * to InvalidBuffer.  We return true to indicate success.
  */
 static bool
-_bt_steppage(IndexScanDesc scan, ScanDirection dir)
+_bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
+	Relation	rel = scan->indexRelation;
 	BlockNumber blkno = InvalidBlockNumber;
 	bool		status = true;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(*currPos));
 
 	/* Before leaving current page, deal with any killed items */
-	if (so->numKilled > 0)
-		_bt_killitems(scan);
+	if (state->numKilled > 0)
+		_bt_killitems(state, rel);
 
 	/*
 	 * Before we modify currPos, make a copy of the page data if there was a
 	 * mark position that needs it.
 	 */
-	if (so->markItemIndex >= 0)
+	if (state->markItemIndex >= 0)
 	{
 		/* bump pin on current buffer for assignment to mark buffer */
-		if (BTScanPosIsPinned(so->currPos))
-			IncrBufferRefCount(so->currPos.buf);
-		memcpy(&so->markPos, &so->currPos,
+		if (BTScanPosIsPinned(*currPos))
+			IncrBufferRefCount(currPos->buf);
+		memcpy(&state->markPos, currPos,
 			   offsetof(BTScanPosData, items[1]) +
-			   so->currPos.lastItem * sizeof(BTScanPosItem));
-		if (so->markTuples)
-			memcpy(so->markTuples, so->currTuples,
-				   so->currPos.nextTupleOffset);
-		so->markPos.itemIndex = so->markItemIndex;
-		so->markItemIndex = -1;
+			   currPos->lastItem * sizeof(BTScanPosItem));
+		if (state->markTuples)
+			memcpy(state->markTuples, state->currTuples,
+				   currPos->nextTupleOffset);
+		state->markPos.itemIndex = state->markItemIndex;
+		state->markItemIndex = -1;
 	}
 
 	if (ScanDirectionIsForward(dir))
@@ -1415,27 +1447,27 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
-				BTScanPosUnpinIfPinned(so->currPos);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosUnpinIfPinned(*currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so use the previously-saved nextPage link. */
-			blkno = so->currPos.nextPage;
+			blkno = currPos->nextPage;
 		}
 
 		/* Remember we left a page with data */
-		so->currPos.moreLeft = true;
+		currPos->moreLeft = true;
 
 		/* release the previous buffer, if pinned */
-		BTScanPosUnpinIfPinned(so->currPos);
+		BTScanPosUnpinIfPinned(*currPos);
 	}
 	else
 	{
 		/* Remember we left a page with data */
-		so->currPos.moreRight = true;
+		currPos->moreRight = true;
 
 		if (scan->parallel_scan != NULL)
 		{
@@ -1444,25 +1476,25 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			 * ended already, bail out.
 			 */
 			status = _bt_parallel_seize(scan, &blkno);
-			BTScanPosUnpinIfPinned(so->currPos);
+			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so just use our own notion of the current page */
-			blkno = so->currPos.currPage;
+			blkno = currPos->currPage;
 		}
 	}
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, currPos);
 
 	return true;
 }
@@ -1478,9 +1510,10 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
  * locks and pins, set so->currPos.buf to InvalidBuffer, and return false.
  */
 static bool
-_bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+				 ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
 	Relation	rel;
 	Page		page;
 	BTPageOpaque opaque;
@@ -1496,17 +1529,17 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * if we're at end of scan, give up and mark parallel scan as
 			 * done, so that all the workers can finish their scan
 			 */
-			if (blkno == P_NONE || !so->currPos.moreRight)
+			if (blkno == P_NONE || !currPos->moreRight)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 			/* check for interrupts while we're not holding any buffer lock */
 			CHECK_FOR_INTERRUPTS();
 			/* step right one page */
-			so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
-			page = BufferGetPage(so->currPos.buf);
+			currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			/* check for deleted page */
@@ -1515,7 +1548,7 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 				PredicateLockPage(rel, blkno, scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreRight if we can stop */
-				if (_bt_readpage(scan, dir, P_FIRSTDATAKEY(opaque)))
+				if (_bt_readpage(scan, state, dir, P_FIRSTDATAKEY(opaque)))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
@@ -1527,18 +1560,18 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
 			}
 			else
 			{
 				blkno = opaque->btpo_next;
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 			}
 		}
 	}
@@ -1548,10 +1581,10 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * Should only happen in parallel cases, when some other backend
 		 * advanced the scan.
 		 */
-		if (so->currPos.currPage != blkno)
+		if (currPos->currPage != blkno)
 		{
-			BTScanPosUnpinIfPinned(so->currPos);
-			so->currPos.currPage = blkno;
+			BTScanPosUnpinIfPinned(*currPos);
+			currPos->currPage = blkno;
 		}
 
 		/*
@@ -1576,31 +1609,30 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * is MVCC the page cannot move past the half-dead state to fully
 		 * deleted.
 		 */
-		if (BTScanPosIsPinned(so->currPos))
-			LockBuffer(so->currPos.buf, BT_READ);
+		if (BTScanPosIsPinned(*currPos))
+			LockBuffer(currPos->buf, BT_READ);
 		else
-			so->currPos.buf = _bt_getbuf(rel, so->currPos.currPage, BT_READ);
+			currPos->buf = _bt_getbuf(rel, currPos->currPage, BT_READ);
 
 		for (;;)
 		{
 			/* Done if we know there are no matching keys to the left */
-			if (!so->currPos.moreLeft)
+			if (!currPos->moreLeft)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
 			/* Step to next physical page */
-			so->currPos.buf = _bt_walk_left(rel, so->currPos.buf,
-											scan->xs_snapshot);
+			currPos->buf = _bt_walk_left(rel, currPos->buf, scan->xs_snapshot);
 
 			/* if we're physically at end of index, return failure */
-			if (so->currPos.buf == InvalidBuffer)
+			if (currPos->buf == InvalidBuffer)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
@@ -1609,21 +1641,21 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * it's not half-dead and contains matching tuples. Else loop back
 			 * and do it all again.
 			 */
-			page = BufferGetPage(so->currPos.buf);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			if (!P_IGNORE(opaque))
 			{
-				PredicateLockPage(rel, BufferGetBlockNumber(so->currPos.buf), scan->xs_snapshot);
+				PredicateLockPage(rel, BufferGetBlockNumber(currPos->buf), scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreLeft if we can stop */
-				if (_bt_readpage(scan, dir, PageGetMaxOffsetNumber(page)))
+				if (_bt_readpage(scan, state, dir, PageGetMaxOffsetNumber(page)))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -1634,14 +1666,14 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
-				so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
+				currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
 			}
 		}
 	}
@@ -1660,13 +1692,13 @@ _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
 
 	return true;
 }
@@ -1891,11 +1923,11 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber start;
-	BTScanPosItem *currItem;
 
 	/*
 	 * Scan down to the leftmost or rightmost leaf page.  This is a simplified
@@ -1911,7 +1943,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 		 * exists.
 		 */
 		PredicateLockRelation(rel, scan->xs_snapshot);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 		return false;
 	}
 
@@ -1940,36 +1972,15 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 	}
 
 	/* remember which buffer we have pinned */
-	so->currPos.buf = buf;
+	currPos->buf = buf;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, start))
-	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
-	}
-	else
-	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
-	}
-
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
+	if (!_bt_load_first_page(scan, &so->state, dir, start))
+		return false;
 
-	return true;
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
@@ -1977,19 +1988,19 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
  * for scan direction
  */
 static inline void
-_bt_initialize_more_data(BTScanOpaque so, ScanDirection dir)
+_bt_initialize_more_data(BTScanState state, ScanDirection dir)
 {
 	/* initialize moreLeft/moreRight appropriately for scan direction */
 	if (ScanDirectionIsForward(dir))
 	{
-		so->currPos.moreLeft = false;
-		so->currPos.moreRight = true;
+		state->currPos.moreLeft = false;
+		state->currPos.moreRight = true;
 	}
 	else
 	{
-		so->currPos.moreLeft = true;
-		so->currPos.moreRight = false;
+		state->currPos.moreLeft = true;
+		state->currPos.moreRight = false;
 	}
-	so->numKilled = 0;			/* just paranoia */
-	so->markItemIndex = -1;		/* ditto */
+	state->numKilled = 0;		/* just paranoia */
+	state->markItemIndex = -1;	/* ditto */
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 2c05fb5..e548354 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -1741,26 +1741,26 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, TupleDesc tupdesc,
  * away and the TID was re-used by a completely different heap tuple.
  */
 void
-_bt_killitems(IndexScanDesc scan)
+_bt_killitems(BTScanState state, Relation indexRelation)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
 	OffsetNumber maxoff;
 	int			i;
-	int			numKilled = so->numKilled;
+	int			numKilled = state->numKilled;
 	bool		killedsomething = false;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(state->currPos));
 
 	/*
 	 * Always reset the scan state, so we don't look for same items on other
 	 * pages.
 	 */
-	so->numKilled = 0;
+	state->numKilled = 0;
 
-	if (BTScanPosIsPinned(so->currPos))
+	if (BTScanPosIsPinned(*pos))
 	{
 		/*
 		 * We have held the pin on this page since we read the index tuples,
@@ -1768,44 +1768,42 @@ _bt_killitems(IndexScanDesc scan)
 		 * re-use of any TID on the page, so there is no need to check the
 		 * LSN.
 		 */
-		LockBuffer(so->currPos.buf, BT_READ);
-
-		page = BufferGetPage(so->currPos.buf);
+		LockBuffer(pos->buf, BT_READ);
 	}
 	else
 	{
 		Buffer		buf;
 
 		/* Attempt to re-read the buffer, getting pin and lock. */
-		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ);
+		buf = _bt_getbuf(indexRelation, pos->currPage, BT_READ);
 
 		/* It might not exist anymore; in which case we can't hint it. */
 		if (!BufferIsValid(buf))
 			return;
 
-		page = BufferGetPage(buf);
-		if (BufferGetLSNAtomic(buf) == so->currPos.lsn)
-			so->currPos.buf = buf;
+		if (BufferGetLSNAtomic(buf) == pos->lsn)
+			pos->buf = buf;
 		else
 		{
 			/* Modified while not pinned means hinting is not safe. */
-			_bt_relbuf(scan->indexRelation, buf);
+			_bt_relbuf(indexRelation, buf);
 			return;
 		}
 	}
 
+	page = BufferGetPage(pos->buf);
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 	minoff = P_FIRSTDATAKEY(opaque);
 	maxoff = PageGetMaxOffsetNumber(page);
 
 	for (i = 0; i < numKilled; i++)
 	{
-		int			itemIndex = so->killedItems[i];
-		BTScanPosItem *kitem = &so->currPos.items[itemIndex];
+		int			itemIndex = state->killedItems[i];
+		BTScanPosItem *kitem = &pos->items[itemIndex];
 		OffsetNumber offnum = kitem->indexOffset;
 
-		Assert(itemIndex >= so->currPos.firstItem &&
-			   itemIndex <= so->currPos.lastItem);
+		Assert(itemIndex >= pos->firstItem &&
+			   itemIndex <= pos->lastItem);
 		if (offnum < minoff)
 			continue;			/* pure paranoia */
 		while (offnum <= maxoff)
@@ -1833,10 +1831,10 @@ _bt_killitems(IndexScanDesc scan)
 	if (killedsomething)
 	{
 		opaque->btpo_flags |= BTP_HAS_GARBAGE;
-		MarkBufferDirtyHint(so->currPos.buf, true);
+		MarkBufferDirtyHint(pos->buf, true);
 	}
 
-	LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
+	LockBuffer(pos->buf, BUFFER_LOCK_UNLOCK);
 }
 
 
@@ -2214,3 +2212,14 @@ _bt_check_natts(Relation rel, Page page, OffsetNumber offnum)
 
 	}
 }
+
+/*
+ * _bt_allocate_tuple_workspaces() -- Allocate buffers for saving index tuples
+ * 		in index-only scans.
+ */
+void
+_bt_allocate_tuple_workspaces(BTScanState state)
+{
+	state->currTuples = (char *) palloc(BLCKSZ * 2);
+	state->markTuples = state->currTuples + BLCKSZ;
+}
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 4fb92d6..472898f 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -433,22 +433,8 @@ typedef struct BTArrayKeyInfo
 	Datum	   *elem_values;	/* array of num_elems Datums */
 } BTArrayKeyInfo;
 
-typedef struct BTScanOpaqueData
+typedef struct BTScanStateData
 {
-	/* these fields are set by _bt_preprocess_keys(): */
-	bool		qual_ok;		/* false if qual can never be satisfied */
-	int			numberOfKeys;	/* number of preprocessed scan keys */
-	ScanKey		keyData;		/* array of preprocessed scan keys */
-
-	/* workspace for SK_SEARCHARRAY support */
-	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
-	int			numArrayKeys;	/* number of equality-type array keys (-1 if
-								 * there are any unsatisfiable array keys) */
-	int			arrayKeyCount;	/* count indicating number of array scan keys
-								 * processed */
-	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
-	MemoryContext arrayContext; /* scan-lifespan context for array data */
-
 	/* info about killed items if any (killedItems is NULL if never used) */
 	int		   *killedItems;	/* currPos.items indexes of killed items */
 	int			numKilled;		/* number of currently stored items */
@@ -473,6 +459,25 @@ typedef struct BTScanOpaqueData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+}	BTScanStateData, *BTScanState;
+
+typedef struct BTScanOpaqueData
+{
+	/* these fields are set by _bt_preprocess_keys(): */
+	bool		qual_ok;		/* false if qual can never be satisfied */
+	int			numberOfKeys;	/* number of preprocessed scan keys */
+	ScanKey		keyData;		/* array of preprocessed scan keys */
+
+	/* workspace for SK_SEARCHARRAY support */
+	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
+	int			numArrayKeys;	/* number of equality-type array keys (-1 if
+								 * there are any unsatisfiable array keys) */
+	int			arrayKeyCount;	/* count indicating number of array scan keys
+								 * processed */
+	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
+	MemoryContext arrayContext; /* scan-lifespan context for array data */
+
+	BTScanStateData	state;
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -589,7 +594,7 @@ extern void _bt_preprocess_keys(IndexScanDesc scan);
 extern IndexTuple _bt_checkkeys(IndexScanDesc scan,
 			  Page page, OffsetNumber offnum,
 			  ScanDirection dir, bool *continuescan);
-extern void _bt_killitems(IndexScanDesc scan);
+extern void _bt_killitems(BTScanState state, Relation indexRelation);
 extern BTCycleId _bt_vacuum_cycleid(Relation rel);
 extern BTCycleId _bt_start_vacuum(Relation rel);
 extern void _bt_end_vacuum(Relation rel);
@@ -602,6 +607,7 @@ extern bool btproperty(Oid index_oid, int attno,
 		   bool *res, bool *isnull);
 extern IndexTuple _bt_nonkey_truncate(Relation rel, IndexTuple itup);
 extern bool _bt_check_natts(Relation rel, Page page, OffsetNumber offnum);
+extern void _bt_allocate_tuple_workspaces(BTScanState state);
 
 /*
  * prototypes for functions in nbtvalidate.c
-- 
2.7.4

0004-Add-kNN-support-to-btree-v05.patchtext/x-patch; name=0004-Add-kNN-support-to-btree-v05.patchDownload
From 227e18379002fea12d48f0d3e5dbf83afafd4892 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 11 Jan 2019 15:51:28 +0300
Subject: [PATCH 4/7] Add kNN support to btree

---
 doc/src/sgml/btree.sgml                     |  13 +
 doc/src/sgml/indices.sgml                   |  11 +
 doc/src/sgml/xindex.sgml                    |   3 +-
 src/backend/access/nbtree/README            |  17 ++
 src/backend/access/nbtree/nbtree.c          | 218 ++++++++++++--
 src/backend/access/nbtree/nbtsearch.c       | 331 ++++++++++++++++++---
 src/backend/access/nbtree/nbtutils.c        | 438 +++++++++++++++++++++++++---
 src/backend/access/nbtree/nbtvalidate.c     |  48 +--
 src/backend/optimizer/path/indxpath.c       |  16 +-
 src/include/access/nbtree.h                 |  29 +-
 src/include/access/stratnum.h               |   5 +-
 src/test/regress/expected/alter_generic.out |  13 +-
 src/test/regress/sql/alter_generic.sql      |   8 +-
 13 files changed, 1018 insertions(+), 132 deletions(-)

diff --git a/doc/src/sgml/btree.sgml b/doc/src/sgml/btree.sgml
index 996932e..613032e 100644
--- a/doc/src/sgml/btree.sgml
+++ b/doc/src/sgml/btree.sgml
@@ -200,6 +200,19 @@
   planner relies on them for optimization purposes.
  </para>
 
+ <para>
+  To implement the distance ordered (nearest-neighbor) search, we only need
+  to define a distance operator (usually it called &lt;-&gt;) with a correpsonding
+  operator family for distance comparison in the index's operator class.
+  These operators must satisfy the following assumptions for all non-null
+  values A,B,C of the datatype:
+
+  A &lt;-&gt; B = B &lt;-&gt; A						symmetric law
+  if A = B, then A &lt;-&gt; C = B &lt;-&gt; C		distance equivalence
+  if (A &lt;= B and B &lt;= C) or (A &gt;= B and B &gt;= C),
+  then A &lt;-&gt; B &lt;= A &lt;-&gt; C			monotonicity
+ </para>
+
 </sect1>
 
 <sect1 id="btree-support-funcs">
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 46f427b..caec484 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -175,6 +175,17 @@ CREATE INDEX test1_id_index ON test1 (id);
   </para>
 
   <para>
+   B-tree indexes are also capable of optimizing <quote>nearest-neighbor</>
+   searches, such as
+<programlisting><![CDATA[
+SELECT * FROM events ORDER BY event_date <-> date '2017-05-05' LIMIT 10;
+]]>
+</programlisting>
+   which finds the ten events closest to a given target date.  The ability
+   to do this is again dependent on the particular operator class being used.
+  </para>
+
+  <para>
    <indexterm>
     <primary>index</primary>
     <secondary>hash</secondary>
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 9446f8b..93094bc 100644
--- a/doc/src/sgml/xindex.sgml
+++ b/doc/src/sgml/xindex.sgml
@@ -1242,7 +1242,8 @@ SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
   <title>Ordering Operators</title>
 
   <para>
-   Some index access methods (currently, only GiST and SP-GiST) support the concept of
+   Some index access methods (currently, only B-tree, GiST and SP-GiST) 
+   support the concept of
    <firstterm>ordering operators</firstterm>.  What we have been discussing so far
    are <firstterm>search operators</firstterm>.  A search operator is one for which
    the index can be searched to find all rows satisfying
diff --git a/src/backend/access/nbtree/README b/src/backend/access/nbtree/README
index 3680e69..3f7e1b1 100644
--- a/src/backend/access/nbtree/README
+++ b/src/backend/access/nbtree/README
@@ -659,3 +659,20 @@ routines must treat it accordingly.  The actual key stored in the
 item is irrelevant, and need not be stored at all.  This arrangement
 corresponds to the fact that an L&Y non-leaf page has one more pointer
 than key.
+
+Nearest-neighbor search
+-----------------------
+
+There is a special scan strategy for nearest-neighbor (kNN) search,
+that is used in queries with ORDER BY distance clauses like this:
+SELECT * FROM tab WHERE col > const1 ORDER BY col <-> const2 LIMIT k.
+But, unlike GiST, B-tree supports only a one ordering operator on the
+first index column.
+
+At the beginning of kNN scan, we need to determine which strategy we
+will use --- a special bidirectional or a ordinary unidirectional.
+If the point from which we measure the distance falls into the scan range,
+we use bidirectional scan starting from this point, else we use simple
+unidirectional scan in the right direction.  Algorithm of a bidirectional
+scan is very simple: at each step we advancing scan in that direction,
+which has the nearest point.
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 27d9c6f..87e4d61 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -25,6 +25,9 @@
 #include "commands/vacuum.h"
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
+#include "nodes/primnodes.h"
+#include "nodes/relation.h"
+#include "optimizer/paths.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "storage/condition_variable.h"
@@ -33,6 +36,7 @@
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/index_selfuncs.h"
 #include "utils/memutils.h"
 
@@ -79,6 +83,7 @@ typedef enum
 typedef struct BTParallelScanDescData
 {
 	BlockNumber btps_scanPage;	/* latest or next page to be scanned */
+	BlockNumber btps_knnScanPage;	/* secondary latest or next page to be scanned */
 	BTPS_State	btps_pageStatus;	/* indicates whether next page is
 									 * available for scan. see above for
 									 * possible states of parallel scan. */
@@ -97,6 +102,9 @@ static void btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 static void btvacuumpage(BTVacState *vstate, BlockNumber blkno,
 			 BlockNumber orig_blkno);
 
+static bool btmatchorderby(IndexOptInfo *index, List *pathkeys,
+			   List **orderby_clauses_p, List **clause_columns_p);
+
 
 /*
  * Btree handler function: return IndexAmRoutine with access method parameters
@@ -107,7 +115,7 @@ bthandler(PG_FUNCTION_ARGS)
 {
 	IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
 
-	amroutine->amstrategies = BTMaxStrategyNumber;
+	amroutine->amstrategies = BtreeMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
 	amroutine->amcanbackward = true;
@@ -143,7 +151,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = btestimateparallelscan;
 	amroutine->aminitparallelscan = btinitparallelscan;
 	amroutine->amparallelrescan = btparallelrescan;
-	amroutine->ammatchorderby = NULL;
+	amroutine->ammatchorderby = btmatchorderby;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -215,23 +223,30 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTScanState state = &so->state;
+	ScanDirection arraydir =
+		scan->numberOfOrderBys > 0 ? ForwardScanDirection : dir;
 	bool		res;
 
 	/* btree indexes are never lossy */
 	scan->xs_recheck = false;
+	scan->xs_recheckorderby = false;
+
+	if (so->scanDirection != NoMovementScanDirection)
+		dir = so->scanDirection;
 
 	/*
 	 * If we have any array keys, initialize them during first call for a
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos) &&
+		(!so->knnState || !BTScanPosIsValid(so->knnState->currPos)))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
 			return false;
 
-		_bt_start_array_keys(scan, dir);
+		_bt_start_array_keys(scan, arraydir);
 	}
 
 	/* This loop handles advancing to the next array elements, if any */
@@ -242,7 +257,8 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(state->currPos))
+		if (!BTScanPosIsValid(state->currPos) &&
+			(!so->knnState || !BTScanPosIsValid(so->knnState->currPos)))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -277,7 +293,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		if (res)
 			break;
 		/* ... otherwise see if we have more array keys to deal with */
-	} while (so->numArrayKeys && _bt_advance_array_keys(scan, dir));
+	} while (so->numArrayKeys && _bt_advance_array_keys(scan, arraydir));
 
 	return res;
 }
@@ -350,9 +366,6 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	IndexScanDesc scan;
 	BTScanOpaque so;
 
-	/* no order by operators allowed */
-	Assert(norderbys == 0);
-
 	/* get the scan */
 	scan = RelationGetIndexScan(rel, nkeys, norderbys);
 
@@ -379,6 +392,9 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
 	so->state.currTuples = so->state.markTuples = NULL;
+	so->knnState = NULL;
+	so->distanceTypeByVal = true;
+	so->scanDirection = NoMovementScanDirection;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -408,6 +424,8 @@ _bt_release_current_position(BTScanState state, Relation indexRelation,
 static void
 _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	/* No need to invalidate positions, if the RAM is about to be freed. */
 	_bt_release_current_position(state, scan->indexRelation, !free);
 
@@ -424,6 +442,17 @@ _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 	}
 	else
 		BTScanPosInvalidate(state->markPos);
+
+	if (!so->distanceTypeByVal)
+	{
+		if (DatumGetPointer(state->currDistance))
+			pfree(DatumGetPointer(state->currDistance));
+		state->currDistance = PointerGetDatum(NULL);
+
+		if (DatumGetPointer(state->markDistance))
+			pfree(DatumGetPointer(state->markDistance));
+		state->markDistance = PointerGetDatum(NULL);
+	}
 }
 
 /*
@@ -438,6 +467,13 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 
 	_bt_release_scan_state(scan, state, false);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+		so->knnState = NULL;
+	}
+
 	so->arrayKeyCount = 0; /* FIXME in _bt_release_scan_state */
 
 	/*
@@ -469,6 +505,14 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 				scan->numberOfKeys * sizeof(ScanKeyData));
 	so->numberOfKeys = 0;		/* until _bt_preprocess_keys sets it */
 
+	if (orderbys && scan->numberOfOrderBys > 0)
+		memmove(scan->orderByData,
+				orderbys,
+				scan->numberOfOrderBys * sizeof(ScanKeyData));
+
+	so->scanDirection = NoMovementScanDirection;
+	so->distanceTypeByVal = true;
+
 	/* If any keys are SK_SEARCHARRAY type, set up array-key info */
 	_bt_preprocess_array_keys(scan);
 }
@@ -483,6 +527,12 @@ btendscan(IndexScanDesc scan)
 
 	_bt_release_scan_state(scan, &so->state, true);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+	}
+
 	/* Release storage */
 	if (so->keyData != NULL)
 		pfree(so->keyData);
@@ -494,7 +544,7 @@ btendscan(IndexScanDesc scan)
 }
 
 static void
-_bt_mark_current_position(BTScanState state)
+_bt_mark_current_position(BTScanOpaque so, BTScanState state)
 {
 	/* There may be an old mark with a pin (but no lock). */
 	BTScanPosUnpinIfPinned(state->markPos);
@@ -512,6 +562,21 @@ _bt_mark_current_position(BTScanState state)
 		BTScanPosInvalidate(state->markPos);
 		state->markItemIndex = -1;
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->markDistance));
+
+		state->markIsNull = !BTScanPosIsValid(state->currPos) ||
+			state->currIsNull;
+
+		state->markDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->currDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -522,7 +587,13 @@ btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_mark_current_position(&so->state);
+	_bt_mark_current_position(so, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_mark_current_position(so, so->knnState);
+		so->markRightIsNearest = so->currRightIsNearest;
+	}
 
 	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
@@ -532,6 +603,8 @@ btmarkpos(IndexScanDesc scan)
 static void
 _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	if (state->markItemIndex >= 0)
 	{
 		/*
@@ -567,6 +640,19 @@ _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 					   state->markPos.nextTupleOffset);
 		}
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->currDistance));
+
+		state->currIsNull = state->markIsNull;
+		state->currDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->markDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -588,6 +674,7 @@ btinitparallelscan(void *target)
 
 	SpinLockInit(&bt_target->btps_mutex);
 	bt_target->btps_scanPage = InvalidBlockNumber;
+	bt_target->btps_knnScanPage = InvalidBlockNumber;
 	bt_target->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	bt_target->btps_arrayKeyCount = 0;
 	ConditionVariableInit(&bt_target->btps_cv);
@@ -614,6 +701,7 @@ btparallelrescan(IndexScanDesc scan)
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
 	btscan->btps_scanPage = InvalidBlockNumber;
+	btscan->btps_knnScanPage = InvalidBlockNumber;
 	btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	btscan->btps_arrayKeyCount = 0;
 	SpinLockRelease(&btscan->btps_mutex);
@@ -638,7 +726,7 @@ btparallelrescan(IndexScanDesc scan)
  * Callers should ignore the value of pageno if the return value is false.
  */
 bool
-_bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
+_bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTPS_State	pageStatus;
@@ -646,12 +734,17 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
 	bool		status = true;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
 
 	*pageno = P_NONE;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	scanPage = state == &so->state
+		? &btscan->btps_scanPage
+		: &btscan->btps_knnScanPage;
+
 	while (1)
 	{
 		SpinLockAcquire(&btscan->btps_mutex);
@@ -677,7 +770,7 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
 			 * of advancing it to a new page!
 			 */
 			btscan->btps_pageStatus = BTPARALLEL_ADVANCING;
-			*pageno = btscan->btps_scanPage;
+			*pageno = *scanPage;
 			exit_loop = true;
 		}
 		SpinLockRelease(&btscan->btps_mutex);
@@ -696,19 +789,42 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
  *		can now begin advancing the scan.
  */
 void
-_bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
+_bt_parallel_release(IndexScanDesc scan, BTScanState state,
+					 BlockNumber scan_page)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
+	bool		status_changed = false;
+	bool		knnScan = so->knnState != NULL;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
+	if (!state || state == &so->state)
+	{
+		scanPage = &btscan->btps_scanPage;
+		otherScanPage = &btscan->btps_knnScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_knnScanPage;
+		otherScanPage = &btscan->btps_scanPage;
+	}
 
 	SpinLockAcquire(&btscan->btps_mutex);
-	btscan->btps_scanPage = scan_page;
-	btscan->btps_pageStatus = BTPARALLEL_IDLE;
+	*scanPage = scan_page;
+	/* switch to idle state only if both KNN pages are initialized */
+	if (!knnScan || *otherScanPage != InvalidBlockNumber)
+	{
+		btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		status_changed = true;
+	}
 	SpinLockRelease(&btscan->btps_mutex);
-	ConditionVariableSignal(&btscan->btps_cv);
+
+	if (status_changed)
+		ConditionVariableSignal(&btscan->btps_cv);
 }
 
 /*
@@ -719,12 +835,15 @@ _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
  * advance to the next page.
  */
 void
-_bt_parallel_done(IndexScanDesc scan)
+_bt_parallel_done(IndexScanDesc scan, BTScanState state)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
 	bool		status_changed = false;
+	bool		knnScan = so->knnState != NULL;
 
 	/* Do nothing, for non-parallel scans */
 	if (parallel_scan == NULL)
@@ -733,18 +852,41 @@ _bt_parallel_done(IndexScanDesc scan)
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	if (!state || state == &so->state)
+	{
+		scanPage = &btscan->btps_scanPage;
+		otherScanPage = &btscan->btps_knnScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_knnScanPage;
+		otherScanPage = &btscan->btps_scanPage;
+	}
+
 	/*
 	 * Mark the parallel scan as done for this combination of scan keys,
 	 * unless some other process already did so.  See also
 	 * _bt_advance_array_keys.
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
-	if (so->arrayKeyCount >= btscan->btps_arrayKeyCount &&
-		btscan->btps_pageStatus != BTPARALLEL_DONE)
+
+	Assert(btscan->btps_pageStatus == BTPARALLEL_ADVANCING);
+
+	if (so->arrayKeyCount >= btscan->btps_arrayKeyCount)
 	{
-		btscan->btps_pageStatus = BTPARALLEL_DONE;
+		*scanPage = P_NONE;
 		status_changed = true;
+
+		/* switch to "done" state only if both KNN scans are done */
+		if (!knnScan || *otherScanPage == P_NONE)
+			btscan->btps_pageStatus = BTPARALLEL_DONE;
+		/* else switch to "idle" state only if both KNN scans are initialized */
+		else if (*otherScanPage != InvalidBlockNumber)
+			btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		else
+			status_changed = false;
 	}
+
 	SpinLockRelease(&btscan->btps_mutex);
 
 	/* wake up all the workers associated with this parallel scan */
@@ -774,6 +916,7 @@ _bt_parallel_advance_array_keys(IndexScanDesc scan)
 	if (btscan->btps_pageStatus == BTPARALLEL_DONE)
 	{
 		btscan->btps_scanPage = InvalidBlockNumber;
+		btscan->btps_knnScanPage = InvalidBlockNumber;
 		btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 		btscan->btps_arrayKeyCount++;
 	}
@@ -859,6 +1002,12 @@ btrestrpos(IndexScanDesc scan)
 		_bt_restore_array_keys(scan);
 
 	_bt_restore_marked_position(scan, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_restore_marked_position(scan, so->knnState);
+		so->currRightIsNearest = so->markRightIsNearest;
+	}
 }
 
 /*
@@ -1394,3 +1543,30 @@ btcanreturn(Relation index, int attno)
 {
 	return true;
 }
+
+/*
+ *	btmatchorderby() -- Check whether KNN-search strategy is applicable to
+ *		the given ORDER BY distance operator.
+ */
+static bool
+btmatchorderby(IndexOptInfo *index, List *pathkeys,
+			   List **orderby_clauses_p, List **clause_columns_p)
+{
+	Expr	   *expr;
+	/* ORDER BY distance to the first index column is only supported */
+	int			indexcol = 0;
+
+	if (list_length(pathkeys) != 1)
+		return false; /* only one ORDER BY clause is supported */
+
+	expr = match_orderbyop_pathkey(index, castNode(PathKey, linitial(pathkeys)),
+								   &indexcol);
+
+	if (!expr)
+		return false;
+
+	*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+	*clause_columns_p = lappend_int(*clause_columns_p, 0);
+
+	return true;
+}
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 9b7374e..1ec1466 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -32,12 +32,14 @@ static void _bt_saveitem(BTScanState state, int itemIndex,
 static bool _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir);
 static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
 				 BlockNumber blkno, ScanDirection dir);
-static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
-					  ScanDirection dir);
+static bool _bt_parallel_readpage(IndexScanDesc scan, BTScanState state,
+					  BlockNumber blkno, ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf, Snapshot snapshot);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
 static inline void _bt_initialize_more_data(BTScanState state, ScanDirection dir);
+static BTScanState _bt_alloc_knn_scan(IndexScanDesc scan);
+static bool _bt_start_knn_scan(IndexScanDesc scan, bool left, bool right);
 
 
 /*
@@ -598,6 +600,156 @@ _bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 }
 
 /*
+ *  _bt_calc_current_dist() -- Calculate distance from the current item
+ *		of the scan state to the target order-by ScanKey argument.
+ */
+static void
+_bt_calc_current_dist(IndexScanDesc scan, BTScanState state)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanPosItem  *currItem = &state->currPos.items[state->currPos.itemIndex];
+	IndexTuple		itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+	ScanKey			scankey = &scan->orderByData[0];
+	Datum			value;
+
+	value = index_getattr(itup, 1, scan->xs_itupdesc, &state->currIsNull);
+
+	if (state->currIsNull)
+		return; /* NULL distance */
+
+	value = FunctionCall2Coll(&scankey->sk_func,
+							  scankey->sk_collation,
+							  value,
+							  scankey->sk_argument);
+
+	/* free previous distance value for by-ref types */
+	if (!so->distanceTypeByVal && DatumGetPointer(state->currDistance))
+		pfree(DatumGetPointer(state->currDistance));
+
+	state->currDistance = value;
+}
+
+/*
+ *  _bt_compare_current_dist() -- Compare current distances of the left and right scan states.
+ *
+ *  NULL distances are considered to be greater than any non-NULL distances.
+ *
+ *  Returns true if right distance is lesser than left, otherwise false.
+ */
+static bool
+_bt_compare_current_dist(BTScanOpaque so, BTScanState rstate, BTScanState lstate)
+{
+	if (lstate->currIsNull)
+		return true; /* non-NULL < NULL */
+
+	if (rstate->currIsNull)
+		return false; /* NULL > non-NULL */
+
+	return DatumGetBool(FunctionCall2Coll(&so->distanceCmpProc,
+										  InvalidOid, /* XXX collation for distance comparison */
+										  rstate->currDistance,
+										  lstate->currDistance));
+}
+
+/*
+ * _bt_alloc_knn_scan() -- Allocate additional backward scan state for KNN.
+ */
+static BTScanState
+_bt_alloc_knn_scan(IndexScanDesc scan)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		lstate = (BTScanState) palloc(sizeof(BTScanStateData));
+
+	_bt_allocate_tuple_workspaces(lstate);
+
+	if (!scan->xs_want_itup)
+	{
+		/* We need to request index tuples for distance comparison. */
+		scan->xs_want_itup = true;
+		_bt_allocate_tuple_workspaces(&so->state);
+	}
+
+	BTScanPosInvalidate(lstate->currPos);
+	lstate->currPos.moreLeft = false;
+	lstate->currPos.moreRight = false;
+	BTScanPosInvalidate(lstate->markPos);
+	lstate->markItemIndex = -1;
+	lstate->killedItems = NULL;
+	lstate->numKilled = 0;
+	lstate->currDistance = PointerGetDatum(NULL);
+	lstate->markDistance = PointerGetDatum(NULL);
+
+	return so->knnState = lstate;
+}
+
+static bool
+_bt_start_knn_scan(IndexScanDesc scan, bool left, bool right)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		rstate; /* right (forward) main scan state */
+	BTScanState		lstate; /* additional left (backward) KNN scan state */
+
+	if (!left && !right)
+		return false; /* empty result */
+
+	rstate = &so->state;
+	lstate = so->knnState;
+
+	if (left && right)
+	{
+		/*
+		 * We have found items in both scan directions,
+		 * determine nearest item to return.
+		 */
+		_bt_calc_current_dist(scan, rstate);
+		_bt_calc_current_dist(scan, lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+
+		/* Reset right flag if the left item is nearer. */
+		right = so->currRightIsNearest;
+	}
+
+	/* Return current item of the selected scan direction. */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
+ * _bt_init_knn_scan() -- Init additional scan state for KNN search.
+ *
+ * Caller must pin and read-lock scan->state.currPos.buf buffer.
+ *
+ * If empty result was found returned false.
+ * Otherwise prepared current item, and returned true.
+ */
+static bool
+_bt_init_knn_scan(IndexScanDesc scan, OffsetNumber offnum)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		rstate = &so->state; /* right (forward) main scan state */
+	BTScanState		lstate; /* additional left (backward) KNN scan state */
+	Buffer			buf = rstate->currPos.buf;
+	bool			left,
+					right;
+
+	lstate = _bt_alloc_knn_scan(scan);
+
+	/* Bump pin and lock count before BTScanPosData copying. */
+	IncrBufferRefCount(buf);
+	LockBuffer(buf, BT_READ);
+
+	memcpy(&lstate->currPos, &rstate->currPos, sizeof(BTScanPosData));
+	lstate->currPos.moreLeft = true;
+	lstate->currPos.moreRight = false;
+
+	/* Load first pages from the both scans. */
+	right = _bt_load_first_page(scan, rstate, ForwardScanDirection, offnum);
+	left = _bt_load_first_page(scan, lstate, BackwardScanDirection,
+							   OffsetNumberPrev(offnum));
+
+	return _bt_start_knn_scan(scan, left, right);
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -655,6 +807,15 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	if (!so->qual_ok)
 		return false;
 
+	if (scan->numberOfOrderBys > 0)
+	{
+		if (so->useBidirectionalKnnScan)
+			_bt_init_distance_comparison(scan);
+		else if (so->scanDirection != NoMovementScanDirection)
+			/* use selected KNN scan direction */
+			dir = so->scanDirection;
+	}
+
 	/*
 	 * For parallel scans, get the starting page from shared state. If the
 	 * scan has not started, proceed to find out first leaf page in the usual
@@ -663,19 +824,50 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 */
 	if (scan->parallel_scan != NULL)
 	{
-		status = _bt_parallel_seize(scan, &blkno);
+		status = _bt_parallel_seize(scan, &so->state, &blkno);
 		if (!status)
 			return false;
-		else if (blkno == P_NONE)
-		{
-			_bt_parallel_done(scan);
-			return false;
-		}
 		else if (blkno != InvalidBlockNumber)
 		{
-			if (!_bt_parallel_readpage(scan, blkno, dir))
-				return false;
-			goto readcomplete;
+			bool		knn = so->useBidirectionalKnnScan;
+			bool		right;
+			bool		left;
+
+			if (knn)
+				_bt_alloc_knn_scan(scan);
+
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, &so->state);
+				right = false;
+			}
+			else
+				right = _bt_parallel_readpage(scan, &so->state, blkno,
+											  knn ? ForwardScanDirection : dir);
+
+			if (!knn)
+				return right && _bt_return_current_item(scan, &so->state);
+
+			/* seize additional backward KNN scan */
+			left = _bt_parallel_seize(scan, so->knnState, &blkno);
+
+			if (left)
+			{
+				if (blkno == P_NONE)
+				{
+					_bt_parallel_done(scan, so->knnState);
+					left = false;
+				}
+				else
+				{
+					/* backward scan should be already initialized */
+					Assert(blkno != InvalidBlockNumber);
+					left = _bt_parallel_readpage(scan, so->knnState, blkno,
+												 BackwardScanDirection);
+				}
+			}
+
+			return _bt_start_knn_scan(scan, left, right);
 		}
 	}
 
@@ -725,14 +917,20 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 * storing their addresses into the local startKeys[] array.
 	 *----------
 	 */
-	strat_total = BTEqualStrategyNumber;
-	if (so->numberOfKeys > 0)
+	if (so->useBidirectionalKnnScan)
+	{
+		keysCount = _bt_init_knn_start_keys(scan, startKeys, notnullkeys);
+		strat_total = BtreeKNNSearchStrategyNumber;
+	}
+	else if (so->numberOfKeys > 0)
 	{
 		AttrNumber	curattr;
 		ScanKey		chosen;
 		ScanKey		impliesNN;
 		ScanKey		cur;
 
+		strat_total = BTEqualStrategyNumber;
+
 		/*
 		 * chosen is the so-far-chosen key for the current attribute, if any.
 		 * We don't cast the decision in stone until we reach keys for the
@@ -866,7 +1064,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		if (!match)
 		{
 			/* No match, so mark (parallel) scan finished */
-			_bt_parallel_done(scan);
+			_bt_parallel_done(scan, NULL);
 		}
 
 		return match;
@@ -901,7 +1099,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			Assert(subkey->sk_flags & SK_ROW_MEMBER);
 			if (subkey->sk_flags & SK_ISNULL)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, NULL);
 				return false;
 			}
 			memcpy(scankeys + i, subkey, sizeof(ScanKeyData));
@@ -1081,6 +1279,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			break;
 
 		case BTGreaterEqualStrategyNumber:
+		case BtreeKNNSearchStrategyNumber:
 
 			/*
 			 * Find first item >= scankey.  (This is only used for forward
@@ -1128,7 +1327,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 * mark parallel scan as done, so that all the workers can finish
 		 * their scan
 		 */
-		_bt_parallel_done(scan);
+		_bt_parallel_done(scan, NULL);
 		BTScanPosInvalidate(*currPos);
 
 		return false;
@@ -1167,17 +1366,21 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	Assert(!BTScanPosIsValid(*currPos));
 	currPos->buf = buf;
 
+	if (strat_total == BtreeKNNSearchStrategyNumber)
+		return _bt_init_knn_scan(scan, offnum);
+
 	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
-		return false;
+		return false; /* empty result */
 
-readcomplete:
 	/* OK, currPos->itemIndex says what to return */
 	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
- *	Advance to next tuple on current page; or if there's no more,
- *	try to step to the next page with data.
+ *	_bt_next_item() -- Advance to next tuple on current page;
+ *		or if there's no more, try to step to the next page with data.
+ *
+ *	If there are no more matching records in the given direction
  */
 static bool
 _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
@@ -1197,6 +1400,51 @@ _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 }
 
 /*
+ *	_bt_next_nearest() -- Return next nearest item from bidirectional KNN scan.
+ */
+static bool
+_bt_next_nearest(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState rstate = &so->state;
+	BTScanState lstate = so->knnState;
+	bool		right = BTScanPosIsValid(rstate->currPos);
+	bool		left = BTScanPosIsValid(lstate->currPos);
+	bool		advanceRight;
+
+	if (right && left)
+		advanceRight = so->currRightIsNearest;
+	else if (right)
+		advanceRight = true;
+	else if (left)
+		advanceRight = false;
+	else
+		return false; /* end of the scan */
+
+	if (advanceRight)
+		right = _bt_next_item(scan, rstate, ForwardScanDirection);
+	else
+		left = _bt_next_item(scan, lstate, BackwardScanDirection);
+
+	if (!left && !right)
+		return false; /* end of the scan */
+
+	if (left && right)
+	{
+		/*
+		 * If there are items in both scans we must recalculate distance
+		 * in the advanced scan.
+		 */
+		_bt_calc_current_dist(scan, advanceRight ? rstate : lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+		right = so->currRightIsNearest;
+	}
+
+	/* return nearest item */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
  *	_bt_next() -- Get the next item in a scan.
  *
  *		On entry, so->currPos describes the current page, which may be pinned
@@ -1215,6 +1463,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
+	if (so->knnState)
+		/* return next neareset item from KNN scan */
+		return _bt_next_nearest(scan);
+
 	if (!_bt_next_item(scan, &so->state, dir))
 		return false;
 
@@ -1267,9 +1519,9 @@ _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 	if (scan->parallel_scan)
 	{
 		if (ScanDirectionIsForward(dir))
-			_bt_parallel_release(scan, opaque->btpo_next);
+			_bt_parallel_release(scan, state, opaque->btpo_next);
 		else
-			_bt_parallel_release(scan, BufferGetBlockNumber(pos->buf));
+			_bt_parallel_release(scan, state, BufferGetBlockNumber(pos->buf));
 	}
 
 	minoff = P_FIRSTDATAKEY(opaque);
@@ -1443,7 +1695,7 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the next block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno);
+			status = _bt_parallel_seize(scan, state, &blkno);
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
@@ -1475,13 +1727,19 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the current block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno);
+			status = _bt_parallel_seize(scan, state, &blkno);
 			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, state);
+				BTScanPosInvalidate(*currPos);
+				return false;
+			}
 		}
 		else
 		{
@@ -1531,7 +1789,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			 */
 			if (blkno == P_NONE || !currPos->moreRight)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1554,14 +1812,14 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, opaque->btpo_next);
+				_bt_parallel_release(scan, state, opaque->btpo_next);
 			}
 
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno);
+				status = _bt_parallel_seize(scan, state, &blkno);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -1620,7 +1878,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (!currPos->moreLeft)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1631,7 +1889,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			/* if we're physically at end of index, return failure */
 			if (currPos->buf == InvalidBuffer)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1655,7 +1913,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
+				_bt_parallel_release(scan, state, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -1667,7 +1925,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno);
+				status = _bt_parallel_seize(scan, state, &blkno);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -1688,17 +1946,16 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
  * indicate success.
  */
 static bool
-_bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_parallel_readpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+					  ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	_bt_initialize_more_data(state, dir);
 
-	_bt_initialize_more_data(&so->state, dir);
-
-	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
 
 	return true;
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index e548354..bf02de1 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -20,16 +20,21 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "catalog/pg_amop.h"
 #include "miscadmin.h"
 #include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 
 
 typedef struct BTSortArrayContext
 {
 	FmgrInfo	flinfo;
+	FmgrInfo	distflinfo;
+	FmgrInfo	distcmpflinfo;
+	ScanKey		distkey;
 	Oid			collation;
 	bool		reverse;
 } BTSortArrayContext;
@@ -49,6 +54,11 @@ static void _bt_mark_scankey_required(ScanKey skey);
 static bool _bt_check_rowcompare(ScanKey skey,
 					 IndexTuple tuple, TupleDesc tupdesc,
 					 ScanDirection dir, bool *continuescan);
+static inline StrategyNumber _bt_select_knn_strategy_for_key(IndexScanDesc scan,
+								ScanKey cond);
+static void _bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype,
+						  FmgrInfo *finfo, int16 *typlen, bool *typbyval);
+
 
 
 /*
@@ -445,6 +455,7 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 {
 	Relation	rel = scan->indexRelation;
 	Oid			elemtype;
+	Oid			opfamily;
 	RegProcedure cmp_proc;
 	BTSortArrayContext cxt;
 	int			last_non_dup;
@@ -462,6 +473,53 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 	if (elemtype == InvalidOid)
 		elemtype = rel->rd_opcintype[skey->sk_attno - 1];
 
+	opfamily = rel->rd_opfamily[skey->sk_attno - 1];
+
+	if (scan->numberOfOrderBys <= 0)
+	{
+		cxt.distkey = NULL;
+		cxt.reverse = reverse;
+	}
+	else
+	{
+		/* Init procedures for distance calculation and comparison. */
+		ScanKey		distkey = &scan->orderByData[0];
+		ScanKeyData	distkey2;
+		Oid			disttype = distkey->sk_subtype;
+		Oid			distopr;
+		RegProcedure distproc;
+
+		if (!OidIsValid(disttype))
+			disttype = rel->rd_opcintype[skey->sk_attno - 1];
+
+		/* Lookup distance operator in index column's operator family. */
+		distopr = get_opfamily_member(opfamily,
+									  elemtype,
+									  disttype,
+									  distkey->sk_strategy);
+
+		if (!OidIsValid(distopr))
+			elog(ERROR, "missing operator (%u,%u) for strategy %d in opfamily %u",
+				 elemtype, disttype, BtreeKNNSearchStrategyNumber, opfamily);
+
+		distproc = get_opcode(distopr);
+
+		if (!RegProcedureIsValid(distproc))
+			elog(ERROR, "missing code for operator %u", distopr);
+
+		fmgr_info(distproc, &cxt.distflinfo);
+
+		distkey2 = *distkey;
+		fmgr_info_copy(&distkey2.sk_func, &cxt.distflinfo, CurrentMemoryContext);
+		distkey2.sk_subtype = disttype;
+
+		_bt_get_distance_cmp_proc(&distkey2, opfamily, elemtype,
+								  &cxt.distcmpflinfo, NULL, NULL);
+
+		cxt.distkey = distkey;
+		cxt.reverse = false;	/* supported only ascending ordering */
+	}
+
 	/*
 	 * Look up the appropriate comparison function in the opfamily.
 	 *
@@ -470,19 +528,17 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 	 * non-cross-type support functions for any datatype that it supports at
 	 * all.
 	 */
-	cmp_proc = get_opfamily_proc(rel->rd_opfamily[skey->sk_attno - 1],
+	cmp_proc = get_opfamily_proc(opfamily,
 								 elemtype,
 								 elemtype,
 								 BTORDER_PROC);
 	if (!RegProcedureIsValid(cmp_proc))
 		elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
-			 BTORDER_PROC, elemtype, elemtype,
-			 rel->rd_opfamily[skey->sk_attno - 1]);
+			 BTORDER_PROC, elemtype, elemtype, opfamily);
 
 	/* Sort the array elements */
 	fmgr_info(cmp_proc, &cxt.flinfo);
 	cxt.collation = skey->sk_collation;
-	cxt.reverse = reverse;
 	qsort_arg((void *) elems, nelems, sizeof(Datum),
 			  _bt_compare_array_elements, (void *) &cxt);
 
@@ -514,6 +570,23 @@ _bt_compare_array_elements(const void *a, const void *b, void *arg)
 	BTSortArrayContext *cxt = (BTSortArrayContext *) arg;
 	int32		compare;
 
+	if (cxt->distkey)
+	{
+		Datum		dista = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  da,
+											  cxt->distkey->sk_argument);
+		Datum		distb = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  db,
+											  cxt->distkey->sk_argument);
+		bool		cmp = DatumGetBool(FunctionCall2Coll(&cxt->distcmpflinfo,
+														 cxt->collation,
+														 dista,
+														 distb));
+		return cmp ? -1 : 1;
+	}
+
 	compare = DatumGetInt32(FunctionCall2Coll(&cxt->flinfo,
 											  cxt->collation,
 											  da, db));
@@ -667,6 +740,66 @@ _bt_restore_array_keys(IndexScanDesc scan)
 	}
 }
 
+/*
+ * _bt_emit_scan_key() -- Emit one prepared scan key
+ *
+ * Push the scan key into the so->keyData[] array, and then mark it if it is
+ * required.  Also update selected kNN strategy.
+ */
+static void
+_bt_emit_scan_key(IndexScanDesc scan, ScanKey skey, int numberOfEqualCols)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	ScanKey		outkey = &so->keyData[so->numberOfKeys++];
+
+	memcpy(outkey, skey, sizeof(ScanKeyData));
+
+	/*
+	 * We can mark the qual as required (possibly only in one direction) if all
+	 * attrs before this one had "=".
+	 */
+	if (outkey->sk_attno - 1 == numberOfEqualCols)
+		_bt_mark_scankey_required(outkey);
+
+	/* Update kNN strategy if it is not already selected. */
+	if (so->useBidirectionalKnnScan)
+	{
+		switch (_bt_select_knn_strategy_for_key(scan, outkey))
+		{
+			case BTLessStrategyNumber:
+			case BTLessEqualStrategyNumber:
+				/*
+				 * Ordering key argument is greater than all values in scan
+				 * range, select backward scan direction.
+				 */
+				so->scanDirection = BackwardScanDirection;
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BTEqualStrategyNumber:
+				/* Use default unidirectional scan direction. */
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+				/*
+				 * Ordering key argument is lesser than all values in scan
+				 * range, select forward scan direction.
+				 */
+				so->scanDirection = ForwardScanDirection;
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BtreeKNNSearchStrategyNumber:
+				/*
+				 * Ordering key argument falls into scan range,
+				 * keep using bidirectional scan.
+				 */
+				break;
+		}
+	}
+}
 
 /*
  *	_bt_preprocess_keys() -- Preprocess scan keys
@@ -758,10 +891,8 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	int			numberOfKeys = scan->numberOfKeys;
 	int16	   *indoption = scan->indexRelation->rd_indoption;
-	int			new_numberOfKeys;
 	int			numberOfEqualCols;
 	ScanKey		inkeys;
-	ScanKey		outkeys;
 	ScanKey		cur;
 	ScanKey		xform[BTMaxStrategyNumber];
 	bool		test_result;
@@ -769,6 +900,25 @@ _bt_preprocess_keys(IndexScanDesc scan)
 				j;
 	AttrNumber	attno;
 
+	if (scan->numberOfOrderBys > 0)
+	{
+		ScanKey		ord = scan->orderByData;
+
+		if (scan->numberOfOrderBys > 1 || ord->sk_attno != 1)
+			/* it should not happen, see btmatchorderby() */
+			elog(ERROR, "only one btree ordering operator "
+						"for the first index column is supported");
+
+		Assert(ord->sk_strategy == BtreeKNNSearchStrategyNumber);
+
+		/* use bidirectional kNN scan by default */
+		so->useBidirectionalKnnScan = true;
+	}
+	else
+	{
+		so->useBidirectionalKnnScan = false;
+	}
+
 	/* initialize result variables */
 	so->qual_ok = true;
 	so->numberOfKeys = 0;
@@ -784,7 +934,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	else
 		inkeys = scan->keyData;
 
-	outkeys = so->keyData;
 	cur = &inkeys[0];
 	/* we check that input keys are correctly ordered */
 	if (cur->sk_attno < 1)
@@ -796,18 +945,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 		/* Apply indoption to scankey (might change sk_strategy!) */
 		if (!_bt_fix_scankey_strategy(cur, indoption))
 			so->qual_ok = false;
-		memcpy(outkeys, cur, sizeof(ScanKeyData));
-		so->numberOfKeys = 1;
-		/* We can mark the qual as required if it's for first index col */
-		if (cur->sk_attno == 1)
-			_bt_mark_scankey_required(outkeys);
+
+		_bt_emit_scan_key(scan, cur, 0);
 		return;
 	}
 
 	/*
 	 * Otherwise, do the full set of pushups.
 	 */
-	new_numberOfKeys = 0;
 	numberOfEqualCols = 0;
 
 	/*
@@ -931,20 +1076,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 			}
 
 			/*
-			 * Emit the cleaned-up keys into the outkeys[] array, and then
+			 * Emit the cleaned-up keys into the so->keyData[] array, and then
 			 * mark them if they are required.  They are required (possibly
 			 * only in one direction) if all attrs before this one had "=".
 			 */
 			for (j = BTMaxStrategyNumber; --j >= 0;)
 			{
 				if (xform[j])
-				{
-					ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-					memcpy(outkey, xform[j], sizeof(ScanKeyData));
-					if (priorNumberOfEqualCols == attno - 1)
-						_bt_mark_scankey_required(outkey);
-				}
+					_bt_emit_scan_key(scan, xform[j], priorNumberOfEqualCols);
 			}
 
 			/*
@@ -964,17 +1103,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 		/* if row comparison, push it directly to the output array */
 		if (cur->sk_flags & SK_ROW_HEADER)
 		{
-			ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-			memcpy(outkey, cur, sizeof(ScanKeyData));
-			if (numberOfEqualCols == attno - 1)
-				_bt_mark_scankey_required(outkey);
+			_bt_emit_scan_key(scan, cur, numberOfEqualCols);
 
 			/*
 			 * We don't support RowCompare using equality; such a qual would
 			 * mess up the numberOfEqualCols tracking.
 			 */
 			Assert(j != (BTEqualStrategyNumber - 1));
+
 			continue;
 		}
 
@@ -1007,16 +1143,10 @@ _bt_preprocess_keys(IndexScanDesc scan)
 				 * previous one in xform[j] and push this one directly to the
 				 * output array.
 				 */
-				ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-				memcpy(outkey, cur, sizeof(ScanKeyData));
-				if (numberOfEqualCols == attno - 1)
-					_bt_mark_scankey_required(outkey);
+				_bt_emit_scan_key(scan, cur, numberOfEqualCols);
 			}
 		}
 	}
-
-	so->numberOfKeys = new_numberOfKeys;
 }
 
 /*
@@ -2075,6 +2205,39 @@ btproperty(Oid index_oid, int attno,
 			*res = true;
 			return true;
 
+		case AMPROP_DISTANCE_ORDERABLE:
+			{
+				Oid			opclass,
+							opfamily,
+							opcindtype;
+
+				/* answer only for columns, not AM or whole index */
+				if (attno == 0)
+					return false;
+
+				opclass = get_index_column_opclass(index_oid, attno);
+
+				if (!OidIsValid(opclass))
+				{
+					*res = false;	/* non-key attribute */
+					return true;
+				}
+
+				if (!get_opclass_opfamily_and_input_type(opclass,
+													 &opfamily, &opcindtype))
+				{
+					*isnull = true;
+					return true;
+				}
+
+				*res = SearchSysCacheExists(AMOPSTRATEGY,
+											ObjectIdGetDatum(opfamily),
+											ObjectIdGetDatum(opcindtype),
+											ObjectIdGetDatum(opcindtype),
+								   Int16GetDatum(BtreeKNNSearchStrategyNumber));
+				return true;
+			}
+
 		default:
 			return false;		/* punt to generic code */
 	}
@@ -2223,3 +2386,212 @@ _bt_allocate_tuple_workspaces(BTScanState state)
 	state->currTuples = (char *) palloc(BLCKSZ * 2);
 	state->markTuples = state->currTuples + BLCKSZ;
 }
+
+static bool
+_bt_compare_row_key_with_ordering_key(ScanKey row, ScanKey ord, bool *result)
+{
+	ScanKey		subkey = (ScanKey) DatumGetPointer(row->sk_argument);
+	int32		cmpresult;
+
+	Assert(subkey->sk_attno == 1);
+	Assert(subkey->sk_flags & SK_ROW_MEMBER);
+
+	if (subkey->sk_flags & SK_ISNULL)
+		return false;
+
+	/* Perform the test --- three-way comparison not bool operator */
+	cmpresult = DatumGetInt32(FunctionCall2Coll(&subkey->sk_func,
+												subkey->sk_collation,
+												ord->sk_argument,
+												subkey->sk_argument));
+
+	if (subkey->sk_flags & SK_BT_DESC)
+		cmpresult = -cmpresult;
+
+	/*
+	 * At this point cmpresult indicates the overall result of the row
+	 * comparison, and subkey points to the deciding column (or the last
+	 * column if the result is "=").
+	 */
+	switch (subkey->sk_strategy)
+	{
+			/* EQ and NE cases aren't allowed here */
+		case BTLessStrategyNumber:
+			*result = cmpresult < 0;
+			break;
+		case BTLessEqualStrategyNumber:
+			*result = cmpresult <= 0;
+			break;
+		case BTGreaterEqualStrategyNumber:
+			*result = cmpresult >= 0;
+			break;
+		case BTGreaterStrategyNumber:
+			*result = cmpresult > 0;
+			break;
+		default:
+			elog(ERROR, "unrecognized RowCompareType: %d",
+				 (int) subkey->sk_strategy);
+			*result = false;	/* keep compiler quiet */
+	}
+
+	return true;
+}
+
+/*
+ * _bt_select_knn_strategy_for_key() -- Determine which kNN scan strategy to use:
+ *		bidirectional or unidirectional.  We are checking here if the
+ *		ordering scankey argument falls into the scan range: if it falls
+ *		we must use bidirectional scan, otherwise we use unidirectional.
+ *
+ *	Returns BtreeKNNSearchStrategyNumber for bidirectional scan or
+ *	strategy number of non-matched scankey for unidirectional.
+ */
+static inline StrategyNumber
+_bt_select_knn_strategy_for_key(IndexScanDesc scan, ScanKey cond)
+{
+	ScanKey		ord = scan->orderByData;
+	bool		result;
+
+	/* only interesting in the first index attribute */
+	if (cond->sk_attno != 1)
+		return BtreeKNNSearchStrategyNumber;
+
+	if (cond->sk_strategy == BTEqualStrategyNumber)
+		/* always use simple unidirectional scan for equals operators */
+		return BTEqualStrategyNumber;
+
+	if (cond->sk_flags & SK_ROW_HEADER)
+	{
+		if (!_bt_compare_row_key_with_ordering_key(cond, ord, &result))
+			return BTEqualStrategyNumber; /* ROW(fist_index_attr, ...) IS NULL */
+	}
+	else
+	{
+		if (!_bt_compare_scankey_args(scan, cond, ord, cond, &result))
+			elog(ERROR, "could not compare ordering key");
+	}
+
+	if (!result)
+		/*
+		 * Ordering scankey argument is out of scan range,
+		 * use unidirectional scan.
+		 */
+		return cond->sk_strategy;
+
+	return BtreeKNNSearchStrategyNumber;
+}
+
+int
+_bt_init_knn_start_keys(IndexScanDesc scan, ScanKey *startKeys, ScanKey bufKeys)
+{
+	ScanKey		ord = scan->orderByData;
+	int			indopt = scan->indexRelation->rd_indoption[ord->sk_attno - 1];
+	int			flags = (indopt << SK_BT_INDOPTION_SHIFT) |
+						SK_ORDER_BY |
+						SK_SEARCHNULL; /* only for invalid procedure oid, see
+										* assert in ScanKeyEntryInitialize() */
+	int			keysCount = 0;
+
+	/* Init btree search key with ordering key argument. */
+	ScanKeyEntryInitialize(&bufKeys[0],
+						   flags,
+						   ord->sk_attno,
+						   BtreeKNNSearchStrategyNumber,
+						   ord->sk_subtype,
+						   ord->sk_collation,
+						   InvalidOid,
+						   ord->sk_argument);
+
+	startKeys[keysCount++] = &bufKeys[0];
+
+	return keysCount;
+}
+
+static Oid
+_bt_get_sortfamily_for_opfamily_op(Oid opfamily, Oid lefttype, Oid righttype,
+								   StrategyNumber strategy)
+{
+	HeapTuple	tp;
+	Form_pg_amop amop_tup;
+	Oid			sortfamily;
+
+	tp = SearchSysCache4(AMOPSTRATEGY,
+						 ObjectIdGetDatum(opfamily),
+						 ObjectIdGetDatum(lefttype),
+						 ObjectIdGetDatum(righttype),
+						 Int16GetDatum(strategy));
+	if (!HeapTupleIsValid(tp))
+		return InvalidOid;
+	amop_tup = (Form_pg_amop) GETSTRUCT(tp);
+	sortfamily = amop_tup->amopsortfamily;
+	ReleaseSysCache(tp);
+
+	return sortfamily;
+}
+
+/*
+ * _bt_get_distance_cmp_proc() -- Init procedure for comparsion of distances
+ *		between "leftargtype" and "distkey".
+ */
+static void
+_bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype,
+						  FmgrInfo *finfo, int16 *typlen, bool *typbyval)
+{
+	RegProcedure opcode;
+	Oid			sortfamily;
+	Oid			opno;
+	Oid			distanceType;
+
+	distanceType = get_func_rettype(distkey->sk_func.fn_oid);
+
+	sortfamily = _bt_get_sortfamily_for_opfamily_op(opfamily, leftargtype,
+													distkey->sk_subtype,
+													distkey->sk_strategy);
+
+	if (!OidIsValid(sortfamily))
+		elog(ERROR, "could not find sort family for btree ordering operator");
+
+	opno = get_opfamily_member(sortfamily,
+							   distanceType,
+							   distanceType,
+							   BTLessEqualStrategyNumber);
+
+	if (!OidIsValid(opno))
+		elog(ERROR, "could not find operator for btree distance comparison");
+
+	opcode = get_opcode(opno);
+
+	if (!RegProcedureIsValid(opcode))
+		elog(ERROR,
+			"could not find procedure for btree distance comparison operator");
+
+	fmgr_info(opcode, finfo);
+
+	if (typlen)
+		get_typlenbyval(distanceType, typlen, typbyval);
+}
+
+/*
+ *  _bt_init_distance_comparison() -- Init distance typlen/typbyval and its
+ *  	comparison procedure.
+ */
+void
+_bt_init_distance_comparison(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	Relation	rel = scan->indexRelation;
+	ScanKey		ord = scan->orderByData;
+
+	_bt_get_distance_cmp_proc(ord,
+							  rel->rd_opfamily[ord->sk_attno - 1],
+							  rel->rd_opcintype[ord->sk_attno - 1],
+							  &so->distanceCmpProc,
+							  &so->distanceTypeLen,
+							  &so->distanceTypeByVal);
+
+	if (!so->distanceTypeByVal)
+	{
+		so->state.currDistance = PointerGetDatum(NULL);
+		so->state.markDistance = PointerGetDatum(NULL);
+	}
+}
diff --git a/src/backend/access/nbtree/nbtvalidate.c b/src/backend/access/nbtree/nbtvalidate.c
index 0148ea7..4558fd3 100644
--- a/src/backend/access/nbtree/nbtvalidate.c
+++ b/src/backend/access/nbtree/nbtvalidate.c
@@ -22,9 +22,17 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_type.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/regproc.h"
 #include "utils/syscache.h"
 
+#define BTRequiredOperatorSet \
+		((1 << BTLessStrategyNumber) | \
+		 (1 << BTLessEqualStrategyNumber) | \
+		 (1 << BTEqualStrategyNumber) | \
+		 (1 << BTGreaterEqualStrategyNumber) | \
+		 (1 << BTGreaterStrategyNumber))
+
 
 /*
  * Validator for a btree opclass.
@@ -132,10 +140,11 @@ btvalidate(Oid opclassoid)
 	{
 		HeapTuple	oprtup = &oprlist->members[i]->tuple;
 		Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
+		Oid			op_rettype;
 
 		/* Check that only allowed strategy numbers exist */
 		if (oprform->amopstrategy < 1 ||
-			oprform->amopstrategy > BTMaxStrategyNumber)
+			oprform->amopstrategy > BtreeMaxStrategyNumber)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -146,20 +155,29 @@ btvalidate(Oid opclassoid)
 			result = false;
 		}
 
-		/* btree doesn't support ORDER BY operators */
-		if (oprform->amoppurpose != AMOP_SEARCH ||
-			OidIsValid(oprform->amopsortfamily))
+		/* btree supports ORDER BY operators */
+		if (oprform->amoppurpose != AMOP_SEARCH)
 		{
-			ereport(INFO,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
-							opfamilyname, "btree",
-							format_operator(oprform->amopopr))));
-			result = false;
+			/* ... and operator result must match the claimed btree opfamily */
+			op_rettype = get_op_rettype(oprform->amopopr);
+			if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype))
+			{
+				ereport(INFO,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
+								opfamilyname, "btree",
+								format_operator(oprform->amopopr))));
+				result = false;
+			}
+		}
+		else
+		{
+			/* Search operators must always return bool */
+			op_rettype = BOOLOID;
 		}
 
 		/* Check operator signature --- same for all btree strategies */
-		if (!check_amop_signature(oprform->amopopr, BOOLOID,
+		if (!check_amop_signature(oprform->amopopr, op_rettype,
 								  oprform->amoplefttype,
 								  oprform->amoprighttype))
 		{
@@ -214,12 +232,8 @@ btvalidate(Oid opclassoid)
 		 * or support functions for this datatype pair.  The only things
 		 * considered optional are the sortsupport and in_range functions.
 		 */
-		if (thisgroup->operatorset !=
-			((1 << BTLessStrategyNumber) |
-			 (1 << BTLessEqualStrategyNumber) |
-			 (1 << BTEqualStrategyNumber) |
-			 (1 << BTGreaterEqualStrategyNumber) |
-			 (1 << BTGreaterStrategyNumber)))
+		if ((thisgroup->operatorset & BTRequiredOperatorSet) !=
+			BTRequiredOperatorSet)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 2bf87ad..2a4cc11 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -990,6 +990,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	 * if we are only trying to build bitmap indexscans, nor if we have to
 	 * assume the scan is unordered.
 	 */
+	useful_pathkeys = NIL;
+	orderbyclauses = NIL;
+	orderbyclausecols = NIL;
+
 	pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN &&
 								!found_lower_saop_clause &&
 								has_useful_pathkeys(root, rel));
@@ -1000,10 +1004,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 											  ForwardScanDirection);
 		useful_pathkeys = truncate_useless_pathkeys(root, rel,
 													index_pathkeys);
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
 	}
-	else if (index->ammatchorderby && pathkeys_possibly_useful)
+
+	if (useful_pathkeys == NIL &&
+		index->ammatchorderby && pathkeys_possibly_useful)
 	{
 		/* see if we can generate ordering operators for query_pathkeys */
 		match_pathkeys_to_index(index, root->query_pathkeys,
@@ -1014,12 +1018,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		else
 			useful_pathkeys = NIL;
 	}
-	else
-	{
-		useful_pathkeys = NIL;
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
-	}
 
 	/*
 	 * 3. Check if an index-only scan is possible.  If we're not building
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 472898f..4d5442f 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -459,6 +459,12 @@ typedef struct BTScanStateData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+
+	/* KNN-search fields: */
+	Datum		currDistance;	/* current distance */
+	Datum		markDistance;	/* marked distance */
+	bool		currIsNull;		/* current item is NULL */
+	bool		markIsNull;		/* marked item is NULL */
 }	BTScanStateData, *BTScanState;
 
 typedef struct BTScanOpaqueData
@@ -477,7 +483,18 @@ typedef struct BTScanOpaqueData
 	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
 	MemoryContext arrayContext; /* scan-lifespan context for array data */
 
-	BTScanStateData	state;
+	BTScanStateData state;		/* main scan state */
+
+	/* kNN-search fields: */
+	BTScanState knnState;			/* optional scan state for kNN search */
+	bool		useBidirectionalKnnScan;	/* use bidirectional kNN scan? */
+	ScanDirection scanDirection;	/* selected scan direction for
+									 * unidirectional kNN scan */
+	FmgrInfo	distanceCmpProc;	/* distance comparison procedure */
+	int16		distanceTypeLen;	/* distance typlen */
+	bool		distanceTypeByVal;	/* distance typebyval */
+	bool		currRightIsNearest;	/* current right item is nearest */
+	bool		markRightIsNearest;	/* marked right item is nearest */
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -523,11 +540,12 @@ extern bool btcanreturn(Relation index, int attno);
 /*
  * prototypes for internal functions in nbtree.c
  */
-extern bool _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno);
-extern void _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page);
-extern void _bt_parallel_done(IndexScanDesc scan);
+extern bool _bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno);
+extern void _bt_parallel_release(IndexScanDesc scan, BTScanState state, BlockNumber scan_page);
+extern void _bt_parallel_done(IndexScanDesc scan, BTScanState state);
 extern void _bt_parallel_advance_array_keys(IndexScanDesc scan);
 
+
 /*
  * prototypes for functions in nbtinsert.c
  */
@@ -608,6 +626,9 @@ extern bool btproperty(Oid index_oid, int attno,
 extern IndexTuple _bt_nonkey_truncate(Relation rel, IndexTuple itup);
 extern bool _bt_check_natts(Relation rel, Page page, OffsetNumber offnum);
 extern void _bt_allocate_tuple_workspaces(BTScanState state);
+extern void _bt_init_distance_comparison(IndexScanDesc scan);
+extern int _bt_init_knn_start_keys(IndexScanDesc scan, ScanKey *startKeys,
+						ScanKey bufKeys);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/include/access/stratnum.h b/src/include/access/stratnum.h
index 8fdba28..8087ffe 100644
--- a/src/include/access/stratnum.h
+++ b/src/include/access/stratnum.h
@@ -32,7 +32,10 @@ typedef uint16 StrategyNumber;
 #define BTGreaterEqualStrategyNumber	4
 #define BTGreaterStrategyNumber			5
 
-#define BTMaxStrategyNumber				5
+#define BTMaxStrategyNumber				5		/* number of canonical B-tree strategies */
+
+#define BtreeKNNSearchStrategyNumber	6		/* for <-> (distance) */
+#define BtreeMaxStrategyNumber			6		/* number of extended B-tree strategies */
 
 
 /*
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index 6faa9d7..c75ef39 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -347,10 +347,10 @@ ROLLBACK;
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
 ERROR:  access method "invalid_index_method" does not exist
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 6, must be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 0, must be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 7, must be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 0, must be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ERROR:  operator argument types must be specified in ALTER OPERATOR FAMILY
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -397,11 +397,12 @@ DROP OPERATOR FAMILY alt_opf8 USING btree;
 CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
-ERROR:  access method "btree" does not support ordering operators
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
 ALTER OPERATOR FAMILY alt_opf11 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql
index 84fd900..73e6e206 100644
--- a/src/test/regress/sql/alter_generic.sql
+++ b/src/test/regress/sql/alter_generic.sql
@@ -295,8 +295,8 @@ ROLLBACK;
 -- Should fail. Invalid values for ALTER OPERATOR FAMILY .. ADD / DROP
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 6 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -340,10 +340,12 @@ CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
 
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
-- 
2.7.4

0005-Add-btree-distance-operators-v05.patchtext/x-patch; name=0005-Add-btree-distance-operators-v05.patchDownload
From 78187a17b539fbf4ef621cdabe3d3955ae15b40a Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 11 Jan 2019 15:51:28 +0300
Subject: [PATCH 5/7] Add btree distance operators

---
 doc/src/sgml/indices.sgml                 |   6 +
 src/backend/utils/adt/cash.c              |  20 +++
 src/backend/utils/adt/date.c              | 113 ++++++++++++++++
 src/backend/utils/adt/float.c             |  41 ++++++
 src/backend/utils/adt/int.c               |  52 ++++++++
 src/backend/utils/adt/int8.c              |  44 ++++++
 src/backend/utils/adt/oid.c               |  14 ++
 src/backend/utils/adt/timestamp.c         | 103 ++++++++++++++
 src/include/catalog/pg_amop.dat           | 104 +++++++++++++++
 src/include/catalog/pg_operator.dat       | 108 +++++++++++++++
 src/include/catalog/pg_proc.dat           |  86 ++++++++++++
 src/include/utils/datetime.h              |   2 +
 src/include/utils/timestamp.h             |   4 +-
 src/test/regress/expected/amutils.out     |   6 +-
 src/test/regress/expected/date.out        |  61 +++++++++
 src/test/regress/expected/float4.out      |  20 +++
 src/test/regress/expected/float8.out      |  21 +++
 src/test/regress/expected/int2.out        |  33 +++++
 src/test/regress/expected/int4.out        |  32 +++++
 src/test/regress/expected/int8.out        |  31 +++++
 src/test/regress/expected/interval.out    |  15 +++
 src/test/regress/expected/money.out       |   6 +
 src/test/regress/expected/oid.out         |  13 ++
 src/test/regress/expected/opr_sanity.out  |   6 +-
 src/test/regress/expected/time.out        |  16 +++
 src/test/regress/expected/timestamp.out   | 211 +++++++++++++++++++++++++++++
 src/test/regress/expected/timestamptz.out | 214 ++++++++++++++++++++++++++++++
 src/test/regress/sql/date.sql             |   5 +
 src/test/regress/sql/float4.sql           |   3 +
 src/test/regress/sql/float8.sql           |   3 +
 src/test/regress/sql/int2.sql             |  10 ++
 src/test/regress/sql/int4.sql             |  10 ++
 src/test/regress/sql/int8.sql             |   5 +
 src/test/regress/sql/interval.sql         |   2 +
 src/test/regress/sql/money.sql            |   1 +
 src/test/regress/sql/oid.sql              |   2 +
 src/test/regress/sql/opr_sanity.sql       |   2 +-
 src/test/regress/sql/time.sql             |   3 +
 src/test/regress/sql/timestamp.sql        |   8 ++
 src/test/regress/sql/timestamptz.sql      |   8 ++
 40 files changed, 1437 insertions(+), 7 deletions(-)

diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index caec484..e656a8a 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -183,6 +183,12 @@ SELECT * FROM events ORDER BY event_date <-> date '2017-05-05' LIMIT 10;
 </programlisting>
    which finds the ten events closest to a given target date.  The ability
    to do this is again dependent on the particular operator class being used.
+   Built-in B-tree operator classes support distance ordering for data types
+   <type>int2</>, <type>int4</>, <type>int8</>,
+   <type>float4</>, <type>float8</>, <type>numeric</>,
+   <type>timestamp with time zone</>, <type>timestamp without time zone</>,
+   <type>time with time zone</>, <type>time without time zone</>,
+   <type>date</>, <type>interval</>, <type>oid</>, <type>money</>.
   </para>
 
   <para>
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index c92e9d5..83073c4 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -30,6 +30,7 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+#define SAMESIGN(a,b)	(((a) < 0) == ((b) < 0))
 
 /*************************************************************************
  * Private routines
@@ -1157,3 +1158,22 @@ int8_cash(PG_FUNCTION_ARGS)
 
 	PG_RETURN_CASH(result);
 }
+
+Datum
+cash_distance(PG_FUNCTION_ARGS)
+{
+	Cash		a = PG_GETARG_CASH(0);
+	Cash		b = PG_GETARG_CASH(1);
+	Cash		r;
+	Cash		ra;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("money out of range")));
+
+	ra = Abs(r);
+
+	PG_RETURN_CASH(ra);
+}
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 3810e4a..6153053 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -562,6 +562,17 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+Datum
+date_distance(PG_FUNCTION_ARGS)
+{
+	/* we assume the difference can't overflow */
+	Datum		diff = DirectFunctionCall2(date_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
+}
+
 /*
  * Internal routines for promoting date to timestamp and timestamp with
  * time zone
@@ -759,6 +770,29 @@ date_cmp_timestamp(PG_FUNCTION_ARGS)
 }
 
 Datum
+date_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+	Timestamp	dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
+Datum
 date_eq_timestamptz(PG_FUNCTION_ARGS)
 {
 	DateADT		dateVal = PG_GETARG_DATEADT(0);
@@ -843,6 +877,30 @@ date_cmp_timestamptz(PG_FUNCTION_ARGS)
 }
 
 Datum
+date_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	TimestampTz	dt2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz	dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
+
+
+Datum
 timestamp_eq_date(PG_FUNCTION_ARGS)
 {
 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
@@ -927,6 +985,29 @@ timestamp_cmp_date(PG_FUNCTION_ARGS)
 }
 
 Datum
+timestamp_dist_date(PG_FUNCTION_ARGS)
+{
+	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	Timestamp	dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
+Datum
 timestamptz_eq_date(PG_FUNCTION_ARGS)
 {
 	TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
@@ -1038,6 +1119,28 @@ in_range_date_interval(PG_FUNCTION_ARGS)
 							   BoolGetDatum(less));
 }
 
+Datum
+timestamptz_dist_date(PG_FUNCTION_ARGS)
+{
+	TimestampTz	dt1 = PG_GETARG_TIMESTAMPTZ(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	TimestampTz	dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
 
 /* Add an interval to a date, giving a new date.
  * Must handle both positive and negative intervals.
@@ -1946,6 +2049,16 @@ time_part(PG_FUNCTION_ARGS)
 	PG_RETURN_FLOAT8(result);
 }
 
+Datum
+time_distance(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(time_mi_time,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
+
 
 /*****************************************************************************
  *	 Time With Time Zone ADT
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 117ded8..656b94d 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -3676,6 +3676,47 @@ width_bucket_float8(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(result);
 }
 
+Datum
+float4dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float4		r = float4_mi(a, b);
+
+	PG_RETURN_FLOAT4(Abs(r));
+}
+
+Datum
+float8dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
+
+Datum
+float48dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
+Datum
+float84dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
 /* ========== PRIVATE ROUTINES ========== */
 
 #ifndef HAVE_CBRT
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index fd82a83..92227f7 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -1427,3 +1427,55 @@ generate_series_step_int4(PG_FUNCTION_ARGS)
 		/* do when there is no more left */
 		SRF_RETURN_DONE(funcctx);
 }
+
+Datum
+int2dist(PG_FUNCTION_ARGS)
+{
+	int16		a = PG_GETARG_INT16(0);
+	int16		b = PG_GETARG_INT16(1);
+	int16		r;
+	int16		ra;
+
+	if (pg_sub_s16_overflow(a, b, &r) ||
+		r == PG_INT16_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("smallint out of range")));
+
+	ra = Abs(r);
+
+	PG_RETURN_INT16(ra);
+}
+
+static int32
+int44_dist(int32 a, int32 b)
+{
+	int32		r;
+
+	if (pg_sub_s32_overflow(a, b, &r) ||
+		r == PG_INT32_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("integer out of range")));
+
+	return Abs(r);
+}
+
+
+Datum
+int4dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int24dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT16(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int42dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT16(1)));
+}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index d16cc9e..a988871 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1373,3 +1373,47 @@ generate_series_step_int8(PG_FUNCTION_ARGS)
 		/* do when there is no more left */
 		SRF_RETURN_DONE(funcctx);
 }
+
+static int64
+int88_dist(int64 a, int64 b)
+{
+	int64		r;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("bigint out of range")));
+
+	return Abs(r);
+}
+
+Datum
+int8dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int82dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT16(1)));
+}
+
+Datum
+int84dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT32(1)));
+}
+
+Datum
+int28dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT16(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int48dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT32(0), PG_GETARG_INT64(1)));
+}
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index eb21b07..6a43c41 100644
--- a/src/backend/utils/adt/oid.c
+++ b/src/backend/utils/adt/oid.c
@@ -469,3 +469,17 @@ oidvectorgt(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(cmp > 0);
 }
+
+Datum
+oiddist(PG_FUNCTION_ARGS)
+{
+	Oid			a = PG_GETARG_OID(0);
+	Oid			b = PG_GETARG_OID(1);
+	Oid			res;
+
+	if (a < b)
+		res = b - a;
+	else
+		res = a - b;
+	PG_RETURN_OID(res);
+}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7befb6a..4826a77 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -2672,6 +2672,86 @@ timestamp_mi(PG_FUNCTION_ARGS)
 	PG_RETURN_INTERVAL_P(result);
 }
 
+Datum
+timestamp_distance(PG_FUNCTION_ARGS)
+{
+	Timestamp	a = PG_GETARG_TIMESTAMP(0);
+	Timestamp	b = PG_GETARG_TIMESTAMP(1);
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		Interval   *p = palloc(sizeof(Interval));
+
+		p->day = INT_MAX;
+		p->month = INT_MAX;
+		p->time = PG_INT64_MAX;
+		PG_RETURN_INTERVAL_P(p);
+	}
+	else
+		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+												  PG_GETARG_DATUM(0),
+												  PG_GETARG_DATUM(1)));
+	PG_RETURN_INTERVAL_P(abs_interval(r));
+}
+
+Interval *
+timestamp_dist_internal(Timestamp a, Timestamp b)
+{
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		return r;
+	}
+
+	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+											  TimestampGetDatum(a),
+											  TimestampGetDatum(b)));
+
+	return abs_interval(r);
+}
+
+Datum
+timestamptz_distance(PG_FUNCTION_ARGS)
+{
+	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
+	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(a, b));
+}
+
+Datum
+timestamp_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	Timestamp	ts1 = PG_GETARG_TIMESTAMP(0);
+	TimestampTz	tstz2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz	tstz1;
+
+	tstz1 = timestamp2timestamptz(ts1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+Datum
+timestamptz_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	TimestampTz	tstz1 = PG_GETARG_TIMESTAMPTZ(0);
+	Timestamp	ts2 = PG_GETARG_TIMESTAMP(1);
+	TimestampTz	tstz2;
+
+	tstz2 = timestamp2timestamptz(ts2);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+
 /*
  *	interval_justify_interval()
  *
@@ -3541,6 +3621,29 @@ interval_avg(PG_FUNCTION_ARGS)
 							   Float8GetDatum((double) N.time));
 }
 
+Interval *
+abs_interval(Interval *a)
+{
+	static Interval zero = {0, 0, 0};
+
+	if (DatumGetBool(DirectFunctionCall2(interval_lt,
+										 IntervalPGetDatum(a),
+										 IntervalPGetDatum(&zero))))
+		a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
+												  IntervalPGetDatum(a)));
+
+	return a;
+}
+
+Datum
+interval_distance(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(interval_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
 
 /* timestamp_age()
  * Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0ab95d8..0e06b04 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -30,6 +30,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int2,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int24
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -47,6 +51,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int2,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int28
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -64,6 +72,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int2,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int4
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -81,6 +93,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int4,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int42
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -98,6 +114,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int4,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int48
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -115,6 +135,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int4,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int8
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -132,6 +156,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int8,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int82
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -149,6 +177,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int8,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int84
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -166,6 +198,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int8,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # btree oid_ops
 
@@ -179,6 +215,10 @@
   amopstrategy => '4', amopopr => '>=(oid,oid)', amopmethod => 'btree' },
 { amopfamily => 'btree/oid_ops', amoplefttype => 'oid', amoprighttype => 'oid',
   amopstrategy => '5', amopopr => '>(oid,oid)', amopmethod => 'btree' },
+{ amopfamily => 'btree/oid_ops', amoplefttype => 'oid',
+  amoprighttype => 'oid', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(oid,oid)', amopmethod => 'btree',
+  amopsortfamily => 'btree/oid_ops' },
 
 # btree tid_ops
 
@@ -229,6 +269,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float4,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float48
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
@@ -246,6 +290,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float4,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # default operators float8
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -263,6 +311,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float8,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float84
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -280,6 +332,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float8,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # btree char_ops
 
@@ -416,6 +472,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(date,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -433,6 +493,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(date,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -450,6 +514,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(date,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -467,6 +535,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamp,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -484,6 +556,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(timestamp,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -501,6 +577,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamp,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -518,6 +598,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -535,6 +619,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'date', amopstrategy => '5',
   amopopr => '>(timestamptz,date)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -552,6 +640,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree time_ops
 
@@ -570,6 +662,10 @@
 { amopfamily => 'btree/time_ops', amoplefttype => 'time',
   amoprighttype => 'time', amopstrategy => '5', amopopr => '>(time,time)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/time_ops', amoplefttype => 'time',
+  amoprighttype => 'time', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(time,time)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree timetz_ops
 
@@ -606,6 +702,10 @@
 { amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
   amoprighttype => 'interval', amopstrategy => '5',
   amopopr => '>(interval,interval)', amopmethod => 'btree' },
+{ amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
+  amoprighttype => 'interval', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(interval,interval)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree macaddr
 
@@ -781,6 +881,10 @@
 { amopfamily => 'btree/money_ops', amoplefttype => 'money',
   amoprighttype => 'money', amopstrategy => '5', amopopr => '>(money,money)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/money_ops', amoplefttype => 'money',
+  amoprighttype => 'money', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(money,money)', amopmethod => 'btree',
+  amopsortfamily => 'btree/money_ops' },
 
 # btree array_ops
 
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 06aec07..914ca76 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2851,6 +2851,114 @@
   oprname => '-', oprleft => 'pg_lsn', oprright => 'pg_lsn',
   oprresult => 'numeric', oprcode => 'pg_lsn_mi' },
 
+# distance operators
+{ oid => '4217', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int2', oprresult => 'int2', oprcom => '<->(int2,int2)',
+  oprcode => 'int2dist'},
+{ oid => '4218', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int4)',
+  oprcode => 'int4dist'},
+{ oid => '4219', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int8)',
+  oprcode => 'int8dist'},
+{ oid => '4220', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'oid',
+  oprright => 'oid', oprresult => 'oid', oprcom => '<->(oid,oid)',
+  oprcode => 'oiddist'},
+{ oid => '4221', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float4', oprresult => 'float4', oprcom => '<->(float4,float4)',
+  oprcode => 'float4dist'},
+{ oid => '4222', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float8)',
+  oprcode => 'float8dist'},
+{ oid => '4223', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'money',
+  oprright => 'money', oprresult => 'money', oprcom => '<->(money,money)',
+  oprcode => 'cash_distance'},
+{ oid => '4224', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'date', oprresult => 'int4', oprcom => '<->(date,date)',
+  oprcode => 'date_distance'},
+{ oid => '4225', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'time',
+  oprright => 'time', oprresult => 'interval', oprcom => '<->(time,time)',
+  oprcode => 'time_distance'},
+{ oid => '4226', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamp)',
+  oprcode => 'timestamp_distance'},
+{ oid => '4227', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamptz)',
+  oprcode => 'timestamptz_distance'},
+{ oid => '4228', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'interval',
+  oprright => 'interval', oprresult => 'interval', oprcom => '<->(interval,interval)',
+  oprcode => 'interval_distance'},
+
+# cross-type distance operators
+{ oid => '4229', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int2)',
+  oprcode => 'int24dist'},
+{ oid => '4230', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int2', oprresult => 'int4', oprcom => '<->(int2,int4)',
+  oprcode => 'int42dist'},
+{ oid => '4231', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int2)',
+  oprcode => 'int28dist'},
+{ oid => '4232', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int2', oprresult => 'int8', oprcom => '<->(int2,int8)',
+  oprcode => 'int82dist'},
+{ oid => '4233', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int4)',
+  oprcode => 'int48dist'},
+{ oid => '4234', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int4', oprresult => 'int8', oprcom => '<->(int4,int8)',
+  oprcode => 'int84dist'},
+{ oid => '4235', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float4)',
+  oprcode => 'float48dist'},
+{ oid => '4236', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float4', oprresult => 'float8', oprcom => '<->(float4,float8)',
+  oprcode => 'float84dist'},
+{ oid => '4237', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,date)',
+  oprcode => 'date_dist_timestamp'},
+{ oid => '4238', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamp)',
+  oprcode => 'timestamp_dist_date'},
+{ oid => '4239', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,date)',
+  oprcode => 'date_dist_timestamptz'},
+{ oid => '4240', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamptz)',
+  oprcode => 'timestamptz_dist_date'},
+{ oid => '4241', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamp)',
+  oprcode => 'timestamp_dist_timestamptz'},
+{ oid => '4242', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamptz)',
+  oprcode => 'timestamptz_dist_timestamp'},
+
 # enum operators
 { oid => '3516', descr => 'equal',
   oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'anyenum',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3ecc2e1..21c8377 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10509,4 +10509,90 @@
   proargnames => '{rootrelid,relid,parentrelid,isleaf,level}',
   prosrc => 'pg_partition_tree' },
 
+# distance functions
+{ oid => '4243',
+  proname => 'int2dist', prorettype => 'int2',
+  proargtypes => 'int2 int2', prosrc => 'int2dist' },
+{ oid => '4244',
+  proname => 'int4dist', prorettype => 'int4',
+  proargtypes => 'int4 int4', prosrc => 'int4dist' },
+{ oid => '4245',
+  proname => 'int8dist', prorettype => 'int8',
+  proargtypes => 'int8 int8', prosrc => 'int8dist' },
+{ oid => '4246',
+  proname => 'oiddist', proleakproof => 't', prorettype => 'oid',
+  proargtypes => 'oid oid', prosrc => 'oiddist' },
+{ oid => '4247',
+  proname => 'float4dist', prorettype => 'float4',
+  proargtypes => 'float4 float4', prosrc => 'float4dist' },
+{ oid => '4248',
+  proname => 'float8dist', prorettype => 'float8',
+  proargtypes => 'float8 float8', prosrc => 'float8dist' },
+{ oid => '4249',
+  proname => 'cash_distance', prorettype => 'money',
+  proargtypes => 'money money', prosrc => 'cash_distance' },
+{ oid => '4250',
+  proname => 'date_distance', prorettype => 'int4',
+  proargtypes => 'date date', prosrc => 'date_distance' },
+{ oid => '4251',
+  proname => 'time_distance', prorettype => 'interval',
+  proargtypes => 'time time', prosrc => 'time_distance' },
+{ oid => '4252',
+  proname => 'timestamp_distance', prorettype => 'interval',
+  proargtypes => 'timestamp timestamp', prosrc => 'timestamp_distance' },
+{ oid => '4253',
+  proname => 'timestamptz_distance', prorettype => 'interval',
+  proargtypes => 'timestamptz timestamptz', prosrc => 'timestamptz_distance' },
+{ oid => '4254',
+  proname => 'interval_distance', prorettype => 'interval',
+  proargtypes => 'interval interval', prosrc => 'interval_distance' },
+
+# cross-type distance functions
+{ oid => '4255',
+  proname => 'int24dist', prorettype => 'int4',
+  proargtypes => 'int2 int4', prosrc => 'int24dist' },
+{ oid => '4256',
+  proname => 'int28dist', prorettype => 'int8',
+  proargtypes => 'int2 int8', prosrc => 'int28dist' },
+{ oid => '4257',
+  proname => 'int42dist', prorettype => 'int4',
+  proargtypes => 'int4 int2', prosrc => 'int42dist' },
+{ oid => '4258',
+  proname => 'int48dist', prorettype => 'int8',
+  proargtypes => 'int4 int8', prosrc => 'int48dist' },
+{ oid => '4259',
+  proname => 'int82dist', prorettype => 'int8',
+  proargtypes => 'int8 int2', prosrc => 'int82dist' },
+{ oid => '4260',
+  proname => 'int84dist', prorettype => 'int8',
+  proargtypes => 'int8 int4', prosrc => 'int84dist' },
+{ oid => '4261',
+  proname => 'float48dist', prorettype => 'float8',
+  proargtypes => 'float4 float8', prosrc => 'float48dist' },
+{ oid => '4262',
+  proname => 'float84dist', prorettype => 'float8',
+  proargtypes => 'float8 float4', prosrc => 'float84dist' },
+{ oid => '4263',
+  proname => 'date_dist_timestamp', prorettype => 'interval',
+  proargtypes => 'date timestamp', prosrc => 'date_dist_timestamp' },
+{ oid => '4264',
+  proname => 'date_dist_timestamptz', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'date timestamptz',
+  prosrc => 'date_dist_timestamptz' },
+{ oid => '4265',
+  proname => 'timestamp_dist_date', prorettype => 'interval',
+  proargtypes => 'timestamp date', prosrc => 'timestamp_dist_date' },
+{ oid => '4266',
+  proname => 'timestamp_dist_timestamptz', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamp timestamptz',
+  prosrc => 'timestamp_dist_timestamptz' },
+{ oid => '4267',
+  proname => 'timestamptz_dist_date', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamptz date',
+  prosrc => 'timestamptz_dist_date' },
+{ oid => '4268',
+  proname => 'timestamptz_dist_timestamp', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamptz timestamp',
+  prosrc => 'timestamptz_dist_timestamp' },
+
 ]
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index f5ec9bb..53a00da 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -338,4 +338,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 					   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern Interval *abs_interval(Interval *a);
+
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index aeb89dc..438a5eb 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -93,9 +93,11 @@ extern Timestamp SetEpochTimestamp(void);
 extern void GetEpochTime(struct pg_tm *tm);
 
 extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern Interval *timestamp_dist_internal(Timestamp a, Timestamp b);
 
-/* timestamp comparison works for timestamptz also */
+/* timestamp comparison and distance works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
+#define timestamptz_dist_internal(dt1,dt2)	timestamp_dist_internal(dt1, dt2)
 
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index 4570a39..630dc6b 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -24,7 +24,7 @@ select prop,
  nulls_first        |    |       | f
  nulls_last         |    |       | t
  orderable          |    |       | t
- distance_orderable |    |       | f
+ distance_orderable |    |       | t
  returnable         |    |       | t
  search_array       |    |       | t
  search_nulls       |    |       | t
@@ -100,7 +100,7 @@ select prop,
  nulls_first        | f     | f    | f    | f            | f           | f   | f
  nulls_last         | t     | f    | f    | f            | f           | f   | f
  orderable          | t     | f    | f    | f            | f           | f   | f
- distance_orderable | f     | f    | t    | f            | t           | f   | f
+ distance_orderable | t     | f    | t    | f            | t           | f   | f
  returnable         | t     | f    | f    | t            | t           | f   | f
  search_array       | t     | f    | f    | f            | f           | f   | f
  search_nulls       | t     | f    | t    | t            | t           | f   | t
@@ -231,7 +231,7 @@ select col, prop, pg_index_column_has_property(o, col, prop)
    1 | desc               | f
    1 | nulls_first        | f
    1 | nulls_last         | t
-   1 | distance_orderable | f
+   1 | distance_orderable | t
    1 | returnable         | t
    1 | bogus              | 
    2 | orderable          | f
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
index 1bcc946..b9b819c 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -1477,3 +1477,64 @@ select make_time(10, 55, 100.1);
 ERROR:  time field value out of range: 10:55:100.1
 select make_time(24, 0, 2.1);
 ERROR:  time field value out of range: 24:00:2.1
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+ Fifteen | Distance 
+---------+----------
+         |    16006
+         |    15941
+         |     1802
+         |     1801
+         |     1800
+         |     1799
+         |     1436
+         |     1435
+         |     1434
+         |      308
+         |      307
+         |      306
+         |    13578
+         |    13944
+         |    14311
+(15 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+ Fifteen |               Distance                
+---------+---------------------------------------
+         | @ 16006 days 1 hour 23 mins 45 secs
+         | @ 15941 days 1 hour 23 mins 45 secs
+         | @ 1802 days 1 hour 23 mins 45 secs
+         | @ 1801 days 1 hour 23 mins 45 secs
+         | @ 1800 days 1 hour 23 mins 45 secs
+         | @ 1799 days 1 hour 23 mins 45 secs
+         | @ 1436 days 1 hour 23 mins 45 secs
+         | @ 1435 days 1 hour 23 mins 45 secs
+         | @ 1434 days 1 hour 23 mins 45 secs
+         | @ 308 days 1 hour 23 mins 45 secs
+         | @ 307 days 1 hour 23 mins 45 secs
+         | @ 306 days 1 hour 23 mins 45 secs
+         | @ 13577 days 22 hours 36 mins 15 secs
+         | @ 13943 days 22 hours 36 mins 15 secs
+         | @ 14310 days 22 hours 36 mins 15 secs
+(15 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
+ Fifteen |               Distance                
+---------+---------------------------------------
+         | @ 16005 days 14 hours 23 mins 45 secs
+         | @ 15940 days 14 hours 23 mins 45 secs
+         | @ 1801 days 14 hours 23 mins 45 secs
+         | @ 1800 days 14 hours 23 mins 45 secs
+         | @ 1799 days 14 hours 23 mins 45 secs
+         | @ 1798 days 14 hours 23 mins 45 secs
+         | @ 1435 days 14 hours 23 mins 45 secs
+         | @ 1434 days 14 hours 23 mins 45 secs
+         | @ 1433 days 14 hours 23 mins 45 secs
+         | @ 307 days 14 hours 23 mins 45 secs
+         | @ 306 days 14 hours 23 mins 45 secs
+         | @ 305 days 15 hours 23 mins 45 secs
+         | @ 13578 days 8 hours 36 mins 15 secs
+         | @ 13944 days 8 hours 36 mins 15 secs
+         | @ 14311 days 8 hours 36 mins 15 secs
+(15 rows)
+
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index 2f47e1c..a9dddc4 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -257,6 +257,26 @@ SELECT '' AS five, * FROM FLOAT4_TBL;
       | -1.23457e-20
 (5 rows)
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+ five |      f1      |    dist     
+------+--------------+-------------
+      |            0 |      1004.3
+      |       -34.84 |     1039.14
+      |      -1004.3 |      2008.6
+      | -1.23457e+20 | 1.23457e+20
+      | -1.23457e-20 |      1004.3
+(5 rows)
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+ five |      f1      |         dist         
+------+--------------+----------------------
+      |            0 |               1004.3
+      |       -34.84 |     1039.14000015259
+      |      -1004.3 |     2008.59998779297
+      | -1.23457e+20 | 1.23456789557014e+20
+      | -1.23457e-20 |               1004.3
+(5 rows)
+
 -- test edge-case coercions to integer
 SELECT '32767.4'::float4::int2;
  int2  
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
index 75c0bf3..abf9ef1 100644
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -404,6 +404,27 @@ SELECT '' AS five, f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
       | 1.2345678901234e-200 |  2.3112042409018e-67
 (5 rows)
 
+-- distance
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+ five |          f1          |         dist         
+------+----------------------+----------------------
+      |                    0 |               1004.3
+      |               1004.3 |                    0
+      |               -34.84 |              1039.14
+      | 1.2345678901234e+200 | 1.2345678901234e+200
+      | 1.2345678901234e-200 |               1004.3
+(5 rows)
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
+ five |          f1          |         dist         
+------+----------------------+----------------------
+      |                    0 |     1004.29998779297
+      |               1004.3 | 1.22070312045253e-05
+      |               -34.84 |     1039.13998779297
+      | 1.2345678901234e+200 | 1.2345678901234e+200
+      | 1.2345678901234e-200 |     1004.29998779297
+(5 rows)
+
 SELECT '' AS five, * FROM FLOAT8_TBL;
  five |          f1          
 ------+----------------------
diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out
index 8c255b9..0edc57e 100644
--- a/src/test/regress/expected/int2.out
+++ b/src/test/regress/expected/int2.out
@@ -242,6 +242,39 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
       | -32767 | -16383
 (5 rows)
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+ERROR:  smallint out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+ four |  f1   |   x   
+------+-------+-------
+      |     0 |     2
+      |  1234 |  1232
+      | -1234 |  1236
+      | 32767 | 32765
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
   text  
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
index bda7a8d..3735dbc 100644
--- a/src/test/regress/expected/int4.out
+++ b/src/test/regress/expected/int4.out
@@ -247,6 +247,38 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
       | -2147483647 | -1073741823
 (5 rows)
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+ERROR:  integer out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+ five |     f1      |     x      
+------+-------------+------------
+      |           0 |          2
+      |      123456 |     123454
+      |     -123456 |     123458
+      |  2147483647 | 2147483645
+      | -2147483647 | 2147483649
+(5 rows)
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index 35e3b3f..8940e81 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -432,6 +432,37 @@ SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 A
  4567890123457035 | -4567890123456543 | 1123700970370370094 |     0
 (5 rows)
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
 SELECT q2, abs(q2) FROM INT8_TBL;
         q2         |       abs        
 -------------------+------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index f88f345..cb95adf 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -207,6 +207,21 @@ SELECT '' AS fortyfive, r1.*, r2.*
            | 34 years        | 6 years
 (45 rows)
 
+SELECT '' AS ten, f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+ ten |          ?column?          
+-----+----------------------------
+     | 2 days 02:59:00
+     | 2 days -02:00:00
+     | 8 days -03:00:00
+     | 34 years -2 days -03:00:00
+     | 3 mons -2 days -03:00:00
+     | 2 days 03:00:14
+     | 1 day 00:56:56
+     | 6 years -2 days -03:00:00
+     | 5 mons -2 days -03:00:00
+     | 5 mons -2 days +09:00:00
+(10 rows)
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index ab86595..fb2a489 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -123,6 +123,12 @@ SELECT m / 2::float4 FROM money_data;
    $61.50
 (1 row)
 
+SELECT m <-> '$123.45' FROM money_data;
+ ?column? 
+----------
+    $0.45
+(1 row)
+
 -- All true
 SELECT m = '$123.00' FROM money_data;
  ?column? 
diff --git a/src/test/regress/expected/oid.out b/src/test/regress/expected/oid.out
index 1eab9cc..5339a48 100644
--- a/src/test/regress/expected/oid.out
+++ b/src/test/regress/expected/oid.out
@@ -119,4 +119,17 @@ SELECT '' AS three, o.* FROM OID_TBL o WHERE o.f1 > '1234';
        |   99999999
 (3 rows)
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+ eight |     f1     |  ?column?  
+-------+------------+------------
+       |       1234 |       1111
+       |       1235 |       1112
+       |        987 |        864
+       | 4294966256 | 4294966133
+       |   99999999 |   99999876
+       |          5 |        118
+       |         10 |        113
+       |         15 |        108
+(8 rows)
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 7328095..9b1babc 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -750,6 +750,7 @@ macaddr8_gt(macaddr8,macaddr8)
 macaddr8_ge(macaddr8,macaddr8)
 macaddr8_ne(macaddr8,macaddr8)
 macaddr8_cmp(macaddr8,macaddr8)
+oiddist(oid,oid)
 -- restore normal output mode
 \a\t
 -- List of functions used by libpq's fe-lobj.c
@@ -1332,7 +1333,7 @@ WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND
     ao.amoprighttype = ap.amprocrighttype AND
     ap.amprocnum = 1 AND
     (pp.provolatile != po.provolatile OR
-     pp.proleakproof != po.proleakproof)
+     (NOT pp.proleakproof AND po.proleakproof))
 ORDER BY 1;
                    proc                    | vp | lp |              opr              | vo | lo 
 -------------------------------------------+----+----+-------------------------------+----+----
@@ -1862,6 +1863,7 @@ ORDER BY 1, 2, 3;
         403 |            5 | *>
         403 |            5 | >
         403 |            5 | ~>~
+        403 |            6 | <->
         405 |            1 | =
         783 |            1 | <<
         783 |            1 | @@
@@ -1971,7 +1973,7 @@ ORDER BY 1, 2, 3;
        4000 |           26 | >>
        4000 |           27 | >>=
        4000 |           28 | ^@
-(123 rows)
+(124 rows)
 
 -- Check that all opclass search operators have selectivity estimators.
 -- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out
index 8e0afe6..ee74faa 100644
--- a/src/test/regress/expected/time.out
+++ b/src/test/regress/expected/time.out
@@ -86,3 +86,19 @@ ERROR:  operator is not unique: time without time zone + time without time zone
 LINE 1: SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
                   ^
 HINT:  Could not choose a best candidate operator. You might need to add explicit type casts.
+-- distance
+SELECT f1 AS "Ten", f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
+     Ten     |           Distance            
+-------------+-------------------------------
+ 00:00:00    | @ 1 hour 23 mins 45 secs
+ 01:00:00    | @ 23 mins 45 secs
+ 02:03:00    | @ 39 mins 15 secs
+ 11:59:00    | @ 10 hours 35 mins 15 secs
+ 12:00:00    | @ 10 hours 36 mins 15 secs
+ 12:01:00    | @ 10 hours 37 mins 15 secs
+ 23:59:00    | @ 22 hours 35 mins 15 secs
+ 23:59:59.99 | @ 22 hours 36 mins 14.99 secs
+ 15:36:39    | @ 14 hours 12 mins 54 secs
+ 15:36:39    | @ 14 hours 12 mins 54 secs
+(10 rows)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 4a2fabd..dcb4205 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1604,3 +1604,214 @@ SELECT make_timestamp(2014,12,28,6,30,45.887);
  Sun Dec 28 06:30:45.887 2014
 (1 row)
 
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 58 secs
+    | @ 1453 days 6 hours 27 mins 58.6 secs
+    | @ 1453 days 6 hours 27 mins 58.5 secs
+    | @ 1453 days 6 hours 27 mins 58.4 secs
+    | @ 1493 days
+    | @ 1492 days 20 hours 55 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 6 hours 27 mins 59 secs
+    | @ 231 days 18 hours 19 mins 20 secs
+    | @ 324 days 15 hours 45 mins 59 secs
+    | @ 324 days 10 hours 45 mins 58 secs
+    | @ 324 days 11 hours 45 mins 57 secs
+    | @ 324 days 20 hours 45 mins 56 secs
+    | @ 324 days 21 hours 45 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 28 mins
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 5 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1452 days 6 hours 27 mins 59 secs
+    | @ 1451 days 6 hours 27 mins 59 secs
+    | @ 1450 days 6 hours 27 mins 59 secs
+    | @ 1449 days 6 hours 27 mins 59 secs
+    | @ 1448 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 765901 days 6 hours 27 mins 59 secs
+    | @ 695407 days 6 hours 27 mins 59 secs
+    | @ 512786 days 6 hours 27 mins 59 secs
+    | @ 330165 days 6 hours 27 mins 59 secs
+    | @ 111019 days 6 hours 27 mins 59 secs
+    | @ 74495 days 6 hours 27 mins 59 secs
+    | @ 37971 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 35077 days 17 hours 32 mins 1 sec
+    | @ 1801 days 6 hours 27 mins 59 secs
+    | @ 1800 days 6 hours 27 mins 59 secs
+    | @ 1799 days 6 hours 27 mins 59 secs
+    | @ 1495 days 6 hours 27 mins 59 secs
+    | @ 1494 days 6 hours 27 mins 59 secs
+    | @ 1493 days 6 hours 27 mins 59 secs
+    | @ 1435 days 6 hours 27 mins 59 secs
+    | @ 1434 days 6 hours 27 mins 59 secs
+    | @ 1130 days 6 hours 27 mins 59 secs
+    | @ 1129 days 6 hours 27 mins 59 secs
+    | @ 399 days 6 hours 27 mins 59 secs
+    | @ 398 days 6 hours 27 mins 59 secs
+    | @ 33 days 6 hours 27 mins 59 secs
+    | @ 32 days 6 hours 27 mins 59 secs
+(63 rows)
+
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 1 hour 23 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 43 secs
+    | @ 1453 days 7 hours 51 mins 43.6 secs
+    | @ 1453 days 7 hours 51 mins 43.5 secs
+    | @ 1453 days 7 hours 51 mins 43.4 secs
+    | @ 1493 days 1 hour 23 mins 45 secs
+    | @ 1492 days 22 hours 19 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 7 hours 51 mins 44 secs
+    | @ 231 days 16 hours 55 mins 35 secs
+    | @ 324 days 17 hours 9 mins 44 secs
+    | @ 324 days 12 hours 9 mins 43 secs
+    | @ 324 days 13 hours 9 mins 42 secs
+    | @ 324 days 22 hours 9 mins 41 secs
+    | @ 324 days 23 hours 9 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 6 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1452 days 7 hours 51 mins 44 secs
+    | @ 1451 days 7 hours 51 mins 44 secs
+    | @ 1450 days 7 hours 51 mins 44 secs
+    | @ 1449 days 7 hours 51 mins 44 secs
+    | @ 1448 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 765901 days 7 hours 51 mins 44 secs
+    | @ 695407 days 7 hours 51 mins 44 secs
+    | @ 512786 days 7 hours 51 mins 44 secs
+    | @ 330165 days 7 hours 51 mins 44 secs
+    | @ 111019 days 7 hours 51 mins 44 secs
+    | @ 74495 days 7 hours 51 mins 44 secs
+    | @ 37971 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 35077 days 16 hours 8 mins 16 secs
+    | @ 1801 days 7 hours 51 mins 44 secs
+    | @ 1800 days 7 hours 51 mins 44 secs
+    | @ 1799 days 7 hours 51 mins 44 secs
+    | @ 1495 days 7 hours 51 mins 44 secs
+    | @ 1494 days 7 hours 51 mins 44 secs
+    | @ 1493 days 7 hours 51 mins 44 secs
+    | @ 1435 days 7 hours 51 mins 44 secs
+    | @ 1434 days 7 hours 51 mins 44 secs
+    | @ 1130 days 7 hours 51 mins 44 secs
+    | @ 1129 days 7 hours 51 mins 44 secs
+    | @ 399 days 7 hours 51 mins 44 secs
+    | @ 398 days 7 hours 51 mins 44 secs
+    | @ 33 days 7 hours 51 mins 44 secs
+    | @ 32 days 7 hours 51 mins 44 secs
+(63 rows)
+
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |                Distance                
+----+----------------------------------------
+    | @ 11355 days 14 hours 23 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 43 secs
+    | @ 1452 days 20 hours 51 mins 43.6 secs
+    | @ 1452 days 20 hours 51 mins 43.5 secs
+    | @ 1452 days 20 hours 51 mins 43.4 secs
+    | @ 1492 days 14 hours 23 mins 45 secs
+    | @ 1492 days 11 hours 19 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 21 hours 51 mins 44 secs
+    | @ 232 days 2 hours 55 mins 35 secs
+    | @ 324 days 6 hours 9 mins 44 secs
+    | @ 324 days 1 hour 9 mins 43 secs
+    | @ 324 days 2 hours 9 mins 42 secs
+    | @ 324 days 11 hours 9 mins 41 secs
+    | @ 324 days 12 hours 9 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1451 days 20 hours 51 mins 44 secs
+    | @ 1450 days 20 hours 51 mins 44 secs
+    | @ 1449 days 20 hours 51 mins 44 secs
+    | @ 1448 days 20 hours 51 mins 44 secs
+    | @ 1447 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 765900 days 20 hours 51 mins 44 secs
+    | @ 695406 days 20 hours 51 mins 44 secs
+    | @ 512785 days 20 hours 51 mins 44 secs
+    | @ 330164 days 20 hours 51 mins 44 secs
+    | @ 111018 days 20 hours 51 mins 44 secs
+    | @ 74494 days 20 hours 51 mins 44 secs
+    | @ 37970 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 35078 days 3 hours 8 mins 16 secs
+    | @ 1800 days 20 hours 51 mins 44 secs
+    | @ 1799 days 20 hours 51 mins 44 secs
+    | @ 1798 days 20 hours 51 mins 44 secs
+    | @ 1494 days 20 hours 51 mins 44 secs
+    | @ 1493 days 20 hours 51 mins 44 secs
+    | @ 1492 days 20 hours 51 mins 44 secs
+    | @ 1434 days 20 hours 51 mins 44 secs
+    | @ 1433 days 20 hours 51 mins 44 secs
+    | @ 1129 days 20 hours 51 mins 44 secs
+    | @ 1128 days 20 hours 51 mins 44 secs
+    | @ 398 days 20 hours 51 mins 44 secs
+    | @ 397 days 20 hours 51 mins 44 secs
+    | @ 32 days 20 hours 51 mins 44 secs
+    | @ 31 days 20 hours 51 mins 44 secs
+(63 rows)
+
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 8a4c719..0a05e37 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2544,3 +2544,217 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
  Tue Jan 17 16:00:00 2017 PST
 (1 row)
 
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 8 hours
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 58 secs
+    | @ 1453 days 6 hours 27 mins 58.6 secs
+    | @ 1453 days 6 hours 27 mins 58.5 secs
+    | @ 1453 days 6 hours 27 mins 58.4 secs
+    | @ 1493 days
+    | @ 1492 days 20 hours 55 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 7 hours 27 mins 59 secs
+    | @ 231 days 17 hours 19 mins 20 secs
+    | @ 324 days 15 hours 45 mins 59 secs
+    | @ 324 days 19 hours 45 mins 58 secs
+    | @ 324 days 21 hours 45 mins 57 secs
+    | @ 324 days 20 hours 45 mins 56 secs
+    | @ 324 days 22 hours 45 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 28 mins
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 9 hours 27 mins 59 secs
+    | @ 1303 days 10 hours 27 mins 59 secs
+    | @ 1333 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1452 days 6 hours 27 mins 59 secs
+    | @ 1451 days 6 hours 27 mins 59 secs
+    | @ 1450 days 6 hours 27 mins 59 secs
+    | @ 1449 days 6 hours 27 mins 59 secs
+    | @ 1448 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 765901 days 6 hours 27 mins 59 secs
+    | @ 695407 days 6 hours 27 mins 59 secs
+    | @ 512786 days 6 hours 27 mins 59 secs
+    | @ 330165 days 6 hours 27 mins 59 secs
+    | @ 111019 days 6 hours 27 mins 59 secs
+    | @ 74495 days 6 hours 27 mins 59 secs
+    | @ 37971 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 35077 days 17 hours 32 mins 1 sec
+    | @ 1801 days 6 hours 27 mins 59 secs
+    | @ 1800 days 6 hours 27 mins 59 secs
+    | @ 1799 days 6 hours 27 mins 59 secs
+    | @ 1495 days 6 hours 27 mins 59 secs
+    | @ 1494 days 6 hours 27 mins 59 secs
+    | @ 1493 days 6 hours 27 mins 59 secs
+    | @ 1435 days 6 hours 27 mins 59 secs
+    | @ 1434 days 6 hours 27 mins 59 secs
+    | @ 1130 days 6 hours 27 mins 59 secs
+    | @ 1129 days 6 hours 27 mins 59 secs
+    | @ 399 days 6 hours 27 mins 59 secs
+    | @ 398 days 6 hours 27 mins 59 secs
+    | @ 33 days 6 hours 27 mins 59 secs
+    | @ 32 days 6 hours 27 mins 59 secs
+(64 rows)
+
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 9 hours 23 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 43 secs
+    | @ 1453 days 7 hours 51 mins 43.6 secs
+    | @ 1453 days 7 hours 51 mins 43.5 secs
+    | @ 1453 days 7 hours 51 mins 43.4 secs
+    | @ 1493 days 1 hour 23 mins 45 secs
+    | @ 1492 days 22 hours 19 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 8 hours 51 mins 44 secs
+    | @ 231 days 15 hours 55 mins 35 secs
+    | @ 324 days 17 hours 9 mins 44 secs
+    | @ 324 days 21 hours 9 mins 43 secs
+    | @ 324 days 23 hours 9 mins 42 secs
+    | @ 324 days 22 hours 9 mins 41 secs
+    | @ 325 days 9 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 10 hours 51 mins 44 secs
+    | @ 1303 days 11 hours 51 mins 44 secs
+    | @ 1333 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1452 days 7 hours 51 mins 44 secs
+    | @ 1451 days 7 hours 51 mins 44 secs
+    | @ 1450 days 7 hours 51 mins 44 secs
+    | @ 1449 days 7 hours 51 mins 44 secs
+    | @ 1448 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 765901 days 7 hours 51 mins 44 secs
+    | @ 695407 days 7 hours 51 mins 44 secs
+    | @ 512786 days 7 hours 51 mins 44 secs
+    | @ 330165 days 7 hours 51 mins 44 secs
+    | @ 111019 days 7 hours 51 mins 44 secs
+    | @ 74495 days 7 hours 51 mins 44 secs
+    | @ 37971 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 35077 days 16 hours 8 mins 16 secs
+    | @ 1801 days 7 hours 51 mins 44 secs
+    | @ 1800 days 7 hours 51 mins 44 secs
+    | @ 1799 days 7 hours 51 mins 44 secs
+    | @ 1495 days 7 hours 51 mins 44 secs
+    | @ 1494 days 7 hours 51 mins 44 secs
+    | @ 1493 days 7 hours 51 mins 44 secs
+    | @ 1435 days 7 hours 51 mins 44 secs
+    | @ 1434 days 7 hours 51 mins 44 secs
+    | @ 1130 days 7 hours 51 mins 44 secs
+    | @ 1129 days 7 hours 51 mins 44 secs
+    | @ 399 days 7 hours 51 mins 44 secs
+    | @ 398 days 7 hours 51 mins 44 secs
+    | @ 33 days 7 hours 51 mins 44 secs
+    | @ 32 days 7 hours 51 mins 44 secs
+(64 rows)
+
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |                Distance                
+----+----------------------------------------
+    | @ 11355 days 22 hours 23 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 43 secs
+    | @ 1452 days 20 hours 51 mins 43.6 secs
+    | @ 1452 days 20 hours 51 mins 43.5 secs
+    | @ 1452 days 20 hours 51 mins 43.4 secs
+    | @ 1492 days 14 hours 23 mins 45 secs
+    | @ 1492 days 11 hours 19 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 21 hours 51 mins 44 secs
+    | @ 232 days 2 hours 55 mins 35 secs
+    | @ 324 days 6 hours 9 mins 44 secs
+    | @ 324 days 10 hours 9 mins 43 secs
+    | @ 324 days 12 hours 9 mins 42 secs
+    | @ 324 days 11 hours 9 mins 41 secs
+    | @ 324 days 13 hours 9 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1452 days 23 hours 51 mins 44 secs
+    | @ 1303 days 51 mins 44 secs
+    | @ 1332 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1451 days 20 hours 51 mins 44 secs
+    | @ 1450 days 20 hours 51 mins 44 secs
+    | @ 1449 days 20 hours 51 mins 44 secs
+    | @ 1448 days 20 hours 51 mins 44 secs
+    | @ 1447 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 765900 days 20 hours 51 mins 44 secs
+    | @ 695406 days 20 hours 51 mins 44 secs
+    | @ 512785 days 20 hours 51 mins 44 secs
+    | @ 330164 days 20 hours 51 mins 44 secs
+    | @ 111018 days 20 hours 51 mins 44 secs
+    | @ 74494 days 20 hours 51 mins 44 secs
+    | @ 37970 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 35078 days 3 hours 8 mins 16 secs
+    | @ 1800 days 20 hours 51 mins 44 secs
+    | @ 1799 days 20 hours 51 mins 44 secs
+    | @ 1798 days 20 hours 51 mins 44 secs
+    | @ 1494 days 20 hours 51 mins 44 secs
+    | @ 1493 days 20 hours 51 mins 44 secs
+    | @ 1492 days 20 hours 51 mins 44 secs
+    | @ 1434 days 20 hours 51 mins 44 secs
+    | @ 1433 days 20 hours 51 mins 44 secs
+    | @ 1129 days 20 hours 51 mins 44 secs
+    | @ 1128 days 20 hours 51 mins 44 secs
+    | @ 398 days 20 hours 51 mins 44 secs
+    | @ 397 days 20 hours 51 mins 44 secs
+    | @ 32 days 20 hours 51 mins 44 secs
+    | @ 31 days 20 hours 51 mins 44 secs
+(64 rows)
+
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
index 22f80f2..24be476 100644
--- a/src/test/regress/sql/date.sql
+++ b/src/test/regress/sql/date.sql
@@ -346,3 +346,8 @@ select make_date(2013, 13, 1);
 select make_date(2013, 11, -1);
 select make_time(10, 55, 100.1);
 select make_time(24, 0, 2.1);
+
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
diff --git a/src/test/regress/sql/float4.sql b/src/test/regress/sql/float4.sql
index 46a9166..f97f80a 100644
--- a/src/test/regress/sql/float4.sql
+++ b/src/test/regress/sql/float4.sql
@@ -82,6 +82,9 @@ UPDATE FLOAT4_TBL
 
 SELECT '' AS five, * FROM FLOAT4_TBL;
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+
 -- test edge-case coercions to integer
 SELECT '32767.4'::float4::int2;
 SELECT '32767.6'::float4::int2;
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
index 6595fd2..9b50210 100644
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -125,6 +125,9 @@ SELECT ||/ float8 '27' AS three;
 
 SELECT '' AS five, f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
 
+-- distance
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
 
 SELECT '' AS five, * FROM FLOAT8_TBL;
 
diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql
index 7dbafb6..16dd5d8 100644
--- a/src/test/regress/sql/int2.sql
+++ b/src/test/regress/sql/int2.sql
@@ -84,6 +84,16 @@ SELECT '' AS five, i.f1, i.f1 / int2 '2' AS x FROM INT2_TBL i;
 
 SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
 SELECT ((-1::int2<<15)+1::int2)::text;
diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql
index f014cb2..cff32946 100644
--- a/src/test/regress/sql/int4.sql
+++ b/src/test/regress/sql/int4.sql
@@ -93,6 +93,16 @@ SELECT '' AS five, i.f1, i.f1 / int2 '2' AS x FROM INT4_TBL i;
 
 SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql
index e890452..d7f5bde 100644
--- a/src/test/regress/sql/int8.sql
+++ b/src/test/regress/sql/int8.sql
@@ -89,6 +89,11 @@ SELECT q1 + 42::int2 AS "8plus2", q1 - 42::int2 AS "8minus2", q1 * 42::int2 AS "
 -- int2 op int8
 SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 AS "2mul8", 246::int2 / q1 AS "2div8" FROM INT8_TBL;
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+
 SELECT q2, abs(q2) FROM INT8_TBL;
 SELECT min(q1), min(q2) FROM INT8_TBL;
 SELECT max(q1), max(q2) FROM INT8_TBL;
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index bc5537d..d51c866 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -59,6 +59,8 @@ SELECT '' AS fortyfive, r1.*, r2.*
    WHERE r1.f1 > r2.f1
    ORDER BY r1.f1, r2.f1;
 
+SELECT '' AS ten, f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql
index 37b9ecc..8428d59 100644
--- a/src/test/regress/sql/money.sql
+++ b/src/test/regress/sql/money.sql
@@ -25,6 +25,7 @@ SELECT m / 2::float8 FROM money_data;
 SELECT m * 2::float4 FROM money_data;
 SELECT 2::float4 * m FROM money_data;
 SELECT m / 2::float4 FROM money_data;
+SELECT m <-> '$123.45' FROM money_data;
 
 -- All true
 SELECT m = '$123.00' FROM money_data;
diff --git a/src/test/regress/sql/oid.sql b/src/test/regress/sql/oid.sql
index 4a09689..9f54f92 100644
--- a/src/test/regress/sql/oid.sql
+++ b/src/test/regress/sql/oid.sql
@@ -40,4 +40,6 @@ SELECT '' AS four, o.* FROM OID_TBL o WHERE o.f1 >= '1234';
 
 SELECT '' AS three, o.* FROM OID_TBL o WHERE o.f1 > '1234';
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 8544cbe..e6688df 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -818,7 +818,7 @@ WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND
     ao.amoprighttype = ap.amprocrighttype AND
     ap.amprocnum = 1 AND
     (pp.provolatile != po.provolatile OR
-     pp.proleakproof != po.proleakproof)
+     (NOT pp.proleakproof AND po.proleakproof))
 ORDER BY 1;
 
 
diff --git a/src/test/regress/sql/time.sql b/src/test/regress/sql/time.sql
index 99a1562..31f0330 100644
--- a/src/test/regress/sql/time.sql
+++ b/src/test/regress/sql/time.sql
@@ -40,3 +40,6 @@ SELECT f1 AS "Eight" FROM TIME_TBL WHERE f1 >= '00:00';
 -- where we do mixed-type arithmetic. - thomas 2000-12-02
 
 SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
+
+-- distance
+SELECT f1 AS "Ten", f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b7957cb..5d023dd 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -230,3 +230,11 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
 
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
+
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index c3bd46c..7f0525d 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -464,3 +464,11 @@ insert into tmptz values ('2017-01-18 00:00+00');
 explain (costs off)
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
-- 
2.7.4

0006-Remove-distance-operators-from-btree_gist-v05.patchtext/x-patch; name=0006-Remove-distance-operators-from-btree_gist-v05.patchDownload
From 229d8a167f9b4e6b31276ec70b46c2994a6c2f47 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 11 Jan 2019 15:51:29 +0300
Subject: [PATCH 6/7] Remove distance operators from btree_gist

---
 contrib/btree_gist/Makefile                 |    5 +-
 contrib/btree_gist/btree_cash.c             |   15 +-
 contrib/btree_gist/btree_date.c             |    7 +-
 contrib/btree_gist/btree_float4.c           |    9 +-
 contrib/btree_gist/btree_float8.c           |    9 +-
 contrib/btree_gist/btree_gist--1.2.sql      | 1570 --------------------------
 contrib/btree_gist/btree_gist--1.5--1.6.sql |   99 ++
 contrib/btree_gist/btree_gist--1.6.sql      | 1615 +++++++++++++++++++++++++++
 contrib/btree_gist/btree_gist.control       |    2 +-
 contrib/btree_gist/btree_int2.c             |   15 +-
 contrib/btree_gist/btree_int4.c             |   15 +-
 contrib/btree_gist/btree_int8.c             |   15 +-
 contrib/btree_gist/btree_interval.c         |    6 +-
 contrib/btree_gist/btree_oid.c              |   10 +-
 contrib/btree_gist/btree_time.c             |    6 +-
 contrib/btree_gist/btree_ts.c               |   38 +-
 doc/src/sgml/btree-gist.sgml                |   13 +
 17 files changed, 1743 insertions(+), 1706 deletions(-)
 delete mode 100644 contrib/btree_gist/btree_gist--1.2.sql
 create mode 100644 contrib/btree_gist/btree_gist--1.5--1.6.sql
 create mode 100644 contrib/btree_gist/btree_gist--1.6.sql

diff --git a/contrib/btree_gist/Makefile b/contrib/btree_gist/Makefile
index af65120..46ab241 100644
--- a/contrib/btree_gist/Makefile
+++ b/contrib/btree_gist/Makefile
@@ -11,8 +11,9 @@ OBJS =  btree_gist.o btree_utils_num.o btree_utils_var.o btree_int2.o \
 
 EXTENSION = btree_gist
 DATA = btree_gist--unpackaged--1.0.sql btree_gist--1.0--1.1.sql \
-       btree_gist--1.1--1.2.sql btree_gist--1.2.sql btree_gist--1.2--1.3.sql \
-       btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql
+       btree_gist--1.1--1.2.sql btree_gist--1.2--1.3.sql \
+       btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql \
+       btree_gist--1.5--1.6.sql btree_gist--1.6.sql
 PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes"
 
 REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \
diff --git a/contrib/btree_gist/btree_cash.c b/contrib/btree_gist/btree_cash.c
index 894d0a2..1b0e317 100644
--- a/contrib/btree_gist/btree_cash.c
+++ b/contrib/btree_gist/btree_cash.c
@@ -95,20 +95,7 @@ PG_FUNCTION_INFO_V1(cash_dist);
 Datum
 cash_dist(PG_FUNCTION_ARGS)
 {
-	Cash		a = PG_GETARG_CASH(0);
-	Cash		b = PG_GETARG_CASH(1);
-	Cash		r;
-	Cash		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("money out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_CASH(ra);
+	return cash_distance(fcinfo);
 }
 
 /**************************************************
diff --git a/contrib/btree_gist/btree_date.c b/contrib/btree_gist/btree_date.c
index 992ce57..f3f0fa1 100644
--- a/contrib/btree_gist/btree_date.c
+++ b/contrib/btree_gist/btree_date.c
@@ -113,12 +113,7 @@ PG_FUNCTION_INFO_V1(date_dist);
 Datum
 date_dist(PG_FUNCTION_ARGS)
 {
-	/* we assume the difference can't overflow */
-	Datum		diff = DirectFunctionCall2(date_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
+	return date_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index 6b20f44..0a9148d 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -93,14 +93,7 @@ PG_FUNCTION_INFO_V1(float4_dist);
 Datum
 float4_dist(PG_FUNCTION_ARGS)
 {
-	float4		a = PG_GETARG_FLOAT4(0);
-	float4		b = PG_GETARG_FLOAT4(1);
-	float4		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT4(Abs(r));
+	return float4dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index ee114cb..8b73b57 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -101,14 +101,7 @@ PG_FUNCTION_INFO_V1(float8_dist);
 Datum
 float8_dist(PG_FUNCTION_ARGS)
 {
-	float8		a = PG_GETARG_FLOAT8(0);
-	float8		b = PG_GETARG_FLOAT8(1);
-	float8		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT8(Abs(r));
+	return float8dist(fcinfo);
 }
 
 /**************************************************
diff --git a/contrib/btree_gist/btree_gist--1.2.sql b/contrib/btree_gist/btree_gist--1.2.sql
deleted file mode 100644
index 1efe753..0000000
--- a/contrib/btree_gist/btree_gist--1.2.sql
+++ /dev/null
@@ -1,1570 +0,0 @@
-/* contrib/btree_gist/btree_gist--1.2.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
-
-CREATE FUNCTION gbtreekey4_in(cstring)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey4_out(gbtreekey4)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey4 (
-	INTERNALLENGTH = 4,
-	INPUT  = gbtreekey4_in,
-	OUTPUT = gbtreekey4_out
-);
-
-CREATE FUNCTION gbtreekey8_in(cstring)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey8_out(gbtreekey8)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey8 (
-	INTERNALLENGTH = 8,
-	INPUT  = gbtreekey8_in,
-	OUTPUT = gbtreekey8_out
-);
-
-CREATE FUNCTION gbtreekey16_in(cstring)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey16_out(gbtreekey16)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey16 (
-	INTERNALLENGTH = 16,
-	INPUT  = gbtreekey16_in,
-	OUTPUT = gbtreekey16_out
-);
-
-CREATE FUNCTION gbtreekey32_in(cstring)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey32_out(gbtreekey32)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey32 (
-	INTERNALLENGTH = 32,
-	INPUT  = gbtreekey32_in,
-	OUTPUT = gbtreekey32_out
-);
-
-CREATE FUNCTION gbtreekey_var_in(cstring)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey_var (
-	INTERNALLENGTH = VARIABLE,
-	INPUT  = gbtreekey_var_in,
-	OUTPUT = gbtreekey_var_out,
-	STORAGE = EXTENDED
-);
-
---distance operators
-
-CREATE FUNCTION cash_dist(money, money)
-RETURNS money
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = money,
-	RIGHTARG = money,
-	PROCEDURE = cash_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION date_dist(date, date)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = date,
-	RIGHTARG = date,
-	PROCEDURE = date_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float4_dist(float4, float4)
-RETURNS float4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float4,
-	RIGHTARG = float4,
-	PROCEDURE = float4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float8_dist(float8, float8)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float8,
-	RIGHTARG = float8,
-	PROCEDURE = float8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int2_dist(int2, int2)
-RETURNS int2
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int2,
-	RIGHTARG = int2,
-	PROCEDURE = int2_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int4_dist(int4, int4)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int4,
-	RIGHTARG = int4,
-	PROCEDURE = int4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int8_dist(int8, int8)
-RETURNS int8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int8,
-	RIGHTARG = int8,
-	PROCEDURE = int8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION interval_dist(interval, interval)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = interval,
-	RIGHTARG = interval,
-	PROCEDURE = interval_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION oid_dist(oid, oid)
-RETURNS oid
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = oid,
-	RIGHTARG = oid,
-	PROCEDURE = oid_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION time_dist(time, time)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = time,
-	RIGHTARG = time,
-	PROCEDURE = time_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION ts_dist(timestamp, timestamp)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamp,
-	RIGHTARG = timestamp,
-	PROCEDURE = ts_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION tstz_dist(timestamptz, timestamptz)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamptz,
-	RIGHTARG = timestamptz,
-	PROCEDURE = tstz_dist,
-	COMMUTATOR = '<->'
-);
-
-
---
---
---
--- oid ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_oid_ops
-DEFAULT FOR TYPE oid USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
-	FUNCTION	2	gbt_oid_union (internal, internal),
-	FUNCTION	3	gbt_oid_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_oid_picksplit (internal, internal),
-	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
--- Add operators that are new in 9.1.  We do it like this, leaving them
--- "loose" in the operator family rather than bound into the opclass, because
--- that's the only state that can be reproduced during an upgrade from 9.0.
-ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
-	OPERATOR	6	<> (oid, oid) ,
-	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
-	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
-	-- Also add support function for index-only-scans, added in 9.5.
-	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
-
-
---
---
---
--- int2 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_union(internal, internal)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int2_ops
-DEFAULT FOR TYPE int2 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
-	FUNCTION	2	gbt_int2_union (internal, internal),
-	FUNCTION	3	gbt_int2_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int2_picksplit (internal, internal),
-	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
-	STORAGE		gbtreekey4;
-
-ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
-	OPERATOR	6	<> (int2, int2) ,
-	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
-	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
-
---
---
---
--- int4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int4_ops
-DEFAULT FOR TYPE int4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
-	FUNCTION	2	gbt_int4_union (internal, internal),
-	FUNCTION	3	gbt_int4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int4_picksplit (internal, internal),
-	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
-	OPERATOR	6	<> (int4, int4) ,
-	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
-	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
-
-
---
---
---
--- int8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int8_ops
-DEFAULT FOR TYPE int8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
-	FUNCTION	2	gbt_int8_union (internal, internal),
-	FUNCTION	3	gbt_int8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int8_picksplit (internal, internal),
-	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
-	OPERATOR	6	<> (int8, int8) ,
-	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
-	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
-
---
---
---
--- float4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float4_ops
-DEFAULT FOR TYPE float4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
-	FUNCTION	2	gbt_float4_union (internal, internal),
-	FUNCTION	3	gbt_float4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float4_picksplit (internal, internal),
-	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
-	OPERATOR	6	<> (float4, float4) ,
-	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
-	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
-
---
---
---
--- float8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float8_ops
-DEFAULT FOR TYPE float8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
-	FUNCTION	2	gbt_float8_union (internal, internal),
-	FUNCTION	3	gbt_float8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float8_picksplit (internal, internal),
-	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
-	OPERATOR	6	<> (float8, float8) ,
-	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
-	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
-
---
---
---
--- timestamp ops
---
---
---
-
-CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamp_ops
-DEFAULT FOR TYPE timestamp USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_ts_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
-	OPERATOR	6	<> (timestamp, timestamp) ,
-	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
-	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamptz_ops
-DEFAULT FOR TYPE timestamptz USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_tstz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
-	OPERATOR	6	<> (timestamptz, timestamptz) ,
-	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
-	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
-
---
---
---
--- time ops
---
---
---
-
-CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_time_ops
-DEFAULT FOR TYPE time USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_time_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
-	OPERATOR	6	<> (time, time) ,
-	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
-	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
-
-
-CREATE OPERATOR CLASS gist_timetz_ops
-DEFAULT FOR TYPE timetz USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_timetz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
-	OPERATOR	6	<> (timetz, timetz) ;
-	-- no 'fetch' function, as the compress function is lossy.
-
-
---
---
---
--- date ops
---
---
---
-
-CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_date_ops
-DEFAULT FOR TYPE date USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
-	FUNCTION	2	gbt_date_union (internal, internal),
-	FUNCTION	3	gbt_date_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_date_picksplit (internal, internal),
-	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
-	OPERATOR	6	<> (date, date) ,
-	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
-	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
-
-
---
---
---
--- interval ops
---
---
---
-
-CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_union(internal, internal)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_interval_ops
-DEFAULT FOR TYPE interval USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
-	FUNCTION	2	gbt_intv_union (internal, internal),
-	FUNCTION	3	gbt_intv_compress (internal),
-	FUNCTION	4	gbt_intv_decompress (internal),
-	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_intv_picksplit (internal, internal),
-	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
-	STORAGE		gbtreekey32;
-
-ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
-	OPERATOR	6	<> (interval, interval) ,
-	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
-	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
-
-
---
---
---
--- cash ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cash_ops
-DEFAULT FOR TYPE money USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
-	FUNCTION	2	gbt_cash_union (internal, internal),
-	FUNCTION	3	gbt_cash_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_cash_picksplit (internal, internal),
-	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
-	OPERATOR	6	<> (money, money) ,
-	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
-	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
-	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
-
-
---
---
---
--- macaddr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_macaddr_ops
-DEFAULT FOR TYPE macaddr USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
-	FUNCTION	2	gbt_macad_union (internal, internal),
-	FUNCTION	3	gbt_macad_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_macad_picksplit (internal, internal),
-	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
-	OPERATOR	6	<> (macaddr, macaddr) ,
-	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
-
-
---
---
---
--- text/ bpchar ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_text_ops
-DEFAULT FOR TYPE text USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_text_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
-	OPERATOR	6	<> (text, text) ,
-	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
-
-
----- Create the operator class
-CREATE OPERATOR CLASS gist_bpchar_ops
-DEFAULT FOR TYPE bpchar USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_bpchar_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
-	OPERATOR	6	<> (bpchar, bpchar) ,
-	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
-
---
---
--- bytea ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bytea_ops
-DEFAULT FOR TYPE bytea USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
-	FUNCTION	2	gbt_bytea_union (internal, internal),
-	FUNCTION	3	gbt_bytea_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
-	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
-	OPERATOR	6	<> (bytea, bytea) ,
-	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
-
-
---
---
---
--- numeric ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_numeric_ops
-DEFAULT FOR TYPE numeric USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
-	FUNCTION	2	gbt_numeric_union (internal, internal),
-	FUNCTION	3	gbt_numeric_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
-	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
-	OPERATOR	6	<> (numeric, numeric) ,
-	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
-
-
---
---
--- bit ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bit_ops
-DEFAULT FOR TYPE bit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
-	OPERATOR	6	<> (bit, bit) ,
-	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
-
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_vbit_ops
-DEFAULT FOR TYPE varbit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
-	OPERATOR	6	<> (varbit, varbit) ,
-	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
-
-
---
---
---
--- inet/cidr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_inet_ops
-DEFAULT FOR TYPE inet USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
-	OPERATOR	6	<>  (inet, inet) ;
-	-- no fetch support, the compress function is lossy
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cidr_ops
-DEFAULT FOR TYPE cidr USING gist
-AS
-	OPERATOR	1	<  (inet, inet)  ,
-	OPERATOR	2	<= (inet, inet)  ,
-	OPERATOR	3	=  (inet, inet)  ,
-	OPERATOR	4	>= (inet, inet)  ,
-	OPERATOR	5	>  (inet, inet)  ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
-	OPERATOR	6	<> (inet, inet) ;
-	-- no fetch support, the compress function is lossy
diff --git a/contrib/btree_gist/btree_gist--1.5--1.6.sql b/contrib/btree_gist/btree_gist--1.5--1.6.sql
new file mode 100644
index 0000000..ef4424e
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.5--1.6.sql
@@ -0,0 +1,99 @@
+/* contrib/btree_gist/btree_gist--1.5--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.6'" to load this file. \quit
+
+-- drop btree_gist distance operators from opfamilies
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist DROP OPERATOR 15 (int2, int2);
+ALTER OPERATOR FAMILY gist_int4_ops USING gist DROP OPERATOR 15 (int4, int4);
+ALTER OPERATOR FAMILY gist_int8_ops USING gist DROP OPERATOR 15 (int8, int8);
+ALTER OPERATOR FAMILY gist_float4_ops USING gist DROP OPERATOR 15 (float4, float4);
+ALTER OPERATOR FAMILY gist_float8_ops USING gist DROP OPERATOR 15 (float8, float8);
+ALTER OPERATOR FAMILY gist_oid_ops USING gist DROP OPERATOR 15 (oid, oid);
+ALTER OPERATOR FAMILY gist_cash_ops USING gist DROP OPERATOR 15 (money, money);
+ALTER OPERATOR FAMILY gist_date_ops USING gist DROP OPERATOR 15 (date, date);
+ALTER OPERATOR FAMILY gist_time_ops USING gist DROP OPERATOR 15 (time, time);
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist DROP OPERATOR 15 (timestamp, timestamp);
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist DROP OPERATOR 15 (timestamptz, timestamptz);
+ALTER OPERATOR FAMILY gist_interval_ops USING gist DROP OPERATOR 15 (interval, interval);
+
+-- add pg_catalog distance operators to opfamilies
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD OPERATOR 15 <-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD OPERATOR 15 <-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD OPERATOR 15 <-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD OPERATOR 15 <-> (float4, float4) FOR ORDER BY pg_catalog.float_ops;
+ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD OPERATOR 15 <-> (float8, float8) FOR ORDER BY pg_catalog.float_ops;
+ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD OPERATOR 15 <-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops;
+ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD OPERATOR 15 <-> (money, money) FOR ORDER BY pg_catalog.money_ops;
+ALTER OPERATOR FAMILY gist_date_ops USING gist ADD OPERATOR 15 <-> (date, date) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_time_ops USING gist ADD OPERATOR 15 <-> (time, time) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD OPERATOR 15 <-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD OPERATOR 15 <-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD OPERATOR 15 <-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops;
+
+-- disable implicit pg_catalog search
+
+DO
+$$
+BEGIN
+	EXECUTE 'SET LOCAL search_path TO ' || current_schema() || ', pg_catalog';
+END
+$$;
+
+-- drop distance operators
+
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int2, int2);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int4, int4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int8, int8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float4, float4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float8, float8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (oid, oid);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (money, money);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (date, date);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (time, time);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (interval, interval);
+
+DROP OPERATOR <-> (int2, int2);
+DROP OPERATOR <-> (int4, int4);
+DROP OPERATOR <-> (int8, int8);
+DROP OPERATOR <-> (float4, float4);
+DROP OPERATOR <-> (float8, float8);
+DROP OPERATOR <-> (oid, oid);
+DROP OPERATOR <-> (money, money);
+DROP OPERATOR <-> (date, date);
+DROP OPERATOR <-> (time, time);
+DROP OPERATOR <-> (timestamp, timestamp);
+DROP OPERATOR <-> (timestamptz, timestamptz);
+DROP OPERATOR <-> (interval, interval);
+
+-- drop distance functions
+
+ALTER EXTENSION btree_gist DROP FUNCTION int2_dist(int2, int2);
+ALTER EXTENSION btree_gist DROP FUNCTION int4_dist(int4, int4);
+ALTER EXTENSION btree_gist DROP FUNCTION int8_dist(int8, int8);
+ALTER EXTENSION btree_gist DROP FUNCTION float4_dist(float4, float4);
+ALTER EXTENSION btree_gist DROP FUNCTION float8_dist(float8, float8);
+ALTER EXTENSION btree_gist DROP FUNCTION oid_dist(oid, oid);
+ALTER EXTENSION btree_gist DROP FUNCTION cash_dist(money, money);
+ALTER EXTENSION btree_gist DROP FUNCTION date_dist(date, date);
+ALTER EXTENSION btree_gist DROP FUNCTION time_dist(time, time);
+ALTER EXTENSION btree_gist DROP FUNCTION ts_dist(timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP FUNCTION interval_dist(interval, interval);
+
+DROP FUNCTION int2_dist(int2, int2);
+DROP FUNCTION int4_dist(int4, int4);
+DROP FUNCTION int8_dist(int8, int8);
+DROP FUNCTION float4_dist(float4, float4);
+DROP FUNCTION float8_dist(float8, float8);
+DROP FUNCTION oid_dist(oid, oid);
+DROP FUNCTION cash_dist(money, money);
+DROP FUNCTION date_dist(date, date);
+DROP FUNCTION time_dist(time, time);
+DROP FUNCTION ts_dist(timestamp, timestamp);
+DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+DROP FUNCTION interval_dist(interval, interval);
diff --git a/contrib/btree_gist/btree_gist--1.6.sql b/contrib/btree_gist/btree_gist--1.6.sql
new file mode 100644
index 0000000..8ff8eb5
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.6.sql
@@ -0,0 +1,1615 @@
+/* contrib/btree_gist/btree_gist--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
+
+CREATE FUNCTION gbtreekey4_in(cstring)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey4_out(gbtreekey4)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey4 (
+	INTERNALLENGTH = 4,
+	INPUT  = gbtreekey4_in,
+	OUTPUT = gbtreekey4_out
+);
+
+CREATE FUNCTION gbtreekey8_in(cstring)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey8_out(gbtreekey8)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey8 (
+	INTERNALLENGTH = 8,
+	INPUT  = gbtreekey8_in,
+	OUTPUT = gbtreekey8_out
+);
+
+CREATE FUNCTION gbtreekey16_in(cstring)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey16_out(gbtreekey16)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey16 (
+	INTERNALLENGTH = 16,
+	INPUT  = gbtreekey16_in,
+	OUTPUT = gbtreekey16_out
+);
+
+CREATE FUNCTION gbtreekey32_in(cstring)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey32_out(gbtreekey32)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey32 (
+	INTERNALLENGTH = 32,
+	INPUT  = gbtreekey32_in,
+	OUTPUT = gbtreekey32_out
+);
+
+CREATE FUNCTION gbtreekey_var_in(cstring)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey_var (
+	INTERNALLENGTH = VARIABLE,
+	INPUT  = gbtreekey_var_in,
+	OUTPUT = gbtreekey_var_out,
+	STORAGE = EXTENDED
+);
+
+
+--
+--
+--
+-- oid ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_oid_ops
+DEFAULT FOR TYPE oid USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
+	FUNCTION	2	gbt_oid_union (internal, internal),
+	FUNCTION	3	gbt_oid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_oid_picksplit (internal, internal),
+	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+-- Add operators that are new in 9.1.  We do it like this, leaving them
+-- "loose" in the operator family rather than bound into the opclass, because
+-- that's the only state that can be reproduced during an upgrade from 9.0.
+ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
+	OPERATOR	6	<> (oid, oid) ,
+	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
+	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
+	-- Also add support function for index-only-scans, added in 9.5.
+	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
+
+
+--
+--
+--
+-- int2 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_union(internal, internal)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int2_ops
+DEFAULT FOR TYPE int2 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
+	FUNCTION	2	gbt_int2_union (internal, internal),
+	FUNCTION	3	gbt_int2_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int2_picksplit (internal, internal),
+	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
+	STORAGE		gbtreekey4;
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
+	OPERATOR	6	<> (int2, int2) ,
+	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
+	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
+
+--
+--
+--
+-- int4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int4_ops
+DEFAULT FOR TYPE int4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
+	FUNCTION	2	gbt_int4_union (internal, internal),
+	FUNCTION	3	gbt_int4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int4_picksplit (internal, internal),
+	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
+	OPERATOR	6	<> (int4, int4) ,
+	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
+	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
+
+
+--
+--
+--
+-- int8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int8_ops
+DEFAULT FOR TYPE int8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
+	FUNCTION	2	gbt_int8_union (internal, internal),
+	FUNCTION	3	gbt_int8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int8_picksplit (internal, internal),
+	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
+	OPERATOR	6	<> (int8, int8) ,
+	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
+	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
+
+--
+--
+--
+-- float4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float4_ops
+DEFAULT FOR TYPE float4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
+	FUNCTION	2	gbt_float4_union (internal, internal),
+	FUNCTION	3	gbt_float4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float4_picksplit (internal, internal),
+	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
+	OPERATOR	6	<> (float4, float4) ,
+	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
+	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
+
+--
+--
+--
+-- float8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float8_ops
+DEFAULT FOR TYPE float8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
+	FUNCTION	2	gbt_float8_union (internal, internal),
+	FUNCTION	3	gbt_float8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float8_picksplit (internal, internal),
+	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
+	OPERATOR	6	<> (float8, float8) ,
+	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
+	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
+
+--
+--
+--
+-- timestamp ops
+--
+--
+--
+
+CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamp_ops
+DEFAULT FOR TYPE timestamp USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_ts_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
+	OPERATOR	6	<> (timestamp, timestamp) ,
+	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
+	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamptz_ops
+DEFAULT FOR TYPE timestamptz USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_tstz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
+	OPERATOR	6	<> (timestamptz, timestamptz) ,
+	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
+	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
+
+--
+--
+--
+-- time ops
+--
+--
+--
+
+CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_time_ops
+DEFAULT FOR TYPE time USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_time_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
+	OPERATOR	6	<> (time, time) ,
+	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
+	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
+
+
+CREATE OPERATOR CLASS gist_timetz_ops
+DEFAULT FOR TYPE timetz USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_timetz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
+	OPERATOR	6	<> (timetz, timetz) ;
+	-- no 'fetch' function, as the compress function is lossy.
+
+
+--
+--
+--
+-- date ops
+--
+--
+--
+
+CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_date_ops
+DEFAULT FOR TYPE date USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
+	FUNCTION	2	gbt_date_union (internal, internal),
+	FUNCTION	3	gbt_date_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_date_picksplit (internal, internal),
+	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
+	OPERATOR	6	<> (date, date) ,
+	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
+	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
+
+
+--
+--
+--
+-- interval ops
+--
+--
+--
+
+CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_interval_ops
+DEFAULT FOR TYPE interval USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
+	FUNCTION	2	gbt_intv_union (internal, internal),
+	FUNCTION	3	gbt_intv_compress (internal),
+	FUNCTION	4	gbt_intv_decompress (internal),
+	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_intv_picksplit (internal, internal),
+	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
+	OPERATOR	6	<> (interval, interval) ,
+	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
+	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
+
+
+--
+--
+--
+-- cash ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cash_ops
+DEFAULT FOR TYPE money USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
+	FUNCTION	2	gbt_cash_union (internal, internal),
+	FUNCTION	3	gbt_cash_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_cash_picksplit (internal, internal),
+	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
+	OPERATOR	6	<> (money, money) ,
+	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
+	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
+	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
+
+
+--
+--
+--
+-- macaddr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_macaddr_ops
+DEFAULT FOR TYPE macaddr USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
+	FUNCTION	2	gbt_macad_union (internal, internal),
+	FUNCTION	3	gbt_macad_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_macad_picksplit (internal, internal),
+	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
+	OPERATOR	6	<> (macaddr, macaddr) ,
+	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
+
+
+--
+--
+--
+-- text/ bpchar ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_text_ops
+DEFAULT FOR TYPE text USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_text_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
+	OPERATOR	6	<> (text, text) ,
+	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
+
+
+---- Create the operator class
+CREATE OPERATOR CLASS gist_bpchar_ops
+DEFAULT FOR TYPE bpchar USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_bpchar_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
+	OPERATOR	6	<> (bpchar, bpchar) ,
+	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
+
+--
+--
+-- bytea ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bytea_ops
+DEFAULT FOR TYPE bytea USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
+	FUNCTION	2	gbt_bytea_union (internal, internal),
+	FUNCTION	3	gbt_bytea_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
+	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
+	OPERATOR	6	<> (bytea, bytea) ,
+	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- numeric ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_numeric_ops
+DEFAULT FOR TYPE numeric USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
+	FUNCTION	2	gbt_numeric_union (internal, internal),
+	FUNCTION	3	gbt_numeric_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
+	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
+	OPERATOR	6	<> (numeric, numeric) ,
+	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
+
+
+--
+--
+-- bit ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bit_ops
+DEFAULT FOR TYPE bit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
+	OPERATOR	6	<> (bit, bit) ,
+	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
+
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_vbit_ops
+DEFAULT FOR TYPE varbit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
+	OPERATOR	6	<> (varbit, varbit) ,
+	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- inet/cidr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_inet_ops
+DEFAULT FOR TYPE inet USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
+	OPERATOR	6	<>  (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cidr_ops
+DEFAULT FOR TYPE cidr USING gist
+AS
+	OPERATOR	1	<  (inet, inet)  ,
+	OPERATOR	2	<= (inet, inet)  ,
+	OPERATOR	3	=  (inet, inet)  ,
+	OPERATOR	4	>= (inet, inet)  ,
+	OPERATOR	5	>  (inet, inet)  ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
+	OPERATOR	6	<> (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+--
+--
+--
+-- uuid ops
+--
+--
+---- define the GiST support methods
+CREATE FUNCTION gbt_uuid_consistent(internal,uuid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_uuid_ops
+DEFAULT FOR TYPE uuid USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_uuid_consistent (internal, uuid, int2, oid, internal),
+	FUNCTION	2	gbt_uuid_union (internal, internal),
+	FUNCTION	3	gbt_uuid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_uuid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_uuid_picksplit (internal, internal),
+	FUNCTION	7	gbt_uuid_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+-- These are "loose" in the opfamily for consistency with the rest of btree_gist
+ALTER OPERATOR FAMILY gist_uuid_ops USING gist ADD
+	OPERATOR	6	<>  (uuid, uuid) ,
+	FUNCTION	9 (uuid, uuid) gbt_uuid_fetch (internal) ;
+
+
+-- Add support for indexing macaddr8 columns
+
+-- define the GiST support methods
+CREATE FUNCTION gbt_macad8_consistent(internal,macaddr8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_macaddr8_ops
+DEFAULT FOR TYPE macaddr8 USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_macad8_consistent (internal, macaddr8, int2, oid, internal),
+	FUNCTION	2	gbt_macad8_union (internal, internal),
+	FUNCTION	3	gbt_macad8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_macad8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_macad8_picksplit (internal, internal),
+	FUNCTION	7	gbt_macad8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_macaddr8_ops USING gist ADD
+	OPERATOR	6	<> (macaddr8, macaddr8) ,
+	FUNCTION	9 (macaddr8, macaddr8) gbt_macad8_fetch (internal);
+
+--
+--
+--
+-- enum ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_enum_consistent(internal,anyenum,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_enum_ops
+DEFAULT FOR TYPE anyenum USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_enum_consistent (internal, anyenum, int2, oid, internal),
+	FUNCTION	2	gbt_enum_union (internal, internal),
+	FUNCTION	3	gbt_enum_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_enum_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_enum_picksplit (internal, internal),
+	FUNCTION	7	gbt_enum_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_enum_ops USING gist ADD
+	OPERATOR	6	<> (anyenum, anyenum) ,
+	FUNCTION	9 (anyenum, anyenum) gbt_enum_fetch (internal) ;
diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control
index 81c8509..9ced3bc 100644
--- a/contrib/btree_gist/btree_gist.control
+++ b/contrib/btree_gist/btree_gist.control
@@ -1,5 +1,5 @@
 # btree_gist extension
 comment = 'support for indexing common datatypes in GiST'
-default_version = '1.5'
+default_version = '1.6'
 module_pathname = '$libdir/btree_gist'
 relocatable = true
diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c
index 7674e2d..2afc343 100644
--- a/contrib/btree_gist/btree_int2.c
+++ b/contrib/btree_gist/btree_int2.c
@@ -94,20 +94,7 @@ PG_FUNCTION_INFO_V1(int2_dist);
 Datum
 int2_dist(PG_FUNCTION_ARGS)
 {
-	int16		a = PG_GETARG_INT16(0);
-	int16		b = PG_GETARG_INT16(1);
-	int16		r;
-	int16		ra;
-
-	if (pg_sub_s16_overflow(a, b, &r) ||
-		r == PG_INT16_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("smallint out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT16(ra);
+	return int2dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c
index 80005ab..2361ce7 100644
--- a/contrib/btree_gist/btree_int4.c
+++ b/contrib/btree_gist/btree_int4.c
@@ -95,20 +95,7 @@ PG_FUNCTION_INFO_V1(int4_dist);
 Datum
 int4_dist(PG_FUNCTION_ARGS)
 {
-	int32		a = PG_GETARG_INT32(0);
-	int32		b = PG_GETARG_INT32(1);
-	int32		r;
-	int32		ra;
-
-	if (pg_sub_s32_overflow(a, b, &r) ||
-		r == PG_INT32_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("integer out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT32(ra);
+	return int4dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c
index b0fd3e1..182d7c4 100644
--- a/contrib/btree_gist/btree_int8.c
+++ b/contrib/btree_gist/btree_int8.c
@@ -95,20 +95,7 @@ PG_FUNCTION_INFO_V1(int8_dist);
 Datum
 int8_dist(PG_FUNCTION_ARGS)
 {
-	int64		a = PG_GETARG_INT64(0);
-	int64		b = PG_GETARG_INT64(1);
-	int64		r;
-	int64		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("bigint out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT64(ra);
+	return int8dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_interval.c b/contrib/btree_gist/btree_interval.c
index 3a527a7..c68d48e 100644
--- a/contrib/btree_gist/btree_interval.c
+++ b/contrib/btree_gist/btree_interval.c
@@ -128,11 +128,7 @@ PG_FUNCTION_INFO_V1(interval_dist);
 Datum
 interval_dist(PG_FUNCTION_ARGS)
 {
-	Datum		diff = DirectFunctionCall2(interval_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+	return interval_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_oid.c b/contrib/btree_gist/btree_oid.c
index 00e7019..c702d51 100644
--- a/contrib/btree_gist/btree_oid.c
+++ b/contrib/btree_gist/btree_oid.c
@@ -100,15 +100,7 @@ PG_FUNCTION_INFO_V1(oid_dist);
 Datum
 oid_dist(PG_FUNCTION_ARGS)
 {
-	Oid			a = PG_GETARG_OID(0);
-	Oid			b = PG_GETARG_OID(1);
-	Oid			res;
-
-	if (a < b)
-		res = b - a;
-	else
-		res = a - b;
-	PG_RETURN_OID(res);
+	return oiddist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_time.c b/contrib/btree_gist/btree_time.c
index 90cf655..cfafd02 100644
--- a/contrib/btree_gist/btree_time.c
+++ b/contrib/btree_gist/btree_time.c
@@ -141,11 +141,7 @@ PG_FUNCTION_INFO_V1(time_dist);
 Datum
 time_dist(PG_FUNCTION_ARGS)
 {
-	Datum		diff = DirectFunctionCall2(time_mi_time,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+	return time_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c
index 49d1849..9e62f7d 100644
--- a/contrib/btree_gist/btree_ts.c
+++ b/contrib/btree_gist/btree_ts.c
@@ -146,48 +146,14 @@ PG_FUNCTION_INFO_V1(ts_dist);
 Datum
 ts_dist(PG_FUNCTION_ARGS)
 {
-	Timestamp	a = PG_GETARG_TIMESTAMP(0);
-	Timestamp	b = PG_GETARG_TIMESTAMP(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-	else
-		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-												  PG_GETARG_DATUM(0),
-												  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
+	return timestamp_distance(fcinfo);
 }
 
 PG_FUNCTION_INFO_V1(tstz_dist);
 Datum
 tstz_dist(PG_FUNCTION_ARGS)
 {
-	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
-	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-
-	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-											  PG_GETARG_DATUM(0),
-											  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
+	return timestamptz_distance(fcinfo);
 }
 
 
diff --git a/doc/src/sgml/btree-gist.sgml b/doc/src/sgml/btree-gist.sgml
index 774442f..6eb4a18 100644
--- a/doc/src/sgml/btree-gist.sgml
+++ b/doc/src/sgml/btree-gist.sgml
@@ -96,6 +96,19 @@ INSERT 0 1
  </sect2>
 
  <sect2>
+  <title>Upgrade notes for version 1.6</title>
+
+  <para>
+   In version 1.6 <literal>btree_gist</literal> switched to using in-core
+   distance operators, and its own implementations were removed.  References to
+   these operators in <literal>btree_gist</literal> opclasses will be updated
+   automatically during the extension upgrade, but if the user has created
+   objects referencing these operators or functions, then these objects must be
+   dropped manually before updating the extension.
+  </para>
+ </sect2>
+
+ <sect2>
   <title>Authors</title>
 
   <para>
-- 
2.7.4

0007-Add-regression-tests-for-kNN-btree-v05.patchtext/x-patch; name=0007-Add-regression-tests-for-kNN-btree-v05.patchDownload
From a4c3367e31d5b9b0a660c6131d9f61ca001a0138 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 11 Jan 2019 15:51:29 +0300
Subject: [PATCH 7/7] Add regression tests for kNN btree

---
 src/test/regress/expected/btree_index.out | 779 ++++++++++++++++++++++++++++++
 src/test/regress/sql/btree_index.sql      | 232 +++++++++
 2 files changed, 1011 insertions(+)

diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index 0bd48dc..a8ed9da 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -179,3 +179,782 @@ select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass;
  {vacuum_cleanup_index_scale_factor=70.0}
 (1 row)
 
+---
+--- Test B-tree distance ordering
+---
+SET enable_bitmapscan = OFF;
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+          QUERY PLAN          
+------------------------------
+ Sort
+   Sort Key: ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+                   QUERY PLAN                   
+------------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((random <-> 1))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)))
+   Order By: (random <-> 4000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+                                                                                               QUERY PLAN                                                                                               
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)) AND (random = ANY ('{1809552,1919087,2321799,2648497,3000193,3013326,4157193,4488889,5257716,5593978,NULL}'::integer[])))
+   Order By: (random <-> 3000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+ seqno | random  
+-------+---------
+  5380 | 3000193
+  6262 | 3013326
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+  6448 | 4157193
+   210 | 1809552
+  4408 | 4488889
+  6320 | 5257716
+  1836 | 5593978
+(10 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+-- test parallel KNN scan
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+RESET enable_indexscan;
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, 1000000) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+EXPLAIN (COSTS OFF)
+SELECT i FROM bt_knn_test WHERE i > 8000000;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Gather
+   Workers Planned: 4
+   ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+         Index Cond: (i > 8000000)
+(4 rows)
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> 4000003) AS n, i * 10 AS i
+	FROM generate_series(1, 1000000) i;
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (t2.n = t1.n)
+   Join Filter: (t1.i <> t2.i)
+   CTE bt_knn_test1
+     ->  WindowAgg
+           ->  Gather Merge
+                 Workers Planned: 4
+                 ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                       Order By: (i <-> 4000003)
+   ->  Seq Scan on bt_knn_test2 t2
+   ->  Hash
+         ->  CTE Scan on bt_knn_test1 t1
+(12 rows)
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+DROP TABLE bt_knn_test;
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, 100000) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (t2.n = t1.n)
+   Join Filter: (t1.i <> t2.i)
+   CTE t1
+     ->  WindowAgg
+           ->  Gather Merge
+                 Workers Planned: 4
+                 ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                       Index Cond: (i = ANY ('{3,4,7,8,2}'::integer[]))
+                       Order By: (i <-> 4)
+   CTE t2
+     ->  Nested Loop
+           ->  Function Scan on generate_series i
+           ->  Function Scan on generate_series j
+   ->  CTE Scan on t2
+   ->  Hash
+         ->  CTE Scan on t1
+(17 rows)
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+ROLLBACK;
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: (ROW(thousand, tenthous) >= ROW(997, 5000))
+   Order By: (thousand <-> 998)
+(3 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+        1 |     9001
+        1 |     8001
+        1 |     7001
+        1 |     6001
+        1 |     5001
+        1 |     4001
+        1 |     3001
+        1 |     2001
+        1 |     1001
+        1 |        1
+        0 |     9000
+        0 |     8000
+        0 |     7000
+        0 |     6000
+        0 |     5000
+        0 |     4000
+        0 |     3000
+        0 |     2000
+        0 |     1000
+        0 |        0
+          |        1
+          |        2
+          |        3
+(33 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: ((thousand > 100) AND (thousand < 800) AND (thousand = ANY ('{0,123,234,345,456,678,901,NULL}'::smallint[])))
+   Order By: (thousand <-> '300'::bigint)
+(3 rows)
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+ thousand | tenthous 
+----------+----------
+      345 |      345
+      345 |     1345
+      345 |     2345
+      345 |     3345
+      345 |     4345
+      345 |     5345
+      345 |     6345
+      345 |     7345
+      345 |     8345
+      345 |     9345
+      234 |      234
+      234 |     1234
+      234 |     2234
+      234 |     3234
+      234 |     4234
+      234 |     5234
+      234 |     6234
+      234 |     7234
+      234 |     8234
+      234 |     9234
+      456 |      456
+      456 |     1456
+      456 |     2456
+      456 |     3456
+      456 |     4456
+      456 |     5456
+      456 |     6456
+      456 |     7456
+      456 |     8456
+      456 |     9456
+      123 |      123
+      123 |     1123
+      123 |     2123
+      123 |     3123
+      123 |     4123
+      123 |     5123
+      123 |     6123
+      123 |     7123
+      123 |     8123
+      123 |     9123
+      678 |      678
+      678 |     1678
+      678 |     2678
+      678 |     3678
+      678 |     4678
+      678 |     5678
+      678 |     6678
+      678 |     7678
+      678 |     8678
+      678 |     9678
+(50 rows)
+
+DROP INDEX tenk3_idx;
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+        1 |        1
+        1 |     1001
+        1 |     2001
+        1 |     3001
+        1 |     4001
+        1 |     5001
+        1 |     6001
+        1 |     7001
+        1 |     8001
+        1 |     9001
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+        0 |        0
+        0 |     1000
+        0 |     2000
+        0 |     3000
+        0 |     4000
+        0 |     5000
+        0 |     6000
+        0 |     7000
+        0 |     8000
+        0 |     9000
+          |        3
+          |        2
+          |        1
+(33 rows)
+
+DROP INDEX tenk3_idx;
+DROP TABLE tenk3;
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |     ?column?      
+--------------------------+-------------------
+ Wed May 03 00:00:00 2017 | @ 2 days
+ Wed May 03 01:00:00 2017 | @ 2 days 1 hour
+ Wed May 03 02:00:00 2017 | @ 2 days 2 hours
+ Wed May 03 03:00:00 2017 | @ 2 days 3 hours
+ Wed May 03 04:00:00 2017 | @ 2 days 4 hours
+ Wed May 03 05:00:00 2017 | @ 2 days 5 hours
+ Wed May 03 06:00:00 2017 | @ 2 days 6 hours
+ Wed May 03 07:00:00 2017 | @ 2 days 7 hours
+ Wed May 03 08:00:00 2017 | @ 2 days 8 hours
+ Wed May 03 09:00:00 2017 | @ 2 days 9 hours
+ Wed May 03 10:00:00 2017 | @ 2 days 10 hours
+ Wed May 03 11:00:00 2017 | @ 2 days 11 hours
+ Wed May 03 12:00:00 2017 | @ 2 days 12 hours
+ Wed May 03 13:00:00 2017 | @ 2 days 13 hours
+ Wed May 03 14:00:00 2017 | @ 2 days 14 hours
+ Wed May 03 15:00:00 2017 | @ 2 days 15 hours
+ Wed May 03 16:00:00 2017 | @ 2 days 16 hours
+ Wed May 03 17:00:00 2017 | @ 2 days 17 hours
+ Wed May 03 18:00:00 2017 | @ 2 days 18 hours
+ Wed May 03 19:00:00 2017 | @ 2 days 19 hours
+(20 rows)
+
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |  ?column?  
+--------------------------+------------
+ Mon Jan 01 00:00:00 2018 | @ 0
+ Mon Jan 01 01:00:00 2018 | @ 1 hour
+ Sun Dec 31 23:00:00 2017 | @ 1 hour
+ Mon Jan 01 02:00:00 2018 | @ 2 hours
+ Sun Dec 31 22:00:00 2017 | @ 2 hours
+ Mon Jan 01 03:00:00 2018 | @ 3 hours
+ Sun Dec 31 21:00:00 2017 | @ 3 hours
+ Mon Jan 01 04:00:00 2018 | @ 4 hours
+ Sun Dec 31 20:00:00 2017 | @ 4 hours
+ Mon Jan 01 05:00:00 2018 | @ 5 hours
+ Sun Dec 31 19:00:00 2017 | @ 5 hours
+ Mon Jan 01 06:00:00 2018 | @ 6 hours
+ Sun Dec 31 18:00:00 2017 | @ 6 hours
+ Mon Jan 01 07:00:00 2018 | @ 7 hours
+ Sun Dec 31 17:00:00 2017 | @ 7 hours
+ Mon Jan 01 08:00:00 2018 | @ 8 hours
+ Sun Dec 31 16:00:00 2017 | @ 8 hours
+ Mon Jan 01 09:00:00 2018 | @ 9 hours
+ Sun Dec 31 15:00:00 2017 | @ 9 hours
+ Mon Jan 01 10:00:00 2018 | @ 10 hours
+(20 rows)
+
+DROP TABLE knn_btree_ts;
+RESET enable_bitmapscan;
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 21171f7..307f2f5 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -111,3 +111,235 @@ create index btree_idx_err on btree_test(a) with (vacuum_cleanup_index_scale_fac
 -- Simple ALTER INDEX
 alter index btree_idx1 set (vacuum_cleanup_index_scale_factor = 70.0);
 select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass;
+
+---
+--- Test B-tree distance ordering
+---
+
+SET enable_bitmapscan = OFF;
+
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+-- test parallel KNN scan
+
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+
+RESET enable_indexscan;
+
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, 1000000) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+EXPLAIN (COSTS OFF)
+SELECT i FROM bt_knn_test WHERE i > 8000000;
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> 4000003) AS n, i * 10 AS i
+	FROM generate_series(1, 1000000) i;
+
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+DROP TABLE bt_knn_test;
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, 100000) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+
+ROLLBACK;
+
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+
+
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+DROP INDEX tenk3_idx;
+
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+DROP INDEX tenk3_idx;
+
+DROP TABLE tenk3;
+
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+
+DROP TABLE knn_btree_ts;
+
+RESET enable_bitmapscan;
-- 
2.7.4

#18Michael Paquier
michael@paquier.xyz
In reply to: Nikita Glukhov (#17)
Re: [PATCH] kNN for btree

On Fri, Jan 11, 2019 at 04:01:51PM +0300, Nikita Glukhov wrote:

All new distance functions except oiddist() are not leakproof,
so I had to relax condition in opr_sanity.sql test:

This patch set needs a rebase because of conflicts caused by the
recent patches for pluggable storage.
--
Michael

#19Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Michael Paquier (#18)
9 attachment(s)
Re: [PATCH] kNN for btree

On 04.02.2019 8:35, Michael Paquier wrote:

This patch set needs a rebase because of conflicts caused by the
recent patches for pluggable storage.

Attached 6th version of the patches rebased onto current master:

* index_clauses now also passed into ammatchorderby()

* added support for queries like
SELECT * FROM tab WHERE col1 = val1 AND col2 = val2 ORDER BY col3 <-> val3

* (experimental patch #9)
added support for queries like
SELECT * FROM tab WHERE col1 IN (v1, v2, v3) ORDER BY col1, col2 <-> val

Patch #9 is experimental. In order to distinguish order-by-operator and
simple order-by-column clauses (index column can be operator expression)
in orderbyclauses lists I am trying to pass negative column numbers in
orderbyclausecols, but it looks ugly, so I think orderbyclauses passing needs
some refactoring like recent IndexClause refactoring. Also I doubt that I
correctly implemented match_pathkey_to_indexcol().

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Fix-get_index_column_opclass-v06.patchtext/x-patch; name=0001-Fix-get_index_column_opclass-v06.patchDownload
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index e88c45d..2760748 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -3118,9 +3118,6 @@ get_index_column_opclass(Oid index_oid, int attno)
 {
 	HeapTuple	tuple;
 	Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY;
-	Datum		datum;
-	bool		isnull;
-	oidvector  *indclass;
 	Oid			opclass;
 
 	/* First we need to know the column's opclass. */
@@ -3134,12 +3131,23 @@ get_index_column_opclass(Oid index_oid, int attno)
 	/* caller is supposed to guarantee this */
 	Assert(attno > 0 && attno <= rd_index->indnatts);
 
-	datum = SysCacheGetAttr(INDEXRELID, tuple,
-							Anum_pg_index_indclass, &isnull);
-	Assert(!isnull);
+	if (attno >= 1 && attno <= rd_index->indnkeyatts)
+	{
+		oidvector  *indclass;
+		bool		isnull;
+		Datum		datum = SysCacheGetAttr(INDEXRELID, tuple,
+											Anum_pg_index_indclass,
+											&isnull);
+
+		Assert(!isnull);
 
-	indclass = ((oidvector *) DatumGetPointer(datum));
-	opclass = indclass->values[attno - 1];
+		indclass = ((oidvector *) DatumGetPointer(datum));
+		opclass = indclass->values[attno - 1];
+	}
+	else
+	{
+		opclass = InvalidOid;
+	}
 
 	ReleaseSysCache(tuple);
 
0002-Introduce-ammatchorderby-function-v06.patchtext/x-patch; name=0002-Introduce-ammatchorderby-function-v06.patchDownload
diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 6458376..9bff793 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -109,7 +109,6 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BLOOM_NSTRATEGIES;
 	amroutine->amsupport = BLOOM_NPROC;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -143,6 +142,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 8f008dd..5cd06c7 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -88,7 +88,6 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = BRIN_LAST_OPTIONAL_PROCNUM;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -122,6 +121,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index afc2023..0f6714d 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -41,7 +41,6 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GINNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -75,6 +74,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index b75b3a8..77ca187 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -18,6 +18,7 @@
 #include "access/gistscan.h"
 #include "catalog/pg_collation.h"
 #include "miscadmin.h"
+#include "optimizer/paths.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "nodes/execnodes.h"
@@ -64,7 +65,6 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GISTNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -98,6 +98,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = match_orderbyop_pathkeys;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index f1f01a0..bb5c6a1 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -59,7 +59,6 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = HTMaxStrategyNumber;
 	amroutine->amsupport = HASHNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -93,6 +92,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 98917de..c56ee5e 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -110,7 +110,6 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BTMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = true;
 	amroutine->amcanmulticol = true;
@@ -144,6 +143,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = btestimateparallelscan;
 	amroutine->aminitparallelscan = btinitparallelscan;
 	amroutine->amparallelrescan = btparallelrescan;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 8e63c1f..37de33c 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -22,6 +22,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/pg_amop.h"
+#include "optimizer/paths.h"
 #include "storage/bufmgr.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -31,7 +32,6 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
-
 /*
  * SP-GiST handler function: return IndexAmRoutine with access method parameters
  * and callbacks.
@@ -44,7 +44,6 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = SPGISTNProc;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -78,6 +77,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = match_orderbyop_pathkeys;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 5d73848..a595c79 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -1097,7 +1097,7 @@ assignOperTypes(OpFamilyMember *member, Oid amoid, Oid typeoid)
 		 */
 		IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid, false);
 
-		if (!amroutine->amcanorderbyop)
+		if (!amroutine->ammatchorderby)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("access method \"%s\" does not support ordering operators",
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 324356e..805abd2 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -198,7 +198,7 @@ IndexNextWithReorder(IndexScanState *node)
 	 * with just Asserting here because the system will not try to run the
 	 * plan backwards if ExecSupportsBackwardScan() says it won't work.
 	 * Currently, that is guaranteed because no index AMs support both
-	 * amcanorderbyop and amcanbackward; if any ever do,
+	 * ammatchorderby and amcanbackward; if any ever do,
 	 * ExecSupportsBackwardScan() will need to consider indexorderbys
 	 * explicitly.
 	 */
@@ -1148,7 +1148,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
  * 5. NullTest ("indexkey IS NULL/IS NOT NULL").  We just fill in the
  * ScanKey properly.
  *
- * This code is also used to prepare ORDER BY expressions for amcanorderbyop
+ * This code is also used to prepare ORDER BY expressions for ammatchorderby
  * indexes.  The behavior is exactly the same, except that we have to look up
  * the operator differently.  Note that only cases 1 and 2 are currently
  * possible for ORDER BY.
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 3434219..6cf0b0d 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -17,6 +17,7 @@
 
 #include <math.h>
 
+#include "access/amapi.h"
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_am.h"
@@ -182,8 +183,9 @@ static IndexClause *expand_indexqual_rowcompare(RestrictInfo *rinfo,
 							Oid expr_op,
 							bool var_on_left);
 static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
+						List *index_clauses,
 						List **orderby_clauses_p,
-						List **clause_columns_p);
+						List **orderby_clause_columns_p);
 static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
 							int indexcol, Expr *clause, Oid pk_opfamily);
 static bool ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
@@ -1001,10 +1003,11 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		orderbyclauses = NIL;
 		orderbyclausecols = NIL;
 	}
-	else if (index->amcanorderbyop && pathkeys_possibly_useful)
+	else if (index->ammatchorderby && pathkeys_possibly_useful)
 	{
 		/* see if we can generate ordering operators for query_pathkeys */
 		match_pathkeys_to_index(index, root->query_pathkeys,
+								index_clauses,
 								&orderbyclauses,
 								&orderbyclausecols);
 		if (orderbyclauses)
@@ -2927,6 +2930,113 @@ match_rowcompare_to_indexcol(RestrictInfo *rinfo,
 	return NULL;
 }
 
+/****************************************************************************
+ *				----  ROUTINES TO CHECK ORDERING OPERATORS	----
+ ****************************************************************************/
+
+/*
+ * Try to match order-by-operator pathkey to the specified index column
+ * (*indexcol_p >= 0) or to all index columns (*indexcol_p < 0).
+ *
+ * Returned matched index clause exression.
+ * Number of matched index column is returned in *indexcol_p.
+ */
+Expr *
+match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey, int *indexcol_p)
+{
+	ListCell   *lc;
+
+	/* Pathkey must request default sort order for the target opfamily */
+	if (pathkey->pk_strategy != BTLessStrategyNumber ||
+		pathkey->pk_nulls_first)
+		return NULL;
+
+	/* If eclass is volatile, no hope of using an indexscan */
+	if (pathkey->pk_eclass->ec_has_volatile)
+		return NULL;
+
+	/*
+	 * Try to match eclass member expression(s) to index.  Note that child EC
+	 * members are considered, but only when they belong to the target
+	 * relation.  (Unlike regular members, the same expression could be a
+	 * child member of more than one EC.  Therefore, the same index could be
+	 * considered to match more than one pathkey list, which is OK here.  See
+	 * also get_eclass_for_sort_expr.)
+	 */
+	foreach(lc, pathkey->pk_eclass->ec_members)
+	{
+		EquivalenceMember *member = lfirst_node(EquivalenceMember, lc);
+		Expr	   *expr;
+
+		/* No possibility of match if it references other relations. */
+		if (!bms_equal(member->em_relids, index->rel->relids))
+			continue;
+
+		/* If *indexcol_p is non-negative then try to match only to it. */
+		if (*indexcol_p >= 0)
+		{
+			expr = match_clause_to_ordering_op(index, *indexcol_p,
+											   member->em_expr,
+											   pathkey->pk_opfamily);
+
+			if (expr)
+				return expr;	/* don't want to look at remaining members */
+		}
+		else
+		{
+			int			indexcol;
+
+			/*
+			 * We allow any column of this index to match each pathkey; they
+			 * don't have to match left-to-right as you might expect.
+			 */
+			for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
+			{
+				expr = match_clause_to_ordering_op(index, indexcol,
+												   member->em_expr,
+												   pathkey->pk_opfamily);
+				if (expr)
+				{
+					*indexcol_p = indexcol;
+					return expr;	/* don't want to look at remaining members */
+				}
+			}
+		}
+	}
+
+	return NULL;
+}
+
+/* Try to match order-by-operator pathkeys to any index columns. */
+bool
+match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
+						 List *index_clauses, List **orderby_clauses_p,
+						 List **orderby_clausecols_p)
+{
+	ListCell   *lc;
+
+	foreach(lc, pathkeys)
+	{
+		PathKey    *pathkey = lfirst_node(PathKey, lc);
+		Expr	   *expr;
+		int			indexcol = -1;	/* match all index columns */
+
+		expr = match_orderbyop_pathkey(index, pathkey, &indexcol);
+
+		/*
+		 * Note: for any failure to match, we just return NIL immediately.
+		 * There is no value in matching just some of the pathkeys.
+		 */
+		if (!expr)
+			return false;
+
+		*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+		*orderby_clausecols_p = lappend_int(*orderby_clausecols_p, indexcol);
+	}
+
+	return true;				/* success */
+}
+
 /*
  * expand_indexqual_rowcompare --- expand a single indexqual condition
  *		that is a RowCompareExpr
@@ -3183,92 +3293,31 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
  */
 static void
 match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
+						List *index_clauses,
 						List **orderby_clauses_p,
-						List **clause_columns_p)
+						List **orderby_clause_columns_p)
 {
 	List	   *orderby_clauses = NIL;
-	List	   *clause_columns = NIL;
-	ListCell   *lc1;
-
-	*orderby_clauses_p = NIL;	/* set default results */
-	*clause_columns_p = NIL;
+	List	   *orderby_clause_columns = NIL;
+	ammatchorderby_function ammatchorderby =
+	(ammatchorderby_function) index->ammatchorderby;
 
-	/* Only indexes with the amcanorderbyop property are interesting here */
-	if (!index->amcanorderbyop)
-		return;
-
-	foreach(lc1, pathkeys)
+	/* Only indexes with the ammatchorderby function are interesting here */
+	if (ammatchorderby &&
+		ammatchorderby(index, pathkeys, index_clauses,
+					   &orderby_clauses, &orderby_clause_columns))
 	{
-		PathKey    *pathkey = (PathKey *) lfirst(lc1);
-		bool		found = false;
-		ListCell   *lc2;
-
-		/*
-		 * Note: for any failure to match, we just return NIL immediately.
-		 * There is no value in matching just some of the pathkeys.
-		 */
-
-		/* Pathkey must request default sort order for the target opfamily */
-		if (pathkey->pk_strategy != BTLessStrategyNumber ||
-			pathkey->pk_nulls_first)
-			return;
-
-		/* If eclass is volatile, no hope of using an indexscan */
-		if (pathkey->pk_eclass->ec_has_volatile)
-			return;
-
-		/*
-		 * Try to match eclass member expression(s) to index.  Note that child
-		 * EC members are considered, but only when they belong to the target
-		 * relation.  (Unlike regular members, the same expression could be a
-		 * child member of more than one EC.  Therefore, the same index could
-		 * be considered to match more than one pathkey list, which is OK
-		 * here.  See also get_eclass_for_sort_expr.)
-		 */
-		foreach(lc2, pathkey->pk_eclass->ec_members)
-		{
-			EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
-			int			indexcol;
-
-			/* No possibility of match if it references other relations */
-			if (!bms_equal(member->em_relids, index->rel->relids))
-				continue;
+		Assert(list_length(pathkeys) == list_length(orderby_clauses));
+		Assert(list_length(pathkeys) == list_length(orderby_clause_columns));
 
-			/*
-			 * We allow any column of the index to match each pathkey; they
-			 * don't have to match left-to-right as you might expect.  This is
-			 * correct for GiST, and it doesn't matter for SP-GiST because
-			 * that doesn't handle multiple columns anyway, and no other
-			 * existing AMs support amcanorderbyop.  We might need different
-			 * logic in future for other implementations.
-			 */
-			for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
-			{
-				Expr	   *expr;
-
-				expr = match_clause_to_ordering_op(index,
-												   indexcol,
-												   member->em_expr,
-												   pathkey->pk_opfamily);
-				if (expr)
-				{
-					orderby_clauses = lappend(orderby_clauses, expr);
-					clause_columns = lappend_int(clause_columns, indexcol);
-					found = true;
-					break;
-				}
-			}
-
-			if (found)			/* don't want to look at remaining members */
-				break;
-		}
-
-		if (!found)				/* fail if no match for this pathkey */
-			return;
+		*orderby_clauses_p = orderby_clauses;	/* success! */
+		*orderby_clause_columns_p = orderby_clause_columns;
+	}
+	else
+	{
+		*orderby_clauses_p = NIL;	/* set default results */
+		*orderby_clause_columns_p = NIL;
 	}
-
-	*orderby_clauses_p = orderby_clauses;	/* success! */
-	*clause_columns_p = clause_columns;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d6dc83c..2d81fd1 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -267,7 +267,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 			/* We copy just the fields we need, not all of rd_indam */
 			amroutine = indexRelation->rd_indam;
-			info->amcanorderbyop = amroutine->amcanorderbyop;
+			info->ammatchorderby = amroutine->ammatchorderby;
 			info->amoptionalkey = amroutine->amoptionalkey;
 			info->amsearcharray = amroutine->amsearcharray;
 			info->amsearchnulls = amroutine->amsearchnulls;
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c
index 060ffe5..3907065 100644
--- a/src/backend/utils/adt/amutils.c
+++ b/src/backend/utils/adt/amutils.c
@@ -298,7 +298,7 @@ indexam_property(FunctionCallInfo fcinfo,
 				 * a nonkey column, and null otherwise (meaning we don't
 				 * know).
 				 */
-				if (!iskey || !routine->amcanorderbyop)
+				if (!iskey || !routine->ammatchorderby)
 				{
 					res = false;
 					isnull = false;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 653ddc9..d8fb7d3 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -21,6 +21,9 @@
  */
 struct PlannerInfo;
 struct IndexPath;
+struct IndexOptInfo;
+struct PathKey;
+struct Expr;
 
 /* Likewise, this file shouldn't depend on execnodes.h. */
 struct IndexInfo;
@@ -140,6 +143,13 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/* does AM support ORDER BY result of an operator on indexed column? */
+typedef bool (*ammatchorderby_function) (struct IndexOptInfo *index,
+										 List *pathkeys,
+										 List *index_clauses,
+										 List **orderby_clauses_p,
+										 List **orderby_clause_columns_p);
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -170,8 +180,6 @@ typedef struct IndexAmRoutine
 	uint16		amsupport;
 	/* does AM support ORDER BY indexed column's value? */
 	bool		amcanorder;
-	/* does AM support ORDER BY result of an operator on indexed column? */
-	bool		amcanorderbyop;
 	/* does AM support backward scanning? */
 	bool		amcanbackward;
 	/* does AM support UNIQUE indexes? */
@@ -221,6 +229,7 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;	/* can be NULL */
 	amrestrpos_function amrestrpos; /* can be NULL */
+	ammatchorderby_function ammatchorderby; /* can be NULL */
 
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index a008ae0..4680cb8 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -814,7 +814,6 @@ struct IndexOptInfo
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
 	bool		amoptionalkey;	/* can query omit key for the first column? */
 	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
 	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
@@ -823,6 +822,12 @@ struct IndexOptInfo
 	bool		amcanparallel;	/* does AM support parallel scan? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
+	/* AM order-by match function */
+	bool		(*ammatchorderby) (struct IndexOptInfo *index,
+								   List *pathkeys,
+								   List *index_clause_columns,
+								   List **orderby_clauses_p,
+								   List **orderby_clause_columns_p);
 };
 
 /*
@@ -1133,7 +1138,7 @@ typedef struct Path
  * An empty list implies a full index scan.
  *
  * 'indexorderbys', if not NIL, is a list of ORDER BY expressions that have
- * been found to be usable as ordering operators for an amcanorderbyop index.
+ * been found to be usable as ordering operators for an ammatchorderby index.
  * The list must match the path's pathkeys, ie, one expression per pathkey
  * in the same order.  These are not RestrictInfos, just bare expressions,
  * since they generally won't yield booleans.  It's guaranteed that each
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 6d087c2..bd6ce97 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -388,7 +388,7 @@ typedef struct SampleScan
  * indexorderbyops is a list of the OIDs of the operators used to sort the
  * ORDER BY expressions.  This is used together with indexorderbyorig to
  * recheck ordering at run time.  (Note that indexorderby, indexorderbyorig,
- * and indexorderbyops are used for amcanorderbyop cases, not amcanorder.)
+ * and indexorderbyops are used for ammatchorderby cases, not amcanorder.)
  *
  * indexorderdir specifies the scan ordering, for indexscans on amcanorder
  * indexes (for other indexes it should be "don't care").
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 040335a..e9f4f75 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -79,6 +79,11 @@ extern bool indexcol_is_bool_constant_for_query(IndexOptInfo *index,
 extern bool match_index_to_operand(Node *operand, int indexcol,
 					   IndexOptInfo *index);
 extern void check_index_predicates(PlannerInfo *root, RelOptInfo *rel);
+extern Expr *match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey,
+						int *indexcol_p);
+extern bool match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
+						 List *index_clauses, List **orderby_clauses_p,
+						 List **orderby_clause_columns_p);
 
 /*
  * tidpath.h
0003-Extract-structure-BTScanState-v06.patchtext/x-patch; name=0003-Extract-structure-BTScanState-v06.patchDownload
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index c56ee5e..bf6a6c6 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -214,6 +214,7 @@ bool
 btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 	bool		res;
 
 	/* btree indexes are never lossy */
@@ -224,7 +225,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(so->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
@@ -241,7 +242,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(so->currPos))
+		if (!BTScanPosIsValid(state->currPos))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -259,11 +260,11 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 				 * trying to optimize that, so we don't detect it, but instead
 				 * just forget any excess entries.
 				 */
-				if (so->killedItems == NULL)
-					so->killedItems = (int *)
+				if (state->killedItems == NULL)
+					state->killedItems = (int *)
 						palloc(MaxIndexTuplesPerPage * sizeof(int));
-				if (so->numKilled < MaxIndexTuplesPerPage)
-					so->killedItems[so->numKilled++] = so->currPos.itemIndex;
+				if (state->numKilled < MaxIndexTuplesPerPage)
+					state->killedItems[so->state.numKilled++] = state->currPos.itemIndex;
 			}
 
 			/*
@@ -288,6 +289,7 @@ int64
 btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	int64		ntids = 0;
 	ItemPointer heapTid;
 
@@ -320,7 +322,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				 * Advance to next tuple within page.  This is the same as the
 				 * easy case in _bt_next().
 				 */
-				if (++so->currPos.itemIndex > so->currPos.lastItem)
+				if (++currPos->itemIndex > currPos->lastItem)
 				{
 					/* let _bt_next do the heavy lifting */
 					if (!_bt_next(scan, ForwardScanDirection))
@@ -328,7 +330,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				}
 
 				/* Save tuple ID, and continue scanning */
-				heapTid = &so->currPos.items[so->currPos.itemIndex].heapTid;
+				heapTid = &currPos->items[currPos->itemIndex].heapTid;
 				tbm_add_tuples(tbm, heapTid, 1, false);
 				ntids++;
 			}
@@ -356,8 +358,8 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 
 	/* allocate private workspace */
 	so = (BTScanOpaque) palloc(sizeof(BTScanOpaqueData));
-	BTScanPosInvalidate(so->currPos);
-	BTScanPosInvalidate(so->markPos);
+	BTScanPosInvalidate(so->state.currPos);
+	BTScanPosInvalidate(so->state.markPos);
 	if (scan->numberOfKeys > 0)
 		so->keyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData));
 	else
@@ -368,15 +370,15 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	so->arrayKeys = NULL;
 	so->arrayContext = NULL;
 
-	so->killedItems = NULL;		/* until needed */
-	so->numKilled = 0;
+	so->state.killedItems = NULL;	/* until needed */
+	so->state.numKilled = 0;
 
 	/*
 	 * We don't know yet whether the scan will be index-only, so we do not
 	 * allocate the tuple workspace arrays until btrescan.  However, we set up
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
-	so->currTuples = so->markTuples = NULL;
+	so->state.currTuples = so->state.markTuples = NULL;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -385,6 +387,45 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	return scan;
 }
 
+static void
+_bt_release_current_position(BTScanState state, Relation indexRelation,
+							 bool invalidate)
+{
+	/* we aren't holding any read locks, but gotta drop the pins */
+	if (BTScanPosIsValid(state->currPos))
+	{
+		/* Before leaving current page, deal with any killed items */
+		if (state->numKilled > 0)
+			_bt_killitems(state, indexRelation);
+
+		BTScanPosUnpinIfPinned(state->currPos);
+
+		if (invalidate)
+			BTScanPosInvalidate(state->currPos);
+	}
+}
+
+static void
+_bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
+{
+	/* No need to invalidate positions, if the RAM is about to be freed. */
+	_bt_release_current_position(state, scan->indexRelation, !free);
+
+	state->markItemIndex = -1;
+	BTScanPosUnpinIfPinned(state->markPos);
+
+	if (free)
+	{
+		if (state->killedItems != NULL)
+			pfree(state->killedItems);
+		if (state->currTuples != NULL)
+			pfree(state->currTuples);
+		/* markTuples should not be pfree'd (_bt_allocate_tuple_workspaces) */
+	}
+	else
+		BTScanPosInvalidate(state->markPos);
+}
+
 /*
  *	btrescan() -- rescan an index relation
  */
@@ -393,21 +434,11 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 		 ScanKey orderbys, int norderbys)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-		BTScanPosInvalidate(so->currPos);
-	}
+	_bt_release_scan_state(scan, state, false);
 
-	so->markItemIndex = -1;
 	so->arrayKeyCount = 0;
-	BTScanPosUnpinIfPinned(so->markPos);
-	BTScanPosInvalidate(so->markPos);
 
 	/*
 	 * Allocate tuple workspace arrays, if needed for an index-only scan and
@@ -425,11 +456,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 	 * a SIGSEGV is not possible.  Yeah, this is ugly as sin, but it beats
 	 * adding special-case treatment for name_ops elsewhere.
 	 */
-	if (scan->xs_want_itup && so->currTuples == NULL)
-	{
-		so->currTuples = (char *) palloc(BLCKSZ * 2);
-		so->markTuples = so->currTuples + BLCKSZ;
-	}
+	if (scan->xs_want_itup && state->currTuples == NULL)
+		_bt_allocate_tuple_workspaces(state);
 
 	/*
 	 * Reset the scan keys. Note that keys ordering stuff moved to _bt_first.
@@ -453,19 +481,7 @@ btendscan(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-	}
-
-	so->markItemIndex = -1;
-	BTScanPosUnpinIfPinned(so->markPos);
-
-	/* No need to invalidate positions, the RAM is about to be freed. */
+	_bt_release_scan_state(scan, &so->state, true);
 
 	/* Release storage */
 	if (so->keyData != NULL)
@@ -473,24 +489,15 @@ btendscan(IndexScanDesc scan)
 	/* so->arrayKeyData and so->arrayKeys are in arrayContext */
 	if (so->arrayContext != NULL)
 		MemoryContextDelete(so->arrayContext);
-	if (so->killedItems != NULL)
-		pfree(so->killedItems);
-	if (so->currTuples != NULL)
-		pfree(so->currTuples);
-	/* so->markTuples should not be pfree'd, see btrescan */
+
 	pfree(so);
 }
 
-/*
- *	btmarkpos() -- save current scan position
- */
-void
-btmarkpos(IndexScanDesc scan)
+static void
+_bt_mark_current_position(BTScanState state)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-
 	/* There may be an old mark with a pin (but no lock). */
-	BTScanPosUnpinIfPinned(so->markPos);
+	BTScanPosUnpinIfPinned(state->markPos);
 
 	/*
 	 * Just record the current itemIndex.  If we later step to next page
@@ -498,32 +505,34 @@ btmarkpos(IndexScanDesc scan)
 	 * the currPos struct in markPos.  If (as often happens) the mark is moved
 	 * before we leave the page, we don't have to do that work.
 	 */
-	if (BTScanPosIsValid(so->currPos))
-		so->markItemIndex = so->currPos.itemIndex;
+	if (BTScanPosIsValid(state->currPos))
+		state->markItemIndex = state->currPos.itemIndex;
 	else
 	{
-		BTScanPosInvalidate(so->markPos);
-		so->markItemIndex = -1;
+		BTScanPosInvalidate(state->markPos);
+		state->markItemIndex = -1;
 	}
-
-	/* Also record the current positions of any array keys */
-	if (so->numArrayKeys)
-		_bt_mark_array_keys(scan);
 }
 
 /*
- *	btrestrpos() -- restore scan to last saved position
+ *	btmarkpos() -- save current scan position
  */
 void
-btrestrpos(IndexScanDesc scan)
+btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* Restore the marked positions of any array keys */
+	_bt_mark_current_position(&so->state);
+
+	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
-		_bt_restore_array_keys(scan);
+		_bt_mark_array_keys(scan);
+}
 
-	if (so->markItemIndex >= 0)
+static void
+_bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
+{
+	if (state->markItemIndex >= 0)
 	{
 		/*
 		 * The scan has never moved to a new page since the last mark.  Just
@@ -532,7 +541,7 @@ btrestrpos(IndexScanDesc scan)
 		 * NB: In this case we can't count on anything in so->markPos to be
 		 * accurate.
 		 */
-		so->currPos.itemIndex = so->markItemIndex;
+		state->currPos.itemIndex = state->markItemIndex;
 	}
 	else
 	{
@@ -542,28 +551,21 @@ btrestrpos(IndexScanDesc scan)
 		 * locks, but if we're still holding the pin for the current position,
 		 * we must drop it.
 		 */
-		if (BTScanPosIsValid(so->currPos))
-		{
-			/* Before leaving current page, deal with any killed items */
-			if (so->numKilled > 0)
-				_bt_killitems(scan);
-			BTScanPosUnpinIfPinned(so->currPos);
-		}
+		_bt_release_current_position(state, scan->indexRelation,
+									 !BTScanPosIsValid(state->markPos));
 
-		if (BTScanPosIsValid(so->markPos))
+		if (BTScanPosIsValid(state->markPos))
 		{
 			/* bump pin on mark buffer for assignment to current buffer */
-			if (BTScanPosIsPinned(so->markPos))
-				IncrBufferRefCount(so->markPos.buf);
-			memcpy(&so->currPos, &so->markPos,
+			if (BTScanPosIsPinned(state->markPos))
+				IncrBufferRefCount(state->markPos.buf);
+			memcpy(&state->currPos, &state->markPos,
 				   offsetof(BTScanPosData, items[1]) +
-				   so->markPos.lastItem * sizeof(BTScanPosItem));
-			if (so->currTuples)
-				memcpy(so->currTuples, so->markTuples,
-					   so->markPos.nextTupleOffset);
+				   state->markPos.lastItem * sizeof(BTScanPosItem));
+			if (state->currTuples)
+				memcpy(state->currTuples, state->markTuples,
+					   state->markPos.nextTupleOffset);
 		}
-		else
-			BTScanPosInvalidate(so->currPos);
 	}
 }
 
@@ -779,9 +781,10 @@ _bt_parallel_advance_array_keys(IndexScanDesc scan)
 }
 
 /*
- * _bt_vacuum_needs_cleanup() -- Checks if index needs cleanup assuming that
- *			btbulkdelete() wasn't called.
- */
+- * _bt_vacuum_needs_cleanup() -- Checks if index needs cleanup assuming that
+- *			btbulkdelete() wasn't called.
++ *	btrestrpos() -- restore scan to last saved position
+  */
 static bool
 _bt_vacuum_needs_cleanup(IndexVacuumInfo *info)
 {
@@ -844,6 +847,21 @@ _bt_vacuum_needs_cleanup(IndexVacuumInfo *info)
 }
 
 /*
+ *	btrestrpos() -- restore scan to last saved position
+ */
+void
+btrestrpos(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
+	/* Restore the marked positions of any array keys */
+	if (so->numArrayKeys)
+		_bt_restore_array_keys(scan);
+
+	_bt_restore_marked_position(scan, &so->state);
+}
+
+/*
  * Bulk deletion of all index entries pointing to a set of heap tuples.
  * The set of target tuples is specified via a callback routine that tells
  * whether any given heap tuple (identified by ItemPointer) is being deleted.
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 9283223..cbd72bd 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -24,18 +24,19 @@
 #include "utils/rel.h"
 
 
-static bool _bt_readpage(IndexScanDesc scan, ScanDirection dir,
+static bool _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 			 OffsetNumber offnum);
-static void _bt_saveitem(BTScanOpaque so, int itemIndex,
+static void _bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup);
-static bool _bt_steppage(IndexScanDesc scan, ScanDirection dir);
-static bool _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir);
+static bool _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir);
+static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
+				 BlockNumber blkno, ScanDirection dir);
 static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
 					  ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf, Snapshot snapshot);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
-static inline void _bt_initialize_more_data(BTScanOpaque so, ScanDirection dir);
+static inline void _bt_initialize_more_data(BTScanState state, ScanDirection dir);
 
 
 /*
@@ -544,6 +545,58 @@ _bt_compare(Relation rel,
 }
 
 /*
+ * _bt_return_current_item() -- Prepare current scan state item for return.
+ *
+ * This function is used only in "return _bt_return_current_item();" statements
+ * and always returns true.
+ */
+static inline bool
+_bt_return_current_item(IndexScanDesc scan, BTScanState state)
+{
+	BTScanPosItem *currItem = &state->currPos.items[state->currPos.itemIndex];
+
+	scan->xs_ctup.t_self = currItem->heapTid;
+
+	if (scan->xs_want_itup)
+		scan->xs_itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+
+	return true;
+}
+
+/*
+ * _bt_load_first_page() -- Load data from the first page of the scan.
+ *
+ * Caller must have pinned and read-locked state->currPos.buf.
+ *
+ * On success exit, state->currPos is updated to contain data from the next
+ * interesting page.  For success on a scan using a non-MVCC snapshot we hold
+ * a pin, but not a read lock, on that page.  If we do not hold the pin, we
+ * set state->currPos.buf to InvalidBuffer.  We return true to indicate success.
+ *
+ * If there are no more matching records in the given direction at all,
+ * we drop all locks and pins, set state->currPos.buf to InvalidBuffer,
+ * and return false.
+ */
+static bool
+_bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+					OffsetNumber offnum)
+{
+	if (!_bt_readpage(scan, state, dir, offnum))
+	{
+		/*
+		 * There's no actually-matching data on this page.  Try to advance to
+		 * the next page.  Return false if there's no matching data at all.
+		 */
+		LockBuffer(state->currPos.buf, BUFFER_LOCK_UNLOCK);
+		return _bt_steppage(scan, state, dir);
+	}
+
+	/* Drop the lock, and maybe the pin, on the current page */
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
+	return true;
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -568,6 +621,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	BTStack		stack;
 	OffsetNumber offnum;
@@ -581,10 +635,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	int			i;
 	bool		status = true;
 	StrategyNumber strat_total;
-	BTScanPosItem *currItem;
 	BlockNumber blkno;
 
-	Assert(!BTScanPosIsValid(so->currPos));
+	Assert(!BTScanPosIsValid(*currPos));
 
 	pgstat_count_index_scan(rel);
 
@@ -1075,7 +1128,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 * their scan
 		 */
 		_bt_parallel_done(scan);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 
 		return false;
 	}
@@ -1083,7 +1136,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		PredicateLockPage(rel, BufferGetBlockNumber(buf),
 						  scan->xs_snapshot);
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
 	/* position to the precise item on the page */
 	offnum = _bt_binsrch(rel, buf, keysCount, scankeys, nextkey);
@@ -1110,36 +1163,36 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		offnum = OffsetNumberPrev(offnum);
 
 	/* remember which buffer we have pinned, if any */
-	Assert(!BTScanPosIsValid(so->currPos));
-	so->currPos.buf = buf;
+	Assert(!BTScanPosIsValid(*currPos));
+	currPos->buf = buf;
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, offnum))
+	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
+		return false;
+
+readcomplete:
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
+}
+
+/*
+ *	Advance to next tuple on current page; or if there's no more,
+ *	try to step to the next page with data.
+ */
+static bool
+_bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
+{
+	if (ScanDirectionIsForward(dir))
 	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
+		if (++state->currPos.itemIndex <= state->currPos.lastItem)
+			return true;
 	}
 	else
 	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+		if (--state->currPos.itemIndex >= state->currPos.firstItem)
+			return true;
 	}
 
-readcomplete:
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_steppage(scan, state, dir);
 }
 
 /*
@@ -1160,44 +1213,20 @@ bool
 _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-	BTScanPosItem *currItem;
 
-	/*
-	 * Advance to next tuple on current page; or if there's no more, try to
-	 * step to the next page with data.
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (++so->currPos.itemIndex > so->currPos.lastItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
-	else
-	{
-		if (--so->currPos.itemIndex < so->currPos.firstItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
+	if (!_bt_next_item(scan, &so->state, dir))
+		return false;
 
 	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
  *	_bt_readpage() -- Load data from current index page into so->currPos
  *
- * Caller must have pinned and read-locked so->currPos.buf; the buffer's state
- * is not changed here.  Also, currPos.moreLeft and moreRight must be valid;
- * they are updated as appropriate.  All other fields of so->currPos are
+ * Caller must have pinned and read-locked pos->buf; the buffer's state
+ * is not changed here.  Also, pos->moreLeft and moreRight must be valid;
+ * they are updated as appropriate.  All other fields of pos are
  * initialized from scratch here.
  *
  * We scan the current page starting at offnum and moving in the indicated
@@ -1212,9 +1241,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
  * Returns true if any matching items found on the page, false if none.
  */
 static bool
-_bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
+_bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+			 OffsetNumber offnum)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
@@ -1227,9 +1257,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We must have the buffer pinned and locked, but the usual macro can't be
 	 * used here; this function is what makes it good for currPos.
 	 */
-	Assert(BufferIsValid(so->currPos.buf));
+	Assert(BufferIsValid(pos->buf));
 
-	page = BufferGetPage(so->currPos.buf);
+	page = BufferGetPage(pos->buf);
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 
 	/* allow next page be processed by parallel worker */
@@ -1238,7 +1268,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		if (ScanDirectionIsForward(dir))
 			_bt_parallel_release(scan, opaque->btpo_next);
 		else
-			_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+			_bt_parallel_release(scan, BufferGetBlockNumber(pos->buf));
 	}
 
 	minoff = P_FIRSTDATAKEY(opaque);
@@ -1248,30 +1278,30 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We note the buffer's block number so that we can release the pin later.
 	 * This allows us to re-read the buffer if it is needed again for hinting.
 	 */
-	so->currPos.currPage = BufferGetBlockNumber(so->currPos.buf);
+	pos->currPage = BufferGetBlockNumber(pos->buf);
 
 	/*
 	 * We save the LSN of the page as we read it, so that we know whether it
 	 * safe to apply LP_DEAD hints to the page later.  This allows us to drop
 	 * the pin for MVCC scans, which allows vacuum to avoid blocking.
 	 */
-	so->currPos.lsn = BufferGetLSNAtomic(so->currPos.buf);
+	pos->lsn = BufferGetLSNAtomic(pos->buf);
 
 	/*
 	 * we must save the page's right-link while scanning it; this tells us
 	 * where to step right to after we're done with these items.  There is no
 	 * corresponding need for the left-link, since splits always go right.
 	 */
-	so->currPos.nextPage = opaque->btpo_next;
+	pos->nextPage = opaque->btpo_next;
 
 	/* initialize tuple workspace to empty */
-	so->currPos.nextTupleOffset = 0;
+	pos->nextTupleOffset = 0;
 
 	/*
 	 * Now that the current page has been made consistent, the macro should be
 	 * good.
 	 */
-	Assert(BTScanPosIsPinned(so->currPos));
+	Assert(BTScanPosIsPinned(*pos));
 
 	if (ScanDirectionIsForward(dir))
 	{
@@ -1286,13 +1316,13 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			if (itup != NULL)
 			{
 				/* tuple passes all scan key conditions, so remember it */
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 				itemIndex++;
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreRight = false;
+				pos->moreRight = false;
 				break;
 			}
 
@@ -1300,9 +1330,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex <= MaxIndexTuplesPerPage);
-		so->currPos.firstItem = 0;
-		so->currPos.lastItem = itemIndex - 1;
-		so->currPos.itemIndex = 0;
+		pos->firstItem = 0;
+		pos->lastItem = itemIndex - 1;
+		pos->itemIndex = 0;
 	}
 	else
 	{
@@ -1318,12 +1348,12 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			{
 				/* tuple passes all scan key conditions, so remember it */
 				itemIndex--;
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreLeft = false;
+				pos->moreLeft = false;
 				break;
 			}
 
@@ -1331,30 +1361,31 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex >= 0);
-		so->currPos.firstItem = itemIndex;
-		so->currPos.lastItem = MaxIndexTuplesPerPage - 1;
-		so->currPos.itemIndex = MaxIndexTuplesPerPage - 1;
+		pos->firstItem = itemIndex;
+		pos->lastItem = MaxIndexTuplesPerPage - 1;
+		pos->itemIndex = MaxIndexTuplesPerPage - 1;
 	}
 
-	return (so->currPos.firstItem <= so->currPos.lastItem);
+	return (pos->firstItem <= pos->lastItem);
 }
 
 /* Save an index item into so->currPos.items[itemIndex] */
 static void
-_bt_saveitem(BTScanOpaque so, int itemIndex,
+_bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup)
 {
-	BTScanPosItem *currItem = &so->currPos.items[itemIndex];
+	BTScanPosItem *currItem = &state->currPos.items[itemIndex];
 
 	currItem->heapTid = itup->t_tid;
 	currItem->indexOffset = offnum;
-	if (so->currTuples)
+	if (state->currTuples)
 	{
 		Size		itupsz = IndexTupleSize(itup);
 
-		currItem->tupleOffset = so->currPos.nextTupleOffset;
-		memcpy(so->currTuples + so->currPos.nextTupleOffset, itup, itupsz);
-		so->currPos.nextTupleOffset += MAXALIGN(itupsz);
+		currItem->tupleOffset = state->currPos.nextTupleOffset;
+		memcpy(state->currTuples + state->currPos.nextTupleOffset,
+			   itup, itupsz);
+		state->currPos.nextTupleOffset += MAXALIGN(itupsz);
 	}
 }
 
@@ -1370,35 +1401,36 @@ _bt_saveitem(BTScanOpaque so, int itemIndex,
  * to InvalidBuffer.  We return true to indicate success.
  */
 static bool
-_bt_steppage(IndexScanDesc scan, ScanDirection dir)
+_bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
+	Relation	rel = scan->indexRelation;
 	BlockNumber blkno = InvalidBlockNumber;
 	bool		status = true;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(*currPos));
 
 	/* Before leaving current page, deal with any killed items */
-	if (so->numKilled > 0)
-		_bt_killitems(scan);
+	if (state->numKilled > 0)
+		_bt_killitems(state, rel);
 
 	/*
 	 * Before we modify currPos, make a copy of the page data if there was a
 	 * mark position that needs it.
 	 */
-	if (so->markItemIndex >= 0)
+	if (state->markItemIndex >= 0)
 	{
 		/* bump pin on current buffer for assignment to mark buffer */
-		if (BTScanPosIsPinned(so->currPos))
-			IncrBufferRefCount(so->currPos.buf);
-		memcpy(&so->markPos, &so->currPos,
+		if (BTScanPosIsPinned(*currPos))
+			IncrBufferRefCount(currPos->buf);
+		memcpy(&state->markPos, currPos,
 			   offsetof(BTScanPosData, items[1]) +
-			   so->currPos.lastItem * sizeof(BTScanPosItem));
-		if (so->markTuples)
-			memcpy(so->markTuples, so->currTuples,
-				   so->currPos.nextTupleOffset);
-		so->markPos.itemIndex = so->markItemIndex;
-		so->markItemIndex = -1;
+			   currPos->lastItem * sizeof(BTScanPosItem));
+		if (state->markTuples)
+			memcpy(state->markTuples, state->currTuples,
+				   currPos->nextTupleOffset);
+		state->markPos.itemIndex = state->markItemIndex;
+		state->markItemIndex = -1;
 	}
 
 	if (ScanDirectionIsForward(dir))
@@ -1414,27 +1446,27 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
-				BTScanPosUnpinIfPinned(so->currPos);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosUnpinIfPinned(*currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so use the previously-saved nextPage link. */
-			blkno = so->currPos.nextPage;
+			blkno = currPos->nextPage;
 		}
 
 		/* Remember we left a page with data */
-		so->currPos.moreLeft = true;
+		currPos->moreLeft = true;
 
 		/* release the previous buffer, if pinned */
-		BTScanPosUnpinIfPinned(so->currPos);
+		BTScanPosUnpinIfPinned(*currPos);
 	}
 	else
 	{
 		/* Remember we left a page with data */
-		so->currPos.moreRight = true;
+		currPos->moreRight = true;
 
 		if (scan->parallel_scan != NULL)
 		{
@@ -1443,25 +1475,25 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			 * ended already, bail out.
 			 */
 			status = _bt_parallel_seize(scan, &blkno);
-			BTScanPosUnpinIfPinned(so->currPos);
+			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so just use our own notion of the current page */
-			blkno = so->currPos.currPage;
+			blkno = currPos->currPage;
 		}
 	}
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, currPos);
 
 	return true;
 }
@@ -1477,9 +1509,10 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
  * locks and pins, set so->currPos.buf to InvalidBuffer, and return false.
  */
 static bool
-_bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+				 ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
 	Relation	rel;
 	Page		page;
 	BTPageOpaque opaque;
@@ -1495,17 +1528,17 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * if we're at end of scan, give up and mark parallel scan as
 			 * done, so that all the workers can finish their scan
 			 */
-			if (blkno == P_NONE || !so->currPos.moreRight)
+			if (blkno == P_NONE || !currPos->moreRight)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 			/* check for interrupts while we're not holding any buffer lock */
 			CHECK_FOR_INTERRUPTS();
 			/* step right one page */
-			so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
-			page = BufferGetPage(so->currPos.buf);
+			currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			/* check for deleted page */
@@ -1514,7 +1547,7 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 				PredicateLockPage(rel, blkno, scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreRight if we can stop */
-				if (_bt_readpage(scan, dir, P_FIRSTDATAKEY(opaque)))
+				if (_bt_readpage(scan, state, dir, P_FIRSTDATAKEY(opaque)))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
@@ -1526,18 +1559,18 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
 			}
 			else
 			{
 				blkno = opaque->btpo_next;
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 			}
 		}
 	}
@@ -1547,10 +1580,10 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * Should only happen in parallel cases, when some other backend
 		 * advanced the scan.
 		 */
-		if (so->currPos.currPage != blkno)
+		if (currPos->currPage != blkno)
 		{
-			BTScanPosUnpinIfPinned(so->currPos);
-			so->currPos.currPage = blkno;
+			BTScanPosUnpinIfPinned(*currPos);
+			currPos->currPage = blkno;
 		}
 
 		/*
@@ -1575,31 +1608,30 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * is MVCC the page cannot move past the half-dead state to fully
 		 * deleted.
 		 */
-		if (BTScanPosIsPinned(so->currPos))
-			LockBuffer(so->currPos.buf, BT_READ);
+		if (BTScanPosIsPinned(*currPos))
+			LockBuffer(currPos->buf, BT_READ);
 		else
-			so->currPos.buf = _bt_getbuf(rel, so->currPos.currPage, BT_READ);
+			currPos->buf = _bt_getbuf(rel, currPos->currPage, BT_READ);
 
 		for (;;)
 		{
 			/* Done if we know there are no matching keys to the left */
-			if (!so->currPos.moreLeft)
+			if (!currPos->moreLeft)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
 			/* Step to next physical page */
-			so->currPos.buf = _bt_walk_left(rel, so->currPos.buf,
-											scan->xs_snapshot);
+			currPos->buf = _bt_walk_left(rel, currPos->buf, scan->xs_snapshot);
 
 			/* if we're physically at end of index, return failure */
-			if (so->currPos.buf == InvalidBuffer)
+			if (currPos->buf == InvalidBuffer)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
@@ -1608,21 +1640,21 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * it's not half-dead and contains matching tuples. Else loop back
 			 * and do it all again.
 			 */
-			page = BufferGetPage(so->currPos.buf);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			if (!P_IGNORE(opaque))
 			{
-				PredicateLockPage(rel, BufferGetBlockNumber(so->currPos.buf), scan->xs_snapshot);
+				PredicateLockPage(rel, BufferGetBlockNumber(currPos->buf), scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreLeft if we can stop */
-				if (_bt_readpage(scan, dir, PageGetMaxOffsetNumber(page)))
+				if (_bt_readpage(scan, state, dir, PageGetMaxOffsetNumber(page)))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -1633,14 +1665,14 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
-				so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
+				currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
 			}
 		}
 	}
@@ -1659,13 +1691,13 @@ _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
 
 	return true;
 }
@@ -1890,11 +1922,11 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber start;
-	BTScanPosItem *currItem;
 
 	/*
 	 * Scan down to the leftmost or rightmost leaf page.  This is a simplified
@@ -1910,7 +1942,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 		 * exists.
 		 */
 		PredicateLockRelation(rel, scan->xs_snapshot);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 		return false;
 	}
 
@@ -1939,36 +1971,15 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 	}
 
 	/* remember which buffer we have pinned */
-	so->currPos.buf = buf;
+	currPos->buf = buf;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, start))
-	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
-	}
-	else
-	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
-	}
-
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
+	if (!_bt_load_first_page(scan, &so->state, dir, start))
+		return false;
 
-	return true;
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
@@ -1976,19 +1987,19 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
  * for scan direction
  */
 static inline void
-_bt_initialize_more_data(BTScanOpaque so, ScanDirection dir)
+_bt_initialize_more_data(BTScanState state, ScanDirection dir)
 {
 	/* initialize moreLeft/moreRight appropriately for scan direction */
 	if (ScanDirectionIsForward(dir))
 	{
-		so->currPos.moreLeft = false;
-		so->currPos.moreRight = true;
+		state->currPos.moreLeft = false;
+		state->currPos.moreRight = true;
 	}
 	else
 	{
-		so->currPos.moreLeft = true;
-		so->currPos.moreRight = false;
+		state->currPos.moreLeft = true;
+		state->currPos.moreRight = false;
 	}
-	so->numKilled = 0;			/* just paranoia */
-	so->markItemIndex = -1;		/* ditto */
+	state->numKilled = 0;		/* just paranoia */
+	state->markItemIndex = -1;	/* ditto */
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 2c05fb5..e548354 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -1741,26 +1741,26 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, TupleDesc tupdesc,
  * away and the TID was re-used by a completely different heap tuple.
  */
 void
-_bt_killitems(IndexScanDesc scan)
+_bt_killitems(BTScanState state, Relation indexRelation)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
 	OffsetNumber maxoff;
 	int			i;
-	int			numKilled = so->numKilled;
+	int			numKilled = state->numKilled;
 	bool		killedsomething = false;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(state->currPos));
 
 	/*
 	 * Always reset the scan state, so we don't look for same items on other
 	 * pages.
 	 */
-	so->numKilled = 0;
+	state->numKilled = 0;
 
-	if (BTScanPosIsPinned(so->currPos))
+	if (BTScanPosIsPinned(*pos))
 	{
 		/*
 		 * We have held the pin on this page since we read the index tuples,
@@ -1768,44 +1768,42 @@ _bt_killitems(IndexScanDesc scan)
 		 * re-use of any TID on the page, so there is no need to check the
 		 * LSN.
 		 */
-		LockBuffer(so->currPos.buf, BT_READ);
-
-		page = BufferGetPage(so->currPos.buf);
+		LockBuffer(pos->buf, BT_READ);
 	}
 	else
 	{
 		Buffer		buf;
 
 		/* Attempt to re-read the buffer, getting pin and lock. */
-		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ);
+		buf = _bt_getbuf(indexRelation, pos->currPage, BT_READ);
 
 		/* It might not exist anymore; in which case we can't hint it. */
 		if (!BufferIsValid(buf))
 			return;
 
-		page = BufferGetPage(buf);
-		if (BufferGetLSNAtomic(buf) == so->currPos.lsn)
-			so->currPos.buf = buf;
+		if (BufferGetLSNAtomic(buf) == pos->lsn)
+			pos->buf = buf;
 		else
 		{
 			/* Modified while not pinned means hinting is not safe. */
-			_bt_relbuf(scan->indexRelation, buf);
+			_bt_relbuf(indexRelation, buf);
 			return;
 		}
 	}
 
+	page = BufferGetPage(pos->buf);
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 	minoff = P_FIRSTDATAKEY(opaque);
 	maxoff = PageGetMaxOffsetNumber(page);
 
 	for (i = 0; i < numKilled; i++)
 	{
-		int			itemIndex = so->killedItems[i];
-		BTScanPosItem *kitem = &so->currPos.items[itemIndex];
+		int			itemIndex = state->killedItems[i];
+		BTScanPosItem *kitem = &pos->items[itemIndex];
 		OffsetNumber offnum = kitem->indexOffset;
 
-		Assert(itemIndex >= so->currPos.firstItem &&
-			   itemIndex <= so->currPos.lastItem);
+		Assert(itemIndex >= pos->firstItem &&
+			   itemIndex <= pos->lastItem);
 		if (offnum < minoff)
 			continue;			/* pure paranoia */
 		while (offnum <= maxoff)
@@ -1833,10 +1831,10 @@ _bt_killitems(IndexScanDesc scan)
 	if (killedsomething)
 	{
 		opaque->btpo_flags |= BTP_HAS_GARBAGE;
-		MarkBufferDirtyHint(so->currPos.buf, true);
+		MarkBufferDirtyHint(pos->buf, true);
 	}
 
-	LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
+	LockBuffer(pos->buf, BUFFER_LOCK_UNLOCK);
 }
 
 
@@ -2214,3 +2212,14 @@ _bt_check_natts(Relation rel, Page page, OffsetNumber offnum)
 
 	}
 }
+
+/*
+ * _bt_allocate_tuple_workspaces() -- Allocate buffers for saving index tuples
+ * 		in index-only scans.
+ */
+void
+_bt_allocate_tuple_workspaces(BTScanState state)
+{
+	state->currTuples = (char *) palloc(BLCKSZ * 2);
+	state->markTuples = state->currTuples + BLCKSZ;
+}
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 4fb92d6..d78df29 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -433,22 +433,8 @@ typedef struct BTArrayKeyInfo
 	Datum	   *elem_values;	/* array of num_elems Datums */
 } BTArrayKeyInfo;
 
-typedef struct BTScanOpaqueData
+typedef struct BTScanStateData
 {
-	/* these fields are set by _bt_preprocess_keys(): */
-	bool		qual_ok;		/* false if qual can never be satisfied */
-	int			numberOfKeys;	/* number of preprocessed scan keys */
-	ScanKey		keyData;		/* array of preprocessed scan keys */
-
-	/* workspace for SK_SEARCHARRAY support */
-	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
-	int			numArrayKeys;	/* number of equality-type array keys (-1 if
-								 * there are any unsatisfiable array keys) */
-	int			arrayKeyCount;	/* count indicating number of array scan keys
-								 * processed */
-	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
-	MemoryContext arrayContext; /* scan-lifespan context for array data */
-
 	/* info about killed items if any (killedItems is NULL if never used) */
 	int		   *killedItems;	/* currPos.items indexes of killed items */
 	int			numKilled;		/* number of currently stored items */
@@ -473,6 +459,27 @@ typedef struct BTScanOpaqueData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+} BTScanStateData;
+
+typedef BTScanStateData *BTScanState;
+
+typedef struct BTScanOpaqueData
+{
+	/* these fields are set by _bt_preprocess_keys(): */
+	bool		qual_ok;		/* false if qual can never be satisfied */
+	int			numberOfKeys;	/* number of preprocessed scan keys */
+	ScanKey		keyData;		/* array of preprocessed scan keys */
+
+	/* workspace for SK_SEARCHARRAY support */
+	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
+	int			numArrayKeys;	/* number of equality-type array keys (-1 if
+								 * there are any unsatisfiable array keys) */
+	int			arrayKeyCount;	/* count indicating number of array scan keys
+								 * processed */
+	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
+	MemoryContext arrayContext; /* scan-lifespan context for array data */
+
+	BTScanStateData	state;
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -589,7 +596,7 @@ extern void _bt_preprocess_keys(IndexScanDesc scan);
 extern IndexTuple _bt_checkkeys(IndexScanDesc scan,
 			  Page page, OffsetNumber offnum,
 			  ScanDirection dir, bool *continuescan);
-extern void _bt_killitems(IndexScanDesc scan);
+extern void _bt_killitems(BTScanState state, Relation indexRelation);
 extern BTCycleId _bt_vacuum_cycleid(Relation rel);
 extern BTCycleId _bt_start_vacuum(Relation rel);
 extern void _bt_end_vacuum(Relation rel);
@@ -602,6 +609,7 @@ extern bool btproperty(Oid index_oid, int attno,
 		   bool *res, bool *isnull);
 extern IndexTuple _bt_nonkey_truncate(Relation rel, IndexTuple itup);
 extern bool _bt_check_natts(Relation rel, Page page, OffsetNumber offnum);
+extern void _bt_allocate_tuple_workspaces(BTScanState state);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3d3c76d..5c065a5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -181,6 +181,8 @@ BTScanOpaqueData
 BTScanPos
 BTScanPosData
 BTScanPosItem
+BTScanState
+BTScanStateData
 BTShared
 BTSortArrayContext
 BTSpool
0004-Add-kNN-support-to-btree-v06.patchtext/x-patch; name=0004-Add-kNN-support-to-btree-v06.patchDownload
diff --git a/doc/src/sgml/btree.sgml b/doc/src/sgml/btree.sgml
index 996932e..613032e 100644
--- a/doc/src/sgml/btree.sgml
+++ b/doc/src/sgml/btree.sgml
@@ -200,6 +200,19 @@
   planner relies on them for optimization purposes.
  </para>
 
+ <para>
+  To implement the distance ordered (nearest-neighbor) search, we only need
+  to define a distance operator (usually it called &lt;-&gt;) with a correpsonding
+  operator family for distance comparison in the index's operator class.
+  These operators must satisfy the following assumptions for all non-null
+  values A,B,C of the datatype:
+
+  A &lt;-&gt; B = B &lt;-&gt; A						symmetric law
+  if A = B, then A &lt;-&gt; C = B &lt;-&gt; C		distance equivalence
+  if (A &lt;= B and B &lt;= C) or (A &gt;= B and B &gt;= C),
+  then A &lt;-&gt; B &lt;= A &lt;-&gt; C			monotonicity
+ </para>
+
 </sect1>
 
 <sect1 id="btree-support-funcs">
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 46f427b..caec484 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -175,6 +175,17 @@ CREATE INDEX test1_id_index ON test1 (id);
   </para>
 
   <para>
+   B-tree indexes are also capable of optimizing <quote>nearest-neighbor</>
+   searches, such as
+<programlisting><![CDATA[
+SELECT * FROM events ORDER BY event_date <-> date '2017-05-05' LIMIT 10;
+]]>
+</programlisting>
+   which finds the ten events closest to a given target date.  The ability
+   to do this is again dependent on the particular operator class being used.
+  </para>
+
+  <para>
    <indexterm>
     <primary>index</primary>
     <secondary>hash</secondary>
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 9446f8b..93094bc 100644
--- a/doc/src/sgml/xindex.sgml
+++ b/doc/src/sgml/xindex.sgml
@@ -1242,7 +1242,8 @@ SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
   <title>Ordering Operators</title>
 
   <para>
-   Some index access methods (currently, only GiST and SP-GiST) support the concept of
+   Some index access methods (currently, only B-tree, GiST and SP-GiST) 
+   support the concept of
    <firstterm>ordering operators</firstterm>.  What we have been discussing so far
    are <firstterm>search operators</firstterm>.  A search operator is one for which
    the index can be searched to find all rows satisfying
diff --git a/src/backend/access/nbtree/README b/src/backend/access/nbtree/README
index 3680e69..3f7e1b1 100644
--- a/src/backend/access/nbtree/README
+++ b/src/backend/access/nbtree/README
@@ -659,3 +659,20 @@ routines must treat it accordingly.  The actual key stored in the
 item is irrelevant, and need not be stored at all.  This arrangement
 corresponds to the fact that an L&Y non-leaf page has one more pointer
 than key.
+
+Nearest-neighbor search
+-----------------------
+
+There is a special scan strategy for nearest-neighbor (kNN) search,
+that is used in queries with ORDER BY distance clauses like this:
+SELECT * FROM tab WHERE col > const1 ORDER BY col <-> const2 LIMIT k.
+But, unlike GiST, B-tree supports only a one ordering operator on the
+first index column.
+
+At the beginning of kNN scan, we need to determine which strategy we
+will use --- a special bidirectional or a ordinary unidirectional.
+If the point from which we measure the distance falls into the scan range,
+we use bidirectional scan starting from this point, else we use simple
+unidirectional scan in the right direction.  Algorithm of a bidirectional
+scan is very simple: at each step we advancing scan in that direction,
+which has the nearest point.
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index bf6a6c6..fb413e7 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -25,6 +25,9 @@
 #include "commands/vacuum.h"
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
+#include "nodes/pathnodes.h"
+#include "nodes/primnodes.h"
+#include "optimizer/paths.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "storage/condition_variable.h"
@@ -33,7 +36,9 @@
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/index_selfuncs.h"
+#include "utils/lsyscache.h"
 #include "utils/memutils.h"
 
 
@@ -79,6 +84,7 @@ typedef enum
 typedef struct BTParallelScanDescData
 {
 	BlockNumber btps_scanPage;	/* latest or next page to be scanned */
+	BlockNumber btps_knnScanPage;	/* secondary kNN page to be scanned */
 	BTPS_State	btps_pageStatus;	/* indicates whether next page is
 									 * available for scan. see above for
 									 * possible states of parallel scan. */
@@ -97,6 +103,10 @@ static void btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 static void btvacuumpage(BTVacState *vstate, BlockNumber blkno,
 			 BlockNumber orig_blkno);
 
+static bool btmatchorderby(IndexOptInfo *index, List *pathkeys,
+			   List *index_clauses, List **orderby_clauses_p,
+			   List **orderby_clause_columns_p);
+
 
 /*
  * Btree handler function: return IndexAmRoutine with access method parameters
@@ -107,7 +117,7 @@ bthandler(PG_FUNCTION_ARGS)
 {
 	IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
 
-	amroutine->amstrategies = BTMaxStrategyNumber;
+	amroutine->amstrategies = BtreeMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
 	amroutine->amcanbackward = true;
@@ -143,7 +153,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = btestimateparallelscan;
 	amroutine->aminitparallelscan = btinitparallelscan;
 	amroutine->amparallelrescan = btparallelrescan;
-	amroutine->ammatchorderby = NULL;
+	amroutine->ammatchorderby = btmatchorderby;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -215,23 +225,30 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTScanState state = &so->state;
+	ScanDirection arraydir =
+	scan->numberOfOrderBys > 0 ? ForwardScanDirection : dir;
 	bool		res;
 
 	/* btree indexes are never lossy */
 	scan->xs_recheck = false;
+	scan->xs_recheckorderby = false;
+
+	if (so->scanDirection != NoMovementScanDirection)
+		dir = so->scanDirection;
 
 	/*
 	 * If we have any array keys, initialize them during first call for a
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos) &&
+		(!so->knnState || !BTScanPosIsValid(so->knnState->currPos)))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
 			return false;
 
-		_bt_start_array_keys(scan, dir);
+		_bt_start_array_keys(scan, arraydir);
 	}
 
 	/* This loop handles advancing to the next array elements, if any */
@@ -242,7 +259,8 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(state->currPos))
+		if (!BTScanPosIsValid(state->currPos) &&
+			(!so->knnState || !BTScanPosIsValid(so->knnState->currPos)))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -277,7 +295,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		if (res)
 			break;
 		/* ... otherwise see if we have more array keys to deal with */
-	} while (so->numArrayKeys && _bt_advance_array_keys(scan, dir));
+	} while (so->numArrayKeys && _bt_advance_array_keys(scan, arraydir));
 
 	return res;
 }
@@ -350,9 +368,6 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	IndexScanDesc scan;
 	BTScanOpaque so;
 
-	/* no order by operators allowed */
-	Assert(norderbys == 0);
-
 	/* get the scan */
 	scan = RelationGetIndexScan(rel, nkeys, norderbys);
 
@@ -379,6 +394,9 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
 	so->state.currTuples = so->state.markTuples = NULL;
+	so->knnState = NULL;
+	so->distanceTypeByVal = true;
+	so->scanDirection = NoMovementScanDirection;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -408,6 +426,8 @@ _bt_release_current_position(BTScanState state, Relation indexRelation,
 static void
 _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	/* No need to invalidate positions, if the RAM is about to be freed. */
 	_bt_release_current_position(state, scan->indexRelation, !free);
 
@@ -424,6 +444,17 @@ _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 	}
 	else
 		BTScanPosInvalidate(state->markPos);
+
+	if (!so->distanceTypeByVal)
+	{
+		if (DatumGetPointer(state->currDistance))
+			pfree(DatumGetPointer(state->currDistance));
+		state->currDistance = PointerGetDatum(NULL);
+
+		if (DatumGetPointer(state->markDistance))
+			pfree(DatumGetPointer(state->markDistance));
+		state->markDistance = PointerGetDatum(NULL);
+	}
 }
 
 /*
@@ -438,6 +469,13 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 
 	_bt_release_scan_state(scan, state, false);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+		so->knnState = NULL;
+	}
+
 	so->arrayKeyCount = 0;
 
 	/*
@@ -469,6 +507,14 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 				scan->numberOfKeys * sizeof(ScanKeyData));
 	so->numberOfKeys = 0;		/* until _bt_preprocess_keys sets it */
 
+	if (orderbys && scan->numberOfOrderBys > 0)
+		memmove(scan->orderByData,
+				orderbys,
+				scan->numberOfOrderBys * sizeof(ScanKeyData));
+
+	so->scanDirection = NoMovementScanDirection;
+	so->distanceTypeByVal = true;
+
 	/* If any keys are SK_SEARCHARRAY type, set up array-key info */
 	_bt_preprocess_array_keys(scan);
 }
@@ -483,6 +529,12 @@ btendscan(IndexScanDesc scan)
 
 	_bt_release_scan_state(scan, &so->state, true);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+	}
+
 	/* Release storage */
 	if (so->keyData != NULL)
 		pfree(so->keyData);
@@ -494,7 +546,7 @@ btendscan(IndexScanDesc scan)
 }
 
 static void
-_bt_mark_current_position(BTScanState state)
+_bt_mark_current_position(BTScanOpaque so, BTScanState state)
 {
 	/* There may be an old mark with a pin (but no lock). */
 	BTScanPosUnpinIfPinned(state->markPos);
@@ -512,6 +564,21 @@ _bt_mark_current_position(BTScanState state)
 		BTScanPosInvalidate(state->markPos);
 		state->markItemIndex = -1;
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->markDistance));
+
+		state->markIsNull = !BTScanPosIsValid(state->currPos) ||
+			state->currIsNull;
+
+		state->markDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->currDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -522,7 +589,13 @@ btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_mark_current_position(&so->state);
+	_bt_mark_current_position(so, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_mark_current_position(so, so->knnState);
+		so->markRightIsNearest = so->currRightIsNearest;
+	}
 
 	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
@@ -532,6 +605,8 @@ btmarkpos(IndexScanDesc scan)
 static void
 _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	if (state->markItemIndex >= 0)
 	{
 		/*
@@ -567,6 +642,19 @@ _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 					   state->markPos.nextTupleOffset);
 		}
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->currDistance));
+
+		state->currIsNull = state->markIsNull;
+		state->currDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->markDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -588,6 +676,7 @@ btinitparallelscan(void *target)
 
 	SpinLockInit(&bt_target->btps_mutex);
 	bt_target->btps_scanPage = InvalidBlockNumber;
+	bt_target->btps_knnScanPage = InvalidBlockNumber;
 	bt_target->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	bt_target->btps_arrayKeyCount = 0;
 	ConditionVariableInit(&bt_target->btps_cv);
@@ -614,6 +703,7 @@ btparallelrescan(IndexScanDesc scan)
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
 	btscan->btps_scanPage = InvalidBlockNumber;
+	btscan->btps_knnScanPage = InvalidBlockNumber;
 	btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	btscan->btps_arrayKeyCount = 0;
 	SpinLockRelease(&btscan->btps_mutex);
@@ -638,7 +728,7 @@ btparallelrescan(IndexScanDesc scan)
  * Callers should ignore the value of pageno if the return value is false.
  */
 bool
-_bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
+_bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTPS_State	pageStatus;
@@ -646,12 +736,17 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
 	bool		status = true;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
 
 	*pageno = P_NONE;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	scanPage = state == &so->state
+		? &btscan->btps_scanPage
+		: &btscan->btps_knnScanPage;
+
 	while (1)
 	{
 		SpinLockAcquire(&btscan->btps_mutex);
@@ -677,7 +772,7 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
 			 * of advancing it to a new page!
 			 */
 			btscan->btps_pageStatus = BTPARALLEL_ADVANCING;
-			*pageno = btscan->btps_scanPage;
+			*pageno = *scanPage;
 			exit_loop = true;
 		}
 		SpinLockRelease(&btscan->btps_mutex);
@@ -696,19 +791,42 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
  *		can now begin advancing the scan.
  */
 void
-_bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
+_bt_parallel_release(IndexScanDesc scan, BTScanState state,
+					 BlockNumber scan_page)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
+	bool		status_changed = false;
+	bool		knnScan = so->knnState != NULL;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
+	if (!state || state == &so->state)
+	{
+		scanPage = &btscan->btps_scanPage;
+		otherScanPage = &btscan->btps_knnScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_knnScanPage;
+		otherScanPage = &btscan->btps_scanPage;
+	}
 
 	SpinLockAcquire(&btscan->btps_mutex);
-	btscan->btps_scanPage = scan_page;
-	btscan->btps_pageStatus = BTPARALLEL_IDLE;
+	*scanPage = scan_page;
+	/* switch to idle state only if both KNN pages are initialized */
+	if (!knnScan || *otherScanPage != InvalidBlockNumber)
+	{
+		btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		status_changed = true;
+	}
 	SpinLockRelease(&btscan->btps_mutex);
-	ConditionVariableSignal(&btscan->btps_cv);
+
+	if (status_changed)
+		ConditionVariableSignal(&btscan->btps_cv);
 }
 
 /*
@@ -719,12 +837,15 @@ _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
  * advance to the next page.
  */
 void
-_bt_parallel_done(IndexScanDesc scan)
+_bt_parallel_done(IndexScanDesc scan, BTScanState state)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
 	bool		status_changed = false;
+	bool		knnScan = so->knnState != NULL;
 
 	/* Do nothing, for non-parallel scans */
 	if (parallel_scan == NULL)
@@ -733,18 +854,41 @@ _bt_parallel_done(IndexScanDesc scan)
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	if (!state || state == &so->state)
+	{
+		scanPage = &btscan->btps_scanPage;
+		otherScanPage = &btscan->btps_knnScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_knnScanPage;
+		otherScanPage = &btscan->btps_scanPage;
+	}
+
 	/*
 	 * Mark the parallel scan as done for this combination of scan keys,
 	 * unless some other process already did so.  See also
 	 * _bt_advance_array_keys.
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
-	if (so->arrayKeyCount >= btscan->btps_arrayKeyCount &&
-		btscan->btps_pageStatus != BTPARALLEL_DONE)
+
+	Assert(btscan->btps_pageStatus == BTPARALLEL_ADVANCING);
+
+	if (so->arrayKeyCount >= btscan->btps_arrayKeyCount)
 	{
-		btscan->btps_pageStatus = BTPARALLEL_DONE;
+		*scanPage = P_NONE;
 		status_changed = true;
+
+		/* switch to "done" state only if both KNN scans are done */
+		if (!knnScan || *otherScanPage == P_NONE)
+			btscan->btps_pageStatus = BTPARALLEL_DONE;
+		/* else switch to "idle" state only if both KNN scans are initialized */
+		else if (*otherScanPage != InvalidBlockNumber)
+			btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		else
+			status_changed = false;
 	}
+
 	SpinLockRelease(&btscan->btps_mutex);
 
 	/* wake up all the workers associated with this parallel scan */
@@ -774,6 +918,7 @@ _bt_parallel_advance_array_keys(IndexScanDesc scan)
 	if (btscan->btps_pageStatus == BTPARALLEL_DONE)
 	{
 		btscan->btps_scanPage = InvalidBlockNumber;
+		btscan->btps_knnScanPage = InvalidBlockNumber;
 		btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 		btscan->btps_arrayKeyCount++;
 	}
@@ -859,6 +1004,12 @@ btrestrpos(IndexScanDesc scan)
 		_bt_restore_array_keys(scan);
 
 	_bt_restore_marked_position(scan, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_restore_marked_position(scan, so->knnState);
+		so->currRightIsNearest = so->markRightIsNearest;
+	}
 }
 
 /*
@@ -1394,3 +1545,91 @@ btcanreturn(Relation index, int attno)
 {
 	return true;
 }
+
+/*
+ *	btmatchorderby() -- Check whether KNN-search strategy is applicable to
+ *		the given ORDER BY distance operator.
+ */
+static bool
+btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
+			   List **orderby_clauses_p, List **orderby_clausecols_p)
+{
+	Expr	   *expr;
+	ListCell   *lc;
+	int			indexcol;
+	int			num_eq_cols = 0;
+
+	/* only one ORDER BY clause is supported */
+	if (list_length(pathkeys) != 1)
+		return false;
+
+	/*
+	 * Compute a number of leading consequent index columns with equality
+	 * restriction clauses.
+	 */
+	foreach(lc, index_clauses)
+	{
+		IndexClause *iclause = lfirst_node(IndexClause, lc);
+		ListCell   *lcq;
+
+		indexcol = iclause->indexcol;
+
+		if (indexcol > num_eq_cols)
+			/* Sequence of equality-restricted columns is broken. */
+			break;
+
+		foreach(lcq, iclause->indexquals)
+		{
+			RestrictInfo *rinfo = lfirst_node(RestrictInfo, lcq);
+			Expr	   *clause = rinfo->clause;
+			Oid			opno;
+			StrategyNumber strat;
+
+			if (!clause)
+				continue;
+
+			if (IsA(clause, OpExpr))
+			{
+				OpExpr	   *opexpr = (OpExpr *) clause;
+
+				opno = opexpr->opno;
+			}
+			else
+			{
+				/* Skip unsupported expression */
+				continue;
+			}
+
+			/* Check if the operator is btree equality operator. */
+			strat = get_op_opfamily_strategy(opno, index->opfamily[indexcol]);
+
+			if (strat == BTEqualStrategyNumber)
+				num_eq_cols = indexcol + 1;
+		}
+	}
+
+	/*
+	 * If there are no equality columns try to match only the first column,
+	 * otherwise try all columns.
+	 */
+	indexcol = num_eq_cols ? -1 : 0;
+
+	expr = match_orderbyop_pathkey(index, castNode(PathKey, linitial(pathkeys)),
+								   &indexcol);
+
+	if (!expr)
+		return false;
+
+	/*
+	 * ORDER BY distance is supported only for the first index column or if
+	 * all previous columns have equality restrictions.
+	 */
+	if (indexcol > num_eq_cols)
+		return false;
+
+	/* Return first ORDER BY clause's expression and column. */
+	*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+	*orderby_clausecols_p = lappend_int(*orderby_clausecols_p, indexcol);
+
+	return true;
+}
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index cbd72bd..28f94e7 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -31,12 +31,14 @@ static void _bt_saveitem(BTScanState state, int itemIndex,
 static bool _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir);
 static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
 				 BlockNumber blkno, ScanDirection dir);
-static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
-					  ScanDirection dir);
+static bool _bt_parallel_readpage(IndexScanDesc scan, BTScanState state,
+					  BlockNumber blkno, ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf, Snapshot snapshot);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
 static inline void _bt_initialize_more_data(BTScanState state, ScanDirection dir);
+static BTScanState _bt_alloc_knn_scan(IndexScanDesc scan);
+static bool _bt_start_knn_scan(IndexScanDesc scan, bool left, bool right);
 
 
 /*
@@ -597,6 +599,157 @@ _bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 }
 
 /*
+ *  _bt_calc_current_dist() -- Calculate distance from the current item
+ *		of the scan state to the target order-by ScanKey argument.
+ */
+static void
+_bt_calc_current_dist(IndexScanDesc scan, BTScanState state)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanPosItem  *currItem = &state->currPos.items[state->currPos.itemIndex];
+	IndexTuple		itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+	ScanKey			scankey = &scan->orderByData[0];
+	Datum			value;
+
+	value = index_getattr(itup, scankey->sk_attno, scan->xs_itupdesc,
+						  &state->currIsNull);
+
+	if (state->currIsNull)
+		return; /* NULL distance */
+
+	value = FunctionCall2Coll(&scankey->sk_func,
+							  scankey->sk_collation,
+							  value,
+							  scankey->sk_argument);
+
+	/* free previous distance value for by-ref types */
+	if (!so->distanceTypeByVal && DatumGetPointer(state->currDistance))
+		pfree(DatumGetPointer(state->currDistance));
+
+	state->currDistance = value;
+}
+
+/*
+ *  _bt_compare_current_dist() -- Compare current distances of the left and right scan states.
+ *
+ *  NULL distances are considered to be greater than any non-NULL distances.
+ *
+ *  Returns true if right distance is lesser than left, otherwise false.
+ */
+static bool
+_bt_compare_current_dist(BTScanOpaque so, BTScanState rstate, BTScanState lstate)
+{
+	if (lstate->currIsNull)
+		return true; /* non-NULL < NULL */
+
+	if (rstate->currIsNull)
+		return false; /* NULL > non-NULL */
+
+	return DatumGetBool(FunctionCall2Coll(&so->distanceCmpProc,
+										  InvalidOid, /* XXX collation for distance comparison */
+										  rstate->currDistance,
+										  lstate->currDistance));
+}
+
+/*
+ * _bt_alloc_knn_scan() -- Allocate additional backward scan state for KNN.
+ */
+static BTScanState
+_bt_alloc_knn_scan(IndexScanDesc scan)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		lstate = (BTScanState) palloc(sizeof(BTScanStateData));
+
+	_bt_allocate_tuple_workspaces(lstate);
+
+	if (!scan->xs_want_itup)
+	{
+		/* We need to request index tuples for distance comparison. */
+		scan->xs_want_itup = true;
+		_bt_allocate_tuple_workspaces(&so->state);
+	}
+
+	BTScanPosInvalidate(lstate->currPos);
+	lstate->currPos.moreLeft = false;
+	lstate->currPos.moreRight = false;
+	BTScanPosInvalidate(lstate->markPos);
+	lstate->markItemIndex = -1;
+	lstate->killedItems = NULL;
+	lstate->numKilled = 0;
+	lstate->currDistance = PointerGetDatum(NULL);
+	lstate->markDistance = PointerGetDatum(NULL);
+
+	return so->knnState = lstate;
+}
+
+static bool
+_bt_start_knn_scan(IndexScanDesc scan, bool left, bool right)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		rstate; /* right (forward) main scan state */
+	BTScanState		lstate; /* additional left (backward) KNN scan state */
+
+	if (!left && !right)
+		return false; /* empty result */
+
+	rstate = &so->state;
+	lstate = so->knnState;
+
+	if (left && right)
+	{
+		/*
+		 * We have found items in both scan directions,
+		 * determine nearest item to return.
+		 */
+		_bt_calc_current_dist(scan, rstate);
+		_bt_calc_current_dist(scan, lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+
+		/* Reset right flag if the left item is nearer. */
+		right = so->currRightIsNearest;
+	}
+
+	/* Return current item of the selected scan direction. */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
+ * _bt_init_knn_scan() -- Init additional scan state for KNN search.
+ *
+ * Caller must pin and read-lock scan->state.currPos.buf buffer.
+ *
+ * If empty result was found returned false.
+ * Otherwise prepared current item, and returned true.
+ */
+static bool
+_bt_init_knn_scan(IndexScanDesc scan, OffsetNumber offnum)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		rstate = &so->state; /* right (forward) main scan state */
+	BTScanState		lstate; /* additional left (backward) KNN scan state */
+	Buffer			buf = rstate->currPos.buf;
+	bool			left,
+					right;
+
+	lstate = _bt_alloc_knn_scan(scan);
+
+	/* Bump pin and lock count before BTScanPosData copying. */
+	IncrBufferRefCount(buf);
+	LockBuffer(buf, BT_READ);
+
+	memcpy(&lstate->currPos, &rstate->currPos, sizeof(BTScanPosData));
+	lstate->currPos.moreLeft = true;
+	lstate->currPos.moreRight = false;
+
+	/* Load first pages from the both scans. */
+	right = _bt_load_first_page(scan, rstate, ForwardScanDirection, offnum);
+	left = _bt_load_first_page(scan, lstate, BackwardScanDirection,
+							   OffsetNumberPrev(offnum));
+
+	return _bt_start_knn_scan(scan, left, right);
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -654,6 +807,15 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	if (!so->qual_ok)
 		return false;
 
+	if (scan->numberOfOrderBys > 0)
+	{
+		if (so->useBidirectionalKnnScan)
+			_bt_init_distance_comparison(scan);
+		else if (so->scanDirection != NoMovementScanDirection)
+			/* use selected KNN scan direction */
+			dir = so->scanDirection;
+	}
+
 	/*
 	 * For parallel scans, get the starting page from shared state. If the
 	 * scan has not started, proceed to find out first leaf page in the usual
@@ -662,19 +824,50 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 */
 	if (scan->parallel_scan != NULL)
 	{
-		status = _bt_parallel_seize(scan, &blkno);
+		status = _bt_parallel_seize(scan, &so->state, &blkno);
 		if (!status)
 			return false;
-		else if (blkno == P_NONE)
-		{
-			_bt_parallel_done(scan);
-			return false;
-		}
 		else if (blkno != InvalidBlockNumber)
 		{
-			if (!_bt_parallel_readpage(scan, blkno, dir))
-				return false;
-			goto readcomplete;
+			bool		knn = so->useBidirectionalKnnScan;
+			bool		right;
+			bool		left;
+
+			if (knn)
+				_bt_alloc_knn_scan(scan);
+
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, &so->state);
+				right = false;
+			}
+			else
+				right = _bt_parallel_readpage(scan, &so->state, blkno,
+											  knn ? ForwardScanDirection : dir);
+
+			if (!knn)
+				return right && _bt_return_current_item(scan, &so->state);
+
+			/* seize additional backward KNN scan */
+			left = _bt_parallel_seize(scan, so->knnState, &blkno);
+
+			if (left)
+			{
+				if (blkno == P_NONE)
+				{
+					_bt_parallel_done(scan, so->knnState);
+					left = false;
+				}
+				else
+				{
+					/* backward scan should be already initialized */
+					Assert(blkno != InvalidBlockNumber);
+					left = _bt_parallel_readpage(scan, so->knnState, blkno,
+												 BackwardScanDirection);
+				}
+			}
+
+			return _bt_start_knn_scan(scan, left, right);
 		}
 	}
 
@@ -724,7 +917,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 * storing their addresses into the local startKeys[] array.
 	 *----------
 	 */
+
 	strat_total = BTEqualStrategyNumber;
+
 	if (so->numberOfKeys > 0)
 	{
 		AttrNumber	curattr;
@@ -749,6 +944,10 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 */
 		for (cur = so->keyData, i = 0;; cur++, i++)
 		{
+			if (so->useBidirectionalKnnScan &&
+				curattr >= scan->orderByData->sk_attno)
+				break;
+
 			if (i >= so->numberOfKeys || cur->sk_attno != curattr)
 			{
 				/*
@@ -851,6 +1050,16 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		}
 	}
 
+	if (so->useBidirectionalKnnScan)
+	{
+		Assert(strat_total == BTEqualStrategyNumber);
+		strat_total = BtreeKNNSearchStrategyNumber;
+
+		(void) _bt_init_knn_start_keys(scan, &startKeys[keysCount],
+									   &notnullkeys[keysCount]);
+		keysCount++;
+	}
+
 	/*
 	 * If we found no usable boundary keys, we have to start from one end of
 	 * the tree.  Walk down that edge to the first or last key, and scan from
@@ -865,7 +1074,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		if (!match)
 		{
 			/* No match, so mark (parallel) scan finished */
-			_bt_parallel_done(scan);
+			_bt_parallel_done(scan, NULL);
 		}
 
 		return match;
@@ -900,7 +1109,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			Assert(subkey->sk_flags & SK_ROW_MEMBER);
 			if (subkey->sk_flags & SK_ISNULL)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, NULL);
 				return false;
 			}
 			memcpy(scankeys + i, subkey, sizeof(ScanKeyData));
@@ -1080,6 +1289,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			break;
 
 		case BTGreaterEqualStrategyNumber:
+		case BtreeKNNSearchStrategyNumber:
 
 			/*
 			 * Find first item >= scankey.  (This is only used for forward
@@ -1127,7 +1337,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 * mark parallel scan as done, so that all the workers can finish
 		 * their scan
 		 */
-		_bt_parallel_done(scan);
+		_bt_parallel_done(scan, NULL);
 		BTScanPosInvalidate(*currPos);
 
 		return false;
@@ -1166,17 +1376,21 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	Assert(!BTScanPosIsValid(*currPos));
 	currPos->buf = buf;
 
+	if (strat_total == BtreeKNNSearchStrategyNumber)
+		return _bt_init_knn_scan(scan, offnum);
+
 	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
-		return false;
+		return false; /* empty result */
 
-readcomplete:
 	/* OK, currPos->itemIndex says what to return */
 	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
- *	Advance to next tuple on current page; or if there's no more,
- *	try to step to the next page with data.
+ *	_bt_next_item() -- Advance to next tuple on current page;
+ *		or if there's no more, try to step to the next page with data.
+ *
+ *	If there are no more matching records in the given direction
  */
 static bool
 _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
@@ -1196,6 +1410,51 @@ _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 }
 
 /*
+ *	_bt_next_nearest() -- Return next nearest item from bidirectional KNN scan.
+ */
+static bool
+_bt_next_nearest(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState rstate = &so->state;
+	BTScanState lstate = so->knnState;
+	bool		right = BTScanPosIsValid(rstate->currPos);
+	bool		left = BTScanPosIsValid(lstate->currPos);
+	bool		advanceRight;
+
+	if (right && left)
+		advanceRight = so->currRightIsNearest;
+	else if (right)
+		advanceRight = true;
+	else if (left)
+		advanceRight = false;
+	else
+		return false; /* end of the scan */
+
+	if (advanceRight)
+		right = _bt_next_item(scan, rstate, ForwardScanDirection);
+	else
+		left = _bt_next_item(scan, lstate, BackwardScanDirection);
+
+	if (!left && !right)
+		return false; /* end of the scan */
+
+	if (left && right)
+	{
+		/*
+		 * If there are items in both scans we must recalculate distance
+		 * in the advanced scan.
+		 */
+		_bt_calc_current_dist(scan, advanceRight ? rstate : lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+		right = so->currRightIsNearest;
+	}
+
+	/* return nearest item */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
  *	_bt_next() -- Get the next item in a scan.
  *
  *		On entry, so->currPos describes the current page, which may be pinned
@@ -1214,6 +1473,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
+	if (so->knnState)
+		/* return next neareset item from KNN scan */
+		return _bt_next_nearest(scan);
+
 	if (!_bt_next_item(scan, &so->state, dir))
 		return false;
 
@@ -1266,9 +1529,9 @@ _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 	if (scan->parallel_scan)
 	{
 		if (ScanDirectionIsForward(dir))
-			_bt_parallel_release(scan, opaque->btpo_next);
+			_bt_parallel_release(scan, state, opaque->btpo_next);
 		else
-			_bt_parallel_release(scan, BufferGetBlockNumber(pos->buf));
+			_bt_parallel_release(scan, state, BufferGetBlockNumber(pos->buf));
 	}
 
 	minoff = P_FIRSTDATAKEY(opaque);
@@ -1442,7 +1705,7 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the next block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno);
+			status = _bt_parallel_seize(scan, state, &blkno);
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
@@ -1474,13 +1737,19 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the current block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno);
+			status = _bt_parallel_seize(scan, state, &blkno);
 			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, state);
+				BTScanPosInvalidate(*currPos);
+				return false;
+			}
 		}
 		else
 		{
@@ -1530,7 +1799,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			 */
 			if (blkno == P_NONE || !currPos->moreRight)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1553,14 +1822,14 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, opaque->btpo_next);
+				_bt_parallel_release(scan, state, opaque->btpo_next);
 			}
 
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno);
+				status = _bt_parallel_seize(scan, state, &blkno);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -1619,7 +1888,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (!currPos->moreLeft)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1630,7 +1899,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			/* if we're physically at end of index, return failure */
 			if (currPos->buf == InvalidBuffer)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1654,7 +1923,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
+				_bt_parallel_release(scan, state, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -1666,7 +1935,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno);
+				status = _bt_parallel_seize(scan, state, &blkno);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -1687,17 +1956,16 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
  * indicate success.
  */
 static bool
-_bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_parallel_readpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+					  ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	_bt_initialize_more_data(state, dir);
 
-	_bt_initialize_more_data(&so->state, dir);
-
-	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
 
 	return true;
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index e548354..5b9294b 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -20,16 +20,21 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "catalog/pg_amop.h"
 #include "miscadmin.h"
 #include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 
 
 typedef struct BTSortArrayContext
 {
 	FmgrInfo	flinfo;
+	FmgrInfo	distflinfo;
+	FmgrInfo	distcmpflinfo;
+	ScanKey		distkey;
 	Oid			collation;
 	bool		reverse;
 } BTSortArrayContext;
@@ -49,6 +54,11 @@ static void _bt_mark_scankey_required(ScanKey skey);
 static bool _bt_check_rowcompare(ScanKey skey,
 					 IndexTuple tuple, TupleDesc tupdesc,
 					 ScanDirection dir, bool *continuescan);
+static inline StrategyNumber _bt_select_knn_strategy_for_key(IndexScanDesc scan,
+								ScanKey cond);
+static void _bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype,
+						  FmgrInfo *finfo, int16 *typlen, bool *typbyval);
+
 
 
 /*
@@ -445,6 +455,7 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 {
 	Relation	rel = scan->indexRelation;
 	Oid			elemtype;
+	Oid			opfamily;
 	RegProcedure cmp_proc;
 	BTSortArrayContext cxt;
 	int			last_non_dup;
@@ -462,6 +473,54 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 	if (elemtype == InvalidOid)
 		elemtype = rel->rd_opcintype[skey->sk_attno - 1];
 
+	opfamily = rel->rd_opfamily[skey->sk_attno - 1];
+
+	if (scan->numberOfOrderBys <= 0 ||
+		scan->orderByData[0].sk_attno != skey->sk_attno)
+	{
+		cxt.distkey = NULL;
+		cxt.reverse = reverse;
+	}
+	else
+	{
+		/* Init procedures for distance calculation and comparison. */
+		ScanKey		distkey = &scan->orderByData[0];
+		ScanKeyData	distkey2;
+		Oid			disttype = distkey->sk_subtype;
+		Oid			distopr;
+		RegProcedure distproc;
+
+		if (!OidIsValid(disttype))
+			disttype = rel->rd_opcintype[skey->sk_attno - 1];
+
+		/* Lookup distance operator in index column's operator family. */
+		distopr = get_opfamily_member(opfamily,
+									  elemtype,
+									  disttype,
+									  distkey->sk_strategy);
+
+		if (!OidIsValid(distopr))
+			elog(ERROR, "missing operator (%u,%u) for strategy %d in opfamily %u",
+				 elemtype, disttype, BtreeKNNSearchStrategyNumber, opfamily);
+
+		distproc = get_opcode(distopr);
+
+		if (!RegProcedureIsValid(distproc))
+			elog(ERROR, "missing code for operator %u", distopr);
+
+		fmgr_info(distproc, &cxt.distflinfo);
+
+		distkey2 = *distkey;
+		fmgr_info_copy(&distkey2.sk_func, &cxt.distflinfo, CurrentMemoryContext);
+		distkey2.sk_subtype = disttype;
+
+		_bt_get_distance_cmp_proc(&distkey2, opfamily, elemtype,
+								  &cxt.distcmpflinfo, NULL, NULL);
+
+		cxt.distkey = distkey;
+		cxt.reverse = false;	/* supported only ascending ordering */
+	}
+
 	/*
 	 * Look up the appropriate comparison function in the opfamily.
 	 *
@@ -470,19 +529,17 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 	 * non-cross-type support functions for any datatype that it supports at
 	 * all.
 	 */
-	cmp_proc = get_opfamily_proc(rel->rd_opfamily[skey->sk_attno - 1],
+	cmp_proc = get_opfamily_proc(opfamily,
 								 elemtype,
 								 elemtype,
 								 BTORDER_PROC);
 	if (!RegProcedureIsValid(cmp_proc))
 		elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
-			 BTORDER_PROC, elemtype, elemtype,
-			 rel->rd_opfamily[skey->sk_attno - 1]);
+			 BTORDER_PROC, elemtype, elemtype, opfamily);
 
 	/* Sort the array elements */
 	fmgr_info(cmp_proc, &cxt.flinfo);
 	cxt.collation = skey->sk_collation;
-	cxt.reverse = reverse;
 	qsort_arg((void *) elems, nelems, sizeof(Datum),
 			  _bt_compare_array_elements, (void *) &cxt);
 
@@ -514,6 +571,23 @@ _bt_compare_array_elements(const void *a, const void *b, void *arg)
 	BTSortArrayContext *cxt = (BTSortArrayContext *) arg;
 	int32		compare;
 
+	if (cxt->distkey)
+	{
+		Datum		dista = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  da,
+											  cxt->distkey->sk_argument);
+		Datum		distb = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  db,
+											  cxt->distkey->sk_argument);
+		bool		cmp = DatumGetBool(FunctionCall2Coll(&cxt->distcmpflinfo,
+														 cxt->collation,
+														 dista,
+														 distb));
+		return cmp ? -1 : 1;
+	}
+
 	compare = DatumGetInt32(FunctionCall2Coll(&cxt->flinfo,
 											  cxt->collation,
 											  da, db));
@@ -667,6 +741,66 @@ _bt_restore_array_keys(IndexScanDesc scan)
 	}
 }
 
+/*
+ * _bt_emit_scan_key() -- Emit one prepared scan key
+ *
+ * Push the scan key into the so->keyData[] array, and then mark it if it is
+ * required.  Also update selected kNN strategy.
+ */
+static void
+_bt_emit_scan_key(IndexScanDesc scan, ScanKey skey, int numberOfEqualCols)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	ScanKey		outkey = &so->keyData[so->numberOfKeys++];
+
+	memcpy(outkey, skey, sizeof(ScanKeyData));
+
+	/*
+	 * We can mark the qual as required (possibly only in one direction) if all
+	 * attrs before this one had "=".
+	 */
+	if (outkey->sk_attno - 1 == numberOfEqualCols)
+		_bt_mark_scankey_required(outkey);
+
+	/* Update kNN strategy if it is not already selected. */
+	if (so->useBidirectionalKnnScan)
+	{
+		switch (_bt_select_knn_strategy_for_key(scan, outkey))
+		{
+			case BTLessStrategyNumber:
+			case BTLessEqualStrategyNumber:
+				/*
+				 * Ordering key argument is greater than all values in scan
+				 * range, select backward scan direction.
+				 */
+				so->scanDirection = BackwardScanDirection;
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BTEqualStrategyNumber:
+				/* Use default unidirectional scan direction. */
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+				/*
+				 * Ordering key argument is lesser than all values in scan
+				 * range, select forward scan direction.
+				 */
+				so->scanDirection = ForwardScanDirection;
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BtreeKNNSearchStrategyNumber:
+				/*
+				 * Ordering key argument falls into scan range,
+				 * keep using bidirectional scan.
+				 */
+				break;
+		}
+	}
+}
 
 /*
  *	_bt_preprocess_keys() -- Preprocess scan keys
@@ -758,10 +892,8 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	int			numberOfKeys = scan->numberOfKeys;
 	int16	   *indoption = scan->indexRelation->rd_indoption;
-	int			new_numberOfKeys;
 	int			numberOfEqualCols;
 	ScanKey		inkeys;
-	ScanKey		outkeys;
 	ScanKey		cur;
 	ScanKey		xform[BTMaxStrategyNumber];
 	bool		test_result;
@@ -769,6 +901,24 @@ _bt_preprocess_keys(IndexScanDesc scan)
 				j;
 	AttrNumber	attno;
 
+	if (scan->numberOfOrderBys > 0)
+	{
+		ScanKey		ord = scan->orderByData;
+
+		if (scan->numberOfOrderBys > 1)
+			/* it should not happen, see btmatchorderby() */
+			elog(ERROR, "only one btree ordering operator is supported");
+
+		Assert(ord->sk_strategy == BtreeKNNSearchStrategyNumber);
+
+		/* use bidirectional kNN scan by default */
+		so->useBidirectionalKnnScan = true;
+	}
+	else
+	{
+		so->useBidirectionalKnnScan = false;
+	}
+
 	/* initialize result variables */
 	so->qual_ok = true;
 	so->numberOfKeys = 0;
@@ -784,7 +934,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	else
 		inkeys = scan->keyData;
 
-	outkeys = so->keyData;
 	cur = &inkeys[0];
 	/* we check that input keys are correctly ordered */
 	if (cur->sk_attno < 1)
@@ -796,18 +945,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 		/* Apply indoption to scankey (might change sk_strategy!) */
 		if (!_bt_fix_scankey_strategy(cur, indoption))
 			so->qual_ok = false;
-		memcpy(outkeys, cur, sizeof(ScanKeyData));
-		so->numberOfKeys = 1;
-		/* We can mark the qual as required if it's for first index col */
-		if (cur->sk_attno == 1)
-			_bt_mark_scankey_required(outkeys);
+
+		_bt_emit_scan_key(scan, cur, 0);
 		return;
 	}
 
 	/*
 	 * Otherwise, do the full set of pushups.
 	 */
-	new_numberOfKeys = 0;
 	numberOfEqualCols = 0;
 
 	/*
@@ -931,20 +1076,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 			}
 
 			/*
-			 * Emit the cleaned-up keys into the outkeys[] array, and then
+			 * Emit the cleaned-up keys into the so->keyData[] array, and then
 			 * mark them if they are required.  They are required (possibly
 			 * only in one direction) if all attrs before this one had "=".
 			 */
 			for (j = BTMaxStrategyNumber; --j >= 0;)
 			{
 				if (xform[j])
-				{
-					ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-					memcpy(outkey, xform[j], sizeof(ScanKeyData));
-					if (priorNumberOfEqualCols == attno - 1)
-						_bt_mark_scankey_required(outkey);
-				}
+					_bt_emit_scan_key(scan, xform[j], priorNumberOfEqualCols);
 			}
 
 			/*
@@ -964,17 +1103,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 		/* if row comparison, push it directly to the output array */
 		if (cur->sk_flags & SK_ROW_HEADER)
 		{
-			ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-			memcpy(outkey, cur, sizeof(ScanKeyData));
-			if (numberOfEqualCols == attno - 1)
-				_bt_mark_scankey_required(outkey);
+			_bt_emit_scan_key(scan, cur, numberOfEqualCols);
 
 			/*
 			 * We don't support RowCompare using equality; such a qual would
 			 * mess up the numberOfEqualCols tracking.
 			 */
 			Assert(j != (BTEqualStrategyNumber - 1));
+
 			continue;
 		}
 
@@ -1007,16 +1143,10 @@ _bt_preprocess_keys(IndexScanDesc scan)
 				 * previous one in xform[j] and push this one directly to the
 				 * output array.
 				 */
-				ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-				memcpy(outkey, cur, sizeof(ScanKeyData));
-				if (numberOfEqualCols == attno - 1)
-					_bt_mark_scankey_required(outkey);
+				_bt_emit_scan_key(scan, cur, numberOfEqualCols);
 			}
 		}
 	}
-
-	so->numberOfKeys = new_numberOfKeys;
 }
 
 /*
@@ -2075,6 +2205,39 @@ btproperty(Oid index_oid, int attno,
 			*res = true;
 			return true;
 
+		case AMPROP_DISTANCE_ORDERABLE:
+			{
+				Oid			opclass,
+							opfamily,
+							opcindtype;
+
+				/* answer only for columns, not AM or whole index */
+				if (attno == 0)
+					return false;
+
+				opclass = get_index_column_opclass(index_oid, attno);
+
+				if (!OidIsValid(opclass))
+				{
+					*res = false;	/* non-key attribute */
+					return true;
+				}
+
+				if (!get_opclass_opfamily_and_input_type(opclass,
+													 &opfamily, &opcindtype))
+				{
+					*isnull = true;
+					return true;
+				}
+
+				*res = SearchSysCacheExists(AMOPSTRATEGY,
+											ObjectIdGetDatum(opfamily),
+											ObjectIdGetDatum(opcindtype),
+											ObjectIdGetDatum(opcindtype),
+								   Int16GetDatum(BtreeKNNSearchStrategyNumber));
+				return true;
+			}
+
 		default:
 			return false;		/* punt to generic code */
 	}
@@ -2223,3 +2386,212 @@ _bt_allocate_tuple_workspaces(BTScanState state)
 	state->currTuples = (char *) palloc(BLCKSZ * 2);
 	state->markTuples = state->currTuples + BLCKSZ;
 }
+
+static bool
+_bt_compare_row_key_with_ordering_key(ScanKey row, ScanKey ord, bool *result)
+{
+	ScanKey		subkey = (ScanKey) DatumGetPointer(row->sk_argument);
+	int32		cmpresult;
+
+	Assert(subkey->sk_attno == 1);
+	Assert(subkey->sk_flags & SK_ROW_MEMBER);
+
+	if (subkey->sk_flags & SK_ISNULL)
+		return false;
+
+	/* Perform the test --- three-way comparison not bool operator */
+	cmpresult = DatumGetInt32(FunctionCall2Coll(&subkey->sk_func,
+												subkey->sk_collation,
+												ord->sk_argument,
+												subkey->sk_argument));
+
+	if (subkey->sk_flags & SK_BT_DESC)
+		cmpresult = -cmpresult;
+
+	/*
+	 * At this point cmpresult indicates the overall result of the row
+	 * comparison, and subkey points to the deciding column (or the last
+	 * column if the result is "=").
+	 */
+	switch (subkey->sk_strategy)
+	{
+			/* EQ and NE cases aren't allowed here */
+		case BTLessStrategyNumber:
+			*result = cmpresult < 0;
+			break;
+		case BTLessEqualStrategyNumber:
+			*result = cmpresult <= 0;
+			break;
+		case BTGreaterEqualStrategyNumber:
+			*result = cmpresult >= 0;
+			break;
+		case BTGreaterStrategyNumber:
+			*result = cmpresult > 0;
+			break;
+		default:
+			elog(ERROR, "unrecognized RowCompareType: %d",
+				 (int) subkey->sk_strategy);
+			*result = false;	/* keep compiler quiet */
+	}
+
+	return true;
+}
+
+/*
+ * _bt_select_knn_strategy_for_key() -- Determine which kNN scan strategy to use:
+ *		bidirectional or unidirectional.  We are checking here if the
+ *		ordering scankey argument falls into the scan range: if it falls
+ *		we must use bidirectional scan, otherwise we use unidirectional.
+ *
+ *	Returns BtreeKNNSearchStrategyNumber for bidirectional scan or
+ *	strategy number of non-matched scankey for unidirectional.
+ */
+static inline StrategyNumber
+_bt_select_knn_strategy_for_key(IndexScanDesc scan, ScanKey cond)
+{
+	ScanKey		ord = scan->orderByData;
+	bool		result;
+
+	/* only interesting in the index attribute that is ordered by a distance */
+	if (cond->sk_attno != ord->sk_attno)
+		return BtreeKNNSearchStrategyNumber;
+
+	if (cond->sk_strategy == BTEqualStrategyNumber)
+		/* always use simple unidirectional scan for equals operators */
+		return BTEqualStrategyNumber;
+
+	if (cond->sk_flags & SK_ROW_HEADER)
+	{
+		if (!_bt_compare_row_key_with_ordering_key(cond, ord, &result))
+			return BTEqualStrategyNumber; /* ROW(fist_index_attr, ...) IS NULL */
+	}
+	else
+	{
+		if (!_bt_compare_scankey_args(scan, cond, ord, cond, &result))
+			elog(ERROR, "could not compare ordering key");
+	}
+
+	if (!result)
+		/*
+		 * Ordering scankey argument is out of scan range,
+		 * use unidirectional scan.
+		 */
+		return cond->sk_strategy;
+
+	return BtreeKNNSearchStrategyNumber;
+}
+
+int
+_bt_init_knn_start_keys(IndexScanDesc scan, ScanKey *startKeys, ScanKey bufKeys)
+{
+	ScanKey		ord = scan->orderByData;
+	int			indopt = scan->indexRelation->rd_indoption[ord->sk_attno - 1];
+	int			flags = (indopt << SK_BT_INDOPTION_SHIFT) |
+						SK_ORDER_BY |
+						SK_SEARCHNULL; /* only for invalid procedure oid, see
+										* assert in ScanKeyEntryInitialize() */
+	int			keysCount = 0;
+
+	/* Init btree search key with ordering key argument. */
+	ScanKeyEntryInitialize(&bufKeys[0],
+						   flags,
+						   ord->sk_attno,
+						   BtreeKNNSearchStrategyNumber,
+						   ord->sk_subtype,
+						   ord->sk_collation,
+						   InvalidOid,
+						   ord->sk_argument);
+
+	startKeys[keysCount++] = &bufKeys[0];
+
+	return keysCount;
+}
+
+static Oid
+_bt_get_sortfamily_for_opfamily_op(Oid opfamily, Oid lefttype, Oid righttype,
+								   StrategyNumber strategy)
+{
+	HeapTuple	tp;
+	Form_pg_amop amop_tup;
+	Oid			sortfamily;
+
+	tp = SearchSysCache4(AMOPSTRATEGY,
+						 ObjectIdGetDatum(opfamily),
+						 ObjectIdGetDatum(lefttype),
+						 ObjectIdGetDatum(righttype),
+						 Int16GetDatum(strategy));
+	if (!HeapTupleIsValid(tp))
+		return InvalidOid;
+	amop_tup = (Form_pg_amop) GETSTRUCT(tp);
+	sortfamily = amop_tup->amopsortfamily;
+	ReleaseSysCache(tp);
+
+	return sortfamily;
+}
+
+/*
+ * _bt_get_distance_cmp_proc() -- Init procedure for comparsion of distances
+ *		between "leftargtype" and "distkey".
+ */
+static void
+_bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype,
+						  FmgrInfo *finfo, int16 *typlen, bool *typbyval)
+{
+	RegProcedure opcode;
+	Oid			sortfamily;
+	Oid			opno;
+	Oid			distanceType;
+
+	distanceType = get_func_rettype(distkey->sk_func.fn_oid);
+
+	sortfamily = _bt_get_sortfamily_for_opfamily_op(opfamily, leftargtype,
+													distkey->sk_subtype,
+													distkey->sk_strategy);
+
+	if (!OidIsValid(sortfamily))
+		elog(ERROR, "could not find sort family for btree ordering operator");
+
+	opno = get_opfamily_member(sortfamily,
+							   distanceType,
+							   distanceType,
+							   BTLessEqualStrategyNumber);
+
+	if (!OidIsValid(opno))
+		elog(ERROR, "could not find operator for btree distance comparison");
+
+	opcode = get_opcode(opno);
+
+	if (!RegProcedureIsValid(opcode))
+		elog(ERROR,
+			"could not find procedure for btree distance comparison operator");
+
+	fmgr_info(opcode, finfo);
+
+	if (typlen)
+		get_typlenbyval(distanceType, typlen, typbyval);
+}
+
+/*
+ *  _bt_init_distance_comparison() -- Init distance typlen/typbyval and its
+ *  	comparison procedure.
+ */
+void
+_bt_init_distance_comparison(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	Relation	rel = scan->indexRelation;
+	ScanKey		ord = scan->orderByData;
+
+	_bt_get_distance_cmp_proc(ord,
+							  rel->rd_opfamily[ord->sk_attno - 1],
+							  rel->rd_opcintype[ord->sk_attno - 1],
+							  &so->distanceCmpProc,
+							  &so->distanceTypeLen,
+							  &so->distanceTypeByVal);
+
+	if (!so->distanceTypeByVal)
+	{
+		so->state.currDistance = PointerGetDatum(NULL);
+		so->state.markDistance = PointerGetDatum(NULL);
+	}
+}
diff --git a/src/backend/access/nbtree/nbtvalidate.c b/src/backend/access/nbtree/nbtvalidate.c
index 0148ea7..4558fd3 100644
--- a/src/backend/access/nbtree/nbtvalidate.c
+++ b/src/backend/access/nbtree/nbtvalidate.c
@@ -22,9 +22,17 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_type.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/regproc.h"
 #include "utils/syscache.h"
 
+#define BTRequiredOperatorSet \
+		((1 << BTLessStrategyNumber) | \
+		 (1 << BTLessEqualStrategyNumber) | \
+		 (1 << BTEqualStrategyNumber) | \
+		 (1 << BTGreaterEqualStrategyNumber) | \
+		 (1 << BTGreaterStrategyNumber))
+
 
 /*
  * Validator for a btree opclass.
@@ -132,10 +140,11 @@ btvalidate(Oid opclassoid)
 	{
 		HeapTuple	oprtup = &oprlist->members[i]->tuple;
 		Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
+		Oid			op_rettype;
 
 		/* Check that only allowed strategy numbers exist */
 		if (oprform->amopstrategy < 1 ||
-			oprform->amopstrategy > BTMaxStrategyNumber)
+			oprform->amopstrategy > BtreeMaxStrategyNumber)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -146,20 +155,29 @@ btvalidate(Oid opclassoid)
 			result = false;
 		}
 
-		/* btree doesn't support ORDER BY operators */
-		if (oprform->amoppurpose != AMOP_SEARCH ||
-			OidIsValid(oprform->amopsortfamily))
+		/* btree supports ORDER BY operators */
+		if (oprform->amoppurpose != AMOP_SEARCH)
 		{
-			ereport(INFO,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
-							opfamilyname, "btree",
-							format_operator(oprform->amopopr))));
-			result = false;
+			/* ... and operator result must match the claimed btree opfamily */
+			op_rettype = get_op_rettype(oprform->amopopr);
+			if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype))
+			{
+				ereport(INFO,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
+								opfamilyname, "btree",
+								format_operator(oprform->amopopr))));
+				result = false;
+			}
+		}
+		else
+		{
+			/* Search operators must always return bool */
+			op_rettype = BOOLOID;
 		}
 
 		/* Check operator signature --- same for all btree strategies */
-		if (!check_amop_signature(oprform->amopopr, BOOLOID,
+		if (!check_amop_signature(oprform->amopopr, op_rettype,
 								  oprform->amoplefttype,
 								  oprform->amoprighttype))
 		{
@@ -214,12 +232,8 @@ btvalidate(Oid opclassoid)
 		 * or support functions for this datatype pair.  The only things
 		 * considered optional are the sortsupport and in_range functions.
 		 */
-		if (thisgroup->operatorset !=
-			((1 << BTLessStrategyNumber) |
-			 (1 << BTLessEqualStrategyNumber) |
-			 (1 << BTEqualStrategyNumber) |
-			 (1 << BTGreaterEqualStrategyNumber) |
-			 (1 << BTGreaterStrategyNumber)))
+		if ((thisgroup->operatorset & BTRequiredOperatorSet) !=
+			BTRequiredOperatorSet)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 6cf0b0d..279d8ba 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -990,6 +990,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	 * if we are only trying to build bitmap indexscans, nor if we have to
 	 * assume the scan is unordered.
 	 */
+	useful_pathkeys = NIL;
+	orderbyclauses = NIL;
+	orderbyclausecols = NIL;
+
 	pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN &&
 								!found_lower_saop_clause &&
 								has_useful_pathkeys(root, rel));
@@ -1000,10 +1004,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 											  ForwardScanDirection);
 		useful_pathkeys = truncate_useless_pathkeys(root, rel,
 													index_pathkeys);
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
 	}
-	else if (index->ammatchorderby && pathkeys_possibly_useful)
+
+	if (useful_pathkeys == NIL &&
+		index->ammatchorderby && pathkeys_possibly_useful)
 	{
 		/* see if we can generate ordering operators for query_pathkeys */
 		match_pathkeys_to_index(index, root->query_pathkeys,
@@ -1015,12 +1019,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		else
 			useful_pathkeys = NIL;
 	}
-	else
-	{
-		useful_pathkeys = NIL;
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
-	}
 
 	/*
 	 * 3. Check if an index-only scan is possible.  If we're not building
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index d78df29..4f98e91 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -459,6 +459,12 @@ typedef struct BTScanStateData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+
+	/* KNN-search fields: */
+	Datum		currDistance;	/* current distance */
+	Datum		markDistance;	/* marked distance */
+	bool		currIsNull;		/* current item is NULL */
+	bool		markIsNull;		/* marked item is NULL */
 } BTScanStateData;
 
 typedef BTScanStateData *BTScanState;
@@ -479,7 +485,18 @@ typedef struct BTScanOpaqueData
 	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
 	MemoryContext arrayContext; /* scan-lifespan context for array data */
 
-	BTScanStateData	state;
+	BTScanStateData state;		/* main scan state */
+
+	/* kNN-search fields: */
+	BTScanState knnState;		/* optional scan state for kNN search */
+	bool		useBidirectionalKnnScan;	/* use bidirectional kNN scan? */
+	ScanDirection scanDirection;	/* selected scan direction for
+									 * unidirectional kNN scan */
+	FmgrInfo	distanceCmpProc;	/* distance comparison procedure */
+	int16		distanceTypeLen;	/* distance typlen */
+	bool		distanceTypeByVal;	/* distance typebyval */
+	bool		currRightIsNearest; /* current right item is nearest */
+	bool		markRightIsNearest; /* marked right item is nearest */
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -525,11 +542,12 @@ extern bool btcanreturn(Relation index, int attno);
 /*
  * prototypes for internal functions in nbtree.c
  */
-extern bool _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno);
-extern void _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page);
-extern void _bt_parallel_done(IndexScanDesc scan);
+extern bool _bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno);
+extern void _bt_parallel_release(IndexScanDesc scan, BTScanState state, BlockNumber scan_page);
+extern void _bt_parallel_done(IndexScanDesc scan, BTScanState state);
 extern void _bt_parallel_advance_array_keys(IndexScanDesc scan);
 
+
 /*
  * prototypes for functions in nbtinsert.c
  */
@@ -610,6 +628,9 @@ extern bool btproperty(Oid index_oid, int attno,
 extern IndexTuple _bt_nonkey_truncate(Relation rel, IndexTuple itup);
 extern bool _bt_check_natts(Relation rel, Page page, OffsetNumber offnum);
 extern void _bt_allocate_tuple_workspaces(BTScanState state);
+extern void _bt_init_distance_comparison(IndexScanDesc scan);
+extern int _bt_init_knn_start_keys(IndexScanDesc scan, ScanKey *startKeys,
+						ScanKey bufKeys);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/include/access/stratnum.h b/src/include/access/stratnum.h
index 8fdba28..8087ffe 100644
--- a/src/include/access/stratnum.h
+++ b/src/include/access/stratnum.h
@@ -32,7 +32,10 @@ typedef uint16 StrategyNumber;
 #define BTGreaterEqualStrategyNumber	4
 #define BTGreaterStrategyNumber			5
 
-#define BTMaxStrategyNumber				5
+#define BTMaxStrategyNumber				5		/* number of canonical B-tree strategies */
+
+#define BtreeKNNSearchStrategyNumber	6		/* for <-> (distance) */
+#define BtreeMaxStrategyNumber			6		/* number of extended B-tree strategies */
 
 
 /*
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index 6faa9d7..c75ef39 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -347,10 +347,10 @@ ROLLBACK;
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
 ERROR:  access method "invalid_index_method" does not exist
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 6, must be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 0, must be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 7, must be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 0, must be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ERROR:  operator argument types must be specified in ALTER OPERATOR FAMILY
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -397,11 +397,12 @@ DROP OPERATOR FAMILY alt_opf8 USING btree;
 CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
-ERROR:  access method "btree" does not support ordering operators
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
 ALTER OPERATOR FAMILY alt_opf11 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql
index 84fd900..73e6e206 100644
--- a/src/test/regress/sql/alter_generic.sql
+++ b/src/test/regress/sql/alter_generic.sql
@@ -295,8 +295,8 @@ ROLLBACK;
 -- Should fail. Invalid values for ALTER OPERATOR FAMILY .. ADD / DROP
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 6 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -340,10 +340,12 @@ CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
 
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
0005-Add-btree-distance-operators-v06.patchtext/x-patch; name=0005-Add-btree-distance-operators-v06.patchDownload
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index caec484..e656a8a 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -183,6 +183,12 @@ SELECT * FROM events ORDER BY event_date <-> date '2017-05-05' LIMIT 10;
 </programlisting>
    which finds the ten events closest to a given target date.  The ability
    to do this is again dependent on the particular operator class being used.
+   Built-in B-tree operator classes support distance ordering for data types
+   <type>int2</>, <type>int4</>, <type>int8</>,
+   <type>float4</>, <type>float8</>, <type>numeric</>,
+   <type>timestamp with time zone</>, <type>timestamp without time zone</>,
+   <type>time with time zone</>, <type>time without time zone</>,
+   <type>date</>, <type>interval</>, <type>oid</>, <type>money</>.
   </para>
 
   <para>
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index c92e9d5..83073c4 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -30,6 +30,7 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+#define SAMESIGN(a,b)	(((a) < 0) == ((b) < 0))
 
 /*************************************************************************
  * Private routines
@@ -1157,3 +1158,22 @@ int8_cash(PG_FUNCTION_ARGS)
 
 	PG_RETURN_CASH(result);
 }
+
+Datum
+cash_distance(PG_FUNCTION_ARGS)
+{
+	Cash		a = PG_GETARG_CASH(0);
+	Cash		b = PG_GETARG_CASH(1);
+	Cash		r;
+	Cash		ra;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("money out of range")));
+
+	ra = Abs(r);
+
+	PG_RETURN_CASH(ra);
+}
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index cf5a1c6..4492f69 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -563,6 +563,17 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+Datum
+date_distance(PG_FUNCTION_ARGS)
+{
+	/* we assume the difference can't overflow */
+	Datum		diff = DirectFunctionCall2(date_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
+}
+
 /*
  * Internal routines for promoting date to timestamp and timestamp with
  * time zone
@@ -760,6 +771,29 @@ date_cmp_timestamp(PG_FUNCTION_ARGS)
 }
 
 Datum
+date_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+	Timestamp	dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
+Datum
 date_eq_timestamptz(PG_FUNCTION_ARGS)
 {
 	DateADT		dateVal = PG_GETARG_DATEADT(0);
@@ -844,6 +878,30 @@ date_cmp_timestamptz(PG_FUNCTION_ARGS)
 }
 
 Datum
+date_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
+
+
+Datum
 timestamp_eq_date(PG_FUNCTION_ARGS)
 {
 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
@@ -928,6 +986,29 @@ timestamp_cmp_date(PG_FUNCTION_ARGS)
 }
 
 Datum
+timestamp_dist_date(PG_FUNCTION_ARGS)
+{
+	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	Timestamp	dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
+Datum
 timestamptz_eq_date(PG_FUNCTION_ARGS)
 {
 	TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
@@ -1039,6 +1120,28 @@ in_range_date_interval(PG_FUNCTION_ARGS)
 							   BoolGetDatum(less));
 }
 
+Datum
+timestamptz_dist_date(PG_FUNCTION_ARGS)
+{
+	TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	TimestampTz dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
 
 /* Add an interval to a date, giving a new date.
  * Must handle both positive and negative intervals.
@@ -1957,6 +2060,16 @@ time_part(PG_FUNCTION_ARGS)
 	PG_RETURN_FLOAT8(result);
 }
 
+Datum
+time_distance(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(time_mi_time,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
+
 
 /*****************************************************************************
  *	 Time With Time Zone ADT
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 37c202d..d72fbef 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -3726,6 +3726,47 @@ width_bucket_float8(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(result);
 }
 
+Datum
+float4dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float4		r = float4_mi(a, b);
+
+	PG_RETURN_FLOAT4(Abs(r));
+}
+
+Datum
+float8dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
+
+Datum
+float48dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
+Datum
+float84dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
 /* ========== PRIVATE ROUTINES ========== */
 
 #ifndef HAVE_CBRT
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 04825fc..76e92ec 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -1501,3 +1501,54 @@ generate_series_int4_support(PG_FUNCTION_ARGS)
 
 	PG_RETURN_POINTER(ret);
 }
+
+Datum
+int2dist(PG_FUNCTION_ARGS)
+{
+	int16		a = PG_GETARG_INT16(0);
+	int16		b = PG_GETARG_INT16(1);
+	int16		r;
+	int16		ra;
+
+	if (pg_sub_s16_overflow(a, b, &r) ||
+		r == PG_INT16_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("smallint out of range")));
+
+	ra = Abs(r);
+
+	PG_RETURN_INT16(ra);
+}
+
+static int32
+int44_dist(int32 a, int32 b)
+{
+	int32		r;
+
+	if (pg_sub_s32_overflow(a, b, &r) ||
+		r == PG_INT32_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("integer out of range")));
+
+	return Abs(r);
+}
+
+Datum
+int4dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int24dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT16(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int42dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT16(1)));
+}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 0ff9394..2ceb16b 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1446,3 +1446,47 @@ generate_series_int8_support(PG_FUNCTION_ARGS)
 
 	PG_RETURN_POINTER(ret);
 }
+
+static int64
+int88_dist(int64 a, int64 b)
+{
+	int64		r;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("bigint out of range")));
+
+	return Abs(r);
+}
+
+Datum
+int8dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int82dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT16(1)));
+}
+
+Datum
+int84dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT32(1)));
+}
+
+Datum
+int28dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT16(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int48dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT32(0), PG_GETARG_INT64(1)));
+}
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index bb67e01..b8f48d5 100644
--- a/src/backend/utils/adt/oid.c
+++ b/src/backend/utils/adt/oid.c
@@ -469,3 +469,17 @@ oidvectorgt(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(cmp > 0);
 }
+
+Datum
+oiddist(PG_FUNCTION_ARGS)
+{
+	Oid			a = PG_GETARG_OID(0);
+	Oid			b = PG_GETARG_OID(1);
+	Oid			res;
+
+	if (a < b)
+		res = b - a;
+	else
+		res = a - b;
+	PG_RETURN_OID(res);
+}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index e0ef2f7..c5d1042 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -2694,6 +2694,86 @@ timestamp_mi(PG_FUNCTION_ARGS)
 	PG_RETURN_INTERVAL_P(result);
 }
 
+Datum
+timestamp_distance(PG_FUNCTION_ARGS)
+{
+	Timestamp	a = PG_GETARG_TIMESTAMP(0);
+	Timestamp	b = PG_GETARG_TIMESTAMP(1);
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		Interval   *p = palloc(sizeof(Interval));
+
+		p->day = INT_MAX;
+		p->month = INT_MAX;
+		p->time = PG_INT64_MAX;
+		PG_RETURN_INTERVAL_P(p);
+	}
+	else
+		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+												  PG_GETARG_DATUM(0),
+												  PG_GETARG_DATUM(1)));
+	PG_RETURN_INTERVAL_P(abs_interval(r));
+}
+
+Interval *
+timestamp_dist_internal(Timestamp a, Timestamp b)
+{
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		return r;
+	}
+
+	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+											  TimestampGetDatum(a),
+											  TimestampGetDatum(b)));
+
+	return abs_interval(r);
+}
+
+Datum
+timestamptz_distance(PG_FUNCTION_ARGS)
+{
+	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
+	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(a, b));
+}
+
+Datum
+timestamp_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	Timestamp	ts1 = PG_GETARG_TIMESTAMP(0);
+	TimestampTz tstz2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz tstz1;
+
+	tstz1 = timestamp2timestamptz(ts1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+Datum
+timestamptz_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	TimestampTz tstz1 = PG_GETARG_TIMESTAMPTZ(0);
+	Timestamp	ts2 = PG_GETARG_TIMESTAMP(1);
+	TimestampTz tstz2;
+
+	tstz2 = timestamp2timestamptz(ts2);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+
 /*
  *	interval_justify_interval()
  *
@@ -3563,6 +3643,29 @@ interval_avg(PG_FUNCTION_ARGS)
 							   Float8GetDatum((double) N.time));
 }
 
+Interval *
+abs_interval(Interval *a)
+{
+	static Interval zero = {0, 0, 0};
+
+	if (DatumGetBool(DirectFunctionCall2(interval_lt,
+										 IntervalPGetDatum(a),
+										 IntervalPGetDatum(&zero))))
+		a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
+												  IntervalPGetDatum(a)));
+
+	return a;
+}
+
+Datum
+interval_distance(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(interval_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
 
 /* timestamp_age()
  * Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0ab95d8..0e06b04 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -30,6 +30,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int2,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int24
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -47,6 +51,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int2,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int28
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -64,6 +72,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int2,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int4
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -81,6 +93,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int4,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int42
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -98,6 +114,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int4,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int48
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -115,6 +135,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int4,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int8
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -132,6 +156,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int8,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int82
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -149,6 +177,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int8,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int84
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -166,6 +198,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int8,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # btree oid_ops
 
@@ -179,6 +215,10 @@
   amopstrategy => '4', amopopr => '>=(oid,oid)', amopmethod => 'btree' },
 { amopfamily => 'btree/oid_ops', amoplefttype => 'oid', amoprighttype => 'oid',
   amopstrategy => '5', amopopr => '>(oid,oid)', amopmethod => 'btree' },
+{ amopfamily => 'btree/oid_ops', amoplefttype => 'oid',
+  amoprighttype => 'oid', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(oid,oid)', amopmethod => 'btree',
+  amopsortfamily => 'btree/oid_ops' },
 
 # btree tid_ops
 
@@ -229,6 +269,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float4,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float48
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
@@ -246,6 +290,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float4,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # default operators float8
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -263,6 +311,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float8,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float84
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -280,6 +332,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float8,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # btree char_ops
 
@@ -416,6 +472,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(date,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -433,6 +493,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(date,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -450,6 +514,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(date,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -467,6 +535,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamp,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -484,6 +556,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(timestamp,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -501,6 +577,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamp,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -518,6 +598,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -535,6 +619,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'date', amopstrategy => '5',
   amopopr => '>(timestamptz,date)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -552,6 +640,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree time_ops
 
@@ -570,6 +662,10 @@
 { amopfamily => 'btree/time_ops', amoplefttype => 'time',
   amoprighttype => 'time', amopstrategy => '5', amopopr => '>(time,time)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/time_ops', amoplefttype => 'time',
+  amoprighttype => 'time', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(time,time)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree timetz_ops
 
@@ -606,6 +702,10 @@
 { amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
   amoprighttype => 'interval', amopstrategy => '5',
   amopopr => '>(interval,interval)', amopmethod => 'btree' },
+{ amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
+  amoprighttype => 'interval', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(interval,interval)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree macaddr
 
@@ -781,6 +881,10 @@
 { amopfamily => 'btree/money_ops', amoplefttype => 'money',
   amoprighttype => 'money', amopstrategy => '5', amopopr => '>(money,money)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/money_ops', amoplefttype => 'money',
+  amoprighttype => 'money', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(money,money)', amopmethod => 'btree',
+  amopsortfamily => 'btree/money_ops' },
 
 # btree array_ops
 
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 06aec07..914ca76 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2851,6 +2851,114 @@
   oprname => '-', oprleft => 'pg_lsn', oprright => 'pg_lsn',
   oprresult => 'numeric', oprcode => 'pg_lsn_mi' },
 
+# distance operators
+{ oid => '4217', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int2', oprresult => 'int2', oprcom => '<->(int2,int2)',
+  oprcode => 'int2dist'},
+{ oid => '4218', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int4)',
+  oprcode => 'int4dist'},
+{ oid => '4219', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int8)',
+  oprcode => 'int8dist'},
+{ oid => '4220', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'oid',
+  oprright => 'oid', oprresult => 'oid', oprcom => '<->(oid,oid)',
+  oprcode => 'oiddist'},
+{ oid => '4221', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float4', oprresult => 'float4', oprcom => '<->(float4,float4)',
+  oprcode => 'float4dist'},
+{ oid => '4222', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float8)',
+  oprcode => 'float8dist'},
+{ oid => '4223', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'money',
+  oprright => 'money', oprresult => 'money', oprcom => '<->(money,money)',
+  oprcode => 'cash_distance'},
+{ oid => '4224', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'date', oprresult => 'int4', oprcom => '<->(date,date)',
+  oprcode => 'date_distance'},
+{ oid => '4225', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'time',
+  oprright => 'time', oprresult => 'interval', oprcom => '<->(time,time)',
+  oprcode => 'time_distance'},
+{ oid => '4226', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamp)',
+  oprcode => 'timestamp_distance'},
+{ oid => '4227', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamptz)',
+  oprcode => 'timestamptz_distance'},
+{ oid => '4228', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'interval',
+  oprright => 'interval', oprresult => 'interval', oprcom => '<->(interval,interval)',
+  oprcode => 'interval_distance'},
+
+# cross-type distance operators
+{ oid => '4229', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int2)',
+  oprcode => 'int24dist'},
+{ oid => '4230', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int2', oprresult => 'int4', oprcom => '<->(int2,int4)',
+  oprcode => 'int42dist'},
+{ oid => '4231', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int2)',
+  oprcode => 'int28dist'},
+{ oid => '4232', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int2', oprresult => 'int8', oprcom => '<->(int2,int8)',
+  oprcode => 'int82dist'},
+{ oid => '4233', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int4)',
+  oprcode => 'int48dist'},
+{ oid => '4234', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int4', oprresult => 'int8', oprcom => '<->(int4,int8)',
+  oprcode => 'int84dist'},
+{ oid => '4235', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float4)',
+  oprcode => 'float48dist'},
+{ oid => '4236', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float4', oprresult => 'float8', oprcom => '<->(float4,float8)',
+  oprcode => 'float84dist'},
+{ oid => '4237', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,date)',
+  oprcode => 'date_dist_timestamp'},
+{ oid => '4238', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamp)',
+  oprcode => 'timestamp_dist_date'},
+{ oid => '4239', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,date)',
+  oprcode => 'date_dist_timestamptz'},
+{ oid => '4240', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamptz)',
+  oprcode => 'timestamptz_dist_date'},
+{ oid => '4241', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamp)',
+  oprcode => 'timestamp_dist_timestamptz'},
+{ oid => '4242', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamptz)',
+  oprcode => 'timestamptz_dist_timestamp'},
+
 # enum operators
 { oid => '3516', descr => 'equal',
   oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'anyenum',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a4e173b..96d5db0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10534,4 +10534,90 @@
   proname => 'pg_partition_root', prorettype => 'regclass',
   proargtypes => 'regclass', prosrc => 'pg_partition_root' },
 
+# distance functions
+{ oid => '4243',
+  proname => 'int2dist', prorettype => 'int2',
+  proargtypes => 'int2 int2', prosrc => 'int2dist' },
+{ oid => '4244',
+  proname => 'int4dist', prorettype => 'int4',
+  proargtypes => 'int4 int4', prosrc => 'int4dist' },
+{ oid => '4245',
+  proname => 'int8dist', prorettype => 'int8',
+  proargtypes => 'int8 int8', prosrc => 'int8dist' },
+{ oid => '4246',
+  proname => 'oiddist', proleakproof => 't', prorettype => 'oid',
+  proargtypes => 'oid oid', prosrc => 'oiddist' },
+{ oid => '4247',
+  proname => 'float4dist', prorettype => 'float4',
+  proargtypes => 'float4 float4', prosrc => 'float4dist' },
+{ oid => '4248',
+  proname => 'float8dist', prorettype => 'float8',
+  proargtypes => 'float8 float8', prosrc => 'float8dist' },
+{ oid => '4249',
+  proname => 'cash_distance', prorettype => 'money',
+  proargtypes => 'money money', prosrc => 'cash_distance' },
+{ oid => '4250',
+  proname => 'date_distance', prorettype => 'int4',
+  proargtypes => 'date date', prosrc => 'date_distance' },
+{ oid => '4251',
+  proname => 'time_distance', prorettype => 'interval',
+  proargtypes => 'time time', prosrc => 'time_distance' },
+{ oid => '4252',
+  proname => 'timestamp_distance', prorettype => 'interval',
+  proargtypes => 'timestamp timestamp', prosrc => 'timestamp_distance' },
+{ oid => '4253',
+  proname => 'timestamptz_distance', prorettype => 'interval',
+  proargtypes => 'timestamptz timestamptz', prosrc => 'timestamptz_distance' },
+{ oid => '4254',
+  proname => 'interval_distance', prorettype => 'interval',
+  proargtypes => 'interval interval', prosrc => 'interval_distance' },
+
+# cross-type distance functions
+{ oid => '4255',
+  proname => 'int24dist', prorettype => 'int4',
+  proargtypes => 'int2 int4', prosrc => 'int24dist' },
+{ oid => '4256',
+  proname => 'int28dist', prorettype => 'int8',
+  proargtypes => 'int2 int8', prosrc => 'int28dist' },
+{ oid => '4257',
+  proname => 'int42dist', prorettype => 'int4',
+  proargtypes => 'int4 int2', prosrc => 'int42dist' },
+{ oid => '4258',
+  proname => 'int48dist', prorettype => 'int8',
+  proargtypes => 'int4 int8', prosrc => 'int48dist' },
+{ oid => '4259',
+  proname => 'int82dist', prorettype => 'int8',
+  proargtypes => 'int8 int2', prosrc => 'int82dist' },
+{ oid => '4260',
+  proname => 'int84dist', prorettype => 'int8',
+  proargtypes => 'int8 int4', prosrc => 'int84dist' },
+{ oid => '4261',
+  proname => 'float48dist', prorettype => 'float8',
+  proargtypes => 'float4 float8', prosrc => 'float48dist' },
+{ oid => '4262',
+  proname => 'float84dist', prorettype => 'float8',
+  proargtypes => 'float8 float4', prosrc => 'float84dist' },
+{ oid => '4263',
+  proname => 'date_dist_timestamp', prorettype => 'interval',
+  proargtypes => 'date timestamp', prosrc => 'date_dist_timestamp' },
+{ oid => '4264',
+  proname => 'date_dist_timestamptz', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'date timestamptz',
+  prosrc => 'date_dist_timestamptz' },
+{ oid => '4265',
+  proname => 'timestamp_dist_date', prorettype => 'interval',
+  proargtypes => 'timestamp date', prosrc => 'timestamp_dist_date' },
+{ oid => '4266',
+  proname => 'timestamp_dist_timestamptz', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamp timestamptz',
+  prosrc => 'timestamp_dist_timestamptz' },
+{ oid => '4267',
+  proname => 'timestamptz_dist_date', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamptz date',
+  prosrc => 'timestamptz_dist_date' },
+{ oid => '4268',
+  proname => 'timestamptz_dist_timestamp', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamptz timestamp',
+  prosrc => 'timestamptz_dist_timestamp' },
+
 ]
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 87f819e..01d2284 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -338,4 +338,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 					   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern Interval *abs_interval(Interval *a);
+
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index aeb89dc..438a5eb 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -93,9 +93,11 @@ extern Timestamp SetEpochTimestamp(void);
 extern void GetEpochTime(struct pg_tm *tm);
 
 extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern Interval *timestamp_dist_internal(Timestamp a, Timestamp b);
 
-/* timestamp comparison works for timestamptz also */
+/* timestamp comparison and distance works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
+#define timestamptz_dist_internal(dt1,dt2)	timestamp_dist_internal(dt1, dt2)
 
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index 4570a39..630dc6b 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -24,7 +24,7 @@ select prop,
  nulls_first        |    |       | f
  nulls_last         |    |       | t
  orderable          |    |       | t
- distance_orderable |    |       | f
+ distance_orderable |    |       | t
  returnable         |    |       | t
  search_array       |    |       | t
  search_nulls       |    |       | t
@@ -100,7 +100,7 @@ select prop,
  nulls_first        | f     | f    | f    | f            | f           | f   | f
  nulls_last         | t     | f    | f    | f            | f           | f   | f
  orderable          | t     | f    | f    | f            | f           | f   | f
- distance_orderable | f     | f    | t    | f            | t           | f   | f
+ distance_orderable | t     | f    | t    | f            | t           | f   | f
  returnable         | t     | f    | f    | t            | t           | f   | f
  search_array       | t     | f    | f    | f            | f           | f   | f
  search_nulls       | t     | f    | t    | t            | t           | f   | t
@@ -231,7 +231,7 @@ select col, prop, pg_index_column_has_property(o, col, prop)
    1 | desc               | f
    1 | nulls_first        | f
    1 | nulls_last         | t
-   1 | distance_orderable | f
+   1 | distance_orderable | t
    1 | returnable         | t
    1 | bogus              | 
    2 | orderable          | f
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
index 1bcc946..b9b819c 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -1477,3 +1477,64 @@ select make_time(10, 55, 100.1);
 ERROR:  time field value out of range: 10:55:100.1
 select make_time(24, 0, 2.1);
 ERROR:  time field value out of range: 24:00:2.1
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+ Fifteen | Distance 
+---------+----------
+         |    16006
+         |    15941
+         |     1802
+         |     1801
+         |     1800
+         |     1799
+         |     1436
+         |     1435
+         |     1434
+         |      308
+         |      307
+         |      306
+         |    13578
+         |    13944
+         |    14311
+(15 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+ Fifteen |               Distance                
+---------+---------------------------------------
+         | @ 16006 days 1 hour 23 mins 45 secs
+         | @ 15941 days 1 hour 23 mins 45 secs
+         | @ 1802 days 1 hour 23 mins 45 secs
+         | @ 1801 days 1 hour 23 mins 45 secs
+         | @ 1800 days 1 hour 23 mins 45 secs
+         | @ 1799 days 1 hour 23 mins 45 secs
+         | @ 1436 days 1 hour 23 mins 45 secs
+         | @ 1435 days 1 hour 23 mins 45 secs
+         | @ 1434 days 1 hour 23 mins 45 secs
+         | @ 308 days 1 hour 23 mins 45 secs
+         | @ 307 days 1 hour 23 mins 45 secs
+         | @ 306 days 1 hour 23 mins 45 secs
+         | @ 13577 days 22 hours 36 mins 15 secs
+         | @ 13943 days 22 hours 36 mins 15 secs
+         | @ 14310 days 22 hours 36 mins 15 secs
+(15 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
+ Fifteen |               Distance                
+---------+---------------------------------------
+         | @ 16005 days 14 hours 23 mins 45 secs
+         | @ 15940 days 14 hours 23 mins 45 secs
+         | @ 1801 days 14 hours 23 mins 45 secs
+         | @ 1800 days 14 hours 23 mins 45 secs
+         | @ 1799 days 14 hours 23 mins 45 secs
+         | @ 1798 days 14 hours 23 mins 45 secs
+         | @ 1435 days 14 hours 23 mins 45 secs
+         | @ 1434 days 14 hours 23 mins 45 secs
+         | @ 1433 days 14 hours 23 mins 45 secs
+         | @ 307 days 14 hours 23 mins 45 secs
+         | @ 306 days 14 hours 23 mins 45 secs
+         | @ 305 days 15 hours 23 mins 45 secs
+         | @ 13578 days 8 hours 36 mins 15 secs
+         | @ 13944 days 8 hours 36 mins 15 secs
+         | @ 14311 days 8 hours 36 mins 15 secs
+(15 rows)
+
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index cf78277..ce29924e 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -273,6 +273,26 @@ SELECT '' AS five, * FROM FLOAT4_TBL;
       | -1.2345679e-20
 (5 rows)
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+ five |       f1       |     dist      
+------+----------------+---------------
+      |              0 |        1004.3
+      |         -34.84 |       1039.14
+      |        -1004.3 |        2008.6
+      | -1.2345679e+20 | 1.2345679e+20
+      | -1.2345679e-20 |        1004.3
+(5 rows)
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+ five |       f1       |          dist          
+------+----------------+------------------------
+      |              0 |                 1004.3
+      |         -34.84 |     1039.1400001525878
+      |        -1004.3 |     2008.5999877929687
+      | -1.2345679e+20 | 1.2345678955701443e+20
+      | -1.2345679e-20 |                 1004.3
+(5 rows)
+
 -- test edge-case coercions to integer
 SELECT '32767.4'::float4::int2;
  int2  
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
index c3a6f53..5485c19 100644
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -413,6 +413,27 @@ SELECT '' AS five, f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
       | 1.2345678901234e-200 |  2.3112042409018e-67
 (5 rows)
 
+-- distance
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+ five |          f1          |         dist         
+------+----------------------+----------------------
+      |                    0 |               1004.3
+      |               1004.3 |                    0
+      |               -34.84 |              1039.14
+      | 1.2345678901234e+200 | 1.2345678901234e+200
+      | 1.2345678901234e-200 |               1004.3
+(5 rows)
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
+ five |          f1          |         dist         
+------+----------------------+----------------------
+      |                    0 |     1004.29998779297
+      |               1004.3 | 1.22070312045253e-05
+      |               -34.84 |     1039.13998779297
+      | 1.2345678901234e+200 | 1.2345678901234e+200
+      | 1.2345678901234e-200 |     1004.29998779297
+(5 rows)
+
 SELECT '' AS five, * FROM FLOAT8_TBL;
  five |          f1          
 ------+----------------------
diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out
index 8c255b9..0edc57e 100644
--- a/src/test/regress/expected/int2.out
+++ b/src/test/regress/expected/int2.out
@@ -242,6 +242,39 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
       | -32767 | -16383
 (5 rows)
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+ERROR:  smallint out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+ four |  f1   |   x   
+------+-------+-------
+      |     0 |     2
+      |  1234 |  1232
+      | -1234 |  1236
+      | 32767 | 32765
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
   text  
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
index bda7a8d..3735dbc 100644
--- a/src/test/regress/expected/int4.out
+++ b/src/test/regress/expected/int4.out
@@ -247,6 +247,38 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
       | -2147483647 | -1073741823
 (5 rows)
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+ERROR:  integer out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+ five |     f1      |     x      
+------+-------------+------------
+      |           0 |          2
+      |      123456 |     123454
+      |     -123456 |     123458
+      |  2147483647 | 2147483645
+      | -2147483647 | 2147483649
+(5 rows)
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index 8447a28..d56886a 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -432,6 +432,37 @@ SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 A
  4567890123457035 | -4567890123456543 | 1123700970370370094 |     0
 (5 rows)
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
 SELECT q2, abs(q2) FROM INT8_TBL;
         q2         |       abs        
 -------------------+------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index f88f345..cb95adf 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -207,6 +207,21 @@ SELECT '' AS fortyfive, r1.*, r2.*
            | 34 years        | 6 years
 (45 rows)
 
+SELECT '' AS ten, f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+ ten |          ?column?          
+-----+----------------------------
+     | 2 days 02:59:00
+     | 2 days -02:00:00
+     | 8 days -03:00:00
+     | 34 years -2 days -03:00:00
+     | 3 mons -2 days -03:00:00
+     | 2 days 03:00:14
+     | 1 day 00:56:56
+     | 6 years -2 days -03:00:00
+     | 5 mons -2 days -03:00:00
+     | 5 mons -2 days +09:00:00
+(10 rows)
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index ab86595..fb2a489 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -123,6 +123,12 @@ SELECT m / 2::float4 FROM money_data;
    $61.50
 (1 row)
 
+SELECT m <-> '$123.45' FROM money_data;
+ ?column? 
+----------
+    $0.45
+(1 row)
+
 -- All true
 SELECT m = '$123.00' FROM money_data;
  ?column? 
diff --git a/src/test/regress/expected/oid.out b/src/test/regress/expected/oid.out
index 1eab9cc..5339a48 100644
--- a/src/test/regress/expected/oid.out
+++ b/src/test/regress/expected/oid.out
@@ -119,4 +119,17 @@ SELECT '' AS three, o.* FROM OID_TBL o WHERE o.f1 > '1234';
        |   99999999
 (3 rows)
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+ eight |     f1     |  ?column?  
+-------+------------+------------
+       |       1234 |       1111
+       |       1235 |       1112
+       |        987 |        864
+       | 4294966256 | 4294966133
+       |   99999999 |   99999876
+       |          5 |        118
+       |         10 |        113
+       |         15 |        108
+(8 rows)
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index ce25ee0..e637420 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -750,6 +750,7 @@ macaddr8_gt(macaddr8,macaddr8)
 macaddr8_ge(macaddr8,macaddr8)
 macaddr8_ne(macaddr8,macaddr8)
 macaddr8_cmp(macaddr8,macaddr8)
+oiddist(oid,oid)
 -- restore normal output mode
 \a\t
 -- List of functions used by libpq's fe-lobj.c
@@ -1332,7 +1333,7 @@ WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND
     ao.amoprighttype = ap.amprocrighttype AND
     ap.amprocnum = 1 AND
     (pp.provolatile != po.provolatile OR
-     pp.proleakproof != po.proleakproof)
+     (NOT pp.proleakproof AND po.proleakproof))
 ORDER BY 1;
                    proc                    | vp | lp |              opr              | vo | lo 
 -------------------------------------------+----+----+-------------------------------+----+----
@@ -1862,6 +1863,7 @@ ORDER BY 1, 2, 3;
         403 |            5 | *>
         403 |            5 | >
         403 |            5 | ~>~
+        403 |            6 | <->
         405 |            1 | =
         783 |            1 | <<
         783 |            1 | @@
@@ -1971,7 +1973,7 @@ ORDER BY 1, 2, 3;
        4000 |           26 | >>
        4000 |           27 | >>=
        4000 |           28 | ^@
-(123 rows)
+(124 rows)
 
 -- Check that all opclass search operators have selectivity estimators.
 -- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out
index 8e0afe6..ee74faa 100644
--- a/src/test/regress/expected/time.out
+++ b/src/test/regress/expected/time.out
@@ -86,3 +86,19 @@ ERROR:  operator is not unique: time without time zone + time without time zone
 LINE 1: SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
                   ^
 HINT:  Could not choose a best candidate operator. You might need to add explicit type casts.
+-- distance
+SELECT f1 AS "Ten", f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
+     Ten     |           Distance            
+-------------+-------------------------------
+ 00:00:00    | @ 1 hour 23 mins 45 secs
+ 01:00:00    | @ 23 mins 45 secs
+ 02:03:00    | @ 39 mins 15 secs
+ 11:59:00    | @ 10 hours 35 mins 15 secs
+ 12:00:00    | @ 10 hours 36 mins 15 secs
+ 12:01:00    | @ 10 hours 37 mins 15 secs
+ 23:59:00    | @ 22 hours 35 mins 15 secs
+ 23:59:59.99 | @ 22 hours 36 mins 14.99 secs
+ 15:36:39    | @ 14 hours 12 mins 54 secs
+ 15:36:39    | @ 14 hours 12 mins 54 secs
+(10 rows)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 4a2fabd..dcb4205 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1604,3 +1604,214 @@ SELECT make_timestamp(2014,12,28,6,30,45.887);
  Sun Dec 28 06:30:45.887 2014
 (1 row)
 
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 58 secs
+    | @ 1453 days 6 hours 27 mins 58.6 secs
+    | @ 1453 days 6 hours 27 mins 58.5 secs
+    | @ 1453 days 6 hours 27 mins 58.4 secs
+    | @ 1493 days
+    | @ 1492 days 20 hours 55 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 6 hours 27 mins 59 secs
+    | @ 231 days 18 hours 19 mins 20 secs
+    | @ 324 days 15 hours 45 mins 59 secs
+    | @ 324 days 10 hours 45 mins 58 secs
+    | @ 324 days 11 hours 45 mins 57 secs
+    | @ 324 days 20 hours 45 mins 56 secs
+    | @ 324 days 21 hours 45 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 28 mins
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 5 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1452 days 6 hours 27 mins 59 secs
+    | @ 1451 days 6 hours 27 mins 59 secs
+    | @ 1450 days 6 hours 27 mins 59 secs
+    | @ 1449 days 6 hours 27 mins 59 secs
+    | @ 1448 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 765901 days 6 hours 27 mins 59 secs
+    | @ 695407 days 6 hours 27 mins 59 secs
+    | @ 512786 days 6 hours 27 mins 59 secs
+    | @ 330165 days 6 hours 27 mins 59 secs
+    | @ 111019 days 6 hours 27 mins 59 secs
+    | @ 74495 days 6 hours 27 mins 59 secs
+    | @ 37971 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 35077 days 17 hours 32 mins 1 sec
+    | @ 1801 days 6 hours 27 mins 59 secs
+    | @ 1800 days 6 hours 27 mins 59 secs
+    | @ 1799 days 6 hours 27 mins 59 secs
+    | @ 1495 days 6 hours 27 mins 59 secs
+    | @ 1494 days 6 hours 27 mins 59 secs
+    | @ 1493 days 6 hours 27 mins 59 secs
+    | @ 1435 days 6 hours 27 mins 59 secs
+    | @ 1434 days 6 hours 27 mins 59 secs
+    | @ 1130 days 6 hours 27 mins 59 secs
+    | @ 1129 days 6 hours 27 mins 59 secs
+    | @ 399 days 6 hours 27 mins 59 secs
+    | @ 398 days 6 hours 27 mins 59 secs
+    | @ 33 days 6 hours 27 mins 59 secs
+    | @ 32 days 6 hours 27 mins 59 secs
+(63 rows)
+
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 1 hour 23 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 43 secs
+    | @ 1453 days 7 hours 51 mins 43.6 secs
+    | @ 1453 days 7 hours 51 mins 43.5 secs
+    | @ 1453 days 7 hours 51 mins 43.4 secs
+    | @ 1493 days 1 hour 23 mins 45 secs
+    | @ 1492 days 22 hours 19 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 7 hours 51 mins 44 secs
+    | @ 231 days 16 hours 55 mins 35 secs
+    | @ 324 days 17 hours 9 mins 44 secs
+    | @ 324 days 12 hours 9 mins 43 secs
+    | @ 324 days 13 hours 9 mins 42 secs
+    | @ 324 days 22 hours 9 mins 41 secs
+    | @ 324 days 23 hours 9 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 6 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1452 days 7 hours 51 mins 44 secs
+    | @ 1451 days 7 hours 51 mins 44 secs
+    | @ 1450 days 7 hours 51 mins 44 secs
+    | @ 1449 days 7 hours 51 mins 44 secs
+    | @ 1448 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 765901 days 7 hours 51 mins 44 secs
+    | @ 695407 days 7 hours 51 mins 44 secs
+    | @ 512786 days 7 hours 51 mins 44 secs
+    | @ 330165 days 7 hours 51 mins 44 secs
+    | @ 111019 days 7 hours 51 mins 44 secs
+    | @ 74495 days 7 hours 51 mins 44 secs
+    | @ 37971 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 35077 days 16 hours 8 mins 16 secs
+    | @ 1801 days 7 hours 51 mins 44 secs
+    | @ 1800 days 7 hours 51 mins 44 secs
+    | @ 1799 days 7 hours 51 mins 44 secs
+    | @ 1495 days 7 hours 51 mins 44 secs
+    | @ 1494 days 7 hours 51 mins 44 secs
+    | @ 1493 days 7 hours 51 mins 44 secs
+    | @ 1435 days 7 hours 51 mins 44 secs
+    | @ 1434 days 7 hours 51 mins 44 secs
+    | @ 1130 days 7 hours 51 mins 44 secs
+    | @ 1129 days 7 hours 51 mins 44 secs
+    | @ 399 days 7 hours 51 mins 44 secs
+    | @ 398 days 7 hours 51 mins 44 secs
+    | @ 33 days 7 hours 51 mins 44 secs
+    | @ 32 days 7 hours 51 mins 44 secs
+(63 rows)
+
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |                Distance                
+----+----------------------------------------
+    | @ 11355 days 14 hours 23 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 43 secs
+    | @ 1452 days 20 hours 51 mins 43.6 secs
+    | @ 1452 days 20 hours 51 mins 43.5 secs
+    | @ 1452 days 20 hours 51 mins 43.4 secs
+    | @ 1492 days 14 hours 23 mins 45 secs
+    | @ 1492 days 11 hours 19 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 21 hours 51 mins 44 secs
+    | @ 232 days 2 hours 55 mins 35 secs
+    | @ 324 days 6 hours 9 mins 44 secs
+    | @ 324 days 1 hour 9 mins 43 secs
+    | @ 324 days 2 hours 9 mins 42 secs
+    | @ 324 days 11 hours 9 mins 41 secs
+    | @ 324 days 12 hours 9 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1451 days 20 hours 51 mins 44 secs
+    | @ 1450 days 20 hours 51 mins 44 secs
+    | @ 1449 days 20 hours 51 mins 44 secs
+    | @ 1448 days 20 hours 51 mins 44 secs
+    | @ 1447 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 765900 days 20 hours 51 mins 44 secs
+    | @ 695406 days 20 hours 51 mins 44 secs
+    | @ 512785 days 20 hours 51 mins 44 secs
+    | @ 330164 days 20 hours 51 mins 44 secs
+    | @ 111018 days 20 hours 51 mins 44 secs
+    | @ 74494 days 20 hours 51 mins 44 secs
+    | @ 37970 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 35078 days 3 hours 8 mins 16 secs
+    | @ 1800 days 20 hours 51 mins 44 secs
+    | @ 1799 days 20 hours 51 mins 44 secs
+    | @ 1798 days 20 hours 51 mins 44 secs
+    | @ 1494 days 20 hours 51 mins 44 secs
+    | @ 1493 days 20 hours 51 mins 44 secs
+    | @ 1492 days 20 hours 51 mins 44 secs
+    | @ 1434 days 20 hours 51 mins 44 secs
+    | @ 1433 days 20 hours 51 mins 44 secs
+    | @ 1129 days 20 hours 51 mins 44 secs
+    | @ 1128 days 20 hours 51 mins 44 secs
+    | @ 398 days 20 hours 51 mins 44 secs
+    | @ 397 days 20 hours 51 mins 44 secs
+    | @ 32 days 20 hours 51 mins 44 secs
+    | @ 31 days 20 hours 51 mins 44 secs
+(63 rows)
+
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 8a4c719..0a05e37 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2544,3 +2544,217 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
  Tue Jan 17 16:00:00 2017 PST
 (1 row)
 
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 8 hours
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 58 secs
+    | @ 1453 days 6 hours 27 mins 58.6 secs
+    | @ 1453 days 6 hours 27 mins 58.5 secs
+    | @ 1453 days 6 hours 27 mins 58.4 secs
+    | @ 1493 days
+    | @ 1492 days 20 hours 55 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 7 hours 27 mins 59 secs
+    | @ 231 days 17 hours 19 mins 20 secs
+    | @ 324 days 15 hours 45 mins 59 secs
+    | @ 324 days 19 hours 45 mins 58 secs
+    | @ 324 days 21 hours 45 mins 57 secs
+    | @ 324 days 20 hours 45 mins 56 secs
+    | @ 324 days 22 hours 45 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 28 mins
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 9 hours 27 mins 59 secs
+    | @ 1303 days 10 hours 27 mins 59 secs
+    | @ 1333 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1452 days 6 hours 27 mins 59 secs
+    | @ 1451 days 6 hours 27 mins 59 secs
+    | @ 1450 days 6 hours 27 mins 59 secs
+    | @ 1449 days 6 hours 27 mins 59 secs
+    | @ 1448 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 765901 days 6 hours 27 mins 59 secs
+    | @ 695407 days 6 hours 27 mins 59 secs
+    | @ 512786 days 6 hours 27 mins 59 secs
+    | @ 330165 days 6 hours 27 mins 59 secs
+    | @ 111019 days 6 hours 27 mins 59 secs
+    | @ 74495 days 6 hours 27 mins 59 secs
+    | @ 37971 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 35077 days 17 hours 32 mins 1 sec
+    | @ 1801 days 6 hours 27 mins 59 secs
+    | @ 1800 days 6 hours 27 mins 59 secs
+    | @ 1799 days 6 hours 27 mins 59 secs
+    | @ 1495 days 6 hours 27 mins 59 secs
+    | @ 1494 days 6 hours 27 mins 59 secs
+    | @ 1493 days 6 hours 27 mins 59 secs
+    | @ 1435 days 6 hours 27 mins 59 secs
+    | @ 1434 days 6 hours 27 mins 59 secs
+    | @ 1130 days 6 hours 27 mins 59 secs
+    | @ 1129 days 6 hours 27 mins 59 secs
+    | @ 399 days 6 hours 27 mins 59 secs
+    | @ 398 days 6 hours 27 mins 59 secs
+    | @ 33 days 6 hours 27 mins 59 secs
+    | @ 32 days 6 hours 27 mins 59 secs
+(64 rows)
+
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 9 hours 23 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 43 secs
+    | @ 1453 days 7 hours 51 mins 43.6 secs
+    | @ 1453 days 7 hours 51 mins 43.5 secs
+    | @ 1453 days 7 hours 51 mins 43.4 secs
+    | @ 1493 days 1 hour 23 mins 45 secs
+    | @ 1492 days 22 hours 19 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 8 hours 51 mins 44 secs
+    | @ 231 days 15 hours 55 mins 35 secs
+    | @ 324 days 17 hours 9 mins 44 secs
+    | @ 324 days 21 hours 9 mins 43 secs
+    | @ 324 days 23 hours 9 mins 42 secs
+    | @ 324 days 22 hours 9 mins 41 secs
+    | @ 325 days 9 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 10 hours 51 mins 44 secs
+    | @ 1303 days 11 hours 51 mins 44 secs
+    | @ 1333 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1452 days 7 hours 51 mins 44 secs
+    | @ 1451 days 7 hours 51 mins 44 secs
+    | @ 1450 days 7 hours 51 mins 44 secs
+    | @ 1449 days 7 hours 51 mins 44 secs
+    | @ 1448 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 765901 days 7 hours 51 mins 44 secs
+    | @ 695407 days 7 hours 51 mins 44 secs
+    | @ 512786 days 7 hours 51 mins 44 secs
+    | @ 330165 days 7 hours 51 mins 44 secs
+    | @ 111019 days 7 hours 51 mins 44 secs
+    | @ 74495 days 7 hours 51 mins 44 secs
+    | @ 37971 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 35077 days 16 hours 8 mins 16 secs
+    | @ 1801 days 7 hours 51 mins 44 secs
+    | @ 1800 days 7 hours 51 mins 44 secs
+    | @ 1799 days 7 hours 51 mins 44 secs
+    | @ 1495 days 7 hours 51 mins 44 secs
+    | @ 1494 days 7 hours 51 mins 44 secs
+    | @ 1493 days 7 hours 51 mins 44 secs
+    | @ 1435 days 7 hours 51 mins 44 secs
+    | @ 1434 days 7 hours 51 mins 44 secs
+    | @ 1130 days 7 hours 51 mins 44 secs
+    | @ 1129 days 7 hours 51 mins 44 secs
+    | @ 399 days 7 hours 51 mins 44 secs
+    | @ 398 days 7 hours 51 mins 44 secs
+    | @ 33 days 7 hours 51 mins 44 secs
+    | @ 32 days 7 hours 51 mins 44 secs
+(64 rows)
+
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |                Distance                
+----+----------------------------------------
+    | @ 11355 days 22 hours 23 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 43 secs
+    | @ 1452 days 20 hours 51 mins 43.6 secs
+    | @ 1452 days 20 hours 51 mins 43.5 secs
+    | @ 1452 days 20 hours 51 mins 43.4 secs
+    | @ 1492 days 14 hours 23 mins 45 secs
+    | @ 1492 days 11 hours 19 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 21 hours 51 mins 44 secs
+    | @ 232 days 2 hours 55 mins 35 secs
+    | @ 324 days 6 hours 9 mins 44 secs
+    | @ 324 days 10 hours 9 mins 43 secs
+    | @ 324 days 12 hours 9 mins 42 secs
+    | @ 324 days 11 hours 9 mins 41 secs
+    | @ 324 days 13 hours 9 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1452 days 23 hours 51 mins 44 secs
+    | @ 1303 days 51 mins 44 secs
+    | @ 1332 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1451 days 20 hours 51 mins 44 secs
+    | @ 1450 days 20 hours 51 mins 44 secs
+    | @ 1449 days 20 hours 51 mins 44 secs
+    | @ 1448 days 20 hours 51 mins 44 secs
+    | @ 1447 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 765900 days 20 hours 51 mins 44 secs
+    | @ 695406 days 20 hours 51 mins 44 secs
+    | @ 512785 days 20 hours 51 mins 44 secs
+    | @ 330164 days 20 hours 51 mins 44 secs
+    | @ 111018 days 20 hours 51 mins 44 secs
+    | @ 74494 days 20 hours 51 mins 44 secs
+    | @ 37970 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 35078 days 3 hours 8 mins 16 secs
+    | @ 1800 days 20 hours 51 mins 44 secs
+    | @ 1799 days 20 hours 51 mins 44 secs
+    | @ 1798 days 20 hours 51 mins 44 secs
+    | @ 1494 days 20 hours 51 mins 44 secs
+    | @ 1493 days 20 hours 51 mins 44 secs
+    | @ 1492 days 20 hours 51 mins 44 secs
+    | @ 1434 days 20 hours 51 mins 44 secs
+    | @ 1433 days 20 hours 51 mins 44 secs
+    | @ 1129 days 20 hours 51 mins 44 secs
+    | @ 1128 days 20 hours 51 mins 44 secs
+    | @ 398 days 20 hours 51 mins 44 secs
+    | @ 397 days 20 hours 51 mins 44 secs
+    | @ 32 days 20 hours 51 mins 44 secs
+    | @ 31 days 20 hours 51 mins 44 secs
+(64 rows)
+
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
index 22f80f2..24be476 100644
--- a/src/test/regress/sql/date.sql
+++ b/src/test/regress/sql/date.sql
@@ -346,3 +346,8 @@ select make_date(2013, 13, 1);
 select make_date(2013, 11, -1);
 select make_time(10, 55, 100.1);
 select make_time(24, 0, 2.1);
+
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
diff --git a/src/test/regress/sql/float4.sql b/src/test/regress/sql/float4.sql
index 646027f..35b5942 100644
--- a/src/test/regress/sql/float4.sql
+++ b/src/test/regress/sql/float4.sql
@@ -87,6 +87,9 @@ UPDATE FLOAT4_TBL
 
 SELECT '' AS five, * FROM FLOAT4_TBL;
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+
 -- test edge-case coercions to integer
 SELECT '32767.4'::float4::int2;
 SELECT '32767.6'::float4::int2;
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
index a333218..7fe9bca 100644
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -131,6 +131,9 @@ SELECT ||/ float8 '27' AS three;
 
 SELECT '' AS five, f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
 
+-- distance
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
 
 SELECT '' AS five, * FROM FLOAT8_TBL;
 
diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql
index 7dbafb6..16dd5d8 100644
--- a/src/test/regress/sql/int2.sql
+++ b/src/test/regress/sql/int2.sql
@@ -84,6 +84,16 @@ SELECT '' AS five, i.f1, i.f1 / int2 '2' AS x FROM INT2_TBL i;
 
 SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
 SELECT ((-1::int2<<15)+1::int2)::text;
diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql
index f014cb2..cff32946 100644
--- a/src/test/regress/sql/int4.sql
+++ b/src/test/regress/sql/int4.sql
@@ -93,6 +93,16 @@ SELECT '' AS five, i.f1, i.f1 / int2 '2' AS x FROM INT4_TBL i;
 
 SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql
index e890452..d7f5bde 100644
--- a/src/test/regress/sql/int8.sql
+++ b/src/test/regress/sql/int8.sql
@@ -89,6 +89,11 @@ SELECT q1 + 42::int2 AS "8plus2", q1 - 42::int2 AS "8minus2", q1 * 42::int2 AS "
 -- int2 op int8
 SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 AS "2mul8", 246::int2 / q1 AS "2div8" FROM INT8_TBL;
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+
 SELECT q2, abs(q2) FROM INT8_TBL;
 SELECT min(q1), min(q2) FROM INT8_TBL;
 SELECT max(q1), max(q2) FROM INT8_TBL;
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index bc5537d..d51c866 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -59,6 +59,8 @@ SELECT '' AS fortyfive, r1.*, r2.*
    WHERE r1.f1 > r2.f1
    ORDER BY r1.f1, r2.f1;
 
+SELECT '' AS ten, f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql
index 37b9ecc..8428d59 100644
--- a/src/test/regress/sql/money.sql
+++ b/src/test/regress/sql/money.sql
@@ -25,6 +25,7 @@ SELECT m / 2::float8 FROM money_data;
 SELECT m * 2::float4 FROM money_data;
 SELECT 2::float4 * m FROM money_data;
 SELECT m / 2::float4 FROM money_data;
+SELECT m <-> '$123.45' FROM money_data;
 
 -- All true
 SELECT m = '$123.00' FROM money_data;
diff --git a/src/test/regress/sql/oid.sql b/src/test/regress/sql/oid.sql
index 4a09689..9f54f92 100644
--- a/src/test/regress/sql/oid.sql
+++ b/src/test/regress/sql/oid.sql
@@ -40,4 +40,6 @@ SELECT '' AS four, o.* FROM OID_TBL o WHERE o.f1 >= '1234';
 
 SELECT '' AS three, o.* FROM OID_TBL o WHERE o.f1 > '1234';
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index e2014fc..959928b 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -818,7 +818,7 @@ WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND
     ao.amoprighttype = ap.amprocrighttype AND
     ap.amprocnum = 1 AND
     (pp.provolatile != po.provolatile OR
-     pp.proleakproof != po.proleakproof)
+     (NOT pp.proleakproof AND po.proleakproof))
 ORDER BY 1;
 
 
diff --git a/src/test/regress/sql/time.sql b/src/test/regress/sql/time.sql
index 99a1562..31f0330 100644
--- a/src/test/regress/sql/time.sql
+++ b/src/test/regress/sql/time.sql
@@ -40,3 +40,6 @@ SELECT f1 AS "Eight" FROM TIME_TBL WHERE f1 >= '00:00';
 -- where we do mixed-type arithmetic. - thomas 2000-12-02
 
 SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
+
+-- distance
+SELECT f1 AS "Ten", f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b7957cb..5d023dd 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -230,3 +230,11 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
 
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
+
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index c3bd46c..7f0525d 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -464,3 +464,11 @@ insert into tmptz values ('2017-01-18 00:00+00');
 explain (costs off)
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
0006-Remove-distance-operators-from-btree_gist-v06.patchtext/x-patch; name=0006-Remove-distance-operators-from-btree_gist-v06.patchDownload
diff --git a/contrib/btree_gist/Makefile b/contrib/btree_gist/Makefile
index af65120..46ab241 100644
--- a/contrib/btree_gist/Makefile
+++ b/contrib/btree_gist/Makefile
@@ -11,8 +11,9 @@ OBJS =  btree_gist.o btree_utils_num.o btree_utils_var.o btree_int2.o \
 
 EXTENSION = btree_gist
 DATA = btree_gist--unpackaged--1.0.sql btree_gist--1.0--1.1.sql \
-       btree_gist--1.1--1.2.sql btree_gist--1.2.sql btree_gist--1.2--1.3.sql \
-       btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql
+       btree_gist--1.1--1.2.sql btree_gist--1.2--1.3.sql \
+       btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql \
+       btree_gist--1.5--1.6.sql btree_gist--1.6.sql
 PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes"
 
 REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \
diff --git a/contrib/btree_gist/btree_cash.c b/contrib/btree_gist/btree_cash.c
index 894d0a2..1b0e317 100644
--- a/contrib/btree_gist/btree_cash.c
+++ b/contrib/btree_gist/btree_cash.c
@@ -95,20 +95,7 @@ PG_FUNCTION_INFO_V1(cash_dist);
 Datum
 cash_dist(PG_FUNCTION_ARGS)
 {
-	Cash		a = PG_GETARG_CASH(0);
-	Cash		b = PG_GETARG_CASH(1);
-	Cash		r;
-	Cash		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("money out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_CASH(ra);
+	return cash_distance(fcinfo);
 }
 
 /**************************************************
diff --git a/contrib/btree_gist/btree_date.c b/contrib/btree_gist/btree_date.c
index 992ce57..f3f0fa1 100644
--- a/contrib/btree_gist/btree_date.c
+++ b/contrib/btree_gist/btree_date.c
@@ -113,12 +113,7 @@ PG_FUNCTION_INFO_V1(date_dist);
 Datum
 date_dist(PG_FUNCTION_ARGS)
 {
-	/* we assume the difference can't overflow */
-	Datum		diff = DirectFunctionCall2(date_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
+	return date_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index 6b20f44..0a9148d 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -93,14 +93,7 @@ PG_FUNCTION_INFO_V1(float4_dist);
 Datum
 float4_dist(PG_FUNCTION_ARGS)
 {
-	float4		a = PG_GETARG_FLOAT4(0);
-	float4		b = PG_GETARG_FLOAT4(1);
-	float4		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT4(Abs(r));
+	return float4dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index ee114cb..8b73b57 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -101,14 +101,7 @@ PG_FUNCTION_INFO_V1(float8_dist);
 Datum
 float8_dist(PG_FUNCTION_ARGS)
 {
-	float8		a = PG_GETARG_FLOAT8(0);
-	float8		b = PG_GETARG_FLOAT8(1);
-	float8		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT8(Abs(r));
+	return float8dist(fcinfo);
 }
 
 /**************************************************
diff --git a/contrib/btree_gist/btree_gist--1.2.sql b/contrib/btree_gist/btree_gist--1.2.sql
deleted file mode 100644
index 1efe753..0000000
--- a/contrib/btree_gist/btree_gist--1.2.sql
+++ /dev/null
@@ -1,1570 +0,0 @@
-/* contrib/btree_gist/btree_gist--1.2.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
-
-CREATE FUNCTION gbtreekey4_in(cstring)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey4_out(gbtreekey4)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey4 (
-	INTERNALLENGTH = 4,
-	INPUT  = gbtreekey4_in,
-	OUTPUT = gbtreekey4_out
-);
-
-CREATE FUNCTION gbtreekey8_in(cstring)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey8_out(gbtreekey8)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey8 (
-	INTERNALLENGTH = 8,
-	INPUT  = gbtreekey8_in,
-	OUTPUT = gbtreekey8_out
-);
-
-CREATE FUNCTION gbtreekey16_in(cstring)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey16_out(gbtreekey16)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey16 (
-	INTERNALLENGTH = 16,
-	INPUT  = gbtreekey16_in,
-	OUTPUT = gbtreekey16_out
-);
-
-CREATE FUNCTION gbtreekey32_in(cstring)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey32_out(gbtreekey32)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey32 (
-	INTERNALLENGTH = 32,
-	INPUT  = gbtreekey32_in,
-	OUTPUT = gbtreekey32_out
-);
-
-CREATE FUNCTION gbtreekey_var_in(cstring)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey_var (
-	INTERNALLENGTH = VARIABLE,
-	INPUT  = gbtreekey_var_in,
-	OUTPUT = gbtreekey_var_out,
-	STORAGE = EXTENDED
-);
-
---distance operators
-
-CREATE FUNCTION cash_dist(money, money)
-RETURNS money
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = money,
-	RIGHTARG = money,
-	PROCEDURE = cash_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION date_dist(date, date)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = date,
-	RIGHTARG = date,
-	PROCEDURE = date_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float4_dist(float4, float4)
-RETURNS float4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float4,
-	RIGHTARG = float4,
-	PROCEDURE = float4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float8_dist(float8, float8)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float8,
-	RIGHTARG = float8,
-	PROCEDURE = float8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int2_dist(int2, int2)
-RETURNS int2
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int2,
-	RIGHTARG = int2,
-	PROCEDURE = int2_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int4_dist(int4, int4)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int4,
-	RIGHTARG = int4,
-	PROCEDURE = int4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int8_dist(int8, int8)
-RETURNS int8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int8,
-	RIGHTARG = int8,
-	PROCEDURE = int8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION interval_dist(interval, interval)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = interval,
-	RIGHTARG = interval,
-	PROCEDURE = interval_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION oid_dist(oid, oid)
-RETURNS oid
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = oid,
-	RIGHTARG = oid,
-	PROCEDURE = oid_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION time_dist(time, time)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = time,
-	RIGHTARG = time,
-	PROCEDURE = time_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION ts_dist(timestamp, timestamp)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamp,
-	RIGHTARG = timestamp,
-	PROCEDURE = ts_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION tstz_dist(timestamptz, timestamptz)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamptz,
-	RIGHTARG = timestamptz,
-	PROCEDURE = tstz_dist,
-	COMMUTATOR = '<->'
-);
-
-
---
---
---
--- oid ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_oid_ops
-DEFAULT FOR TYPE oid USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
-	FUNCTION	2	gbt_oid_union (internal, internal),
-	FUNCTION	3	gbt_oid_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_oid_picksplit (internal, internal),
-	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
--- Add operators that are new in 9.1.  We do it like this, leaving them
--- "loose" in the operator family rather than bound into the opclass, because
--- that's the only state that can be reproduced during an upgrade from 9.0.
-ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
-	OPERATOR	6	<> (oid, oid) ,
-	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
-	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
-	-- Also add support function for index-only-scans, added in 9.5.
-	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
-
-
---
---
---
--- int2 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_union(internal, internal)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int2_ops
-DEFAULT FOR TYPE int2 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
-	FUNCTION	2	gbt_int2_union (internal, internal),
-	FUNCTION	3	gbt_int2_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int2_picksplit (internal, internal),
-	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
-	STORAGE		gbtreekey4;
-
-ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
-	OPERATOR	6	<> (int2, int2) ,
-	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
-	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
-
---
---
---
--- int4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int4_ops
-DEFAULT FOR TYPE int4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
-	FUNCTION	2	gbt_int4_union (internal, internal),
-	FUNCTION	3	gbt_int4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int4_picksplit (internal, internal),
-	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
-	OPERATOR	6	<> (int4, int4) ,
-	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
-	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
-
-
---
---
---
--- int8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int8_ops
-DEFAULT FOR TYPE int8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
-	FUNCTION	2	gbt_int8_union (internal, internal),
-	FUNCTION	3	gbt_int8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int8_picksplit (internal, internal),
-	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
-	OPERATOR	6	<> (int8, int8) ,
-	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
-	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
-
---
---
---
--- float4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float4_ops
-DEFAULT FOR TYPE float4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
-	FUNCTION	2	gbt_float4_union (internal, internal),
-	FUNCTION	3	gbt_float4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float4_picksplit (internal, internal),
-	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
-	OPERATOR	6	<> (float4, float4) ,
-	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
-	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
-
---
---
---
--- float8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float8_ops
-DEFAULT FOR TYPE float8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
-	FUNCTION	2	gbt_float8_union (internal, internal),
-	FUNCTION	3	gbt_float8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float8_picksplit (internal, internal),
-	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
-	OPERATOR	6	<> (float8, float8) ,
-	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
-	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
-
---
---
---
--- timestamp ops
---
---
---
-
-CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamp_ops
-DEFAULT FOR TYPE timestamp USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_ts_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
-	OPERATOR	6	<> (timestamp, timestamp) ,
-	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
-	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamptz_ops
-DEFAULT FOR TYPE timestamptz USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_tstz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
-	OPERATOR	6	<> (timestamptz, timestamptz) ,
-	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
-	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
-
---
---
---
--- time ops
---
---
---
-
-CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_time_ops
-DEFAULT FOR TYPE time USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_time_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
-	OPERATOR	6	<> (time, time) ,
-	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
-	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
-
-
-CREATE OPERATOR CLASS gist_timetz_ops
-DEFAULT FOR TYPE timetz USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_timetz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
-	OPERATOR	6	<> (timetz, timetz) ;
-	-- no 'fetch' function, as the compress function is lossy.
-
-
---
---
---
--- date ops
---
---
---
-
-CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_date_ops
-DEFAULT FOR TYPE date USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
-	FUNCTION	2	gbt_date_union (internal, internal),
-	FUNCTION	3	gbt_date_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_date_picksplit (internal, internal),
-	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
-	OPERATOR	6	<> (date, date) ,
-	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
-	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
-
-
---
---
---
--- interval ops
---
---
---
-
-CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_union(internal, internal)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_interval_ops
-DEFAULT FOR TYPE interval USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
-	FUNCTION	2	gbt_intv_union (internal, internal),
-	FUNCTION	3	gbt_intv_compress (internal),
-	FUNCTION	4	gbt_intv_decompress (internal),
-	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_intv_picksplit (internal, internal),
-	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
-	STORAGE		gbtreekey32;
-
-ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
-	OPERATOR	6	<> (interval, interval) ,
-	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
-	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
-
-
---
---
---
--- cash ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cash_ops
-DEFAULT FOR TYPE money USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
-	FUNCTION	2	gbt_cash_union (internal, internal),
-	FUNCTION	3	gbt_cash_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_cash_picksplit (internal, internal),
-	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
-	OPERATOR	6	<> (money, money) ,
-	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
-	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
-	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
-
-
---
---
---
--- macaddr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_macaddr_ops
-DEFAULT FOR TYPE macaddr USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
-	FUNCTION	2	gbt_macad_union (internal, internal),
-	FUNCTION	3	gbt_macad_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_macad_picksplit (internal, internal),
-	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
-	OPERATOR	6	<> (macaddr, macaddr) ,
-	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
-
-
---
---
---
--- text/ bpchar ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_text_ops
-DEFAULT FOR TYPE text USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_text_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
-	OPERATOR	6	<> (text, text) ,
-	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
-
-
----- Create the operator class
-CREATE OPERATOR CLASS gist_bpchar_ops
-DEFAULT FOR TYPE bpchar USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_bpchar_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
-	OPERATOR	6	<> (bpchar, bpchar) ,
-	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
-
---
---
--- bytea ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bytea_ops
-DEFAULT FOR TYPE bytea USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
-	FUNCTION	2	gbt_bytea_union (internal, internal),
-	FUNCTION	3	gbt_bytea_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
-	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
-	OPERATOR	6	<> (bytea, bytea) ,
-	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
-
-
---
---
---
--- numeric ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_numeric_ops
-DEFAULT FOR TYPE numeric USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
-	FUNCTION	2	gbt_numeric_union (internal, internal),
-	FUNCTION	3	gbt_numeric_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
-	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
-	OPERATOR	6	<> (numeric, numeric) ,
-	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
-
-
---
---
--- bit ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bit_ops
-DEFAULT FOR TYPE bit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
-	OPERATOR	6	<> (bit, bit) ,
-	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
-
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_vbit_ops
-DEFAULT FOR TYPE varbit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
-	OPERATOR	6	<> (varbit, varbit) ,
-	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
-
-
---
---
---
--- inet/cidr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_inet_ops
-DEFAULT FOR TYPE inet USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
-	OPERATOR	6	<>  (inet, inet) ;
-	-- no fetch support, the compress function is lossy
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cidr_ops
-DEFAULT FOR TYPE cidr USING gist
-AS
-	OPERATOR	1	<  (inet, inet)  ,
-	OPERATOR	2	<= (inet, inet)  ,
-	OPERATOR	3	=  (inet, inet)  ,
-	OPERATOR	4	>= (inet, inet)  ,
-	OPERATOR	5	>  (inet, inet)  ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
-	OPERATOR	6	<> (inet, inet) ;
-	-- no fetch support, the compress function is lossy
diff --git a/contrib/btree_gist/btree_gist--1.5--1.6.sql b/contrib/btree_gist/btree_gist--1.5--1.6.sql
new file mode 100644
index 0000000..ef4424e
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.5--1.6.sql
@@ -0,0 +1,99 @@
+/* contrib/btree_gist/btree_gist--1.5--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.6'" to load this file. \quit
+
+-- drop btree_gist distance operators from opfamilies
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist DROP OPERATOR 15 (int2, int2);
+ALTER OPERATOR FAMILY gist_int4_ops USING gist DROP OPERATOR 15 (int4, int4);
+ALTER OPERATOR FAMILY gist_int8_ops USING gist DROP OPERATOR 15 (int8, int8);
+ALTER OPERATOR FAMILY gist_float4_ops USING gist DROP OPERATOR 15 (float4, float4);
+ALTER OPERATOR FAMILY gist_float8_ops USING gist DROP OPERATOR 15 (float8, float8);
+ALTER OPERATOR FAMILY gist_oid_ops USING gist DROP OPERATOR 15 (oid, oid);
+ALTER OPERATOR FAMILY gist_cash_ops USING gist DROP OPERATOR 15 (money, money);
+ALTER OPERATOR FAMILY gist_date_ops USING gist DROP OPERATOR 15 (date, date);
+ALTER OPERATOR FAMILY gist_time_ops USING gist DROP OPERATOR 15 (time, time);
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist DROP OPERATOR 15 (timestamp, timestamp);
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist DROP OPERATOR 15 (timestamptz, timestamptz);
+ALTER OPERATOR FAMILY gist_interval_ops USING gist DROP OPERATOR 15 (interval, interval);
+
+-- add pg_catalog distance operators to opfamilies
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD OPERATOR 15 <-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD OPERATOR 15 <-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD OPERATOR 15 <-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD OPERATOR 15 <-> (float4, float4) FOR ORDER BY pg_catalog.float_ops;
+ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD OPERATOR 15 <-> (float8, float8) FOR ORDER BY pg_catalog.float_ops;
+ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD OPERATOR 15 <-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops;
+ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD OPERATOR 15 <-> (money, money) FOR ORDER BY pg_catalog.money_ops;
+ALTER OPERATOR FAMILY gist_date_ops USING gist ADD OPERATOR 15 <-> (date, date) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_time_ops USING gist ADD OPERATOR 15 <-> (time, time) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD OPERATOR 15 <-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD OPERATOR 15 <-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD OPERATOR 15 <-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops;
+
+-- disable implicit pg_catalog search
+
+DO
+$$
+BEGIN
+	EXECUTE 'SET LOCAL search_path TO ' || current_schema() || ', pg_catalog';
+END
+$$;
+
+-- drop distance operators
+
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int2, int2);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int4, int4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int8, int8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float4, float4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float8, float8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (oid, oid);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (money, money);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (date, date);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (time, time);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (interval, interval);
+
+DROP OPERATOR <-> (int2, int2);
+DROP OPERATOR <-> (int4, int4);
+DROP OPERATOR <-> (int8, int8);
+DROP OPERATOR <-> (float4, float4);
+DROP OPERATOR <-> (float8, float8);
+DROP OPERATOR <-> (oid, oid);
+DROP OPERATOR <-> (money, money);
+DROP OPERATOR <-> (date, date);
+DROP OPERATOR <-> (time, time);
+DROP OPERATOR <-> (timestamp, timestamp);
+DROP OPERATOR <-> (timestamptz, timestamptz);
+DROP OPERATOR <-> (interval, interval);
+
+-- drop distance functions
+
+ALTER EXTENSION btree_gist DROP FUNCTION int2_dist(int2, int2);
+ALTER EXTENSION btree_gist DROP FUNCTION int4_dist(int4, int4);
+ALTER EXTENSION btree_gist DROP FUNCTION int8_dist(int8, int8);
+ALTER EXTENSION btree_gist DROP FUNCTION float4_dist(float4, float4);
+ALTER EXTENSION btree_gist DROP FUNCTION float8_dist(float8, float8);
+ALTER EXTENSION btree_gist DROP FUNCTION oid_dist(oid, oid);
+ALTER EXTENSION btree_gist DROP FUNCTION cash_dist(money, money);
+ALTER EXTENSION btree_gist DROP FUNCTION date_dist(date, date);
+ALTER EXTENSION btree_gist DROP FUNCTION time_dist(time, time);
+ALTER EXTENSION btree_gist DROP FUNCTION ts_dist(timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP FUNCTION interval_dist(interval, interval);
+
+DROP FUNCTION int2_dist(int2, int2);
+DROP FUNCTION int4_dist(int4, int4);
+DROP FUNCTION int8_dist(int8, int8);
+DROP FUNCTION float4_dist(float4, float4);
+DROP FUNCTION float8_dist(float8, float8);
+DROP FUNCTION oid_dist(oid, oid);
+DROP FUNCTION cash_dist(money, money);
+DROP FUNCTION date_dist(date, date);
+DROP FUNCTION time_dist(time, time);
+DROP FUNCTION ts_dist(timestamp, timestamp);
+DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+DROP FUNCTION interval_dist(interval, interval);
diff --git a/contrib/btree_gist/btree_gist--1.6.sql b/contrib/btree_gist/btree_gist--1.6.sql
new file mode 100644
index 0000000..8ff8eb5
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.6.sql
@@ -0,0 +1,1615 @@
+/* contrib/btree_gist/btree_gist--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
+
+CREATE FUNCTION gbtreekey4_in(cstring)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey4_out(gbtreekey4)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey4 (
+	INTERNALLENGTH = 4,
+	INPUT  = gbtreekey4_in,
+	OUTPUT = gbtreekey4_out
+);
+
+CREATE FUNCTION gbtreekey8_in(cstring)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey8_out(gbtreekey8)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey8 (
+	INTERNALLENGTH = 8,
+	INPUT  = gbtreekey8_in,
+	OUTPUT = gbtreekey8_out
+);
+
+CREATE FUNCTION gbtreekey16_in(cstring)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey16_out(gbtreekey16)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey16 (
+	INTERNALLENGTH = 16,
+	INPUT  = gbtreekey16_in,
+	OUTPUT = gbtreekey16_out
+);
+
+CREATE FUNCTION gbtreekey32_in(cstring)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey32_out(gbtreekey32)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey32 (
+	INTERNALLENGTH = 32,
+	INPUT  = gbtreekey32_in,
+	OUTPUT = gbtreekey32_out
+);
+
+CREATE FUNCTION gbtreekey_var_in(cstring)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey_var (
+	INTERNALLENGTH = VARIABLE,
+	INPUT  = gbtreekey_var_in,
+	OUTPUT = gbtreekey_var_out,
+	STORAGE = EXTENDED
+);
+
+
+--
+--
+--
+-- oid ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_oid_ops
+DEFAULT FOR TYPE oid USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
+	FUNCTION	2	gbt_oid_union (internal, internal),
+	FUNCTION	3	gbt_oid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_oid_picksplit (internal, internal),
+	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+-- Add operators that are new in 9.1.  We do it like this, leaving them
+-- "loose" in the operator family rather than bound into the opclass, because
+-- that's the only state that can be reproduced during an upgrade from 9.0.
+ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
+	OPERATOR	6	<> (oid, oid) ,
+	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
+	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
+	-- Also add support function for index-only-scans, added in 9.5.
+	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
+
+
+--
+--
+--
+-- int2 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_union(internal, internal)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int2_ops
+DEFAULT FOR TYPE int2 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
+	FUNCTION	2	gbt_int2_union (internal, internal),
+	FUNCTION	3	gbt_int2_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int2_picksplit (internal, internal),
+	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
+	STORAGE		gbtreekey4;
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
+	OPERATOR	6	<> (int2, int2) ,
+	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
+	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
+
+--
+--
+--
+-- int4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int4_ops
+DEFAULT FOR TYPE int4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
+	FUNCTION	2	gbt_int4_union (internal, internal),
+	FUNCTION	3	gbt_int4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int4_picksplit (internal, internal),
+	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
+	OPERATOR	6	<> (int4, int4) ,
+	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
+	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
+
+
+--
+--
+--
+-- int8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int8_ops
+DEFAULT FOR TYPE int8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
+	FUNCTION	2	gbt_int8_union (internal, internal),
+	FUNCTION	3	gbt_int8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int8_picksplit (internal, internal),
+	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
+	OPERATOR	6	<> (int8, int8) ,
+	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
+	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
+
+--
+--
+--
+-- float4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float4_ops
+DEFAULT FOR TYPE float4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
+	FUNCTION	2	gbt_float4_union (internal, internal),
+	FUNCTION	3	gbt_float4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float4_picksplit (internal, internal),
+	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
+	OPERATOR	6	<> (float4, float4) ,
+	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
+	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
+
+--
+--
+--
+-- float8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float8_ops
+DEFAULT FOR TYPE float8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
+	FUNCTION	2	gbt_float8_union (internal, internal),
+	FUNCTION	3	gbt_float8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float8_picksplit (internal, internal),
+	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
+	OPERATOR	6	<> (float8, float8) ,
+	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
+	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
+
+--
+--
+--
+-- timestamp ops
+--
+--
+--
+
+CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamp_ops
+DEFAULT FOR TYPE timestamp USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_ts_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
+	OPERATOR	6	<> (timestamp, timestamp) ,
+	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
+	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamptz_ops
+DEFAULT FOR TYPE timestamptz USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_tstz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
+	OPERATOR	6	<> (timestamptz, timestamptz) ,
+	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
+	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
+
+--
+--
+--
+-- time ops
+--
+--
+--
+
+CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_time_ops
+DEFAULT FOR TYPE time USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_time_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
+	OPERATOR	6	<> (time, time) ,
+	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
+	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
+
+
+CREATE OPERATOR CLASS gist_timetz_ops
+DEFAULT FOR TYPE timetz USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_timetz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
+	OPERATOR	6	<> (timetz, timetz) ;
+	-- no 'fetch' function, as the compress function is lossy.
+
+
+--
+--
+--
+-- date ops
+--
+--
+--
+
+CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_date_ops
+DEFAULT FOR TYPE date USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
+	FUNCTION	2	gbt_date_union (internal, internal),
+	FUNCTION	3	gbt_date_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_date_picksplit (internal, internal),
+	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
+	OPERATOR	6	<> (date, date) ,
+	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
+	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
+
+
+--
+--
+--
+-- interval ops
+--
+--
+--
+
+CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_interval_ops
+DEFAULT FOR TYPE interval USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
+	FUNCTION	2	gbt_intv_union (internal, internal),
+	FUNCTION	3	gbt_intv_compress (internal),
+	FUNCTION	4	gbt_intv_decompress (internal),
+	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_intv_picksplit (internal, internal),
+	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
+	OPERATOR	6	<> (interval, interval) ,
+	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
+	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
+
+
+--
+--
+--
+-- cash ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cash_ops
+DEFAULT FOR TYPE money USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
+	FUNCTION	2	gbt_cash_union (internal, internal),
+	FUNCTION	3	gbt_cash_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_cash_picksplit (internal, internal),
+	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
+	OPERATOR	6	<> (money, money) ,
+	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
+	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
+	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
+
+
+--
+--
+--
+-- macaddr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_macaddr_ops
+DEFAULT FOR TYPE macaddr USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
+	FUNCTION	2	gbt_macad_union (internal, internal),
+	FUNCTION	3	gbt_macad_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_macad_picksplit (internal, internal),
+	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
+	OPERATOR	6	<> (macaddr, macaddr) ,
+	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
+
+
+--
+--
+--
+-- text/ bpchar ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_text_ops
+DEFAULT FOR TYPE text USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_text_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
+	OPERATOR	6	<> (text, text) ,
+	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
+
+
+---- Create the operator class
+CREATE OPERATOR CLASS gist_bpchar_ops
+DEFAULT FOR TYPE bpchar USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_bpchar_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
+	OPERATOR	6	<> (bpchar, bpchar) ,
+	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
+
+--
+--
+-- bytea ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bytea_ops
+DEFAULT FOR TYPE bytea USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
+	FUNCTION	2	gbt_bytea_union (internal, internal),
+	FUNCTION	3	gbt_bytea_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
+	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
+	OPERATOR	6	<> (bytea, bytea) ,
+	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- numeric ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_numeric_ops
+DEFAULT FOR TYPE numeric USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
+	FUNCTION	2	gbt_numeric_union (internal, internal),
+	FUNCTION	3	gbt_numeric_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
+	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
+	OPERATOR	6	<> (numeric, numeric) ,
+	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
+
+
+--
+--
+-- bit ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bit_ops
+DEFAULT FOR TYPE bit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
+	OPERATOR	6	<> (bit, bit) ,
+	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
+
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_vbit_ops
+DEFAULT FOR TYPE varbit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
+	OPERATOR	6	<> (varbit, varbit) ,
+	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- inet/cidr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_inet_ops
+DEFAULT FOR TYPE inet USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
+	OPERATOR	6	<>  (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cidr_ops
+DEFAULT FOR TYPE cidr USING gist
+AS
+	OPERATOR	1	<  (inet, inet)  ,
+	OPERATOR	2	<= (inet, inet)  ,
+	OPERATOR	3	=  (inet, inet)  ,
+	OPERATOR	4	>= (inet, inet)  ,
+	OPERATOR	5	>  (inet, inet)  ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
+	OPERATOR	6	<> (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+--
+--
+--
+-- uuid ops
+--
+--
+---- define the GiST support methods
+CREATE FUNCTION gbt_uuid_consistent(internal,uuid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_uuid_ops
+DEFAULT FOR TYPE uuid USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_uuid_consistent (internal, uuid, int2, oid, internal),
+	FUNCTION	2	gbt_uuid_union (internal, internal),
+	FUNCTION	3	gbt_uuid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_uuid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_uuid_picksplit (internal, internal),
+	FUNCTION	7	gbt_uuid_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+-- These are "loose" in the opfamily for consistency with the rest of btree_gist
+ALTER OPERATOR FAMILY gist_uuid_ops USING gist ADD
+	OPERATOR	6	<>  (uuid, uuid) ,
+	FUNCTION	9 (uuid, uuid) gbt_uuid_fetch (internal) ;
+
+
+-- Add support for indexing macaddr8 columns
+
+-- define the GiST support methods
+CREATE FUNCTION gbt_macad8_consistent(internal,macaddr8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_macaddr8_ops
+DEFAULT FOR TYPE macaddr8 USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_macad8_consistent (internal, macaddr8, int2, oid, internal),
+	FUNCTION	2	gbt_macad8_union (internal, internal),
+	FUNCTION	3	gbt_macad8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_macad8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_macad8_picksplit (internal, internal),
+	FUNCTION	7	gbt_macad8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_macaddr8_ops USING gist ADD
+	OPERATOR	6	<> (macaddr8, macaddr8) ,
+	FUNCTION	9 (macaddr8, macaddr8) gbt_macad8_fetch (internal);
+
+--
+--
+--
+-- enum ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_enum_consistent(internal,anyenum,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_enum_ops
+DEFAULT FOR TYPE anyenum USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_enum_consistent (internal, anyenum, int2, oid, internal),
+	FUNCTION	2	gbt_enum_union (internal, internal),
+	FUNCTION	3	gbt_enum_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_enum_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_enum_picksplit (internal, internal),
+	FUNCTION	7	gbt_enum_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_enum_ops USING gist ADD
+	OPERATOR	6	<> (anyenum, anyenum) ,
+	FUNCTION	9 (anyenum, anyenum) gbt_enum_fetch (internal) ;
diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control
index 81c8509..9ced3bc 100644
--- a/contrib/btree_gist/btree_gist.control
+++ b/contrib/btree_gist/btree_gist.control
@@ -1,5 +1,5 @@
 # btree_gist extension
 comment = 'support for indexing common datatypes in GiST'
-default_version = '1.5'
+default_version = '1.6'
 module_pathname = '$libdir/btree_gist'
 relocatable = true
diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c
index 7674e2d..2afc343 100644
--- a/contrib/btree_gist/btree_int2.c
+++ b/contrib/btree_gist/btree_int2.c
@@ -94,20 +94,7 @@ PG_FUNCTION_INFO_V1(int2_dist);
 Datum
 int2_dist(PG_FUNCTION_ARGS)
 {
-	int16		a = PG_GETARG_INT16(0);
-	int16		b = PG_GETARG_INT16(1);
-	int16		r;
-	int16		ra;
-
-	if (pg_sub_s16_overflow(a, b, &r) ||
-		r == PG_INT16_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("smallint out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT16(ra);
+	return int2dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c
index 80005ab..2361ce7 100644
--- a/contrib/btree_gist/btree_int4.c
+++ b/contrib/btree_gist/btree_int4.c
@@ -95,20 +95,7 @@ PG_FUNCTION_INFO_V1(int4_dist);
 Datum
 int4_dist(PG_FUNCTION_ARGS)
 {
-	int32		a = PG_GETARG_INT32(0);
-	int32		b = PG_GETARG_INT32(1);
-	int32		r;
-	int32		ra;
-
-	if (pg_sub_s32_overflow(a, b, &r) ||
-		r == PG_INT32_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("integer out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT32(ra);
+	return int4dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c
index b0fd3e1..182d7c4 100644
--- a/contrib/btree_gist/btree_int8.c
+++ b/contrib/btree_gist/btree_int8.c
@@ -95,20 +95,7 @@ PG_FUNCTION_INFO_V1(int8_dist);
 Datum
 int8_dist(PG_FUNCTION_ARGS)
 {
-	int64		a = PG_GETARG_INT64(0);
-	int64		b = PG_GETARG_INT64(1);
-	int64		r;
-	int64		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("bigint out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT64(ra);
+	return int8dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_interval.c b/contrib/btree_gist/btree_interval.c
index 3a527a7..c68d48e 100644
--- a/contrib/btree_gist/btree_interval.c
+++ b/contrib/btree_gist/btree_interval.c
@@ -128,11 +128,7 @@ PG_FUNCTION_INFO_V1(interval_dist);
 Datum
 interval_dist(PG_FUNCTION_ARGS)
 {
-	Datum		diff = DirectFunctionCall2(interval_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+	return interval_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_oid.c b/contrib/btree_gist/btree_oid.c
index 00e7019..c702d51 100644
--- a/contrib/btree_gist/btree_oid.c
+++ b/contrib/btree_gist/btree_oid.c
@@ -100,15 +100,7 @@ PG_FUNCTION_INFO_V1(oid_dist);
 Datum
 oid_dist(PG_FUNCTION_ARGS)
 {
-	Oid			a = PG_GETARG_OID(0);
-	Oid			b = PG_GETARG_OID(1);
-	Oid			res;
-
-	if (a < b)
-		res = b - a;
-	else
-		res = a - b;
-	PG_RETURN_OID(res);
+	return oiddist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_time.c b/contrib/btree_gist/btree_time.c
index 90cf655..cfafd02 100644
--- a/contrib/btree_gist/btree_time.c
+++ b/contrib/btree_gist/btree_time.c
@@ -141,11 +141,7 @@ PG_FUNCTION_INFO_V1(time_dist);
 Datum
 time_dist(PG_FUNCTION_ARGS)
 {
-	Datum		diff = DirectFunctionCall2(time_mi_time,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+	return time_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c
index 49d1849..9e62f7d 100644
--- a/contrib/btree_gist/btree_ts.c
+++ b/contrib/btree_gist/btree_ts.c
@@ -146,48 +146,14 @@ PG_FUNCTION_INFO_V1(ts_dist);
 Datum
 ts_dist(PG_FUNCTION_ARGS)
 {
-	Timestamp	a = PG_GETARG_TIMESTAMP(0);
-	Timestamp	b = PG_GETARG_TIMESTAMP(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-	else
-		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-												  PG_GETARG_DATUM(0),
-												  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
+	return timestamp_distance(fcinfo);
 }
 
 PG_FUNCTION_INFO_V1(tstz_dist);
 Datum
 tstz_dist(PG_FUNCTION_ARGS)
 {
-	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
-	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-
-	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-											  PG_GETARG_DATUM(0),
-											  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
+	return timestamptz_distance(fcinfo);
 }
 
 
diff --git a/doc/src/sgml/btree-gist.sgml b/doc/src/sgml/btree-gist.sgml
index 774442f..6eb4a18 100644
--- a/doc/src/sgml/btree-gist.sgml
+++ b/doc/src/sgml/btree-gist.sgml
@@ -96,6 +96,19 @@ INSERT 0 1
  </sect2>
 
  <sect2>
+  <title>Upgrade notes for version 1.6</title>
+
+  <para>
+   In version 1.6 <literal>btree_gist</literal> switched to using in-core
+   distance operators, and its own implementations were removed.  References to
+   these operators in <literal>btree_gist</literal> opclasses will be updated
+   automatically during the extension upgrade, but if the user has created
+   objects referencing these operators or functions, then these objects must be
+   dropped manually before updating the extension.
+  </para>
+ </sect2>
+
+ <sect2>
   <title>Authors</title>
 
   <para>
0007-Add-regression-tests-for-kNN-btree-v06.patchtext/x-patch; name=0007-Add-regression-tests-for-kNN-btree-v06.patchDownload
diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index b21298a..0cefbcc 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -250,3 +250,1109 @@ select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass;
  {vacuum_cleanup_index_scale_factor=70.0}
 (1 row)
 
+---
+--- Test B-tree distance ordering
+---
+SET enable_bitmapscan = OFF;
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+          QUERY PLAN          
+------------------------------
+ Sort
+   Sort Key: ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+                   QUERY PLAN                   
+------------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((random <-> 1))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)))
+   Order By: (random <-> 4000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+                                                                                               QUERY PLAN                                                                                               
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)) AND (random = ANY ('{1809552,1919087,2321799,2648497,3000193,3013326,4157193,4488889,5257716,5593978,NULL}'::integer[])))
+   Order By: (random <-> 3000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+ seqno | random  
+-------+---------
+  5380 | 3000193
+  6262 | 3013326
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+  6448 | 4157193
+   210 | 1809552
+  4408 | 4488889
+  6320 | 5257716
+  1836 | 5593978
+(10 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+-- test parallel KNN scan
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+RESET enable_indexscan;
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, 1000000) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+EXPLAIN (COSTS OFF)
+SELECT i FROM bt_knn_test WHERE i > 8000000;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Gather
+   Workers Planned: 4
+   ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+         Index Cond: (i > 8000000)
+(4 rows)
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> 4000003) AS n, i * 10 AS i
+	FROM generate_series(1, 1000000) i;
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (t1.n = t2.n)
+   Join Filter: (t1.i <> t2.i)
+   ->  Subquery Scan on t1
+         ->  WindowAgg
+               ->  Gather Merge
+                     Workers Planned: 4
+                     ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                           Order By: (i <-> 4000003)
+   ->  Hash
+         ->  Gather
+               Workers Planned: 4
+               ->  Parallel Seq Scan on bt_knn_test2 t2
+(13 rows)
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+DROP TABLE bt_knn_test;
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, 100000) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (t1.n = ((i.i * 100000) + j.j))
+   Join Filter: (t1.i <> ('{4,3,2,7,8}'::integer[])[(i.i + 1)])
+   ->  Subquery Scan on t1
+         ->  WindowAgg
+               ->  Gather Merge
+                     Workers Planned: 4
+                     ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                           Index Cond: (i = ANY ('{3,4,7,8,2}'::integer[]))
+                           Order By: (i <-> 4)
+   ->  Hash
+         ->  Nested Loop
+               ->  Function Scan on generate_series i
+               ->  Function Scan on generate_series j
+(14 rows)
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+ROLLBACK;
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: (ROW(thousand, tenthous) >= ROW(997, 5000))
+   Order By: (thousand <-> 998)
+(3 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+        1 |     9001
+        1 |     8001
+        1 |     7001
+        1 |     6001
+        1 |     5001
+        1 |     4001
+        1 |     3001
+        1 |     2001
+        1 |     1001
+        1 |        1
+        0 |     9000
+        0 |     8000
+        0 |     7000
+        0 |     6000
+        0 |     5000
+        0 |     4000
+        0 |     3000
+        0 |     2000
+        0 |     1000
+        0 |        0
+          |        1
+          |        2
+          |        3
+(33 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: ((thousand > 100) AND (thousand < 800) AND (thousand = ANY ('{0,123,234,345,456,678,901,NULL}'::smallint[])))
+   Order By: (thousand <-> '300'::bigint)
+(3 rows)
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+ thousand | tenthous 
+----------+----------
+      345 |      345
+      345 |     1345
+      345 |     2345
+      345 |     3345
+      345 |     4345
+      345 |     5345
+      345 |     6345
+      345 |     7345
+      345 |     8345
+      345 |     9345
+      234 |      234
+      234 |     1234
+      234 |     2234
+      234 |     3234
+      234 |     4234
+      234 |     5234
+      234 |     6234
+      234 |     7234
+      234 |     8234
+      234 |     9234
+      456 |      456
+      456 |     1456
+      456 |     2456
+      456 |     3456
+      456 |     4456
+      456 |     5456
+      456 |     6456
+      456 |     7456
+      456 |     8456
+      456 |     9456
+      123 |      123
+      123 |     1123
+      123 |     2123
+      123 |     3123
+      123 |     4123
+      123 |     5123
+      123 |     6123
+      123 |     7123
+      123 |     8123
+      123 |     9123
+      678 |      678
+      678 |     1678
+      678 |     2678
+      678 |     3678
+      678 |     4678
+      678 |     5678
+      678 |     6678
+      678 |     7678
+      678 |     8678
+      678 |     9678
+(50 rows)
+
+DROP INDEX tenk3_idx;
+-- Test order by distance ordering on non-first column
+SET enable_sort = OFF;
+-- Ranges are not supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand > 120
+ORDER BY tenthous <-> 3500;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Sort
+   Sort Key: ((tenthous <-> 3500))
+   ->  Index Only Scan using tenk1_thous_tenthous on tenk1
+         Index Cond: (thousand > 120)
+(4 rows)
+
+-- Equality restriction on the first column is supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand = 120
+ORDER BY tenthous <-> 3500;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Index Only Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: (thousand = 120)
+   Order By: (tenthous <-> 3500)
+(3 rows)
+
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand = 120
+ORDER BY tenthous <-> 3500;
+ thousand | tenthous 
+----------+----------
+      120 |     3120
+      120 |     4120
+      120 |     2120
+      120 |     5120
+      120 |     1120
+      120 |     6120
+      120 |      120
+      120 |     7120
+      120 |     8120
+      120 |     9120
+(10 rows)
+
+-- IN restriction on the first column is not supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY tenthous <-> 3500;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Sort
+   Sort Key: ((tenthous <-> 3500))
+   ->  Index Only Scan using tenk1_thous_tenthous on tenk1
+         Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
+(4 rows)
+
+-- Test kNN search using 4-column index
+CREATE INDEX tenk1_knn_idx ON tenk1(ten, hundred, thousand, tenthous);
+-- Ordering by distance to 3rd column
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY thousand <-> 600;
+                  QUERY PLAN                  
+----------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = 3) AND (hundred = 43))
+   Order By: (thousand <-> 600)
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY thousand <-> 600;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      43 |      643 |      643
+   3 |      43 |      643 |     1643
+   3 |      43 |      643 |     2643
+   3 |      43 |      643 |     3643
+   3 |      43 |      643 |     4643
+   3 |      43 |      643 |     5643
+   3 |      43 |      643 |     6643
+   3 |      43 |      643 |     7643
+   3 |      43 |      643 |     8643
+   3 |      43 |      643 |     9643
+   3 |      43 |      543 |     9543
+   3 |      43 |      543 |     8543
+   3 |      43 |      543 |     7543
+   3 |      43 |      543 |     6543
+   3 |      43 |      543 |     5543
+   3 |      43 |      543 |     4543
+   3 |      43 |      543 |     3543
+   3 |      43 |      543 |     2543
+   3 |      43 |      543 |     1543
+   3 |      43 |      543 |      543
+   3 |      43 |      743 |      743
+   3 |      43 |      743 |     1743
+   3 |      43 |      743 |     2743
+   3 |      43 |      743 |     3743
+   3 |      43 |      743 |     4743
+   3 |      43 |      743 |     5743
+   3 |      43 |      743 |     6743
+   3 |      43 |      743 |     7743
+   3 |      43 |      743 |     8743
+   3 |      43 |      743 |     9743
+   3 |      43 |      443 |     9443
+   3 |      43 |      443 |     8443
+   3 |      43 |      443 |     7443
+   3 |      43 |      443 |     6443
+   3 |      43 |      443 |     5443
+   3 |      43 |      443 |     4443
+   3 |      43 |      443 |     3443
+   3 |      43 |      443 |     2443
+   3 |      43 |      443 |     1443
+   3 |      43 |      443 |      443
+   3 |      43 |      843 |      843
+   3 |      43 |      843 |     1843
+   3 |      43 |      843 |     2843
+   3 |      43 |      843 |     3843
+   3 |      43 |      843 |     4843
+   3 |      43 |      843 |     5843
+   3 |      43 |      843 |     6843
+   3 |      43 |      843 |     7843
+   3 |      43 |      843 |     8843
+   3 |      43 |      843 |     9843
+   3 |      43 |      343 |     9343
+   3 |      43 |      343 |     8343
+   3 |      43 |      343 |     7343
+   3 |      43 |      343 |     6343
+   3 |      43 |      343 |     5343
+   3 |      43 |      343 |     4343
+   3 |      43 |      343 |     3343
+   3 |      43 |      343 |     2343
+   3 |      43 |      343 |     1343
+   3 |      43 |      343 |      343
+   3 |      43 |      943 |      943
+   3 |      43 |      943 |     1943
+   3 |      43 |      943 |     2943
+   3 |      43 |      943 |     3943
+   3 |      43 |      943 |     4943
+   3 |      43 |      943 |     5943
+   3 |      43 |      943 |     6943
+   3 |      43 |      943 |     7943
+   3 |      43 |      943 |     8943
+   3 |      43 |      943 |     9943
+   3 |      43 |      243 |     9243
+   3 |      43 |      243 |     8243
+   3 |      43 |      243 |     7243
+   3 |      43 |      243 |     6243
+   3 |      43 |      243 |     5243
+   3 |      43 |      243 |     4243
+   3 |      43 |      243 |     3243
+   3 |      43 |      243 |     2243
+   3 |      43 |      243 |     1243
+   3 |      43 |      243 |      243
+   3 |      43 |      143 |     9143
+   3 |      43 |      143 |     8143
+   3 |      43 |      143 |     7143
+   3 |      43 |      143 |     6143
+   3 |      43 |      143 |     5143
+   3 |      43 |      143 |     4143
+   3 |      43 |      143 |     3143
+   3 |      43 |      143 |     2143
+   3 |      43 |      143 |     1143
+   3 |      43 |      143 |      143
+   3 |      43 |       43 |     9043
+   3 |      43 |       43 |     8043
+   3 |      43 |       43 |     7043
+   3 |      43 |       43 |     6043
+   3 |      43 |       43 |     5043
+   3 |      43 |       43 |     4043
+   3 |      43 |       43 |     3043
+   3 |      43 |       43 |     2043
+   3 |      43 |       43 |     1043
+   3 |      43 |       43 |       43
+(100 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND tenthous > 3000 ORDER BY thousand <-> 600;
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = 3) AND (hundred = 43) AND (tenthous > 3000))
+   Order By: (thousand <-> 600)
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND tenthous > 3000 ORDER BY thousand <-> 600;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      43 |      643 |     3643
+   3 |      43 |      643 |     4643
+   3 |      43 |      643 |     5643
+   3 |      43 |      643 |     6643
+   3 |      43 |      643 |     7643
+   3 |      43 |      643 |     8643
+   3 |      43 |      643 |     9643
+   3 |      43 |      543 |     9543
+   3 |      43 |      543 |     8543
+   3 |      43 |      543 |     7543
+   3 |      43 |      543 |     6543
+   3 |      43 |      543 |     5543
+   3 |      43 |      543 |     4543
+   3 |      43 |      543 |     3543
+   3 |      43 |      743 |     3743
+   3 |      43 |      743 |     4743
+   3 |      43 |      743 |     5743
+   3 |      43 |      743 |     6743
+   3 |      43 |      743 |     7743
+   3 |      43 |      743 |     8743
+   3 |      43 |      743 |     9743
+   3 |      43 |      443 |     9443
+   3 |      43 |      443 |     8443
+   3 |      43 |      443 |     7443
+   3 |      43 |      443 |     6443
+   3 |      43 |      443 |     5443
+   3 |      43 |      443 |     4443
+   3 |      43 |      443 |     3443
+   3 |      43 |      843 |     3843
+   3 |      43 |      843 |     4843
+   3 |      43 |      843 |     5843
+   3 |      43 |      843 |     6843
+   3 |      43 |      843 |     7843
+   3 |      43 |      843 |     8843
+   3 |      43 |      843 |     9843
+   3 |      43 |      343 |     9343
+   3 |      43 |      343 |     8343
+   3 |      43 |      343 |     7343
+   3 |      43 |      343 |     6343
+   3 |      43 |      343 |     5343
+   3 |      43 |      343 |     4343
+   3 |      43 |      343 |     3343
+   3 |      43 |      943 |     3943
+   3 |      43 |      943 |     4943
+   3 |      43 |      943 |     5943
+   3 |      43 |      943 |     6943
+   3 |      43 |      943 |     7943
+   3 |      43 |      943 |     8943
+   3 |      43 |      943 |     9943
+   3 |      43 |      243 |     9243
+   3 |      43 |      243 |     8243
+   3 |      43 |      243 |     7243
+   3 |      43 |      243 |     6243
+   3 |      43 |      243 |     5243
+   3 |      43 |      243 |     4243
+   3 |      43 |      243 |     3243
+   3 |      43 |      143 |     9143
+   3 |      43 |      143 |     8143
+   3 |      43 |      143 |     7143
+   3 |      43 |      143 |     6143
+   3 |      43 |      143 |     5143
+   3 |      43 |      143 |     4143
+   3 |      43 |      143 |     3143
+   3 |      43 |       43 |     9043
+   3 |      43 |       43 |     8043
+   3 |      43 |       43 |     7043
+   3 |      43 |       43 |     6043
+   3 |      43 |       43 |     5043
+   3 |      43 |       43 |     4043
+   3 |      43 |       43 |     3043
+(70 rows)
+
+-- Ordering by distance to 4th column
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = 3) AND (hundred = 43) AND (thousand = 643))
+   Order By: (tenthous <-> 4000)
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      43 |      643 |     3643
+   3 |      43 |      643 |     4643
+   3 |      43 |      643 |     2643
+   3 |      43 |      643 |     5643
+   3 |      43 |      643 |     1643
+   3 |      43 |      643 |     6643
+   3 |      43 |      643 |      643
+   3 |      43 |      643 |     7643
+   3 |      43 |      643 |     8643
+   3 |      43 |      643 |     9643
+(10 rows)
+
+-- Not supported by tenk1_knn_idx (not all previous columns are eq-restricted)
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: (thousand = 643)
+   Order By: (tenthous <-> 4000)
+   Filter: (hundred = 43)
+(4 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY tenthous <-> 4000;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Sort
+   Sort Key: ((tenthous <-> 4000))
+   ->  Index Only Scan using tenk1_knn_idx on tenk1
+         Index Cond: ((ten = 3) AND (hundred = 43))
+(4 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND thousand = 643 ORDER BY tenthous <-> 4000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: (thousand = 643)
+   Order By: (tenthous <-> 4000)
+   Filter: (ten = 3)
+(4 rows)
+
+DROP INDEX tenk1_knn_idx;
+RESET enable_sort;
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+        1 |        1
+        1 |     1001
+        1 |     2001
+        1 |     3001
+        1 |     4001
+        1 |     5001
+        1 |     6001
+        1 |     7001
+        1 |     8001
+        1 |     9001
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+        0 |        0
+        0 |     1000
+        0 |     2000
+        0 |     3000
+        0 |     4000
+        0 |     5000
+        0 |     6000
+        0 |     7000
+        0 |     8000
+        0 |     9000
+          |        3
+          |        2
+          |        1
+(33 rows)
+
+DROP INDEX tenk3_idx;
+DROP TABLE tenk3;
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |     ?column?      
+--------------------------+-------------------
+ Wed May 03 00:00:00 2017 | @ 2 days
+ Wed May 03 01:00:00 2017 | @ 2 days 1 hour
+ Wed May 03 02:00:00 2017 | @ 2 days 2 hours
+ Wed May 03 03:00:00 2017 | @ 2 days 3 hours
+ Wed May 03 04:00:00 2017 | @ 2 days 4 hours
+ Wed May 03 05:00:00 2017 | @ 2 days 5 hours
+ Wed May 03 06:00:00 2017 | @ 2 days 6 hours
+ Wed May 03 07:00:00 2017 | @ 2 days 7 hours
+ Wed May 03 08:00:00 2017 | @ 2 days 8 hours
+ Wed May 03 09:00:00 2017 | @ 2 days 9 hours
+ Wed May 03 10:00:00 2017 | @ 2 days 10 hours
+ Wed May 03 11:00:00 2017 | @ 2 days 11 hours
+ Wed May 03 12:00:00 2017 | @ 2 days 12 hours
+ Wed May 03 13:00:00 2017 | @ 2 days 13 hours
+ Wed May 03 14:00:00 2017 | @ 2 days 14 hours
+ Wed May 03 15:00:00 2017 | @ 2 days 15 hours
+ Wed May 03 16:00:00 2017 | @ 2 days 16 hours
+ Wed May 03 17:00:00 2017 | @ 2 days 17 hours
+ Wed May 03 18:00:00 2017 | @ 2 days 18 hours
+ Wed May 03 19:00:00 2017 | @ 2 days 19 hours
+(20 rows)
+
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |  ?column?  
+--------------------------+------------
+ Mon Jan 01 00:00:00 2018 | @ 0
+ Mon Jan 01 01:00:00 2018 | @ 1 hour
+ Sun Dec 31 23:00:00 2017 | @ 1 hour
+ Mon Jan 01 02:00:00 2018 | @ 2 hours
+ Sun Dec 31 22:00:00 2017 | @ 2 hours
+ Mon Jan 01 03:00:00 2018 | @ 3 hours
+ Sun Dec 31 21:00:00 2017 | @ 3 hours
+ Mon Jan 01 04:00:00 2018 | @ 4 hours
+ Sun Dec 31 20:00:00 2017 | @ 4 hours
+ Mon Jan 01 05:00:00 2018 | @ 5 hours
+ Sun Dec 31 19:00:00 2017 | @ 5 hours
+ Mon Jan 01 06:00:00 2018 | @ 6 hours
+ Sun Dec 31 18:00:00 2017 | @ 6 hours
+ Mon Jan 01 07:00:00 2018 | @ 7 hours
+ Sun Dec 31 17:00:00 2017 | @ 7 hours
+ Mon Jan 01 08:00:00 2018 | @ 8 hours
+ Sun Dec 31 16:00:00 2017 | @ 8 hours
+ Mon Jan 01 09:00:00 2018 | @ 9 hours
+ Sun Dec 31 15:00:00 2017 | @ 9 hours
+ Mon Jan 01 10:00:00 2018 | @ 10 hours
+(20 rows)
+
+DROP TABLE knn_btree_ts;
+RESET enable_bitmapscan;
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 2b087be..5e3bffe 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -129,3 +129,307 @@ create index btree_idx_err on btree_test(a) with (vacuum_cleanup_index_scale_fac
 -- Simple ALTER INDEX
 alter index btree_idx1 set (vacuum_cleanup_index_scale_factor = 70.0);
 select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass;
+
+---
+--- Test B-tree distance ordering
+---
+
+SET enable_bitmapscan = OFF;
+
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+-- test parallel KNN scan
+
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+
+RESET enable_indexscan;
+
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, 1000000) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+EXPLAIN (COSTS OFF)
+SELECT i FROM bt_knn_test WHERE i > 8000000;
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> 4000003) AS n, i * 10 AS i
+	FROM generate_series(1, 1000000) i;
+
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+DROP TABLE bt_knn_test;
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, 100000) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+
+ROLLBACK;
+
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+
+
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+DROP INDEX tenk3_idx;
+
+-- Test order by distance ordering on non-first column
+SET enable_sort = OFF;
+
+-- Ranges are not supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand > 120
+ORDER BY tenthous <-> 3500;
+
+-- Equality restriction on the first column is supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand = 120
+ORDER BY tenthous <-> 3500;
+
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand = 120
+ORDER BY tenthous <-> 3500;
+
+-- IN restriction on the first column is not supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY tenthous <-> 3500;
+
+-- Test kNN search using 4-column index
+CREATE INDEX tenk1_knn_idx ON tenk1(ten, hundred, thousand, tenthous);
+
+-- Ordering by distance to 3rd column
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY thousand <-> 600;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY thousand <-> 600;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND tenthous > 3000 ORDER BY thousand <-> 600;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND tenthous > 3000 ORDER BY thousand <-> 600;
+
+-- Ordering by distance to 4th column
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+
+-- Not supported by tenk1_knn_idx (not all previous columns are eq-restricted)
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY tenthous <-> 4000;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND thousand = 643 ORDER BY tenthous <-> 4000;
+
+DROP INDEX tenk1_knn_idx;
+
+RESET enable_sort;
+
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+DROP INDEX tenk3_idx;
+
+DROP TABLE tenk3;
+
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+
+DROP TABLE knn_btree_ts;
+
+RESET enable_bitmapscan;
0008-Allow-ammatchorderby-to-return-pathkey-sublists-v06.patchtext/x-patch; name=0008-Allow-ammatchorderby-to-return-pathkey-sublists-v06.patchDownload
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 279d8ba..b88044b 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1014,8 +1014,25 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 								index_clauses,
 								&orderbyclauses,
 								&orderbyclausecols);
+
 		if (orderbyclauses)
-			useful_pathkeys = root->query_pathkeys;
+		{
+			int			norderbys = list_length(orderbyclauses);
+			int			npathkeys = list_length(root->query_pathkeys);
+
+			if (norderbys < npathkeys)
+			{
+				/*
+				 * We do not accept pathkey sublists until we implement
+				 * partial sorting.
+				 */
+				useful_pathkeys = NIL;
+				orderbyclauses = NIL;
+				orderbyclausecols = NIL;
+			}
+			else
+				useful_pathkeys = root->query_pathkeys;
+		}
 		else
 			useful_pathkeys = NIL;
 	}
@@ -3286,8 +3303,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
  * index column numbers (zero based) that each clause would be used with.
  * NIL lists are returned if the ordering is not achievable this way.
  *
- * On success, the result list is ordered by pathkeys, and in fact is
- * one-to-one with the requested pathkeys.
+ * On success, the result list is ordered by pathkeys.
  */
 static void
 match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
@@ -3305,8 +3321,8 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 		ammatchorderby(index, pathkeys, index_clauses,
 					   &orderby_clauses, &orderby_clause_columns))
 	{
-		Assert(list_length(pathkeys) == list_length(orderby_clauses));
-		Assert(list_length(pathkeys) == list_length(orderby_clause_columns));
+		Assert(list_length(orderby_clauses) <= list_length(pathkeys));
+		Assert(list_length(orderby_clauses) == list_length(orderby_clause_columns));
 
 		*orderby_clauses_p = orderby_clauses;	/* success! */
 		*orderby_clause_columns_p = orderby_clause_columns;
0009-Add-support-of-array-ops-to-btree-kNN-v06.patchtext/x-patch; name=0009-Add-support-of-array-ops-to-btree-kNN-v06.patchDownload
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index fb413e7..3085fae 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -509,8 +509,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 
 	if (orderbys && scan->numberOfOrderBys > 0)
 		memmove(scan->orderByData,
-				orderbys,
-				scan->numberOfOrderBys * sizeof(ScanKeyData));
+				&orderbys[scan->numberOfOrderBys - 1],
+				1 /* scan->numberOfOrderBys */ * sizeof(ScanKeyData));
 
 	so->scanDirection = NoMovementScanDirection;
 	so->distanceTypeByVal = true;
@@ -1554,13 +1554,15 @@ static bool
 btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 			   List **orderby_clauses_p, List **orderby_clausecols_p)
 {
-	Expr	   *expr;
+	Expr	   *expr = NULL;
 	ListCell   *lc;
 	int			indexcol;
 	int			num_eq_cols = 0;
+	int			nsaops = 0;
+	int			last_saop_ord_col = -1;
+	bool		saops[INDEX_MAX_KEYS] = {0};
 
-	/* only one ORDER BY clause is supported */
-	if (list_length(pathkeys) != 1)
+	if (list_length(pathkeys) < 1)
 		return false;
 
 	/*
@@ -1584,6 +1586,7 @@ btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 			Expr	   *clause = rinfo->clause;
 			Oid			opno;
 			StrategyNumber strat;
+			bool		is_saop;
 
 			if (!clause)
 				continue;
@@ -1593,6 +1596,18 @@ btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 				OpExpr	   *opexpr = (OpExpr *) clause;
 
 				opno = opexpr->opno;
+				is_saop = false;
+			}
+			else if (IsA(clause, ScalarArrayOpExpr))
+			{
+				ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+
+				/* We only accept ANY clauses, not ALL */
+				if (!saop->useOr)
+					continue;
+
+				opno = saop->opno;
+				is_saop = true;
 			}
 			else
 			{
@@ -1603,19 +1618,67 @@ btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 			/* Check if the operator is btree equality operator. */
 			strat = get_op_opfamily_strategy(opno, index->opfamily[indexcol]);
 
-			if (strat == BTEqualStrategyNumber)
-				num_eq_cols = indexcol + 1;
+			if (strat != BTEqualStrategyNumber)
+				continue;
+
+			if (is_saop && indexcol == num_eq_cols)
+			{
+				saops[indexcol] = true;
+				nsaops++;
+			}
+			else if (!is_saop && saops[indexcol])
+			{
+				saops[indexcol] = false;
+				nsaops--;
+			}
+
+			num_eq_cols = indexcol + 1;
 		}
 	}
 
-	/*
-	 * If there are no equality columns try to match only the first column,
-	 * otherwise try all columns.
-	 */
-	indexcol = num_eq_cols ? -1 : 0;
+	foreach(lc, pathkeys)
+	{
+		PathKey    *pathkey = lfirst_node(PathKey, lc);
+
+		/*
+		 * If there are no equality columns try to match only the first column,
+		 * otherwise try all columns.
+		 */
+		indexcol = num_eq_cols ? -1 : 0;
+
+		if ((expr = match_orderbyop_pathkey(index, pathkey, &indexcol)))
+			break;	/* found order-by-operator pathkey */
+
+		if (!num_eq_cols)
+			return false;	/* first pathkey is not order-by-operator */
 
-	expr = match_orderbyop_pathkey(index, castNode(PathKey, linitial(pathkeys)),
-								   &indexcol);
+		indexcol = -1;
+
+		if (!(expr = match_pathkey_to_indexcol(index, pathkey, &indexcol)))
+			return false;
+
+		if (indexcol >= num_eq_cols)
+			return false;
+
+		if (saops[indexcol])
+		{
+			saops[indexcol] = false;
+			nsaops--;
+
+			/*
+			 * ORDER BY column numbers for array ops should go in
+			 * non-decreasing order.
+			 */
+			if (indexcol < last_saop_ord_col)
+				return false;
+
+			last_saop_ord_col = indexcol;
+		}
+		/* else: order of equality-restricted columns is arbitrary */
+
+		*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+		*orderby_clausecols_p = lappend_int(*orderby_clausecols_p, -indexcol - 1);
+	}
 
 	if (!expr)
 		return false;
@@ -1627,6 +1690,19 @@ btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 	if (indexcol > num_eq_cols)
 		return false;
 
+	if (nsaops)
+	{
+		int			i;
+
+		/*
+		 * Check that all preceding array-op columns are included into
+		 * ORDER BY clause.
+		 */
+		for (i = 0; i < indexcol; i++)
+			if (saops[i])
+				return false;
+	}
+
 	/* Return first ORDER BY clause's expression and column. */
 	*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
 	*orderby_clausecols_p = lappend_int(*orderby_clausecols_p, indexcol);
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 5b9294b..e980351 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -905,10 +905,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	{
 		ScanKey		ord = scan->orderByData;
 
-		if (scan->numberOfOrderBys > 1)
-			/* it should not happen, see btmatchorderby() */
-			elog(ERROR, "only one btree ordering operator is supported");
-
 		Assert(ord->sk_strategy == BtreeKNNSearchStrategyNumber);
 
 		/* use bidirectional kNN scan by default */
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 805abd2..034e3b8 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -1644,6 +1644,27 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
 								   InvalidOid,	/* no reg proc for this */
 								   (Datum) 0);	/* constant */
 		}
+		else if (IsA(clause, Var))
+		{
+			/* indexkey IS NULL or indexkey IS NOT NULL */
+			Var		   *var = (Var *) clause;
+
+			Assert(isorderby);
+
+			if (var->varno != INDEX_VAR)
+				elog(ERROR, "Var indexqual has wrong key");
+
+			varattno = var->varattno;
+
+			ScanKeyEntryInitialize(this_scan_key,
+								   SK_ORDER_BY | SK_SEARCHNOTNULL,
+								   varattno,	/* attribute number to scan */
+								   InvalidStrategy, /* no strategy */
+								   InvalidOid,	/* no strategy subtype */
+								   var->varcollid,	/* collation FIXME */
+								   InvalidOid,	/* no reg proc for this */
+								   (Datum) 0);	/* constant */
+		}
 		else
 			elog(ERROR, "unsupported indexqual type: %d",
 				 (int) nodeTag(clause));
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index b88044b..a1a36ad 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -2945,6 +2945,62 @@ match_rowcompare_to_indexcol(RestrictInfo *rinfo,
 	return NULL;
 }
 
+/*
+ * Try to match pathkey to the specified index column (*indexcol >= 0) or
+ * to all index columns (*indexcol < 0).
+ */
+Expr *
+match_pathkey_to_indexcol(IndexOptInfo *index, PathKey *pathkey, int *indexcol)
+{
+	ListCell   *lc;
+
+	/* Pathkey must request default sort order for the target opfamily */
+	if (pathkey->pk_strategy != BTLessStrategyNumber ||
+		pathkey->pk_nulls_first)
+		return NULL;
+
+	/* If eclass is volatile, no hope of using an indexscan */
+	if (pathkey->pk_eclass->ec_has_volatile)
+		return NULL;
+
+	/*
+	 * Try to match eclass member expression(s) to index.  Note that child
+	 * EC members are considered, but only when they belong to the target
+	 * relation.  (Unlike regular members, the same expression could be a
+	 * child member of more than one EC.  Therefore, the same index could
+	 * be considered to match more than one pathkey list, which is OK
+	 * here.  See also get_eclass_for_sort_expr.)
+	 */
+	foreach(lc, pathkey->pk_eclass->ec_members)
+	{
+		EquivalenceMember *member = lfirst_node(EquivalenceMember, lc);
+		Expr	   *expr = member->em_expr;
+
+		/* No possibility of match if it references other relations */
+		if (!bms_equal(member->em_relids, index->rel->relids))
+			continue;
+
+		/* If *indexcol is non-negative then try to match only to it */
+		if (*indexcol >= 0)
+		{
+			if (match_index_to_operand((Node *) expr, *indexcol, index))
+				/* don't want to look at remaining members */
+				return expr;
+		}
+		else	/* try to match all columns */
+		{
+			for (*indexcol = 0; *indexcol < index->nkeycolumns; ++*indexcol)
+			{
+				if (match_index_to_operand((Node *) expr, *indexcol, index))
+					/* don't want to look at remaining members */
+					return expr;
+			}
+		}
+	}
+
+	return NULL;
+}
+
 /****************************************************************************
  *				----  ROUTINES TO CHECK ORDERING OPERATORS	----
  ****************************************************************************/
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 236f506..307af39 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4555,7 +4555,11 @@ fix_indexqual_clause(PlannerInfo *root, IndexOptInfo *index, int indexcol,
 	 */
 	clause = replace_nestloop_params(root, clause);
 
-	if (IsA(clause, OpExpr))
+	if (indexcol < 0)
+	{
+		clause = fix_indexqual_operand(clause, index, -indexcol - 1);
+	}
+	else if (IsA(clause, OpExpr))
 	{
 		OpExpr	   *op = (OpExpr *) clause;
 
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 2e3fab6..2076405 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5284,10 +5284,12 @@ get_quals_from_indexclauses(List *indexclauses)
  * index key expression is on the left side of binary clauses.
  */
 Cost
-index_other_operands_eval_cost(PlannerInfo *root, List *indexquals)
+index_other_operands_eval_cost(PlannerInfo *root, List *indexquals,
+							   List *indexcolnos)
 {
 	Cost		qual_arg_cost = 0;
 	ListCell   *lc;
+	ListCell   *indexcolno_lc = indexcolnos ? list_head(indexcolnos) : NULL;
 
 	foreach(lc, indexquals)
 	{
@@ -5302,6 +5304,20 @@ index_other_operands_eval_cost(PlannerInfo *root, List *indexquals)
 		if (IsA(clause, RestrictInfo))
 			clause = ((RestrictInfo *) clause)->clause;
 
+		if (indexcolnos)
+		{
+			int			indexcol = lfirst_int(indexcolno_lc);
+
+			if (indexcol < 0)
+			{
+				/* FIXME */
+				qual_arg_cost += index_qual_cost.startup + index_qual_cost.per_tuple;
+				continue;
+			}
+
+			indexcolno_lc = lnext(indexcolno_lc);
+		}
+
 		if (IsA(clause, OpExpr))
 		{
 			OpExpr	   *op = (OpExpr *) clause;
@@ -5331,7 +5347,8 @@ index_other_operands_eval_cost(PlannerInfo *root, List *indexquals)
 			other_operand = NULL;	/* keep compiler quiet */
 		}
 
-		cost_qual_eval_node(&index_qual_cost, other_operand, root);
+		if (other_operand)
+			cost_qual_eval_node(&index_qual_cost, other_operand, root);
 		qual_arg_cost += index_qual_cost.startup + index_qual_cost.per_tuple;
 	}
 	return qual_arg_cost;
@@ -5346,6 +5363,7 @@ genericcostestimate(PlannerInfo *root,
 	IndexOptInfo *index = path->indexinfo;
 	List	   *indexQuals = get_quals_from_indexclauses(path->indexclauses);
 	List	   *indexOrderBys = path->indexorderbys;
+	List	   *indexOrderByCols = path->indexorderbycols;
 	Cost		indexStartupCost;
 	Cost		indexTotalCost;
 	Selectivity indexSelectivity;
@@ -5509,8 +5527,8 @@ genericcostestimate(PlannerInfo *root,
 	 * Detecting that that might be needed seems more expensive than it's
 	 * worth, though, considering all the other inaccuracies here ...
 	 */
-	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals) +
-		index_other_operands_eval_cost(root, indexOrderBys);
+	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals, NIL) +
+		index_other_operands_eval_cost(root, indexOrderBys, indexOrderByCols);
 	qual_op_cost = cpu_operator_cost *
 		(list_length(indexQuals) + list_length(indexOrderBys));
 
@@ -6629,7 +6647,7 @@ gincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	 * Add on index qual eval costs, much as in genericcostestimate.  But we
 	 * can disregard indexorderbys, since GIN doesn't support those.
 	 */
-	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals);
+	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals, NIL);
 	qual_op_cost = cpu_operator_cost * list_length(indexQuals);
 
 	*indexStartupCost += qual_arg_cost;
@@ -6809,7 +6827,7 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	 * the index costs.  We can disregard indexorderbys, since BRIN doesn't
 	 * support those.
 	 */
-	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals);
+	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals, NIL);
 
 	/*
 	 * Compute the startup cost as the cost to read the whole revmap
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index e9f4f75..6428932 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -79,6 +79,8 @@ extern bool indexcol_is_bool_constant_for_query(IndexOptInfo *index,
 extern bool match_index_to_operand(Node *operand, int indexcol,
 					   IndexOptInfo *index);
 extern void check_index_predicates(PlannerInfo *root, RelOptInfo *rel);
+extern Expr *match_pathkey_to_indexcol(IndexOptInfo *index, PathKey *pathkey,
+									 int *indexcol_p);
 extern Expr *match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey,
 						int *indexcol_p);
 extern bool match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 0ce2175..636e280 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -189,7 +189,7 @@ extern void estimate_hash_bucket_stats(PlannerInfo *root,
 
 extern List *get_quals_from_indexclauses(List *indexclauses);
 extern Cost index_other_operands_eval_cost(PlannerInfo *root,
-							   List *indexquals);
+							   List *indexquals, List *indexcolnos);
 extern List *add_predicate_to_index_quals(IndexOptInfo *index,
 							 List *indexQuals);
 extern void genericcostestimate(PlannerInfo *root, IndexPath *path,
diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index 0cefbcc..724890b 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -876,11 +876,9 @@ ORDER BY tenthous <-> 3500;
       120 |     9120
 (10 rows)
 
--- IN restriction on the first column is not supported
+-- IN restriction on the first column is not supported without 'ORDER BY col1 ASC'
 EXPLAIN (COSTS OFF)
-SELECT thousand, tenthous
-FROM tenk1
-WHERE thousand IN (5, 120, 3456, 23)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
 ORDER BY tenthous <-> 3500;
                              QUERY PLAN                              
 ---------------------------------------------------------------------
@@ -890,6 +888,63 @@ ORDER BY tenthous <-> 3500;
          Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
 (4 rows)
 
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand DESC, tenthous <-> 3500;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Sort
+   Sort Key: thousand DESC, ((tenthous <-> 3500))
+   ->  Index Only Scan using tenk1_thous_tenthous on tenk1
+         Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
+(4 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand, tenthous <-> 3500;
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Index Only Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
+   Order By: (thousand AND (tenthous <-> 3500))
+(3 rows)
+
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand, tenthous <-> 3500;
+ thousand | tenthous 
+----------+----------
+        5 |     3005
+        5 |     4005
+        5 |     2005
+        5 |     5005
+        5 |     1005
+        5 |     6005
+        5 |        5
+        5 |     7005
+        5 |     8005
+        5 |     9005
+       23 |     3023
+       23 |     4023
+       23 |     2023
+       23 |     5023
+       23 |     1023
+       23 |     6023
+       23 |       23
+       23 |     7023
+       23 |     8023
+       23 |     9023
+      120 |     3120
+      120 |     4120
+      120 |     2120
+      120 |     5120
+      120 |     1120
+      120 |     6120
+      120 |      120
+      120 |     7120
+      120 |     8120
+      120 |     9120
+(30 rows)
+
 -- Test kNN search using 4-column index
 CREATE INDEX tenk1_knn_idx ON tenk1(ten, hundred, thousand, tenthous);
 -- Ordering by distance to 3rd column
@@ -1122,38 +1177,132 @@ WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
    3 |      43 |      643 |     9643
 (10 rows)
 
--- Not supported by tenk1_knn_idx (not all previous columns are eq-restricted)
+-- Array ops on non-first columns are not supported
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
-                   QUERY PLAN                   
-------------------------------------------------
- Index Scan using tenk1_thous_tenthous on tenk1
-   Index Cond: (thousand = 643)
-   Order By: (tenthous <-> 4000)
-   Filter: (hundred = 43)
+WHERE ten IN (3, 4, 5) AND hundred IN (23, 24, 35) AND thousand IN (843, 132, 623, 243)
+ORDER BY tenthous <-> 6000;
+                                                                          QUERY PLAN                                                                          
+--------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: ((tenthous <-> 6000))
+   ->  Index Only Scan using tenk1_knn_idx on tenk1
+         Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = ANY ('{23,24,35}'::integer[])) AND (thousand = ANY ('{843,132,623,243}'::integer[])))
 (4 rows)
 
+-- All array columns should be included into ORDER BY
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE ten = 3 AND hundred = 43 ORDER BY tenthous <-> 4000;
-                     QUERY PLAN                     
-----------------------------------------------------
- Sort
-   Sort Key: ((tenthous <-> 4000))
-   ->  Index Only Scan using tenk1_knn_idx on tenk1
-         Index Cond: ((ten = 3) AND (hundred = 43))
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY tenthous <-> 6000;
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: ((thousand = 123) AND (tenthous > 2000))
+   Order By: (tenthous <-> 6000)
+   Filter: ((hundred = 23) AND (ten = ANY ('{3,4,5}'::integer[])))
 (4 rows)
 
+-- Eq-restricted columns can be omitted from ORDER BY
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE ten = 3 AND thousand = 643 ORDER BY tenthous <-> 4000;
-                   QUERY PLAN                   
-------------------------------------------------
- Index Scan using tenk1_thous_tenthous on tenk1
-   Index Cond: (thousand = 643)
-   Order By: (tenthous <-> 4000)
-   Filter: (ten = 3)
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, tenthous <-> 6000;
+                                                    QUERY PLAN                                                    
+------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23) AND (thousand = 123) AND (tenthous > 2000))
+   Order By: (ten AND (tenthous <-> 6000))
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, tenthous <-> 6000;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      23 |      123 |     6123
+   3 |      23 |      123 |     5123
+   3 |      23 |      123 |     7123
+   3 |      23 |      123 |     4123
+   3 |      23 |      123 |     8123
+   3 |      23 |      123 |     3123
+   3 |      23 |      123 |     9123
+   3 |      23 |      123 |     2123
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, tenthous <-> 6000;
+                                                    QUERY PLAN                                                    
+------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23) AND (thousand = 123) AND (tenthous > 2000))
+   Order By: (ten AND (tenthous <-> 6000))
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, tenthous <-> 6000;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      23 |      123 |     6123
+   3 |      23 |      123 |     5123
+   3 |      23 |      123 |     7123
+   3 |      23 |      123 |     4123
+   3 |      23 |      123 |     8123
+   3 |      23 |      123 |     3123
+   3 |      23 |      123 |     9123
+   3 |      23 |      123 |     2123
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4 ,5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, thousand, tenthous <-> 6000;
+                                                    QUERY PLAN                                                    
+------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23) AND (thousand = 123) AND (tenthous > 2000))
+   Order By: (ten AND (tenthous <-> 6000))
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, thousand, tenthous <-> 6000;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      23 |      123 |     6123
+   3 |      23 |      123 |     5123
+   3 |      23 |      123 |     7123
+   3 |      23 |      123 |     4123
+   3 |      23 |      123 |     8123
+   3 |      23 |      123 |     3123
+   3 |      23 |      123 |     9123
+   3 |      23 |      123 |     2123
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, thousand, tenthous <-> 6000;
+                                                    QUERY PLAN                                                    
+------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23) AND (thousand = 123) AND (tenthous > 2000))
+   Order By: (ten AND (tenthous <-> 6000))
+(3 rows)
+
+-- Extra ORDER BY columns after order-by-op are not supported
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 ORDER BY ten, thousand <-> 6000, tenthous;
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Sort
+   Sort Key: ten, ((thousand <-> 6000)), tenthous
+   ->  Index Only Scan using tenk1_knn_idx on tenk1
+         Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23))
 (4 rows)
 
 DROP INDEX tenk1_knn_idx;
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 5e3bffe..69c890e 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -345,13 +345,22 @@ FROM tenk1
 WHERE thousand = 120
 ORDER BY tenthous <-> 3500;
 
--- IN restriction on the first column is not supported
+-- IN restriction on the first column is not supported without 'ORDER BY col1 ASC'
 EXPLAIN (COSTS OFF)
-SELECT thousand, tenthous
-FROM tenk1
-WHERE thousand IN (5, 120, 3456, 23)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
 ORDER BY tenthous <-> 3500;
 
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand DESC, tenthous <-> 3500;
+
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand, tenthous <-> 3500;
+
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand, tenthous <-> 3500;
+
 -- Test kNN search using 4-column index
 CREATE INDEX tenk1_knn_idx ON tenk1(ten, hundred, thousand, tenthous);
 
@@ -378,18 +387,55 @@ WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
 SELECT ten, hundred, thousand, tenthous FROM tenk1
 WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
 
--- Not supported by tenk1_knn_idx (not all previous columns are eq-restricted)
+-- Array ops on non-first columns are not supported
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred IN (23, 24, 35) AND thousand IN (843, 132, 623, 243)
+ORDER BY tenthous <-> 6000;
+
+-- All array columns should be included into ORDER BY
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY tenthous <-> 6000;
+
+-- Eq-restricted columns can be omitted from ORDER BY
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, tenthous <-> 6000;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, tenthous <-> 6000;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, tenthous <-> 6000;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, tenthous <-> 6000;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4 ,5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, thousand, tenthous <-> 6000;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, thousand, tenthous <-> 6000;
 
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE ten = 3 AND hundred = 43 ORDER BY tenthous <-> 4000;
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, thousand, tenthous <-> 6000;
 
+-- Extra ORDER BY columns after order-by-op are not supported
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE ten = 3 AND thousand = 643 ORDER BY tenthous <-> 4000;
+WHERE ten IN (3, 4, 5) AND hundred = 23 ORDER BY ten, thousand <-> 6000, tenthous;
 
 DROP INDEX tenk1_knn_idx;
 
#20Thomas Munro
thomas.munro@gmail.com
In reply to: Nikita Glukhov (#19)
Re: [PATCH] kNN for btree

On Wed, Feb 20, 2019 at 2:18 PM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

On 04.02.2019 8:35, Michael Paquier wrote:
This patch set needs a rebase because of conflicts caused by the
recent patches for pluggable storage.

Hi Nikita,
From the department of trivialities: according to cfbot the
documentation doesn't build. Looks like you have some cases of </>,
but these days you have to write out </quote> (or whatever) in full.

--
Thomas Munro
https://enterprisedb.com

#21Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Thomas Munro (#20)
9 attachment(s)
Re: [PATCH] kNN for btree

On 20.02.2019 7:35, Thomas Munro wrote:

On Wed, Feb 20, 2019 at 2:18 PM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

On 04.02.2019 8:35, Michael Paquier wrote:
This patch set needs a rebase because of conflicts caused by the
recent patches for pluggable storage.

Hi Nikita,
From the department of trivialities: according to cfbot the
documentation doesn't build. Looks like you have some cases of </>,
but these days you have to write out </quote> (or whatever) in full.

Sorry, tags in docs were fixed. Also I fixed list of data types with built-in
distance operators and list of assumptions for btree distance operators.

Attached 7th version the patches (only documentation was changed).

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0003-Extract-structure-BTScanState-v07.patchtext/x-patch; name=0003-Extract-structure-BTScanState-v07.patchDownload
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index c56ee5e..bf6a6c6 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -214,6 +214,7 @@ bool
 btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 	bool		res;
 
 	/* btree indexes are never lossy */
@@ -224,7 +225,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(so->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
@@ -241,7 +242,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(so->currPos))
+		if (!BTScanPosIsValid(state->currPos))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -259,11 +260,11 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 				 * trying to optimize that, so we don't detect it, but instead
 				 * just forget any excess entries.
 				 */
-				if (so->killedItems == NULL)
-					so->killedItems = (int *)
+				if (state->killedItems == NULL)
+					state->killedItems = (int *)
 						palloc(MaxIndexTuplesPerPage * sizeof(int));
-				if (so->numKilled < MaxIndexTuplesPerPage)
-					so->killedItems[so->numKilled++] = so->currPos.itemIndex;
+				if (state->numKilled < MaxIndexTuplesPerPage)
+					state->killedItems[so->state.numKilled++] = state->currPos.itemIndex;
 			}
 
 			/*
@@ -288,6 +289,7 @@ int64
 btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	int64		ntids = 0;
 	ItemPointer heapTid;
 
@@ -320,7 +322,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				 * Advance to next tuple within page.  This is the same as the
 				 * easy case in _bt_next().
 				 */
-				if (++so->currPos.itemIndex > so->currPos.lastItem)
+				if (++currPos->itemIndex > currPos->lastItem)
 				{
 					/* let _bt_next do the heavy lifting */
 					if (!_bt_next(scan, ForwardScanDirection))
@@ -328,7 +330,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				}
 
 				/* Save tuple ID, and continue scanning */
-				heapTid = &so->currPos.items[so->currPos.itemIndex].heapTid;
+				heapTid = &currPos->items[currPos->itemIndex].heapTid;
 				tbm_add_tuples(tbm, heapTid, 1, false);
 				ntids++;
 			}
@@ -356,8 +358,8 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 
 	/* allocate private workspace */
 	so = (BTScanOpaque) palloc(sizeof(BTScanOpaqueData));
-	BTScanPosInvalidate(so->currPos);
-	BTScanPosInvalidate(so->markPos);
+	BTScanPosInvalidate(so->state.currPos);
+	BTScanPosInvalidate(so->state.markPos);
 	if (scan->numberOfKeys > 0)
 		so->keyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData));
 	else
@@ -368,15 +370,15 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	so->arrayKeys = NULL;
 	so->arrayContext = NULL;
 
-	so->killedItems = NULL;		/* until needed */
-	so->numKilled = 0;
+	so->state.killedItems = NULL;	/* until needed */
+	so->state.numKilled = 0;
 
 	/*
 	 * We don't know yet whether the scan will be index-only, so we do not
 	 * allocate the tuple workspace arrays until btrescan.  However, we set up
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
-	so->currTuples = so->markTuples = NULL;
+	so->state.currTuples = so->state.markTuples = NULL;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -385,6 +387,45 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	return scan;
 }
 
+static void
+_bt_release_current_position(BTScanState state, Relation indexRelation,
+							 bool invalidate)
+{
+	/* we aren't holding any read locks, but gotta drop the pins */
+	if (BTScanPosIsValid(state->currPos))
+	{
+		/* Before leaving current page, deal with any killed items */
+		if (state->numKilled > 0)
+			_bt_killitems(state, indexRelation);
+
+		BTScanPosUnpinIfPinned(state->currPos);
+
+		if (invalidate)
+			BTScanPosInvalidate(state->currPos);
+	}
+}
+
+static void
+_bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
+{
+	/* No need to invalidate positions, if the RAM is about to be freed. */
+	_bt_release_current_position(state, scan->indexRelation, !free);
+
+	state->markItemIndex = -1;
+	BTScanPosUnpinIfPinned(state->markPos);
+
+	if (free)
+	{
+		if (state->killedItems != NULL)
+			pfree(state->killedItems);
+		if (state->currTuples != NULL)
+			pfree(state->currTuples);
+		/* markTuples should not be pfree'd (_bt_allocate_tuple_workspaces) */
+	}
+	else
+		BTScanPosInvalidate(state->markPos);
+}
+
 /*
  *	btrescan() -- rescan an index relation
  */
@@ -393,21 +434,11 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 		 ScanKey orderbys, int norderbys)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-		BTScanPosInvalidate(so->currPos);
-	}
+	_bt_release_scan_state(scan, state, false);
 
-	so->markItemIndex = -1;
 	so->arrayKeyCount = 0;
-	BTScanPosUnpinIfPinned(so->markPos);
-	BTScanPosInvalidate(so->markPos);
 
 	/*
 	 * Allocate tuple workspace arrays, if needed for an index-only scan and
@@ -425,11 +456,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 	 * a SIGSEGV is not possible.  Yeah, this is ugly as sin, but it beats
 	 * adding special-case treatment for name_ops elsewhere.
 	 */
-	if (scan->xs_want_itup && so->currTuples == NULL)
-	{
-		so->currTuples = (char *) palloc(BLCKSZ * 2);
-		so->markTuples = so->currTuples + BLCKSZ;
-	}
+	if (scan->xs_want_itup && state->currTuples == NULL)
+		_bt_allocate_tuple_workspaces(state);
 
 	/*
 	 * Reset the scan keys. Note that keys ordering stuff moved to _bt_first.
@@ -453,19 +481,7 @@ btendscan(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-	}
-
-	so->markItemIndex = -1;
-	BTScanPosUnpinIfPinned(so->markPos);
-
-	/* No need to invalidate positions, the RAM is about to be freed. */
+	_bt_release_scan_state(scan, &so->state, true);
 
 	/* Release storage */
 	if (so->keyData != NULL)
@@ -473,24 +489,15 @@ btendscan(IndexScanDesc scan)
 	/* so->arrayKeyData and so->arrayKeys are in arrayContext */
 	if (so->arrayContext != NULL)
 		MemoryContextDelete(so->arrayContext);
-	if (so->killedItems != NULL)
-		pfree(so->killedItems);
-	if (so->currTuples != NULL)
-		pfree(so->currTuples);
-	/* so->markTuples should not be pfree'd, see btrescan */
+
 	pfree(so);
 }
 
-/*
- *	btmarkpos() -- save current scan position
- */
-void
-btmarkpos(IndexScanDesc scan)
+static void
+_bt_mark_current_position(BTScanState state)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-
 	/* There may be an old mark with a pin (but no lock). */
-	BTScanPosUnpinIfPinned(so->markPos);
+	BTScanPosUnpinIfPinned(state->markPos);
 
 	/*
 	 * Just record the current itemIndex.  If we later step to next page
@@ -498,32 +505,34 @@ btmarkpos(IndexScanDesc scan)
 	 * the currPos struct in markPos.  If (as often happens) the mark is moved
 	 * before we leave the page, we don't have to do that work.
 	 */
-	if (BTScanPosIsValid(so->currPos))
-		so->markItemIndex = so->currPos.itemIndex;
+	if (BTScanPosIsValid(state->currPos))
+		state->markItemIndex = state->currPos.itemIndex;
 	else
 	{
-		BTScanPosInvalidate(so->markPos);
-		so->markItemIndex = -1;
+		BTScanPosInvalidate(state->markPos);
+		state->markItemIndex = -1;
 	}
-
-	/* Also record the current positions of any array keys */
-	if (so->numArrayKeys)
-		_bt_mark_array_keys(scan);
 }
 
 /*
- *	btrestrpos() -- restore scan to last saved position
+ *	btmarkpos() -- save current scan position
  */
 void
-btrestrpos(IndexScanDesc scan)
+btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* Restore the marked positions of any array keys */
+	_bt_mark_current_position(&so->state);
+
+	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
-		_bt_restore_array_keys(scan);
+		_bt_mark_array_keys(scan);
+}
 
-	if (so->markItemIndex >= 0)
+static void
+_bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
+{
+	if (state->markItemIndex >= 0)
 	{
 		/*
 		 * The scan has never moved to a new page since the last mark.  Just
@@ -532,7 +541,7 @@ btrestrpos(IndexScanDesc scan)
 		 * NB: In this case we can't count on anything in so->markPos to be
 		 * accurate.
 		 */
-		so->currPos.itemIndex = so->markItemIndex;
+		state->currPos.itemIndex = state->markItemIndex;
 	}
 	else
 	{
@@ -542,28 +551,21 @@ btrestrpos(IndexScanDesc scan)
 		 * locks, but if we're still holding the pin for the current position,
 		 * we must drop it.
 		 */
-		if (BTScanPosIsValid(so->currPos))
-		{
-			/* Before leaving current page, deal with any killed items */
-			if (so->numKilled > 0)
-				_bt_killitems(scan);
-			BTScanPosUnpinIfPinned(so->currPos);
-		}
+		_bt_release_current_position(state, scan->indexRelation,
+									 !BTScanPosIsValid(state->markPos));
 
-		if (BTScanPosIsValid(so->markPos))
+		if (BTScanPosIsValid(state->markPos))
 		{
 			/* bump pin on mark buffer for assignment to current buffer */
-			if (BTScanPosIsPinned(so->markPos))
-				IncrBufferRefCount(so->markPos.buf);
-			memcpy(&so->currPos, &so->markPos,
+			if (BTScanPosIsPinned(state->markPos))
+				IncrBufferRefCount(state->markPos.buf);
+			memcpy(&state->currPos, &state->markPos,
 				   offsetof(BTScanPosData, items[1]) +
-				   so->markPos.lastItem * sizeof(BTScanPosItem));
-			if (so->currTuples)
-				memcpy(so->currTuples, so->markTuples,
-					   so->markPos.nextTupleOffset);
+				   state->markPos.lastItem * sizeof(BTScanPosItem));
+			if (state->currTuples)
+				memcpy(state->currTuples, state->markTuples,
+					   state->markPos.nextTupleOffset);
 		}
-		else
-			BTScanPosInvalidate(so->currPos);
 	}
 }
 
@@ -779,9 +781,10 @@ _bt_parallel_advance_array_keys(IndexScanDesc scan)
 }
 
 /*
- * _bt_vacuum_needs_cleanup() -- Checks if index needs cleanup assuming that
- *			btbulkdelete() wasn't called.
- */
+- * _bt_vacuum_needs_cleanup() -- Checks if index needs cleanup assuming that
+- *			btbulkdelete() wasn't called.
++ *	btrestrpos() -- restore scan to last saved position
+  */
 static bool
 _bt_vacuum_needs_cleanup(IndexVacuumInfo *info)
 {
@@ -844,6 +847,21 @@ _bt_vacuum_needs_cleanup(IndexVacuumInfo *info)
 }
 
 /*
+ *	btrestrpos() -- restore scan to last saved position
+ */
+void
+btrestrpos(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
+	/* Restore the marked positions of any array keys */
+	if (so->numArrayKeys)
+		_bt_restore_array_keys(scan);
+
+	_bt_restore_marked_position(scan, &so->state);
+}
+
+/*
  * Bulk deletion of all index entries pointing to a set of heap tuples.
  * The set of target tuples is specified via a callback routine that tells
  * whether any given heap tuple (identified by ItemPointer) is being deleted.
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 9283223..cbd72bd 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -24,18 +24,19 @@
 #include "utils/rel.h"
 
 
-static bool _bt_readpage(IndexScanDesc scan, ScanDirection dir,
+static bool _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 			 OffsetNumber offnum);
-static void _bt_saveitem(BTScanOpaque so, int itemIndex,
+static void _bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup);
-static bool _bt_steppage(IndexScanDesc scan, ScanDirection dir);
-static bool _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir);
+static bool _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir);
+static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
+				 BlockNumber blkno, ScanDirection dir);
 static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
 					  ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf, Snapshot snapshot);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
-static inline void _bt_initialize_more_data(BTScanOpaque so, ScanDirection dir);
+static inline void _bt_initialize_more_data(BTScanState state, ScanDirection dir);
 
 
 /*
@@ -544,6 +545,58 @@ _bt_compare(Relation rel,
 }
 
 /*
+ * _bt_return_current_item() -- Prepare current scan state item for return.
+ *
+ * This function is used only in "return _bt_return_current_item();" statements
+ * and always returns true.
+ */
+static inline bool
+_bt_return_current_item(IndexScanDesc scan, BTScanState state)
+{
+	BTScanPosItem *currItem = &state->currPos.items[state->currPos.itemIndex];
+
+	scan->xs_ctup.t_self = currItem->heapTid;
+
+	if (scan->xs_want_itup)
+		scan->xs_itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+
+	return true;
+}
+
+/*
+ * _bt_load_first_page() -- Load data from the first page of the scan.
+ *
+ * Caller must have pinned and read-locked state->currPos.buf.
+ *
+ * On success exit, state->currPos is updated to contain data from the next
+ * interesting page.  For success on a scan using a non-MVCC snapshot we hold
+ * a pin, but not a read lock, on that page.  If we do not hold the pin, we
+ * set state->currPos.buf to InvalidBuffer.  We return true to indicate success.
+ *
+ * If there are no more matching records in the given direction at all,
+ * we drop all locks and pins, set state->currPos.buf to InvalidBuffer,
+ * and return false.
+ */
+static bool
+_bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+					OffsetNumber offnum)
+{
+	if (!_bt_readpage(scan, state, dir, offnum))
+	{
+		/*
+		 * There's no actually-matching data on this page.  Try to advance to
+		 * the next page.  Return false if there's no matching data at all.
+		 */
+		LockBuffer(state->currPos.buf, BUFFER_LOCK_UNLOCK);
+		return _bt_steppage(scan, state, dir);
+	}
+
+	/* Drop the lock, and maybe the pin, on the current page */
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
+	return true;
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -568,6 +621,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	BTStack		stack;
 	OffsetNumber offnum;
@@ -581,10 +635,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	int			i;
 	bool		status = true;
 	StrategyNumber strat_total;
-	BTScanPosItem *currItem;
 	BlockNumber blkno;
 
-	Assert(!BTScanPosIsValid(so->currPos));
+	Assert(!BTScanPosIsValid(*currPos));
 
 	pgstat_count_index_scan(rel);
 
@@ -1075,7 +1128,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 * their scan
 		 */
 		_bt_parallel_done(scan);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 
 		return false;
 	}
@@ -1083,7 +1136,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		PredicateLockPage(rel, BufferGetBlockNumber(buf),
 						  scan->xs_snapshot);
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
 	/* position to the precise item on the page */
 	offnum = _bt_binsrch(rel, buf, keysCount, scankeys, nextkey);
@@ -1110,36 +1163,36 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		offnum = OffsetNumberPrev(offnum);
 
 	/* remember which buffer we have pinned, if any */
-	Assert(!BTScanPosIsValid(so->currPos));
-	so->currPos.buf = buf;
+	Assert(!BTScanPosIsValid(*currPos));
+	currPos->buf = buf;
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, offnum))
+	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
+		return false;
+
+readcomplete:
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
+}
+
+/*
+ *	Advance to next tuple on current page; or if there's no more,
+ *	try to step to the next page with data.
+ */
+static bool
+_bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
+{
+	if (ScanDirectionIsForward(dir))
 	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
+		if (++state->currPos.itemIndex <= state->currPos.lastItem)
+			return true;
 	}
 	else
 	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+		if (--state->currPos.itemIndex >= state->currPos.firstItem)
+			return true;
 	}
 
-readcomplete:
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_steppage(scan, state, dir);
 }
 
 /*
@@ -1160,44 +1213,20 @@ bool
 _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-	BTScanPosItem *currItem;
 
-	/*
-	 * Advance to next tuple on current page; or if there's no more, try to
-	 * step to the next page with data.
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (++so->currPos.itemIndex > so->currPos.lastItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
-	else
-	{
-		if (--so->currPos.itemIndex < so->currPos.firstItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
+	if (!_bt_next_item(scan, &so->state, dir))
+		return false;
 
 	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
  *	_bt_readpage() -- Load data from current index page into so->currPos
  *
- * Caller must have pinned and read-locked so->currPos.buf; the buffer's state
- * is not changed here.  Also, currPos.moreLeft and moreRight must be valid;
- * they are updated as appropriate.  All other fields of so->currPos are
+ * Caller must have pinned and read-locked pos->buf; the buffer's state
+ * is not changed here.  Also, pos->moreLeft and moreRight must be valid;
+ * they are updated as appropriate.  All other fields of pos are
  * initialized from scratch here.
  *
  * We scan the current page starting at offnum and moving in the indicated
@@ -1212,9 +1241,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
  * Returns true if any matching items found on the page, false if none.
  */
 static bool
-_bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
+_bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+			 OffsetNumber offnum)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
@@ -1227,9 +1257,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We must have the buffer pinned and locked, but the usual macro can't be
 	 * used here; this function is what makes it good for currPos.
 	 */
-	Assert(BufferIsValid(so->currPos.buf));
+	Assert(BufferIsValid(pos->buf));
 
-	page = BufferGetPage(so->currPos.buf);
+	page = BufferGetPage(pos->buf);
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 
 	/* allow next page be processed by parallel worker */
@@ -1238,7 +1268,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		if (ScanDirectionIsForward(dir))
 			_bt_parallel_release(scan, opaque->btpo_next);
 		else
-			_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+			_bt_parallel_release(scan, BufferGetBlockNumber(pos->buf));
 	}
 
 	minoff = P_FIRSTDATAKEY(opaque);
@@ -1248,30 +1278,30 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We note the buffer's block number so that we can release the pin later.
 	 * This allows us to re-read the buffer if it is needed again for hinting.
 	 */
-	so->currPos.currPage = BufferGetBlockNumber(so->currPos.buf);
+	pos->currPage = BufferGetBlockNumber(pos->buf);
 
 	/*
 	 * We save the LSN of the page as we read it, so that we know whether it
 	 * safe to apply LP_DEAD hints to the page later.  This allows us to drop
 	 * the pin for MVCC scans, which allows vacuum to avoid blocking.
 	 */
-	so->currPos.lsn = BufferGetLSNAtomic(so->currPos.buf);
+	pos->lsn = BufferGetLSNAtomic(pos->buf);
 
 	/*
 	 * we must save the page's right-link while scanning it; this tells us
 	 * where to step right to after we're done with these items.  There is no
 	 * corresponding need for the left-link, since splits always go right.
 	 */
-	so->currPos.nextPage = opaque->btpo_next;
+	pos->nextPage = opaque->btpo_next;
 
 	/* initialize tuple workspace to empty */
-	so->currPos.nextTupleOffset = 0;
+	pos->nextTupleOffset = 0;
 
 	/*
 	 * Now that the current page has been made consistent, the macro should be
 	 * good.
 	 */
-	Assert(BTScanPosIsPinned(so->currPos));
+	Assert(BTScanPosIsPinned(*pos));
 
 	if (ScanDirectionIsForward(dir))
 	{
@@ -1286,13 +1316,13 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			if (itup != NULL)
 			{
 				/* tuple passes all scan key conditions, so remember it */
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 				itemIndex++;
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreRight = false;
+				pos->moreRight = false;
 				break;
 			}
 
@@ -1300,9 +1330,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex <= MaxIndexTuplesPerPage);
-		so->currPos.firstItem = 0;
-		so->currPos.lastItem = itemIndex - 1;
-		so->currPos.itemIndex = 0;
+		pos->firstItem = 0;
+		pos->lastItem = itemIndex - 1;
+		pos->itemIndex = 0;
 	}
 	else
 	{
@@ -1318,12 +1348,12 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			{
 				/* tuple passes all scan key conditions, so remember it */
 				itemIndex--;
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreLeft = false;
+				pos->moreLeft = false;
 				break;
 			}
 
@@ -1331,30 +1361,31 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex >= 0);
-		so->currPos.firstItem = itemIndex;
-		so->currPos.lastItem = MaxIndexTuplesPerPage - 1;
-		so->currPos.itemIndex = MaxIndexTuplesPerPage - 1;
+		pos->firstItem = itemIndex;
+		pos->lastItem = MaxIndexTuplesPerPage - 1;
+		pos->itemIndex = MaxIndexTuplesPerPage - 1;
 	}
 
-	return (so->currPos.firstItem <= so->currPos.lastItem);
+	return (pos->firstItem <= pos->lastItem);
 }
 
 /* Save an index item into so->currPos.items[itemIndex] */
 static void
-_bt_saveitem(BTScanOpaque so, int itemIndex,
+_bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup)
 {
-	BTScanPosItem *currItem = &so->currPos.items[itemIndex];
+	BTScanPosItem *currItem = &state->currPos.items[itemIndex];
 
 	currItem->heapTid = itup->t_tid;
 	currItem->indexOffset = offnum;
-	if (so->currTuples)
+	if (state->currTuples)
 	{
 		Size		itupsz = IndexTupleSize(itup);
 
-		currItem->tupleOffset = so->currPos.nextTupleOffset;
-		memcpy(so->currTuples + so->currPos.nextTupleOffset, itup, itupsz);
-		so->currPos.nextTupleOffset += MAXALIGN(itupsz);
+		currItem->tupleOffset = state->currPos.nextTupleOffset;
+		memcpy(state->currTuples + state->currPos.nextTupleOffset,
+			   itup, itupsz);
+		state->currPos.nextTupleOffset += MAXALIGN(itupsz);
 	}
 }
 
@@ -1370,35 +1401,36 @@ _bt_saveitem(BTScanOpaque so, int itemIndex,
  * to InvalidBuffer.  We return true to indicate success.
  */
 static bool
-_bt_steppage(IndexScanDesc scan, ScanDirection dir)
+_bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
+	Relation	rel = scan->indexRelation;
 	BlockNumber blkno = InvalidBlockNumber;
 	bool		status = true;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(*currPos));
 
 	/* Before leaving current page, deal with any killed items */
-	if (so->numKilled > 0)
-		_bt_killitems(scan);
+	if (state->numKilled > 0)
+		_bt_killitems(state, rel);
 
 	/*
 	 * Before we modify currPos, make a copy of the page data if there was a
 	 * mark position that needs it.
 	 */
-	if (so->markItemIndex >= 0)
+	if (state->markItemIndex >= 0)
 	{
 		/* bump pin on current buffer for assignment to mark buffer */
-		if (BTScanPosIsPinned(so->currPos))
-			IncrBufferRefCount(so->currPos.buf);
-		memcpy(&so->markPos, &so->currPos,
+		if (BTScanPosIsPinned(*currPos))
+			IncrBufferRefCount(currPos->buf);
+		memcpy(&state->markPos, currPos,
 			   offsetof(BTScanPosData, items[1]) +
-			   so->currPos.lastItem * sizeof(BTScanPosItem));
-		if (so->markTuples)
-			memcpy(so->markTuples, so->currTuples,
-				   so->currPos.nextTupleOffset);
-		so->markPos.itemIndex = so->markItemIndex;
-		so->markItemIndex = -1;
+			   currPos->lastItem * sizeof(BTScanPosItem));
+		if (state->markTuples)
+			memcpy(state->markTuples, state->currTuples,
+				   currPos->nextTupleOffset);
+		state->markPos.itemIndex = state->markItemIndex;
+		state->markItemIndex = -1;
 	}
 
 	if (ScanDirectionIsForward(dir))
@@ -1414,27 +1446,27 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
-				BTScanPosUnpinIfPinned(so->currPos);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosUnpinIfPinned(*currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so use the previously-saved nextPage link. */
-			blkno = so->currPos.nextPage;
+			blkno = currPos->nextPage;
 		}
 
 		/* Remember we left a page with data */
-		so->currPos.moreLeft = true;
+		currPos->moreLeft = true;
 
 		/* release the previous buffer, if pinned */
-		BTScanPosUnpinIfPinned(so->currPos);
+		BTScanPosUnpinIfPinned(*currPos);
 	}
 	else
 	{
 		/* Remember we left a page with data */
-		so->currPos.moreRight = true;
+		currPos->moreRight = true;
 
 		if (scan->parallel_scan != NULL)
 		{
@@ -1443,25 +1475,25 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			 * ended already, bail out.
 			 */
 			status = _bt_parallel_seize(scan, &blkno);
-			BTScanPosUnpinIfPinned(so->currPos);
+			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so just use our own notion of the current page */
-			blkno = so->currPos.currPage;
+			blkno = currPos->currPage;
 		}
 	}
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, currPos);
 
 	return true;
 }
@@ -1477,9 +1509,10 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
  * locks and pins, set so->currPos.buf to InvalidBuffer, and return false.
  */
 static bool
-_bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+				 ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
 	Relation	rel;
 	Page		page;
 	BTPageOpaque opaque;
@@ -1495,17 +1528,17 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * if we're at end of scan, give up and mark parallel scan as
 			 * done, so that all the workers can finish their scan
 			 */
-			if (blkno == P_NONE || !so->currPos.moreRight)
+			if (blkno == P_NONE || !currPos->moreRight)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 			/* check for interrupts while we're not holding any buffer lock */
 			CHECK_FOR_INTERRUPTS();
 			/* step right one page */
-			so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
-			page = BufferGetPage(so->currPos.buf);
+			currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			/* check for deleted page */
@@ -1514,7 +1547,7 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 				PredicateLockPage(rel, blkno, scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreRight if we can stop */
-				if (_bt_readpage(scan, dir, P_FIRSTDATAKEY(opaque)))
+				if (_bt_readpage(scan, state, dir, P_FIRSTDATAKEY(opaque)))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
@@ -1526,18 +1559,18 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
 			}
 			else
 			{
 				blkno = opaque->btpo_next;
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 			}
 		}
 	}
@@ -1547,10 +1580,10 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * Should only happen in parallel cases, when some other backend
 		 * advanced the scan.
 		 */
-		if (so->currPos.currPage != blkno)
+		if (currPos->currPage != blkno)
 		{
-			BTScanPosUnpinIfPinned(so->currPos);
-			so->currPos.currPage = blkno;
+			BTScanPosUnpinIfPinned(*currPos);
+			currPos->currPage = blkno;
 		}
 
 		/*
@@ -1575,31 +1608,30 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * is MVCC the page cannot move past the half-dead state to fully
 		 * deleted.
 		 */
-		if (BTScanPosIsPinned(so->currPos))
-			LockBuffer(so->currPos.buf, BT_READ);
+		if (BTScanPosIsPinned(*currPos))
+			LockBuffer(currPos->buf, BT_READ);
 		else
-			so->currPos.buf = _bt_getbuf(rel, so->currPos.currPage, BT_READ);
+			currPos->buf = _bt_getbuf(rel, currPos->currPage, BT_READ);
 
 		for (;;)
 		{
 			/* Done if we know there are no matching keys to the left */
-			if (!so->currPos.moreLeft)
+			if (!currPos->moreLeft)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
 			/* Step to next physical page */
-			so->currPos.buf = _bt_walk_left(rel, so->currPos.buf,
-											scan->xs_snapshot);
+			currPos->buf = _bt_walk_left(rel, currPos->buf, scan->xs_snapshot);
 
 			/* if we're physically at end of index, return failure */
-			if (so->currPos.buf == InvalidBuffer)
+			if (currPos->buf == InvalidBuffer)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
@@ -1608,21 +1640,21 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * it's not half-dead and contains matching tuples. Else loop back
 			 * and do it all again.
 			 */
-			page = BufferGetPage(so->currPos.buf);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			if (!P_IGNORE(opaque))
 			{
-				PredicateLockPage(rel, BufferGetBlockNumber(so->currPos.buf), scan->xs_snapshot);
+				PredicateLockPage(rel, BufferGetBlockNumber(currPos->buf), scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreLeft if we can stop */
-				if (_bt_readpage(scan, dir, PageGetMaxOffsetNumber(page)))
+				if (_bt_readpage(scan, state, dir, PageGetMaxOffsetNumber(page)))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -1633,14 +1665,14 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
-				so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
+				currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
 			}
 		}
 	}
@@ -1659,13 +1691,13 @@ _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
 
 	return true;
 }
@@ -1890,11 +1922,11 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber start;
-	BTScanPosItem *currItem;
 
 	/*
 	 * Scan down to the leftmost or rightmost leaf page.  This is a simplified
@@ -1910,7 +1942,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 		 * exists.
 		 */
 		PredicateLockRelation(rel, scan->xs_snapshot);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 		return false;
 	}
 
@@ -1939,36 +1971,15 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 	}
 
 	/* remember which buffer we have pinned */
-	so->currPos.buf = buf;
+	currPos->buf = buf;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, start))
-	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
-	}
-	else
-	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
-	}
-
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
+	if (!_bt_load_first_page(scan, &so->state, dir, start))
+		return false;
 
-	return true;
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
@@ -1976,19 +1987,19 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
  * for scan direction
  */
 static inline void
-_bt_initialize_more_data(BTScanOpaque so, ScanDirection dir)
+_bt_initialize_more_data(BTScanState state, ScanDirection dir)
 {
 	/* initialize moreLeft/moreRight appropriately for scan direction */
 	if (ScanDirectionIsForward(dir))
 	{
-		so->currPos.moreLeft = false;
-		so->currPos.moreRight = true;
+		state->currPos.moreLeft = false;
+		state->currPos.moreRight = true;
 	}
 	else
 	{
-		so->currPos.moreLeft = true;
-		so->currPos.moreRight = false;
+		state->currPos.moreLeft = true;
+		state->currPos.moreRight = false;
 	}
-	so->numKilled = 0;			/* just paranoia */
-	so->markItemIndex = -1;		/* ditto */
+	state->numKilled = 0;		/* just paranoia */
+	state->markItemIndex = -1;	/* ditto */
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 2c05fb5..e548354 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -1741,26 +1741,26 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, TupleDesc tupdesc,
  * away and the TID was re-used by a completely different heap tuple.
  */
 void
-_bt_killitems(IndexScanDesc scan)
+_bt_killitems(BTScanState state, Relation indexRelation)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
 	OffsetNumber maxoff;
 	int			i;
-	int			numKilled = so->numKilled;
+	int			numKilled = state->numKilled;
 	bool		killedsomething = false;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(state->currPos));
 
 	/*
 	 * Always reset the scan state, so we don't look for same items on other
 	 * pages.
 	 */
-	so->numKilled = 0;
+	state->numKilled = 0;
 
-	if (BTScanPosIsPinned(so->currPos))
+	if (BTScanPosIsPinned(*pos))
 	{
 		/*
 		 * We have held the pin on this page since we read the index tuples,
@@ -1768,44 +1768,42 @@ _bt_killitems(IndexScanDesc scan)
 		 * re-use of any TID on the page, so there is no need to check the
 		 * LSN.
 		 */
-		LockBuffer(so->currPos.buf, BT_READ);
-
-		page = BufferGetPage(so->currPos.buf);
+		LockBuffer(pos->buf, BT_READ);
 	}
 	else
 	{
 		Buffer		buf;
 
 		/* Attempt to re-read the buffer, getting pin and lock. */
-		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ);
+		buf = _bt_getbuf(indexRelation, pos->currPage, BT_READ);
 
 		/* It might not exist anymore; in which case we can't hint it. */
 		if (!BufferIsValid(buf))
 			return;
 
-		page = BufferGetPage(buf);
-		if (BufferGetLSNAtomic(buf) == so->currPos.lsn)
-			so->currPos.buf = buf;
+		if (BufferGetLSNAtomic(buf) == pos->lsn)
+			pos->buf = buf;
 		else
 		{
 			/* Modified while not pinned means hinting is not safe. */
-			_bt_relbuf(scan->indexRelation, buf);
+			_bt_relbuf(indexRelation, buf);
 			return;
 		}
 	}
 
+	page = BufferGetPage(pos->buf);
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 	minoff = P_FIRSTDATAKEY(opaque);
 	maxoff = PageGetMaxOffsetNumber(page);
 
 	for (i = 0; i < numKilled; i++)
 	{
-		int			itemIndex = so->killedItems[i];
-		BTScanPosItem *kitem = &so->currPos.items[itemIndex];
+		int			itemIndex = state->killedItems[i];
+		BTScanPosItem *kitem = &pos->items[itemIndex];
 		OffsetNumber offnum = kitem->indexOffset;
 
-		Assert(itemIndex >= so->currPos.firstItem &&
-			   itemIndex <= so->currPos.lastItem);
+		Assert(itemIndex >= pos->firstItem &&
+			   itemIndex <= pos->lastItem);
 		if (offnum < minoff)
 			continue;			/* pure paranoia */
 		while (offnum <= maxoff)
@@ -1833,10 +1831,10 @@ _bt_killitems(IndexScanDesc scan)
 	if (killedsomething)
 	{
 		opaque->btpo_flags |= BTP_HAS_GARBAGE;
-		MarkBufferDirtyHint(so->currPos.buf, true);
+		MarkBufferDirtyHint(pos->buf, true);
 	}
 
-	LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
+	LockBuffer(pos->buf, BUFFER_LOCK_UNLOCK);
 }
 
 
@@ -2214,3 +2212,14 @@ _bt_check_natts(Relation rel, Page page, OffsetNumber offnum)
 
 	}
 }
+
+/*
+ * _bt_allocate_tuple_workspaces() -- Allocate buffers for saving index tuples
+ * 		in index-only scans.
+ */
+void
+_bt_allocate_tuple_workspaces(BTScanState state)
+{
+	state->currTuples = (char *) palloc(BLCKSZ * 2);
+	state->markTuples = state->currTuples + BLCKSZ;
+}
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 4fb92d6..d78df29 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -433,22 +433,8 @@ typedef struct BTArrayKeyInfo
 	Datum	   *elem_values;	/* array of num_elems Datums */
 } BTArrayKeyInfo;
 
-typedef struct BTScanOpaqueData
+typedef struct BTScanStateData
 {
-	/* these fields are set by _bt_preprocess_keys(): */
-	bool		qual_ok;		/* false if qual can never be satisfied */
-	int			numberOfKeys;	/* number of preprocessed scan keys */
-	ScanKey		keyData;		/* array of preprocessed scan keys */
-
-	/* workspace for SK_SEARCHARRAY support */
-	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
-	int			numArrayKeys;	/* number of equality-type array keys (-1 if
-								 * there are any unsatisfiable array keys) */
-	int			arrayKeyCount;	/* count indicating number of array scan keys
-								 * processed */
-	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
-	MemoryContext arrayContext; /* scan-lifespan context for array data */
-
 	/* info about killed items if any (killedItems is NULL if never used) */
 	int		   *killedItems;	/* currPos.items indexes of killed items */
 	int			numKilled;		/* number of currently stored items */
@@ -473,6 +459,27 @@ typedef struct BTScanOpaqueData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+} BTScanStateData;
+
+typedef BTScanStateData *BTScanState;
+
+typedef struct BTScanOpaqueData
+{
+	/* these fields are set by _bt_preprocess_keys(): */
+	bool		qual_ok;		/* false if qual can never be satisfied */
+	int			numberOfKeys;	/* number of preprocessed scan keys */
+	ScanKey		keyData;		/* array of preprocessed scan keys */
+
+	/* workspace for SK_SEARCHARRAY support */
+	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
+	int			numArrayKeys;	/* number of equality-type array keys (-1 if
+								 * there are any unsatisfiable array keys) */
+	int			arrayKeyCount;	/* count indicating number of array scan keys
+								 * processed */
+	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
+	MemoryContext arrayContext; /* scan-lifespan context for array data */
+
+	BTScanStateData	state;
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -589,7 +596,7 @@ extern void _bt_preprocess_keys(IndexScanDesc scan);
 extern IndexTuple _bt_checkkeys(IndexScanDesc scan,
 			  Page page, OffsetNumber offnum,
 			  ScanDirection dir, bool *continuescan);
-extern void _bt_killitems(IndexScanDesc scan);
+extern void _bt_killitems(BTScanState state, Relation indexRelation);
 extern BTCycleId _bt_vacuum_cycleid(Relation rel);
 extern BTCycleId _bt_start_vacuum(Relation rel);
 extern void _bt_end_vacuum(Relation rel);
@@ -602,6 +609,7 @@ extern bool btproperty(Oid index_oid, int attno,
 		   bool *res, bool *isnull);
 extern IndexTuple _bt_nonkey_truncate(Relation rel, IndexTuple itup);
 extern bool _bt_check_natts(Relation rel, Page page, OffsetNumber offnum);
+extern void _bt_allocate_tuple_workspaces(BTScanState state);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3d3c76d..5c065a5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -181,6 +181,8 @@ BTScanOpaqueData
 BTScanPos
 BTScanPosData
 BTScanPosItem
+BTScanState
+BTScanStateData
 BTShared
 BTSortArrayContext
 BTSpool
0001-Fix-get_index_column_opclass-v07.patchtext/x-patch; name=0001-Fix-get_index_column_opclass-v07.patchDownload
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index e88c45d..2760748 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -3118,9 +3118,6 @@ get_index_column_opclass(Oid index_oid, int attno)
 {
 	HeapTuple	tuple;
 	Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY;
-	Datum		datum;
-	bool		isnull;
-	oidvector  *indclass;
 	Oid			opclass;
 
 	/* First we need to know the column's opclass. */
@@ -3134,12 +3131,23 @@ get_index_column_opclass(Oid index_oid, int attno)
 	/* caller is supposed to guarantee this */
 	Assert(attno > 0 && attno <= rd_index->indnatts);
 
-	datum = SysCacheGetAttr(INDEXRELID, tuple,
-							Anum_pg_index_indclass, &isnull);
-	Assert(!isnull);
+	if (attno >= 1 && attno <= rd_index->indnkeyatts)
+	{
+		oidvector  *indclass;
+		bool		isnull;
+		Datum		datum = SysCacheGetAttr(INDEXRELID, tuple,
+											Anum_pg_index_indclass,
+											&isnull);
+
+		Assert(!isnull);
 
-	indclass = ((oidvector *) DatumGetPointer(datum));
-	opclass = indclass->values[attno - 1];
+		indclass = ((oidvector *) DatumGetPointer(datum));
+		opclass = indclass->values[attno - 1];
+	}
+	else
+	{
+		opclass = InvalidOid;
+	}
 
 	ReleaseSysCache(tuple);
 
0002-Introduce-ammatchorderby-function-v07.patchtext/x-patch; name=0002-Introduce-ammatchorderby-function-v07.patchDownload
diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 6458376..9bff793 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -109,7 +109,6 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BLOOM_NSTRATEGIES;
 	amroutine->amsupport = BLOOM_NPROC;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -143,6 +142,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 8f008dd..5cd06c7 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -88,7 +88,6 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = BRIN_LAST_OPTIONAL_PROCNUM;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -122,6 +121,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index afc2023..0f6714d 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -41,7 +41,6 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GINNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -75,6 +74,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index b75b3a8..77ca187 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -18,6 +18,7 @@
 #include "access/gistscan.h"
 #include "catalog/pg_collation.h"
 #include "miscadmin.h"
+#include "optimizer/paths.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "nodes/execnodes.h"
@@ -64,7 +65,6 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GISTNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -98,6 +98,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = match_orderbyop_pathkeys;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index f1f01a0..bb5c6a1 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -59,7 +59,6 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = HTMaxStrategyNumber;
 	amroutine->amsupport = HASHNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -93,6 +92,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 98917de..c56ee5e 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -110,7 +110,6 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BTMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = true;
 	amroutine->amcanmulticol = true;
@@ -144,6 +143,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = btestimateparallelscan;
 	amroutine->aminitparallelscan = btinitparallelscan;
 	amroutine->amparallelrescan = btparallelrescan;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 8e63c1f..37de33c 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -22,6 +22,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/pg_amop.h"
+#include "optimizer/paths.h"
 #include "storage/bufmgr.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -31,7 +32,6 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
-
 /*
  * SP-GiST handler function: return IndexAmRoutine with access method parameters
  * and callbacks.
@@ -44,7 +44,6 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = SPGISTNProc;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -78,6 +77,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = match_orderbyop_pathkeys;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 5d73848..a595c79 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -1097,7 +1097,7 @@ assignOperTypes(OpFamilyMember *member, Oid amoid, Oid typeoid)
 		 */
 		IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid, false);
 
-		if (!amroutine->amcanorderbyop)
+		if (!amroutine->ammatchorderby)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("access method \"%s\" does not support ordering operators",
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 324356e..805abd2 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -198,7 +198,7 @@ IndexNextWithReorder(IndexScanState *node)
 	 * with just Asserting here because the system will not try to run the
 	 * plan backwards if ExecSupportsBackwardScan() says it won't work.
 	 * Currently, that is guaranteed because no index AMs support both
-	 * amcanorderbyop and amcanbackward; if any ever do,
+	 * ammatchorderby and amcanbackward; if any ever do,
 	 * ExecSupportsBackwardScan() will need to consider indexorderbys
 	 * explicitly.
 	 */
@@ -1148,7 +1148,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
  * 5. NullTest ("indexkey IS NULL/IS NOT NULL").  We just fill in the
  * ScanKey properly.
  *
- * This code is also used to prepare ORDER BY expressions for amcanorderbyop
+ * This code is also used to prepare ORDER BY expressions for ammatchorderby
  * indexes.  The behavior is exactly the same, except that we have to look up
  * the operator differently.  Note that only cases 1 and 2 are currently
  * possible for ORDER BY.
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 3434219..6cf0b0d 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -17,6 +17,7 @@
 
 #include <math.h>
 
+#include "access/amapi.h"
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_am.h"
@@ -182,8 +183,9 @@ static IndexClause *expand_indexqual_rowcompare(RestrictInfo *rinfo,
 							Oid expr_op,
 							bool var_on_left);
 static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
+						List *index_clauses,
 						List **orderby_clauses_p,
-						List **clause_columns_p);
+						List **orderby_clause_columns_p);
 static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
 							int indexcol, Expr *clause, Oid pk_opfamily);
 static bool ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
@@ -1001,10 +1003,11 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		orderbyclauses = NIL;
 		orderbyclausecols = NIL;
 	}
-	else if (index->amcanorderbyop && pathkeys_possibly_useful)
+	else if (index->ammatchorderby && pathkeys_possibly_useful)
 	{
 		/* see if we can generate ordering operators for query_pathkeys */
 		match_pathkeys_to_index(index, root->query_pathkeys,
+								index_clauses,
 								&orderbyclauses,
 								&orderbyclausecols);
 		if (orderbyclauses)
@@ -2927,6 +2930,113 @@ match_rowcompare_to_indexcol(RestrictInfo *rinfo,
 	return NULL;
 }
 
+/****************************************************************************
+ *				----  ROUTINES TO CHECK ORDERING OPERATORS	----
+ ****************************************************************************/
+
+/*
+ * Try to match order-by-operator pathkey to the specified index column
+ * (*indexcol_p >= 0) or to all index columns (*indexcol_p < 0).
+ *
+ * Returned matched index clause exression.
+ * Number of matched index column is returned in *indexcol_p.
+ */
+Expr *
+match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey, int *indexcol_p)
+{
+	ListCell   *lc;
+
+	/* Pathkey must request default sort order for the target opfamily */
+	if (pathkey->pk_strategy != BTLessStrategyNumber ||
+		pathkey->pk_nulls_first)
+		return NULL;
+
+	/* If eclass is volatile, no hope of using an indexscan */
+	if (pathkey->pk_eclass->ec_has_volatile)
+		return NULL;
+
+	/*
+	 * Try to match eclass member expression(s) to index.  Note that child EC
+	 * members are considered, but only when they belong to the target
+	 * relation.  (Unlike regular members, the same expression could be a
+	 * child member of more than one EC.  Therefore, the same index could be
+	 * considered to match more than one pathkey list, which is OK here.  See
+	 * also get_eclass_for_sort_expr.)
+	 */
+	foreach(lc, pathkey->pk_eclass->ec_members)
+	{
+		EquivalenceMember *member = lfirst_node(EquivalenceMember, lc);
+		Expr	   *expr;
+
+		/* No possibility of match if it references other relations. */
+		if (!bms_equal(member->em_relids, index->rel->relids))
+			continue;
+
+		/* If *indexcol_p is non-negative then try to match only to it. */
+		if (*indexcol_p >= 0)
+		{
+			expr = match_clause_to_ordering_op(index, *indexcol_p,
+											   member->em_expr,
+											   pathkey->pk_opfamily);
+
+			if (expr)
+				return expr;	/* don't want to look at remaining members */
+		}
+		else
+		{
+			int			indexcol;
+
+			/*
+			 * We allow any column of this index to match each pathkey; they
+			 * don't have to match left-to-right as you might expect.
+			 */
+			for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
+			{
+				expr = match_clause_to_ordering_op(index, indexcol,
+												   member->em_expr,
+												   pathkey->pk_opfamily);
+				if (expr)
+				{
+					*indexcol_p = indexcol;
+					return expr;	/* don't want to look at remaining members */
+				}
+			}
+		}
+	}
+
+	return NULL;
+}
+
+/* Try to match order-by-operator pathkeys to any index columns. */
+bool
+match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
+						 List *index_clauses, List **orderby_clauses_p,
+						 List **orderby_clausecols_p)
+{
+	ListCell   *lc;
+
+	foreach(lc, pathkeys)
+	{
+		PathKey    *pathkey = lfirst_node(PathKey, lc);
+		Expr	   *expr;
+		int			indexcol = -1;	/* match all index columns */
+
+		expr = match_orderbyop_pathkey(index, pathkey, &indexcol);
+
+		/*
+		 * Note: for any failure to match, we just return NIL immediately.
+		 * There is no value in matching just some of the pathkeys.
+		 */
+		if (!expr)
+			return false;
+
+		*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+		*orderby_clausecols_p = lappend_int(*orderby_clausecols_p, indexcol);
+	}
+
+	return true;				/* success */
+}
+
 /*
  * expand_indexqual_rowcompare --- expand a single indexqual condition
  *		that is a RowCompareExpr
@@ -3183,92 +3293,31 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
  */
 static void
 match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
+						List *index_clauses,
 						List **orderby_clauses_p,
-						List **clause_columns_p)
+						List **orderby_clause_columns_p)
 {
 	List	   *orderby_clauses = NIL;
-	List	   *clause_columns = NIL;
-	ListCell   *lc1;
-
-	*orderby_clauses_p = NIL;	/* set default results */
-	*clause_columns_p = NIL;
+	List	   *orderby_clause_columns = NIL;
+	ammatchorderby_function ammatchorderby =
+	(ammatchorderby_function) index->ammatchorderby;
 
-	/* Only indexes with the amcanorderbyop property are interesting here */
-	if (!index->amcanorderbyop)
-		return;
-
-	foreach(lc1, pathkeys)
+	/* Only indexes with the ammatchorderby function are interesting here */
+	if (ammatchorderby &&
+		ammatchorderby(index, pathkeys, index_clauses,
+					   &orderby_clauses, &orderby_clause_columns))
 	{
-		PathKey    *pathkey = (PathKey *) lfirst(lc1);
-		bool		found = false;
-		ListCell   *lc2;
-
-		/*
-		 * Note: for any failure to match, we just return NIL immediately.
-		 * There is no value in matching just some of the pathkeys.
-		 */
-
-		/* Pathkey must request default sort order for the target opfamily */
-		if (pathkey->pk_strategy != BTLessStrategyNumber ||
-			pathkey->pk_nulls_first)
-			return;
-
-		/* If eclass is volatile, no hope of using an indexscan */
-		if (pathkey->pk_eclass->ec_has_volatile)
-			return;
-
-		/*
-		 * Try to match eclass member expression(s) to index.  Note that child
-		 * EC members are considered, but only when they belong to the target
-		 * relation.  (Unlike regular members, the same expression could be a
-		 * child member of more than one EC.  Therefore, the same index could
-		 * be considered to match more than one pathkey list, which is OK
-		 * here.  See also get_eclass_for_sort_expr.)
-		 */
-		foreach(lc2, pathkey->pk_eclass->ec_members)
-		{
-			EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
-			int			indexcol;
-
-			/* No possibility of match if it references other relations */
-			if (!bms_equal(member->em_relids, index->rel->relids))
-				continue;
+		Assert(list_length(pathkeys) == list_length(orderby_clauses));
+		Assert(list_length(pathkeys) == list_length(orderby_clause_columns));
 
-			/*
-			 * We allow any column of the index to match each pathkey; they
-			 * don't have to match left-to-right as you might expect.  This is
-			 * correct for GiST, and it doesn't matter for SP-GiST because
-			 * that doesn't handle multiple columns anyway, and no other
-			 * existing AMs support amcanorderbyop.  We might need different
-			 * logic in future for other implementations.
-			 */
-			for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
-			{
-				Expr	   *expr;
-
-				expr = match_clause_to_ordering_op(index,
-												   indexcol,
-												   member->em_expr,
-												   pathkey->pk_opfamily);
-				if (expr)
-				{
-					orderby_clauses = lappend(orderby_clauses, expr);
-					clause_columns = lappend_int(clause_columns, indexcol);
-					found = true;
-					break;
-				}
-			}
-
-			if (found)			/* don't want to look at remaining members */
-				break;
-		}
-
-		if (!found)				/* fail if no match for this pathkey */
-			return;
+		*orderby_clauses_p = orderby_clauses;	/* success! */
+		*orderby_clause_columns_p = orderby_clause_columns;
+	}
+	else
+	{
+		*orderby_clauses_p = NIL;	/* set default results */
+		*orderby_clause_columns_p = NIL;
 	}
-
-	*orderby_clauses_p = orderby_clauses;	/* success! */
-	*clause_columns_p = clause_columns;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d6dc83c..2d81fd1 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -267,7 +267,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 			/* We copy just the fields we need, not all of rd_indam */
 			amroutine = indexRelation->rd_indam;
-			info->amcanorderbyop = amroutine->amcanorderbyop;
+			info->ammatchorderby = amroutine->ammatchorderby;
 			info->amoptionalkey = amroutine->amoptionalkey;
 			info->amsearcharray = amroutine->amsearcharray;
 			info->amsearchnulls = amroutine->amsearchnulls;
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c
index 060ffe5..3907065 100644
--- a/src/backend/utils/adt/amutils.c
+++ b/src/backend/utils/adt/amutils.c
@@ -298,7 +298,7 @@ indexam_property(FunctionCallInfo fcinfo,
 				 * a nonkey column, and null otherwise (meaning we don't
 				 * know).
 				 */
-				if (!iskey || !routine->amcanorderbyop)
+				if (!iskey || !routine->ammatchorderby)
 				{
 					res = false;
 					isnull = false;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 653ddc9..d8fb7d3 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -21,6 +21,9 @@
  */
 struct PlannerInfo;
 struct IndexPath;
+struct IndexOptInfo;
+struct PathKey;
+struct Expr;
 
 /* Likewise, this file shouldn't depend on execnodes.h. */
 struct IndexInfo;
@@ -140,6 +143,13 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/* does AM support ORDER BY result of an operator on indexed column? */
+typedef bool (*ammatchorderby_function) (struct IndexOptInfo *index,
+										 List *pathkeys,
+										 List *index_clauses,
+										 List **orderby_clauses_p,
+										 List **orderby_clause_columns_p);
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -170,8 +180,6 @@ typedef struct IndexAmRoutine
 	uint16		amsupport;
 	/* does AM support ORDER BY indexed column's value? */
 	bool		amcanorder;
-	/* does AM support ORDER BY result of an operator on indexed column? */
-	bool		amcanorderbyop;
 	/* does AM support backward scanning? */
 	bool		amcanbackward;
 	/* does AM support UNIQUE indexes? */
@@ -221,6 +229,7 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;	/* can be NULL */
 	amrestrpos_function amrestrpos; /* can be NULL */
+	ammatchorderby_function ammatchorderby; /* can be NULL */
 
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index a008ae0..4680cb8 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -814,7 +814,6 @@ struct IndexOptInfo
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
 	bool		amoptionalkey;	/* can query omit key for the first column? */
 	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
 	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
@@ -823,6 +822,12 @@ struct IndexOptInfo
 	bool		amcanparallel;	/* does AM support parallel scan? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
+	/* AM order-by match function */
+	bool		(*ammatchorderby) (struct IndexOptInfo *index,
+								   List *pathkeys,
+								   List *index_clause_columns,
+								   List **orderby_clauses_p,
+								   List **orderby_clause_columns_p);
 };
 
 /*
@@ -1133,7 +1138,7 @@ typedef struct Path
  * An empty list implies a full index scan.
  *
  * 'indexorderbys', if not NIL, is a list of ORDER BY expressions that have
- * been found to be usable as ordering operators for an amcanorderbyop index.
+ * been found to be usable as ordering operators for an ammatchorderby index.
  * The list must match the path's pathkeys, ie, one expression per pathkey
  * in the same order.  These are not RestrictInfos, just bare expressions,
  * since they generally won't yield booleans.  It's guaranteed that each
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 6d087c2..bd6ce97 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -388,7 +388,7 @@ typedef struct SampleScan
  * indexorderbyops is a list of the OIDs of the operators used to sort the
  * ORDER BY expressions.  This is used together with indexorderbyorig to
  * recheck ordering at run time.  (Note that indexorderby, indexorderbyorig,
- * and indexorderbyops are used for amcanorderbyop cases, not amcanorder.)
+ * and indexorderbyops are used for ammatchorderby cases, not amcanorder.)
  *
  * indexorderdir specifies the scan ordering, for indexscans on amcanorder
  * indexes (for other indexes it should be "don't care").
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 040335a..e9f4f75 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -79,6 +79,11 @@ extern bool indexcol_is_bool_constant_for_query(IndexOptInfo *index,
 extern bool match_index_to_operand(Node *operand, int indexcol,
 					   IndexOptInfo *index);
 extern void check_index_predicates(PlannerInfo *root, RelOptInfo *rel);
+extern Expr *match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey,
+						int *indexcol_p);
+extern bool match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
+						 List *index_clauses, List **orderby_clauses_p,
+						 List **orderby_clause_columns_p);
 
 /*
  * tidpath.h
0004-Add-kNN-support-to-btree-v07.patchtext/x-patch; name=0004-Add-kNN-support-to-btree-v07.patchDownload
diff --git a/doc/src/sgml/btree.sgml b/doc/src/sgml/btree.sgml
index 996932e..b94be7a 100644
--- a/doc/src/sgml/btree.sgml
+++ b/doc/src/sgml/btree.sgml
@@ -200,6 +200,53 @@
   planner relies on them for optimization purposes.
  </para>
 
+ <para>
+  To implement the distance ordered (nearest-neighbor) search, we only need
+  to define a distance operator (usually it called
+  <literal>&lt;-&gt;</literal>) with a correpsonding operator family for
+  distance comparison in the index's operator class.  These operators must
+  satisfy the following assumptions for all non-null values
+  <replaceable>A</replaceable>, <replaceable>B</replaceable>,
+  <replaceable>C</replaceable> of the datatype:
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     <replaceable>A</replaceable> <literal>&lt;-&gt;</literal>
+     <replaceable>B</replaceable> <literal>=</literal>
+     <replaceable>B</replaceable> <literal>&lt;-&gt;</literal>
+     <replaceable>A</replaceable>
+     (<firstterm>symmetric law</firstterm>)
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     if <replaceable>A</replaceable> <literal>=</literal>
+     <replaceable>B</replaceable>, then <replaceable>A</replaceable>
+     <literal>&lt;-&gt;</literal> <replaceable>C</replaceable>
+     <literal>=</literal> <replaceable>B</replaceable>
+     <literal>&lt;-&gt;</literal> <replaceable>C</replaceable>
+     (<firstterm>distance equivalence</firstterm>)
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      if (<replaceable>A</replaceable> <literal>&lt;=</literal>
+      <replaceable>B</replaceable> and <replaceable>B</replaceable>
+      <literal>&lt;=</literal> <replaceable>C</replaceable>) or
+      (<replaceable>A</replaceable> <literal>&gt;=</literal>
+      <replaceable>B</replaceable> and <replaceable>B</replaceable>
+      <literal>&gt;=</literal> <replaceable>C</replaceable>),
+      then <replaceable>A</replaceable> <literal>&lt;-&gt;</literal>
+      <replaceable>B</replaceable> <literal>&lt;=</literal>
+      <replaceable>A</replaceable> <literal>&lt;-&gt;</literal>
+      <replaceable>C</replaceable>
+     (<firstterm>monotonicity</firstterm>)
+    </para>
+   </listitem>
+  </itemizedlist>
+ </para>
+
 </sect1>
 
 <sect1 id="btree-support-funcs">
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 46f427b..1998171 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -175,6 +175,17 @@ CREATE INDEX test1_id_index ON test1 (id);
   </para>
 
   <para>
+   B-tree indexes are also capable of optimizing <quote>nearest-neighbor</quote>
+   searches, such as
+<programlisting><![CDATA[
+SELECT * FROM events ORDER BY event_date <-> date '2017-05-05' LIMIT 10;
+]]>
+</programlisting>
+   which finds the ten events closest to a given target date.  The ability
+   to do this is again dependent on the particular operator class being used.
+  </para>
+
+  <para>
    <indexterm>
     <primary>index</primary>
     <secondary>hash</secondary>
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 9446f8b..93094bc 100644
--- a/doc/src/sgml/xindex.sgml
+++ b/doc/src/sgml/xindex.sgml
@@ -1242,7 +1242,8 @@ SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
   <title>Ordering Operators</title>
 
   <para>
-   Some index access methods (currently, only GiST and SP-GiST) support the concept of
+   Some index access methods (currently, only B-tree, GiST and SP-GiST) 
+   support the concept of
    <firstterm>ordering operators</firstterm>.  What we have been discussing so far
    are <firstterm>search operators</firstterm>.  A search operator is one for which
    the index can be searched to find all rows satisfying
diff --git a/src/backend/access/nbtree/README b/src/backend/access/nbtree/README
index 3680e69..3f7e1b1 100644
--- a/src/backend/access/nbtree/README
+++ b/src/backend/access/nbtree/README
@@ -659,3 +659,20 @@ routines must treat it accordingly.  The actual key stored in the
 item is irrelevant, and need not be stored at all.  This arrangement
 corresponds to the fact that an L&Y non-leaf page has one more pointer
 than key.
+
+Nearest-neighbor search
+-----------------------
+
+There is a special scan strategy for nearest-neighbor (kNN) search,
+that is used in queries with ORDER BY distance clauses like this:
+SELECT * FROM tab WHERE col > const1 ORDER BY col <-> const2 LIMIT k.
+But, unlike GiST, B-tree supports only a one ordering operator on the
+first index column.
+
+At the beginning of kNN scan, we need to determine which strategy we
+will use --- a special bidirectional or a ordinary unidirectional.
+If the point from which we measure the distance falls into the scan range,
+we use bidirectional scan starting from this point, else we use simple
+unidirectional scan in the right direction.  Algorithm of a bidirectional
+scan is very simple: at each step we advancing scan in that direction,
+which has the nearest point.
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index bf6a6c6..fb413e7 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -25,6 +25,9 @@
 #include "commands/vacuum.h"
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
+#include "nodes/pathnodes.h"
+#include "nodes/primnodes.h"
+#include "optimizer/paths.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "storage/condition_variable.h"
@@ -33,7 +36,9 @@
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/index_selfuncs.h"
+#include "utils/lsyscache.h"
 #include "utils/memutils.h"
 
 
@@ -79,6 +84,7 @@ typedef enum
 typedef struct BTParallelScanDescData
 {
 	BlockNumber btps_scanPage;	/* latest or next page to be scanned */
+	BlockNumber btps_knnScanPage;	/* secondary kNN page to be scanned */
 	BTPS_State	btps_pageStatus;	/* indicates whether next page is
 									 * available for scan. see above for
 									 * possible states of parallel scan. */
@@ -97,6 +103,10 @@ static void btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 static void btvacuumpage(BTVacState *vstate, BlockNumber blkno,
 			 BlockNumber orig_blkno);
 
+static bool btmatchorderby(IndexOptInfo *index, List *pathkeys,
+			   List *index_clauses, List **orderby_clauses_p,
+			   List **orderby_clause_columns_p);
+
 
 /*
  * Btree handler function: return IndexAmRoutine with access method parameters
@@ -107,7 +117,7 @@ bthandler(PG_FUNCTION_ARGS)
 {
 	IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
 
-	amroutine->amstrategies = BTMaxStrategyNumber;
+	amroutine->amstrategies = BtreeMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
 	amroutine->amcanbackward = true;
@@ -143,7 +153,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = btestimateparallelscan;
 	amroutine->aminitparallelscan = btinitparallelscan;
 	amroutine->amparallelrescan = btparallelrescan;
-	amroutine->ammatchorderby = NULL;
+	amroutine->ammatchorderby = btmatchorderby;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -215,23 +225,30 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTScanState state = &so->state;
+	ScanDirection arraydir =
+	scan->numberOfOrderBys > 0 ? ForwardScanDirection : dir;
 	bool		res;
 
 	/* btree indexes are never lossy */
 	scan->xs_recheck = false;
+	scan->xs_recheckorderby = false;
+
+	if (so->scanDirection != NoMovementScanDirection)
+		dir = so->scanDirection;
 
 	/*
 	 * If we have any array keys, initialize them during first call for a
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos) &&
+		(!so->knnState || !BTScanPosIsValid(so->knnState->currPos)))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
 			return false;
 
-		_bt_start_array_keys(scan, dir);
+		_bt_start_array_keys(scan, arraydir);
 	}
 
 	/* This loop handles advancing to the next array elements, if any */
@@ -242,7 +259,8 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(state->currPos))
+		if (!BTScanPosIsValid(state->currPos) &&
+			(!so->knnState || !BTScanPosIsValid(so->knnState->currPos)))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -277,7 +295,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		if (res)
 			break;
 		/* ... otherwise see if we have more array keys to deal with */
-	} while (so->numArrayKeys && _bt_advance_array_keys(scan, dir));
+	} while (so->numArrayKeys && _bt_advance_array_keys(scan, arraydir));
 
 	return res;
 }
@@ -350,9 +368,6 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	IndexScanDesc scan;
 	BTScanOpaque so;
 
-	/* no order by operators allowed */
-	Assert(norderbys == 0);
-
 	/* get the scan */
 	scan = RelationGetIndexScan(rel, nkeys, norderbys);
 
@@ -379,6 +394,9 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
 	so->state.currTuples = so->state.markTuples = NULL;
+	so->knnState = NULL;
+	so->distanceTypeByVal = true;
+	so->scanDirection = NoMovementScanDirection;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -408,6 +426,8 @@ _bt_release_current_position(BTScanState state, Relation indexRelation,
 static void
 _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	/* No need to invalidate positions, if the RAM is about to be freed. */
 	_bt_release_current_position(state, scan->indexRelation, !free);
 
@@ -424,6 +444,17 @@ _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 	}
 	else
 		BTScanPosInvalidate(state->markPos);
+
+	if (!so->distanceTypeByVal)
+	{
+		if (DatumGetPointer(state->currDistance))
+			pfree(DatumGetPointer(state->currDistance));
+		state->currDistance = PointerGetDatum(NULL);
+
+		if (DatumGetPointer(state->markDistance))
+			pfree(DatumGetPointer(state->markDistance));
+		state->markDistance = PointerGetDatum(NULL);
+	}
 }
 
 /*
@@ -438,6 +469,13 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 
 	_bt_release_scan_state(scan, state, false);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+		so->knnState = NULL;
+	}
+
 	so->arrayKeyCount = 0;
 
 	/*
@@ -469,6 +507,14 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 				scan->numberOfKeys * sizeof(ScanKeyData));
 	so->numberOfKeys = 0;		/* until _bt_preprocess_keys sets it */
 
+	if (orderbys && scan->numberOfOrderBys > 0)
+		memmove(scan->orderByData,
+				orderbys,
+				scan->numberOfOrderBys * sizeof(ScanKeyData));
+
+	so->scanDirection = NoMovementScanDirection;
+	so->distanceTypeByVal = true;
+
 	/* If any keys are SK_SEARCHARRAY type, set up array-key info */
 	_bt_preprocess_array_keys(scan);
 }
@@ -483,6 +529,12 @@ btendscan(IndexScanDesc scan)
 
 	_bt_release_scan_state(scan, &so->state, true);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+	}
+
 	/* Release storage */
 	if (so->keyData != NULL)
 		pfree(so->keyData);
@@ -494,7 +546,7 @@ btendscan(IndexScanDesc scan)
 }
 
 static void
-_bt_mark_current_position(BTScanState state)
+_bt_mark_current_position(BTScanOpaque so, BTScanState state)
 {
 	/* There may be an old mark with a pin (but no lock). */
 	BTScanPosUnpinIfPinned(state->markPos);
@@ -512,6 +564,21 @@ _bt_mark_current_position(BTScanState state)
 		BTScanPosInvalidate(state->markPos);
 		state->markItemIndex = -1;
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->markDistance));
+
+		state->markIsNull = !BTScanPosIsValid(state->currPos) ||
+			state->currIsNull;
+
+		state->markDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->currDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -522,7 +589,13 @@ btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_mark_current_position(&so->state);
+	_bt_mark_current_position(so, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_mark_current_position(so, so->knnState);
+		so->markRightIsNearest = so->currRightIsNearest;
+	}
 
 	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
@@ -532,6 +605,8 @@ btmarkpos(IndexScanDesc scan)
 static void
 _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	if (state->markItemIndex >= 0)
 	{
 		/*
@@ -567,6 +642,19 @@ _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 					   state->markPos.nextTupleOffset);
 		}
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->currDistance));
+
+		state->currIsNull = state->markIsNull;
+		state->currDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->markDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -588,6 +676,7 @@ btinitparallelscan(void *target)
 
 	SpinLockInit(&bt_target->btps_mutex);
 	bt_target->btps_scanPage = InvalidBlockNumber;
+	bt_target->btps_knnScanPage = InvalidBlockNumber;
 	bt_target->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	bt_target->btps_arrayKeyCount = 0;
 	ConditionVariableInit(&bt_target->btps_cv);
@@ -614,6 +703,7 @@ btparallelrescan(IndexScanDesc scan)
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
 	btscan->btps_scanPage = InvalidBlockNumber;
+	btscan->btps_knnScanPage = InvalidBlockNumber;
 	btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	btscan->btps_arrayKeyCount = 0;
 	SpinLockRelease(&btscan->btps_mutex);
@@ -638,7 +728,7 @@ btparallelrescan(IndexScanDesc scan)
  * Callers should ignore the value of pageno if the return value is false.
  */
 bool
-_bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
+_bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTPS_State	pageStatus;
@@ -646,12 +736,17 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
 	bool		status = true;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
 
 	*pageno = P_NONE;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	scanPage = state == &so->state
+		? &btscan->btps_scanPage
+		: &btscan->btps_knnScanPage;
+
 	while (1)
 	{
 		SpinLockAcquire(&btscan->btps_mutex);
@@ -677,7 +772,7 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
 			 * of advancing it to a new page!
 			 */
 			btscan->btps_pageStatus = BTPARALLEL_ADVANCING;
-			*pageno = btscan->btps_scanPage;
+			*pageno = *scanPage;
 			exit_loop = true;
 		}
 		SpinLockRelease(&btscan->btps_mutex);
@@ -696,19 +791,42 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
  *		can now begin advancing the scan.
  */
 void
-_bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
+_bt_parallel_release(IndexScanDesc scan, BTScanState state,
+					 BlockNumber scan_page)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
+	bool		status_changed = false;
+	bool		knnScan = so->knnState != NULL;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
+	if (!state || state == &so->state)
+	{
+		scanPage = &btscan->btps_scanPage;
+		otherScanPage = &btscan->btps_knnScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_knnScanPage;
+		otherScanPage = &btscan->btps_scanPage;
+	}
 
 	SpinLockAcquire(&btscan->btps_mutex);
-	btscan->btps_scanPage = scan_page;
-	btscan->btps_pageStatus = BTPARALLEL_IDLE;
+	*scanPage = scan_page;
+	/* switch to idle state only if both KNN pages are initialized */
+	if (!knnScan || *otherScanPage != InvalidBlockNumber)
+	{
+		btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		status_changed = true;
+	}
 	SpinLockRelease(&btscan->btps_mutex);
-	ConditionVariableSignal(&btscan->btps_cv);
+
+	if (status_changed)
+		ConditionVariableSignal(&btscan->btps_cv);
 }
 
 /*
@@ -719,12 +837,15 @@ _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
  * advance to the next page.
  */
 void
-_bt_parallel_done(IndexScanDesc scan)
+_bt_parallel_done(IndexScanDesc scan, BTScanState state)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
 	bool		status_changed = false;
+	bool		knnScan = so->knnState != NULL;
 
 	/* Do nothing, for non-parallel scans */
 	if (parallel_scan == NULL)
@@ -733,18 +854,41 @@ _bt_parallel_done(IndexScanDesc scan)
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	if (!state || state == &so->state)
+	{
+		scanPage = &btscan->btps_scanPage;
+		otherScanPage = &btscan->btps_knnScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_knnScanPage;
+		otherScanPage = &btscan->btps_scanPage;
+	}
+
 	/*
 	 * Mark the parallel scan as done for this combination of scan keys,
 	 * unless some other process already did so.  See also
 	 * _bt_advance_array_keys.
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
-	if (so->arrayKeyCount >= btscan->btps_arrayKeyCount &&
-		btscan->btps_pageStatus != BTPARALLEL_DONE)
+
+	Assert(btscan->btps_pageStatus == BTPARALLEL_ADVANCING);
+
+	if (so->arrayKeyCount >= btscan->btps_arrayKeyCount)
 	{
-		btscan->btps_pageStatus = BTPARALLEL_DONE;
+		*scanPage = P_NONE;
 		status_changed = true;
+
+		/* switch to "done" state only if both KNN scans are done */
+		if (!knnScan || *otherScanPage == P_NONE)
+			btscan->btps_pageStatus = BTPARALLEL_DONE;
+		/* else switch to "idle" state only if both KNN scans are initialized */
+		else if (*otherScanPage != InvalidBlockNumber)
+			btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		else
+			status_changed = false;
 	}
+
 	SpinLockRelease(&btscan->btps_mutex);
 
 	/* wake up all the workers associated with this parallel scan */
@@ -774,6 +918,7 @@ _bt_parallel_advance_array_keys(IndexScanDesc scan)
 	if (btscan->btps_pageStatus == BTPARALLEL_DONE)
 	{
 		btscan->btps_scanPage = InvalidBlockNumber;
+		btscan->btps_knnScanPage = InvalidBlockNumber;
 		btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 		btscan->btps_arrayKeyCount++;
 	}
@@ -859,6 +1004,12 @@ btrestrpos(IndexScanDesc scan)
 		_bt_restore_array_keys(scan);
 
 	_bt_restore_marked_position(scan, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_restore_marked_position(scan, so->knnState);
+		so->currRightIsNearest = so->markRightIsNearest;
+	}
 }
 
 /*
@@ -1394,3 +1545,91 @@ btcanreturn(Relation index, int attno)
 {
 	return true;
 }
+
+/*
+ *	btmatchorderby() -- Check whether KNN-search strategy is applicable to
+ *		the given ORDER BY distance operator.
+ */
+static bool
+btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
+			   List **orderby_clauses_p, List **orderby_clausecols_p)
+{
+	Expr	   *expr;
+	ListCell   *lc;
+	int			indexcol;
+	int			num_eq_cols = 0;
+
+	/* only one ORDER BY clause is supported */
+	if (list_length(pathkeys) != 1)
+		return false;
+
+	/*
+	 * Compute a number of leading consequent index columns with equality
+	 * restriction clauses.
+	 */
+	foreach(lc, index_clauses)
+	{
+		IndexClause *iclause = lfirst_node(IndexClause, lc);
+		ListCell   *lcq;
+
+		indexcol = iclause->indexcol;
+
+		if (indexcol > num_eq_cols)
+			/* Sequence of equality-restricted columns is broken. */
+			break;
+
+		foreach(lcq, iclause->indexquals)
+		{
+			RestrictInfo *rinfo = lfirst_node(RestrictInfo, lcq);
+			Expr	   *clause = rinfo->clause;
+			Oid			opno;
+			StrategyNumber strat;
+
+			if (!clause)
+				continue;
+
+			if (IsA(clause, OpExpr))
+			{
+				OpExpr	   *opexpr = (OpExpr *) clause;
+
+				opno = opexpr->opno;
+			}
+			else
+			{
+				/* Skip unsupported expression */
+				continue;
+			}
+
+			/* Check if the operator is btree equality operator. */
+			strat = get_op_opfamily_strategy(opno, index->opfamily[indexcol]);
+
+			if (strat == BTEqualStrategyNumber)
+				num_eq_cols = indexcol + 1;
+		}
+	}
+
+	/*
+	 * If there are no equality columns try to match only the first column,
+	 * otherwise try all columns.
+	 */
+	indexcol = num_eq_cols ? -1 : 0;
+
+	expr = match_orderbyop_pathkey(index, castNode(PathKey, linitial(pathkeys)),
+								   &indexcol);
+
+	if (!expr)
+		return false;
+
+	/*
+	 * ORDER BY distance is supported only for the first index column or if
+	 * all previous columns have equality restrictions.
+	 */
+	if (indexcol > num_eq_cols)
+		return false;
+
+	/* Return first ORDER BY clause's expression and column. */
+	*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+	*orderby_clausecols_p = lappend_int(*orderby_clausecols_p, indexcol);
+
+	return true;
+}
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index cbd72bd..28f94e7 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -31,12 +31,14 @@ static void _bt_saveitem(BTScanState state, int itemIndex,
 static bool _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir);
 static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
 				 BlockNumber blkno, ScanDirection dir);
-static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
-					  ScanDirection dir);
+static bool _bt_parallel_readpage(IndexScanDesc scan, BTScanState state,
+					  BlockNumber blkno, ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf, Snapshot snapshot);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
 static inline void _bt_initialize_more_data(BTScanState state, ScanDirection dir);
+static BTScanState _bt_alloc_knn_scan(IndexScanDesc scan);
+static bool _bt_start_knn_scan(IndexScanDesc scan, bool left, bool right);
 
 
 /*
@@ -597,6 +599,157 @@ _bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 }
 
 /*
+ *  _bt_calc_current_dist() -- Calculate distance from the current item
+ *		of the scan state to the target order-by ScanKey argument.
+ */
+static void
+_bt_calc_current_dist(IndexScanDesc scan, BTScanState state)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanPosItem  *currItem = &state->currPos.items[state->currPos.itemIndex];
+	IndexTuple		itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+	ScanKey			scankey = &scan->orderByData[0];
+	Datum			value;
+
+	value = index_getattr(itup, scankey->sk_attno, scan->xs_itupdesc,
+						  &state->currIsNull);
+
+	if (state->currIsNull)
+		return; /* NULL distance */
+
+	value = FunctionCall2Coll(&scankey->sk_func,
+							  scankey->sk_collation,
+							  value,
+							  scankey->sk_argument);
+
+	/* free previous distance value for by-ref types */
+	if (!so->distanceTypeByVal && DatumGetPointer(state->currDistance))
+		pfree(DatumGetPointer(state->currDistance));
+
+	state->currDistance = value;
+}
+
+/*
+ *  _bt_compare_current_dist() -- Compare current distances of the left and right scan states.
+ *
+ *  NULL distances are considered to be greater than any non-NULL distances.
+ *
+ *  Returns true if right distance is lesser than left, otherwise false.
+ */
+static bool
+_bt_compare_current_dist(BTScanOpaque so, BTScanState rstate, BTScanState lstate)
+{
+	if (lstate->currIsNull)
+		return true; /* non-NULL < NULL */
+
+	if (rstate->currIsNull)
+		return false; /* NULL > non-NULL */
+
+	return DatumGetBool(FunctionCall2Coll(&so->distanceCmpProc,
+										  InvalidOid, /* XXX collation for distance comparison */
+										  rstate->currDistance,
+										  lstate->currDistance));
+}
+
+/*
+ * _bt_alloc_knn_scan() -- Allocate additional backward scan state for KNN.
+ */
+static BTScanState
+_bt_alloc_knn_scan(IndexScanDesc scan)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		lstate = (BTScanState) palloc(sizeof(BTScanStateData));
+
+	_bt_allocate_tuple_workspaces(lstate);
+
+	if (!scan->xs_want_itup)
+	{
+		/* We need to request index tuples for distance comparison. */
+		scan->xs_want_itup = true;
+		_bt_allocate_tuple_workspaces(&so->state);
+	}
+
+	BTScanPosInvalidate(lstate->currPos);
+	lstate->currPos.moreLeft = false;
+	lstate->currPos.moreRight = false;
+	BTScanPosInvalidate(lstate->markPos);
+	lstate->markItemIndex = -1;
+	lstate->killedItems = NULL;
+	lstate->numKilled = 0;
+	lstate->currDistance = PointerGetDatum(NULL);
+	lstate->markDistance = PointerGetDatum(NULL);
+
+	return so->knnState = lstate;
+}
+
+static bool
+_bt_start_knn_scan(IndexScanDesc scan, bool left, bool right)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		rstate; /* right (forward) main scan state */
+	BTScanState		lstate; /* additional left (backward) KNN scan state */
+
+	if (!left && !right)
+		return false; /* empty result */
+
+	rstate = &so->state;
+	lstate = so->knnState;
+
+	if (left && right)
+	{
+		/*
+		 * We have found items in both scan directions,
+		 * determine nearest item to return.
+		 */
+		_bt_calc_current_dist(scan, rstate);
+		_bt_calc_current_dist(scan, lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+
+		/* Reset right flag if the left item is nearer. */
+		right = so->currRightIsNearest;
+	}
+
+	/* Return current item of the selected scan direction. */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
+ * _bt_init_knn_scan() -- Init additional scan state for KNN search.
+ *
+ * Caller must pin and read-lock scan->state.currPos.buf buffer.
+ *
+ * If empty result was found returned false.
+ * Otherwise prepared current item, and returned true.
+ */
+static bool
+_bt_init_knn_scan(IndexScanDesc scan, OffsetNumber offnum)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		rstate = &so->state; /* right (forward) main scan state */
+	BTScanState		lstate; /* additional left (backward) KNN scan state */
+	Buffer			buf = rstate->currPos.buf;
+	bool			left,
+					right;
+
+	lstate = _bt_alloc_knn_scan(scan);
+
+	/* Bump pin and lock count before BTScanPosData copying. */
+	IncrBufferRefCount(buf);
+	LockBuffer(buf, BT_READ);
+
+	memcpy(&lstate->currPos, &rstate->currPos, sizeof(BTScanPosData));
+	lstate->currPos.moreLeft = true;
+	lstate->currPos.moreRight = false;
+
+	/* Load first pages from the both scans. */
+	right = _bt_load_first_page(scan, rstate, ForwardScanDirection, offnum);
+	left = _bt_load_first_page(scan, lstate, BackwardScanDirection,
+							   OffsetNumberPrev(offnum));
+
+	return _bt_start_knn_scan(scan, left, right);
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -654,6 +807,15 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	if (!so->qual_ok)
 		return false;
 
+	if (scan->numberOfOrderBys > 0)
+	{
+		if (so->useBidirectionalKnnScan)
+			_bt_init_distance_comparison(scan);
+		else if (so->scanDirection != NoMovementScanDirection)
+			/* use selected KNN scan direction */
+			dir = so->scanDirection;
+	}
+
 	/*
 	 * For parallel scans, get the starting page from shared state. If the
 	 * scan has not started, proceed to find out first leaf page in the usual
@@ -662,19 +824,50 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 */
 	if (scan->parallel_scan != NULL)
 	{
-		status = _bt_parallel_seize(scan, &blkno);
+		status = _bt_parallel_seize(scan, &so->state, &blkno);
 		if (!status)
 			return false;
-		else if (blkno == P_NONE)
-		{
-			_bt_parallel_done(scan);
-			return false;
-		}
 		else if (blkno != InvalidBlockNumber)
 		{
-			if (!_bt_parallel_readpage(scan, blkno, dir))
-				return false;
-			goto readcomplete;
+			bool		knn = so->useBidirectionalKnnScan;
+			bool		right;
+			bool		left;
+
+			if (knn)
+				_bt_alloc_knn_scan(scan);
+
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, &so->state);
+				right = false;
+			}
+			else
+				right = _bt_parallel_readpage(scan, &so->state, blkno,
+											  knn ? ForwardScanDirection : dir);
+
+			if (!knn)
+				return right && _bt_return_current_item(scan, &so->state);
+
+			/* seize additional backward KNN scan */
+			left = _bt_parallel_seize(scan, so->knnState, &blkno);
+
+			if (left)
+			{
+				if (blkno == P_NONE)
+				{
+					_bt_parallel_done(scan, so->knnState);
+					left = false;
+				}
+				else
+				{
+					/* backward scan should be already initialized */
+					Assert(blkno != InvalidBlockNumber);
+					left = _bt_parallel_readpage(scan, so->knnState, blkno,
+												 BackwardScanDirection);
+				}
+			}
+
+			return _bt_start_knn_scan(scan, left, right);
 		}
 	}
 
@@ -724,7 +917,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 * storing their addresses into the local startKeys[] array.
 	 *----------
 	 */
+
 	strat_total = BTEqualStrategyNumber;
+
 	if (so->numberOfKeys > 0)
 	{
 		AttrNumber	curattr;
@@ -749,6 +944,10 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 */
 		for (cur = so->keyData, i = 0;; cur++, i++)
 		{
+			if (so->useBidirectionalKnnScan &&
+				curattr >= scan->orderByData->sk_attno)
+				break;
+
 			if (i >= so->numberOfKeys || cur->sk_attno != curattr)
 			{
 				/*
@@ -851,6 +1050,16 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		}
 	}
 
+	if (so->useBidirectionalKnnScan)
+	{
+		Assert(strat_total == BTEqualStrategyNumber);
+		strat_total = BtreeKNNSearchStrategyNumber;
+
+		(void) _bt_init_knn_start_keys(scan, &startKeys[keysCount],
+									   &notnullkeys[keysCount]);
+		keysCount++;
+	}
+
 	/*
 	 * If we found no usable boundary keys, we have to start from one end of
 	 * the tree.  Walk down that edge to the first or last key, and scan from
@@ -865,7 +1074,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		if (!match)
 		{
 			/* No match, so mark (parallel) scan finished */
-			_bt_parallel_done(scan);
+			_bt_parallel_done(scan, NULL);
 		}
 
 		return match;
@@ -900,7 +1109,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			Assert(subkey->sk_flags & SK_ROW_MEMBER);
 			if (subkey->sk_flags & SK_ISNULL)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, NULL);
 				return false;
 			}
 			memcpy(scankeys + i, subkey, sizeof(ScanKeyData));
@@ -1080,6 +1289,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			break;
 
 		case BTGreaterEqualStrategyNumber:
+		case BtreeKNNSearchStrategyNumber:
 
 			/*
 			 * Find first item >= scankey.  (This is only used for forward
@@ -1127,7 +1337,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 * mark parallel scan as done, so that all the workers can finish
 		 * their scan
 		 */
-		_bt_parallel_done(scan);
+		_bt_parallel_done(scan, NULL);
 		BTScanPosInvalidate(*currPos);
 
 		return false;
@@ -1166,17 +1376,21 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	Assert(!BTScanPosIsValid(*currPos));
 	currPos->buf = buf;
 
+	if (strat_total == BtreeKNNSearchStrategyNumber)
+		return _bt_init_knn_scan(scan, offnum);
+
 	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
-		return false;
+		return false; /* empty result */
 
-readcomplete:
 	/* OK, currPos->itemIndex says what to return */
 	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
- *	Advance to next tuple on current page; or if there's no more,
- *	try to step to the next page with data.
+ *	_bt_next_item() -- Advance to next tuple on current page;
+ *		or if there's no more, try to step to the next page with data.
+ *
+ *	If there are no more matching records in the given direction
  */
 static bool
 _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
@@ -1196,6 +1410,51 @@ _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 }
 
 /*
+ *	_bt_next_nearest() -- Return next nearest item from bidirectional KNN scan.
+ */
+static bool
+_bt_next_nearest(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState rstate = &so->state;
+	BTScanState lstate = so->knnState;
+	bool		right = BTScanPosIsValid(rstate->currPos);
+	bool		left = BTScanPosIsValid(lstate->currPos);
+	bool		advanceRight;
+
+	if (right && left)
+		advanceRight = so->currRightIsNearest;
+	else if (right)
+		advanceRight = true;
+	else if (left)
+		advanceRight = false;
+	else
+		return false; /* end of the scan */
+
+	if (advanceRight)
+		right = _bt_next_item(scan, rstate, ForwardScanDirection);
+	else
+		left = _bt_next_item(scan, lstate, BackwardScanDirection);
+
+	if (!left && !right)
+		return false; /* end of the scan */
+
+	if (left && right)
+	{
+		/*
+		 * If there are items in both scans we must recalculate distance
+		 * in the advanced scan.
+		 */
+		_bt_calc_current_dist(scan, advanceRight ? rstate : lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+		right = so->currRightIsNearest;
+	}
+
+	/* return nearest item */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
  *	_bt_next() -- Get the next item in a scan.
  *
  *		On entry, so->currPos describes the current page, which may be pinned
@@ -1214,6 +1473,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
+	if (so->knnState)
+		/* return next neareset item from KNN scan */
+		return _bt_next_nearest(scan);
+
 	if (!_bt_next_item(scan, &so->state, dir))
 		return false;
 
@@ -1266,9 +1529,9 @@ _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 	if (scan->parallel_scan)
 	{
 		if (ScanDirectionIsForward(dir))
-			_bt_parallel_release(scan, opaque->btpo_next);
+			_bt_parallel_release(scan, state, opaque->btpo_next);
 		else
-			_bt_parallel_release(scan, BufferGetBlockNumber(pos->buf));
+			_bt_parallel_release(scan, state, BufferGetBlockNumber(pos->buf));
 	}
 
 	minoff = P_FIRSTDATAKEY(opaque);
@@ -1442,7 +1705,7 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the next block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno);
+			status = _bt_parallel_seize(scan, state, &blkno);
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
@@ -1474,13 +1737,19 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the current block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno);
+			status = _bt_parallel_seize(scan, state, &blkno);
 			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, state);
+				BTScanPosInvalidate(*currPos);
+				return false;
+			}
 		}
 		else
 		{
@@ -1530,7 +1799,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			 */
 			if (blkno == P_NONE || !currPos->moreRight)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1553,14 +1822,14 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, opaque->btpo_next);
+				_bt_parallel_release(scan, state, opaque->btpo_next);
 			}
 
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno);
+				status = _bt_parallel_seize(scan, state, &blkno);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -1619,7 +1888,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (!currPos->moreLeft)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1630,7 +1899,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			/* if we're physically at end of index, return failure */
 			if (currPos->buf == InvalidBuffer)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1654,7 +1923,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
+				_bt_parallel_release(scan, state, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -1666,7 +1935,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno);
+				status = _bt_parallel_seize(scan, state, &blkno);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -1687,17 +1956,16 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
  * indicate success.
  */
 static bool
-_bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_parallel_readpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+					  ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	_bt_initialize_more_data(state, dir);
 
-	_bt_initialize_more_data(&so->state, dir);
-
-	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
 
 	return true;
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index e548354..5b9294b 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -20,16 +20,21 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "catalog/pg_amop.h"
 #include "miscadmin.h"
 #include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 
 
 typedef struct BTSortArrayContext
 {
 	FmgrInfo	flinfo;
+	FmgrInfo	distflinfo;
+	FmgrInfo	distcmpflinfo;
+	ScanKey		distkey;
 	Oid			collation;
 	bool		reverse;
 } BTSortArrayContext;
@@ -49,6 +54,11 @@ static void _bt_mark_scankey_required(ScanKey skey);
 static bool _bt_check_rowcompare(ScanKey skey,
 					 IndexTuple tuple, TupleDesc tupdesc,
 					 ScanDirection dir, bool *continuescan);
+static inline StrategyNumber _bt_select_knn_strategy_for_key(IndexScanDesc scan,
+								ScanKey cond);
+static void _bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype,
+						  FmgrInfo *finfo, int16 *typlen, bool *typbyval);
+
 
 
 /*
@@ -445,6 +455,7 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 {
 	Relation	rel = scan->indexRelation;
 	Oid			elemtype;
+	Oid			opfamily;
 	RegProcedure cmp_proc;
 	BTSortArrayContext cxt;
 	int			last_non_dup;
@@ -462,6 +473,54 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 	if (elemtype == InvalidOid)
 		elemtype = rel->rd_opcintype[skey->sk_attno - 1];
 
+	opfamily = rel->rd_opfamily[skey->sk_attno - 1];
+
+	if (scan->numberOfOrderBys <= 0 ||
+		scan->orderByData[0].sk_attno != skey->sk_attno)
+	{
+		cxt.distkey = NULL;
+		cxt.reverse = reverse;
+	}
+	else
+	{
+		/* Init procedures for distance calculation and comparison. */
+		ScanKey		distkey = &scan->orderByData[0];
+		ScanKeyData	distkey2;
+		Oid			disttype = distkey->sk_subtype;
+		Oid			distopr;
+		RegProcedure distproc;
+
+		if (!OidIsValid(disttype))
+			disttype = rel->rd_opcintype[skey->sk_attno - 1];
+
+		/* Lookup distance operator in index column's operator family. */
+		distopr = get_opfamily_member(opfamily,
+									  elemtype,
+									  disttype,
+									  distkey->sk_strategy);
+
+		if (!OidIsValid(distopr))
+			elog(ERROR, "missing operator (%u,%u) for strategy %d in opfamily %u",
+				 elemtype, disttype, BtreeKNNSearchStrategyNumber, opfamily);
+
+		distproc = get_opcode(distopr);
+
+		if (!RegProcedureIsValid(distproc))
+			elog(ERROR, "missing code for operator %u", distopr);
+
+		fmgr_info(distproc, &cxt.distflinfo);
+
+		distkey2 = *distkey;
+		fmgr_info_copy(&distkey2.sk_func, &cxt.distflinfo, CurrentMemoryContext);
+		distkey2.sk_subtype = disttype;
+
+		_bt_get_distance_cmp_proc(&distkey2, opfamily, elemtype,
+								  &cxt.distcmpflinfo, NULL, NULL);
+
+		cxt.distkey = distkey;
+		cxt.reverse = false;	/* supported only ascending ordering */
+	}
+
 	/*
 	 * Look up the appropriate comparison function in the opfamily.
 	 *
@@ -470,19 +529,17 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 	 * non-cross-type support functions for any datatype that it supports at
 	 * all.
 	 */
-	cmp_proc = get_opfamily_proc(rel->rd_opfamily[skey->sk_attno - 1],
+	cmp_proc = get_opfamily_proc(opfamily,
 								 elemtype,
 								 elemtype,
 								 BTORDER_PROC);
 	if (!RegProcedureIsValid(cmp_proc))
 		elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
-			 BTORDER_PROC, elemtype, elemtype,
-			 rel->rd_opfamily[skey->sk_attno - 1]);
+			 BTORDER_PROC, elemtype, elemtype, opfamily);
 
 	/* Sort the array elements */
 	fmgr_info(cmp_proc, &cxt.flinfo);
 	cxt.collation = skey->sk_collation;
-	cxt.reverse = reverse;
 	qsort_arg((void *) elems, nelems, sizeof(Datum),
 			  _bt_compare_array_elements, (void *) &cxt);
 
@@ -514,6 +571,23 @@ _bt_compare_array_elements(const void *a, const void *b, void *arg)
 	BTSortArrayContext *cxt = (BTSortArrayContext *) arg;
 	int32		compare;
 
+	if (cxt->distkey)
+	{
+		Datum		dista = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  da,
+											  cxt->distkey->sk_argument);
+		Datum		distb = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  db,
+											  cxt->distkey->sk_argument);
+		bool		cmp = DatumGetBool(FunctionCall2Coll(&cxt->distcmpflinfo,
+														 cxt->collation,
+														 dista,
+														 distb));
+		return cmp ? -1 : 1;
+	}
+
 	compare = DatumGetInt32(FunctionCall2Coll(&cxt->flinfo,
 											  cxt->collation,
 											  da, db));
@@ -667,6 +741,66 @@ _bt_restore_array_keys(IndexScanDesc scan)
 	}
 }
 
+/*
+ * _bt_emit_scan_key() -- Emit one prepared scan key
+ *
+ * Push the scan key into the so->keyData[] array, and then mark it if it is
+ * required.  Also update selected kNN strategy.
+ */
+static void
+_bt_emit_scan_key(IndexScanDesc scan, ScanKey skey, int numberOfEqualCols)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	ScanKey		outkey = &so->keyData[so->numberOfKeys++];
+
+	memcpy(outkey, skey, sizeof(ScanKeyData));
+
+	/*
+	 * We can mark the qual as required (possibly only in one direction) if all
+	 * attrs before this one had "=".
+	 */
+	if (outkey->sk_attno - 1 == numberOfEqualCols)
+		_bt_mark_scankey_required(outkey);
+
+	/* Update kNN strategy if it is not already selected. */
+	if (so->useBidirectionalKnnScan)
+	{
+		switch (_bt_select_knn_strategy_for_key(scan, outkey))
+		{
+			case BTLessStrategyNumber:
+			case BTLessEqualStrategyNumber:
+				/*
+				 * Ordering key argument is greater than all values in scan
+				 * range, select backward scan direction.
+				 */
+				so->scanDirection = BackwardScanDirection;
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BTEqualStrategyNumber:
+				/* Use default unidirectional scan direction. */
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+				/*
+				 * Ordering key argument is lesser than all values in scan
+				 * range, select forward scan direction.
+				 */
+				so->scanDirection = ForwardScanDirection;
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BtreeKNNSearchStrategyNumber:
+				/*
+				 * Ordering key argument falls into scan range,
+				 * keep using bidirectional scan.
+				 */
+				break;
+		}
+	}
+}
 
 /*
  *	_bt_preprocess_keys() -- Preprocess scan keys
@@ -758,10 +892,8 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	int			numberOfKeys = scan->numberOfKeys;
 	int16	   *indoption = scan->indexRelation->rd_indoption;
-	int			new_numberOfKeys;
 	int			numberOfEqualCols;
 	ScanKey		inkeys;
-	ScanKey		outkeys;
 	ScanKey		cur;
 	ScanKey		xform[BTMaxStrategyNumber];
 	bool		test_result;
@@ -769,6 +901,24 @@ _bt_preprocess_keys(IndexScanDesc scan)
 				j;
 	AttrNumber	attno;
 
+	if (scan->numberOfOrderBys > 0)
+	{
+		ScanKey		ord = scan->orderByData;
+
+		if (scan->numberOfOrderBys > 1)
+			/* it should not happen, see btmatchorderby() */
+			elog(ERROR, "only one btree ordering operator is supported");
+
+		Assert(ord->sk_strategy == BtreeKNNSearchStrategyNumber);
+
+		/* use bidirectional kNN scan by default */
+		so->useBidirectionalKnnScan = true;
+	}
+	else
+	{
+		so->useBidirectionalKnnScan = false;
+	}
+
 	/* initialize result variables */
 	so->qual_ok = true;
 	so->numberOfKeys = 0;
@@ -784,7 +934,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	else
 		inkeys = scan->keyData;
 
-	outkeys = so->keyData;
 	cur = &inkeys[0];
 	/* we check that input keys are correctly ordered */
 	if (cur->sk_attno < 1)
@@ -796,18 +945,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 		/* Apply indoption to scankey (might change sk_strategy!) */
 		if (!_bt_fix_scankey_strategy(cur, indoption))
 			so->qual_ok = false;
-		memcpy(outkeys, cur, sizeof(ScanKeyData));
-		so->numberOfKeys = 1;
-		/* We can mark the qual as required if it's for first index col */
-		if (cur->sk_attno == 1)
-			_bt_mark_scankey_required(outkeys);
+
+		_bt_emit_scan_key(scan, cur, 0);
 		return;
 	}
 
 	/*
 	 * Otherwise, do the full set of pushups.
 	 */
-	new_numberOfKeys = 0;
 	numberOfEqualCols = 0;
 
 	/*
@@ -931,20 +1076,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 			}
 
 			/*
-			 * Emit the cleaned-up keys into the outkeys[] array, and then
+			 * Emit the cleaned-up keys into the so->keyData[] array, and then
 			 * mark them if they are required.  They are required (possibly
 			 * only in one direction) if all attrs before this one had "=".
 			 */
 			for (j = BTMaxStrategyNumber; --j >= 0;)
 			{
 				if (xform[j])
-				{
-					ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-					memcpy(outkey, xform[j], sizeof(ScanKeyData));
-					if (priorNumberOfEqualCols == attno - 1)
-						_bt_mark_scankey_required(outkey);
-				}
+					_bt_emit_scan_key(scan, xform[j], priorNumberOfEqualCols);
 			}
 
 			/*
@@ -964,17 +1103,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 		/* if row comparison, push it directly to the output array */
 		if (cur->sk_flags & SK_ROW_HEADER)
 		{
-			ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-			memcpy(outkey, cur, sizeof(ScanKeyData));
-			if (numberOfEqualCols == attno - 1)
-				_bt_mark_scankey_required(outkey);
+			_bt_emit_scan_key(scan, cur, numberOfEqualCols);
 
 			/*
 			 * We don't support RowCompare using equality; such a qual would
 			 * mess up the numberOfEqualCols tracking.
 			 */
 			Assert(j != (BTEqualStrategyNumber - 1));
+
 			continue;
 		}
 
@@ -1007,16 +1143,10 @@ _bt_preprocess_keys(IndexScanDesc scan)
 				 * previous one in xform[j] and push this one directly to the
 				 * output array.
 				 */
-				ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-				memcpy(outkey, cur, sizeof(ScanKeyData));
-				if (numberOfEqualCols == attno - 1)
-					_bt_mark_scankey_required(outkey);
+				_bt_emit_scan_key(scan, cur, numberOfEqualCols);
 			}
 		}
 	}
-
-	so->numberOfKeys = new_numberOfKeys;
 }
 
 /*
@@ -2075,6 +2205,39 @@ btproperty(Oid index_oid, int attno,
 			*res = true;
 			return true;
 
+		case AMPROP_DISTANCE_ORDERABLE:
+			{
+				Oid			opclass,
+							opfamily,
+							opcindtype;
+
+				/* answer only for columns, not AM or whole index */
+				if (attno == 0)
+					return false;
+
+				opclass = get_index_column_opclass(index_oid, attno);
+
+				if (!OidIsValid(opclass))
+				{
+					*res = false;	/* non-key attribute */
+					return true;
+				}
+
+				if (!get_opclass_opfamily_and_input_type(opclass,
+													 &opfamily, &opcindtype))
+				{
+					*isnull = true;
+					return true;
+				}
+
+				*res = SearchSysCacheExists(AMOPSTRATEGY,
+											ObjectIdGetDatum(opfamily),
+											ObjectIdGetDatum(opcindtype),
+											ObjectIdGetDatum(opcindtype),
+								   Int16GetDatum(BtreeKNNSearchStrategyNumber));
+				return true;
+			}
+
 		default:
 			return false;		/* punt to generic code */
 	}
@@ -2223,3 +2386,212 @@ _bt_allocate_tuple_workspaces(BTScanState state)
 	state->currTuples = (char *) palloc(BLCKSZ * 2);
 	state->markTuples = state->currTuples + BLCKSZ;
 }
+
+static bool
+_bt_compare_row_key_with_ordering_key(ScanKey row, ScanKey ord, bool *result)
+{
+	ScanKey		subkey = (ScanKey) DatumGetPointer(row->sk_argument);
+	int32		cmpresult;
+
+	Assert(subkey->sk_attno == 1);
+	Assert(subkey->sk_flags & SK_ROW_MEMBER);
+
+	if (subkey->sk_flags & SK_ISNULL)
+		return false;
+
+	/* Perform the test --- three-way comparison not bool operator */
+	cmpresult = DatumGetInt32(FunctionCall2Coll(&subkey->sk_func,
+												subkey->sk_collation,
+												ord->sk_argument,
+												subkey->sk_argument));
+
+	if (subkey->sk_flags & SK_BT_DESC)
+		cmpresult = -cmpresult;
+
+	/*
+	 * At this point cmpresult indicates the overall result of the row
+	 * comparison, and subkey points to the deciding column (or the last
+	 * column if the result is "=").
+	 */
+	switch (subkey->sk_strategy)
+	{
+			/* EQ and NE cases aren't allowed here */
+		case BTLessStrategyNumber:
+			*result = cmpresult < 0;
+			break;
+		case BTLessEqualStrategyNumber:
+			*result = cmpresult <= 0;
+			break;
+		case BTGreaterEqualStrategyNumber:
+			*result = cmpresult >= 0;
+			break;
+		case BTGreaterStrategyNumber:
+			*result = cmpresult > 0;
+			break;
+		default:
+			elog(ERROR, "unrecognized RowCompareType: %d",
+				 (int) subkey->sk_strategy);
+			*result = false;	/* keep compiler quiet */
+	}
+
+	return true;
+}
+
+/*
+ * _bt_select_knn_strategy_for_key() -- Determine which kNN scan strategy to use:
+ *		bidirectional or unidirectional.  We are checking here if the
+ *		ordering scankey argument falls into the scan range: if it falls
+ *		we must use bidirectional scan, otherwise we use unidirectional.
+ *
+ *	Returns BtreeKNNSearchStrategyNumber for bidirectional scan or
+ *	strategy number of non-matched scankey for unidirectional.
+ */
+static inline StrategyNumber
+_bt_select_knn_strategy_for_key(IndexScanDesc scan, ScanKey cond)
+{
+	ScanKey		ord = scan->orderByData;
+	bool		result;
+
+	/* only interesting in the index attribute that is ordered by a distance */
+	if (cond->sk_attno != ord->sk_attno)
+		return BtreeKNNSearchStrategyNumber;
+
+	if (cond->sk_strategy == BTEqualStrategyNumber)
+		/* always use simple unidirectional scan for equals operators */
+		return BTEqualStrategyNumber;
+
+	if (cond->sk_flags & SK_ROW_HEADER)
+	{
+		if (!_bt_compare_row_key_with_ordering_key(cond, ord, &result))
+			return BTEqualStrategyNumber; /* ROW(fist_index_attr, ...) IS NULL */
+	}
+	else
+	{
+		if (!_bt_compare_scankey_args(scan, cond, ord, cond, &result))
+			elog(ERROR, "could not compare ordering key");
+	}
+
+	if (!result)
+		/*
+		 * Ordering scankey argument is out of scan range,
+		 * use unidirectional scan.
+		 */
+		return cond->sk_strategy;
+
+	return BtreeKNNSearchStrategyNumber;
+}
+
+int
+_bt_init_knn_start_keys(IndexScanDesc scan, ScanKey *startKeys, ScanKey bufKeys)
+{
+	ScanKey		ord = scan->orderByData;
+	int			indopt = scan->indexRelation->rd_indoption[ord->sk_attno - 1];
+	int			flags = (indopt << SK_BT_INDOPTION_SHIFT) |
+						SK_ORDER_BY |
+						SK_SEARCHNULL; /* only for invalid procedure oid, see
+										* assert in ScanKeyEntryInitialize() */
+	int			keysCount = 0;
+
+	/* Init btree search key with ordering key argument. */
+	ScanKeyEntryInitialize(&bufKeys[0],
+						   flags,
+						   ord->sk_attno,
+						   BtreeKNNSearchStrategyNumber,
+						   ord->sk_subtype,
+						   ord->sk_collation,
+						   InvalidOid,
+						   ord->sk_argument);
+
+	startKeys[keysCount++] = &bufKeys[0];
+
+	return keysCount;
+}
+
+static Oid
+_bt_get_sortfamily_for_opfamily_op(Oid opfamily, Oid lefttype, Oid righttype,
+								   StrategyNumber strategy)
+{
+	HeapTuple	tp;
+	Form_pg_amop amop_tup;
+	Oid			sortfamily;
+
+	tp = SearchSysCache4(AMOPSTRATEGY,
+						 ObjectIdGetDatum(opfamily),
+						 ObjectIdGetDatum(lefttype),
+						 ObjectIdGetDatum(righttype),
+						 Int16GetDatum(strategy));
+	if (!HeapTupleIsValid(tp))
+		return InvalidOid;
+	amop_tup = (Form_pg_amop) GETSTRUCT(tp);
+	sortfamily = amop_tup->amopsortfamily;
+	ReleaseSysCache(tp);
+
+	return sortfamily;
+}
+
+/*
+ * _bt_get_distance_cmp_proc() -- Init procedure for comparsion of distances
+ *		between "leftargtype" and "distkey".
+ */
+static void
+_bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype,
+						  FmgrInfo *finfo, int16 *typlen, bool *typbyval)
+{
+	RegProcedure opcode;
+	Oid			sortfamily;
+	Oid			opno;
+	Oid			distanceType;
+
+	distanceType = get_func_rettype(distkey->sk_func.fn_oid);
+
+	sortfamily = _bt_get_sortfamily_for_opfamily_op(opfamily, leftargtype,
+													distkey->sk_subtype,
+													distkey->sk_strategy);
+
+	if (!OidIsValid(sortfamily))
+		elog(ERROR, "could not find sort family for btree ordering operator");
+
+	opno = get_opfamily_member(sortfamily,
+							   distanceType,
+							   distanceType,
+							   BTLessEqualStrategyNumber);
+
+	if (!OidIsValid(opno))
+		elog(ERROR, "could not find operator for btree distance comparison");
+
+	opcode = get_opcode(opno);
+
+	if (!RegProcedureIsValid(opcode))
+		elog(ERROR,
+			"could not find procedure for btree distance comparison operator");
+
+	fmgr_info(opcode, finfo);
+
+	if (typlen)
+		get_typlenbyval(distanceType, typlen, typbyval);
+}
+
+/*
+ *  _bt_init_distance_comparison() -- Init distance typlen/typbyval and its
+ *  	comparison procedure.
+ */
+void
+_bt_init_distance_comparison(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	Relation	rel = scan->indexRelation;
+	ScanKey		ord = scan->orderByData;
+
+	_bt_get_distance_cmp_proc(ord,
+							  rel->rd_opfamily[ord->sk_attno - 1],
+							  rel->rd_opcintype[ord->sk_attno - 1],
+							  &so->distanceCmpProc,
+							  &so->distanceTypeLen,
+							  &so->distanceTypeByVal);
+
+	if (!so->distanceTypeByVal)
+	{
+		so->state.currDistance = PointerGetDatum(NULL);
+		so->state.markDistance = PointerGetDatum(NULL);
+	}
+}
diff --git a/src/backend/access/nbtree/nbtvalidate.c b/src/backend/access/nbtree/nbtvalidate.c
index 0148ea7..4558fd3 100644
--- a/src/backend/access/nbtree/nbtvalidate.c
+++ b/src/backend/access/nbtree/nbtvalidate.c
@@ -22,9 +22,17 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_type.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/regproc.h"
 #include "utils/syscache.h"
 
+#define BTRequiredOperatorSet \
+		((1 << BTLessStrategyNumber) | \
+		 (1 << BTLessEqualStrategyNumber) | \
+		 (1 << BTEqualStrategyNumber) | \
+		 (1 << BTGreaterEqualStrategyNumber) | \
+		 (1 << BTGreaterStrategyNumber))
+
 
 /*
  * Validator for a btree opclass.
@@ -132,10 +140,11 @@ btvalidate(Oid opclassoid)
 	{
 		HeapTuple	oprtup = &oprlist->members[i]->tuple;
 		Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
+		Oid			op_rettype;
 
 		/* Check that only allowed strategy numbers exist */
 		if (oprform->amopstrategy < 1 ||
-			oprform->amopstrategy > BTMaxStrategyNumber)
+			oprform->amopstrategy > BtreeMaxStrategyNumber)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -146,20 +155,29 @@ btvalidate(Oid opclassoid)
 			result = false;
 		}
 
-		/* btree doesn't support ORDER BY operators */
-		if (oprform->amoppurpose != AMOP_SEARCH ||
-			OidIsValid(oprform->amopsortfamily))
+		/* btree supports ORDER BY operators */
+		if (oprform->amoppurpose != AMOP_SEARCH)
 		{
-			ereport(INFO,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
-							opfamilyname, "btree",
-							format_operator(oprform->amopopr))));
-			result = false;
+			/* ... and operator result must match the claimed btree opfamily */
+			op_rettype = get_op_rettype(oprform->amopopr);
+			if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype))
+			{
+				ereport(INFO,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
+								opfamilyname, "btree",
+								format_operator(oprform->amopopr))));
+				result = false;
+			}
+		}
+		else
+		{
+			/* Search operators must always return bool */
+			op_rettype = BOOLOID;
 		}
 
 		/* Check operator signature --- same for all btree strategies */
-		if (!check_amop_signature(oprform->amopopr, BOOLOID,
+		if (!check_amop_signature(oprform->amopopr, op_rettype,
 								  oprform->amoplefttype,
 								  oprform->amoprighttype))
 		{
@@ -214,12 +232,8 @@ btvalidate(Oid opclassoid)
 		 * or support functions for this datatype pair.  The only things
 		 * considered optional are the sortsupport and in_range functions.
 		 */
-		if (thisgroup->operatorset !=
-			((1 << BTLessStrategyNumber) |
-			 (1 << BTLessEqualStrategyNumber) |
-			 (1 << BTEqualStrategyNumber) |
-			 (1 << BTGreaterEqualStrategyNumber) |
-			 (1 << BTGreaterStrategyNumber)))
+		if ((thisgroup->operatorset & BTRequiredOperatorSet) !=
+			BTRequiredOperatorSet)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 6cf0b0d..279d8ba 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -990,6 +990,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	 * if we are only trying to build bitmap indexscans, nor if we have to
 	 * assume the scan is unordered.
 	 */
+	useful_pathkeys = NIL;
+	orderbyclauses = NIL;
+	orderbyclausecols = NIL;
+
 	pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN &&
 								!found_lower_saop_clause &&
 								has_useful_pathkeys(root, rel));
@@ -1000,10 +1004,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 											  ForwardScanDirection);
 		useful_pathkeys = truncate_useless_pathkeys(root, rel,
 													index_pathkeys);
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
 	}
-	else if (index->ammatchorderby && pathkeys_possibly_useful)
+
+	if (useful_pathkeys == NIL &&
+		index->ammatchorderby && pathkeys_possibly_useful)
 	{
 		/* see if we can generate ordering operators for query_pathkeys */
 		match_pathkeys_to_index(index, root->query_pathkeys,
@@ -1015,12 +1019,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		else
 			useful_pathkeys = NIL;
 	}
-	else
-	{
-		useful_pathkeys = NIL;
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
-	}
 
 	/*
 	 * 3. Check if an index-only scan is possible.  If we're not building
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index d78df29..4f98e91 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -459,6 +459,12 @@ typedef struct BTScanStateData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+
+	/* KNN-search fields: */
+	Datum		currDistance;	/* current distance */
+	Datum		markDistance;	/* marked distance */
+	bool		currIsNull;		/* current item is NULL */
+	bool		markIsNull;		/* marked item is NULL */
 } BTScanStateData;
 
 typedef BTScanStateData *BTScanState;
@@ -479,7 +485,18 @@ typedef struct BTScanOpaqueData
 	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
 	MemoryContext arrayContext; /* scan-lifespan context for array data */
 
-	BTScanStateData	state;
+	BTScanStateData state;		/* main scan state */
+
+	/* kNN-search fields: */
+	BTScanState knnState;		/* optional scan state for kNN search */
+	bool		useBidirectionalKnnScan;	/* use bidirectional kNN scan? */
+	ScanDirection scanDirection;	/* selected scan direction for
+									 * unidirectional kNN scan */
+	FmgrInfo	distanceCmpProc;	/* distance comparison procedure */
+	int16		distanceTypeLen;	/* distance typlen */
+	bool		distanceTypeByVal;	/* distance typebyval */
+	bool		currRightIsNearest; /* current right item is nearest */
+	bool		markRightIsNearest; /* marked right item is nearest */
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -525,11 +542,12 @@ extern bool btcanreturn(Relation index, int attno);
 /*
  * prototypes for internal functions in nbtree.c
  */
-extern bool _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno);
-extern void _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page);
-extern void _bt_parallel_done(IndexScanDesc scan);
+extern bool _bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno);
+extern void _bt_parallel_release(IndexScanDesc scan, BTScanState state, BlockNumber scan_page);
+extern void _bt_parallel_done(IndexScanDesc scan, BTScanState state);
 extern void _bt_parallel_advance_array_keys(IndexScanDesc scan);
 
+
 /*
  * prototypes for functions in nbtinsert.c
  */
@@ -610,6 +628,9 @@ extern bool btproperty(Oid index_oid, int attno,
 extern IndexTuple _bt_nonkey_truncate(Relation rel, IndexTuple itup);
 extern bool _bt_check_natts(Relation rel, Page page, OffsetNumber offnum);
 extern void _bt_allocate_tuple_workspaces(BTScanState state);
+extern void _bt_init_distance_comparison(IndexScanDesc scan);
+extern int _bt_init_knn_start_keys(IndexScanDesc scan, ScanKey *startKeys,
+						ScanKey bufKeys);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/include/access/stratnum.h b/src/include/access/stratnum.h
index 8fdba28..8087ffe 100644
--- a/src/include/access/stratnum.h
+++ b/src/include/access/stratnum.h
@@ -32,7 +32,10 @@ typedef uint16 StrategyNumber;
 #define BTGreaterEqualStrategyNumber	4
 #define BTGreaterStrategyNumber			5
 
-#define BTMaxStrategyNumber				5
+#define BTMaxStrategyNumber				5		/* number of canonical B-tree strategies */
+
+#define BtreeKNNSearchStrategyNumber	6		/* for <-> (distance) */
+#define BtreeMaxStrategyNumber			6		/* number of extended B-tree strategies */
 
 
 /*
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index 6faa9d7..c75ef39 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -347,10 +347,10 @@ ROLLBACK;
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
 ERROR:  access method "invalid_index_method" does not exist
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 6, must be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 0, must be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 7, must be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 0, must be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ERROR:  operator argument types must be specified in ALTER OPERATOR FAMILY
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -397,11 +397,12 @@ DROP OPERATOR FAMILY alt_opf8 USING btree;
 CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
-ERROR:  access method "btree" does not support ordering operators
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
 ALTER OPERATOR FAMILY alt_opf11 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql
index 84fd900..73e6e206 100644
--- a/src/test/regress/sql/alter_generic.sql
+++ b/src/test/regress/sql/alter_generic.sql
@@ -295,8 +295,8 @@ ROLLBACK;
 -- Should fail. Invalid values for ALTER OPERATOR FAMILY .. ADD / DROP
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 6 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -340,10 +340,12 @@ CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
 
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
0005-Add-btree-distance-operators-v07.patchtext/x-patch; name=0005-Add-btree-distance-operators-v07.patchDownload
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 1998171..9203497 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -183,6 +183,24 @@ SELECT * FROM events ORDER BY event_date <-> date '2017-05-05' LIMIT 10;
 </programlisting>
    which finds the ten events closest to a given target date.  The ability
    to do this is again dependent on the particular operator class being used.
+   Built-in B-tree operator classes support distance ordering for the following
+   data types:
+   <simplelist>
+    <member><type>int2</type></member>
+    <member><type>int4</type></member>
+    <member><type>int8</type></member>
+    <member><type>float4</type></member>
+    <member><type>float8</type></member>
+    <member><type>numeric</type></member>
+    <member><type>timestamp with time zone</type></member>
+    <member><type>timestamp without time zone</type></member>
+    <member><type>time with time zone</type></member>
+    <member><type>time without time zone</type></member>
+    <member><type>date</type></member>
+    <member><type>interval</type></member>
+    <member><type>oid</type></member>
+    <member><type>money</type></member>
+   </simplelist>
   </para>
 
   <para>
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index c92e9d5..83073c4 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -30,6 +30,7 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+#define SAMESIGN(a,b)	(((a) < 0) == ((b) < 0))
 
 /*************************************************************************
  * Private routines
@@ -1157,3 +1158,22 @@ int8_cash(PG_FUNCTION_ARGS)
 
 	PG_RETURN_CASH(result);
 }
+
+Datum
+cash_distance(PG_FUNCTION_ARGS)
+{
+	Cash		a = PG_GETARG_CASH(0);
+	Cash		b = PG_GETARG_CASH(1);
+	Cash		r;
+	Cash		ra;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("money out of range")));
+
+	ra = Abs(r);
+
+	PG_RETURN_CASH(ra);
+}
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index cf5a1c6..4492f69 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -563,6 +563,17 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+Datum
+date_distance(PG_FUNCTION_ARGS)
+{
+	/* we assume the difference can't overflow */
+	Datum		diff = DirectFunctionCall2(date_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
+}
+
 /*
  * Internal routines for promoting date to timestamp and timestamp with
  * time zone
@@ -760,6 +771,29 @@ date_cmp_timestamp(PG_FUNCTION_ARGS)
 }
 
 Datum
+date_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+	Timestamp	dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
+Datum
 date_eq_timestamptz(PG_FUNCTION_ARGS)
 {
 	DateADT		dateVal = PG_GETARG_DATEADT(0);
@@ -844,6 +878,30 @@ date_cmp_timestamptz(PG_FUNCTION_ARGS)
 }
 
 Datum
+date_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
+
+
+Datum
 timestamp_eq_date(PG_FUNCTION_ARGS)
 {
 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
@@ -928,6 +986,29 @@ timestamp_cmp_date(PG_FUNCTION_ARGS)
 }
 
 Datum
+timestamp_dist_date(PG_FUNCTION_ARGS)
+{
+	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	Timestamp	dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
+Datum
 timestamptz_eq_date(PG_FUNCTION_ARGS)
 {
 	TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
@@ -1039,6 +1120,28 @@ in_range_date_interval(PG_FUNCTION_ARGS)
 							   BoolGetDatum(less));
 }
 
+Datum
+timestamptz_dist_date(PG_FUNCTION_ARGS)
+{
+	TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	TimestampTz dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
 
 /* Add an interval to a date, giving a new date.
  * Must handle both positive and negative intervals.
@@ -1957,6 +2060,16 @@ time_part(PG_FUNCTION_ARGS)
 	PG_RETURN_FLOAT8(result);
 }
 
+Datum
+time_distance(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(time_mi_time,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
+
 
 /*****************************************************************************
  *	 Time With Time Zone ADT
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 37c202d..d72fbef 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -3726,6 +3726,47 @@ width_bucket_float8(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(result);
 }
 
+Datum
+float4dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float4		r = float4_mi(a, b);
+
+	PG_RETURN_FLOAT4(Abs(r));
+}
+
+Datum
+float8dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
+
+Datum
+float48dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
+Datum
+float84dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
 /* ========== PRIVATE ROUTINES ========== */
 
 #ifndef HAVE_CBRT
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 04825fc..76e92ec 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -1501,3 +1501,54 @@ generate_series_int4_support(PG_FUNCTION_ARGS)
 
 	PG_RETURN_POINTER(ret);
 }
+
+Datum
+int2dist(PG_FUNCTION_ARGS)
+{
+	int16		a = PG_GETARG_INT16(0);
+	int16		b = PG_GETARG_INT16(1);
+	int16		r;
+	int16		ra;
+
+	if (pg_sub_s16_overflow(a, b, &r) ||
+		r == PG_INT16_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("smallint out of range")));
+
+	ra = Abs(r);
+
+	PG_RETURN_INT16(ra);
+}
+
+static int32
+int44_dist(int32 a, int32 b)
+{
+	int32		r;
+
+	if (pg_sub_s32_overflow(a, b, &r) ||
+		r == PG_INT32_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("integer out of range")));
+
+	return Abs(r);
+}
+
+Datum
+int4dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int24dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT16(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int42dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT16(1)));
+}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 0ff9394..2ceb16b 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1446,3 +1446,47 @@ generate_series_int8_support(PG_FUNCTION_ARGS)
 
 	PG_RETURN_POINTER(ret);
 }
+
+static int64
+int88_dist(int64 a, int64 b)
+{
+	int64		r;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("bigint out of range")));
+
+	return Abs(r);
+}
+
+Datum
+int8dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int82dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT16(1)));
+}
+
+Datum
+int84dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT32(1)));
+}
+
+Datum
+int28dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT16(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int48dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT32(0), PG_GETARG_INT64(1)));
+}
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index bb67e01..b8f48d5 100644
--- a/src/backend/utils/adt/oid.c
+++ b/src/backend/utils/adt/oid.c
@@ -469,3 +469,17 @@ oidvectorgt(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(cmp > 0);
 }
+
+Datum
+oiddist(PG_FUNCTION_ARGS)
+{
+	Oid			a = PG_GETARG_OID(0);
+	Oid			b = PG_GETARG_OID(1);
+	Oid			res;
+
+	if (a < b)
+		res = b - a;
+	else
+		res = a - b;
+	PG_RETURN_OID(res);
+}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index e0ef2f7..c5d1042 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -2694,6 +2694,86 @@ timestamp_mi(PG_FUNCTION_ARGS)
 	PG_RETURN_INTERVAL_P(result);
 }
 
+Datum
+timestamp_distance(PG_FUNCTION_ARGS)
+{
+	Timestamp	a = PG_GETARG_TIMESTAMP(0);
+	Timestamp	b = PG_GETARG_TIMESTAMP(1);
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		Interval   *p = palloc(sizeof(Interval));
+
+		p->day = INT_MAX;
+		p->month = INT_MAX;
+		p->time = PG_INT64_MAX;
+		PG_RETURN_INTERVAL_P(p);
+	}
+	else
+		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+												  PG_GETARG_DATUM(0),
+												  PG_GETARG_DATUM(1)));
+	PG_RETURN_INTERVAL_P(abs_interval(r));
+}
+
+Interval *
+timestamp_dist_internal(Timestamp a, Timestamp b)
+{
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		return r;
+	}
+
+	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+											  TimestampGetDatum(a),
+											  TimestampGetDatum(b)));
+
+	return abs_interval(r);
+}
+
+Datum
+timestamptz_distance(PG_FUNCTION_ARGS)
+{
+	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
+	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(a, b));
+}
+
+Datum
+timestamp_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	Timestamp	ts1 = PG_GETARG_TIMESTAMP(0);
+	TimestampTz tstz2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz tstz1;
+
+	tstz1 = timestamp2timestamptz(ts1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+Datum
+timestamptz_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	TimestampTz tstz1 = PG_GETARG_TIMESTAMPTZ(0);
+	Timestamp	ts2 = PG_GETARG_TIMESTAMP(1);
+	TimestampTz tstz2;
+
+	tstz2 = timestamp2timestamptz(ts2);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+
 /*
  *	interval_justify_interval()
  *
@@ -3563,6 +3643,29 @@ interval_avg(PG_FUNCTION_ARGS)
 							   Float8GetDatum((double) N.time));
 }
 
+Interval *
+abs_interval(Interval *a)
+{
+	static Interval zero = {0, 0, 0};
+
+	if (DatumGetBool(DirectFunctionCall2(interval_lt,
+										 IntervalPGetDatum(a),
+										 IntervalPGetDatum(&zero))))
+		a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
+												  IntervalPGetDatum(a)));
+
+	return a;
+}
+
+Datum
+interval_distance(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(interval_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
 
 /* timestamp_age()
  * Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0ab95d8..0e06b04 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -30,6 +30,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int2,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int24
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -47,6 +51,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int2,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int28
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -64,6 +72,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int2,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int4
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -81,6 +93,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int4,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int42
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -98,6 +114,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int4,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int48
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -115,6 +135,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int4,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int8
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -132,6 +156,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int8,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int82
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -149,6 +177,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int8,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int84
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -166,6 +198,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int8,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # btree oid_ops
 
@@ -179,6 +215,10 @@
   amopstrategy => '4', amopopr => '>=(oid,oid)', amopmethod => 'btree' },
 { amopfamily => 'btree/oid_ops', amoplefttype => 'oid', amoprighttype => 'oid',
   amopstrategy => '5', amopopr => '>(oid,oid)', amopmethod => 'btree' },
+{ amopfamily => 'btree/oid_ops', amoplefttype => 'oid',
+  amoprighttype => 'oid', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(oid,oid)', amopmethod => 'btree',
+  amopsortfamily => 'btree/oid_ops' },
 
 # btree tid_ops
 
@@ -229,6 +269,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float4,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float48
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
@@ -246,6 +290,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float4,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # default operators float8
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -263,6 +311,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float8,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float84
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -280,6 +332,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float8,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # btree char_ops
 
@@ -416,6 +472,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(date,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -433,6 +493,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(date,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -450,6 +514,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(date,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -467,6 +535,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamp,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -484,6 +556,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(timestamp,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -501,6 +577,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamp,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -518,6 +598,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -535,6 +619,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'date', amopstrategy => '5',
   amopopr => '>(timestamptz,date)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -552,6 +640,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree time_ops
 
@@ -570,6 +662,10 @@
 { amopfamily => 'btree/time_ops', amoplefttype => 'time',
   amoprighttype => 'time', amopstrategy => '5', amopopr => '>(time,time)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/time_ops', amoplefttype => 'time',
+  amoprighttype => 'time', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(time,time)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree timetz_ops
 
@@ -606,6 +702,10 @@
 { amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
   amoprighttype => 'interval', amopstrategy => '5',
   amopopr => '>(interval,interval)', amopmethod => 'btree' },
+{ amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
+  amoprighttype => 'interval', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(interval,interval)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree macaddr
 
@@ -781,6 +881,10 @@
 { amopfamily => 'btree/money_ops', amoplefttype => 'money',
   amoprighttype => 'money', amopstrategy => '5', amopopr => '>(money,money)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/money_ops', amoplefttype => 'money',
+  amoprighttype => 'money', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(money,money)', amopmethod => 'btree',
+  amopsortfamily => 'btree/money_ops' },
 
 # btree array_ops
 
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 06aec07..914ca76 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2851,6 +2851,114 @@
   oprname => '-', oprleft => 'pg_lsn', oprright => 'pg_lsn',
   oprresult => 'numeric', oprcode => 'pg_lsn_mi' },
 
+# distance operators
+{ oid => '4217', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int2', oprresult => 'int2', oprcom => '<->(int2,int2)',
+  oprcode => 'int2dist'},
+{ oid => '4218', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int4)',
+  oprcode => 'int4dist'},
+{ oid => '4219', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int8)',
+  oprcode => 'int8dist'},
+{ oid => '4220', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'oid',
+  oprright => 'oid', oprresult => 'oid', oprcom => '<->(oid,oid)',
+  oprcode => 'oiddist'},
+{ oid => '4221', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float4', oprresult => 'float4', oprcom => '<->(float4,float4)',
+  oprcode => 'float4dist'},
+{ oid => '4222', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float8)',
+  oprcode => 'float8dist'},
+{ oid => '4223', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'money',
+  oprright => 'money', oprresult => 'money', oprcom => '<->(money,money)',
+  oprcode => 'cash_distance'},
+{ oid => '4224', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'date', oprresult => 'int4', oprcom => '<->(date,date)',
+  oprcode => 'date_distance'},
+{ oid => '4225', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'time',
+  oprright => 'time', oprresult => 'interval', oprcom => '<->(time,time)',
+  oprcode => 'time_distance'},
+{ oid => '4226', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamp)',
+  oprcode => 'timestamp_distance'},
+{ oid => '4227', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamptz)',
+  oprcode => 'timestamptz_distance'},
+{ oid => '4228', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'interval',
+  oprright => 'interval', oprresult => 'interval', oprcom => '<->(interval,interval)',
+  oprcode => 'interval_distance'},
+
+# cross-type distance operators
+{ oid => '4229', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int2)',
+  oprcode => 'int24dist'},
+{ oid => '4230', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int2', oprresult => 'int4', oprcom => '<->(int2,int4)',
+  oprcode => 'int42dist'},
+{ oid => '4231', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int2)',
+  oprcode => 'int28dist'},
+{ oid => '4232', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int2', oprresult => 'int8', oprcom => '<->(int2,int8)',
+  oprcode => 'int82dist'},
+{ oid => '4233', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int4)',
+  oprcode => 'int48dist'},
+{ oid => '4234', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int4', oprresult => 'int8', oprcom => '<->(int4,int8)',
+  oprcode => 'int84dist'},
+{ oid => '4235', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float4)',
+  oprcode => 'float48dist'},
+{ oid => '4236', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float4', oprresult => 'float8', oprcom => '<->(float4,float8)',
+  oprcode => 'float84dist'},
+{ oid => '4237', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,date)',
+  oprcode => 'date_dist_timestamp'},
+{ oid => '4238', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamp)',
+  oprcode => 'timestamp_dist_date'},
+{ oid => '4239', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,date)',
+  oprcode => 'date_dist_timestamptz'},
+{ oid => '4240', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamptz)',
+  oprcode => 'timestamptz_dist_date'},
+{ oid => '4241', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamp)',
+  oprcode => 'timestamp_dist_timestamptz'},
+{ oid => '4242', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamptz)',
+  oprcode => 'timestamptz_dist_timestamp'},
+
 # enum operators
 { oid => '3516', descr => 'equal',
   oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'anyenum',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a4e173b..96d5db0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10534,4 +10534,90 @@
   proname => 'pg_partition_root', prorettype => 'regclass',
   proargtypes => 'regclass', prosrc => 'pg_partition_root' },
 
+# distance functions
+{ oid => '4243',
+  proname => 'int2dist', prorettype => 'int2',
+  proargtypes => 'int2 int2', prosrc => 'int2dist' },
+{ oid => '4244',
+  proname => 'int4dist', prorettype => 'int4',
+  proargtypes => 'int4 int4', prosrc => 'int4dist' },
+{ oid => '4245',
+  proname => 'int8dist', prorettype => 'int8',
+  proargtypes => 'int8 int8', prosrc => 'int8dist' },
+{ oid => '4246',
+  proname => 'oiddist', proleakproof => 't', prorettype => 'oid',
+  proargtypes => 'oid oid', prosrc => 'oiddist' },
+{ oid => '4247',
+  proname => 'float4dist', prorettype => 'float4',
+  proargtypes => 'float4 float4', prosrc => 'float4dist' },
+{ oid => '4248',
+  proname => 'float8dist', prorettype => 'float8',
+  proargtypes => 'float8 float8', prosrc => 'float8dist' },
+{ oid => '4249',
+  proname => 'cash_distance', prorettype => 'money',
+  proargtypes => 'money money', prosrc => 'cash_distance' },
+{ oid => '4250',
+  proname => 'date_distance', prorettype => 'int4',
+  proargtypes => 'date date', prosrc => 'date_distance' },
+{ oid => '4251',
+  proname => 'time_distance', prorettype => 'interval',
+  proargtypes => 'time time', prosrc => 'time_distance' },
+{ oid => '4252',
+  proname => 'timestamp_distance', prorettype => 'interval',
+  proargtypes => 'timestamp timestamp', prosrc => 'timestamp_distance' },
+{ oid => '4253',
+  proname => 'timestamptz_distance', prorettype => 'interval',
+  proargtypes => 'timestamptz timestamptz', prosrc => 'timestamptz_distance' },
+{ oid => '4254',
+  proname => 'interval_distance', prorettype => 'interval',
+  proargtypes => 'interval interval', prosrc => 'interval_distance' },
+
+# cross-type distance functions
+{ oid => '4255',
+  proname => 'int24dist', prorettype => 'int4',
+  proargtypes => 'int2 int4', prosrc => 'int24dist' },
+{ oid => '4256',
+  proname => 'int28dist', prorettype => 'int8',
+  proargtypes => 'int2 int8', prosrc => 'int28dist' },
+{ oid => '4257',
+  proname => 'int42dist', prorettype => 'int4',
+  proargtypes => 'int4 int2', prosrc => 'int42dist' },
+{ oid => '4258',
+  proname => 'int48dist', prorettype => 'int8',
+  proargtypes => 'int4 int8', prosrc => 'int48dist' },
+{ oid => '4259',
+  proname => 'int82dist', prorettype => 'int8',
+  proargtypes => 'int8 int2', prosrc => 'int82dist' },
+{ oid => '4260',
+  proname => 'int84dist', prorettype => 'int8',
+  proargtypes => 'int8 int4', prosrc => 'int84dist' },
+{ oid => '4261',
+  proname => 'float48dist', prorettype => 'float8',
+  proargtypes => 'float4 float8', prosrc => 'float48dist' },
+{ oid => '4262',
+  proname => 'float84dist', prorettype => 'float8',
+  proargtypes => 'float8 float4', prosrc => 'float84dist' },
+{ oid => '4263',
+  proname => 'date_dist_timestamp', prorettype => 'interval',
+  proargtypes => 'date timestamp', prosrc => 'date_dist_timestamp' },
+{ oid => '4264',
+  proname => 'date_dist_timestamptz', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'date timestamptz',
+  prosrc => 'date_dist_timestamptz' },
+{ oid => '4265',
+  proname => 'timestamp_dist_date', prorettype => 'interval',
+  proargtypes => 'timestamp date', prosrc => 'timestamp_dist_date' },
+{ oid => '4266',
+  proname => 'timestamp_dist_timestamptz', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamp timestamptz',
+  prosrc => 'timestamp_dist_timestamptz' },
+{ oid => '4267',
+  proname => 'timestamptz_dist_date', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamptz date',
+  prosrc => 'timestamptz_dist_date' },
+{ oid => '4268',
+  proname => 'timestamptz_dist_timestamp', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamptz timestamp',
+  prosrc => 'timestamptz_dist_timestamp' },
+
 ]
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 87f819e..01d2284 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -338,4 +338,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 					   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern Interval *abs_interval(Interval *a);
+
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index aeb89dc..438a5eb 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -93,9 +93,11 @@ extern Timestamp SetEpochTimestamp(void);
 extern void GetEpochTime(struct pg_tm *tm);
 
 extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern Interval *timestamp_dist_internal(Timestamp a, Timestamp b);
 
-/* timestamp comparison works for timestamptz also */
+/* timestamp comparison and distance works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
+#define timestamptz_dist_internal(dt1,dt2)	timestamp_dist_internal(dt1, dt2)
 
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index 4570a39..630dc6b 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -24,7 +24,7 @@ select prop,
  nulls_first        |    |       | f
  nulls_last         |    |       | t
  orderable          |    |       | t
- distance_orderable |    |       | f
+ distance_orderable |    |       | t
  returnable         |    |       | t
  search_array       |    |       | t
  search_nulls       |    |       | t
@@ -100,7 +100,7 @@ select prop,
  nulls_first        | f     | f    | f    | f            | f           | f   | f
  nulls_last         | t     | f    | f    | f            | f           | f   | f
  orderable          | t     | f    | f    | f            | f           | f   | f
- distance_orderable | f     | f    | t    | f            | t           | f   | f
+ distance_orderable | t     | f    | t    | f            | t           | f   | f
  returnable         | t     | f    | f    | t            | t           | f   | f
  search_array       | t     | f    | f    | f            | f           | f   | f
  search_nulls       | t     | f    | t    | t            | t           | f   | t
@@ -231,7 +231,7 @@ select col, prop, pg_index_column_has_property(o, col, prop)
    1 | desc               | f
    1 | nulls_first        | f
    1 | nulls_last         | t
-   1 | distance_orderable | f
+   1 | distance_orderable | t
    1 | returnable         | t
    1 | bogus              | 
    2 | orderable          | f
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
index 1bcc946..b9b819c 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -1477,3 +1477,64 @@ select make_time(10, 55, 100.1);
 ERROR:  time field value out of range: 10:55:100.1
 select make_time(24, 0, 2.1);
 ERROR:  time field value out of range: 24:00:2.1
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+ Fifteen | Distance 
+---------+----------
+         |    16006
+         |    15941
+         |     1802
+         |     1801
+         |     1800
+         |     1799
+         |     1436
+         |     1435
+         |     1434
+         |      308
+         |      307
+         |      306
+         |    13578
+         |    13944
+         |    14311
+(15 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+ Fifteen |               Distance                
+---------+---------------------------------------
+         | @ 16006 days 1 hour 23 mins 45 secs
+         | @ 15941 days 1 hour 23 mins 45 secs
+         | @ 1802 days 1 hour 23 mins 45 secs
+         | @ 1801 days 1 hour 23 mins 45 secs
+         | @ 1800 days 1 hour 23 mins 45 secs
+         | @ 1799 days 1 hour 23 mins 45 secs
+         | @ 1436 days 1 hour 23 mins 45 secs
+         | @ 1435 days 1 hour 23 mins 45 secs
+         | @ 1434 days 1 hour 23 mins 45 secs
+         | @ 308 days 1 hour 23 mins 45 secs
+         | @ 307 days 1 hour 23 mins 45 secs
+         | @ 306 days 1 hour 23 mins 45 secs
+         | @ 13577 days 22 hours 36 mins 15 secs
+         | @ 13943 days 22 hours 36 mins 15 secs
+         | @ 14310 days 22 hours 36 mins 15 secs
+(15 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
+ Fifteen |               Distance                
+---------+---------------------------------------
+         | @ 16005 days 14 hours 23 mins 45 secs
+         | @ 15940 days 14 hours 23 mins 45 secs
+         | @ 1801 days 14 hours 23 mins 45 secs
+         | @ 1800 days 14 hours 23 mins 45 secs
+         | @ 1799 days 14 hours 23 mins 45 secs
+         | @ 1798 days 14 hours 23 mins 45 secs
+         | @ 1435 days 14 hours 23 mins 45 secs
+         | @ 1434 days 14 hours 23 mins 45 secs
+         | @ 1433 days 14 hours 23 mins 45 secs
+         | @ 307 days 14 hours 23 mins 45 secs
+         | @ 306 days 14 hours 23 mins 45 secs
+         | @ 305 days 15 hours 23 mins 45 secs
+         | @ 13578 days 8 hours 36 mins 15 secs
+         | @ 13944 days 8 hours 36 mins 15 secs
+         | @ 14311 days 8 hours 36 mins 15 secs
+(15 rows)
+
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index cf78277..ce29924e 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -273,6 +273,26 @@ SELECT '' AS five, * FROM FLOAT4_TBL;
       | -1.2345679e-20
 (5 rows)
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+ five |       f1       |     dist      
+------+----------------+---------------
+      |              0 |        1004.3
+      |         -34.84 |       1039.14
+      |        -1004.3 |        2008.6
+      | -1.2345679e+20 | 1.2345679e+20
+      | -1.2345679e-20 |        1004.3
+(5 rows)
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+ five |       f1       |          dist          
+------+----------------+------------------------
+      |              0 |                 1004.3
+      |         -34.84 |     1039.1400001525878
+      |        -1004.3 |     2008.5999877929687
+      | -1.2345679e+20 | 1.2345678955701443e+20
+      | -1.2345679e-20 |                 1004.3
+(5 rows)
+
 -- test edge-case coercions to integer
 SELECT '32767.4'::float4::int2;
  int2  
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
index c3a6f53..5485c19 100644
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -413,6 +413,27 @@ SELECT '' AS five, f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
       | 1.2345678901234e-200 |  2.3112042409018e-67
 (5 rows)
 
+-- distance
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+ five |          f1          |         dist         
+------+----------------------+----------------------
+      |                    0 |               1004.3
+      |               1004.3 |                    0
+      |               -34.84 |              1039.14
+      | 1.2345678901234e+200 | 1.2345678901234e+200
+      | 1.2345678901234e-200 |               1004.3
+(5 rows)
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
+ five |          f1          |         dist         
+------+----------------------+----------------------
+      |                    0 |     1004.29998779297
+      |               1004.3 | 1.22070312045253e-05
+      |               -34.84 |     1039.13998779297
+      | 1.2345678901234e+200 | 1.2345678901234e+200
+      | 1.2345678901234e-200 |     1004.29998779297
+(5 rows)
+
 SELECT '' AS five, * FROM FLOAT8_TBL;
  five |          f1          
 ------+----------------------
diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out
index 8c255b9..0edc57e 100644
--- a/src/test/regress/expected/int2.out
+++ b/src/test/regress/expected/int2.out
@@ -242,6 +242,39 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
       | -32767 | -16383
 (5 rows)
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+ERROR:  smallint out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+ four |  f1   |   x   
+------+-------+-------
+      |     0 |     2
+      |  1234 |  1232
+      | -1234 |  1236
+      | 32767 | 32765
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
   text  
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
index bda7a8d..3735dbc 100644
--- a/src/test/regress/expected/int4.out
+++ b/src/test/regress/expected/int4.out
@@ -247,6 +247,38 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
       | -2147483647 | -1073741823
 (5 rows)
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+ERROR:  integer out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+ five |     f1      |     x      
+------+-------------+------------
+      |           0 |          2
+      |      123456 |     123454
+      |     -123456 |     123458
+      |  2147483647 | 2147483645
+      | -2147483647 | 2147483649
+(5 rows)
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index 8447a28..d56886a 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -432,6 +432,37 @@ SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 A
  4567890123457035 | -4567890123456543 | 1123700970370370094 |     0
 (5 rows)
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
 SELECT q2, abs(q2) FROM INT8_TBL;
         q2         |       abs        
 -------------------+------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index f88f345..cb95adf 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -207,6 +207,21 @@ SELECT '' AS fortyfive, r1.*, r2.*
            | 34 years        | 6 years
 (45 rows)
 
+SELECT '' AS ten, f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+ ten |          ?column?          
+-----+----------------------------
+     | 2 days 02:59:00
+     | 2 days -02:00:00
+     | 8 days -03:00:00
+     | 34 years -2 days -03:00:00
+     | 3 mons -2 days -03:00:00
+     | 2 days 03:00:14
+     | 1 day 00:56:56
+     | 6 years -2 days -03:00:00
+     | 5 mons -2 days -03:00:00
+     | 5 mons -2 days +09:00:00
+(10 rows)
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index ab86595..fb2a489 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -123,6 +123,12 @@ SELECT m / 2::float4 FROM money_data;
    $61.50
 (1 row)
 
+SELECT m <-> '$123.45' FROM money_data;
+ ?column? 
+----------
+    $0.45
+(1 row)
+
 -- All true
 SELECT m = '$123.00' FROM money_data;
  ?column? 
diff --git a/src/test/regress/expected/oid.out b/src/test/regress/expected/oid.out
index 1eab9cc..5339a48 100644
--- a/src/test/regress/expected/oid.out
+++ b/src/test/regress/expected/oid.out
@@ -119,4 +119,17 @@ SELECT '' AS three, o.* FROM OID_TBL o WHERE o.f1 > '1234';
        |   99999999
 (3 rows)
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+ eight |     f1     |  ?column?  
+-------+------------+------------
+       |       1234 |       1111
+       |       1235 |       1112
+       |        987 |        864
+       | 4294966256 | 4294966133
+       |   99999999 |   99999876
+       |          5 |        118
+       |         10 |        113
+       |         15 |        108
+(8 rows)
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index ce25ee0..e637420 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -750,6 +750,7 @@ macaddr8_gt(macaddr8,macaddr8)
 macaddr8_ge(macaddr8,macaddr8)
 macaddr8_ne(macaddr8,macaddr8)
 macaddr8_cmp(macaddr8,macaddr8)
+oiddist(oid,oid)
 -- restore normal output mode
 \a\t
 -- List of functions used by libpq's fe-lobj.c
@@ -1332,7 +1333,7 @@ WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND
     ao.amoprighttype = ap.amprocrighttype AND
     ap.amprocnum = 1 AND
     (pp.provolatile != po.provolatile OR
-     pp.proleakproof != po.proleakproof)
+     (NOT pp.proleakproof AND po.proleakproof))
 ORDER BY 1;
                    proc                    | vp | lp |              opr              | vo | lo 
 -------------------------------------------+----+----+-------------------------------+----+----
@@ -1862,6 +1863,7 @@ ORDER BY 1, 2, 3;
         403 |            5 | *>
         403 |            5 | >
         403 |            5 | ~>~
+        403 |            6 | <->
         405 |            1 | =
         783 |            1 | <<
         783 |            1 | @@
@@ -1971,7 +1973,7 @@ ORDER BY 1, 2, 3;
        4000 |           26 | >>
        4000 |           27 | >>=
        4000 |           28 | ^@
-(123 rows)
+(124 rows)
 
 -- Check that all opclass search operators have selectivity estimators.
 -- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out
index 8e0afe6..ee74faa 100644
--- a/src/test/regress/expected/time.out
+++ b/src/test/regress/expected/time.out
@@ -86,3 +86,19 @@ ERROR:  operator is not unique: time without time zone + time without time zone
 LINE 1: SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
                   ^
 HINT:  Could not choose a best candidate operator. You might need to add explicit type casts.
+-- distance
+SELECT f1 AS "Ten", f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
+     Ten     |           Distance            
+-------------+-------------------------------
+ 00:00:00    | @ 1 hour 23 mins 45 secs
+ 01:00:00    | @ 23 mins 45 secs
+ 02:03:00    | @ 39 mins 15 secs
+ 11:59:00    | @ 10 hours 35 mins 15 secs
+ 12:00:00    | @ 10 hours 36 mins 15 secs
+ 12:01:00    | @ 10 hours 37 mins 15 secs
+ 23:59:00    | @ 22 hours 35 mins 15 secs
+ 23:59:59.99 | @ 22 hours 36 mins 14.99 secs
+ 15:36:39    | @ 14 hours 12 mins 54 secs
+ 15:36:39    | @ 14 hours 12 mins 54 secs
+(10 rows)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 4a2fabd..dcb4205 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1604,3 +1604,214 @@ SELECT make_timestamp(2014,12,28,6,30,45.887);
  Sun Dec 28 06:30:45.887 2014
 (1 row)
 
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 58 secs
+    | @ 1453 days 6 hours 27 mins 58.6 secs
+    | @ 1453 days 6 hours 27 mins 58.5 secs
+    | @ 1453 days 6 hours 27 mins 58.4 secs
+    | @ 1493 days
+    | @ 1492 days 20 hours 55 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 6 hours 27 mins 59 secs
+    | @ 231 days 18 hours 19 mins 20 secs
+    | @ 324 days 15 hours 45 mins 59 secs
+    | @ 324 days 10 hours 45 mins 58 secs
+    | @ 324 days 11 hours 45 mins 57 secs
+    | @ 324 days 20 hours 45 mins 56 secs
+    | @ 324 days 21 hours 45 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 28 mins
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 5 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1452 days 6 hours 27 mins 59 secs
+    | @ 1451 days 6 hours 27 mins 59 secs
+    | @ 1450 days 6 hours 27 mins 59 secs
+    | @ 1449 days 6 hours 27 mins 59 secs
+    | @ 1448 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 765901 days 6 hours 27 mins 59 secs
+    | @ 695407 days 6 hours 27 mins 59 secs
+    | @ 512786 days 6 hours 27 mins 59 secs
+    | @ 330165 days 6 hours 27 mins 59 secs
+    | @ 111019 days 6 hours 27 mins 59 secs
+    | @ 74495 days 6 hours 27 mins 59 secs
+    | @ 37971 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 35077 days 17 hours 32 mins 1 sec
+    | @ 1801 days 6 hours 27 mins 59 secs
+    | @ 1800 days 6 hours 27 mins 59 secs
+    | @ 1799 days 6 hours 27 mins 59 secs
+    | @ 1495 days 6 hours 27 mins 59 secs
+    | @ 1494 days 6 hours 27 mins 59 secs
+    | @ 1493 days 6 hours 27 mins 59 secs
+    | @ 1435 days 6 hours 27 mins 59 secs
+    | @ 1434 days 6 hours 27 mins 59 secs
+    | @ 1130 days 6 hours 27 mins 59 secs
+    | @ 1129 days 6 hours 27 mins 59 secs
+    | @ 399 days 6 hours 27 mins 59 secs
+    | @ 398 days 6 hours 27 mins 59 secs
+    | @ 33 days 6 hours 27 mins 59 secs
+    | @ 32 days 6 hours 27 mins 59 secs
+(63 rows)
+
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 1 hour 23 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 43 secs
+    | @ 1453 days 7 hours 51 mins 43.6 secs
+    | @ 1453 days 7 hours 51 mins 43.5 secs
+    | @ 1453 days 7 hours 51 mins 43.4 secs
+    | @ 1493 days 1 hour 23 mins 45 secs
+    | @ 1492 days 22 hours 19 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 7 hours 51 mins 44 secs
+    | @ 231 days 16 hours 55 mins 35 secs
+    | @ 324 days 17 hours 9 mins 44 secs
+    | @ 324 days 12 hours 9 mins 43 secs
+    | @ 324 days 13 hours 9 mins 42 secs
+    | @ 324 days 22 hours 9 mins 41 secs
+    | @ 324 days 23 hours 9 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 6 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1452 days 7 hours 51 mins 44 secs
+    | @ 1451 days 7 hours 51 mins 44 secs
+    | @ 1450 days 7 hours 51 mins 44 secs
+    | @ 1449 days 7 hours 51 mins 44 secs
+    | @ 1448 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 765901 days 7 hours 51 mins 44 secs
+    | @ 695407 days 7 hours 51 mins 44 secs
+    | @ 512786 days 7 hours 51 mins 44 secs
+    | @ 330165 days 7 hours 51 mins 44 secs
+    | @ 111019 days 7 hours 51 mins 44 secs
+    | @ 74495 days 7 hours 51 mins 44 secs
+    | @ 37971 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 35077 days 16 hours 8 mins 16 secs
+    | @ 1801 days 7 hours 51 mins 44 secs
+    | @ 1800 days 7 hours 51 mins 44 secs
+    | @ 1799 days 7 hours 51 mins 44 secs
+    | @ 1495 days 7 hours 51 mins 44 secs
+    | @ 1494 days 7 hours 51 mins 44 secs
+    | @ 1493 days 7 hours 51 mins 44 secs
+    | @ 1435 days 7 hours 51 mins 44 secs
+    | @ 1434 days 7 hours 51 mins 44 secs
+    | @ 1130 days 7 hours 51 mins 44 secs
+    | @ 1129 days 7 hours 51 mins 44 secs
+    | @ 399 days 7 hours 51 mins 44 secs
+    | @ 398 days 7 hours 51 mins 44 secs
+    | @ 33 days 7 hours 51 mins 44 secs
+    | @ 32 days 7 hours 51 mins 44 secs
+(63 rows)
+
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |                Distance                
+----+----------------------------------------
+    | @ 11355 days 14 hours 23 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 43 secs
+    | @ 1452 days 20 hours 51 mins 43.6 secs
+    | @ 1452 days 20 hours 51 mins 43.5 secs
+    | @ 1452 days 20 hours 51 mins 43.4 secs
+    | @ 1492 days 14 hours 23 mins 45 secs
+    | @ 1492 days 11 hours 19 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 21 hours 51 mins 44 secs
+    | @ 232 days 2 hours 55 mins 35 secs
+    | @ 324 days 6 hours 9 mins 44 secs
+    | @ 324 days 1 hour 9 mins 43 secs
+    | @ 324 days 2 hours 9 mins 42 secs
+    | @ 324 days 11 hours 9 mins 41 secs
+    | @ 324 days 12 hours 9 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1451 days 20 hours 51 mins 44 secs
+    | @ 1450 days 20 hours 51 mins 44 secs
+    | @ 1449 days 20 hours 51 mins 44 secs
+    | @ 1448 days 20 hours 51 mins 44 secs
+    | @ 1447 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 765900 days 20 hours 51 mins 44 secs
+    | @ 695406 days 20 hours 51 mins 44 secs
+    | @ 512785 days 20 hours 51 mins 44 secs
+    | @ 330164 days 20 hours 51 mins 44 secs
+    | @ 111018 days 20 hours 51 mins 44 secs
+    | @ 74494 days 20 hours 51 mins 44 secs
+    | @ 37970 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 35078 days 3 hours 8 mins 16 secs
+    | @ 1800 days 20 hours 51 mins 44 secs
+    | @ 1799 days 20 hours 51 mins 44 secs
+    | @ 1798 days 20 hours 51 mins 44 secs
+    | @ 1494 days 20 hours 51 mins 44 secs
+    | @ 1493 days 20 hours 51 mins 44 secs
+    | @ 1492 days 20 hours 51 mins 44 secs
+    | @ 1434 days 20 hours 51 mins 44 secs
+    | @ 1433 days 20 hours 51 mins 44 secs
+    | @ 1129 days 20 hours 51 mins 44 secs
+    | @ 1128 days 20 hours 51 mins 44 secs
+    | @ 398 days 20 hours 51 mins 44 secs
+    | @ 397 days 20 hours 51 mins 44 secs
+    | @ 32 days 20 hours 51 mins 44 secs
+    | @ 31 days 20 hours 51 mins 44 secs
+(63 rows)
+
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 8a4c719..0a05e37 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2544,3 +2544,217 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
  Tue Jan 17 16:00:00 2017 PST
 (1 row)
 
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 8 hours
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 58 secs
+    | @ 1453 days 6 hours 27 mins 58.6 secs
+    | @ 1453 days 6 hours 27 mins 58.5 secs
+    | @ 1453 days 6 hours 27 mins 58.4 secs
+    | @ 1493 days
+    | @ 1492 days 20 hours 55 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 7 hours 27 mins 59 secs
+    | @ 231 days 17 hours 19 mins 20 secs
+    | @ 324 days 15 hours 45 mins 59 secs
+    | @ 324 days 19 hours 45 mins 58 secs
+    | @ 324 days 21 hours 45 mins 57 secs
+    | @ 324 days 20 hours 45 mins 56 secs
+    | @ 324 days 22 hours 45 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 28 mins
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 9 hours 27 mins 59 secs
+    | @ 1303 days 10 hours 27 mins 59 secs
+    | @ 1333 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1452 days 6 hours 27 mins 59 secs
+    | @ 1451 days 6 hours 27 mins 59 secs
+    | @ 1450 days 6 hours 27 mins 59 secs
+    | @ 1449 days 6 hours 27 mins 59 secs
+    | @ 1448 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 765901 days 6 hours 27 mins 59 secs
+    | @ 695407 days 6 hours 27 mins 59 secs
+    | @ 512786 days 6 hours 27 mins 59 secs
+    | @ 330165 days 6 hours 27 mins 59 secs
+    | @ 111019 days 6 hours 27 mins 59 secs
+    | @ 74495 days 6 hours 27 mins 59 secs
+    | @ 37971 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 35077 days 17 hours 32 mins 1 sec
+    | @ 1801 days 6 hours 27 mins 59 secs
+    | @ 1800 days 6 hours 27 mins 59 secs
+    | @ 1799 days 6 hours 27 mins 59 secs
+    | @ 1495 days 6 hours 27 mins 59 secs
+    | @ 1494 days 6 hours 27 mins 59 secs
+    | @ 1493 days 6 hours 27 mins 59 secs
+    | @ 1435 days 6 hours 27 mins 59 secs
+    | @ 1434 days 6 hours 27 mins 59 secs
+    | @ 1130 days 6 hours 27 mins 59 secs
+    | @ 1129 days 6 hours 27 mins 59 secs
+    | @ 399 days 6 hours 27 mins 59 secs
+    | @ 398 days 6 hours 27 mins 59 secs
+    | @ 33 days 6 hours 27 mins 59 secs
+    | @ 32 days 6 hours 27 mins 59 secs
+(64 rows)
+
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 9 hours 23 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 43 secs
+    | @ 1453 days 7 hours 51 mins 43.6 secs
+    | @ 1453 days 7 hours 51 mins 43.5 secs
+    | @ 1453 days 7 hours 51 mins 43.4 secs
+    | @ 1493 days 1 hour 23 mins 45 secs
+    | @ 1492 days 22 hours 19 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 8 hours 51 mins 44 secs
+    | @ 231 days 15 hours 55 mins 35 secs
+    | @ 324 days 17 hours 9 mins 44 secs
+    | @ 324 days 21 hours 9 mins 43 secs
+    | @ 324 days 23 hours 9 mins 42 secs
+    | @ 324 days 22 hours 9 mins 41 secs
+    | @ 325 days 9 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 10 hours 51 mins 44 secs
+    | @ 1303 days 11 hours 51 mins 44 secs
+    | @ 1333 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1452 days 7 hours 51 mins 44 secs
+    | @ 1451 days 7 hours 51 mins 44 secs
+    | @ 1450 days 7 hours 51 mins 44 secs
+    | @ 1449 days 7 hours 51 mins 44 secs
+    | @ 1448 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 765901 days 7 hours 51 mins 44 secs
+    | @ 695407 days 7 hours 51 mins 44 secs
+    | @ 512786 days 7 hours 51 mins 44 secs
+    | @ 330165 days 7 hours 51 mins 44 secs
+    | @ 111019 days 7 hours 51 mins 44 secs
+    | @ 74495 days 7 hours 51 mins 44 secs
+    | @ 37971 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 35077 days 16 hours 8 mins 16 secs
+    | @ 1801 days 7 hours 51 mins 44 secs
+    | @ 1800 days 7 hours 51 mins 44 secs
+    | @ 1799 days 7 hours 51 mins 44 secs
+    | @ 1495 days 7 hours 51 mins 44 secs
+    | @ 1494 days 7 hours 51 mins 44 secs
+    | @ 1493 days 7 hours 51 mins 44 secs
+    | @ 1435 days 7 hours 51 mins 44 secs
+    | @ 1434 days 7 hours 51 mins 44 secs
+    | @ 1130 days 7 hours 51 mins 44 secs
+    | @ 1129 days 7 hours 51 mins 44 secs
+    | @ 399 days 7 hours 51 mins 44 secs
+    | @ 398 days 7 hours 51 mins 44 secs
+    | @ 33 days 7 hours 51 mins 44 secs
+    | @ 32 days 7 hours 51 mins 44 secs
+(64 rows)
+
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |                Distance                
+----+----------------------------------------
+    | @ 11355 days 22 hours 23 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 43 secs
+    | @ 1452 days 20 hours 51 mins 43.6 secs
+    | @ 1452 days 20 hours 51 mins 43.5 secs
+    | @ 1452 days 20 hours 51 mins 43.4 secs
+    | @ 1492 days 14 hours 23 mins 45 secs
+    | @ 1492 days 11 hours 19 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 21 hours 51 mins 44 secs
+    | @ 232 days 2 hours 55 mins 35 secs
+    | @ 324 days 6 hours 9 mins 44 secs
+    | @ 324 days 10 hours 9 mins 43 secs
+    | @ 324 days 12 hours 9 mins 42 secs
+    | @ 324 days 11 hours 9 mins 41 secs
+    | @ 324 days 13 hours 9 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1452 days 23 hours 51 mins 44 secs
+    | @ 1303 days 51 mins 44 secs
+    | @ 1332 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1451 days 20 hours 51 mins 44 secs
+    | @ 1450 days 20 hours 51 mins 44 secs
+    | @ 1449 days 20 hours 51 mins 44 secs
+    | @ 1448 days 20 hours 51 mins 44 secs
+    | @ 1447 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 765900 days 20 hours 51 mins 44 secs
+    | @ 695406 days 20 hours 51 mins 44 secs
+    | @ 512785 days 20 hours 51 mins 44 secs
+    | @ 330164 days 20 hours 51 mins 44 secs
+    | @ 111018 days 20 hours 51 mins 44 secs
+    | @ 74494 days 20 hours 51 mins 44 secs
+    | @ 37970 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 35078 days 3 hours 8 mins 16 secs
+    | @ 1800 days 20 hours 51 mins 44 secs
+    | @ 1799 days 20 hours 51 mins 44 secs
+    | @ 1798 days 20 hours 51 mins 44 secs
+    | @ 1494 days 20 hours 51 mins 44 secs
+    | @ 1493 days 20 hours 51 mins 44 secs
+    | @ 1492 days 20 hours 51 mins 44 secs
+    | @ 1434 days 20 hours 51 mins 44 secs
+    | @ 1433 days 20 hours 51 mins 44 secs
+    | @ 1129 days 20 hours 51 mins 44 secs
+    | @ 1128 days 20 hours 51 mins 44 secs
+    | @ 398 days 20 hours 51 mins 44 secs
+    | @ 397 days 20 hours 51 mins 44 secs
+    | @ 32 days 20 hours 51 mins 44 secs
+    | @ 31 days 20 hours 51 mins 44 secs
+(64 rows)
+
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
index 22f80f2..24be476 100644
--- a/src/test/regress/sql/date.sql
+++ b/src/test/regress/sql/date.sql
@@ -346,3 +346,8 @@ select make_date(2013, 13, 1);
 select make_date(2013, 11, -1);
 select make_time(10, 55, 100.1);
 select make_time(24, 0, 2.1);
+
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
diff --git a/src/test/regress/sql/float4.sql b/src/test/regress/sql/float4.sql
index 646027f..35b5942 100644
--- a/src/test/regress/sql/float4.sql
+++ b/src/test/regress/sql/float4.sql
@@ -87,6 +87,9 @@ UPDATE FLOAT4_TBL
 
 SELECT '' AS five, * FROM FLOAT4_TBL;
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+
 -- test edge-case coercions to integer
 SELECT '32767.4'::float4::int2;
 SELECT '32767.6'::float4::int2;
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
index a333218..7fe9bca 100644
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -131,6 +131,9 @@ SELECT ||/ float8 '27' AS three;
 
 SELECT '' AS five, f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
 
+-- distance
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
 
 SELECT '' AS five, * FROM FLOAT8_TBL;
 
diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql
index 7dbafb6..16dd5d8 100644
--- a/src/test/regress/sql/int2.sql
+++ b/src/test/regress/sql/int2.sql
@@ -84,6 +84,16 @@ SELECT '' AS five, i.f1, i.f1 / int2 '2' AS x FROM INT2_TBL i;
 
 SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
 SELECT ((-1::int2<<15)+1::int2)::text;
diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql
index f014cb2..cff32946 100644
--- a/src/test/regress/sql/int4.sql
+++ b/src/test/regress/sql/int4.sql
@@ -93,6 +93,16 @@ SELECT '' AS five, i.f1, i.f1 / int2 '2' AS x FROM INT4_TBL i;
 
 SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql
index e890452..d7f5bde 100644
--- a/src/test/regress/sql/int8.sql
+++ b/src/test/regress/sql/int8.sql
@@ -89,6 +89,11 @@ SELECT q1 + 42::int2 AS "8plus2", q1 - 42::int2 AS "8minus2", q1 * 42::int2 AS "
 -- int2 op int8
 SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 AS "2mul8", 246::int2 / q1 AS "2div8" FROM INT8_TBL;
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+
 SELECT q2, abs(q2) FROM INT8_TBL;
 SELECT min(q1), min(q2) FROM INT8_TBL;
 SELECT max(q1), max(q2) FROM INT8_TBL;
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index bc5537d..d51c866 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -59,6 +59,8 @@ SELECT '' AS fortyfive, r1.*, r2.*
    WHERE r1.f1 > r2.f1
    ORDER BY r1.f1, r2.f1;
 
+SELECT '' AS ten, f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql
index 37b9ecc..8428d59 100644
--- a/src/test/regress/sql/money.sql
+++ b/src/test/regress/sql/money.sql
@@ -25,6 +25,7 @@ SELECT m / 2::float8 FROM money_data;
 SELECT m * 2::float4 FROM money_data;
 SELECT 2::float4 * m FROM money_data;
 SELECT m / 2::float4 FROM money_data;
+SELECT m <-> '$123.45' FROM money_data;
 
 -- All true
 SELECT m = '$123.00' FROM money_data;
diff --git a/src/test/regress/sql/oid.sql b/src/test/regress/sql/oid.sql
index 4a09689..9f54f92 100644
--- a/src/test/regress/sql/oid.sql
+++ b/src/test/regress/sql/oid.sql
@@ -40,4 +40,6 @@ SELECT '' AS four, o.* FROM OID_TBL o WHERE o.f1 >= '1234';
 
 SELECT '' AS three, o.* FROM OID_TBL o WHERE o.f1 > '1234';
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index e2014fc..959928b 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -818,7 +818,7 @@ WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND
     ao.amoprighttype = ap.amprocrighttype AND
     ap.amprocnum = 1 AND
     (pp.provolatile != po.provolatile OR
-     pp.proleakproof != po.proleakproof)
+     (NOT pp.proleakproof AND po.proleakproof))
 ORDER BY 1;
 
 
diff --git a/src/test/regress/sql/time.sql b/src/test/regress/sql/time.sql
index 99a1562..31f0330 100644
--- a/src/test/regress/sql/time.sql
+++ b/src/test/regress/sql/time.sql
@@ -40,3 +40,6 @@ SELECT f1 AS "Eight" FROM TIME_TBL WHERE f1 >= '00:00';
 -- where we do mixed-type arithmetic. - thomas 2000-12-02
 
 SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
+
+-- distance
+SELECT f1 AS "Ten", f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b7957cb..5d023dd 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -230,3 +230,11 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
 
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
+
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index c3bd46c..7f0525d 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -464,3 +464,11 @@ insert into tmptz values ('2017-01-18 00:00+00');
 explain (costs off)
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
0006-Remove-distance-operators-from-btree_gist-v07.patchtext/x-patch; name=0006-Remove-distance-operators-from-btree_gist-v07.patchDownload
diff --git a/contrib/btree_gist/Makefile b/contrib/btree_gist/Makefile
index af65120..46ab241 100644
--- a/contrib/btree_gist/Makefile
+++ b/contrib/btree_gist/Makefile
@@ -11,8 +11,9 @@ OBJS =  btree_gist.o btree_utils_num.o btree_utils_var.o btree_int2.o \
 
 EXTENSION = btree_gist
 DATA = btree_gist--unpackaged--1.0.sql btree_gist--1.0--1.1.sql \
-       btree_gist--1.1--1.2.sql btree_gist--1.2.sql btree_gist--1.2--1.3.sql \
-       btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql
+       btree_gist--1.1--1.2.sql btree_gist--1.2--1.3.sql \
+       btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql \
+       btree_gist--1.5--1.6.sql btree_gist--1.6.sql
 PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes"
 
 REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \
diff --git a/contrib/btree_gist/btree_cash.c b/contrib/btree_gist/btree_cash.c
index 894d0a2..1b0e317 100644
--- a/contrib/btree_gist/btree_cash.c
+++ b/contrib/btree_gist/btree_cash.c
@@ -95,20 +95,7 @@ PG_FUNCTION_INFO_V1(cash_dist);
 Datum
 cash_dist(PG_FUNCTION_ARGS)
 {
-	Cash		a = PG_GETARG_CASH(0);
-	Cash		b = PG_GETARG_CASH(1);
-	Cash		r;
-	Cash		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("money out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_CASH(ra);
+	return cash_distance(fcinfo);
 }
 
 /**************************************************
diff --git a/contrib/btree_gist/btree_date.c b/contrib/btree_gist/btree_date.c
index 992ce57..f3f0fa1 100644
--- a/contrib/btree_gist/btree_date.c
+++ b/contrib/btree_gist/btree_date.c
@@ -113,12 +113,7 @@ PG_FUNCTION_INFO_V1(date_dist);
 Datum
 date_dist(PG_FUNCTION_ARGS)
 {
-	/* we assume the difference can't overflow */
-	Datum		diff = DirectFunctionCall2(date_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
+	return date_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index 6b20f44..0a9148d 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -93,14 +93,7 @@ PG_FUNCTION_INFO_V1(float4_dist);
 Datum
 float4_dist(PG_FUNCTION_ARGS)
 {
-	float4		a = PG_GETARG_FLOAT4(0);
-	float4		b = PG_GETARG_FLOAT4(1);
-	float4		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT4(Abs(r));
+	return float4dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index ee114cb..8b73b57 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -101,14 +101,7 @@ PG_FUNCTION_INFO_V1(float8_dist);
 Datum
 float8_dist(PG_FUNCTION_ARGS)
 {
-	float8		a = PG_GETARG_FLOAT8(0);
-	float8		b = PG_GETARG_FLOAT8(1);
-	float8		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT8(Abs(r));
+	return float8dist(fcinfo);
 }
 
 /**************************************************
diff --git a/contrib/btree_gist/btree_gist--1.2.sql b/contrib/btree_gist/btree_gist--1.2.sql
deleted file mode 100644
index 1efe753..0000000
--- a/contrib/btree_gist/btree_gist--1.2.sql
+++ /dev/null
@@ -1,1570 +0,0 @@
-/* contrib/btree_gist/btree_gist--1.2.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
-
-CREATE FUNCTION gbtreekey4_in(cstring)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey4_out(gbtreekey4)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey4 (
-	INTERNALLENGTH = 4,
-	INPUT  = gbtreekey4_in,
-	OUTPUT = gbtreekey4_out
-);
-
-CREATE FUNCTION gbtreekey8_in(cstring)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey8_out(gbtreekey8)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey8 (
-	INTERNALLENGTH = 8,
-	INPUT  = gbtreekey8_in,
-	OUTPUT = gbtreekey8_out
-);
-
-CREATE FUNCTION gbtreekey16_in(cstring)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey16_out(gbtreekey16)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey16 (
-	INTERNALLENGTH = 16,
-	INPUT  = gbtreekey16_in,
-	OUTPUT = gbtreekey16_out
-);
-
-CREATE FUNCTION gbtreekey32_in(cstring)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey32_out(gbtreekey32)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey32 (
-	INTERNALLENGTH = 32,
-	INPUT  = gbtreekey32_in,
-	OUTPUT = gbtreekey32_out
-);
-
-CREATE FUNCTION gbtreekey_var_in(cstring)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey_var (
-	INTERNALLENGTH = VARIABLE,
-	INPUT  = gbtreekey_var_in,
-	OUTPUT = gbtreekey_var_out,
-	STORAGE = EXTENDED
-);
-
---distance operators
-
-CREATE FUNCTION cash_dist(money, money)
-RETURNS money
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = money,
-	RIGHTARG = money,
-	PROCEDURE = cash_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION date_dist(date, date)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = date,
-	RIGHTARG = date,
-	PROCEDURE = date_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float4_dist(float4, float4)
-RETURNS float4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float4,
-	RIGHTARG = float4,
-	PROCEDURE = float4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float8_dist(float8, float8)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float8,
-	RIGHTARG = float8,
-	PROCEDURE = float8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int2_dist(int2, int2)
-RETURNS int2
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int2,
-	RIGHTARG = int2,
-	PROCEDURE = int2_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int4_dist(int4, int4)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int4,
-	RIGHTARG = int4,
-	PROCEDURE = int4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int8_dist(int8, int8)
-RETURNS int8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int8,
-	RIGHTARG = int8,
-	PROCEDURE = int8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION interval_dist(interval, interval)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = interval,
-	RIGHTARG = interval,
-	PROCEDURE = interval_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION oid_dist(oid, oid)
-RETURNS oid
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = oid,
-	RIGHTARG = oid,
-	PROCEDURE = oid_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION time_dist(time, time)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = time,
-	RIGHTARG = time,
-	PROCEDURE = time_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION ts_dist(timestamp, timestamp)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamp,
-	RIGHTARG = timestamp,
-	PROCEDURE = ts_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION tstz_dist(timestamptz, timestamptz)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamptz,
-	RIGHTARG = timestamptz,
-	PROCEDURE = tstz_dist,
-	COMMUTATOR = '<->'
-);
-
-
---
---
---
--- oid ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_oid_ops
-DEFAULT FOR TYPE oid USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
-	FUNCTION	2	gbt_oid_union (internal, internal),
-	FUNCTION	3	gbt_oid_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_oid_picksplit (internal, internal),
-	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
--- Add operators that are new in 9.1.  We do it like this, leaving them
--- "loose" in the operator family rather than bound into the opclass, because
--- that's the only state that can be reproduced during an upgrade from 9.0.
-ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
-	OPERATOR	6	<> (oid, oid) ,
-	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
-	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
-	-- Also add support function for index-only-scans, added in 9.5.
-	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
-
-
---
---
---
--- int2 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_union(internal, internal)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int2_ops
-DEFAULT FOR TYPE int2 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
-	FUNCTION	2	gbt_int2_union (internal, internal),
-	FUNCTION	3	gbt_int2_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int2_picksplit (internal, internal),
-	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
-	STORAGE		gbtreekey4;
-
-ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
-	OPERATOR	6	<> (int2, int2) ,
-	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
-	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
-
---
---
---
--- int4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int4_ops
-DEFAULT FOR TYPE int4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
-	FUNCTION	2	gbt_int4_union (internal, internal),
-	FUNCTION	3	gbt_int4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int4_picksplit (internal, internal),
-	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
-	OPERATOR	6	<> (int4, int4) ,
-	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
-	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
-
-
---
---
---
--- int8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int8_ops
-DEFAULT FOR TYPE int8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
-	FUNCTION	2	gbt_int8_union (internal, internal),
-	FUNCTION	3	gbt_int8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int8_picksplit (internal, internal),
-	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
-	OPERATOR	6	<> (int8, int8) ,
-	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
-	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
-
---
---
---
--- float4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float4_ops
-DEFAULT FOR TYPE float4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
-	FUNCTION	2	gbt_float4_union (internal, internal),
-	FUNCTION	3	gbt_float4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float4_picksplit (internal, internal),
-	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
-	OPERATOR	6	<> (float4, float4) ,
-	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
-	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
-
---
---
---
--- float8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float8_ops
-DEFAULT FOR TYPE float8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
-	FUNCTION	2	gbt_float8_union (internal, internal),
-	FUNCTION	3	gbt_float8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float8_picksplit (internal, internal),
-	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
-	OPERATOR	6	<> (float8, float8) ,
-	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
-	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
-
---
---
---
--- timestamp ops
---
---
---
-
-CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamp_ops
-DEFAULT FOR TYPE timestamp USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_ts_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
-	OPERATOR	6	<> (timestamp, timestamp) ,
-	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
-	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamptz_ops
-DEFAULT FOR TYPE timestamptz USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_tstz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
-	OPERATOR	6	<> (timestamptz, timestamptz) ,
-	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
-	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
-
---
---
---
--- time ops
---
---
---
-
-CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_time_ops
-DEFAULT FOR TYPE time USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_time_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
-	OPERATOR	6	<> (time, time) ,
-	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
-	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
-
-
-CREATE OPERATOR CLASS gist_timetz_ops
-DEFAULT FOR TYPE timetz USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_timetz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
-	OPERATOR	6	<> (timetz, timetz) ;
-	-- no 'fetch' function, as the compress function is lossy.
-
-
---
---
---
--- date ops
---
---
---
-
-CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_date_ops
-DEFAULT FOR TYPE date USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
-	FUNCTION	2	gbt_date_union (internal, internal),
-	FUNCTION	3	gbt_date_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_date_picksplit (internal, internal),
-	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
-	OPERATOR	6	<> (date, date) ,
-	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
-	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
-
-
---
---
---
--- interval ops
---
---
---
-
-CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_union(internal, internal)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_interval_ops
-DEFAULT FOR TYPE interval USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
-	FUNCTION	2	gbt_intv_union (internal, internal),
-	FUNCTION	3	gbt_intv_compress (internal),
-	FUNCTION	4	gbt_intv_decompress (internal),
-	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_intv_picksplit (internal, internal),
-	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
-	STORAGE		gbtreekey32;
-
-ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
-	OPERATOR	6	<> (interval, interval) ,
-	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
-	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
-
-
---
---
---
--- cash ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cash_ops
-DEFAULT FOR TYPE money USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
-	FUNCTION	2	gbt_cash_union (internal, internal),
-	FUNCTION	3	gbt_cash_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_cash_picksplit (internal, internal),
-	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
-	OPERATOR	6	<> (money, money) ,
-	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
-	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
-	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
-
-
---
---
---
--- macaddr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_macaddr_ops
-DEFAULT FOR TYPE macaddr USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
-	FUNCTION	2	gbt_macad_union (internal, internal),
-	FUNCTION	3	gbt_macad_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_macad_picksplit (internal, internal),
-	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
-	OPERATOR	6	<> (macaddr, macaddr) ,
-	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
-
-
---
---
---
--- text/ bpchar ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_text_ops
-DEFAULT FOR TYPE text USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_text_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
-	OPERATOR	6	<> (text, text) ,
-	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
-
-
----- Create the operator class
-CREATE OPERATOR CLASS gist_bpchar_ops
-DEFAULT FOR TYPE bpchar USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_bpchar_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
-	OPERATOR	6	<> (bpchar, bpchar) ,
-	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
-
---
---
--- bytea ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bytea_ops
-DEFAULT FOR TYPE bytea USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
-	FUNCTION	2	gbt_bytea_union (internal, internal),
-	FUNCTION	3	gbt_bytea_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
-	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
-	OPERATOR	6	<> (bytea, bytea) ,
-	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
-
-
---
---
---
--- numeric ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_numeric_ops
-DEFAULT FOR TYPE numeric USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
-	FUNCTION	2	gbt_numeric_union (internal, internal),
-	FUNCTION	3	gbt_numeric_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
-	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
-	OPERATOR	6	<> (numeric, numeric) ,
-	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
-
-
---
---
--- bit ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bit_ops
-DEFAULT FOR TYPE bit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
-	OPERATOR	6	<> (bit, bit) ,
-	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
-
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_vbit_ops
-DEFAULT FOR TYPE varbit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
-	OPERATOR	6	<> (varbit, varbit) ,
-	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
-
-
---
---
---
--- inet/cidr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_inet_ops
-DEFAULT FOR TYPE inet USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
-	OPERATOR	6	<>  (inet, inet) ;
-	-- no fetch support, the compress function is lossy
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cidr_ops
-DEFAULT FOR TYPE cidr USING gist
-AS
-	OPERATOR	1	<  (inet, inet)  ,
-	OPERATOR	2	<= (inet, inet)  ,
-	OPERATOR	3	=  (inet, inet)  ,
-	OPERATOR	4	>= (inet, inet)  ,
-	OPERATOR	5	>  (inet, inet)  ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
-	OPERATOR	6	<> (inet, inet) ;
-	-- no fetch support, the compress function is lossy
diff --git a/contrib/btree_gist/btree_gist--1.5--1.6.sql b/contrib/btree_gist/btree_gist--1.5--1.6.sql
new file mode 100644
index 0000000..ef4424e
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.5--1.6.sql
@@ -0,0 +1,99 @@
+/* contrib/btree_gist/btree_gist--1.5--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.6'" to load this file. \quit
+
+-- drop btree_gist distance operators from opfamilies
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist DROP OPERATOR 15 (int2, int2);
+ALTER OPERATOR FAMILY gist_int4_ops USING gist DROP OPERATOR 15 (int4, int4);
+ALTER OPERATOR FAMILY gist_int8_ops USING gist DROP OPERATOR 15 (int8, int8);
+ALTER OPERATOR FAMILY gist_float4_ops USING gist DROP OPERATOR 15 (float4, float4);
+ALTER OPERATOR FAMILY gist_float8_ops USING gist DROP OPERATOR 15 (float8, float8);
+ALTER OPERATOR FAMILY gist_oid_ops USING gist DROP OPERATOR 15 (oid, oid);
+ALTER OPERATOR FAMILY gist_cash_ops USING gist DROP OPERATOR 15 (money, money);
+ALTER OPERATOR FAMILY gist_date_ops USING gist DROP OPERATOR 15 (date, date);
+ALTER OPERATOR FAMILY gist_time_ops USING gist DROP OPERATOR 15 (time, time);
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist DROP OPERATOR 15 (timestamp, timestamp);
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist DROP OPERATOR 15 (timestamptz, timestamptz);
+ALTER OPERATOR FAMILY gist_interval_ops USING gist DROP OPERATOR 15 (interval, interval);
+
+-- add pg_catalog distance operators to opfamilies
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD OPERATOR 15 <-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD OPERATOR 15 <-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD OPERATOR 15 <-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD OPERATOR 15 <-> (float4, float4) FOR ORDER BY pg_catalog.float_ops;
+ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD OPERATOR 15 <-> (float8, float8) FOR ORDER BY pg_catalog.float_ops;
+ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD OPERATOR 15 <-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops;
+ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD OPERATOR 15 <-> (money, money) FOR ORDER BY pg_catalog.money_ops;
+ALTER OPERATOR FAMILY gist_date_ops USING gist ADD OPERATOR 15 <-> (date, date) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_time_ops USING gist ADD OPERATOR 15 <-> (time, time) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD OPERATOR 15 <-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD OPERATOR 15 <-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD OPERATOR 15 <-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops;
+
+-- disable implicit pg_catalog search
+
+DO
+$$
+BEGIN
+	EXECUTE 'SET LOCAL search_path TO ' || current_schema() || ', pg_catalog';
+END
+$$;
+
+-- drop distance operators
+
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int2, int2);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int4, int4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int8, int8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float4, float4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float8, float8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (oid, oid);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (money, money);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (date, date);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (time, time);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (interval, interval);
+
+DROP OPERATOR <-> (int2, int2);
+DROP OPERATOR <-> (int4, int4);
+DROP OPERATOR <-> (int8, int8);
+DROP OPERATOR <-> (float4, float4);
+DROP OPERATOR <-> (float8, float8);
+DROP OPERATOR <-> (oid, oid);
+DROP OPERATOR <-> (money, money);
+DROP OPERATOR <-> (date, date);
+DROP OPERATOR <-> (time, time);
+DROP OPERATOR <-> (timestamp, timestamp);
+DROP OPERATOR <-> (timestamptz, timestamptz);
+DROP OPERATOR <-> (interval, interval);
+
+-- drop distance functions
+
+ALTER EXTENSION btree_gist DROP FUNCTION int2_dist(int2, int2);
+ALTER EXTENSION btree_gist DROP FUNCTION int4_dist(int4, int4);
+ALTER EXTENSION btree_gist DROP FUNCTION int8_dist(int8, int8);
+ALTER EXTENSION btree_gist DROP FUNCTION float4_dist(float4, float4);
+ALTER EXTENSION btree_gist DROP FUNCTION float8_dist(float8, float8);
+ALTER EXTENSION btree_gist DROP FUNCTION oid_dist(oid, oid);
+ALTER EXTENSION btree_gist DROP FUNCTION cash_dist(money, money);
+ALTER EXTENSION btree_gist DROP FUNCTION date_dist(date, date);
+ALTER EXTENSION btree_gist DROP FUNCTION time_dist(time, time);
+ALTER EXTENSION btree_gist DROP FUNCTION ts_dist(timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP FUNCTION interval_dist(interval, interval);
+
+DROP FUNCTION int2_dist(int2, int2);
+DROP FUNCTION int4_dist(int4, int4);
+DROP FUNCTION int8_dist(int8, int8);
+DROP FUNCTION float4_dist(float4, float4);
+DROP FUNCTION float8_dist(float8, float8);
+DROP FUNCTION oid_dist(oid, oid);
+DROP FUNCTION cash_dist(money, money);
+DROP FUNCTION date_dist(date, date);
+DROP FUNCTION time_dist(time, time);
+DROP FUNCTION ts_dist(timestamp, timestamp);
+DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+DROP FUNCTION interval_dist(interval, interval);
diff --git a/contrib/btree_gist/btree_gist--1.6.sql b/contrib/btree_gist/btree_gist--1.6.sql
new file mode 100644
index 0000000..8ff8eb5
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.6.sql
@@ -0,0 +1,1615 @@
+/* contrib/btree_gist/btree_gist--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
+
+CREATE FUNCTION gbtreekey4_in(cstring)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey4_out(gbtreekey4)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey4 (
+	INTERNALLENGTH = 4,
+	INPUT  = gbtreekey4_in,
+	OUTPUT = gbtreekey4_out
+);
+
+CREATE FUNCTION gbtreekey8_in(cstring)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey8_out(gbtreekey8)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey8 (
+	INTERNALLENGTH = 8,
+	INPUT  = gbtreekey8_in,
+	OUTPUT = gbtreekey8_out
+);
+
+CREATE FUNCTION gbtreekey16_in(cstring)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey16_out(gbtreekey16)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey16 (
+	INTERNALLENGTH = 16,
+	INPUT  = gbtreekey16_in,
+	OUTPUT = gbtreekey16_out
+);
+
+CREATE FUNCTION gbtreekey32_in(cstring)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey32_out(gbtreekey32)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey32 (
+	INTERNALLENGTH = 32,
+	INPUT  = gbtreekey32_in,
+	OUTPUT = gbtreekey32_out
+);
+
+CREATE FUNCTION gbtreekey_var_in(cstring)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey_var (
+	INTERNALLENGTH = VARIABLE,
+	INPUT  = gbtreekey_var_in,
+	OUTPUT = gbtreekey_var_out,
+	STORAGE = EXTENDED
+);
+
+
+--
+--
+--
+-- oid ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_oid_ops
+DEFAULT FOR TYPE oid USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
+	FUNCTION	2	gbt_oid_union (internal, internal),
+	FUNCTION	3	gbt_oid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_oid_picksplit (internal, internal),
+	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+-- Add operators that are new in 9.1.  We do it like this, leaving them
+-- "loose" in the operator family rather than bound into the opclass, because
+-- that's the only state that can be reproduced during an upgrade from 9.0.
+ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
+	OPERATOR	6	<> (oid, oid) ,
+	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
+	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
+	-- Also add support function for index-only-scans, added in 9.5.
+	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
+
+
+--
+--
+--
+-- int2 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_union(internal, internal)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int2_ops
+DEFAULT FOR TYPE int2 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
+	FUNCTION	2	gbt_int2_union (internal, internal),
+	FUNCTION	3	gbt_int2_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int2_picksplit (internal, internal),
+	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
+	STORAGE		gbtreekey4;
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
+	OPERATOR	6	<> (int2, int2) ,
+	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
+	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
+
+--
+--
+--
+-- int4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int4_ops
+DEFAULT FOR TYPE int4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
+	FUNCTION	2	gbt_int4_union (internal, internal),
+	FUNCTION	3	gbt_int4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int4_picksplit (internal, internal),
+	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
+	OPERATOR	6	<> (int4, int4) ,
+	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
+	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
+
+
+--
+--
+--
+-- int8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int8_ops
+DEFAULT FOR TYPE int8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
+	FUNCTION	2	gbt_int8_union (internal, internal),
+	FUNCTION	3	gbt_int8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int8_picksplit (internal, internal),
+	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
+	OPERATOR	6	<> (int8, int8) ,
+	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
+	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
+
+--
+--
+--
+-- float4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float4_ops
+DEFAULT FOR TYPE float4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
+	FUNCTION	2	gbt_float4_union (internal, internal),
+	FUNCTION	3	gbt_float4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float4_picksplit (internal, internal),
+	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
+	OPERATOR	6	<> (float4, float4) ,
+	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
+	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
+
+--
+--
+--
+-- float8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float8_ops
+DEFAULT FOR TYPE float8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
+	FUNCTION	2	gbt_float8_union (internal, internal),
+	FUNCTION	3	gbt_float8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float8_picksplit (internal, internal),
+	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
+	OPERATOR	6	<> (float8, float8) ,
+	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
+	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
+
+--
+--
+--
+-- timestamp ops
+--
+--
+--
+
+CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamp_ops
+DEFAULT FOR TYPE timestamp USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_ts_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
+	OPERATOR	6	<> (timestamp, timestamp) ,
+	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
+	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamptz_ops
+DEFAULT FOR TYPE timestamptz USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_tstz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
+	OPERATOR	6	<> (timestamptz, timestamptz) ,
+	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
+	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
+
+--
+--
+--
+-- time ops
+--
+--
+--
+
+CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_time_ops
+DEFAULT FOR TYPE time USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_time_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
+	OPERATOR	6	<> (time, time) ,
+	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
+	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
+
+
+CREATE OPERATOR CLASS gist_timetz_ops
+DEFAULT FOR TYPE timetz USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_timetz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
+	OPERATOR	6	<> (timetz, timetz) ;
+	-- no 'fetch' function, as the compress function is lossy.
+
+
+--
+--
+--
+-- date ops
+--
+--
+--
+
+CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_date_ops
+DEFAULT FOR TYPE date USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
+	FUNCTION	2	gbt_date_union (internal, internal),
+	FUNCTION	3	gbt_date_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_date_picksplit (internal, internal),
+	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
+	OPERATOR	6	<> (date, date) ,
+	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
+	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
+
+
+--
+--
+--
+-- interval ops
+--
+--
+--
+
+CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_interval_ops
+DEFAULT FOR TYPE interval USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
+	FUNCTION	2	gbt_intv_union (internal, internal),
+	FUNCTION	3	gbt_intv_compress (internal),
+	FUNCTION	4	gbt_intv_decompress (internal),
+	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_intv_picksplit (internal, internal),
+	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
+	OPERATOR	6	<> (interval, interval) ,
+	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
+	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
+
+
+--
+--
+--
+-- cash ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cash_ops
+DEFAULT FOR TYPE money USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
+	FUNCTION	2	gbt_cash_union (internal, internal),
+	FUNCTION	3	gbt_cash_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_cash_picksplit (internal, internal),
+	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
+	OPERATOR	6	<> (money, money) ,
+	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
+	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
+	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
+
+
+--
+--
+--
+-- macaddr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_macaddr_ops
+DEFAULT FOR TYPE macaddr USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
+	FUNCTION	2	gbt_macad_union (internal, internal),
+	FUNCTION	3	gbt_macad_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_macad_picksplit (internal, internal),
+	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
+	OPERATOR	6	<> (macaddr, macaddr) ,
+	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
+
+
+--
+--
+--
+-- text/ bpchar ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_text_ops
+DEFAULT FOR TYPE text USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_text_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
+	OPERATOR	6	<> (text, text) ,
+	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
+
+
+---- Create the operator class
+CREATE OPERATOR CLASS gist_bpchar_ops
+DEFAULT FOR TYPE bpchar USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_bpchar_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
+	OPERATOR	6	<> (bpchar, bpchar) ,
+	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
+
+--
+--
+-- bytea ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bytea_ops
+DEFAULT FOR TYPE bytea USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
+	FUNCTION	2	gbt_bytea_union (internal, internal),
+	FUNCTION	3	gbt_bytea_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
+	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
+	OPERATOR	6	<> (bytea, bytea) ,
+	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- numeric ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_numeric_ops
+DEFAULT FOR TYPE numeric USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
+	FUNCTION	2	gbt_numeric_union (internal, internal),
+	FUNCTION	3	gbt_numeric_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
+	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
+	OPERATOR	6	<> (numeric, numeric) ,
+	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
+
+
+--
+--
+-- bit ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bit_ops
+DEFAULT FOR TYPE bit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
+	OPERATOR	6	<> (bit, bit) ,
+	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
+
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_vbit_ops
+DEFAULT FOR TYPE varbit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
+	OPERATOR	6	<> (varbit, varbit) ,
+	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- inet/cidr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_inet_ops
+DEFAULT FOR TYPE inet USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
+	OPERATOR	6	<>  (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cidr_ops
+DEFAULT FOR TYPE cidr USING gist
+AS
+	OPERATOR	1	<  (inet, inet)  ,
+	OPERATOR	2	<= (inet, inet)  ,
+	OPERATOR	3	=  (inet, inet)  ,
+	OPERATOR	4	>= (inet, inet)  ,
+	OPERATOR	5	>  (inet, inet)  ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
+	OPERATOR	6	<> (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+--
+--
+--
+-- uuid ops
+--
+--
+---- define the GiST support methods
+CREATE FUNCTION gbt_uuid_consistent(internal,uuid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_uuid_ops
+DEFAULT FOR TYPE uuid USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_uuid_consistent (internal, uuid, int2, oid, internal),
+	FUNCTION	2	gbt_uuid_union (internal, internal),
+	FUNCTION	3	gbt_uuid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_uuid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_uuid_picksplit (internal, internal),
+	FUNCTION	7	gbt_uuid_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+-- These are "loose" in the opfamily for consistency with the rest of btree_gist
+ALTER OPERATOR FAMILY gist_uuid_ops USING gist ADD
+	OPERATOR	6	<>  (uuid, uuid) ,
+	FUNCTION	9 (uuid, uuid) gbt_uuid_fetch (internal) ;
+
+
+-- Add support for indexing macaddr8 columns
+
+-- define the GiST support methods
+CREATE FUNCTION gbt_macad8_consistent(internal,macaddr8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_macaddr8_ops
+DEFAULT FOR TYPE macaddr8 USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_macad8_consistent (internal, macaddr8, int2, oid, internal),
+	FUNCTION	2	gbt_macad8_union (internal, internal),
+	FUNCTION	3	gbt_macad8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_macad8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_macad8_picksplit (internal, internal),
+	FUNCTION	7	gbt_macad8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_macaddr8_ops USING gist ADD
+	OPERATOR	6	<> (macaddr8, macaddr8) ,
+	FUNCTION	9 (macaddr8, macaddr8) gbt_macad8_fetch (internal);
+
+--
+--
+--
+-- enum ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_enum_consistent(internal,anyenum,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_enum_ops
+DEFAULT FOR TYPE anyenum USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_enum_consistent (internal, anyenum, int2, oid, internal),
+	FUNCTION	2	gbt_enum_union (internal, internal),
+	FUNCTION	3	gbt_enum_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_enum_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_enum_picksplit (internal, internal),
+	FUNCTION	7	gbt_enum_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_enum_ops USING gist ADD
+	OPERATOR	6	<> (anyenum, anyenum) ,
+	FUNCTION	9 (anyenum, anyenum) gbt_enum_fetch (internal) ;
diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control
index 81c8509..9ced3bc 100644
--- a/contrib/btree_gist/btree_gist.control
+++ b/contrib/btree_gist/btree_gist.control
@@ -1,5 +1,5 @@
 # btree_gist extension
 comment = 'support for indexing common datatypes in GiST'
-default_version = '1.5'
+default_version = '1.6'
 module_pathname = '$libdir/btree_gist'
 relocatable = true
diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c
index 7674e2d..2afc343 100644
--- a/contrib/btree_gist/btree_int2.c
+++ b/contrib/btree_gist/btree_int2.c
@@ -94,20 +94,7 @@ PG_FUNCTION_INFO_V1(int2_dist);
 Datum
 int2_dist(PG_FUNCTION_ARGS)
 {
-	int16		a = PG_GETARG_INT16(0);
-	int16		b = PG_GETARG_INT16(1);
-	int16		r;
-	int16		ra;
-
-	if (pg_sub_s16_overflow(a, b, &r) ||
-		r == PG_INT16_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("smallint out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT16(ra);
+	return int2dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c
index 80005ab..2361ce7 100644
--- a/contrib/btree_gist/btree_int4.c
+++ b/contrib/btree_gist/btree_int4.c
@@ -95,20 +95,7 @@ PG_FUNCTION_INFO_V1(int4_dist);
 Datum
 int4_dist(PG_FUNCTION_ARGS)
 {
-	int32		a = PG_GETARG_INT32(0);
-	int32		b = PG_GETARG_INT32(1);
-	int32		r;
-	int32		ra;
-
-	if (pg_sub_s32_overflow(a, b, &r) ||
-		r == PG_INT32_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("integer out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT32(ra);
+	return int4dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c
index b0fd3e1..182d7c4 100644
--- a/contrib/btree_gist/btree_int8.c
+++ b/contrib/btree_gist/btree_int8.c
@@ -95,20 +95,7 @@ PG_FUNCTION_INFO_V1(int8_dist);
 Datum
 int8_dist(PG_FUNCTION_ARGS)
 {
-	int64		a = PG_GETARG_INT64(0);
-	int64		b = PG_GETARG_INT64(1);
-	int64		r;
-	int64		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("bigint out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT64(ra);
+	return int8dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_interval.c b/contrib/btree_gist/btree_interval.c
index 3a527a7..c68d48e 100644
--- a/contrib/btree_gist/btree_interval.c
+++ b/contrib/btree_gist/btree_interval.c
@@ -128,11 +128,7 @@ PG_FUNCTION_INFO_V1(interval_dist);
 Datum
 interval_dist(PG_FUNCTION_ARGS)
 {
-	Datum		diff = DirectFunctionCall2(interval_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+	return interval_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_oid.c b/contrib/btree_gist/btree_oid.c
index 00e7019..c702d51 100644
--- a/contrib/btree_gist/btree_oid.c
+++ b/contrib/btree_gist/btree_oid.c
@@ -100,15 +100,7 @@ PG_FUNCTION_INFO_V1(oid_dist);
 Datum
 oid_dist(PG_FUNCTION_ARGS)
 {
-	Oid			a = PG_GETARG_OID(0);
-	Oid			b = PG_GETARG_OID(1);
-	Oid			res;
-
-	if (a < b)
-		res = b - a;
-	else
-		res = a - b;
-	PG_RETURN_OID(res);
+	return oiddist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_time.c b/contrib/btree_gist/btree_time.c
index 90cf655..cfafd02 100644
--- a/contrib/btree_gist/btree_time.c
+++ b/contrib/btree_gist/btree_time.c
@@ -141,11 +141,7 @@ PG_FUNCTION_INFO_V1(time_dist);
 Datum
 time_dist(PG_FUNCTION_ARGS)
 {
-	Datum		diff = DirectFunctionCall2(time_mi_time,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+	return time_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c
index 49d1849..9e62f7d 100644
--- a/contrib/btree_gist/btree_ts.c
+++ b/contrib/btree_gist/btree_ts.c
@@ -146,48 +146,14 @@ PG_FUNCTION_INFO_V1(ts_dist);
 Datum
 ts_dist(PG_FUNCTION_ARGS)
 {
-	Timestamp	a = PG_GETARG_TIMESTAMP(0);
-	Timestamp	b = PG_GETARG_TIMESTAMP(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-	else
-		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-												  PG_GETARG_DATUM(0),
-												  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
+	return timestamp_distance(fcinfo);
 }
 
 PG_FUNCTION_INFO_V1(tstz_dist);
 Datum
 tstz_dist(PG_FUNCTION_ARGS)
 {
-	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
-	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-
-	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-											  PG_GETARG_DATUM(0),
-											  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
+	return timestamptz_distance(fcinfo);
 }
 
 
diff --git a/doc/src/sgml/btree-gist.sgml b/doc/src/sgml/btree-gist.sgml
index 774442f..6eb4a18 100644
--- a/doc/src/sgml/btree-gist.sgml
+++ b/doc/src/sgml/btree-gist.sgml
@@ -96,6 +96,19 @@ INSERT 0 1
  </sect2>
 
  <sect2>
+  <title>Upgrade notes for version 1.6</title>
+
+  <para>
+   In version 1.6 <literal>btree_gist</literal> switched to using in-core
+   distance operators, and its own implementations were removed.  References to
+   these operators in <literal>btree_gist</literal> opclasses will be updated
+   automatically during the extension upgrade, but if the user has created
+   objects referencing these operators or functions, then these objects must be
+   dropped manually before updating the extension.
+  </para>
+ </sect2>
+
+ <sect2>
   <title>Authors</title>
 
   <para>
0007-Add-regression-tests-for-kNN-btree-v07.patchtext/x-patch; name=0007-Add-regression-tests-for-kNN-btree-v07.patchDownload
diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index b21298a..0cefbcc 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -250,3 +250,1109 @@ select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass;
  {vacuum_cleanup_index_scale_factor=70.0}
 (1 row)
 
+---
+--- Test B-tree distance ordering
+---
+SET enable_bitmapscan = OFF;
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+          QUERY PLAN          
+------------------------------
+ Sort
+   Sort Key: ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+                   QUERY PLAN                   
+------------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((random <-> 1))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)))
+   Order By: (random <-> 4000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+                                                                                               QUERY PLAN                                                                                               
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)) AND (random = ANY ('{1809552,1919087,2321799,2648497,3000193,3013326,4157193,4488889,5257716,5593978,NULL}'::integer[])))
+   Order By: (random <-> 3000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+ seqno | random  
+-------+---------
+  5380 | 3000193
+  6262 | 3013326
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+  6448 | 4157193
+   210 | 1809552
+  4408 | 4488889
+  6320 | 5257716
+  1836 | 5593978
+(10 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+-- test parallel KNN scan
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+RESET enable_indexscan;
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, 1000000) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+EXPLAIN (COSTS OFF)
+SELECT i FROM bt_knn_test WHERE i > 8000000;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Gather
+   Workers Planned: 4
+   ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+         Index Cond: (i > 8000000)
+(4 rows)
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> 4000003) AS n, i * 10 AS i
+	FROM generate_series(1, 1000000) i;
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (t1.n = t2.n)
+   Join Filter: (t1.i <> t2.i)
+   ->  Subquery Scan on t1
+         ->  WindowAgg
+               ->  Gather Merge
+                     Workers Planned: 4
+                     ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                           Order By: (i <-> 4000003)
+   ->  Hash
+         ->  Gather
+               Workers Planned: 4
+               ->  Parallel Seq Scan on bt_knn_test2 t2
+(13 rows)
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+DROP TABLE bt_knn_test;
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, 100000) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (t1.n = ((i.i * 100000) + j.j))
+   Join Filter: (t1.i <> ('{4,3,2,7,8}'::integer[])[(i.i + 1)])
+   ->  Subquery Scan on t1
+         ->  WindowAgg
+               ->  Gather Merge
+                     Workers Planned: 4
+                     ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                           Index Cond: (i = ANY ('{3,4,7,8,2}'::integer[]))
+                           Order By: (i <-> 4)
+   ->  Hash
+         ->  Nested Loop
+               ->  Function Scan on generate_series i
+               ->  Function Scan on generate_series j
+(14 rows)
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+ROLLBACK;
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: (ROW(thousand, tenthous) >= ROW(997, 5000))
+   Order By: (thousand <-> 998)
+(3 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+        1 |     9001
+        1 |     8001
+        1 |     7001
+        1 |     6001
+        1 |     5001
+        1 |     4001
+        1 |     3001
+        1 |     2001
+        1 |     1001
+        1 |        1
+        0 |     9000
+        0 |     8000
+        0 |     7000
+        0 |     6000
+        0 |     5000
+        0 |     4000
+        0 |     3000
+        0 |     2000
+        0 |     1000
+        0 |        0
+          |        1
+          |        2
+          |        3
+(33 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: ((thousand > 100) AND (thousand < 800) AND (thousand = ANY ('{0,123,234,345,456,678,901,NULL}'::smallint[])))
+   Order By: (thousand <-> '300'::bigint)
+(3 rows)
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+ thousand | tenthous 
+----------+----------
+      345 |      345
+      345 |     1345
+      345 |     2345
+      345 |     3345
+      345 |     4345
+      345 |     5345
+      345 |     6345
+      345 |     7345
+      345 |     8345
+      345 |     9345
+      234 |      234
+      234 |     1234
+      234 |     2234
+      234 |     3234
+      234 |     4234
+      234 |     5234
+      234 |     6234
+      234 |     7234
+      234 |     8234
+      234 |     9234
+      456 |      456
+      456 |     1456
+      456 |     2456
+      456 |     3456
+      456 |     4456
+      456 |     5456
+      456 |     6456
+      456 |     7456
+      456 |     8456
+      456 |     9456
+      123 |      123
+      123 |     1123
+      123 |     2123
+      123 |     3123
+      123 |     4123
+      123 |     5123
+      123 |     6123
+      123 |     7123
+      123 |     8123
+      123 |     9123
+      678 |      678
+      678 |     1678
+      678 |     2678
+      678 |     3678
+      678 |     4678
+      678 |     5678
+      678 |     6678
+      678 |     7678
+      678 |     8678
+      678 |     9678
+(50 rows)
+
+DROP INDEX tenk3_idx;
+-- Test order by distance ordering on non-first column
+SET enable_sort = OFF;
+-- Ranges are not supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand > 120
+ORDER BY tenthous <-> 3500;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Sort
+   Sort Key: ((tenthous <-> 3500))
+   ->  Index Only Scan using tenk1_thous_tenthous on tenk1
+         Index Cond: (thousand > 120)
+(4 rows)
+
+-- Equality restriction on the first column is supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand = 120
+ORDER BY tenthous <-> 3500;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Index Only Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: (thousand = 120)
+   Order By: (tenthous <-> 3500)
+(3 rows)
+
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand = 120
+ORDER BY tenthous <-> 3500;
+ thousand | tenthous 
+----------+----------
+      120 |     3120
+      120 |     4120
+      120 |     2120
+      120 |     5120
+      120 |     1120
+      120 |     6120
+      120 |      120
+      120 |     7120
+      120 |     8120
+      120 |     9120
+(10 rows)
+
+-- IN restriction on the first column is not supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY tenthous <-> 3500;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Sort
+   Sort Key: ((tenthous <-> 3500))
+   ->  Index Only Scan using tenk1_thous_tenthous on tenk1
+         Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
+(4 rows)
+
+-- Test kNN search using 4-column index
+CREATE INDEX tenk1_knn_idx ON tenk1(ten, hundred, thousand, tenthous);
+-- Ordering by distance to 3rd column
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY thousand <-> 600;
+                  QUERY PLAN                  
+----------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = 3) AND (hundred = 43))
+   Order By: (thousand <-> 600)
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY thousand <-> 600;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      43 |      643 |      643
+   3 |      43 |      643 |     1643
+   3 |      43 |      643 |     2643
+   3 |      43 |      643 |     3643
+   3 |      43 |      643 |     4643
+   3 |      43 |      643 |     5643
+   3 |      43 |      643 |     6643
+   3 |      43 |      643 |     7643
+   3 |      43 |      643 |     8643
+   3 |      43 |      643 |     9643
+   3 |      43 |      543 |     9543
+   3 |      43 |      543 |     8543
+   3 |      43 |      543 |     7543
+   3 |      43 |      543 |     6543
+   3 |      43 |      543 |     5543
+   3 |      43 |      543 |     4543
+   3 |      43 |      543 |     3543
+   3 |      43 |      543 |     2543
+   3 |      43 |      543 |     1543
+   3 |      43 |      543 |      543
+   3 |      43 |      743 |      743
+   3 |      43 |      743 |     1743
+   3 |      43 |      743 |     2743
+   3 |      43 |      743 |     3743
+   3 |      43 |      743 |     4743
+   3 |      43 |      743 |     5743
+   3 |      43 |      743 |     6743
+   3 |      43 |      743 |     7743
+   3 |      43 |      743 |     8743
+   3 |      43 |      743 |     9743
+   3 |      43 |      443 |     9443
+   3 |      43 |      443 |     8443
+   3 |      43 |      443 |     7443
+   3 |      43 |      443 |     6443
+   3 |      43 |      443 |     5443
+   3 |      43 |      443 |     4443
+   3 |      43 |      443 |     3443
+   3 |      43 |      443 |     2443
+   3 |      43 |      443 |     1443
+   3 |      43 |      443 |      443
+   3 |      43 |      843 |      843
+   3 |      43 |      843 |     1843
+   3 |      43 |      843 |     2843
+   3 |      43 |      843 |     3843
+   3 |      43 |      843 |     4843
+   3 |      43 |      843 |     5843
+   3 |      43 |      843 |     6843
+   3 |      43 |      843 |     7843
+   3 |      43 |      843 |     8843
+   3 |      43 |      843 |     9843
+   3 |      43 |      343 |     9343
+   3 |      43 |      343 |     8343
+   3 |      43 |      343 |     7343
+   3 |      43 |      343 |     6343
+   3 |      43 |      343 |     5343
+   3 |      43 |      343 |     4343
+   3 |      43 |      343 |     3343
+   3 |      43 |      343 |     2343
+   3 |      43 |      343 |     1343
+   3 |      43 |      343 |      343
+   3 |      43 |      943 |      943
+   3 |      43 |      943 |     1943
+   3 |      43 |      943 |     2943
+   3 |      43 |      943 |     3943
+   3 |      43 |      943 |     4943
+   3 |      43 |      943 |     5943
+   3 |      43 |      943 |     6943
+   3 |      43 |      943 |     7943
+   3 |      43 |      943 |     8943
+   3 |      43 |      943 |     9943
+   3 |      43 |      243 |     9243
+   3 |      43 |      243 |     8243
+   3 |      43 |      243 |     7243
+   3 |      43 |      243 |     6243
+   3 |      43 |      243 |     5243
+   3 |      43 |      243 |     4243
+   3 |      43 |      243 |     3243
+   3 |      43 |      243 |     2243
+   3 |      43 |      243 |     1243
+   3 |      43 |      243 |      243
+   3 |      43 |      143 |     9143
+   3 |      43 |      143 |     8143
+   3 |      43 |      143 |     7143
+   3 |      43 |      143 |     6143
+   3 |      43 |      143 |     5143
+   3 |      43 |      143 |     4143
+   3 |      43 |      143 |     3143
+   3 |      43 |      143 |     2143
+   3 |      43 |      143 |     1143
+   3 |      43 |      143 |      143
+   3 |      43 |       43 |     9043
+   3 |      43 |       43 |     8043
+   3 |      43 |       43 |     7043
+   3 |      43 |       43 |     6043
+   3 |      43 |       43 |     5043
+   3 |      43 |       43 |     4043
+   3 |      43 |       43 |     3043
+   3 |      43 |       43 |     2043
+   3 |      43 |       43 |     1043
+   3 |      43 |       43 |       43
+(100 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND tenthous > 3000 ORDER BY thousand <-> 600;
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = 3) AND (hundred = 43) AND (tenthous > 3000))
+   Order By: (thousand <-> 600)
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND tenthous > 3000 ORDER BY thousand <-> 600;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      43 |      643 |     3643
+   3 |      43 |      643 |     4643
+   3 |      43 |      643 |     5643
+   3 |      43 |      643 |     6643
+   3 |      43 |      643 |     7643
+   3 |      43 |      643 |     8643
+   3 |      43 |      643 |     9643
+   3 |      43 |      543 |     9543
+   3 |      43 |      543 |     8543
+   3 |      43 |      543 |     7543
+   3 |      43 |      543 |     6543
+   3 |      43 |      543 |     5543
+   3 |      43 |      543 |     4543
+   3 |      43 |      543 |     3543
+   3 |      43 |      743 |     3743
+   3 |      43 |      743 |     4743
+   3 |      43 |      743 |     5743
+   3 |      43 |      743 |     6743
+   3 |      43 |      743 |     7743
+   3 |      43 |      743 |     8743
+   3 |      43 |      743 |     9743
+   3 |      43 |      443 |     9443
+   3 |      43 |      443 |     8443
+   3 |      43 |      443 |     7443
+   3 |      43 |      443 |     6443
+   3 |      43 |      443 |     5443
+   3 |      43 |      443 |     4443
+   3 |      43 |      443 |     3443
+   3 |      43 |      843 |     3843
+   3 |      43 |      843 |     4843
+   3 |      43 |      843 |     5843
+   3 |      43 |      843 |     6843
+   3 |      43 |      843 |     7843
+   3 |      43 |      843 |     8843
+   3 |      43 |      843 |     9843
+   3 |      43 |      343 |     9343
+   3 |      43 |      343 |     8343
+   3 |      43 |      343 |     7343
+   3 |      43 |      343 |     6343
+   3 |      43 |      343 |     5343
+   3 |      43 |      343 |     4343
+   3 |      43 |      343 |     3343
+   3 |      43 |      943 |     3943
+   3 |      43 |      943 |     4943
+   3 |      43 |      943 |     5943
+   3 |      43 |      943 |     6943
+   3 |      43 |      943 |     7943
+   3 |      43 |      943 |     8943
+   3 |      43 |      943 |     9943
+   3 |      43 |      243 |     9243
+   3 |      43 |      243 |     8243
+   3 |      43 |      243 |     7243
+   3 |      43 |      243 |     6243
+   3 |      43 |      243 |     5243
+   3 |      43 |      243 |     4243
+   3 |      43 |      243 |     3243
+   3 |      43 |      143 |     9143
+   3 |      43 |      143 |     8143
+   3 |      43 |      143 |     7143
+   3 |      43 |      143 |     6143
+   3 |      43 |      143 |     5143
+   3 |      43 |      143 |     4143
+   3 |      43 |      143 |     3143
+   3 |      43 |       43 |     9043
+   3 |      43 |       43 |     8043
+   3 |      43 |       43 |     7043
+   3 |      43 |       43 |     6043
+   3 |      43 |       43 |     5043
+   3 |      43 |       43 |     4043
+   3 |      43 |       43 |     3043
+(70 rows)
+
+-- Ordering by distance to 4th column
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = 3) AND (hundred = 43) AND (thousand = 643))
+   Order By: (tenthous <-> 4000)
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      43 |      643 |     3643
+   3 |      43 |      643 |     4643
+   3 |      43 |      643 |     2643
+   3 |      43 |      643 |     5643
+   3 |      43 |      643 |     1643
+   3 |      43 |      643 |     6643
+   3 |      43 |      643 |      643
+   3 |      43 |      643 |     7643
+   3 |      43 |      643 |     8643
+   3 |      43 |      643 |     9643
+(10 rows)
+
+-- Not supported by tenk1_knn_idx (not all previous columns are eq-restricted)
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: (thousand = 643)
+   Order By: (tenthous <-> 4000)
+   Filter: (hundred = 43)
+(4 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY tenthous <-> 4000;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Sort
+   Sort Key: ((tenthous <-> 4000))
+   ->  Index Only Scan using tenk1_knn_idx on tenk1
+         Index Cond: ((ten = 3) AND (hundred = 43))
+(4 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND thousand = 643 ORDER BY tenthous <-> 4000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: (thousand = 643)
+   Order By: (tenthous <-> 4000)
+   Filter: (ten = 3)
+(4 rows)
+
+DROP INDEX tenk1_knn_idx;
+RESET enable_sort;
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+        1 |        1
+        1 |     1001
+        1 |     2001
+        1 |     3001
+        1 |     4001
+        1 |     5001
+        1 |     6001
+        1 |     7001
+        1 |     8001
+        1 |     9001
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+        0 |        0
+        0 |     1000
+        0 |     2000
+        0 |     3000
+        0 |     4000
+        0 |     5000
+        0 |     6000
+        0 |     7000
+        0 |     8000
+        0 |     9000
+          |        3
+          |        2
+          |        1
+(33 rows)
+
+DROP INDEX tenk3_idx;
+DROP TABLE tenk3;
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |     ?column?      
+--------------------------+-------------------
+ Wed May 03 00:00:00 2017 | @ 2 days
+ Wed May 03 01:00:00 2017 | @ 2 days 1 hour
+ Wed May 03 02:00:00 2017 | @ 2 days 2 hours
+ Wed May 03 03:00:00 2017 | @ 2 days 3 hours
+ Wed May 03 04:00:00 2017 | @ 2 days 4 hours
+ Wed May 03 05:00:00 2017 | @ 2 days 5 hours
+ Wed May 03 06:00:00 2017 | @ 2 days 6 hours
+ Wed May 03 07:00:00 2017 | @ 2 days 7 hours
+ Wed May 03 08:00:00 2017 | @ 2 days 8 hours
+ Wed May 03 09:00:00 2017 | @ 2 days 9 hours
+ Wed May 03 10:00:00 2017 | @ 2 days 10 hours
+ Wed May 03 11:00:00 2017 | @ 2 days 11 hours
+ Wed May 03 12:00:00 2017 | @ 2 days 12 hours
+ Wed May 03 13:00:00 2017 | @ 2 days 13 hours
+ Wed May 03 14:00:00 2017 | @ 2 days 14 hours
+ Wed May 03 15:00:00 2017 | @ 2 days 15 hours
+ Wed May 03 16:00:00 2017 | @ 2 days 16 hours
+ Wed May 03 17:00:00 2017 | @ 2 days 17 hours
+ Wed May 03 18:00:00 2017 | @ 2 days 18 hours
+ Wed May 03 19:00:00 2017 | @ 2 days 19 hours
+(20 rows)
+
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |  ?column?  
+--------------------------+------------
+ Mon Jan 01 00:00:00 2018 | @ 0
+ Mon Jan 01 01:00:00 2018 | @ 1 hour
+ Sun Dec 31 23:00:00 2017 | @ 1 hour
+ Mon Jan 01 02:00:00 2018 | @ 2 hours
+ Sun Dec 31 22:00:00 2017 | @ 2 hours
+ Mon Jan 01 03:00:00 2018 | @ 3 hours
+ Sun Dec 31 21:00:00 2017 | @ 3 hours
+ Mon Jan 01 04:00:00 2018 | @ 4 hours
+ Sun Dec 31 20:00:00 2017 | @ 4 hours
+ Mon Jan 01 05:00:00 2018 | @ 5 hours
+ Sun Dec 31 19:00:00 2017 | @ 5 hours
+ Mon Jan 01 06:00:00 2018 | @ 6 hours
+ Sun Dec 31 18:00:00 2017 | @ 6 hours
+ Mon Jan 01 07:00:00 2018 | @ 7 hours
+ Sun Dec 31 17:00:00 2017 | @ 7 hours
+ Mon Jan 01 08:00:00 2018 | @ 8 hours
+ Sun Dec 31 16:00:00 2017 | @ 8 hours
+ Mon Jan 01 09:00:00 2018 | @ 9 hours
+ Sun Dec 31 15:00:00 2017 | @ 9 hours
+ Mon Jan 01 10:00:00 2018 | @ 10 hours
+(20 rows)
+
+DROP TABLE knn_btree_ts;
+RESET enable_bitmapscan;
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 2b087be..5e3bffe 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -129,3 +129,307 @@ create index btree_idx_err on btree_test(a) with (vacuum_cleanup_index_scale_fac
 -- Simple ALTER INDEX
 alter index btree_idx1 set (vacuum_cleanup_index_scale_factor = 70.0);
 select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass;
+
+---
+--- Test B-tree distance ordering
+---
+
+SET enable_bitmapscan = OFF;
+
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+-- test parallel KNN scan
+
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+
+RESET enable_indexscan;
+
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, 1000000) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+EXPLAIN (COSTS OFF)
+SELECT i FROM bt_knn_test WHERE i > 8000000;
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> 4000003) AS n, i * 10 AS i
+	FROM generate_series(1, 1000000) i;
+
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+DROP TABLE bt_knn_test;
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, 100000) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+
+ROLLBACK;
+
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+
+
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+DROP INDEX tenk3_idx;
+
+-- Test order by distance ordering on non-first column
+SET enable_sort = OFF;
+
+-- Ranges are not supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand > 120
+ORDER BY tenthous <-> 3500;
+
+-- Equality restriction on the first column is supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand = 120
+ORDER BY tenthous <-> 3500;
+
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand = 120
+ORDER BY tenthous <-> 3500;
+
+-- IN restriction on the first column is not supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY tenthous <-> 3500;
+
+-- Test kNN search using 4-column index
+CREATE INDEX tenk1_knn_idx ON tenk1(ten, hundred, thousand, tenthous);
+
+-- Ordering by distance to 3rd column
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY thousand <-> 600;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY thousand <-> 600;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND tenthous > 3000 ORDER BY thousand <-> 600;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND tenthous > 3000 ORDER BY thousand <-> 600;
+
+-- Ordering by distance to 4th column
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+
+-- Not supported by tenk1_knn_idx (not all previous columns are eq-restricted)
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY tenthous <-> 4000;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND thousand = 643 ORDER BY tenthous <-> 4000;
+
+DROP INDEX tenk1_knn_idx;
+
+RESET enable_sort;
+
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+DROP INDEX tenk3_idx;
+
+DROP TABLE tenk3;
+
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+
+DROP TABLE knn_btree_ts;
+
+RESET enable_bitmapscan;
0008-Allow-ammatchorderby-to-return-pathkey-sublists-v07.patchtext/x-patch; name=0008-Allow-ammatchorderby-to-return-pathkey-sublists-v07.patchDownload
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 279d8ba..b88044b 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1014,8 +1014,25 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 								index_clauses,
 								&orderbyclauses,
 								&orderbyclausecols);
+
 		if (orderbyclauses)
-			useful_pathkeys = root->query_pathkeys;
+		{
+			int			norderbys = list_length(orderbyclauses);
+			int			npathkeys = list_length(root->query_pathkeys);
+
+			if (norderbys < npathkeys)
+			{
+				/*
+				 * We do not accept pathkey sublists until we implement
+				 * partial sorting.
+				 */
+				useful_pathkeys = NIL;
+				orderbyclauses = NIL;
+				orderbyclausecols = NIL;
+			}
+			else
+				useful_pathkeys = root->query_pathkeys;
+		}
 		else
 			useful_pathkeys = NIL;
 	}
@@ -3286,8 +3303,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
  * index column numbers (zero based) that each clause would be used with.
  * NIL lists are returned if the ordering is not achievable this way.
  *
- * On success, the result list is ordered by pathkeys, and in fact is
- * one-to-one with the requested pathkeys.
+ * On success, the result list is ordered by pathkeys.
  */
 static void
 match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
@@ -3305,8 +3321,8 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 		ammatchorderby(index, pathkeys, index_clauses,
 					   &orderby_clauses, &orderby_clause_columns))
 	{
-		Assert(list_length(pathkeys) == list_length(orderby_clauses));
-		Assert(list_length(pathkeys) == list_length(orderby_clause_columns));
+		Assert(list_length(orderby_clauses) <= list_length(pathkeys));
+		Assert(list_length(orderby_clauses) == list_length(orderby_clause_columns));
 
 		*orderby_clauses_p = orderby_clauses;	/* success! */
 		*orderby_clause_columns_p = orderby_clause_columns;
0009-Add-support-of-array-ops-to-btree-kNN-v07.patchtext/x-patch; name=0009-Add-support-of-array-ops-to-btree-kNN-v07.patchDownload
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index fb413e7..3085fae 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -509,8 +509,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 
 	if (orderbys && scan->numberOfOrderBys > 0)
 		memmove(scan->orderByData,
-				orderbys,
-				scan->numberOfOrderBys * sizeof(ScanKeyData));
+				&orderbys[scan->numberOfOrderBys - 1],
+				1 /* scan->numberOfOrderBys */ * sizeof(ScanKeyData));
 
 	so->scanDirection = NoMovementScanDirection;
 	so->distanceTypeByVal = true;
@@ -1554,13 +1554,15 @@ static bool
 btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 			   List **orderby_clauses_p, List **orderby_clausecols_p)
 {
-	Expr	   *expr;
+	Expr	   *expr = NULL;
 	ListCell   *lc;
 	int			indexcol;
 	int			num_eq_cols = 0;
+	int			nsaops = 0;
+	int			last_saop_ord_col = -1;
+	bool		saops[INDEX_MAX_KEYS] = {0};
 
-	/* only one ORDER BY clause is supported */
-	if (list_length(pathkeys) != 1)
+	if (list_length(pathkeys) < 1)
 		return false;
 
 	/*
@@ -1584,6 +1586,7 @@ btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 			Expr	   *clause = rinfo->clause;
 			Oid			opno;
 			StrategyNumber strat;
+			bool		is_saop;
 
 			if (!clause)
 				continue;
@@ -1593,6 +1596,18 @@ btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 				OpExpr	   *opexpr = (OpExpr *) clause;
 
 				opno = opexpr->opno;
+				is_saop = false;
+			}
+			else if (IsA(clause, ScalarArrayOpExpr))
+			{
+				ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+
+				/* We only accept ANY clauses, not ALL */
+				if (!saop->useOr)
+					continue;
+
+				opno = saop->opno;
+				is_saop = true;
 			}
 			else
 			{
@@ -1603,19 +1618,67 @@ btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 			/* Check if the operator is btree equality operator. */
 			strat = get_op_opfamily_strategy(opno, index->opfamily[indexcol]);
 
-			if (strat == BTEqualStrategyNumber)
-				num_eq_cols = indexcol + 1;
+			if (strat != BTEqualStrategyNumber)
+				continue;
+
+			if (is_saop && indexcol == num_eq_cols)
+			{
+				saops[indexcol] = true;
+				nsaops++;
+			}
+			else if (!is_saop && saops[indexcol])
+			{
+				saops[indexcol] = false;
+				nsaops--;
+			}
+
+			num_eq_cols = indexcol + 1;
 		}
 	}
 
-	/*
-	 * If there are no equality columns try to match only the first column,
-	 * otherwise try all columns.
-	 */
-	indexcol = num_eq_cols ? -1 : 0;
+	foreach(lc, pathkeys)
+	{
+		PathKey    *pathkey = lfirst_node(PathKey, lc);
+
+		/*
+		 * If there are no equality columns try to match only the first column,
+		 * otherwise try all columns.
+		 */
+		indexcol = num_eq_cols ? -1 : 0;
+
+		if ((expr = match_orderbyop_pathkey(index, pathkey, &indexcol)))
+			break;	/* found order-by-operator pathkey */
+
+		if (!num_eq_cols)
+			return false;	/* first pathkey is not order-by-operator */
 
-	expr = match_orderbyop_pathkey(index, castNode(PathKey, linitial(pathkeys)),
-								   &indexcol);
+		indexcol = -1;
+
+		if (!(expr = match_pathkey_to_indexcol(index, pathkey, &indexcol)))
+			return false;
+
+		if (indexcol >= num_eq_cols)
+			return false;
+
+		if (saops[indexcol])
+		{
+			saops[indexcol] = false;
+			nsaops--;
+
+			/*
+			 * ORDER BY column numbers for array ops should go in
+			 * non-decreasing order.
+			 */
+			if (indexcol < last_saop_ord_col)
+				return false;
+
+			last_saop_ord_col = indexcol;
+		}
+		/* else: order of equality-restricted columns is arbitrary */
+
+		*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+		*orderby_clausecols_p = lappend_int(*orderby_clausecols_p, -indexcol - 1);
+	}
 
 	if (!expr)
 		return false;
@@ -1627,6 +1690,19 @@ btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 	if (indexcol > num_eq_cols)
 		return false;
 
+	if (nsaops)
+	{
+		int			i;
+
+		/*
+		 * Check that all preceding array-op columns are included into
+		 * ORDER BY clause.
+		 */
+		for (i = 0; i < indexcol; i++)
+			if (saops[i])
+				return false;
+	}
+
 	/* Return first ORDER BY clause's expression and column. */
 	*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
 	*orderby_clausecols_p = lappend_int(*orderby_clausecols_p, indexcol);
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 5b9294b..e980351 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -905,10 +905,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	{
 		ScanKey		ord = scan->orderByData;
 
-		if (scan->numberOfOrderBys > 1)
-			/* it should not happen, see btmatchorderby() */
-			elog(ERROR, "only one btree ordering operator is supported");
-
 		Assert(ord->sk_strategy == BtreeKNNSearchStrategyNumber);
 
 		/* use bidirectional kNN scan by default */
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 805abd2..034e3b8 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -1644,6 +1644,27 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
 								   InvalidOid,	/* no reg proc for this */
 								   (Datum) 0);	/* constant */
 		}
+		else if (IsA(clause, Var))
+		{
+			/* indexkey IS NULL or indexkey IS NOT NULL */
+			Var		   *var = (Var *) clause;
+
+			Assert(isorderby);
+
+			if (var->varno != INDEX_VAR)
+				elog(ERROR, "Var indexqual has wrong key");
+
+			varattno = var->varattno;
+
+			ScanKeyEntryInitialize(this_scan_key,
+								   SK_ORDER_BY | SK_SEARCHNOTNULL,
+								   varattno,	/* attribute number to scan */
+								   InvalidStrategy, /* no strategy */
+								   InvalidOid,	/* no strategy subtype */
+								   var->varcollid,	/* collation FIXME */
+								   InvalidOid,	/* no reg proc for this */
+								   (Datum) 0);	/* constant */
+		}
 		else
 			elog(ERROR, "unsupported indexqual type: %d",
 				 (int) nodeTag(clause));
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index b88044b..a1a36ad 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -2945,6 +2945,62 @@ match_rowcompare_to_indexcol(RestrictInfo *rinfo,
 	return NULL;
 }
 
+/*
+ * Try to match pathkey to the specified index column (*indexcol >= 0) or
+ * to all index columns (*indexcol < 0).
+ */
+Expr *
+match_pathkey_to_indexcol(IndexOptInfo *index, PathKey *pathkey, int *indexcol)
+{
+	ListCell   *lc;
+
+	/* Pathkey must request default sort order for the target opfamily */
+	if (pathkey->pk_strategy != BTLessStrategyNumber ||
+		pathkey->pk_nulls_first)
+		return NULL;
+
+	/* If eclass is volatile, no hope of using an indexscan */
+	if (pathkey->pk_eclass->ec_has_volatile)
+		return NULL;
+
+	/*
+	 * Try to match eclass member expression(s) to index.  Note that child
+	 * EC members are considered, but only when they belong to the target
+	 * relation.  (Unlike regular members, the same expression could be a
+	 * child member of more than one EC.  Therefore, the same index could
+	 * be considered to match more than one pathkey list, which is OK
+	 * here.  See also get_eclass_for_sort_expr.)
+	 */
+	foreach(lc, pathkey->pk_eclass->ec_members)
+	{
+		EquivalenceMember *member = lfirst_node(EquivalenceMember, lc);
+		Expr	   *expr = member->em_expr;
+
+		/* No possibility of match if it references other relations */
+		if (!bms_equal(member->em_relids, index->rel->relids))
+			continue;
+
+		/* If *indexcol is non-negative then try to match only to it */
+		if (*indexcol >= 0)
+		{
+			if (match_index_to_operand((Node *) expr, *indexcol, index))
+				/* don't want to look at remaining members */
+				return expr;
+		}
+		else	/* try to match all columns */
+		{
+			for (*indexcol = 0; *indexcol < index->nkeycolumns; ++*indexcol)
+			{
+				if (match_index_to_operand((Node *) expr, *indexcol, index))
+					/* don't want to look at remaining members */
+					return expr;
+			}
+		}
+	}
+
+	return NULL;
+}
+
 /****************************************************************************
  *				----  ROUTINES TO CHECK ORDERING OPERATORS	----
  ****************************************************************************/
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 236f506..307af39 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4555,7 +4555,11 @@ fix_indexqual_clause(PlannerInfo *root, IndexOptInfo *index, int indexcol,
 	 */
 	clause = replace_nestloop_params(root, clause);
 
-	if (IsA(clause, OpExpr))
+	if (indexcol < 0)
+	{
+		clause = fix_indexqual_operand(clause, index, -indexcol - 1);
+	}
+	else if (IsA(clause, OpExpr))
 	{
 		OpExpr	   *op = (OpExpr *) clause;
 
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 2e3fab6..2076405 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5284,10 +5284,12 @@ get_quals_from_indexclauses(List *indexclauses)
  * index key expression is on the left side of binary clauses.
  */
 Cost
-index_other_operands_eval_cost(PlannerInfo *root, List *indexquals)
+index_other_operands_eval_cost(PlannerInfo *root, List *indexquals,
+							   List *indexcolnos)
 {
 	Cost		qual_arg_cost = 0;
 	ListCell   *lc;
+	ListCell   *indexcolno_lc = indexcolnos ? list_head(indexcolnos) : NULL;
 
 	foreach(lc, indexquals)
 	{
@@ -5302,6 +5304,20 @@ index_other_operands_eval_cost(PlannerInfo *root, List *indexquals)
 		if (IsA(clause, RestrictInfo))
 			clause = ((RestrictInfo *) clause)->clause;
 
+		if (indexcolnos)
+		{
+			int			indexcol = lfirst_int(indexcolno_lc);
+
+			if (indexcol < 0)
+			{
+				/* FIXME */
+				qual_arg_cost += index_qual_cost.startup + index_qual_cost.per_tuple;
+				continue;
+			}
+
+			indexcolno_lc = lnext(indexcolno_lc);
+		}
+
 		if (IsA(clause, OpExpr))
 		{
 			OpExpr	   *op = (OpExpr *) clause;
@@ -5331,7 +5347,8 @@ index_other_operands_eval_cost(PlannerInfo *root, List *indexquals)
 			other_operand = NULL;	/* keep compiler quiet */
 		}
 
-		cost_qual_eval_node(&index_qual_cost, other_operand, root);
+		if (other_operand)
+			cost_qual_eval_node(&index_qual_cost, other_operand, root);
 		qual_arg_cost += index_qual_cost.startup + index_qual_cost.per_tuple;
 	}
 	return qual_arg_cost;
@@ -5346,6 +5363,7 @@ genericcostestimate(PlannerInfo *root,
 	IndexOptInfo *index = path->indexinfo;
 	List	   *indexQuals = get_quals_from_indexclauses(path->indexclauses);
 	List	   *indexOrderBys = path->indexorderbys;
+	List	   *indexOrderByCols = path->indexorderbycols;
 	Cost		indexStartupCost;
 	Cost		indexTotalCost;
 	Selectivity indexSelectivity;
@@ -5509,8 +5527,8 @@ genericcostestimate(PlannerInfo *root,
 	 * Detecting that that might be needed seems more expensive than it's
 	 * worth, though, considering all the other inaccuracies here ...
 	 */
-	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals) +
-		index_other_operands_eval_cost(root, indexOrderBys);
+	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals, NIL) +
+		index_other_operands_eval_cost(root, indexOrderBys, indexOrderByCols);
 	qual_op_cost = cpu_operator_cost *
 		(list_length(indexQuals) + list_length(indexOrderBys));
 
@@ -6629,7 +6647,7 @@ gincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	 * Add on index qual eval costs, much as in genericcostestimate.  But we
 	 * can disregard indexorderbys, since GIN doesn't support those.
 	 */
-	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals);
+	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals, NIL);
 	qual_op_cost = cpu_operator_cost * list_length(indexQuals);
 
 	*indexStartupCost += qual_arg_cost;
@@ -6809,7 +6827,7 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	 * the index costs.  We can disregard indexorderbys, since BRIN doesn't
 	 * support those.
 	 */
-	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals);
+	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals, NIL);
 
 	/*
 	 * Compute the startup cost as the cost to read the whole revmap
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index e9f4f75..6428932 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -79,6 +79,8 @@ extern bool indexcol_is_bool_constant_for_query(IndexOptInfo *index,
 extern bool match_index_to_operand(Node *operand, int indexcol,
 					   IndexOptInfo *index);
 extern void check_index_predicates(PlannerInfo *root, RelOptInfo *rel);
+extern Expr *match_pathkey_to_indexcol(IndexOptInfo *index, PathKey *pathkey,
+									 int *indexcol_p);
 extern Expr *match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey,
 						int *indexcol_p);
 extern bool match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 0ce2175..636e280 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -189,7 +189,7 @@ extern void estimate_hash_bucket_stats(PlannerInfo *root,
 
 extern List *get_quals_from_indexclauses(List *indexclauses);
 extern Cost index_other_operands_eval_cost(PlannerInfo *root,
-							   List *indexquals);
+							   List *indexquals, List *indexcolnos);
 extern List *add_predicate_to_index_quals(IndexOptInfo *index,
 							 List *indexQuals);
 extern void genericcostestimate(PlannerInfo *root, IndexPath *path,
diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index 0cefbcc..724890b 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -876,11 +876,9 @@ ORDER BY tenthous <-> 3500;
       120 |     9120
 (10 rows)
 
--- IN restriction on the first column is not supported
+-- IN restriction on the first column is not supported without 'ORDER BY col1 ASC'
 EXPLAIN (COSTS OFF)
-SELECT thousand, tenthous
-FROM tenk1
-WHERE thousand IN (5, 120, 3456, 23)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
 ORDER BY tenthous <-> 3500;
                              QUERY PLAN                              
 ---------------------------------------------------------------------
@@ -890,6 +888,63 @@ ORDER BY tenthous <-> 3500;
          Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
 (4 rows)
 
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand DESC, tenthous <-> 3500;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Sort
+   Sort Key: thousand DESC, ((tenthous <-> 3500))
+   ->  Index Only Scan using tenk1_thous_tenthous on tenk1
+         Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
+(4 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand, tenthous <-> 3500;
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Index Only Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
+   Order By: (thousand AND (tenthous <-> 3500))
+(3 rows)
+
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand, tenthous <-> 3500;
+ thousand | tenthous 
+----------+----------
+        5 |     3005
+        5 |     4005
+        5 |     2005
+        5 |     5005
+        5 |     1005
+        5 |     6005
+        5 |        5
+        5 |     7005
+        5 |     8005
+        5 |     9005
+       23 |     3023
+       23 |     4023
+       23 |     2023
+       23 |     5023
+       23 |     1023
+       23 |     6023
+       23 |       23
+       23 |     7023
+       23 |     8023
+       23 |     9023
+      120 |     3120
+      120 |     4120
+      120 |     2120
+      120 |     5120
+      120 |     1120
+      120 |     6120
+      120 |      120
+      120 |     7120
+      120 |     8120
+      120 |     9120
+(30 rows)
+
 -- Test kNN search using 4-column index
 CREATE INDEX tenk1_knn_idx ON tenk1(ten, hundred, thousand, tenthous);
 -- Ordering by distance to 3rd column
@@ -1122,38 +1177,132 @@ WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
    3 |      43 |      643 |     9643
 (10 rows)
 
--- Not supported by tenk1_knn_idx (not all previous columns are eq-restricted)
+-- Array ops on non-first columns are not supported
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
-                   QUERY PLAN                   
-------------------------------------------------
- Index Scan using tenk1_thous_tenthous on tenk1
-   Index Cond: (thousand = 643)
-   Order By: (tenthous <-> 4000)
-   Filter: (hundred = 43)
+WHERE ten IN (3, 4, 5) AND hundred IN (23, 24, 35) AND thousand IN (843, 132, 623, 243)
+ORDER BY tenthous <-> 6000;
+                                                                          QUERY PLAN                                                                          
+--------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: ((tenthous <-> 6000))
+   ->  Index Only Scan using tenk1_knn_idx on tenk1
+         Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = ANY ('{23,24,35}'::integer[])) AND (thousand = ANY ('{843,132,623,243}'::integer[])))
 (4 rows)
 
+-- All array columns should be included into ORDER BY
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE ten = 3 AND hundred = 43 ORDER BY tenthous <-> 4000;
-                     QUERY PLAN                     
-----------------------------------------------------
- Sort
-   Sort Key: ((tenthous <-> 4000))
-   ->  Index Only Scan using tenk1_knn_idx on tenk1
-         Index Cond: ((ten = 3) AND (hundred = 43))
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY tenthous <-> 6000;
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: ((thousand = 123) AND (tenthous > 2000))
+   Order By: (tenthous <-> 6000)
+   Filter: ((hundred = 23) AND (ten = ANY ('{3,4,5}'::integer[])))
 (4 rows)
 
+-- Eq-restricted columns can be omitted from ORDER BY
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE ten = 3 AND thousand = 643 ORDER BY tenthous <-> 4000;
-                   QUERY PLAN                   
-------------------------------------------------
- Index Scan using tenk1_thous_tenthous on tenk1
-   Index Cond: (thousand = 643)
-   Order By: (tenthous <-> 4000)
-   Filter: (ten = 3)
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, tenthous <-> 6000;
+                                                    QUERY PLAN                                                    
+------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23) AND (thousand = 123) AND (tenthous > 2000))
+   Order By: (ten AND (tenthous <-> 6000))
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, tenthous <-> 6000;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      23 |      123 |     6123
+   3 |      23 |      123 |     5123
+   3 |      23 |      123 |     7123
+   3 |      23 |      123 |     4123
+   3 |      23 |      123 |     8123
+   3 |      23 |      123 |     3123
+   3 |      23 |      123 |     9123
+   3 |      23 |      123 |     2123
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, tenthous <-> 6000;
+                                                    QUERY PLAN                                                    
+------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23) AND (thousand = 123) AND (tenthous > 2000))
+   Order By: (ten AND (tenthous <-> 6000))
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, tenthous <-> 6000;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      23 |      123 |     6123
+   3 |      23 |      123 |     5123
+   3 |      23 |      123 |     7123
+   3 |      23 |      123 |     4123
+   3 |      23 |      123 |     8123
+   3 |      23 |      123 |     3123
+   3 |      23 |      123 |     9123
+   3 |      23 |      123 |     2123
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4 ,5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, thousand, tenthous <-> 6000;
+                                                    QUERY PLAN                                                    
+------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23) AND (thousand = 123) AND (tenthous > 2000))
+   Order By: (ten AND (tenthous <-> 6000))
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, thousand, tenthous <-> 6000;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      23 |      123 |     6123
+   3 |      23 |      123 |     5123
+   3 |      23 |      123 |     7123
+   3 |      23 |      123 |     4123
+   3 |      23 |      123 |     8123
+   3 |      23 |      123 |     3123
+   3 |      23 |      123 |     9123
+   3 |      23 |      123 |     2123
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, thousand, tenthous <-> 6000;
+                                                    QUERY PLAN                                                    
+------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23) AND (thousand = 123) AND (tenthous > 2000))
+   Order By: (ten AND (tenthous <-> 6000))
+(3 rows)
+
+-- Extra ORDER BY columns after order-by-op are not supported
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 ORDER BY ten, thousand <-> 6000, tenthous;
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Sort
+   Sort Key: ten, ((thousand <-> 6000)), tenthous
+   ->  Index Only Scan using tenk1_knn_idx on tenk1
+         Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23))
 (4 rows)
 
 DROP INDEX tenk1_knn_idx;
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 5e3bffe..69c890e 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -345,13 +345,22 @@ FROM tenk1
 WHERE thousand = 120
 ORDER BY tenthous <-> 3500;
 
--- IN restriction on the first column is not supported
+-- IN restriction on the first column is not supported without 'ORDER BY col1 ASC'
 EXPLAIN (COSTS OFF)
-SELECT thousand, tenthous
-FROM tenk1
-WHERE thousand IN (5, 120, 3456, 23)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
 ORDER BY tenthous <-> 3500;
 
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand DESC, tenthous <-> 3500;
+
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand, tenthous <-> 3500;
+
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand, tenthous <-> 3500;
+
 -- Test kNN search using 4-column index
 CREATE INDEX tenk1_knn_idx ON tenk1(ten, hundred, thousand, tenthous);
 
@@ -378,18 +387,55 @@ WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
 SELECT ten, hundred, thousand, tenthous FROM tenk1
 WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
 
--- Not supported by tenk1_knn_idx (not all previous columns are eq-restricted)
+-- Array ops on non-first columns are not supported
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred IN (23, 24, 35) AND thousand IN (843, 132, 623, 243)
+ORDER BY tenthous <-> 6000;
+
+-- All array columns should be included into ORDER BY
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY tenthous <-> 6000;
+
+-- Eq-restricted columns can be omitted from ORDER BY
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, tenthous <-> 6000;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, tenthous <-> 6000;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, tenthous <-> 6000;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, tenthous <-> 6000;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4 ,5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, thousand, tenthous <-> 6000;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, thousand, tenthous <-> 6000;
 
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE ten = 3 AND hundred = 43 ORDER BY tenthous <-> 4000;
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, thousand, tenthous <-> 6000;
 
+-- Extra ORDER BY columns after order-by-op are not supported
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE ten = 3 AND thousand = 643 ORDER BY tenthous <-> 4000;
+WHERE ten IN (3, 4, 5) AND hundred = 23 ORDER BY ten, thousand <-> 6000, tenthous;
 
 DROP INDEX tenk1_knn_idx;
 
#22Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Nikita Glukhov (#21)
9 attachment(s)
Re: [PATCH] kNN for btree

On 20.02.2019 15:44, Nikita Glukhov wrote:

On 20.02.2019 7:35, Thomas Munro wrote:

On Wed, Feb 20, 2019 at 2:18 PM Nikita Glukhov<n.gluhov@postgrespro.ru> wrote:

On 04.02.2019 8:35, Michael Paquier wrote:
This patch set needs a rebase because of conflicts caused by the
recent patches for pluggable storage.

Hi Nikita,
From the department of trivialities: according to cfbot the
documentation doesn't build. Looks like you have some cases of </>,
but these days you have to write out </quote> (or whatever) in full.

Sorry, tags in docs were fixed. Also I fixed list of data types with built-in
distance operators and list of assumptions for btree distance operators.

Attached 7th version the patches (only documentation was changed).

Sorry again. Experimental patch #9 contained a bug leading to random failures
in the 'brin' test.

Attached fixed 7th version the patches.

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Fix-get_index_column_opclass-v07.patchtext/x-patch; name=0001-Fix-get_index_column_opclass-v07.patchDownload
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index e88c45d..2760748 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -3118,9 +3118,6 @@ get_index_column_opclass(Oid index_oid, int attno)
 {
 	HeapTuple	tuple;
 	Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY;
-	Datum		datum;
-	bool		isnull;
-	oidvector  *indclass;
 	Oid			opclass;
 
 	/* First we need to know the column's opclass. */
@@ -3134,12 +3131,23 @@ get_index_column_opclass(Oid index_oid, int attno)
 	/* caller is supposed to guarantee this */
 	Assert(attno > 0 && attno <= rd_index->indnatts);
 
-	datum = SysCacheGetAttr(INDEXRELID, tuple,
-							Anum_pg_index_indclass, &isnull);
-	Assert(!isnull);
+	if (attno >= 1 && attno <= rd_index->indnkeyatts)
+	{
+		oidvector  *indclass;
+		bool		isnull;
+		Datum		datum = SysCacheGetAttr(INDEXRELID, tuple,
+											Anum_pg_index_indclass,
+											&isnull);
+
+		Assert(!isnull);
 
-	indclass = ((oidvector *) DatumGetPointer(datum));
-	opclass = indclass->values[attno - 1];
+		indclass = ((oidvector *) DatumGetPointer(datum));
+		opclass = indclass->values[attno - 1];
+	}
+	else
+	{
+		opclass = InvalidOid;
+	}
 
 	ReleaseSysCache(tuple);
 
0002-Introduce-ammatchorderby-function-v07.patchtext/x-patch; name=0002-Introduce-ammatchorderby-function-v07.patchDownload
diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 6458376..9bff793 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -109,7 +109,6 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BLOOM_NSTRATEGIES;
 	amroutine->amsupport = BLOOM_NPROC;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -143,6 +142,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 8f008dd..5cd06c7 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -88,7 +88,6 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = BRIN_LAST_OPTIONAL_PROCNUM;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -122,6 +121,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index afc2023..0f6714d 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -41,7 +41,6 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GINNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -75,6 +74,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index b75b3a8..77ca187 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -18,6 +18,7 @@
 #include "access/gistscan.h"
 #include "catalog/pg_collation.h"
 #include "miscadmin.h"
+#include "optimizer/paths.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "nodes/execnodes.h"
@@ -64,7 +65,6 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = GISTNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
@@ -98,6 +98,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = match_orderbyop_pathkeys;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index f1f01a0..bb5c6a1 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -59,7 +59,6 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = HTMaxStrategyNumber;
 	amroutine->amsupport = HASHNProcs;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -93,6 +92,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 98917de..c56ee5e 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -110,7 +110,6 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = BTMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
-	amroutine->amcanorderbyop = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = true;
 	amroutine->amcanmulticol = true;
@@ -144,6 +143,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = btestimateparallelscan;
 	amroutine->aminitparallelscan = btinitparallelscan;
 	amroutine->amparallelrescan = btparallelrescan;
+	amroutine->ammatchorderby = NULL;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 8e63c1f..37de33c 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -22,6 +22,7 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/pg_amop.h"
+#include "optimizer/paths.h"
 #include "storage/bufmgr.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
@@ -31,7 +32,6 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
-
 /*
  * SP-GiST handler function: return IndexAmRoutine with access method parameters
  * and callbacks.
@@ -44,7 +44,6 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amstrategies = 0;
 	amroutine->amsupport = SPGISTNProc;
 	amroutine->amcanorder = false;
-	amroutine->amcanorderbyop = true;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
@@ -78,6 +77,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = NULL;
 	amroutine->aminitparallelscan = NULL;
 	amroutine->amparallelrescan = NULL;
+	amroutine->ammatchorderby = match_orderbyop_pathkeys;
 
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 5d73848..a595c79 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -1097,7 +1097,7 @@ assignOperTypes(OpFamilyMember *member, Oid amoid, Oid typeoid)
 		 */
 		IndexAmRoutine *amroutine = GetIndexAmRoutineByAmId(amoid, false);
 
-		if (!amroutine->amcanorderbyop)
+		if (!amroutine->ammatchorderby)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("access method \"%s\" does not support ordering operators",
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 324356e..805abd2 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -198,7 +198,7 @@ IndexNextWithReorder(IndexScanState *node)
 	 * with just Asserting here because the system will not try to run the
 	 * plan backwards if ExecSupportsBackwardScan() says it won't work.
 	 * Currently, that is guaranteed because no index AMs support both
-	 * amcanorderbyop and amcanbackward; if any ever do,
+	 * ammatchorderby and amcanbackward; if any ever do,
 	 * ExecSupportsBackwardScan() will need to consider indexorderbys
 	 * explicitly.
 	 */
@@ -1148,7 +1148,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
  * 5. NullTest ("indexkey IS NULL/IS NOT NULL").  We just fill in the
  * ScanKey properly.
  *
- * This code is also used to prepare ORDER BY expressions for amcanorderbyop
+ * This code is also used to prepare ORDER BY expressions for ammatchorderby
  * indexes.  The behavior is exactly the same, except that we have to look up
  * the operator differently.  Note that only cases 1 and 2 are currently
  * possible for ORDER BY.
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 3434219..6cf0b0d 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -17,6 +17,7 @@
 
 #include <math.h>
 
+#include "access/amapi.h"
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_am.h"
@@ -182,8 +183,9 @@ static IndexClause *expand_indexqual_rowcompare(RestrictInfo *rinfo,
 							Oid expr_op,
 							bool var_on_left);
 static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
+						List *index_clauses,
 						List **orderby_clauses_p,
-						List **clause_columns_p);
+						List **orderby_clause_columns_p);
 static Expr *match_clause_to_ordering_op(IndexOptInfo *index,
 							int indexcol, Expr *clause, Oid pk_opfamily);
 static bool ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
@@ -1001,10 +1003,11 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		orderbyclauses = NIL;
 		orderbyclausecols = NIL;
 	}
-	else if (index->amcanorderbyop && pathkeys_possibly_useful)
+	else if (index->ammatchorderby && pathkeys_possibly_useful)
 	{
 		/* see if we can generate ordering operators for query_pathkeys */
 		match_pathkeys_to_index(index, root->query_pathkeys,
+								index_clauses,
 								&orderbyclauses,
 								&orderbyclausecols);
 		if (orderbyclauses)
@@ -2927,6 +2930,113 @@ match_rowcompare_to_indexcol(RestrictInfo *rinfo,
 	return NULL;
 }
 
+/****************************************************************************
+ *				----  ROUTINES TO CHECK ORDERING OPERATORS	----
+ ****************************************************************************/
+
+/*
+ * Try to match order-by-operator pathkey to the specified index column
+ * (*indexcol_p >= 0) or to all index columns (*indexcol_p < 0).
+ *
+ * Returned matched index clause exression.
+ * Number of matched index column is returned in *indexcol_p.
+ */
+Expr *
+match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey, int *indexcol_p)
+{
+	ListCell   *lc;
+
+	/* Pathkey must request default sort order for the target opfamily */
+	if (pathkey->pk_strategy != BTLessStrategyNumber ||
+		pathkey->pk_nulls_first)
+		return NULL;
+
+	/* If eclass is volatile, no hope of using an indexscan */
+	if (pathkey->pk_eclass->ec_has_volatile)
+		return NULL;
+
+	/*
+	 * Try to match eclass member expression(s) to index.  Note that child EC
+	 * members are considered, but only when they belong to the target
+	 * relation.  (Unlike regular members, the same expression could be a
+	 * child member of more than one EC.  Therefore, the same index could be
+	 * considered to match more than one pathkey list, which is OK here.  See
+	 * also get_eclass_for_sort_expr.)
+	 */
+	foreach(lc, pathkey->pk_eclass->ec_members)
+	{
+		EquivalenceMember *member = lfirst_node(EquivalenceMember, lc);
+		Expr	   *expr;
+
+		/* No possibility of match if it references other relations. */
+		if (!bms_equal(member->em_relids, index->rel->relids))
+			continue;
+
+		/* If *indexcol_p is non-negative then try to match only to it. */
+		if (*indexcol_p >= 0)
+		{
+			expr = match_clause_to_ordering_op(index, *indexcol_p,
+											   member->em_expr,
+											   pathkey->pk_opfamily);
+
+			if (expr)
+				return expr;	/* don't want to look at remaining members */
+		}
+		else
+		{
+			int			indexcol;
+
+			/*
+			 * We allow any column of this index to match each pathkey; they
+			 * don't have to match left-to-right as you might expect.
+			 */
+			for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
+			{
+				expr = match_clause_to_ordering_op(index, indexcol,
+												   member->em_expr,
+												   pathkey->pk_opfamily);
+				if (expr)
+				{
+					*indexcol_p = indexcol;
+					return expr;	/* don't want to look at remaining members */
+				}
+			}
+		}
+	}
+
+	return NULL;
+}
+
+/* Try to match order-by-operator pathkeys to any index columns. */
+bool
+match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
+						 List *index_clauses, List **orderby_clauses_p,
+						 List **orderby_clausecols_p)
+{
+	ListCell   *lc;
+
+	foreach(lc, pathkeys)
+	{
+		PathKey    *pathkey = lfirst_node(PathKey, lc);
+		Expr	   *expr;
+		int			indexcol = -1;	/* match all index columns */
+
+		expr = match_orderbyop_pathkey(index, pathkey, &indexcol);
+
+		/*
+		 * Note: for any failure to match, we just return NIL immediately.
+		 * There is no value in matching just some of the pathkeys.
+		 */
+		if (!expr)
+			return false;
+
+		*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+		*orderby_clausecols_p = lappend_int(*orderby_clausecols_p, indexcol);
+	}
+
+	return true;				/* success */
+}
+
 /*
  * expand_indexqual_rowcompare --- expand a single indexqual condition
  *		that is a RowCompareExpr
@@ -3183,92 +3293,31 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
  */
 static void
 match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
+						List *index_clauses,
 						List **orderby_clauses_p,
-						List **clause_columns_p)
+						List **orderby_clause_columns_p)
 {
 	List	   *orderby_clauses = NIL;
-	List	   *clause_columns = NIL;
-	ListCell   *lc1;
-
-	*orderby_clauses_p = NIL;	/* set default results */
-	*clause_columns_p = NIL;
+	List	   *orderby_clause_columns = NIL;
+	ammatchorderby_function ammatchorderby =
+	(ammatchorderby_function) index->ammatchorderby;
 
-	/* Only indexes with the amcanorderbyop property are interesting here */
-	if (!index->amcanorderbyop)
-		return;
-
-	foreach(lc1, pathkeys)
+	/* Only indexes with the ammatchorderby function are interesting here */
+	if (ammatchorderby &&
+		ammatchorderby(index, pathkeys, index_clauses,
+					   &orderby_clauses, &orderby_clause_columns))
 	{
-		PathKey    *pathkey = (PathKey *) lfirst(lc1);
-		bool		found = false;
-		ListCell   *lc2;
-
-		/*
-		 * Note: for any failure to match, we just return NIL immediately.
-		 * There is no value in matching just some of the pathkeys.
-		 */
-
-		/* Pathkey must request default sort order for the target opfamily */
-		if (pathkey->pk_strategy != BTLessStrategyNumber ||
-			pathkey->pk_nulls_first)
-			return;
-
-		/* If eclass is volatile, no hope of using an indexscan */
-		if (pathkey->pk_eclass->ec_has_volatile)
-			return;
-
-		/*
-		 * Try to match eclass member expression(s) to index.  Note that child
-		 * EC members are considered, but only when they belong to the target
-		 * relation.  (Unlike regular members, the same expression could be a
-		 * child member of more than one EC.  Therefore, the same index could
-		 * be considered to match more than one pathkey list, which is OK
-		 * here.  See also get_eclass_for_sort_expr.)
-		 */
-		foreach(lc2, pathkey->pk_eclass->ec_members)
-		{
-			EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
-			int			indexcol;
-
-			/* No possibility of match if it references other relations */
-			if (!bms_equal(member->em_relids, index->rel->relids))
-				continue;
+		Assert(list_length(pathkeys) == list_length(orderby_clauses));
+		Assert(list_length(pathkeys) == list_length(orderby_clause_columns));
 
-			/*
-			 * We allow any column of the index to match each pathkey; they
-			 * don't have to match left-to-right as you might expect.  This is
-			 * correct for GiST, and it doesn't matter for SP-GiST because
-			 * that doesn't handle multiple columns anyway, and no other
-			 * existing AMs support amcanorderbyop.  We might need different
-			 * logic in future for other implementations.
-			 */
-			for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
-			{
-				Expr	   *expr;
-
-				expr = match_clause_to_ordering_op(index,
-												   indexcol,
-												   member->em_expr,
-												   pathkey->pk_opfamily);
-				if (expr)
-				{
-					orderby_clauses = lappend(orderby_clauses, expr);
-					clause_columns = lappend_int(clause_columns, indexcol);
-					found = true;
-					break;
-				}
-			}
-
-			if (found)			/* don't want to look at remaining members */
-				break;
-		}
-
-		if (!found)				/* fail if no match for this pathkey */
-			return;
+		*orderby_clauses_p = orderby_clauses;	/* success! */
+		*orderby_clause_columns_p = orderby_clause_columns;
+	}
+	else
+	{
+		*orderby_clauses_p = NIL;	/* set default results */
+		*orderby_clause_columns_p = NIL;
 	}
-
-	*orderby_clauses_p = orderby_clauses;	/* success! */
-	*clause_columns_p = clause_columns;
 }
 
 /*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d6dc83c..2d81fd1 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -267,7 +267,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 			/* We copy just the fields we need, not all of rd_indam */
 			amroutine = indexRelation->rd_indam;
-			info->amcanorderbyop = amroutine->amcanorderbyop;
+			info->ammatchorderby = amroutine->ammatchorderby;
 			info->amoptionalkey = amroutine->amoptionalkey;
 			info->amsearcharray = amroutine->amsearcharray;
 			info->amsearchnulls = amroutine->amsearchnulls;
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c
index 060ffe5..3907065 100644
--- a/src/backend/utils/adt/amutils.c
+++ b/src/backend/utils/adt/amutils.c
@@ -298,7 +298,7 @@ indexam_property(FunctionCallInfo fcinfo,
 				 * a nonkey column, and null otherwise (meaning we don't
 				 * know).
 				 */
-				if (!iskey || !routine->amcanorderbyop)
+				if (!iskey || !routine->ammatchorderby)
 				{
 					res = false;
 					isnull = false;
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 653ddc9..d8fb7d3 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -21,6 +21,9 @@
  */
 struct PlannerInfo;
 struct IndexPath;
+struct IndexOptInfo;
+struct PathKey;
+struct Expr;
 
 /* Likewise, this file shouldn't depend on execnodes.h. */
 struct IndexInfo;
@@ -140,6 +143,13 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan);
 /* restore marked scan position */
 typedef void (*amrestrpos_function) (IndexScanDesc scan);
 
+/* does AM support ORDER BY result of an operator on indexed column? */
+typedef bool (*ammatchorderby_function) (struct IndexOptInfo *index,
+										 List *pathkeys,
+										 List *index_clauses,
+										 List **orderby_clauses_p,
+										 List **orderby_clause_columns_p);
+
 /*
  * Callback function signatures - for parallel index scans.
  */
@@ -170,8 +180,6 @@ typedef struct IndexAmRoutine
 	uint16		amsupport;
 	/* does AM support ORDER BY indexed column's value? */
 	bool		amcanorder;
-	/* does AM support ORDER BY result of an operator on indexed column? */
-	bool		amcanorderbyop;
 	/* does AM support backward scanning? */
 	bool		amcanbackward;
 	/* does AM support UNIQUE indexes? */
@@ -221,6 +229,7 @@ typedef struct IndexAmRoutine
 	amendscan_function amendscan;
 	ammarkpos_function ammarkpos;	/* can be NULL */
 	amrestrpos_function amrestrpos; /* can be NULL */
+	ammatchorderby_function ammatchorderby; /* can be NULL */
 
 	/* interface functions to support parallel index scans */
 	amestimateparallelscan_function amestimateparallelscan; /* can be NULL */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index a008ae0..4680cb8 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -814,7 +814,6 @@ struct IndexOptInfo
 	bool		hypothetical;	/* true if index doesn't really exist */
 
 	/* Remaining fields are copied from the index AM's API struct: */
-	bool		amcanorderbyop; /* does AM support order by operator result? */
 	bool		amoptionalkey;	/* can query omit key for the first column? */
 	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
 	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
@@ -823,6 +822,12 @@ struct IndexOptInfo
 	bool		amcanparallel;	/* does AM support parallel scan? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
+	/* AM order-by match function */
+	bool		(*ammatchorderby) (struct IndexOptInfo *index,
+								   List *pathkeys,
+								   List *index_clause_columns,
+								   List **orderby_clauses_p,
+								   List **orderby_clause_columns_p);
 };
 
 /*
@@ -1133,7 +1138,7 @@ typedef struct Path
  * An empty list implies a full index scan.
  *
  * 'indexorderbys', if not NIL, is a list of ORDER BY expressions that have
- * been found to be usable as ordering operators for an amcanorderbyop index.
+ * been found to be usable as ordering operators for an ammatchorderby index.
  * The list must match the path's pathkeys, ie, one expression per pathkey
  * in the same order.  These are not RestrictInfos, just bare expressions,
  * since they generally won't yield booleans.  It's guaranteed that each
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 6d087c2..bd6ce97 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -388,7 +388,7 @@ typedef struct SampleScan
  * indexorderbyops is a list of the OIDs of the operators used to sort the
  * ORDER BY expressions.  This is used together with indexorderbyorig to
  * recheck ordering at run time.  (Note that indexorderby, indexorderbyorig,
- * and indexorderbyops are used for amcanorderbyop cases, not amcanorder.)
+ * and indexorderbyops are used for ammatchorderby cases, not amcanorder.)
  *
  * indexorderdir specifies the scan ordering, for indexscans on amcanorder
  * indexes (for other indexes it should be "don't care").
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 040335a..e9f4f75 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -79,6 +79,11 @@ extern bool indexcol_is_bool_constant_for_query(IndexOptInfo *index,
 extern bool match_index_to_operand(Node *operand, int indexcol,
 					   IndexOptInfo *index);
 extern void check_index_predicates(PlannerInfo *root, RelOptInfo *rel);
+extern Expr *match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey,
+						int *indexcol_p);
+extern bool match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
+						 List *index_clauses, List **orderby_clauses_p,
+						 List **orderby_clause_columns_p);
 
 /*
  * tidpath.h
0003-Extract-structure-BTScanState-v07.patchtext/x-patch; name=0003-Extract-structure-BTScanState-v07.patchDownload
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index c56ee5e..bf6a6c6 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -214,6 +214,7 @@ bool
 btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 	bool		res;
 
 	/* btree indexes are never lossy */
@@ -224,7 +225,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(so->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
@@ -241,7 +242,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(so->currPos))
+		if (!BTScanPosIsValid(state->currPos))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -259,11 +260,11 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 				 * trying to optimize that, so we don't detect it, but instead
 				 * just forget any excess entries.
 				 */
-				if (so->killedItems == NULL)
-					so->killedItems = (int *)
+				if (state->killedItems == NULL)
+					state->killedItems = (int *)
 						palloc(MaxIndexTuplesPerPage * sizeof(int));
-				if (so->numKilled < MaxIndexTuplesPerPage)
-					so->killedItems[so->numKilled++] = so->currPos.itemIndex;
+				if (state->numKilled < MaxIndexTuplesPerPage)
+					state->killedItems[so->state.numKilled++] = state->currPos.itemIndex;
 			}
 
 			/*
@@ -288,6 +289,7 @@ int64
 btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	int64		ntids = 0;
 	ItemPointer heapTid;
 
@@ -320,7 +322,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				 * Advance to next tuple within page.  This is the same as the
 				 * easy case in _bt_next().
 				 */
-				if (++so->currPos.itemIndex > so->currPos.lastItem)
+				if (++currPos->itemIndex > currPos->lastItem)
 				{
 					/* let _bt_next do the heavy lifting */
 					if (!_bt_next(scan, ForwardScanDirection))
@@ -328,7 +330,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				}
 
 				/* Save tuple ID, and continue scanning */
-				heapTid = &so->currPos.items[so->currPos.itemIndex].heapTid;
+				heapTid = &currPos->items[currPos->itemIndex].heapTid;
 				tbm_add_tuples(tbm, heapTid, 1, false);
 				ntids++;
 			}
@@ -356,8 +358,8 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 
 	/* allocate private workspace */
 	so = (BTScanOpaque) palloc(sizeof(BTScanOpaqueData));
-	BTScanPosInvalidate(so->currPos);
-	BTScanPosInvalidate(so->markPos);
+	BTScanPosInvalidate(so->state.currPos);
+	BTScanPosInvalidate(so->state.markPos);
 	if (scan->numberOfKeys > 0)
 		so->keyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData));
 	else
@@ -368,15 +370,15 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	so->arrayKeys = NULL;
 	so->arrayContext = NULL;
 
-	so->killedItems = NULL;		/* until needed */
-	so->numKilled = 0;
+	so->state.killedItems = NULL;	/* until needed */
+	so->state.numKilled = 0;
 
 	/*
 	 * We don't know yet whether the scan will be index-only, so we do not
 	 * allocate the tuple workspace arrays until btrescan.  However, we set up
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
-	so->currTuples = so->markTuples = NULL;
+	so->state.currTuples = so->state.markTuples = NULL;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -385,6 +387,45 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	return scan;
 }
 
+static void
+_bt_release_current_position(BTScanState state, Relation indexRelation,
+							 bool invalidate)
+{
+	/* we aren't holding any read locks, but gotta drop the pins */
+	if (BTScanPosIsValid(state->currPos))
+	{
+		/* Before leaving current page, deal with any killed items */
+		if (state->numKilled > 0)
+			_bt_killitems(state, indexRelation);
+
+		BTScanPosUnpinIfPinned(state->currPos);
+
+		if (invalidate)
+			BTScanPosInvalidate(state->currPos);
+	}
+}
+
+static void
+_bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
+{
+	/* No need to invalidate positions, if the RAM is about to be freed. */
+	_bt_release_current_position(state, scan->indexRelation, !free);
+
+	state->markItemIndex = -1;
+	BTScanPosUnpinIfPinned(state->markPos);
+
+	if (free)
+	{
+		if (state->killedItems != NULL)
+			pfree(state->killedItems);
+		if (state->currTuples != NULL)
+			pfree(state->currTuples);
+		/* markTuples should not be pfree'd (_bt_allocate_tuple_workspaces) */
+	}
+	else
+		BTScanPosInvalidate(state->markPos);
+}
+
 /*
  *	btrescan() -- rescan an index relation
  */
@@ -393,21 +434,11 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 		 ScanKey orderbys, int norderbys)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-		BTScanPosInvalidate(so->currPos);
-	}
+	_bt_release_scan_state(scan, state, false);
 
-	so->markItemIndex = -1;
 	so->arrayKeyCount = 0;
-	BTScanPosUnpinIfPinned(so->markPos);
-	BTScanPosInvalidate(so->markPos);
 
 	/*
 	 * Allocate tuple workspace arrays, if needed for an index-only scan and
@@ -425,11 +456,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 	 * a SIGSEGV is not possible.  Yeah, this is ugly as sin, but it beats
 	 * adding special-case treatment for name_ops elsewhere.
 	 */
-	if (scan->xs_want_itup && so->currTuples == NULL)
-	{
-		so->currTuples = (char *) palloc(BLCKSZ * 2);
-		so->markTuples = so->currTuples + BLCKSZ;
-	}
+	if (scan->xs_want_itup && state->currTuples == NULL)
+		_bt_allocate_tuple_workspaces(state);
 
 	/*
 	 * Reset the scan keys. Note that keys ordering stuff moved to _bt_first.
@@ -453,19 +481,7 @@ btendscan(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-	}
-
-	so->markItemIndex = -1;
-	BTScanPosUnpinIfPinned(so->markPos);
-
-	/* No need to invalidate positions, the RAM is about to be freed. */
+	_bt_release_scan_state(scan, &so->state, true);
 
 	/* Release storage */
 	if (so->keyData != NULL)
@@ -473,24 +489,15 @@ btendscan(IndexScanDesc scan)
 	/* so->arrayKeyData and so->arrayKeys are in arrayContext */
 	if (so->arrayContext != NULL)
 		MemoryContextDelete(so->arrayContext);
-	if (so->killedItems != NULL)
-		pfree(so->killedItems);
-	if (so->currTuples != NULL)
-		pfree(so->currTuples);
-	/* so->markTuples should not be pfree'd, see btrescan */
+
 	pfree(so);
 }
 
-/*
- *	btmarkpos() -- save current scan position
- */
-void
-btmarkpos(IndexScanDesc scan)
+static void
+_bt_mark_current_position(BTScanState state)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-
 	/* There may be an old mark with a pin (but no lock). */
-	BTScanPosUnpinIfPinned(so->markPos);
+	BTScanPosUnpinIfPinned(state->markPos);
 
 	/*
 	 * Just record the current itemIndex.  If we later step to next page
@@ -498,32 +505,34 @@ btmarkpos(IndexScanDesc scan)
 	 * the currPos struct in markPos.  If (as often happens) the mark is moved
 	 * before we leave the page, we don't have to do that work.
 	 */
-	if (BTScanPosIsValid(so->currPos))
-		so->markItemIndex = so->currPos.itemIndex;
+	if (BTScanPosIsValid(state->currPos))
+		state->markItemIndex = state->currPos.itemIndex;
 	else
 	{
-		BTScanPosInvalidate(so->markPos);
-		so->markItemIndex = -1;
+		BTScanPosInvalidate(state->markPos);
+		state->markItemIndex = -1;
 	}
-
-	/* Also record the current positions of any array keys */
-	if (so->numArrayKeys)
-		_bt_mark_array_keys(scan);
 }
 
 /*
- *	btrestrpos() -- restore scan to last saved position
+ *	btmarkpos() -- save current scan position
  */
 void
-btrestrpos(IndexScanDesc scan)
+btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* Restore the marked positions of any array keys */
+	_bt_mark_current_position(&so->state);
+
+	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
-		_bt_restore_array_keys(scan);
+		_bt_mark_array_keys(scan);
+}
 
-	if (so->markItemIndex >= 0)
+static void
+_bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
+{
+	if (state->markItemIndex >= 0)
 	{
 		/*
 		 * The scan has never moved to a new page since the last mark.  Just
@@ -532,7 +541,7 @@ btrestrpos(IndexScanDesc scan)
 		 * NB: In this case we can't count on anything in so->markPos to be
 		 * accurate.
 		 */
-		so->currPos.itemIndex = so->markItemIndex;
+		state->currPos.itemIndex = state->markItemIndex;
 	}
 	else
 	{
@@ -542,28 +551,21 @@ btrestrpos(IndexScanDesc scan)
 		 * locks, but if we're still holding the pin for the current position,
 		 * we must drop it.
 		 */
-		if (BTScanPosIsValid(so->currPos))
-		{
-			/* Before leaving current page, deal with any killed items */
-			if (so->numKilled > 0)
-				_bt_killitems(scan);
-			BTScanPosUnpinIfPinned(so->currPos);
-		}
+		_bt_release_current_position(state, scan->indexRelation,
+									 !BTScanPosIsValid(state->markPos));
 
-		if (BTScanPosIsValid(so->markPos))
+		if (BTScanPosIsValid(state->markPos))
 		{
 			/* bump pin on mark buffer for assignment to current buffer */
-			if (BTScanPosIsPinned(so->markPos))
-				IncrBufferRefCount(so->markPos.buf);
-			memcpy(&so->currPos, &so->markPos,
+			if (BTScanPosIsPinned(state->markPos))
+				IncrBufferRefCount(state->markPos.buf);
+			memcpy(&state->currPos, &state->markPos,
 				   offsetof(BTScanPosData, items[1]) +
-				   so->markPos.lastItem * sizeof(BTScanPosItem));
-			if (so->currTuples)
-				memcpy(so->currTuples, so->markTuples,
-					   so->markPos.nextTupleOffset);
+				   state->markPos.lastItem * sizeof(BTScanPosItem));
+			if (state->currTuples)
+				memcpy(state->currTuples, state->markTuples,
+					   state->markPos.nextTupleOffset);
 		}
-		else
-			BTScanPosInvalidate(so->currPos);
 	}
 }
 
@@ -779,9 +781,10 @@ _bt_parallel_advance_array_keys(IndexScanDesc scan)
 }
 
 /*
- * _bt_vacuum_needs_cleanup() -- Checks if index needs cleanup assuming that
- *			btbulkdelete() wasn't called.
- */
+- * _bt_vacuum_needs_cleanup() -- Checks if index needs cleanup assuming that
+- *			btbulkdelete() wasn't called.
++ *	btrestrpos() -- restore scan to last saved position
+  */
 static bool
 _bt_vacuum_needs_cleanup(IndexVacuumInfo *info)
 {
@@ -844,6 +847,21 @@ _bt_vacuum_needs_cleanup(IndexVacuumInfo *info)
 }
 
 /*
+ *	btrestrpos() -- restore scan to last saved position
+ */
+void
+btrestrpos(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
+	/* Restore the marked positions of any array keys */
+	if (so->numArrayKeys)
+		_bt_restore_array_keys(scan);
+
+	_bt_restore_marked_position(scan, &so->state);
+}
+
+/*
  * Bulk deletion of all index entries pointing to a set of heap tuples.
  * The set of target tuples is specified via a callback routine that tells
  * whether any given heap tuple (identified by ItemPointer) is being deleted.
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 9283223..cbd72bd 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -24,18 +24,19 @@
 #include "utils/rel.h"
 
 
-static bool _bt_readpage(IndexScanDesc scan, ScanDirection dir,
+static bool _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 			 OffsetNumber offnum);
-static void _bt_saveitem(BTScanOpaque so, int itemIndex,
+static void _bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup);
-static bool _bt_steppage(IndexScanDesc scan, ScanDirection dir);
-static bool _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir);
+static bool _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir);
+static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
+				 BlockNumber blkno, ScanDirection dir);
 static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
 					  ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf, Snapshot snapshot);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
-static inline void _bt_initialize_more_data(BTScanOpaque so, ScanDirection dir);
+static inline void _bt_initialize_more_data(BTScanState state, ScanDirection dir);
 
 
 /*
@@ -544,6 +545,58 @@ _bt_compare(Relation rel,
 }
 
 /*
+ * _bt_return_current_item() -- Prepare current scan state item for return.
+ *
+ * This function is used only in "return _bt_return_current_item();" statements
+ * and always returns true.
+ */
+static inline bool
+_bt_return_current_item(IndexScanDesc scan, BTScanState state)
+{
+	BTScanPosItem *currItem = &state->currPos.items[state->currPos.itemIndex];
+
+	scan->xs_ctup.t_self = currItem->heapTid;
+
+	if (scan->xs_want_itup)
+		scan->xs_itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+
+	return true;
+}
+
+/*
+ * _bt_load_first_page() -- Load data from the first page of the scan.
+ *
+ * Caller must have pinned and read-locked state->currPos.buf.
+ *
+ * On success exit, state->currPos is updated to contain data from the next
+ * interesting page.  For success on a scan using a non-MVCC snapshot we hold
+ * a pin, but not a read lock, on that page.  If we do not hold the pin, we
+ * set state->currPos.buf to InvalidBuffer.  We return true to indicate success.
+ *
+ * If there are no more matching records in the given direction at all,
+ * we drop all locks and pins, set state->currPos.buf to InvalidBuffer,
+ * and return false.
+ */
+static bool
+_bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+					OffsetNumber offnum)
+{
+	if (!_bt_readpage(scan, state, dir, offnum))
+	{
+		/*
+		 * There's no actually-matching data on this page.  Try to advance to
+		 * the next page.  Return false if there's no matching data at all.
+		 */
+		LockBuffer(state->currPos.buf, BUFFER_LOCK_UNLOCK);
+		return _bt_steppage(scan, state, dir);
+	}
+
+	/* Drop the lock, and maybe the pin, on the current page */
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
+	return true;
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -568,6 +621,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	BTStack		stack;
 	OffsetNumber offnum;
@@ -581,10 +635,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	int			i;
 	bool		status = true;
 	StrategyNumber strat_total;
-	BTScanPosItem *currItem;
 	BlockNumber blkno;
 
-	Assert(!BTScanPosIsValid(so->currPos));
+	Assert(!BTScanPosIsValid(*currPos));
 
 	pgstat_count_index_scan(rel);
 
@@ -1075,7 +1128,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 * their scan
 		 */
 		_bt_parallel_done(scan);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 
 		return false;
 	}
@@ -1083,7 +1136,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		PredicateLockPage(rel, BufferGetBlockNumber(buf),
 						  scan->xs_snapshot);
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
 	/* position to the precise item on the page */
 	offnum = _bt_binsrch(rel, buf, keysCount, scankeys, nextkey);
@@ -1110,36 +1163,36 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		offnum = OffsetNumberPrev(offnum);
 
 	/* remember which buffer we have pinned, if any */
-	Assert(!BTScanPosIsValid(so->currPos));
-	so->currPos.buf = buf;
+	Assert(!BTScanPosIsValid(*currPos));
+	currPos->buf = buf;
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, offnum))
+	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
+		return false;
+
+readcomplete:
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
+}
+
+/*
+ *	Advance to next tuple on current page; or if there's no more,
+ *	try to step to the next page with data.
+ */
+static bool
+_bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
+{
+	if (ScanDirectionIsForward(dir))
 	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
+		if (++state->currPos.itemIndex <= state->currPos.lastItem)
+			return true;
 	}
 	else
 	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+		if (--state->currPos.itemIndex >= state->currPos.firstItem)
+			return true;
 	}
 
-readcomplete:
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_steppage(scan, state, dir);
 }
 
 /*
@@ -1160,44 +1213,20 @@ bool
 _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-	BTScanPosItem *currItem;
 
-	/*
-	 * Advance to next tuple on current page; or if there's no more, try to
-	 * step to the next page with data.
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (++so->currPos.itemIndex > so->currPos.lastItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
-	else
-	{
-		if (--so->currPos.itemIndex < so->currPos.firstItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
+	if (!_bt_next_item(scan, &so->state, dir))
+		return false;
 
 	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
  *	_bt_readpage() -- Load data from current index page into so->currPos
  *
- * Caller must have pinned and read-locked so->currPos.buf; the buffer's state
- * is not changed here.  Also, currPos.moreLeft and moreRight must be valid;
- * they are updated as appropriate.  All other fields of so->currPos are
+ * Caller must have pinned and read-locked pos->buf; the buffer's state
+ * is not changed here.  Also, pos->moreLeft and moreRight must be valid;
+ * they are updated as appropriate.  All other fields of pos are
  * initialized from scratch here.
  *
  * We scan the current page starting at offnum and moving in the indicated
@@ -1212,9 +1241,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
  * Returns true if any matching items found on the page, false if none.
  */
 static bool
-_bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
+_bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+			 OffsetNumber offnum)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
@@ -1227,9 +1257,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We must have the buffer pinned and locked, but the usual macro can't be
 	 * used here; this function is what makes it good for currPos.
 	 */
-	Assert(BufferIsValid(so->currPos.buf));
+	Assert(BufferIsValid(pos->buf));
 
-	page = BufferGetPage(so->currPos.buf);
+	page = BufferGetPage(pos->buf);
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 
 	/* allow next page be processed by parallel worker */
@@ -1238,7 +1268,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		if (ScanDirectionIsForward(dir))
 			_bt_parallel_release(scan, opaque->btpo_next);
 		else
-			_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+			_bt_parallel_release(scan, BufferGetBlockNumber(pos->buf));
 	}
 
 	minoff = P_FIRSTDATAKEY(opaque);
@@ -1248,30 +1278,30 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 	 * We note the buffer's block number so that we can release the pin later.
 	 * This allows us to re-read the buffer if it is needed again for hinting.
 	 */
-	so->currPos.currPage = BufferGetBlockNumber(so->currPos.buf);
+	pos->currPage = BufferGetBlockNumber(pos->buf);
 
 	/*
 	 * We save the LSN of the page as we read it, so that we know whether it
 	 * safe to apply LP_DEAD hints to the page later.  This allows us to drop
 	 * the pin for MVCC scans, which allows vacuum to avoid blocking.
 	 */
-	so->currPos.lsn = BufferGetLSNAtomic(so->currPos.buf);
+	pos->lsn = BufferGetLSNAtomic(pos->buf);
 
 	/*
 	 * we must save the page's right-link while scanning it; this tells us
 	 * where to step right to after we're done with these items.  There is no
 	 * corresponding need for the left-link, since splits always go right.
 	 */
-	so->currPos.nextPage = opaque->btpo_next;
+	pos->nextPage = opaque->btpo_next;
 
 	/* initialize tuple workspace to empty */
-	so->currPos.nextTupleOffset = 0;
+	pos->nextTupleOffset = 0;
 
 	/*
 	 * Now that the current page has been made consistent, the macro should be
 	 * good.
 	 */
-	Assert(BTScanPosIsPinned(so->currPos));
+	Assert(BTScanPosIsPinned(*pos));
 
 	if (ScanDirectionIsForward(dir))
 	{
@@ -1286,13 +1316,13 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			if (itup != NULL)
 			{
 				/* tuple passes all scan key conditions, so remember it */
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 				itemIndex++;
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreRight = false;
+				pos->moreRight = false;
 				break;
 			}
 
@@ -1300,9 +1330,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex <= MaxIndexTuplesPerPage);
-		so->currPos.firstItem = 0;
-		so->currPos.lastItem = itemIndex - 1;
-		so->currPos.itemIndex = 0;
+		pos->firstItem = 0;
+		pos->lastItem = itemIndex - 1;
+		pos->itemIndex = 0;
 	}
 	else
 	{
@@ -1318,12 +1348,12 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 			{
 				/* tuple passes all scan key conditions, so remember it */
 				itemIndex--;
-				_bt_saveitem(so, itemIndex, offnum, itup);
+				_bt_saveitem(state, itemIndex, offnum, itup);
 			}
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreLeft = false;
+				pos->moreLeft = false;
 				break;
 			}
 
@@ -1331,30 +1361,31 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
 		}
 
 		Assert(itemIndex >= 0);
-		so->currPos.firstItem = itemIndex;
-		so->currPos.lastItem = MaxIndexTuplesPerPage - 1;
-		so->currPos.itemIndex = MaxIndexTuplesPerPage - 1;
+		pos->firstItem = itemIndex;
+		pos->lastItem = MaxIndexTuplesPerPage - 1;
+		pos->itemIndex = MaxIndexTuplesPerPage - 1;
 	}
 
-	return (so->currPos.firstItem <= so->currPos.lastItem);
+	return (pos->firstItem <= pos->lastItem);
 }
 
 /* Save an index item into so->currPos.items[itemIndex] */
 static void
-_bt_saveitem(BTScanOpaque so, int itemIndex,
+_bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup)
 {
-	BTScanPosItem *currItem = &so->currPos.items[itemIndex];
+	BTScanPosItem *currItem = &state->currPos.items[itemIndex];
 
 	currItem->heapTid = itup->t_tid;
 	currItem->indexOffset = offnum;
-	if (so->currTuples)
+	if (state->currTuples)
 	{
 		Size		itupsz = IndexTupleSize(itup);
 
-		currItem->tupleOffset = so->currPos.nextTupleOffset;
-		memcpy(so->currTuples + so->currPos.nextTupleOffset, itup, itupsz);
-		so->currPos.nextTupleOffset += MAXALIGN(itupsz);
+		currItem->tupleOffset = state->currPos.nextTupleOffset;
+		memcpy(state->currTuples + state->currPos.nextTupleOffset,
+			   itup, itupsz);
+		state->currPos.nextTupleOffset += MAXALIGN(itupsz);
 	}
 }
 
@@ -1370,35 +1401,36 @@ _bt_saveitem(BTScanOpaque so, int itemIndex,
  * to InvalidBuffer.  We return true to indicate success.
  */
 static bool
-_bt_steppage(IndexScanDesc scan, ScanDirection dir)
+_bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
+	Relation	rel = scan->indexRelation;
 	BlockNumber blkno = InvalidBlockNumber;
 	bool		status = true;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(*currPos));
 
 	/* Before leaving current page, deal with any killed items */
-	if (so->numKilled > 0)
-		_bt_killitems(scan);
+	if (state->numKilled > 0)
+		_bt_killitems(state, rel);
 
 	/*
 	 * Before we modify currPos, make a copy of the page data if there was a
 	 * mark position that needs it.
 	 */
-	if (so->markItemIndex >= 0)
+	if (state->markItemIndex >= 0)
 	{
 		/* bump pin on current buffer for assignment to mark buffer */
-		if (BTScanPosIsPinned(so->currPos))
-			IncrBufferRefCount(so->currPos.buf);
-		memcpy(&so->markPos, &so->currPos,
+		if (BTScanPosIsPinned(*currPos))
+			IncrBufferRefCount(currPos->buf);
+		memcpy(&state->markPos, currPos,
 			   offsetof(BTScanPosData, items[1]) +
-			   so->currPos.lastItem * sizeof(BTScanPosItem));
-		if (so->markTuples)
-			memcpy(so->markTuples, so->currTuples,
-				   so->currPos.nextTupleOffset);
-		so->markPos.itemIndex = so->markItemIndex;
-		so->markItemIndex = -1;
+			   currPos->lastItem * sizeof(BTScanPosItem));
+		if (state->markTuples)
+			memcpy(state->markTuples, state->currTuples,
+				   currPos->nextTupleOffset);
+		state->markPos.itemIndex = state->markItemIndex;
+		state->markItemIndex = -1;
 	}
 
 	if (ScanDirectionIsForward(dir))
@@ -1414,27 +1446,27 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
-				BTScanPosUnpinIfPinned(so->currPos);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosUnpinIfPinned(*currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so use the previously-saved nextPage link. */
-			blkno = so->currPos.nextPage;
+			blkno = currPos->nextPage;
 		}
 
 		/* Remember we left a page with data */
-		so->currPos.moreLeft = true;
+		currPos->moreLeft = true;
 
 		/* release the previous buffer, if pinned */
-		BTScanPosUnpinIfPinned(so->currPos);
+		BTScanPosUnpinIfPinned(*currPos);
 	}
 	else
 	{
 		/* Remember we left a page with data */
-		so->currPos.moreRight = true;
+		currPos->moreRight = true;
 
 		if (scan->parallel_scan != NULL)
 		{
@@ -1443,25 +1475,25 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			 * ended already, bail out.
 			 */
 			status = _bt_parallel_seize(scan, &blkno);
-			BTScanPosUnpinIfPinned(so->currPos);
+			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so just use our own notion of the current page */
-			blkno = so->currPos.currPage;
+			blkno = currPos->currPage;
 		}
 	}
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, currPos);
 
 	return true;
 }
@@ -1477,9 +1509,10 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
  * locks and pins, set so->currPos.buf to InvalidBuffer, and return false.
  */
 static bool
-_bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+				 ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
 	Relation	rel;
 	Page		page;
 	BTPageOpaque opaque;
@@ -1495,17 +1528,17 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * if we're at end of scan, give up and mark parallel scan as
 			 * done, so that all the workers can finish their scan
 			 */
-			if (blkno == P_NONE || !so->currPos.moreRight)
+			if (blkno == P_NONE || !currPos->moreRight)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 			/* check for interrupts while we're not holding any buffer lock */
 			CHECK_FOR_INTERRUPTS();
 			/* step right one page */
-			so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
-			page = BufferGetPage(so->currPos.buf);
+			currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			/* check for deleted page */
@@ -1514,7 +1547,7 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 				PredicateLockPage(rel, blkno, scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreRight if we can stop */
-				if (_bt_readpage(scan, dir, P_FIRSTDATAKEY(opaque)))
+				if (_bt_readpage(scan, state, dir, P_FIRSTDATAKEY(opaque)))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
@@ -1526,18 +1559,18 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
 			}
 			else
 			{
 				blkno = opaque->btpo_next;
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 			}
 		}
 	}
@@ -1547,10 +1580,10 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * Should only happen in parallel cases, when some other backend
 		 * advanced the scan.
 		 */
-		if (so->currPos.currPage != blkno)
+		if (currPos->currPage != blkno)
 		{
-			BTScanPosUnpinIfPinned(so->currPos);
-			so->currPos.currPage = blkno;
+			BTScanPosUnpinIfPinned(*currPos);
+			currPos->currPage = blkno;
 		}
 
 		/*
@@ -1575,31 +1608,30 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * is MVCC the page cannot move past the half-dead state to fully
 		 * deleted.
 		 */
-		if (BTScanPosIsPinned(so->currPos))
-			LockBuffer(so->currPos.buf, BT_READ);
+		if (BTScanPosIsPinned(*currPos))
+			LockBuffer(currPos->buf, BT_READ);
 		else
-			so->currPos.buf = _bt_getbuf(rel, so->currPos.currPage, BT_READ);
+			currPos->buf = _bt_getbuf(rel, currPos->currPage, BT_READ);
 
 		for (;;)
 		{
 			/* Done if we know there are no matching keys to the left */
-			if (!so->currPos.moreLeft)
+			if (!currPos->moreLeft)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
 			/* Step to next physical page */
-			so->currPos.buf = _bt_walk_left(rel, so->currPos.buf,
-											scan->xs_snapshot);
+			currPos->buf = _bt_walk_left(rel, currPos->buf, scan->xs_snapshot);
 
 			/* if we're physically at end of index, return failure */
-			if (so->currPos.buf == InvalidBuffer)
+			if (currPos->buf == InvalidBuffer)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
@@ -1608,21 +1640,21 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * it's not half-dead and contains matching tuples. Else loop back
 			 * and do it all again.
 			 */
-			page = BufferGetPage(so->currPos.buf);
+			page = BufferGetPage(currPos->buf);
 			TestForOldSnapshot(scan->xs_snapshot, rel, page);
 			opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 			if (!P_IGNORE(opaque))
 			{
-				PredicateLockPage(rel, BufferGetBlockNumber(so->currPos.buf), scan->xs_snapshot);
+				PredicateLockPage(rel, BufferGetBlockNumber(currPos->buf), scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreLeft if we can stop */
-				if (_bt_readpage(scan, dir, PageGetMaxOffsetNumber(page)))
+				if (_bt_readpage(scan, state, dir, PageGetMaxOffsetNumber(page)))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -1633,14 +1665,14 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
-				so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
+				currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
 			}
 		}
 	}
@@ -1659,13 +1691,13 @@ _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
 
 	return true;
 }
@@ -1890,11 +1922,11 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber start;
-	BTScanPosItem *currItem;
 
 	/*
 	 * Scan down to the leftmost or rightmost leaf page.  This is a simplified
@@ -1910,7 +1942,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 		 * exists.
 		 */
 		PredicateLockRelation(rel, scan->xs_snapshot);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 		return false;
 	}
 
@@ -1939,36 +1971,15 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 	}
 
 	/* remember which buffer we have pinned */
-	so->currPos.buf = buf;
+	currPos->buf = buf;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, start))
-	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
-		if (!_bt_steppage(scan, dir))
-			return false;
-	}
-	else
-	{
-		/* Drop the lock, and maybe the pin, on the current page */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
-	}
-
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_ctup.t_self = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
+	if (!_bt_load_first_page(scan, &so->state, dir, start))
+		return false;
 
-	return true;
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
@@ -1976,19 +1987,19 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
  * for scan direction
  */
 static inline void
-_bt_initialize_more_data(BTScanOpaque so, ScanDirection dir)
+_bt_initialize_more_data(BTScanState state, ScanDirection dir)
 {
 	/* initialize moreLeft/moreRight appropriately for scan direction */
 	if (ScanDirectionIsForward(dir))
 	{
-		so->currPos.moreLeft = false;
-		so->currPos.moreRight = true;
+		state->currPos.moreLeft = false;
+		state->currPos.moreRight = true;
 	}
 	else
 	{
-		so->currPos.moreLeft = true;
-		so->currPos.moreRight = false;
+		state->currPos.moreLeft = true;
+		state->currPos.moreRight = false;
 	}
-	so->numKilled = 0;			/* just paranoia */
-	so->markItemIndex = -1;		/* ditto */
+	state->numKilled = 0;		/* just paranoia */
+	state->markItemIndex = -1;	/* ditto */
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 2c05fb5..e548354 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -1741,26 +1741,26 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, TupleDesc tupdesc,
  * away and the TID was re-used by a completely different heap tuple.
  */
 void
-_bt_killitems(IndexScanDesc scan)
+_bt_killitems(BTScanState state, Relation indexRelation)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
 	OffsetNumber maxoff;
 	int			i;
-	int			numKilled = so->numKilled;
+	int			numKilled = state->numKilled;
 	bool		killedsomething = false;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(state->currPos));
 
 	/*
 	 * Always reset the scan state, so we don't look for same items on other
 	 * pages.
 	 */
-	so->numKilled = 0;
+	state->numKilled = 0;
 
-	if (BTScanPosIsPinned(so->currPos))
+	if (BTScanPosIsPinned(*pos))
 	{
 		/*
 		 * We have held the pin on this page since we read the index tuples,
@@ -1768,44 +1768,42 @@ _bt_killitems(IndexScanDesc scan)
 		 * re-use of any TID on the page, so there is no need to check the
 		 * LSN.
 		 */
-		LockBuffer(so->currPos.buf, BT_READ);
-
-		page = BufferGetPage(so->currPos.buf);
+		LockBuffer(pos->buf, BT_READ);
 	}
 	else
 	{
 		Buffer		buf;
 
 		/* Attempt to re-read the buffer, getting pin and lock. */
-		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ);
+		buf = _bt_getbuf(indexRelation, pos->currPage, BT_READ);
 
 		/* It might not exist anymore; in which case we can't hint it. */
 		if (!BufferIsValid(buf))
 			return;
 
-		page = BufferGetPage(buf);
-		if (BufferGetLSNAtomic(buf) == so->currPos.lsn)
-			so->currPos.buf = buf;
+		if (BufferGetLSNAtomic(buf) == pos->lsn)
+			pos->buf = buf;
 		else
 		{
 			/* Modified while not pinned means hinting is not safe. */
-			_bt_relbuf(scan->indexRelation, buf);
+			_bt_relbuf(indexRelation, buf);
 			return;
 		}
 	}
 
+	page = BufferGetPage(pos->buf);
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 	minoff = P_FIRSTDATAKEY(opaque);
 	maxoff = PageGetMaxOffsetNumber(page);
 
 	for (i = 0; i < numKilled; i++)
 	{
-		int			itemIndex = so->killedItems[i];
-		BTScanPosItem *kitem = &so->currPos.items[itemIndex];
+		int			itemIndex = state->killedItems[i];
+		BTScanPosItem *kitem = &pos->items[itemIndex];
 		OffsetNumber offnum = kitem->indexOffset;
 
-		Assert(itemIndex >= so->currPos.firstItem &&
-			   itemIndex <= so->currPos.lastItem);
+		Assert(itemIndex >= pos->firstItem &&
+			   itemIndex <= pos->lastItem);
 		if (offnum < minoff)
 			continue;			/* pure paranoia */
 		while (offnum <= maxoff)
@@ -1833,10 +1831,10 @@ _bt_killitems(IndexScanDesc scan)
 	if (killedsomething)
 	{
 		opaque->btpo_flags |= BTP_HAS_GARBAGE;
-		MarkBufferDirtyHint(so->currPos.buf, true);
+		MarkBufferDirtyHint(pos->buf, true);
 	}
 
-	LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
+	LockBuffer(pos->buf, BUFFER_LOCK_UNLOCK);
 }
 
 
@@ -2214,3 +2212,14 @@ _bt_check_natts(Relation rel, Page page, OffsetNumber offnum)
 
 	}
 }
+
+/*
+ * _bt_allocate_tuple_workspaces() -- Allocate buffers for saving index tuples
+ * 		in index-only scans.
+ */
+void
+_bt_allocate_tuple_workspaces(BTScanState state)
+{
+	state->currTuples = (char *) palloc(BLCKSZ * 2);
+	state->markTuples = state->currTuples + BLCKSZ;
+}
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 4fb92d6..d78df29 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -433,22 +433,8 @@ typedef struct BTArrayKeyInfo
 	Datum	   *elem_values;	/* array of num_elems Datums */
 } BTArrayKeyInfo;
 
-typedef struct BTScanOpaqueData
+typedef struct BTScanStateData
 {
-	/* these fields are set by _bt_preprocess_keys(): */
-	bool		qual_ok;		/* false if qual can never be satisfied */
-	int			numberOfKeys;	/* number of preprocessed scan keys */
-	ScanKey		keyData;		/* array of preprocessed scan keys */
-
-	/* workspace for SK_SEARCHARRAY support */
-	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
-	int			numArrayKeys;	/* number of equality-type array keys (-1 if
-								 * there are any unsatisfiable array keys) */
-	int			arrayKeyCount;	/* count indicating number of array scan keys
-								 * processed */
-	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
-	MemoryContext arrayContext; /* scan-lifespan context for array data */
-
 	/* info about killed items if any (killedItems is NULL if never used) */
 	int		   *killedItems;	/* currPos.items indexes of killed items */
 	int			numKilled;		/* number of currently stored items */
@@ -473,6 +459,27 @@ typedef struct BTScanOpaqueData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+} BTScanStateData;
+
+typedef BTScanStateData *BTScanState;
+
+typedef struct BTScanOpaqueData
+{
+	/* these fields are set by _bt_preprocess_keys(): */
+	bool		qual_ok;		/* false if qual can never be satisfied */
+	int			numberOfKeys;	/* number of preprocessed scan keys */
+	ScanKey		keyData;		/* array of preprocessed scan keys */
+
+	/* workspace for SK_SEARCHARRAY support */
+	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
+	int			numArrayKeys;	/* number of equality-type array keys (-1 if
+								 * there are any unsatisfiable array keys) */
+	int			arrayKeyCount;	/* count indicating number of array scan keys
+								 * processed */
+	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
+	MemoryContext arrayContext; /* scan-lifespan context for array data */
+
+	BTScanStateData	state;
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -589,7 +596,7 @@ extern void _bt_preprocess_keys(IndexScanDesc scan);
 extern IndexTuple _bt_checkkeys(IndexScanDesc scan,
 			  Page page, OffsetNumber offnum,
 			  ScanDirection dir, bool *continuescan);
-extern void _bt_killitems(IndexScanDesc scan);
+extern void _bt_killitems(BTScanState state, Relation indexRelation);
 extern BTCycleId _bt_vacuum_cycleid(Relation rel);
 extern BTCycleId _bt_start_vacuum(Relation rel);
 extern void _bt_end_vacuum(Relation rel);
@@ -602,6 +609,7 @@ extern bool btproperty(Oid index_oid, int attno,
 		   bool *res, bool *isnull);
 extern IndexTuple _bt_nonkey_truncate(Relation rel, IndexTuple itup);
 extern bool _bt_check_natts(Relation rel, Page page, OffsetNumber offnum);
+extern void _bt_allocate_tuple_workspaces(BTScanState state);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3d3c76d..5c065a5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -181,6 +181,8 @@ BTScanOpaqueData
 BTScanPos
 BTScanPosData
 BTScanPosItem
+BTScanState
+BTScanStateData
 BTShared
 BTSortArrayContext
 BTSpool
0004-Add-kNN-support-to-btree-v07.patchtext/x-patch; name=0004-Add-kNN-support-to-btree-v07.patchDownload
diff --git a/doc/src/sgml/btree.sgml b/doc/src/sgml/btree.sgml
index 996932e..b94be7a 100644
--- a/doc/src/sgml/btree.sgml
+++ b/doc/src/sgml/btree.sgml
@@ -200,6 +200,53 @@
   planner relies on them for optimization purposes.
  </para>
 
+ <para>
+  To implement the distance ordered (nearest-neighbor) search, we only need
+  to define a distance operator (usually it called
+  <literal>&lt;-&gt;</literal>) with a correpsonding operator family for
+  distance comparison in the index's operator class.  These operators must
+  satisfy the following assumptions for all non-null values
+  <replaceable>A</replaceable>, <replaceable>B</replaceable>,
+  <replaceable>C</replaceable> of the datatype:
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     <replaceable>A</replaceable> <literal>&lt;-&gt;</literal>
+     <replaceable>B</replaceable> <literal>=</literal>
+     <replaceable>B</replaceable> <literal>&lt;-&gt;</literal>
+     <replaceable>A</replaceable>
+     (<firstterm>symmetric law</firstterm>)
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     if <replaceable>A</replaceable> <literal>=</literal>
+     <replaceable>B</replaceable>, then <replaceable>A</replaceable>
+     <literal>&lt;-&gt;</literal> <replaceable>C</replaceable>
+     <literal>=</literal> <replaceable>B</replaceable>
+     <literal>&lt;-&gt;</literal> <replaceable>C</replaceable>
+     (<firstterm>distance equivalence</firstterm>)
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      if (<replaceable>A</replaceable> <literal>&lt;=</literal>
+      <replaceable>B</replaceable> and <replaceable>B</replaceable>
+      <literal>&lt;=</literal> <replaceable>C</replaceable>) or
+      (<replaceable>A</replaceable> <literal>&gt;=</literal>
+      <replaceable>B</replaceable> and <replaceable>B</replaceable>
+      <literal>&gt;=</literal> <replaceable>C</replaceable>),
+      then <replaceable>A</replaceable> <literal>&lt;-&gt;</literal>
+      <replaceable>B</replaceable> <literal>&lt;=</literal>
+      <replaceable>A</replaceable> <literal>&lt;-&gt;</literal>
+      <replaceable>C</replaceable>
+     (<firstterm>monotonicity</firstterm>)
+    </para>
+   </listitem>
+  </itemizedlist>
+ </para>
+
 </sect1>
 
 <sect1 id="btree-support-funcs">
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 46f427b..1998171 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -175,6 +175,17 @@ CREATE INDEX test1_id_index ON test1 (id);
   </para>
 
   <para>
+   B-tree indexes are also capable of optimizing <quote>nearest-neighbor</quote>
+   searches, such as
+<programlisting><![CDATA[
+SELECT * FROM events ORDER BY event_date <-> date '2017-05-05' LIMIT 10;
+]]>
+</programlisting>
+   which finds the ten events closest to a given target date.  The ability
+   to do this is again dependent on the particular operator class being used.
+  </para>
+
+  <para>
    <indexterm>
     <primary>index</primary>
     <secondary>hash</secondary>
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 9446f8b..93094bc 100644
--- a/doc/src/sgml/xindex.sgml
+++ b/doc/src/sgml/xindex.sgml
@@ -1242,7 +1242,8 @@ SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
   <title>Ordering Operators</title>
 
   <para>
-   Some index access methods (currently, only GiST and SP-GiST) support the concept of
+   Some index access methods (currently, only B-tree, GiST and SP-GiST) 
+   support the concept of
    <firstterm>ordering operators</firstterm>.  What we have been discussing so far
    are <firstterm>search operators</firstterm>.  A search operator is one for which
    the index can be searched to find all rows satisfying
diff --git a/src/backend/access/nbtree/README b/src/backend/access/nbtree/README
index 3680e69..3f7e1b1 100644
--- a/src/backend/access/nbtree/README
+++ b/src/backend/access/nbtree/README
@@ -659,3 +659,20 @@ routines must treat it accordingly.  The actual key stored in the
 item is irrelevant, and need not be stored at all.  This arrangement
 corresponds to the fact that an L&Y non-leaf page has one more pointer
 than key.
+
+Nearest-neighbor search
+-----------------------
+
+There is a special scan strategy for nearest-neighbor (kNN) search,
+that is used in queries with ORDER BY distance clauses like this:
+SELECT * FROM tab WHERE col > const1 ORDER BY col <-> const2 LIMIT k.
+But, unlike GiST, B-tree supports only a one ordering operator on the
+first index column.
+
+At the beginning of kNN scan, we need to determine which strategy we
+will use --- a special bidirectional or a ordinary unidirectional.
+If the point from which we measure the distance falls into the scan range,
+we use bidirectional scan starting from this point, else we use simple
+unidirectional scan in the right direction.  Algorithm of a bidirectional
+scan is very simple: at each step we advancing scan in that direction,
+which has the nearest point.
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index bf6a6c6..fb413e7 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -25,6 +25,9 @@
 #include "commands/vacuum.h"
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
+#include "nodes/pathnodes.h"
+#include "nodes/primnodes.h"
+#include "optimizer/paths.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "storage/condition_variable.h"
@@ -33,7 +36,9 @@
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/index_selfuncs.h"
+#include "utils/lsyscache.h"
 #include "utils/memutils.h"
 
 
@@ -79,6 +84,7 @@ typedef enum
 typedef struct BTParallelScanDescData
 {
 	BlockNumber btps_scanPage;	/* latest or next page to be scanned */
+	BlockNumber btps_knnScanPage;	/* secondary kNN page to be scanned */
 	BTPS_State	btps_pageStatus;	/* indicates whether next page is
 									 * available for scan. see above for
 									 * possible states of parallel scan. */
@@ -97,6 +103,10 @@ static void btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 static void btvacuumpage(BTVacState *vstate, BlockNumber blkno,
 			 BlockNumber orig_blkno);
 
+static bool btmatchorderby(IndexOptInfo *index, List *pathkeys,
+			   List *index_clauses, List **orderby_clauses_p,
+			   List **orderby_clause_columns_p);
+
 
 /*
  * Btree handler function: return IndexAmRoutine with access method parameters
@@ -107,7 +117,7 @@ bthandler(PG_FUNCTION_ARGS)
 {
 	IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
 
-	amroutine->amstrategies = BTMaxStrategyNumber;
+	amroutine->amstrategies = BtreeMaxStrategyNumber;
 	amroutine->amsupport = BTNProcs;
 	amroutine->amcanorder = true;
 	amroutine->amcanbackward = true;
@@ -143,7 +153,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amestimateparallelscan = btestimateparallelscan;
 	amroutine->aminitparallelscan = btinitparallelscan;
 	amroutine->amparallelrescan = btparallelrescan;
-	amroutine->ammatchorderby = NULL;
+	amroutine->ammatchorderby = btmatchorderby;
 
 	PG_RETURN_POINTER(amroutine);
 }
@@ -215,23 +225,30 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTScanState state = &so->state;
+	ScanDirection arraydir =
+	scan->numberOfOrderBys > 0 ? ForwardScanDirection : dir;
 	bool		res;
 
 	/* btree indexes are never lossy */
 	scan->xs_recheck = false;
+	scan->xs_recheckorderby = false;
+
+	if (so->scanDirection != NoMovementScanDirection)
+		dir = so->scanDirection;
 
 	/*
 	 * If we have any array keys, initialize them during first call for a
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos) &&
+		(!so->knnState || !BTScanPosIsValid(so->knnState->currPos)))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
 			return false;
 
-		_bt_start_array_keys(scan, dir);
+		_bt_start_array_keys(scan, arraydir);
 	}
 
 	/* This loop handles advancing to the next array elements, if any */
@@ -242,7 +259,8 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(state->currPos))
+		if (!BTScanPosIsValid(state->currPos) &&
+			(!so->knnState || !BTScanPosIsValid(so->knnState->currPos)))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -277,7 +295,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		if (res)
 			break;
 		/* ... otherwise see if we have more array keys to deal with */
-	} while (so->numArrayKeys && _bt_advance_array_keys(scan, dir));
+	} while (so->numArrayKeys && _bt_advance_array_keys(scan, arraydir));
 
 	return res;
 }
@@ -350,9 +368,6 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	IndexScanDesc scan;
 	BTScanOpaque so;
 
-	/* no order by operators allowed */
-	Assert(norderbys == 0);
-
 	/* get the scan */
 	scan = RelationGetIndexScan(rel, nkeys, norderbys);
 
@@ -379,6 +394,9 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
 	so->state.currTuples = so->state.markTuples = NULL;
+	so->knnState = NULL;
+	so->distanceTypeByVal = true;
+	so->scanDirection = NoMovementScanDirection;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -408,6 +426,8 @@ _bt_release_current_position(BTScanState state, Relation indexRelation,
 static void
 _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	/* No need to invalidate positions, if the RAM is about to be freed. */
 	_bt_release_current_position(state, scan->indexRelation, !free);
 
@@ -424,6 +444,17 @@ _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 	}
 	else
 		BTScanPosInvalidate(state->markPos);
+
+	if (!so->distanceTypeByVal)
+	{
+		if (DatumGetPointer(state->currDistance))
+			pfree(DatumGetPointer(state->currDistance));
+		state->currDistance = PointerGetDatum(NULL);
+
+		if (DatumGetPointer(state->markDistance))
+			pfree(DatumGetPointer(state->markDistance));
+		state->markDistance = PointerGetDatum(NULL);
+	}
 }
 
 /*
@@ -438,6 +469,13 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 
 	_bt_release_scan_state(scan, state, false);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+		so->knnState = NULL;
+	}
+
 	so->arrayKeyCount = 0;
 
 	/*
@@ -469,6 +507,14 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 				scan->numberOfKeys * sizeof(ScanKeyData));
 	so->numberOfKeys = 0;		/* until _bt_preprocess_keys sets it */
 
+	if (orderbys && scan->numberOfOrderBys > 0)
+		memmove(scan->orderByData,
+				orderbys,
+				scan->numberOfOrderBys * sizeof(ScanKeyData));
+
+	so->scanDirection = NoMovementScanDirection;
+	so->distanceTypeByVal = true;
+
 	/* If any keys are SK_SEARCHARRAY type, set up array-key info */
 	_bt_preprocess_array_keys(scan);
 }
@@ -483,6 +529,12 @@ btendscan(IndexScanDesc scan)
 
 	_bt_release_scan_state(scan, &so->state, true);
 
+	if (so->knnState)
+	{
+		_bt_release_scan_state(scan, so->knnState, true);
+		pfree(so->knnState);
+	}
+
 	/* Release storage */
 	if (so->keyData != NULL)
 		pfree(so->keyData);
@@ -494,7 +546,7 @@ btendscan(IndexScanDesc scan)
 }
 
 static void
-_bt_mark_current_position(BTScanState state)
+_bt_mark_current_position(BTScanOpaque so, BTScanState state)
 {
 	/* There may be an old mark with a pin (but no lock). */
 	BTScanPosUnpinIfPinned(state->markPos);
@@ -512,6 +564,21 @@ _bt_mark_current_position(BTScanState state)
 		BTScanPosInvalidate(state->markPos);
 		state->markItemIndex = -1;
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->markDistance));
+
+		state->markIsNull = !BTScanPosIsValid(state->currPos) ||
+			state->currIsNull;
+
+		state->markDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->currDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -522,7 +589,13 @@ btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_mark_current_position(&so->state);
+	_bt_mark_current_position(so, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_mark_current_position(so, so->knnState);
+		so->markRightIsNearest = so->currRightIsNearest;
+	}
 
 	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
@@ -532,6 +605,8 @@ btmarkpos(IndexScanDesc scan)
 static void
 _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	if (state->markItemIndex >= 0)
 	{
 		/*
@@ -567,6 +642,19 @@ _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 					   state->markPos.nextTupleOffset);
 		}
 	}
+
+	if (so->knnState)
+	{
+		if (!so->distanceTypeByVal)
+			pfree(DatumGetPointer(state->currDistance));
+
+		state->currIsNull = state->markIsNull;
+		state->currDistance =
+			state->markIsNull ? PointerGetDatum(NULL)
+			: datumCopy(state->markDistance,
+						so->distanceTypeByVal,
+						so->distanceTypeLen);
+	}
 }
 
 /*
@@ -588,6 +676,7 @@ btinitparallelscan(void *target)
 
 	SpinLockInit(&bt_target->btps_mutex);
 	bt_target->btps_scanPage = InvalidBlockNumber;
+	bt_target->btps_knnScanPage = InvalidBlockNumber;
 	bt_target->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	bt_target->btps_arrayKeyCount = 0;
 	ConditionVariableInit(&bt_target->btps_cv);
@@ -614,6 +703,7 @@ btparallelrescan(IndexScanDesc scan)
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
 	btscan->btps_scanPage = InvalidBlockNumber;
+	btscan->btps_knnScanPage = InvalidBlockNumber;
 	btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	btscan->btps_arrayKeyCount = 0;
 	SpinLockRelease(&btscan->btps_mutex);
@@ -638,7 +728,7 @@ btparallelrescan(IndexScanDesc scan)
  * Callers should ignore the value of pageno if the return value is false.
  */
 bool
-_bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
+_bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTPS_State	pageStatus;
@@ -646,12 +736,17 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
 	bool		status = true;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
 
 	*pageno = P_NONE;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	scanPage = state == &so->state
+		? &btscan->btps_scanPage
+		: &btscan->btps_knnScanPage;
+
 	while (1)
 	{
 		SpinLockAcquire(&btscan->btps_mutex);
@@ -677,7 +772,7 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
 			 * of advancing it to a new page!
 			 */
 			btscan->btps_pageStatus = BTPARALLEL_ADVANCING;
-			*pageno = btscan->btps_scanPage;
+			*pageno = *scanPage;
 			exit_loop = true;
 		}
 		SpinLockRelease(&btscan->btps_mutex);
@@ -696,19 +791,42 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
  *		can now begin advancing the scan.
  */
 void
-_bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
+_bt_parallel_release(IndexScanDesc scan, BTScanState state,
+					 BlockNumber scan_page)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
+	bool		status_changed = false;
+	bool		knnScan = so->knnState != NULL;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
+	if (!state || state == &so->state)
+	{
+		scanPage = &btscan->btps_scanPage;
+		otherScanPage = &btscan->btps_knnScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_knnScanPage;
+		otherScanPage = &btscan->btps_scanPage;
+	}
 
 	SpinLockAcquire(&btscan->btps_mutex);
-	btscan->btps_scanPage = scan_page;
-	btscan->btps_pageStatus = BTPARALLEL_IDLE;
+	*scanPage = scan_page;
+	/* switch to idle state only if both KNN pages are initialized */
+	if (!knnScan || *otherScanPage != InvalidBlockNumber)
+	{
+		btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		status_changed = true;
+	}
 	SpinLockRelease(&btscan->btps_mutex);
-	ConditionVariableSignal(&btscan->btps_cv);
+
+	if (status_changed)
+		ConditionVariableSignal(&btscan->btps_cv);
 }
 
 /*
@@ -719,12 +837,15 @@ _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
  * advance to the next page.
  */
 void
-_bt_parallel_done(IndexScanDesc scan)
+_bt_parallel_done(IndexScanDesc scan, BTScanState state)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
 	bool		status_changed = false;
+	bool		knnScan = so->knnState != NULL;
 
 	/* Do nothing, for non-parallel scans */
 	if (parallel_scan == NULL)
@@ -733,18 +854,41 @@ _bt_parallel_done(IndexScanDesc scan)
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	if (!state || state == &so->state)
+	{
+		scanPage = &btscan->btps_scanPage;
+		otherScanPage = &btscan->btps_knnScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_knnScanPage;
+		otherScanPage = &btscan->btps_scanPage;
+	}
+
 	/*
 	 * Mark the parallel scan as done for this combination of scan keys,
 	 * unless some other process already did so.  See also
 	 * _bt_advance_array_keys.
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
-	if (so->arrayKeyCount >= btscan->btps_arrayKeyCount &&
-		btscan->btps_pageStatus != BTPARALLEL_DONE)
+
+	Assert(btscan->btps_pageStatus == BTPARALLEL_ADVANCING);
+
+	if (so->arrayKeyCount >= btscan->btps_arrayKeyCount)
 	{
-		btscan->btps_pageStatus = BTPARALLEL_DONE;
+		*scanPage = P_NONE;
 		status_changed = true;
+
+		/* switch to "done" state only if both KNN scans are done */
+		if (!knnScan || *otherScanPage == P_NONE)
+			btscan->btps_pageStatus = BTPARALLEL_DONE;
+		/* else switch to "idle" state only if both KNN scans are initialized */
+		else if (*otherScanPage != InvalidBlockNumber)
+			btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		else
+			status_changed = false;
 	}
+
 	SpinLockRelease(&btscan->btps_mutex);
 
 	/* wake up all the workers associated with this parallel scan */
@@ -774,6 +918,7 @@ _bt_parallel_advance_array_keys(IndexScanDesc scan)
 	if (btscan->btps_pageStatus == BTPARALLEL_DONE)
 	{
 		btscan->btps_scanPage = InvalidBlockNumber;
+		btscan->btps_knnScanPage = InvalidBlockNumber;
 		btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 		btscan->btps_arrayKeyCount++;
 	}
@@ -859,6 +1004,12 @@ btrestrpos(IndexScanDesc scan)
 		_bt_restore_array_keys(scan);
 
 	_bt_restore_marked_position(scan, &so->state);
+
+	if (so->knnState)
+	{
+		_bt_restore_marked_position(scan, so->knnState);
+		so->currRightIsNearest = so->markRightIsNearest;
+	}
 }
 
 /*
@@ -1394,3 +1545,91 @@ btcanreturn(Relation index, int attno)
 {
 	return true;
 }
+
+/*
+ *	btmatchorderby() -- Check whether KNN-search strategy is applicable to
+ *		the given ORDER BY distance operator.
+ */
+static bool
+btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
+			   List **orderby_clauses_p, List **orderby_clausecols_p)
+{
+	Expr	   *expr;
+	ListCell   *lc;
+	int			indexcol;
+	int			num_eq_cols = 0;
+
+	/* only one ORDER BY clause is supported */
+	if (list_length(pathkeys) != 1)
+		return false;
+
+	/*
+	 * Compute a number of leading consequent index columns with equality
+	 * restriction clauses.
+	 */
+	foreach(lc, index_clauses)
+	{
+		IndexClause *iclause = lfirst_node(IndexClause, lc);
+		ListCell   *lcq;
+
+		indexcol = iclause->indexcol;
+
+		if (indexcol > num_eq_cols)
+			/* Sequence of equality-restricted columns is broken. */
+			break;
+
+		foreach(lcq, iclause->indexquals)
+		{
+			RestrictInfo *rinfo = lfirst_node(RestrictInfo, lcq);
+			Expr	   *clause = rinfo->clause;
+			Oid			opno;
+			StrategyNumber strat;
+
+			if (!clause)
+				continue;
+
+			if (IsA(clause, OpExpr))
+			{
+				OpExpr	   *opexpr = (OpExpr *) clause;
+
+				opno = opexpr->opno;
+			}
+			else
+			{
+				/* Skip unsupported expression */
+				continue;
+			}
+
+			/* Check if the operator is btree equality operator. */
+			strat = get_op_opfamily_strategy(opno, index->opfamily[indexcol]);
+
+			if (strat == BTEqualStrategyNumber)
+				num_eq_cols = indexcol + 1;
+		}
+	}
+
+	/*
+	 * If there are no equality columns try to match only the first column,
+	 * otherwise try all columns.
+	 */
+	indexcol = num_eq_cols ? -1 : 0;
+
+	expr = match_orderbyop_pathkey(index, castNode(PathKey, linitial(pathkeys)),
+								   &indexcol);
+
+	if (!expr)
+		return false;
+
+	/*
+	 * ORDER BY distance is supported only for the first index column or if
+	 * all previous columns have equality restrictions.
+	 */
+	if (indexcol > num_eq_cols)
+		return false;
+
+	/* Return first ORDER BY clause's expression and column. */
+	*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+	*orderby_clausecols_p = lappend_int(*orderby_clausecols_p, indexcol);
+
+	return true;
+}
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index cbd72bd..28f94e7 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -31,12 +31,14 @@ static void _bt_saveitem(BTScanState state, int itemIndex,
 static bool _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir);
 static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
 				 BlockNumber blkno, ScanDirection dir);
-static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
-					  ScanDirection dir);
+static bool _bt_parallel_readpage(IndexScanDesc scan, BTScanState state,
+					  BlockNumber blkno, ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf, Snapshot snapshot);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
 static inline void _bt_initialize_more_data(BTScanState state, ScanDirection dir);
+static BTScanState _bt_alloc_knn_scan(IndexScanDesc scan);
+static bool _bt_start_knn_scan(IndexScanDesc scan, bool left, bool right);
 
 
 /*
@@ -597,6 +599,157 @@ _bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 }
 
 /*
+ *  _bt_calc_current_dist() -- Calculate distance from the current item
+ *		of the scan state to the target order-by ScanKey argument.
+ */
+static void
+_bt_calc_current_dist(IndexScanDesc scan, BTScanState state)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanPosItem  *currItem = &state->currPos.items[state->currPos.itemIndex];
+	IndexTuple		itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+	ScanKey			scankey = &scan->orderByData[0];
+	Datum			value;
+
+	value = index_getattr(itup, scankey->sk_attno, scan->xs_itupdesc,
+						  &state->currIsNull);
+
+	if (state->currIsNull)
+		return; /* NULL distance */
+
+	value = FunctionCall2Coll(&scankey->sk_func,
+							  scankey->sk_collation,
+							  value,
+							  scankey->sk_argument);
+
+	/* free previous distance value for by-ref types */
+	if (!so->distanceTypeByVal && DatumGetPointer(state->currDistance))
+		pfree(DatumGetPointer(state->currDistance));
+
+	state->currDistance = value;
+}
+
+/*
+ *  _bt_compare_current_dist() -- Compare current distances of the left and right scan states.
+ *
+ *  NULL distances are considered to be greater than any non-NULL distances.
+ *
+ *  Returns true if right distance is lesser than left, otherwise false.
+ */
+static bool
+_bt_compare_current_dist(BTScanOpaque so, BTScanState rstate, BTScanState lstate)
+{
+	if (lstate->currIsNull)
+		return true; /* non-NULL < NULL */
+
+	if (rstate->currIsNull)
+		return false; /* NULL > non-NULL */
+
+	return DatumGetBool(FunctionCall2Coll(&so->distanceCmpProc,
+										  InvalidOid, /* XXX collation for distance comparison */
+										  rstate->currDistance,
+										  lstate->currDistance));
+}
+
+/*
+ * _bt_alloc_knn_scan() -- Allocate additional backward scan state for KNN.
+ */
+static BTScanState
+_bt_alloc_knn_scan(IndexScanDesc scan)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		lstate = (BTScanState) palloc(sizeof(BTScanStateData));
+
+	_bt_allocate_tuple_workspaces(lstate);
+
+	if (!scan->xs_want_itup)
+	{
+		/* We need to request index tuples for distance comparison. */
+		scan->xs_want_itup = true;
+		_bt_allocate_tuple_workspaces(&so->state);
+	}
+
+	BTScanPosInvalidate(lstate->currPos);
+	lstate->currPos.moreLeft = false;
+	lstate->currPos.moreRight = false;
+	BTScanPosInvalidate(lstate->markPos);
+	lstate->markItemIndex = -1;
+	lstate->killedItems = NULL;
+	lstate->numKilled = 0;
+	lstate->currDistance = PointerGetDatum(NULL);
+	lstate->markDistance = PointerGetDatum(NULL);
+
+	return so->knnState = lstate;
+}
+
+static bool
+_bt_start_knn_scan(IndexScanDesc scan, bool left, bool right)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		rstate; /* right (forward) main scan state */
+	BTScanState		lstate; /* additional left (backward) KNN scan state */
+
+	if (!left && !right)
+		return false; /* empty result */
+
+	rstate = &so->state;
+	lstate = so->knnState;
+
+	if (left && right)
+	{
+		/*
+		 * We have found items in both scan directions,
+		 * determine nearest item to return.
+		 */
+		_bt_calc_current_dist(scan, rstate);
+		_bt_calc_current_dist(scan, lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+
+		/* Reset right flag if the left item is nearer. */
+		right = so->currRightIsNearest;
+	}
+
+	/* Return current item of the selected scan direction. */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
+ * _bt_init_knn_scan() -- Init additional scan state for KNN search.
+ *
+ * Caller must pin and read-lock scan->state.currPos.buf buffer.
+ *
+ * If empty result was found returned false.
+ * Otherwise prepared current item, and returned true.
+ */
+static bool
+_bt_init_knn_scan(IndexScanDesc scan, OffsetNumber offnum)
+{
+	BTScanOpaque	so = (BTScanOpaque) scan->opaque;
+	BTScanState		rstate = &so->state; /* right (forward) main scan state */
+	BTScanState		lstate; /* additional left (backward) KNN scan state */
+	Buffer			buf = rstate->currPos.buf;
+	bool			left,
+					right;
+
+	lstate = _bt_alloc_knn_scan(scan);
+
+	/* Bump pin and lock count before BTScanPosData copying. */
+	IncrBufferRefCount(buf);
+	LockBuffer(buf, BT_READ);
+
+	memcpy(&lstate->currPos, &rstate->currPos, sizeof(BTScanPosData));
+	lstate->currPos.moreLeft = true;
+	lstate->currPos.moreRight = false;
+
+	/* Load first pages from the both scans. */
+	right = _bt_load_first_page(scan, rstate, ForwardScanDirection, offnum);
+	left = _bt_load_first_page(scan, lstate, BackwardScanDirection,
+							   OffsetNumberPrev(offnum));
+
+	return _bt_start_knn_scan(scan, left, right);
+}
+
+/*
  *	_bt_first() -- Find the first item in a scan.
  *
  *		We need to be clever about the direction of scan, the search
@@ -654,6 +807,15 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	if (!so->qual_ok)
 		return false;
 
+	if (scan->numberOfOrderBys > 0)
+	{
+		if (so->useBidirectionalKnnScan)
+			_bt_init_distance_comparison(scan);
+		else if (so->scanDirection != NoMovementScanDirection)
+			/* use selected KNN scan direction */
+			dir = so->scanDirection;
+	}
+
 	/*
 	 * For parallel scans, get the starting page from shared state. If the
 	 * scan has not started, proceed to find out first leaf page in the usual
@@ -662,19 +824,50 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 */
 	if (scan->parallel_scan != NULL)
 	{
-		status = _bt_parallel_seize(scan, &blkno);
+		status = _bt_parallel_seize(scan, &so->state, &blkno);
 		if (!status)
 			return false;
-		else if (blkno == P_NONE)
-		{
-			_bt_parallel_done(scan);
-			return false;
-		}
 		else if (blkno != InvalidBlockNumber)
 		{
-			if (!_bt_parallel_readpage(scan, blkno, dir))
-				return false;
-			goto readcomplete;
+			bool		knn = so->useBidirectionalKnnScan;
+			bool		right;
+			bool		left;
+
+			if (knn)
+				_bt_alloc_knn_scan(scan);
+
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, &so->state);
+				right = false;
+			}
+			else
+				right = _bt_parallel_readpage(scan, &so->state, blkno,
+											  knn ? ForwardScanDirection : dir);
+
+			if (!knn)
+				return right && _bt_return_current_item(scan, &so->state);
+
+			/* seize additional backward KNN scan */
+			left = _bt_parallel_seize(scan, so->knnState, &blkno);
+
+			if (left)
+			{
+				if (blkno == P_NONE)
+				{
+					_bt_parallel_done(scan, so->knnState);
+					left = false;
+				}
+				else
+				{
+					/* backward scan should be already initialized */
+					Assert(blkno != InvalidBlockNumber);
+					left = _bt_parallel_readpage(scan, so->knnState, blkno,
+												 BackwardScanDirection);
+				}
+			}
+
+			return _bt_start_knn_scan(scan, left, right);
 		}
 	}
 
@@ -724,7 +917,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 * storing their addresses into the local startKeys[] array.
 	 *----------
 	 */
+
 	strat_total = BTEqualStrategyNumber;
+
 	if (so->numberOfKeys > 0)
 	{
 		AttrNumber	curattr;
@@ -749,6 +944,10 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 */
 		for (cur = so->keyData, i = 0;; cur++, i++)
 		{
+			if (so->useBidirectionalKnnScan &&
+				curattr >= scan->orderByData->sk_attno)
+				break;
+
 			if (i >= so->numberOfKeys || cur->sk_attno != curattr)
 			{
 				/*
@@ -851,6 +1050,16 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		}
 	}
 
+	if (so->useBidirectionalKnnScan)
+	{
+		Assert(strat_total == BTEqualStrategyNumber);
+		strat_total = BtreeKNNSearchStrategyNumber;
+
+		(void) _bt_init_knn_start_keys(scan, &startKeys[keysCount],
+									   &notnullkeys[keysCount]);
+		keysCount++;
+	}
+
 	/*
 	 * If we found no usable boundary keys, we have to start from one end of
 	 * the tree.  Walk down that edge to the first or last key, and scan from
@@ -865,7 +1074,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		if (!match)
 		{
 			/* No match, so mark (parallel) scan finished */
-			_bt_parallel_done(scan);
+			_bt_parallel_done(scan, NULL);
 		}
 
 		return match;
@@ -900,7 +1109,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			Assert(subkey->sk_flags & SK_ROW_MEMBER);
 			if (subkey->sk_flags & SK_ISNULL)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, NULL);
 				return false;
 			}
 			memcpy(scankeys + i, subkey, sizeof(ScanKeyData));
@@ -1080,6 +1289,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			break;
 
 		case BTGreaterEqualStrategyNumber:
+		case BtreeKNNSearchStrategyNumber:
 
 			/*
 			 * Find first item >= scankey.  (This is only used for forward
@@ -1127,7 +1337,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		 * mark parallel scan as done, so that all the workers can finish
 		 * their scan
 		 */
-		_bt_parallel_done(scan);
+		_bt_parallel_done(scan, NULL);
 		BTScanPosInvalidate(*currPos);
 
 		return false;
@@ -1166,17 +1376,21 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	Assert(!BTScanPosIsValid(*currPos));
 	currPos->buf = buf;
 
+	if (strat_total == BtreeKNNSearchStrategyNumber)
+		return _bt_init_knn_scan(scan, offnum);
+
 	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
-		return false;
+		return false; /* empty result */
 
-readcomplete:
 	/* OK, currPos->itemIndex says what to return */
 	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
- *	Advance to next tuple on current page; or if there's no more,
- *	try to step to the next page with data.
+ *	_bt_next_item() -- Advance to next tuple on current page;
+ *		or if there's no more, try to step to the next page with data.
+ *
+ *	If there are no more matching records in the given direction
  */
 static bool
 _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
@@ -1196,6 +1410,51 @@ _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 }
 
 /*
+ *	_bt_next_nearest() -- Return next nearest item from bidirectional KNN scan.
+ */
+static bool
+_bt_next_nearest(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState rstate = &so->state;
+	BTScanState lstate = so->knnState;
+	bool		right = BTScanPosIsValid(rstate->currPos);
+	bool		left = BTScanPosIsValid(lstate->currPos);
+	bool		advanceRight;
+
+	if (right && left)
+		advanceRight = so->currRightIsNearest;
+	else if (right)
+		advanceRight = true;
+	else if (left)
+		advanceRight = false;
+	else
+		return false; /* end of the scan */
+
+	if (advanceRight)
+		right = _bt_next_item(scan, rstate, ForwardScanDirection);
+	else
+		left = _bt_next_item(scan, lstate, BackwardScanDirection);
+
+	if (!left && !right)
+		return false; /* end of the scan */
+
+	if (left && right)
+	{
+		/*
+		 * If there are items in both scans we must recalculate distance
+		 * in the advanced scan.
+		 */
+		_bt_calc_current_dist(scan, advanceRight ? rstate : lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+		right = so->currRightIsNearest;
+	}
+
+	/* return nearest item */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
  *	_bt_next() -- Get the next item in a scan.
  *
  *		On entry, so->currPos describes the current page, which may be pinned
@@ -1214,6 +1473,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
+	if (so->knnState)
+		/* return next neareset item from KNN scan */
+		return _bt_next_nearest(scan);
+
 	if (!_bt_next_item(scan, &so->state, dir))
 		return false;
 
@@ -1266,9 +1529,9 @@ _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 	if (scan->parallel_scan)
 	{
 		if (ScanDirectionIsForward(dir))
-			_bt_parallel_release(scan, opaque->btpo_next);
+			_bt_parallel_release(scan, state, opaque->btpo_next);
 		else
-			_bt_parallel_release(scan, BufferGetBlockNumber(pos->buf));
+			_bt_parallel_release(scan, state, BufferGetBlockNumber(pos->buf));
 	}
 
 	minoff = P_FIRSTDATAKEY(opaque);
@@ -1442,7 +1705,7 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the next block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno);
+			status = _bt_parallel_seize(scan, state, &blkno);
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
@@ -1474,13 +1737,19 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the current block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno);
+			status = _bt_parallel_seize(scan, state, &blkno);
 			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, state);
+				BTScanPosInvalidate(*currPos);
+				return false;
+			}
 		}
 		else
 		{
@@ -1530,7 +1799,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			 */
 			if (blkno == P_NONE || !currPos->moreRight)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1553,14 +1822,14 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, opaque->btpo_next);
+				_bt_parallel_release(scan, state, opaque->btpo_next);
 			}
 
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno);
+				status = _bt_parallel_seize(scan, state, &blkno);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -1619,7 +1888,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (!currPos->moreLeft)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1630,7 +1899,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			/* if we're physically at end of index, return failure */
 			if (currPos->buf == InvalidBuffer)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -1654,7 +1923,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
+				_bt_parallel_release(scan, state, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -1666,7 +1935,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno);
+				status = _bt_parallel_seize(scan, state, &blkno);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -1687,17 +1956,16 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
  * indicate success.
  */
 static bool
-_bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_parallel_readpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+					  ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	_bt_initialize_more_data(state, dir);
 
-	_bt_initialize_more_data(&so->state, dir);
-
-	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
 	/* Drop the lock, and maybe the pin, on the current page */
-	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
 
 	return true;
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index e548354..5b9294b 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -20,16 +20,21 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "catalog/pg_amop.h"
 #include "miscadmin.h"
 #include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 
 
 typedef struct BTSortArrayContext
 {
 	FmgrInfo	flinfo;
+	FmgrInfo	distflinfo;
+	FmgrInfo	distcmpflinfo;
+	ScanKey		distkey;
 	Oid			collation;
 	bool		reverse;
 } BTSortArrayContext;
@@ -49,6 +54,11 @@ static void _bt_mark_scankey_required(ScanKey skey);
 static bool _bt_check_rowcompare(ScanKey skey,
 					 IndexTuple tuple, TupleDesc tupdesc,
 					 ScanDirection dir, bool *continuescan);
+static inline StrategyNumber _bt_select_knn_strategy_for_key(IndexScanDesc scan,
+								ScanKey cond);
+static void _bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype,
+						  FmgrInfo *finfo, int16 *typlen, bool *typbyval);
+
 
 
 /*
@@ -445,6 +455,7 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 {
 	Relation	rel = scan->indexRelation;
 	Oid			elemtype;
+	Oid			opfamily;
 	RegProcedure cmp_proc;
 	BTSortArrayContext cxt;
 	int			last_non_dup;
@@ -462,6 +473,54 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 	if (elemtype == InvalidOid)
 		elemtype = rel->rd_opcintype[skey->sk_attno - 1];
 
+	opfamily = rel->rd_opfamily[skey->sk_attno - 1];
+
+	if (scan->numberOfOrderBys <= 0 ||
+		scan->orderByData[0].sk_attno != skey->sk_attno)
+	{
+		cxt.distkey = NULL;
+		cxt.reverse = reverse;
+	}
+	else
+	{
+		/* Init procedures for distance calculation and comparison. */
+		ScanKey		distkey = &scan->orderByData[0];
+		ScanKeyData	distkey2;
+		Oid			disttype = distkey->sk_subtype;
+		Oid			distopr;
+		RegProcedure distproc;
+
+		if (!OidIsValid(disttype))
+			disttype = rel->rd_opcintype[skey->sk_attno - 1];
+
+		/* Lookup distance operator in index column's operator family. */
+		distopr = get_opfamily_member(opfamily,
+									  elemtype,
+									  disttype,
+									  distkey->sk_strategy);
+
+		if (!OidIsValid(distopr))
+			elog(ERROR, "missing operator (%u,%u) for strategy %d in opfamily %u",
+				 elemtype, disttype, BtreeKNNSearchStrategyNumber, opfamily);
+
+		distproc = get_opcode(distopr);
+
+		if (!RegProcedureIsValid(distproc))
+			elog(ERROR, "missing code for operator %u", distopr);
+
+		fmgr_info(distproc, &cxt.distflinfo);
+
+		distkey2 = *distkey;
+		fmgr_info_copy(&distkey2.sk_func, &cxt.distflinfo, CurrentMemoryContext);
+		distkey2.sk_subtype = disttype;
+
+		_bt_get_distance_cmp_proc(&distkey2, opfamily, elemtype,
+								  &cxt.distcmpflinfo, NULL, NULL);
+
+		cxt.distkey = distkey;
+		cxt.reverse = false;	/* supported only ascending ordering */
+	}
+
 	/*
 	 * Look up the appropriate comparison function in the opfamily.
 	 *
@@ -470,19 +529,17 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 	 * non-cross-type support functions for any datatype that it supports at
 	 * all.
 	 */
-	cmp_proc = get_opfamily_proc(rel->rd_opfamily[skey->sk_attno - 1],
+	cmp_proc = get_opfamily_proc(opfamily,
 								 elemtype,
 								 elemtype,
 								 BTORDER_PROC);
 	if (!RegProcedureIsValid(cmp_proc))
 		elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
-			 BTORDER_PROC, elemtype, elemtype,
-			 rel->rd_opfamily[skey->sk_attno - 1]);
+			 BTORDER_PROC, elemtype, elemtype, opfamily);
 
 	/* Sort the array elements */
 	fmgr_info(cmp_proc, &cxt.flinfo);
 	cxt.collation = skey->sk_collation;
-	cxt.reverse = reverse;
 	qsort_arg((void *) elems, nelems, sizeof(Datum),
 			  _bt_compare_array_elements, (void *) &cxt);
 
@@ -514,6 +571,23 @@ _bt_compare_array_elements(const void *a, const void *b, void *arg)
 	BTSortArrayContext *cxt = (BTSortArrayContext *) arg;
 	int32		compare;
 
+	if (cxt->distkey)
+	{
+		Datum		dista = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  da,
+											  cxt->distkey->sk_argument);
+		Datum		distb = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  db,
+											  cxt->distkey->sk_argument);
+		bool		cmp = DatumGetBool(FunctionCall2Coll(&cxt->distcmpflinfo,
+														 cxt->collation,
+														 dista,
+														 distb));
+		return cmp ? -1 : 1;
+	}
+
 	compare = DatumGetInt32(FunctionCall2Coll(&cxt->flinfo,
 											  cxt->collation,
 											  da, db));
@@ -667,6 +741,66 @@ _bt_restore_array_keys(IndexScanDesc scan)
 	}
 }
 
+/*
+ * _bt_emit_scan_key() -- Emit one prepared scan key
+ *
+ * Push the scan key into the so->keyData[] array, and then mark it if it is
+ * required.  Also update selected kNN strategy.
+ */
+static void
+_bt_emit_scan_key(IndexScanDesc scan, ScanKey skey, int numberOfEqualCols)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	ScanKey		outkey = &so->keyData[so->numberOfKeys++];
+
+	memcpy(outkey, skey, sizeof(ScanKeyData));
+
+	/*
+	 * We can mark the qual as required (possibly only in one direction) if all
+	 * attrs before this one had "=".
+	 */
+	if (outkey->sk_attno - 1 == numberOfEqualCols)
+		_bt_mark_scankey_required(outkey);
+
+	/* Update kNN strategy if it is not already selected. */
+	if (so->useBidirectionalKnnScan)
+	{
+		switch (_bt_select_knn_strategy_for_key(scan, outkey))
+		{
+			case BTLessStrategyNumber:
+			case BTLessEqualStrategyNumber:
+				/*
+				 * Ordering key argument is greater than all values in scan
+				 * range, select backward scan direction.
+				 */
+				so->scanDirection = BackwardScanDirection;
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BTEqualStrategyNumber:
+				/* Use default unidirectional scan direction. */
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+				/*
+				 * Ordering key argument is lesser than all values in scan
+				 * range, select forward scan direction.
+				 */
+				so->scanDirection = ForwardScanDirection;
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BtreeKNNSearchStrategyNumber:
+				/*
+				 * Ordering key argument falls into scan range,
+				 * keep using bidirectional scan.
+				 */
+				break;
+		}
+	}
+}
 
 /*
  *	_bt_preprocess_keys() -- Preprocess scan keys
@@ -758,10 +892,8 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	int			numberOfKeys = scan->numberOfKeys;
 	int16	   *indoption = scan->indexRelation->rd_indoption;
-	int			new_numberOfKeys;
 	int			numberOfEqualCols;
 	ScanKey		inkeys;
-	ScanKey		outkeys;
 	ScanKey		cur;
 	ScanKey		xform[BTMaxStrategyNumber];
 	bool		test_result;
@@ -769,6 +901,24 @@ _bt_preprocess_keys(IndexScanDesc scan)
 				j;
 	AttrNumber	attno;
 
+	if (scan->numberOfOrderBys > 0)
+	{
+		ScanKey		ord = scan->orderByData;
+
+		if (scan->numberOfOrderBys > 1)
+			/* it should not happen, see btmatchorderby() */
+			elog(ERROR, "only one btree ordering operator is supported");
+
+		Assert(ord->sk_strategy == BtreeKNNSearchStrategyNumber);
+
+		/* use bidirectional kNN scan by default */
+		so->useBidirectionalKnnScan = true;
+	}
+	else
+	{
+		so->useBidirectionalKnnScan = false;
+	}
+
 	/* initialize result variables */
 	so->qual_ok = true;
 	so->numberOfKeys = 0;
@@ -784,7 +934,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	else
 		inkeys = scan->keyData;
 
-	outkeys = so->keyData;
 	cur = &inkeys[0];
 	/* we check that input keys are correctly ordered */
 	if (cur->sk_attno < 1)
@@ -796,18 +945,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 		/* Apply indoption to scankey (might change sk_strategy!) */
 		if (!_bt_fix_scankey_strategy(cur, indoption))
 			so->qual_ok = false;
-		memcpy(outkeys, cur, sizeof(ScanKeyData));
-		so->numberOfKeys = 1;
-		/* We can mark the qual as required if it's for first index col */
-		if (cur->sk_attno == 1)
-			_bt_mark_scankey_required(outkeys);
+
+		_bt_emit_scan_key(scan, cur, 0);
 		return;
 	}
 
 	/*
 	 * Otherwise, do the full set of pushups.
 	 */
-	new_numberOfKeys = 0;
 	numberOfEqualCols = 0;
 
 	/*
@@ -931,20 +1076,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 			}
 
 			/*
-			 * Emit the cleaned-up keys into the outkeys[] array, and then
+			 * Emit the cleaned-up keys into the so->keyData[] array, and then
 			 * mark them if they are required.  They are required (possibly
 			 * only in one direction) if all attrs before this one had "=".
 			 */
 			for (j = BTMaxStrategyNumber; --j >= 0;)
 			{
 				if (xform[j])
-				{
-					ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-					memcpy(outkey, xform[j], sizeof(ScanKeyData));
-					if (priorNumberOfEqualCols == attno - 1)
-						_bt_mark_scankey_required(outkey);
-				}
+					_bt_emit_scan_key(scan, xform[j], priorNumberOfEqualCols);
 			}
 
 			/*
@@ -964,17 +1103,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 		/* if row comparison, push it directly to the output array */
 		if (cur->sk_flags & SK_ROW_HEADER)
 		{
-			ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-			memcpy(outkey, cur, sizeof(ScanKeyData));
-			if (numberOfEqualCols == attno - 1)
-				_bt_mark_scankey_required(outkey);
+			_bt_emit_scan_key(scan, cur, numberOfEqualCols);
 
 			/*
 			 * We don't support RowCompare using equality; such a qual would
 			 * mess up the numberOfEqualCols tracking.
 			 */
 			Assert(j != (BTEqualStrategyNumber - 1));
+
 			continue;
 		}
 
@@ -1007,16 +1143,10 @@ _bt_preprocess_keys(IndexScanDesc scan)
 				 * previous one in xform[j] and push this one directly to the
 				 * output array.
 				 */
-				ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-				memcpy(outkey, cur, sizeof(ScanKeyData));
-				if (numberOfEqualCols == attno - 1)
-					_bt_mark_scankey_required(outkey);
+				_bt_emit_scan_key(scan, cur, numberOfEqualCols);
 			}
 		}
 	}
-
-	so->numberOfKeys = new_numberOfKeys;
 }
 
 /*
@@ -2075,6 +2205,39 @@ btproperty(Oid index_oid, int attno,
 			*res = true;
 			return true;
 
+		case AMPROP_DISTANCE_ORDERABLE:
+			{
+				Oid			opclass,
+							opfamily,
+							opcindtype;
+
+				/* answer only for columns, not AM or whole index */
+				if (attno == 0)
+					return false;
+
+				opclass = get_index_column_opclass(index_oid, attno);
+
+				if (!OidIsValid(opclass))
+				{
+					*res = false;	/* non-key attribute */
+					return true;
+				}
+
+				if (!get_opclass_opfamily_and_input_type(opclass,
+													 &opfamily, &opcindtype))
+				{
+					*isnull = true;
+					return true;
+				}
+
+				*res = SearchSysCacheExists(AMOPSTRATEGY,
+											ObjectIdGetDatum(opfamily),
+											ObjectIdGetDatum(opcindtype),
+											ObjectIdGetDatum(opcindtype),
+								   Int16GetDatum(BtreeKNNSearchStrategyNumber));
+				return true;
+			}
+
 		default:
 			return false;		/* punt to generic code */
 	}
@@ -2223,3 +2386,212 @@ _bt_allocate_tuple_workspaces(BTScanState state)
 	state->currTuples = (char *) palloc(BLCKSZ * 2);
 	state->markTuples = state->currTuples + BLCKSZ;
 }
+
+static bool
+_bt_compare_row_key_with_ordering_key(ScanKey row, ScanKey ord, bool *result)
+{
+	ScanKey		subkey = (ScanKey) DatumGetPointer(row->sk_argument);
+	int32		cmpresult;
+
+	Assert(subkey->sk_attno == 1);
+	Assert(subkey->sk_flags & SK_ROW_MEMBER);
+
+	if (subkey->sk_flags & SK_ISNULL)
+		return false;
+
+	/* Perform the test --- three-way comparison not bool operator */
+	cmpresult = DatumGetInt32(FunctionCall2Coll(&subkey->sk_func,
+												subkey->sk_collation,
+												ord->sk_argument,
+												subkey->sk_argument));
+
+	if (subkey->sk_flags & SK_BT_DESC)
+		cmpresult = -cmpresult;
+
+	/*
+	 * At this point cmpresult indicates the overall result of the row
+	 * comparison, and subkey points to the deciding column (or the last
+	 * column if the result is "=").
+	 */
+	switch (subkey->sk_strategy)
+	{
+			/* EQ and NE cases aren't allowed here */
+		case BTLessStrategyNumber:
+			*result = cmpresult < 0;
+			break;
+		case BTLessEqualStrategyNumber:
+			*result = cmpresult <= 0;
+			break;
+		case BTGreaterEqualStrategyNumber:
+			*result = cmpresult >= 0;
+			break;
+		case BTGreaterStrategyNumber:
+			*result = cmpresult > 0;
+			break;
+		default:
+			elog(ERROR, "unrecognized RowCompareType: %d",
+				 (int) subkey->sk_strategy);
+			*result = false;	/* keep compiler quiet */
+	}
+
+	return true;
+}
+
+/*
+ * _bt_select_knn_strategy_for_key() -- Determine which kNN scan strategy to use:
+ *		bidirectional or unidirectional.  We are checking here if the
+ *		ordering scankey argument falls into the scan range: if it falls
+ *		we must use bidirectional scan, otherwise we use unidirectional.
+ *
+ *	Returns BtreeKNNSearchStrategyNumber for bidirectional scan or
+ *	strategy number of non-matched scankey for unidirectional.
+ */
+static inline StrategyNumber
+_bt_select_knn_strategy_for_key(IndexScanDesc scan, ScanKey cond)
+{
+	ScanKey		ord = scan->orderByData;
+	bool		result;
+
+	/* only interesting in the index attribute that is ordered by a distance */
+	if (cond->sk_attno != ord->sk_attno)
+		return BtreeKNNSearchStrategyNumber;
+
+	if (cond->sk_strategy == BTEqualStrategyNumber)
+		/* always use simple unidirectional scan for equals operators */
+		return BTEqualStrategyNumber;
+
+	if (cond->sk_flags & SK_ROW_HEADER)
+	{
+		if (!_bt_compare_row_key_with_ordering_key(cond, ord, &result))
+			return BTEqualStrategyNumber; /* ROW(fist_index_attr, ...) IS NULL */
+	}
+	else
+	{
+		if (!_bt_compare_scankey_args(scan, cond, ord, cond, &result))
+			elog(ERROR, "could not compare ordering key");
+	}
+
+	if (!result)
+		/*
+		 * Ordering scankey argument is out of scan range,
+		 * use unidirectional scan.
+		 */
+		return cond->sk_strategy;
+
+	return BtreeKNNSearchStrategyNumber;
+}
+
+int
+_bt_init_knn_start_keys(IndexScanDesc scan, ScanKey *startKeys, ScanKey bufKeys)
+{
+	ScanKey		ord = scan->orderByData;
+	int			indopt = scan->indexRelation->rd_indoption[ord->sk_attno - 1];
+	int			flags = (indopt << SK_BT_INDOPTION_SHIFT) |
+						SK_ORDER_BY |
+						SK_SEARCHNULL; /* only for invalid procedure oid, see
+										* assert in ScanKeyEntryInitialize() */
+	int			keysCount = 0;
+
+	/* Init btree search key with ordering key argument. */
+	ScanKeyEntryInitialize(&bufKeys[0],
+						   flags,
+						   ord->sk_attno,
+						   BtreeKNNSearchStrategyNumber,
+						   ord->sk_subtype,
+						   ord->sk_collation,
+						   InvalidOid,
+						   ord->sk_argument);
+
+	startKeys[keysCount++] = &bufKeys[0];
+
+	return keysCount;
+}
+
+static Oid
+_bt_get_sortfamily_for_opfamily_op(Oid opfamily, Oid lefttype, Oid righttype,
+								   StrategyNumber strategy)
+{
+	HeapTuple	tp;
+	Form_pg_amop amop_tup;
+	Oid			sortfamily;
+
+	tp = SearchSysCache4(AMOPSTRATEGY,
+						 ObjectIdGetDatum(opfamily),
+						 ObjectIdGetDatum(lefttype),
+						 ObjectIdGetDatum(righttype),
+						 Int16GetDatum(strategy));
+	if (!HeapTupleIsValid(tp))
+		return InvalidOid;
+	amop_tup = (Form_pg_amop) GETSTRUCT(tp);
+	sortfamily = amop_tup->amopsortfamily;
+	ReleaseSysCache(tp);
+
+	return sortfamily;
+}
+
+/*
+ * _bt_get_distance_cmp_proc() -- Init procedure for comparsion of distances
+ *		between "leftargtype" and "distkey".
+ */
+static void
+_bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype,
+						  FmgrInfo *finfo, int16 *typlen, bool *typbyval)
+{
+	RegProcedure opcode;
+	Oid			sortfamily;
+	Oid			opno;
+	Oid			distanceType;
+
+	distanceType = get_func_rettype(distkey->sk_func.fn_oid);
+
+	sortfamily = _bt_get_sortfamily_for_opfamily_op(opfamily, leftargtype,
+													distkey->sk_subtype,
+													distkey->sk_strategy);
+
+	if (!OidIsValid(sortfamily))
+		elog(ERROR, "could not find sort family for btree ordering operator");
+
+	opno = get_opfamily_member(sortfamily,
+							   distanceType,
+							   distanceType,
+							   BTLessEqualStrategyNumber);
+
+	if (!OidIsValid(opno))
+		elog(ERROR, "could not find operator for btree distance comparison");
+
+	opcode = get_opcode(opno);
+
+	if (!RegProcedureIsValid(opcode))
+		elog(ERROR,
+			"could not find procedure for btree distance comparison operator");
+
+	fmgr_info(opcode, finfo);
+
+	if (typlen)
+		get_typlenbyval(distanceType, typlen, typbyval);
+}
+
+/*
+ *  _bt_init_distance_comparison() -- Init distance typlen/typbyval and its
+ *  	comparison procedure.
+ */
+void
+_bt_init_distance_comparison(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	Relation	rel = scan->indexRelation;
+	ScanKey		ord = scan->orderByData;
+
+	_bt_get_distance_cmp_proc(ord,
+							  rel->rd_opfamily[ord->sk_attno - 1],
+							  rel->rd_opcintype[ord->sk_attno - 1],
+							  &so->distanceCmpProc,
+							  &so->distanceTypeLen,
+							  &so->distanceTypeByVal);
+
+	if (!so->distanceTypeByVal)
+	{
+		so->state.currDistance = PointerGetDatum(NULL);
+		so->state.markDistance = PointerGetDatum(NULL);
+	}
+}
diff --git a/src/backend/access/nbtree/nbtvalidate.c b/src/backend/access/nbtree/nbtvalidate.c
index 0148ea7..4558fd3 100644
--- a/src/backend/access/nbtree/nbtvalidate.c
+++ b/src/backend/access/nbtree/nbtvalidate.c
@@ -22,9 +22,17 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_type.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/regproc.h"
 #include "utils/syscache.h"
 
+#define BTRequiredOperatorSet \
+		((1 << BTLessStrategyNumber) | \
+		 (1 << BTLessEqualStrategyNumber) | \
+		 (1 << BTEqualStrategyNumber) | \
+		 (1 << BTGreaterEqualStrategyNumber) | \
+		 (1 << BTGreaterStrategyNumber))
+
 
 /*
  * Validator for a btree opclass.
@@ -132,10 +140,11 @@ btvalidate(Oid opclassoid)
 	{
 		HeapTuple	oprtup = &oprlist->members[i]->tuple;
 		Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
+		Oid			op_rettype;
 
 		/* Check that only allowed strategy numbers exist */
 		if (oprform->amopstrategy < 1 ||
-			oprform->amopstrategy > BTMaxStrategyNumber)
+			oprform->amopstrategy > BtreeMaxStrategyNumber)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -146,20 +155,29 @@ btvalidate(Oid opclassoid)
 			result = false;
 		}
 
-		/* btree doesn't support ORDER BY operators */
-		if (oprform->amoppurpose != AMOP_SEARCH ||
-			OidIsValid(oprform->amopsortfamily))
+		/* btree supports ORDER BY operators */
+		if (oprform->amoppurpose != AMOP_SEARCH)
 		{
-			ereport(INFO,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
-							opfamilyname, "btree",
-							format_operator(oprform->amopopr))));
-			result = false;
+			/* ... and operator result must match the claimed btree opfamily */
+			op_rettype = get_op_rettype(oprform->amopopr);
+			if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype))
+			{
+				ereport(INFO,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
+								opfamilyname, "btree",
+								format_operator(oprform->amopopr))));
+				result = false;
+			}
+		}
+		else
+		{
+			/* Search operators must always return bool */
+			op_rettype = BOOLOID;
 		}
 
 		/* Check operator signature --- same for all btree strategies */
-		if (!check_amop_signature(oprform->amopopr, BOOLOID,
+		if (!check_amop_signature(oprform->amopopr, op_rettype,
 								  oprform->amoplefttype,
 								  oprform->amoprighttype))
 		{
@@ -214,12 +232,8 @@ btvalidate(Oid opclassoid)
 		 * or support functions for this datatype pair.  The only things
 		 * considered optional are the sortsupport and in_range functions.
 		 */
-		if (thisgroup->operatorset !=
-			((1 << BTLessStrategyNumber) |
-			 (1 << BTLessEqualStrategyNumber) |
-			 (1 << BTEqualStrategyNumber) |
-			 (1 << BTGreaterEqualStrategyNumber) |
-			 (1 << BTGreaterStrategyNumber)))
+		if ((thisgroup->operatorset & BTRequiredOperatorSet) !=
+			BTRequiredOperatorSet)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 6cf0b0d..279d8ba 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -990,6 +990,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	 * if we are only trying to build bitmap indexscans, nor if we have to
 	 * assume the scan is unordered.
 	 */
+	useful_pathkeys = NIL;
+	orderbyclauses = NIL;
+	orderbyclausecols = NIL;
+
 	pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN &&
 								!found_lower_saop_clause &&
 								has_useful_pathkeys(root, rel));
@@ -1000,10 +1004,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 											  ForwardScanDirection);
 		useful_pathkeys = truncate_useless_pathkeys(root, rel,
 													index_pathkeys);
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
 	}
-	else if (index->ammatchorderby && pathkeys_possibly_useful)
+
+	if (useful_pathkeys == NIL &&
+		index->ammatchorderby && pathkeys_possibly_useful)
 	{
 		/* see if we can generate ordering operators for query_pathkeys */
 		match_pathkeys_to_index(index, root->query_pathkeys,
@@ -1015,12 +1019,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		else
 			useful_pathkeys = NIL;
 	}
-	else
-	{
-		useful_pathkeys = NIL;
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
-	}
 
 	/*
 	 * 3. Check if an index-only scan is possible.  If we're not building
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index d78df29..4f98e91 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -459,6 +459,12 @@ typedef struct BTScanStateData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+
+	/* KNN-search fields: */
+	Datum		currDistance;	/* current distance */
+	Datum		markDistance;	/* marked distance */
+	bool		currIsNull;		/* current item is NULL */
+	bool		markIsNull;		/* marked item is NULL */
 } BTScanStateData;
 
 typedef BTScanStateData *BTScanState;
@@ -479,7 +485,18 @@ typedef struct BTScanOpaqueData
 	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
 	MemoryContext arrayContext; /* scan-lifespan context for array data */
 
-	BTScanStateData	state;
+	BTScanStateData state;		/* main scan state */
+
+	/* kNN-search fields: */
+	BTScanState knnState;		/* optional scan state for kNN search */
+	bool		useBidirectionalKnnScan;	/* use bidirectional kNN scan? */
+	ScanDirection scanDirection;	/* selected scan direction for
+									 * unidirectional kNN scan */
+	FmgrInfo	distanceCmpProc;	/* distance comparison procedure */
+	int16		distanceTypeLen;	/* distance typlen */
+	bool		distanceTypeByVal;	/* distance typebyval */
+	bool		currRightIsNearest; /* current right item is nearest */
+	bool		markRightIsNearest; /* marked right item is nearest */
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -525,11 +542,12 @@ extern bool btcanreturn(Relation index, int attno);
 /*
  * prototypes for internal functions in nbtree.c
  */
-extern bool _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno);
-extern void _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page);
-extern void _bt_parallel_done(IndexScanDesc scan);
+extern bool _bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno);
+extern void _bt_parallel_release(IndexScanDesc scan, BTScanState state, BlockNumber scan_page);
+extern void _bt_parallel_done(IndexScanDesc scan, BTScanState state);
 extern void _bt_parallel_advance_array_keys(IndexScanDesc scan);
 
+
 /*
  * prototypes for functions in nbtinsert.c
  */
@@ -610,6 +628,9 @@ extern bool btproperty(Oid index_oid, int attno,
 extern IndexTuple _bt_nonkey_truncate(Relation rel, IndexTuple itup);
 extern bool _bt_check_natts(Relation rel, Page page, OffsetNumber offnum);
 extern void _bt_allocate_tuple_workspaces(BTScanState state);
+extern void _bt_init_distance_comparison(IndexScanDesc scan);
+extern int _bt_init_knn_start_keys(IndexScanDesc scan, ScanKey *startKeys,
+						ScanKey bufKeys);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/include/access/stratnum.h b/src/include/access/stratnum.h
index 8fdba28..8087ffe 100644
--- a/src/include/access/stratnum.h
+++ b/src/include/access/stratnum.h
@@ -32,7 +32,10 @@ typedef uint16 StrategyNumber;
 #define BTGreaterEqualStrategyNumber	4
 #define BTGreaterStrategyNumber			5
 
-#define BTMaxStrategyNumber				5
+#define BTMaxStrategyNumber				5		/* number of canonical B-tree strategies */
+
+#define BtreeKNNSearchStrategyNumber	6		/* for <-> (distance) */
+#define BtreeMaxStrategyNumber			6		/* number of extended B-tree strategies */
 
 
 /*
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index 6faa9d7..c75ef39 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -347,10 +347,10 @@ ROLLBACK;
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
 ERROR:  access method "invalid_index_method" does not exist
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 6, must be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 0, must be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 7, must be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 0, must be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ERROR:  operator argument types must be specified in ALTER OPERATOR FAMILY
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -397,11 +397,12 @@ DROP OPERATOR FAMILY alt_opf8 USING btree;
 CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
-ERROR:  access method "btree" does not support ordering operators
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
 ALTER OPERATOR FAMILY alt_opf11 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql
index 84fd900..73e6e206 100644
--- a/src/test/regress/sql/alter_generic.sql
+++ b/src/test/regress/sql/alter_generic.sql
@@ -295,8 +295,8 @@ ROLLBACK;
 -- Should fail. Invalid values for ALTER OPERATOR FAMILY .. ADD / DROP
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- function number should be between 1 and 5
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 6 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -340,10 +340,12 @@ CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
 
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
0005-Add-btree-distance-operators-v07.patchtext/x-patch; name=0005-Add-btree-distance-operators-v07.patchDownload
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 1998171..9203497 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -183,6 +183,24 @@ SELECT * FROM events ORDER BY event_date <-> date '2017-05-05' LIMIT 10;
 </programlisting>
    which finds the ten events closest to a given target date.  The ability
    to do this is again dependent on the particular operator class being used.
+   Built-in B-tree operator classes support distance ordering for the following
+   data types:
+   <simplelist>
+    <member><type>int2</type></member>
+    <member><type>int4</type></member>
+    <member><type>int8</type></member>
+    <member><type>float4</type></member>
+    <member><type>float8</type></member>
+    <member><type>numeric</type></member>
+    <member><type>timestamp with time zone</type></member>
+    <member><type>timestamp without time zone</type></member>
+    <member><type>time with time zone</type></member>
+    <member><type>time without time zone</type></member>
+    <member><type>date</type></member>
+    <member><type>interval</type></member>
+    <member><type>oid</type></member>
+    <member><type>money</type></member>
+   </simplelist>
   </para>
 
   <para>
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index c92e9d5..83073c4 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -30,6 +30,7 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+#define SAMESIGN(a,b)	(((a) < 0) == ((b) < 0))
 
 /*************************************************************************
  * Private routines
@@ -1157,3 +1158,22 @@ int8_cash(PG_FUNCTION_ARGS)
 
 	PG_RETURN_CASH(result);
 }
+
+Datum
+cash_distance(PG_FUNCTION_ARGS)
+{
+	Cash		a = PG_GETARG_CASH(0);
+	Cash		b = PG_GETARG_CASH(1);
+	Cash		r;
+	Cash		ra;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("money out of range")));
+
+	ra = Abs(r);
+
+	PG_RETURN_CASH(ra);
+}
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index cf5a1c6..4492f69 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -563,6 +563,17 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+Datum
+date_distance(PG_FUNCTION_ARGS)
+{
+	/* we assume the difference can't overflow */
+	Datum		diff = DirectFunctionCall2(date_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
+}
+
 /*
  * Internal routines for promoting date to timestamp and timestamp with
  * time zone
@@ -760,6 +771,29 @@ date_cmp_timestamp(PG_FUNCTION_ARGS)
 }
 
 Datum
+date_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+	Timestamp	dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
+Datum
 date_eq_timestamptz(PG_FUNCTION_ARGS)
 {
 	DateADT		dateVal = PG_GETARG_DATEADT(0);
@@ -844,6 +878,30 @@ date_cmp_timestamptz(PG_FUNCTION_ARGS)
 }
 
 Datum
+date_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
+
+
+Datum
 timestamp_eq_date(PG_FUNCTION_ARGS)
 {
 	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
@@ -928,6 +986,29 @@ timestamp_cmp_date(PG_FUNCTION_ARGS)
 }
 
 Datum
+timestamp_dist_date(PG_FUNCTION_ARGS)
+{
+	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	Timestamp	dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
+Datum
 timestamptz_eq_date(PG_FUNCTION_ARGS)
 {
 	TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
@@ -1039,6 +1120,28 @@ in_range_date_interval(PG_FUNCTION_ARGS)
 							   BoolGetDatum(less));
 }
 
+Datum
+timestamptz_dist_date(PG_FUNCTION_ARGS)
+{
+	TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	TimestampTz dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
 
 /* Add an interval to a date, giving a new date.
  * Must handle both positive and negative intervals.
@@ -1957,6 +2060,16 @@ time_part(PG_FUNCTION_ARGS)
 	PG_RETURN_FLOAT8(result);
 }
 
+Datum
+time_distance(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(time_mi_time,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
+
 
 /*****************************************************************************
  *	 Time With Time Zone ADT
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 37c202d..d72fbef 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -3726,6 +3726,47 @@ width_bucket_float8(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(result);
 }
 
+Datum
+float4dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float4		r = float4_mi(a, b);
+
+	PG_RETURN_FLOAT4(Abs(r));
+}
+
+Datum
+float8dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
+
+Datum
+float48dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
+Datum
+float84dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(Abs(r));
+}
+
 /* ========== PRIVATE ROUTINES ========== */
 
 #ifndef HAVE_CBRT
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 04825fc..76e92ec 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -1501,3 +1501,54 @@ generate_series_int4_support(PG_FUNCTION_ARGS)
 
 	PG_RETURN_POINTER(ret);
 }
+
+Datum
+int2dist(PG_FUNCTION_ARGS)
+{
+	int16		a = PG_GETARG_INT16(0);
+	int16		b = PG_GETARG_INT16(1);
+	int16		r;
+	int16		ra;
+
+	if (pg_sub_s16_overflow(a, b, &r) ||
+		r == PG_INT16_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("smallint out of range")));
+
+	ra = Abs(r);
+
+	PG_RETURN_INT16(ra);
+}
+
+static int32
+int44_dist(int32 a, int32 b)
+{
+	int32		r;
+
+	if (pg_sub_s32_overflow(a, b, &r) ||
+		r == PG_INT32_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("integer out of range")));
+
+	return Abs(r);
+}
+
+Datum
+int4dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int24dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT16(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int42dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT16(1)));
+}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 0ff9394..2ceb16b 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1446,3 +1446,47 @@ generate_series_int8_support(PG_FUNCTION_ARGS)
 
 	PG_RETURN_POINTER(ret);
 }
+
+static int64
+int88_dist(int64 a, int64 b)
+{
+	int64		r;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("bigint out of range")));
+
+	return Abs(r);
+}
+
+Datum
+int8dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int82dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT16(1)));
+}
+
+Datum
+int84dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT32(1)));
+}
+
+Datum
+int28dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT16(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int48dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT32(0), PG_GETARG_INT64(1)));
+}
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index bb67e01..b8f48d5 100644
--- a/src/backend/utils/adt/oid.c
+++ b/src/backend/utils/adt/oid.c
@@ -469,3 +469,17 @@ oidvectorgt(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(cmp > 0);
 }
+
+Datum
+oiddist(PG_FUNCTION_ARGS)
+{
+	Oid			a = PG_GETARG_OID(0);
+	Oid			b = PG_GETARG_OID(1);
+	Oid			res;
+
+	if (a < b)
+		res = b - a;
+	else
+		res = a - b;
+	PG_RETURN_OID(res);
+}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index e0ef2f7..c5d1042 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -2694,6 +2694,86 @@ timestamp_mi(PG_FUNCTION_ARGS)
 	PG_RETURN_INTERVAL_P(result);
 }
 
+Datum
+timestamp_distance(PG_FUNCTION_ARGS)
+{
+	Timestamp	a = PG_GETARG_TIMESTAMP(0);
+	Timestamp	b = PG_GETARG_TIMESTAMP(1);
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		Interval   *p = palloc(sizeof(Interval));
+
+		p->day = INT_MAX;
+		p->month = INT_MAX;
+		p->time = PG_INT64_MAX;
+		PG_RETURN_INTERVAL_P(p);
+	}
+	else
+		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+												  PG_GETARG_DATUM(0),
+												  PG_GETARG_DATUM(1)));
+	PG_RETURN_INTERVAL_P(abs_interval(r));
+}
+
+Interval *
+timestamp_dist_internal(Timestamp a, Timestamp b)
+{
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		return r;
+	}
+
+	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+											  TimestampGetDatum(a),
+											  TimestampGetDatum(b)));
+
+	return abs_interval(r);
+}
+
+Datum
+timestamptz_distance(PG_FUNCTION_ARGS)
+{
+	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
+	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(a, b));
+}
+
+Datum
+timestamp_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	Timestamp	ts1 = PG_GETARG_TIMESTAMP(0);
+	TimestampTz tstz2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz tstz1;
+
+	tstz1 = timestamp2timestamptz(ts1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+Datum
+timestamptz_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	TimestampTz tstz1 = PG_GETARG_TIMESTAMPTZ(0);
+	Timestamp	ts2 = PG_GETARG_TIMESTAMP(1);
+	TimestampTz tstz2;
+
+	tstz2 = timestamp2timestamptz(ts2);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+
 /*
  *	interval_justify_interval()
  *
@@ -3563,6 +3643,29 @@ interval_avg(PG_FUNCTION_ARGS)
 							   Float8GetDatum((double) N.time));
 }
 
+Interval *
+abs_interval(Interval *a)
+{
+	static Interval zero = {0, 0, 0};
+
+	if (DatumGetBool(DirectFunctionCall2(interval_lt,
+										 IntervalPGetDatum(a),
+										 IntervalPGetDatum(&zero))))
+		a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
+												  IntervalPGetDatum(a)));
+
+	return a;
+}
+
+Datum
+interval_distance(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(interval_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
 
 /* timestamp_age()
  * Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0ab95d8..0e06b04 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -30,6 +30,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int2,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int24
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -47,6 +51,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int2,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int28
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -64,6 +72,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int2,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int4
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -81,6 +93,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int4,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int42
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -98,6 +114,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int4,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int48
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -115,6 +135,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int4,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int8
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -132,6 +156,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int8,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int82
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -149,6 +177,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int8,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int84
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -166,6 +198,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int8,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # btree oid_ops
 
@@ -179,6 +215,10 @@
   amopstrategy => '4', amopopr => '>=(oid,oid)', amopmethod => 'btree' },
 { amopfamily => 'btree/oid_ops', amoplefttype => 'oid', amoprighttype => 'oid',
   amopstrategy => '5', amopopr => '>(oid,oid)', amopmethod => 'btree' },
+{ amopfamily => 'btree/oid_ops', amoplefttype => 'oid',
+  amoprighttype => 'oid', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(oid,oid)', amopmethod => 'btree',
+  amopsortfamily => 'btree/oid_ops' },
 
 # btree tid_ops
 
@@ -229,6 +269,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float4,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float48
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
@@ -246,6 +290,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float4,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # default operators float8
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -263,6 +311,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float8,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float84
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -280,6 +332,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float8,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # btree char_ops
 
@@ -416,6 +472,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(date,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -433,6 +493,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(date,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -450,6 +514,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(date,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -467,6 +535,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamp,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -484,6 +556,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(timestamp,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -501,6 +577,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamp,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -518,6 +598,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -535,6 +619,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'date', amopstrategy => '5',
   amopopr => '>(timestamptz,date)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -552,6 +640,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree time_ops
 
@@ -570,6 +662,10 @@
 { amopfamily => 'btree/time_ops', amoplefttype => 'time',
   amoprighttype => 'time', amopstrategy => '5', amopopr => '>(time,time)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/time_ops', amoplefttype => 'time',
+  amoprighttype => 'time', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(time,time)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree timetz_ops
 
@@ -606,6 +702,10 @@
 { amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
   amoprighttype => 'interval', amopstrategy => '5',
   amopopr => '>(interval,interval)', amopmethod => 'btree' },
+{ amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
+  amoprighttype => 'interval', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(interval,interval)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree macaddr
 
@@ -781,6 +881,10 @@
 { amopfamily => 'btree/money_ops', amoplefttype => 'money',
   amoprighttype => 'money', amopstrategy => '5', amopopr => '>(money,money)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/money_ops', amoplefttype => 'money',
+  amoprighttype => 'money', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(money,money)', amopmethod => 'btree',
+  amopsortfamily => 'btree/money_ops' },
 
 # btree array_ops
 
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 06aec07..914ca76 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2851,6 +2851,114 @@
   oprname => '-', oprleft => 'pg_lsn', oprright => 'pg_lsn',
   oprresult => 'numeric', oprcode => 'pg_lsn_mi' },
 
+# distance operators
+{ oid => '4217', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int2', oprresult => 'int2', oprcom => '<->(int2,int2)',
+  oprcode => 'int2dist'},
+{ oid => '4218', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int4)',
+  oprcode => 'int4dist'},
+{ oid => '4219', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int8)',
+  oprcode => 'int8dist'},
+{ oid => '4220', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'oid',
+  oprright => 'oid', oprresult => 'oid', oprcom => '<->(oid,oid)',
+  oprcode => 'oiddist'},
+{ oid => '4221', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float4', oprresult => 'float4', oprcom => '<->(float4,float4)',
+  oprcode => 'float4dist'},
+{ oid => '4222', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float8)',
+  oprcode => 'float8dist'},
+{ oid => '4223', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'money',
+  oprright => 'money', oprresult => 'money', oprcom => '<->(money,money)',
+  oprcode => 'cash_distance'},
+{ oid => '4224', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'date', oprresult => 'int4', oprcom => '<->(date,date)',
+  oprcode => 'date_distance'},
+{ oid => '4225', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'time',
+  oprright => 'time', oprresult => 'interval', oprcom => '<->(time,time)',
+  oprcode => 'time_distance'},
+{ oid => '4226', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamp)',
+  oprcode => 'timestamp_distance'},
+{ oid => '4227', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamptz)',
+  oprcode => 'timestamptz_distance'},
+{ oid => '4228', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'interval',
+  oprright => 'interval', oprresult => 'interval', oprcom => '<->(interval,interval)',
+  oprcode => 'interval_distance'},
+
+# cross-type distance operators
+{ oid => '4229', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int2)',
+  oprcode => 'int24dist'},
+{ oid => '4230', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int2', oprresult => 'int4', oprcom => '<->(int2,int4)',
+  oprcode => 'int42dist'},
+{ oid => '4231', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int2)',
+  oprcode => 'int28dist'},
+{ oid => '4232', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int2', oprresult => 'int8', oprcom => '<->(int2,int8)',
+  oprcode => 'int82dist'},
+{ oid => '4233', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int4)',
+  oprcode => 'int48dist'},
+{ oid => '4234', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int4', oprresult => 'int8', oprcom => '<->(int4,int8)',
+  oprcode => 'int84dist'},
+{ oid => '4235', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float4)',
+  oprcode => 'float48dist'},
+{ oid => '4236', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float4', oprresult => 'float8', oprcom => '<->(float4,float8)',
+  oprcode => 'float84dist'},
+{ oid => '4237', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,date)',
+  oprcode => 'date_dist_timestamp'},
+{ oid => '4238', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamp)',
+  oprcode => 'timestamp_dist_date'},
+{ oid => '4239', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,date)',
+  oprcode => 'date_dist_timestamptz'},
+{ oid => '4240', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamptz)',
+  oprcode => 'timestamptz_dist_date'},
+{ oid => '4241', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamp)',
+  oprcode => 'timestamp_dist_timestamptz'},
+{ oid => '4242', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamptz)',
+  oprcode => 'timestamptz_dist_timestamp'},
+
 # enum operators
 { oid => '3516', descr => 'equal',
   oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'anyenum',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a4e173b..96d5db0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10534,4 +10534,90 @@
   proname => 'pg_partition_root', prorettype => 'regclass',
   proargtypes => 'regclass', prosrc => 'pg_partition_root' },
 
+# distance functions
+{ oid => '4243',
+  proname => 'int2dist', prorettype => 'int2',
+  proargtypes => 'int2 int2', prosrc => 'int2dist' },
+{ oid => '4244',
+  proname => 'int4dist', prorettype => 'int4',
+  proargtypes => 'int4 int4', prosrc => 'int4dist' },
+{ oid => '4245',
+  proname => 'int8dist', prorettype => 'int8',
+  proargtypes => 'int8 int8', prosrc => 'int8dist' },
+{ oid => '4246',
+  proname => 'oiddist', proleakproof => 't', prorettype => 'oid',
+  proargtypes => 'oid oid', prosrc => 'oiddist' },
+{ oid => '4247',
+  proname => 'float4dist', prorettype => 'float4',
+  proargtypes => 'float4 float4', prosrc => 'float4dist' },
+{ oid => '4248',
+  proname => 'float8dist', prorettype => 'float8',
+  proargtypes => 'float8 float8', prosrc => 'float8dist' },
+{ oid => '4249',
+  proname => 'cash_distance', prorettype => 'money',
+  proargtypes => 'money money', prosrc => 'cash_distance' },
+{ oid => '4250',
+  proname => 'date_distance', prorettype => 'int4',
+  proargtypes => 'date date', prosrc => 'date_distance' },
+{ oid => '4251',
+  proname => 'time_distance', prorettype => 'interval',
+  proargtypes => 'time time', prosrc => 'time_distance' },
+{ oid => '4252',
+  proname => 'timestamp_distance', prorettype => 'interval',
+  proargtypes => 'timestamp timestamp', prosrc => 'timestamp_distance' },
+{ oid => '4253',
+  proname => 'timestamptz_distance', prorettype => 'interval',
+  proargtypes => 'timestamptz timestamptz', prosrc => 'timestamptz_distance' },
+{ oid => '4254',
+  proname => 'interval_distance', prorettype => 'interval',
+  proargtypes => 'interval interval', prosrc => 'interval_distance' },
+
+# cross-type distance functions
+{ oid => '4255',
+  proname => 'int24dist', prorettype => 'int4',
+  proargtypes => 'int2 int4', prosrc => 'int24dist' },
+{ oid => '4256',
+  proname => 'int28dist', prorettype => 'int8',
+  proargtypes => 'int2 int8', prosrc => 'int28dist' },
+{ oid => '4257',
+  proname => 'int42dist', prorettype => 'int4',
+  proargtypes => 'int4 int2', prosrc => 'int42dist' },
+{ oid => '4258',
+  proname => 'int48dist', prorettype => 'int8',
+  proargtypes => 'int4 int8', prosrc => 'int48dist' },
+{ oid => '4259',
+  proname => 'int82dist', prorettype => 'int8',
+  proargtypes => 'int8 int2', prosrc => 'int82dist' },
+{ oid => '4260',
+  proname => 'int84dist', prorettype => 'int8',
+  proargtypes => 'int8 int4', prosrc => 'int84dist' },
+{ oid => '4261',
+  proname => 'float48dist', prorettype => 'float8',
+  proargtypes => 'float4 float8', prosrc => 'float48dist' },
+{ oid => '4262',
+  proname => 'float84dist', prorettype => 'float8',
+  proargtypes => 'float8 float4', prosrc => 'float84dist' },
+{ oid => '4263',
+  proname => 'date_dist_timestamp', prorettype => 'interval',
+  proargtypes => 'date timestamp', prosrc => 'date_dist_timestamp' },
+{ oid => '4264',
+  proname => 'date_dist_timestamptz', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'date timestamptz',
+  prosrc => 'date_dist_timestamptz' },
+{ oid => '4265',
+  proname => 'timestamp_dist_date', prorettype => 'interval',
+  proargtypes => 'timestamp date', prosrc => 'timestamp_dist_date' },
+{ oid => '4266',
+  proname => 'timestamp_dist_timestamptz', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamp timestamptz',
+  prosrc => 'timestamp_dist_timestamptz' },
+{ oid => '4267',
+  proname => 'timestamptz_dist_date', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamptz date',
+  prosrc => 'timestamptz_dist_date' },
+{ oid => '4268',
+  proname => 'timestamptz_dist_timestamp', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamptz timestamp',
+  prosrc => 'timestamptz_dist_timestamp' },
+
 ]
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 87f819e..01d2284 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -338,4 +338,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
 					   int n);
 extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 
+extern Interval *abs_interval(Interval *a);
+
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index aeb89dc..438a5eb 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -93,9 +93,11 @@ extern Timestamp SetEpochTimestamp(void);
 extern void GetEpochTime(struct pg_tm *tm);
 
 extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern Interval *timestamp_dist_internal(Timestamp a, Timestamp b);
 
-/* timestamp comparison works for timestamptz also */
+/* timestamp comparison and distance works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
+#define timestamptz_dist_internal(dt1,dt2)	timestamp_dist_internal(dt1, dt2)
 
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index 4570a39..630dc6b 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -24,7 +24,7 @@ select prop,
  nulls_first        |    |       | f
  nulls_last         |    |       | t
  orderable          |    |       | t
- distance_orderable |    |       | f
+ distance_orderable |    |       | t
  returnable         |    |       | t
  search_array       |    |       | t
  search_nulls       |    |       | t
@@ -100,7 +100,7 @@ select prop,
  nulls_first        | f     | f    | f    | f            | f           | f   | f
  nulls_last         | t     | f    | f    | f            | f           | f   | f
  orderable          | t     | f    | f    | f            | f           | f   | f
- distance_orderable | f     | f    | t    | f            | t           | f   | f
+ distance_orderable | t     | f    | t    | f            | t           | f   | f
  returnable         | t     | f    | f    | t            | t           | f   | f
  search_array       | t     | f    | f    | f            | f           | f   | f
  search_nulls       | t     | f    | t    | t            | t           | f   | t
@@ -231,7 +231,7 @@ select col, prop, pg_index_column_has_property(o, col, prop)
    1 | desc               | f
    1 | nulls_first        | f
    1 | nulls_last         | t
-   1 | distance_orderable | f
+   1 | distance_orderable | t
    1 | returnable         | t
    1 | bogus              | 
    2 | orderable          | f
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
index 1bcc946..b9b819c 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -1477,3 +1477,64 @@ select make_time(10, 55, 100.1);
 ERROR:  time field value out of range: 10:55:100.1
 select make_time(24, 0, 2.1);
 ERROR:  time field value out of range: 24:00:2.1
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+ Fifteen | Distance 
+---------+----------
+         |    16006
+         |    15941
+         |     1802
+         |     1801
+         |     1800
+         |     1799
+         |     1436
+         |     1435
+         |     1434
+         |      308
+         |      307
+         |      306
+         |    13578
+         |    13944
+         |    14311
+(15 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+ Fifteen |               Distance                
+---------+---------------------------------------
+         | @ 16006 days 1 hour 23 mins 45 secs
+         | @ 15941 days 1 hour 23 mins 45 secs
+         | @ 1802 days 1 hour 23 mins 45 secs
+         | @ 1801 days 1 hour 23 mins 45 secs
+         | @ 1800 days 1 hour 23 mins 45 secs
+         | @ 1799 days 1 hour 23 mins 45 secs
+         | @ 1436 days 1 hour 23 mins 45 secs
+         | @ 1435 days 1 hour 23 mins 45 secs
+         | @ 1434 days 1 hour 23 mins 45 secs
+         | @ 308 days 1 hour 23 mins 45 secs
+         | @ 307 days 1 hour 23 mins 45 secs
+         | @ 306 days 1 hour 23 mins 45 secs
+         | @ 13577 days 22 hours 36 mins 15 secs
+         | @ 13943 days 22 hours 36 mins 15 secs
+         | @ 14310 days 22 hours 36 mins 15 secs
+(15 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
+ Fifteen |               Distance                
+---------+---------------------------------------
+         | @ 16005 days 14 hours 23 mins 45 secs
+         | @ 15940 days 14 hours 23 mins 45 secs
+         | @ 1801 days 14 hours 23 mins 45 secs
+         | @ 1800 days 14 hours 23 mins 45 secs
+         | @ 1799 days 14 hours 23 mins 45 secs
+         | @ 1798 days 14 hours 23 mins 45 secs
+         | @ 1435 days 14 hours 23 mins 45 secs
+         | @ 1434 days 14 hours 23 mins 45 secs
+         | @ 1433 days 14 hours 23 mins 45 secs
+         | @ 307 days 14 hours 23 mins 45 secs
+         | @ 306 days 14 hours 23 mins 45 secs
+         | @ 305 days 15 hours 23 mins 45 secs
+         | @ 13578 days 8 hours 36 mins 15 secs
+         | @ 13944 days 8 hours 36 mins 15 secs
+         | @ 14311 days 8 hours 36 mins 15 secs
+(15 rows)
+
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index cf78277..ce29924e 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -273,6 +273,26 @@ SELECT '' AS five, * FROM FLOAT4_TBL;
       | -1.2345679e-20
 (5 rows)
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+ five |       f1       |     dist      
+------+----------------+---------------
+      |              0 |        1004.3
+      |         -34.84 |       1039.14
+      |        -1004.3 |        2008.6
+      | -1.2345679e+20 | 1.2345679e+20
+      | -1.2345679e-20 |        1004.3
+(5 rows)
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+ five |       f1       |          dist          
+------+----------------+------------------------
+      |              0 |                 1004.3
+      |         -34.84 |     1039.1400001525878
+      |        -1004.3 |     2008.5999877929687
+      | -1.2345679e+20 | 1.2345678955701443e+20
+      | -1.2345679e-20 |                 1004.3
+(5 rows)
+
 -- test edge-case coercions to integer
 SELECT '32767.4'::float4::int2;
  int2  
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
index c3a6f53..5485c19 100644
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -413,6 +413,27 @@ SELECT '' AS five, f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
       | 1.2345678901234e-200 |  2.3112042409018e-67
 (5 rows)
 
+-- distance
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+ five |          f1          |         dist         
+------+----------------------+----------------------
+      |                    0 |               1004.3
+      |               1004.3 |                    0
+      |               -34.84 |              1039.14
+      | 1.2345678901234e+200 | 1.2345678901234e+200
+      | 1.2345678901234e-200 |               1004.3
+(5 rows)
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
+ five |          f1          |         dist         
+------+----------------------+----------------------
+      |                    0 |     1004.29998779297
+      |               1004.3 | 1.22070312045253e-05
+      |               -34.84 |     1039.13998779297
+      | 1.2345678901234e+200 | 1.2345678901234e+200
+      | 1.2345678901234e-200 |     1004.29998779297
+(5 rows)
+
 SELECT '' AS five, * FROM FLOAT8_TBL;
  five |          f1          
 ------+----------------------
diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out
index 8c255b9..0edc57e 100644
--- a/src/test/regress/expected/int2.out
+++ b/src/test/regress/expected/int2.out
@@ -242,6 +242,39 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
       | -32767 | -16383
 (5 rows)
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+ERROR:  smallint out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+ four |  f1   |   x   
+------+-------+-------
+      |     0 |     2
+      |  1234 |  1232
+      | -1234 |  1236
+      | 32767 | 32765
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
   text  
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
index bda7a8d..3735dbc 100644
--- a/src/test/regress/expected/int4.out
+++ b/src/test/regress/expected/int4.out
@@ -247,6 +247,38 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
       | -2147483647 | -1073741823
 (5 rows)
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+ERROR:  integer out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+ five |     f1      |     x      
+------+-------------+------------
+      |           0 |          2
+      |      123456 |     123454
+      |     -123456 |     123458
+      |  2147483647 | 2147483645
+      | -2147483647 | 2147483649
+(5 rows)
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index 8447a28..d56886a 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -432,6 +432,37 @@ SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 A
  4567890123457035 | -4567890123456543 | 1123700970370370094 |     0
 (5 rows)
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
 SELECT q2, abs(q2) FROM INT8_TBL;
         q2         |       abs        
 -------------------+------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index f88f345..cb95adf 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -207,6 +207,21 @@ SELECT '' AS fortyfive, r1.*, r2.*
            | 34 years        | 6 years
 (45 rows)
 
+SELECT '' AS ten, f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+ ten |          ?column?          
+-----+----------------------------
+     | 2 days 02:59:00
+     | 2 days -02:00:00
+     | 8 days -03:00:00
+     | 34 years -2 days -03:00:00
+     | 3 mons -2 days -03:00:00
+     | 2 days 03:00:14
+     | 1 day 00:56:56
+     | 6 years -2 days -03:00:00
+     | 5 mons -2 days -03:00:00
+     | 5 mons -2 days +09:00:00
+(10 rows)
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index ab86595..fb2a489 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -123,6 +123,12 @@ SELECT m / 2::float4 FROM money_data;
    $61.50
 (1 row)
 
+SELECT m <-> '$123.45' FROM money_data;
+ ?column? 
+----------
+    $0.45
+(1 row)
+
 -- All true
 SELECT m = '$123.00' FROM money_data;
  ?column? 
diff --git a/src/test/regress/expected/oid.out b/src/test/regress/expected/oid.out
index 1eab9cc..5339a48 100644
--- a/src/test/regress/expected/oid.out
+++ b/src/test/regress/expected/oid.out
@@ -119,4 +119,17 @@ SELECT '' AS three, o.* FROM OID_TBL o WHERE o.f1 > '1234';
        |   99999999
 (3 rows)
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+ eight |     f1     |  ?column?  
+-------+------------+------------
+       |       1234 |       1111
+       |       1235 |       1112
+       |        987 |        864
+       | 4294966256 | 4294966133
+       |   99999999 |   99999876
+       |          5 |        118
+       |         10 |        113
+       |         15 |        108
+(8 rows)
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index ce25ee0..e637420 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -750,6 +750,7 @@ macaddr8_gt(macaddr8,macaddr8)
 macaddr8_ge(macaddr8,macaddr8)
 macaddr8_ne(macaddr8,macaddr8)
 macaddr8_cmp(macaddr8,macaddr8)
+oiddist(oid,oid)
 -- restore normal output mode
 \a\t
 -- List of functions used by libpq's fe-lobj.c
@@ -1332,7 +1333,7 @@ WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND
     ao.amoprighttype = ap.amprocrighttype AND
     ap.amprocnum = 1 AND
     (pp.provolatile != po.provolatile OR
-     pp.proleakproof != po.proleakproof)
+     (NOT pp.proleakproof AND po.proleakproof))
 ORDER BY 1;
                    proc                    | vp | lp |              opr              | vo | lo 
 -------------------------------------------+----+----+-------------------------------+----+----
@@ -1862,6 +1863,7 @@ ORDER BY 1, 2, 3;
         403 |            5 | *>
         403 |            5 | >
         403 |            5 | ~>~
+        403 |            6 | <->
         405 |            1 | =
         783 |            1 | <<
         783 |            1 | @@
@@ -1971,7 +1973,7 @@ ORDER BY 1, 2, 3;
        4000 |           26 | >>
        4000 |           27 | >>=
        4000 |           28 | ^@
-(123 rows)
+(124 rows)
 
 -- Check that all opclass search operators have selectivity estimators.
 -- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out
index 8e0afe6..ee74faa 100644
--- a/src/test/regress/expected/time.out
+++ b/src/test/regress/expected/time.out
@@ -86,3 +86,19 @@ ERROR:  operator is not unique: time without time zone + time without time zone
 LINE 1: SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
                   ^
 HINT:  Could not choose a best candidate operator. You might need to add explicit type casts.
+-- distance
+SELECT f1 AS "Ten", f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
+     Ten     |           Distance            
+-------------+-------------------------------
+ 00:00:00    | @ 1 hour 23 mins 45 secs
+ 01:00:00    | @ 23 mins 45 secs
+ 02:03:00    | @ 39 mins 15 secs
+ 11:59:00    | @ 10 hours 35 mins 15 secs
+ 12:00:00    | @ 10 hours 36 mins 15 secs
+ 12:01:00    | @ 10 hours 37 mins 15 secs
+ 23:59:00    | @ 22 hours 35 mins 15 secs
+ 23:59:59.99 | @ 22 hours 36 mins 14.99 secs
+ 15:36:39    | @ 14 hours 12 mins 54 secs
+ 15:36:39    | @ 14 hours 12 mins 54 secs
+(10 rows)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 4a2fabd..dcb4205 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1604,3 +1604,214 @@ SELECT make_timestamp(2014,12,28,6,30,45.887);
  Sun Dec 28 06:30:45.887 2014
 (1 row)
 
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 58 secs
+    | @ 1453 days 6 hours 27 mins 58.6 secs
+    | @ 1453 days 6 hours 27 mins 58.5 secs
+    | @ 1453 days 6 hours 27 mins 58.4 secs
+    | @ 1493 days
+    | @ 1492 days 20 hours 55 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 6 hours 27 mins 59 secs
+    | @ 231 days 18 hours 19 mins 20 secs
+    | @ 324 days 15 hours 45 mins 59 secs
+    | @ 324 days 10 hours 45 mins 58 secs
+    | @ 324 days 11 hours 45 mins 57 secs
+    | @ 324 days 20 hours 45 mins 56 secs
+    | @ 324 days 21 hours 45 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 28 mins
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 5 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1452 days 6 hours 27 mins 59 secs
+    | @ 1451 days 6 hours 27 mins 59 secs
+    | @ 1450 days 6 hours 27 mins 59 secs
+    | @ 1449 days 6 hours 27 mins 59 secs
+    | @ 1448 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 765901 days 6 hours 27 mins 59 secs
+    | @ 695407 days 6 hours 27 mins 59 secs
+    | @ 512786 days 6 hours 27 mins 59 secs
+    | @ 330165 days 6 hours 27 mins 59 secs
+    | @ 111019 days 6 hours 27 mins 59 secs
+    | @ 74495 days 6 hours 27 mins 59 secs
+    | @ 37971 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 35077 days 17 hours 32 mins 1 sec
+    | @ 1801 days 6 hours 27 mins 59 secs
+    | @ 1800 days 6 hours 27 mins 59 secs
+    | @ 1799 days 6 hours 27 mins 59 secs
+    | @ 1495 days 6 hours 27 mins 59 secs
+    | @ 1494 days 6 hours 27 mins 59 secs
+    | @ 1493 days 6 hours 27 mins 59 secs
+    | @ 1435 days 6 hours 27 mins 59 secs
+    | @ 1434 days 6 hours 27 mins 59 secs
+    | @ 1130 days 6 hours 27 mins 59 secs
+    | @ 1129 days 6 hours 27 mins 59 secs
+    | @ 399 days 6 hours 27 mins 59 secs
+    | @ 398 days 6 hours 27 mins 59 secs
+    | @ 33 days 6 hours 27 mins 59 secs
+    | @ 32 days 6 hours 27 mins 59 secs
+(63 rows)
+
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 1 hour 23 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 43 secs
+    | @ 1453 days 7 hours 51 mins 43.6 secs
+    | @ 1453 days 7 hours 51 mins 43.5 secs
+    | @ 1453 days 7 hours 51 mins 43.4 secs
+    | @ 1493 days 1 hour 23 mins 45 secs
+    | @ 1492 days 22 hours 19 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 7 hours 51 mins 44 secs
+    | @ 231 days 16 hours 55 mins 35 secs
+    | @ 324 days 17 hours 9 mins 44 secs
+    | @ 324 days 12 hours 9 mins 43 secs
+    | @ 324 days 13 hours 9 mins 42 secs
+    | @ 324 days 22 hours 9 mins 41 secs
+    | @ 324 days 23 hours 9 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 6 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1452 days 7 hours 51 mins 44 secs
+    | @ 1451 days 7 hours 51 mins 44 secs
+    | @ 1450 days 7 hours 51 mins 44 secs
+    | @ 1449 days 7 hours 51 mins 44 secs
+    | @ 1448 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 765901 days 7 hours 51 mins 44 secs
+    | @ 695407 days 7 hours 51 mins 44 secs
+    | @ 512786 days 7 hours 51 mins 44 secs
+    | @ 330165 days 7 hours 51 mins 44 secs
+    | @ 111019 days 7 hours 51 mins 44 secs
+    | @ 74495 days 7 hours 51 mins 44 secs
+    | @ 37971 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 35077 days 16 hours 8 mins 16 secs
+    | @ 1801 days 7 hours 51 mins 44 secs
+    | @ 1800 days 7 hours 51 mins 44 secs
+    | @ 1799 days 7 hours 51 mins 44 secs
+    | @ 1495 days 7 hours 51 mins 44 secs
+    | @ 1494 days 7 hours 51 mins 44 secs
+    | @ 1493 days 7 hours 51 mins 44 secs
+    | @ 1435 days 7 hours 51 mins 44 secs
+    | @ 1434 days 7 hours 51 mins 44 secs
+    | @ 1130 days 7 hours 51 mins 44 secs
+    | @ 1129 days 7 hours 51 mins 44 secs
+    | @ 399 days 7 hours 51 mins 44 secs
+    | @ 398 days 7 hours 51 mins 44 secs
+    | @ 33 days 7 hours 51 mins 44 secs
+    | @ 32 days 7 hours 51 mins 44 secs
+(63 rows)
+
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+ 63 |                Distance                
+----+----------------------------------------
+    | @ 11355 days 14 hours 23 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 43 secs
+    | @ 1452 days 20 hours 51 mins 43.6 secs
+    | @ 1452 days 20 hours 51 mins 43.5 secs
+    | @ 1452 days 20 hours 51 mins 43.4 secs
+    | @ 1492 days 14 hours 23 mins 45 secs
+    | @ 1492 days 11 hours 19 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 21 hours 51 mins 44 secs
+    | @ 232 days 2 hours 55 mins 35 secs
+    | @ 324 days 6 hours 9 mins 44 secs
+    | @ 324 days 1 hour 9 mins 43 secs
+    | @ 324 days 2 hours 9 mins 42 secs
+    | @ 324 days 11 hours 9 mins 41 secs
+    | @ 324 days 12 hours 9 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1451 days 20 hours 51 mins 44 secs
+    | @ 1450 days 20 hours 51 mins 44 secs
+    | @ 1449 days 20 hours 51 mins 44 secs
+    | @ 1448 days 20 hours 51 mins 44 secs
+    | @ 1447 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 765900 days 20 hours 51 mins 44 secs
+    | @ 695406 days 20 hours 51 mins 44 secs
+    | @ 512785 days 20 hours 51 mins 44 secs
+    | @ 330164 days 20 hours 51 mins 44 secs
+    | @ 111018 days 20 hours 51 mins 44 secs
+    | @ 74494 days 20 hours 51 mins 44 secs
+    | @ 37970 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 35078 days 3 hours 8 mins 16 secs
+    | @ 1800 days 20 hours 51 mins 44 secs
+    | @ 1799 days 20 hours 51 mins 44 secs
+    | @ 1798 days 20 hours 51 mins 44 secs
+    | @ 1494 days 20 hours 51 mins 44 secs
+    | @ 1493 days 20 hours 51 mins 44 secs
+    | @ 1492 days 20 hours 51 mins 44 secs
+    | @ 1434 days 20 hours 51 mins 44 secs
+    | @ 1433 days 20 hours 51 mins 44 secs
+    | @ 1129 days 20 hours 51 mins 44 secs
+    | @ 1128 days 20 hours 51 mins 44 secs
+    | @ 398 days 20 hours 51 mins 44 secs
+    | @ 397 days 20 hours 51 mins 44 secs
+    | @ 32 days 20 hours 51 mins 44 secs
+    | @ 31 days 20 hours 51 mins 44 secs
+(63 rows)
+
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 8a4c719..0a05e37 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2544,3 +2544,217 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
  Tue Jan 17 16:00:00 2017 PST
 (1 row)
 
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 8 hours
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 58 secs
+    | @ 1453 days 6 hours 27 mins 58.6 secs
+    | @ 1453 days 6 hours 27 mins 58.5 secs
+    | @ 1453 days 6 hours 27 mins 58.4 secs
+    | @ 1493 days
+    | @ 1492 days 20 hours 55 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1333 days 7 hours 27 mins 59 secs
+    | @ 231 days 17 hours 19 mins 20 secs
+    | @ 324 days 15 hours 45 mins 59 secs
+    | @ 324 days 19 hours 45 mins 58 secs
+    | @ 324 days 21 hours 45 mins 57 secs
+    | @ 324 days 20 hours 45 mins 56 secs
+    | @ 324 days 22 hours 45 mins 55 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 28 mins
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 14 hours 27 mins 59 secs
+    | @ 1453 days 9 hours 27 mins 59 secs
+    | @ 1303 days 10 hours 27 mins 59 secs
+    | @ 1333 days 6 hours 27 mins 59 secs
+    | @ 1453 days 6 hours 27 mins 59 secs
+    | @ 1452 days 6 hours 27 mins 59 secs
+    | @ 1451 days 6 hours 27 mins 59 secs
+    | @ 1450 days 6 hours 27 mins 59 secs
+    | @ 1449 days 6 hours 27 mins 59 secs
+    | @ 1448 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 765901 days 6 hours 27 mins 59 secs
+    | @ 695407 days 6 hours 27 mins 59 secs
+    | @ 512786 days 6 hours 27 mins 59 secs
+    | @ 330165 days 6 hours 27 mins 59 secs
+    | @ 111019 days 6 hours 27 mins 59 secs
+    | @ 74495 days 6 hours 27 mins 59 secs
+    | @ 37971 days 6 hours 27 mins 59 secs
+    | @ 1447 days 6 hours 27 mins 59 secs
+    | @ 35077 days 17 hours 32 mins 1 sec
+    | @ 1801 days 6 hours 27 mins 59 secs
+    | @ 1800 days 6 hours 27 mins 59 secs
+    | @ 1799 days 6 hours 27 mins 59 secs
+    | @ 1495 days 6 hours 27 mins 59 secs
+    | @ 1494 days 6 hours 27 mins 59 secs
+    | @ 1493 days 6 hours 27 mins 59 secs
+    | @ 1435 days 6 hours 27 mins 59 secs
+    | @ 1434 days 6 hours 27 mins 59 secs
+    | @ 1130 days 6 hours 27 mins 59 secs
+    | @ 1129 days 6 hours 27 mins 59 secs
+    | @ 399 days 6 hours 27 mins 59 secs
+    | @ 398 days 6 hours 27 mins 59 secs
+    | @ 33 days 6 hours 27 mins 59 secs
+    | @ 32 days 6 hours 27 mins 59 secs
+(64 rows)
+
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |               Distance                
+----+---------------------------------------
+    | @ 11356 days 9 hours 23 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 43 secs
+    | @ 1453 days 7 hours 51 mins 43.6 secs
+    | @ 1453 days 7 hours 51 mins 43.5 secs
+    | @ 1453 days 7 hours 51 mins 43.4 secs
+    | @ 1493 days 1 hour 23 mins 45 secs
+    | @ 1492 days 22 hours 19 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1333 days 8 hours 51 mins 44 secs
+    | @ 231 days 15 hours 55 mins 35 secs
+    | @ 324 days 17 hours 9 mins 44 secs
+    | @ 324 days 21 hours 9 mins 43 secs
+    | @ 324 days 23 hours 9 mins 42 secs
+    | @ 324 days 22 hours 9 mins 41 secs
+    | @ 325 days 9 mins 40 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 45 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 15 hours 51 mins 44 secs
+    | @ 1453 days 10 hours 51 mins 44 secs
+    | @ 1303 days 11 hours 51 mins 44 secs
+    | @ 1333 days 7 hours 51 mins 44 secs
+    | @ 1453 days 7 hours 51 mins 44 secs
+    | @ 1452 days 7 hours 51 mins 44 secs
+    | @ 1451 days 7 hours 51 mins 44 secs
+    | @ 1450 days 7 hours 51 mins 44 secs
+    | @ 1449 days 7 hours 51 mins 44 secs
+    | @ 1448 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 765901 days 7 hours 51 mins 44 secs
+    | @ 695407 days 7 hours 51 mins 44 secs
+    | @ 512786 days 7 hours 51 mins 44 secs
+    | @ 330165 days 7 hours 51 mins 44 secs
+    | @ 111019 days 7 hours 51 mins 44 secs
+    | @ 74495 days 7 hours 51 mins 44 secs
+    | @ 37971 days 7 hours 51 mins 44 secs
+    | @ 1447 days 7 hours 51 mins 44 secs
+    | @ 35077 days 16 hours 8 mins 16 secs
+    | @ 1801 days 7 hours 51 mins 44 secs
+    | @ 1800 days 7 hours 51 mins 44 secs
+    | @ 1799 days 7 hours 51 mins 44 secs
+    | @ 1495 days 7 hours 51 mins 44 secs
+    | @ 1494 days 7 hours 51 mins 44 secs
+    | @ 1493 days 7 hours 51 mins 44 secs
+    | @ 1435 days 7 hours 51 mins 44 secs
+    | @ 1434 days 7 hours 51 mins 44 secs
+    | @ 1130 days 7 hours 51 mins 44 secs
+    | @ 1129 days 7 hours 51 mins 44 secs
+    | @ 399 days 7 hours 51 mins 44 secs
+    | @ 398 days 7 hours 51 mins 44 secs
+    | @ 33 days 7 hours 51 mins 44 secs
+    | @ 32 days 7 hours 51 mins 44 secs
+(64 rows)
+
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+ERROR:  interval out of range
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+ 63 |                Distance                
+----+----------------------------------------
+    | @ 11355 days 22 hours 23 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 43 secs
+    | @ 1452 days 20 hours 51 mins 43.6 secs
+    | @ 1452 days 20 hours 51 mins 43.5 secs
+    | @ 1452 days 20 hours 51 mins 43.4 secs
+    | @ 1492 days 14 hours 23 mins 45 secs
+    | @ 1492 days 11 hours 19 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1332 days 21 hours 51 mins 44 secs
+    | @ 232 days 2 hours 55 mins 35 secs
+    | @ 324 days 6 hours 9 mins 44 secs
+    | @ 324 days 10 hours 9 mins 43 secs
+    | @ 324 days 12 hours 9 mins 42 secs
+    | @ 324 days 11 hours 9 mins 41 secs
+    | @ 324 days 13 hours 9 mins 40 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 45 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1453 days 4 hours 51 mins 44 secs
+    | @ 1452 days 23 hours 51 mins 44 secs
+    | @ 1303 days 51 mins 44 secs
+    | @ 1332 days 20 hours 51 mins 44 secs
+    | @ 1452 days 20 hours 51 mins 44 secs
+    | @ 1451 days 20 hours 51 mins 44 secs
+    | @ 1450 days 20 hours 51 mins 44 secs
+    | @ 1449 days 20 hours 51 mins 44 secs
+    | @ 1448 days 20 hours 51 mins 44 secs
+    | @ 1447 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 765900 days 20 hours 51 mins 44 secs
+    | @ 695406 days 20 hours 51 mins 44 secs
+    | @ 512785 days 20 hours 51 mins 44 secs
+    | @ 330164 days 20 hours 51 mins 44 secs
+    | @ 111018 days 20 hours 51 mins 44 secs
+    | @ 74494 days 20 hours 51 mins 44 secs
+    | @ 37970 days 20 hours 51 mins 44 secs
+    | @ 1446 days 20 hours 51 mins 44 secs
+    | @ 35078 days 3 hours 8 mins 16 secs
+    | @ 1800 days 20 hours 51 mins 44 secs
+    | @ 1799 days 20 hours 51 mins 44 secs
+    | @ 1798 days 20 hours 51 mins 44 secs
+    | @ 1494 days 20 hours 51 mins 44 secs
+    | @ 1493 days 20 hours 51 mins 44 secs
+    | @ 1492 days 20 hours 51 mins 44 secs
+    | @ 1434 days 20 hours 51 mins 44 secs
+    | @ 1433 days 20 hours 51 mins 44 secs
+    | @ 1129 days 20 hours 51 mins 44 secs
+    | @ 1128 days 20 hours 51 mins 44 secs
+    | @ 398 days 20 hours 51 mins 44 secs
+    | @ 397 days 20 hours 51 mins 44 secs
+    | @ 32 days 20 hours 51 mins 44 secs
+    | @ 31 days 20 hours 51 mins 44 secs
+(64 rows)
+
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
index 22f80f2..24be476 100644
--- a/src/test/regress/sql/date.sql
+++ b/src/test/regress/sql/date.sql
@@ -346,3 +346,8 @@ select make_date(2013, 13, 1);
 select make_date(2013, 11, -1);
 select make_time(10, 55, 100.1);
 select make_time(24, 0, 2.1);
+
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
diff --git a/src/test/regress/sql/float4.sql b/src/test/regress/sql/float4.sql
index 646027f..35b5942 100644
--- a/src/test/regress/sql/float4.sql
+++ b/src/test/regress/sql/float4.sql
@@ -87,6 +87,9 @@ UPDATE FLOAT4_TBL
 
 SELECT '' AS five, * FROM FLOAT4_TBL;
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+
 -- test edge-case coercions to integer
 SELECT '32767.4'::float4::int2;
 SELECT '32767.6'::float4::int2;
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
index a333218..7fe9bca 100644
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -131,6 +131,9 @@ SELECT ||/ float8 '27' AS three;
 
 SELECT '' AS five, f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
 
+-- distance
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
 
 SELECT '' AS five, * FROM FLOAT8_TBL;
 
diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql
index 7dbafb6..16dd5d8 100644
--- a/src/test/regress/sql/int2.sql
+++ b/src/test/regress/sql/int2.sql
@@ -84,6 +84,16 @@ SELECT '' AS five, i.f1, i.f1 / int2 '2' AS x FROM INT2_TBL i;
 
 SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
 SELECT ((-1::int2<<15)+1::int2)::text;
diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql
index f014cb2..cff32946 100644
--- a/src/test/regress/sql/int4.sql
+++ b/src/test/regress/sql/int4.sql
@@ -93,6 +93,16 @@ SELECT '' AS five, i.f1, i.f1 / int2 '2' AS x FROM INT4_TBL i;
 
 SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql
index e890452..d7f5bde 100644
--- a/src/test/regress/sql/int8.sql
+++ b/src/test/regress/sql/int8.sql
@@ -89,6 +89,11 @@ SELECT q1 + 42::int2 AS "8plus2", q1 - 42::int2 AS "8minus2", q1 * 42::int2 AS "
 -- int2 op int8
 SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 AS "2mul8", 246::int2 / q1 AS "2div8" FROM INT8_TBL;
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+
 SELECT q2, abs(q2) FROM INT8_TBL;
 SELECT min(q1), min(q2) FROM INT8_TBL;
 SELECT max(q1), max(q2) FROM INT8_TBL;
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index bc5537d..d51c866 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -59,6 +59,8 @@ SELECT '' AS fortyfive, r1.*, r2.*
    WHERE r1.f1 > r2.f1
    ORDER BY r1.f1, r2.f1;
 
+SELECT '' AS ten, f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql
index 37b9ecc..8428d59 100644
--- a/src/test/regress/sql/money.sql
+++ b/src/test/regress/sql/money.sql
@@ -25,6 +25,7 @@ SELECT m / 2::float8 FROM money_data;
 SELECT m * 2::float4 FROM money_data;
 SELECT 2::float4 * m FROM money_data;
 SELECT m / 2::float4 FROM money_data;
+SELECT m <-> '$123.45' FROM money_data;
 
 -- All true
 SELECT m = '$123.00' FROM money_data;
diff --git a/src/test/regress/sql/oid.sql b/src/test/regress/sql/oid.sql
index 4a09689..9f54f92 100644
--- a/src/test/regress/sql/oid.sql
+++ b/src/test/regress/sql/oid.sql
@@ -40,4 +40,6 @@ SELECT '' AS four, o.* FROM OID_TBL o WHERE o.f1 >= '1234';
 
 SELECT '' AS three, o.* FROM OID_TBL o WHERE o.f1 > '1234';
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index e2014fc..959928b 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -818,7 +818,7 @@ WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND
     ao.amoprighttype = ap.amprocrighttype AND
     ap.amprocnum = 1 AND
     (pp.provolatile != po.provolatile OR
-     pp.proleakproof != po.proleakproof)
+     (NOT pp.proleakproof AND po.proleakproof))
 ORDER BY 1;
 
 
diff --git a/src/test/regress/sql/time.sql b/src/test/regress/sql/time.sql
index 99a1562..31f0330 100644
--- a/src/test/regress/sql/time.sql
+++ b/src/test/regress/sql/time.sql
@@ -40,3 +40,6 @@ SELECT f1 AS "Eight" FROM TIME_TBL WHERE f1 >= '00:00';
 -- where we do mixed-type arithmetic. - thomas 2000-12-02
 
 SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
+
+-- distance
+SELECT f1 AS "Ten", f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b7957cb..5d023dd 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -230,3 +230,11 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
 
 -- timestamp numeric fields constructor
 SELECT make_timestamp(2014,12,28,6,30,45.887);
+
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index c3bd46c..7f0525d 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -464,3 +464,11 @@ insert into tmptz values ('2017-01-18 00:00+00');
 explain (costs off)
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- distance operators
+SELECT '' AS  "0", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT '' AS  "0", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT '' AS "63", d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
0006-Remove-distance-operators-from-btree_gist-v07.patchtext/x-patch; name=0006-Remove-distance-operators-from-btree_gist-v07.patchDownload
diff --git a/contrib/btree_gist/Makefile b/contrib/btree_gist/Makefile
index af65120..46ab241 100644
--- a/contrib/btree_gist/Makefile
+++ b/contrib/btree_gist/Makefile
@@ -11,8 +11,9 @@ OBJS =  btree_gist.o btree_utils_num.o btree_utils_var.o btree_int2.o \
 
 EXTENSION = btree_gist
 DATA = btree_gist--unpackaged--1.0.sql btree_gist--1.0--1.1.sql \
-       btree_gist--1.1--1.2.sql btree_gist--1.2.sql btree_gist--1.2--1.3.sql \
-       btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql
+       btree_gist--1.1--1.2.sql btree_gist--1.2--1.3.sql \
+       btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql \
+       btree_gist--1.5--1.6.sql btree_gist--1.6.sql
 PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes"
 
 REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \
diff --git a/contrib/btree_gist/btree_cash.c b/contrib/btree_gist/btree_cash.c
index 894d0a2..1b0e317 100644
--- a/contrib/btree_gist/btree_cash.c
+++ b/contrib/btree_gist/btree_cash.c
@@ -95,20 +95,7 @@ PG_FUNCTION_INFO_V1(cash_dist);
 Datum
 cash_dist(PG_FUNCTION_ARGS)
 {
-	Cash		a = PG_GETARG_CASH(0);
-	Cash		b = PG_GETARG_CASH(1);
-	Cash		r;
-	Cash		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("money out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_CASH(ra);
+	return cash_distance(fcinfo);
 }
 
 /**************************************************
diff --git a/contrib/btree_gist/btree_date.c b/contrib/btree_gist/btree_date.c
index 992ce57..f3f0fa1 100644
--- a/contrib/btree_gist/btree_date.c
+++ b/contrib/btree_gist/btree_date.c
@@ -113,12 +113,7 @@ PG_FUNCTION_INFO_V1(date_dist);
 Datum
 date_dist(PG_FUNCTION_ARGS)
 {
-	/* we assume the difference can't overflow */
-	Datum		diff = DirectFunctionCall2(date_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INT32(Abs(DatumGetInt32(diff)));
+	return date_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index 6b20f44..0a9148d 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -93,14 +93,7 @@ PG_FUNCTION_INFO_V1(float4_dist);
 Datum
 float4_dist(PG_FUNCTION_ARGS)
 {
-	float4		a = PG_GETARG_FLOAT4(0);
-	float4		b = PG_GETARG_FLOAT4(1);
-	float4		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT4(Abs(r));
+	return float4dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index ee114cb..8b73b57 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -101,14 +101,7 @@ PG_FUNCTION_INFO_V1(float8_dist);
 Datum
 float8_dist(PG_FUNCTION_ARGS)
 {
-	float8		a = PG_GETARG_FLOAT8(0);
-	float8		b = PG_GETARG_FLOAT8(1);
-	float8		r;
-
-	r = a - b;
-	CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
-	PG_RETURN_FLOAT8(Abs(r));
+	return float8dist(fcinfo);
 }
 
 /**************************************************
diff --git a/contrib/btree_gist/btree_gist--1.2.sql b/contrib/btree_gist/btree_gist--1.2.sql
deleted file mode 100644
index 1efe753..0000000
--- a/contrib/btree_gist/btree_gist--1.2.sql
+++ /dev/null
@@ -1,1570 +0,0 @@
-/* contrib/btree_gist/btree_gist--1.2.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
-
-CREATE FUNCTION gbtreekey4_in(cstring)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey4_out(gbtreekey4)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey4 (
-	INTERNALLENGTH = 4,
-	INPUT  = gbtreekey4_in,
-	OUTPUT = gbtreekey4_out
-);
-
-CREATE FUNCTION gbtreekey8_in(cstring)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey8_out(gbtreekey8)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey8 (
-	INTERNALLENGTH = 8,
-	INPUT  = gbtreekey8_in,
-	OUTPUT = gbtreekey8_out
-);
-
-CREATE FUNCTION gbtreekey16_in(cstring)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey16_out(gbtreekey16)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey16 (
-	INTERNALLENGTH = 16,
-	INPUT  = gbtreekey16_in,
-	OUTPUT = gbtreekey16_out
-);
-
-CREATE FUNCTION gbtreekey32_in(cstring)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey32_out(gbtreekey32)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey32 (
-	INTERNALLENGTH = 32,
-	INPUT  = gbtreekey32_in,
-	OUTPUT = gbtreekey32_out
-);
-
-CREATE FUNCTION gbtreekey_var_in(cstring)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME', 'gbtreekey_in'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
-RETURNS cstring
-AS 'MODULE_PATHNAME', 'gbtreekey_out'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE TYPE gbtreekey_var (
-	INTERNALLENGTH = VARIABLE,
-	INPUT  = gbtreekey_var_in,
-	OUTPUT = gbtreekey_var_out,
-	STORAGE = EXTENDED
-);
-
---distance operators
-
-CREATE FUNCTION cash_dist(money, money)
-RETURNS money
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = money,
-	RIGHTARG = money,
-	PROCEDURE = cash_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION date_dist(date, date)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = date,
-	RIGHTARG = date,
-	PROCEDURE = date_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float4_dist(float4, float4)
-RETURNS float4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float4,
-	RIGHTARG = float4,
-	PROCEDURE = float4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION float8_dist(float8, float8)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = float8,
-	RIGHTARG = float8,
-	PROCEDURE = float8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int2_dist(int2, int2)
-RETURNS int2
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int2,
-	RIGHTARG = int2,
-	PROCEDURE = int2_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int4_dist(int4, int4)
-RETURNS int4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int4,
-	RIGHTARG = int4,
-	PROCEDURE = int4_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION int8_dist(int8, int8)
-RETURNS int8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = int8,
-	RIGHTARG = int8,
-	PROCEDURE = int8_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION interval_dist(interval, interval)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = interval,
-	RIGHTARG = interval,
-	PROCEDURE = interval_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION oid_dist(oid, oid)
-RETURNS oid
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = oid,
-	RIGHTARG = oid,
-	PROCEDURE = oid_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION time_dist(time, time)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = time,
-	RIGHTARG = time,
-	PROCEDURE = time_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION ts_dist(timestamp, timestamp)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamp,
-	RIGHTARG = timestamp,
-	PROCEDURE = ts_dist,
-	COMMUTATOR = '<->'
-);
-
-CREATE FUNCTION tstz_dist(timestamptz, timestamptz)
-RETURNS interval
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE OPERATOR <-> (
-	LEFTARG = timestamptz,
-	RIGHTARG = timestamptz,
-	PROCEDURE = tstz_dist,
-	COMMUTATOR = '<->'
-);
-
-
---
---
---
--- oid ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_var_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_oid_ops
-DEFAULT FOR TYPE oid USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
-	FUNCTION	2	gbt_oid_union (internal, internal),
-	FUNCTION	3	gbt_oid_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_oid_picksplit (internal, internal),
-	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
--- Add operators that are new in 9.1.  We do it like this, leaving them
--- "loose" in the operator family rather than bound into the opclass, because
--- that's the only state that can be reproduced during an upgrade from 9.0.
-ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
-	OPERATOR	6	<> (oid, oid) ,
-	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
-	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
-	-- Also add support function for index-only-scans, added in 9.5.
-	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
-
-
---
---
---
--- int2 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_union(internal, internal)
-RETURNS gbtreekey4
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int2_ops
-DEFAULT FOR TYPE int2 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
-	FUNCTION	2	gbt_int2_union (internal, internal),
-	FUNCTION	3	gbt_int2_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int2_picksplit (internal, internal),
-	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
-	STORAGE		gbtreekey4;
-
-ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
-	OPERATOR	6	<> (int2, int2) ,
-	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
-	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
-
---
---
---
--- int4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int4_ops
-DEFAULT FOR TYPE int4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
-	FUNCTION	2	gbt_int4_union (internal, internal),
-	FUNCTION	3	gbt_int4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int4_picksplit (internal, internal),
-	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
-	OPERATOR	6	<> (int4, int4) ,
-	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
-	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
-
-
---
---
---
--- int8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_int8_ops
-DEFAULT FOR TYPE int8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
-	FUNCTION	2	gbt_int8_union (internal, internal),
-	FUNCTION	3	gbt_int8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_int8_picksplit (internal, internal),
-	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
-	OPERATOR	6	<> (int8, int8) ,
-	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
-	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
-
---
---
---
--- float4 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float4_ops
-DEFAULT FOR TYPE float4 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
-	FUNCTION	2	gbt_float4_union (internal, internal),
-	FUNCTION	3	gbt_float4_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float4_picksplit (internal, internal),
-	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
-	OPERATOR	6	<> (float4, float4) ,
-	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
-	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
-
---
---
---
--- float8 ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_float8_ops
-DEFAULT FOR TYPE float8 USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
-	FUNCTION	2	gbt_float8_union (internal, internal),
-	FUNCTION	3	gbt_float8_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_float8_picksplit (internal, internal),
-	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
-	OPERATOR	6	<> (float8, float8) ,
-	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
-	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
-	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
-
---
---
---
--- timestamp ops
---
---
---
-
-CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_tstz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamp_ops
-DEFAULT FOR TYPE timestamp USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_ts_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
-	OPERATOR	6	<> (timestamp, timestamp) ,
-	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
-	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_timestamptz_ops
-DEFAULT FOR TYPE timestamptz USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
-	FUNCTION	2	gbt_ts_union (internal, internal),
-	FUNCTION	3	gbt_tstz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_ts_picksplit (internal, internal),
-	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
-	OPERATOR	6	<> (timestamptz, timestamptz) ,
-	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
-	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
-
---
---
---
--- time ops
---
---
---
-
-CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_timetz_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_time_ops
-DEFAULT FOR TYPE time USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_time_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
-	OPERATOR	6	<> (time, time) ,
-	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
-	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
-
-
-CREATE OPERATOR CLASS gist_timetz_ops
-DEFAULT FOR TYPE timetz USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
-	FUNCTION	2	gbt_time_union (internal, internal),
-	FUNCTION	3	gbt_timetz_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_time_picksplit (internal, internal),
-	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
-	OPERATOR	6	<> (timetz, timetz) ;
-	-- no 'fetch' function, as the compress function is lossy.
-
-
---
---
---
--- date ops
---
---
---
-
-CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_union(internal, internal)
-RETURNS gbtreekey8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_date_ops
-DEFAULT FOR TYPE date USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
-	FUNCTION	2	gbt_date_union (internal, internal),
-	FUNCTION	3	gbt_date_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_date_picksplit (internal, internal),
-	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
-	STORAGE		gbtreekey8;
-
-ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
-	OPERATOR	6	<> (date, date) ,
-	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
-	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
-	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
-
-
---
---
---
--- interval ops
---
---
---
-
-CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_decompress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_union(internal, internal)
-RETURNS gbtreekey32
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_interval_ops
-DEFAULT FOR TYPE interval USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
-	FUNCTION	2	gbt_intv_union (internal, internal),
-	FUNCTION	3	gbt_intv_compress (internal),
-	FUNCTION	4	gbt_intv_decompress (internal),
-	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_intv_picksplit (internal, internal),
-	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
-	STORAGE		gbtreekey32;
-
-ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
-	OPERATOR	6	<> (interval, interval) ,
-	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
-	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
-	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
-
-
---
---
---
--- cash ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
-RETURNS float8
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cash_ops
-DEFAULT FOR TYPE money USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
-	FUNCTION	2	gbt_cash_union (internal, internal),
-	FUNCTION	3	gbt_cash_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_cash_picksplit (internal, internal),
-	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
-	OPERATOR	6	<> (money, money) ,
-	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
-	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
-	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
-
-
---
---
---
--- macaddr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_fetch(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_macaddr_ops
-DEFAULT FOR TYPE macaddr USING gist
-AS
-	OPERATOR	1	< ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	= ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	> ,
-	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
-	FUNCTION	2	gbt_macad_union (internal, internal),
-	FUNCTION	3	gbt_macad_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_macad_picksplit (internal, internal),
-	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
-	OPERATOR	6	<> (macaddr, macaddr) ,
-	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
-
-
---
---
---
--- text/ bpchar ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bpchar_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_text_ops
-DEFAULT FOR TYPE text USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_text_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
-	OPERATOR	6	<> (text, text) ,
-	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
-
-
----- Create the operator class
-CREATE OPERATOR CLASS gist_bpchar_ops
-DEFAULT FOR TYPE bpchar USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
-	FUNCTION	2	gbt_text_union (internal, internal),
-	FUNCTION	3	gbt_bpchar_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_text_picksplit (internal, internal),
-	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
-	OPERATOR	6	<> (bpchar, bpchar) ,
-	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
-
---
---
--- bytea ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bytea_ops
-DEFAULT FOR TYPE bytea USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
-	FUNCTION	2	gbt_bytea_union (internal, internal),
-	FUNCTION	3	gbt_bytea_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
-	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
-	OPERATOR	6	<> (bytea, bytea) ,
-	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
-
-
---
---
---
--- numeric ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_numeric_ops
-DEFAULT FOR TYPE numeric USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
-	FUNCTION	2	gbt_numeric_union (internal, internal),
-	FUNCTION	3	gbt_numeric_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
-	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
-	OPERATOR	6	<> (numeric, numeric) ,
-	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
-
-
---
---
--- bit ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_union(internal, internal)
-RETURNS gbtreekey_var
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_bit_ops
-DEFAULT FOR TYPE bit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
-	OPERATOR	6	<> (bit, bit) ,
-	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
-
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_vbit_ops
-DEFAULT FOR TYPE varbit USING gist
-AS
-	OPERATOR	1	<  ,
-	OPERATOR	2	<= ,
-	OPERATOR	3	=  ,
-	OPERATOR	4	>= ,
-	OPERATOR	5	>  ,
-	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
-	FUNCTION	2	gbt_bit_union (internal, internal),
-	FUNCTION	3	gbt_bit_compress (internal),
-	FUNCTION	4	gbt_var_decompress (internal),
-	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_bit_picksplit (internal, internal),
-	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
-	STORAGE			gbtreekey_var;
-
-ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
-	OPERATOR	6	<> (varbit, varbit) ,
-	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
-
-
---
---
---
--- inet/cidr ops
---
---
---
--- define the GiST support methods
-CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
-RETURNS bool
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_compress(internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_picksplit(internal, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_union(internal, internal)
-RETURNS gbtreekey16
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
-CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
-RETURNS internal
-AS 'MODULE_PATHNAME'
-LANGUAGE C IMMUTABLE STRICT;
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_inet_ops
-DEFAULT FOR TYPE inet USING gist
-AS
-	OPERATOR	1	<   ,
-	OPERATOR	2	<=  ,
-	OPERATOR	3	=   ,
-	OPERATOR	4	>=  ,
-	OPERATOR	5	>   ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
-	OPERATOR	6	<>  (inet, inet) ;
-	-- no fetch support, the compress function is lossy
-
--- Create the operator class
-CREATE OPERATOR CLASS gist_cidr_ops
-DEFAULT FOR TYPE cidr USING gist
-AS
-	OPERATOR	1	<  (inet, inet)  ,
-	OPERATOR	2	<= (inet, inet)  ,
-	OPERATOR	3	=  (inet, inet)  ,
-	OPERATOR	4	>= (inet, inet)  ,
-	OPERATOR	5	>  (inet, inet)  ,
-	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
-	FUNCTION	2	gbt_inet_union (internal, internal),
-	FUNCTION	3	gbt_inet_compress (internal),
-	FUNCTION	4	gbt_decompress (internal),
-	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
-	FUNCTION	6	gbt_inet_picksplit (internal, internal),
-	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
-	STORAGE		gbtreekey16;
-
-ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
-	OPERATOR	6	<> (inet, inet) ;
-	-- no fetch support, the compress function is lossy
diff --git a/contrib/btree_gist/btree_gist--1.5--1.6.sql b/contrib/btree_gist/btree_gist--1.5--1.6.sql
new file mode 100644
index 0000000..ef4424e
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.5--1.6.sql
@@ -0,0 +1,99 @@
+/* contrib/btree_gist/btree_gist--1.5--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.6'" to load this file. \quit
+
+-- drop btree_gist distance operators from opfamilies
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist DROP OPERATOR 15 (int2, int2);
+ALTER OPERATOR FAMILY gist_int4_ops USING gist DROP OPERATOR 15 (int4, int4);
+ALTER OPERATOR FAMILY gist_int8_ops USING gist DROP OPERATOR 15 (int8, int8);
+ALTER OPERATOR FAMILY gist_float4_ops USING gist DROP OPERATOR 15 (float4, float4);
+ALTER OPERATOR FAMILY gist_float8_ops USING gist DROP OPERATOR 15 (float8, float8);
+ALTER OPERATOR FAMILY gist_oid_ops USING gist DROP OPERATOR 15 (oid, oid);
+ALTER OPERATOR FAMILY gist_cash_ops USING gist DROP OPERATOR 15 (money, money);
+ALTER OPERATOR FAMILY gist_date_ops USING gist DROP OPERATOR 15 (date, date);
+ALTER OPERATOR FAMILY gist_time_ops USING gist DROP OPERATOR 15 (time, time);
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist DROP OPERATOR 15 (timestamp, timestamp);
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist DROP OPERATOR 15 (timestamptz, timestamptz);
+ALTER OPERATOR FAMILY gist_interval_ops USING gist DROP OPERATOR 15 (interval, interval);
+
+-- add pg_catalog distance operators to opfamilies
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD OPERATOR 15 <-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD OPERATOR 15 <-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD OPERATOR 15 <-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD OPERATOR 15 <-> (float4, float4) FOR ORDER BY pg_catalog.float_ops;
+ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD OPERATOR 15 <-> (float8, float8) FOR ORDER BY pg_catalog.float_ops;
+ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD OPERATOR 15 <-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops;
+ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD OPERATOR 15 <-> (money, money) FOR ORDER BY pg_catalog.money_ops;
+ALTER OPERATOR FAMILY gist_date_ops USING gist ADD OPERATOR 15 <-> (date, date) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_time_ops USING gist ADD OPERATOR 15 <-> (time, time) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD OPERATOR 15 <-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD OPERATOR 15 <-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD OPERATOR 15 <-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops;
+
+-- disable implicit pg_catalog search
+
+DO
+$$
+BEGIN
+	EXECUTE 'SET LOCAL search_path TO ' || current_schema() || ', pg_catalog';
+END
+$$;
+
+-- drop distance operators
+
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int2, int2);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int4, int4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (int8, int8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float4, float4);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (float8, float8);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (oid, oid);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (money, money);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (date, date);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (time, time);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP OPERATOR <-> (interval, interval);
+
+DROP OPERATOR <-> (int2, int2);
+DROP OPERATOR <-> (int4, int4);
+DROP OPERATOR <-> (int8, int8);
+DROP OPERATOR <-> (float4, float4);
+DROP OPERATOR <-> (float8, float8);
+DROP OPERATOR <-> (oid, oid);
+DROP OPERATOR <-> (money, money);
+DROP OPERATOR <-> (date, date);
+DROP OPERATOR <-> (time, time);
+DROP OPERATOR <-> (timestamp, timestamp);
+DROP OPERATOR <-> (timestamptz, timestamptz);
+DROP OPERATOR <-> (interval, interval);
+
+-- drop distance functions
+
+ALTER EXTENSION btree_gist DROP FUNCTION int2_dist(int2, int2);
+ALTER EXTENSION btree_gist DROP FUNCTION int4_dist(int4, int4);
+ALTER EXTENSION btree_gist DROP FUNCTION int8_dist(int8, int8);
+ALTER EXTENSION btree_gist DROP FUNCTION float4_dist(float4, float4);
+ALTER EXTENSION btree_gist DROP FUNCTION float8_dist(float8, float8);
+ALTER EXTENSION btree_gist DROP FUNCTION oid_dist(oid, oid);
+ALTER EXTENSION btree_gist DROP FUNCTION cash_dist(money, money);
+ALTER EXTENSION btree_gist DROP FUNCTION date_dist(date, date);
+ALTER EXTENSION btree_gist DROP FUNCTION time_dist(time, time);
+ALTER EXTENSION btree_gist DROP FUNCTION ts_dist(timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP FUNCTION interval_dist(interval, interval);
+
+DROP FUNCTION int2_dist(int2, int2);
+DROP FUNCTION int4_dist(int4, int4);
+DROP FUNCTION int8_dist(int8, int8);
+DROP FUNCTION float4_dist(float4, float4);
+DROP FUNCTION float8_dist(float8, float8);
+DROP FUNCTION oid_dist(oid, oid);
+DROP FUNCTION cash_dist(money, money);
+DROP FUNCTION date_dist(date, date);
+DROP FUNCTION time_dist(time, time);
+DROP FUNCTION ts_dist(timestamp, timestamp);
+DROP FUNCTION tstz_dist(timestamptz, timestamptz);
+DROP FUNCTION interval_dist(interval, interval);
diff --git a/contrib/btree_gist/btree_gist--1.6.sql b/contrib/btree_gist/btree_gist--1.6.sql
new file mode 100644
index 0000000..8ff8eb5
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.6.sql
@@ -0,0 +1,1615 @@
+/* contrib/btree_gist/btree_gist--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit
+
+CREATE FUNCTION gbtreekey4_in(cstring)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey4_out(gbtreekey4)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey4 (
+	INTERNALLENGTH = 4,
+	INPUT  = gbtreekey4_in,
+	OUTPUT = gbtreekey4_out
+);
+
+CREATE FUNCTION gbtreekey8_in(cstring)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey8_out(gbtreekey8)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey8 (
+	INTERNALLENGTH = 8,
+	INPUT  = gbtreekey8_in,
+	OUTPUT = gbtreekey8_out
+);
+
+CREATE FUNCTION gbtreekey16_in(cstring)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey16_out(gbtreekey16)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey16 (
+	INTERNALLENGTH = 16,
+	INPUT  = gbtreekey16_in,
+	OUTPUT = gbtreekey16_out
+);
+
+CREATE FUNCTION gbtreekey32_in(cstring)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey32_out(gbtreekey32)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey32 (
+	INTERNALLENGTH = 32,
+	INPUT  = gbtreekey32_in,
+	OUTPUT = gbtreekey32_out
+);
+
+CREATE FUNCTION gbtreekey_var_in(cstring)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME', 'gbtreekey_in'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbtreekey_var_out(gbtreekey_var)
+RETURNS cstring
+AS 'MODULE_PATHNAME', 'gbtreekey_out'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE TYPE gbtreekey_var (
+	INTERNALLENGTH = VARIABLE,
+	INPUT  = gbtreekey_var_in,
+	OUTPUT = gbtreekey_var_out,
+	STORAGE = EXTENDED
+);
+
+
+--
+--
+--
+-- oid ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_var_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_oid_ops
+DEFAULT FOR TYPE oid USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_oid_consistent (internal, oid, int2, oid, internal),
+	FUNCTION	2	gbt_oid_union (internal, internal),
+	FUNCTION	3	gbt_oid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_oid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_oid_picksplit (internal, internal),
+	FUNCTION	7	gbt_oid_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+-- Add operators that are new in 9.1.  We do it like this, leaving them
+-- "loose" in the operator family rather than bound into the opclass, because
+-- that's the only state that can be reproduced during an upgrade from 9.0.
+ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD
+	OPERATOR	6	<> (oid, oid) ,
+	OPERATOR	15	<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops ,
+	FUNCTION	8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) ,
+	-- Also add support function for index-only-scans, added in 9.5.
+	FUNCTION	9 (oid, oid) gbt_oid_fetch (internal) ;
+
+
+--
+--
+--
+-- int2 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_union(internal, internal)
+RETURNS gbtreekey4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int2_ops
+DEFAULT FOR TYPE int2 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int2_consistent (internal, int2, int2, oid, internal),
+	FUNCTION	2	gbt_int2_union (internal, internal),
+	FUNCTION	3	gbt_int2_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int2_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int2_picksplit (internal, internal),
+	FUNCTION	7	gbt_int2_same (gbtreekey4, gbtreekey4, internal),
+	STORAGE		gbtreekey4;
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD
+	OPERATOR	6	<> (int2, int2) ,
+	OPERATOR	15	<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) ,
+	FUNCTION	9 (int2, int2) gbt_int2_fetch (internal) ;
+
+--
+--
+--
+-- int4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int4_ops
+DEFAULT FOR TYPE int4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int4_consistent (internal, int4, int2, oid, internal),
+	FUNCTION	2	gbt_int4_union (internal, internal),
+	FUNCTION	3	gbt_int4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int4_picksplit (internal, internal),
+	FUNCTION	7	gbt_int4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD
+	OPERATOR	6	<> (int4, int4) ,
+	OPERATOR	15	<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) ,
+	FUNCTION	9 (int4, int4) gbt_int4_fetch (internal) ;
+
+
+--
+--
+--
+-- int8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_int8_ops
+DEFAULT FOR TYPE int8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_int8_consistent (internal, int8, int2, oid, internal),
+	FUNCTION	2	gbt_int8_union (internal, internal),
+	FUNCTION	3	gbt_int8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_int8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_int8_picksplit (internal, internal),
+	FUNCTION	7	gbt_int8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD
+	OPERATOR	6	<> (int8, int8) ,
+	OPERATOR	15	<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) ,
+	FUNCTION	9 (int8, int8) gbt_int8_fetch (internal) ;
+
+--
+--
+--
+-- float4 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float4_ops
+DEFAULT FOR TYPE float4 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float4_consistent (internal, float4, int2, oid, internal),
+	FUNCTION	2	gbt_float4_union (internal, internal),
+	FUNCTION	3	gbt_float4_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float4_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float4_picksplit (internal, internal),
+	FUNCTION	7	gbt_float4_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD
+	OPERATOR	6	<> (float4, float4) ,
+	OPERATOR	15	<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) ,
+	FUNCTION	9 (float4, float4) gbt_float4_fetch (internal) ;
+
+--
+--
+--
+-- float8 ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_float8_ops
+DEFAULT FOR TYPE float8 USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_float8_consistent (internal, float8, int2, oid, internal),
+	FUNCTION	2	gbt_float8_union (internal, internal),
+	FUNCTION	3	gbt_float8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_float8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_float8_picksplit (internal, internal),
+	FUNCTION	7	gbt_float8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD
+	OPERATOR	6	<> (float8, float8) ,
+	OPERATOR	15	<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops ,
+	FUNCTION	8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) ,
+	FUNCTION	9 (float8, float8) gbt_float8_fetch (internal) ;
+
+--
+--
+--
+-- timestamp ops
+--
+--
+--
+
+CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_tstz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamp_ops
+DEFAULT FOR TYPE timestamp USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_ts_consistent (internal, timestamp, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_ts_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
+	OPERATOR	6	<> (timestamp, timestamp) ,
+	OPERATOR	15	<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) ,
+	FUNCTION	9 (timestamp, timestamp) gbt_ts_fetch (internal) ;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_timestamptz_ops
+DEFAULT FOR TYPE timestamptz USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_tstz_consistent (internal, timestamptz, int2, oid, internal),
+	FUNCTION	2	gbt_ts_union (internal, internal),
+	FUNCTION	3	gbt_tstz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_ts_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_ts_picksplit (internal, internal),
+	FUNCTION	7	gbt_ts_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD
+	OPERATOR	6	<> (timestamptz, timestamptz) ,
+	OPERATOR	15	<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) ,
+	FUNCTION	9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ;
+
+--
+--
+--
+-- time ops
+--
+--
+--
+
+CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_timetz_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_time_ops
+DEFAULT FOR TYPE time USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_time_consistent (internal, time, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_time_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
+	OPERATOR	6	<> (time, time) ,
+	OPERATOR	15	<-> (time, time) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) ,
+	FUNCTION	9 (time, time) gbt_time_fetch (internal) ;
+
+
+CREATE OPERATOR CLASS gist_timetz_ops
+DEFAULT FOR TYPE timetz USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_timetz_consistent (internal, timetz, int2, oid, internal),
+	FUNCTION	2	gbt_time_union (internal, internal),
+	FUNCTION	3	gbt_timetz_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_time_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_time_picksplit (internal, internal),
+	FUNCTION	7	gbt_time_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD
+	OPERATOR	6	<> (timetz, timetz) ;
+	-- no 'fetch' function, as the compress function is lossy.
+
+
+--
+--
+--
+-- date ops
+--
+--
+--
+
+CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_date_ops
+DEFAULT FOR TYPE date USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_date_consistent (internal, date, int2, oid, internal),
+	FUNCTION	2	gbt_date_union (internal, internal),
+	FUNCTION	3	gbt_date_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_date_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_date_picksplit (internal, internal),
+	FUNCTION	7	gbt_date_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_date_ops USING gist ADD
+	OPERATOR	6	<> (date, date) ,
+	OPERATOR	15	<-> (date, date) FOR ORDER BY pg_catalog.integer_ops ,
+	FUNCTION	8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) ,
+	FUNCTION	9 (date, date) gbt_date_fetch (internal) ;
+
+
+--
+--
+--
+-- interval ops
+--
+--
+--
+
+CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_decompress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_interval_ops
+DEFAULT FOR TYPE interval USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_intv_consistent (internal, interval, int2, oid, internal),
+	FUNCTION	2	gbt_intv_union (internal, internal),
+	FUNCTION	3	gbt_intv_compress (internal),
+	FUNCTION	4	gbt_intv_decompress (internal),
+	FUNCTION	5	gbt_intv_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_intv_picksplit (internal, internal),
+	FUNCTION	7	gbt_intv_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD
+	OPERATOR	6	<> (interval, interval) ,
+	OPERATOR	15	<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops ,
+	FUNCTION	8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) ,
+	FUNCTION	9 (interval, interval) gbt_intv_fetch (internal) ;
+
+
+--
+--
+--
+-- cash ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal)
+RETURNS float8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cash_ops
+DEFAULT FOR TYPE money USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_cash_consistent (internal, money, int2, oid, internal),
+	FUNCTION	2	gbt_cash_union (internal, internal),
+	FUNCTION	3	gbt_cash_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_cash_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_cash_picksplit (internal, internal),
+	FUNCTION	7	gbt_cash_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD
+	OPERATOR	6	<> (money, money) ,
+	OPERATOR	15	<-> (money, money) FOR ORDER BY pg_catalog.money_ops ,
+	FUNCTION	8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) ,
+	FUNCTION	9 (money, money) gbt_cash_fetch (internal) ;
+
+
+--
+--
+--
+-- macaddr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_macaddr_ops
+DEFAULT FOR TYPE macaddr USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_macad_consistent (internal, macaddr, int2, oid, internal),
+	FUNCTION	2	gbt_macad_union (internal, internal),
+	FUNCTION	3	gbt_macad_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_macad_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_macad_picksplit (internal, internal),
+	FUNCTION	7	gbt_macad_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD
+	OPERATOR	6	<> (macaddr, macaddr) ,
+	FUNCTION	9 (macaddr, macaddr) gbt_macad_fetch (internal);
+
+
+--
+--
+--
+-- text/ bpchar ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bpchar_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_text_ops
+DEFAULT FOR TYPE text USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_text_consistent (internal, text, int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_text_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
+	OPERATOR	6	<> (text, text) ,
+	FUNCTION	9 (text, text) gbt_var_fetch (internal) ;
+
+
+---- Create the operator class
+CREATE OPERATOR CLASS gist_bpchar_ops
+DEFAULT FOR TYPE bpchar USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bpchar_consistent (internal, bpchar , int2, oid, internal),
+	FUNCTION	2	gbt_text_union (internal, internal),
+	FUNCTION	3	gbt_bpchar_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_text_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_text_picksplit (internal, internal),
+	FUNCTION	7	gbt_text_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD
+	OPERATOR	6	<> (bpchar, bpchar) ,
+	FUNCTION	9 (bpchar, bpchar) gbt_var_fetch (internal) ;
+
+--
+--
+-- bytea ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bytea_ops
+DEFAULT FOR TYPE bytea USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bytea_consistent (internal, bytea, int2, oid, internal),
+	FUNCTION	2	gbt_bytea_union (internal, internal),
+	FUNCTION	3	gbt_bytea_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bytea_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bytea_picksplit (internal, internal),
+	FUNCTION	7	gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD
+	OPERATOR	6	<> (bytea, bytea) ,
+	FUNCTION	9 (bytea, bytea) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- numeric ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_numeric_ops
+DEFAULT FOR TYPE numeric USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_numeric_consistent (internal, numeric, int2, oid, internal),
+	FUNCTION	2	gbt_numeric_union (internal, internal),
+	FUNCTION	3	gbt_numeric_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_numeric_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_numeric_picksplit (internal, internal),
+	FUNCTION	7	gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD
+	OPERATOR	6	<> (numeric, numeric) ,
+	FUNCTION	9 (numeric, numeric) gbt_var_fetch (internal) ;
+
+
+--
+--
+-- bit ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_union(internal, internal)
+RETURNS gbtreekey_var
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bit_ops
+DEFAULT FOR TYPE bit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
+	OPERATOR	6	<> (bit, bit) ,
+	FUNCTION	9 (bit, bit) gbt_var_fetch (internal) ;
+
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_vbit_ops
+DEFAULT FOR TYPE varbit USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_bit_consistent (internal, bit, int2, oid, internal),
+	FUNCTION	2	gbt_bit_union (internal, internal),
+	FUNCTION	3	gbt_bit_compress (internal),
+	FUNCTION	4	gbt_var_decompress (internal),
+	FUNCTION	5	gbt_bit_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_bit_picksplit (internal, internal),
+	FUNCTION	7	gbt_bit_same (gbtreekey_var, gbtreekey_var, internal),
+	STORAGE			gbtreekey_var;
+
+ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD
+	OPERATOR	6	<> (varbit, varbit) ,
+	FUNCTION	9 (varbit, varbit) gbt_var_fetch (internal) ;
+
+
+--
+--
+--
+-- inet/cidr ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_inet_ops
+DEFAULT FOR TYPE inet USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
+	OPERATOR	6	<>  (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_cidr_ops
+DEFAULT FOR TYPE cidr USING gist
+AS
+	OPERATOR	1	<  (inet, inet)  ,
+	OPERATOR	2	<= (inet, inet)  ,
+	OPERATOR	3	=  (inet, inet)  ,
+	OPERATOR	4	>= (inet, inet)  ,
+	OPERATOR	5	>  (inet, inet)  ,
+	FUNCTION	1	gbt_inet_consistent (internal, inet, int2, oid, internal),
+	FUNCTION	2	gbt_inet_union (internal, internal),
+	FUNCTION	3	gbt_inet_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_inet_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_inet_picksplit (internal, internal),
+	FUNCTION	7	gbt_inet_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD
+	OPERATOR	6	<> (inet, inet) ;
+	-- no fetch support, the compress function is lossy
+
+--
+--
+--
+-- uuid ops
+--
+--
+---- define the GiST support methods
+CREATE FUNCTION gbt_uuid_consistent(internal,uuid,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_union(internal, internal)
+RETURNS gbtreekey32
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_uuid_same(gbtreekey32, gbtreekey32, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_uuid_ops
+DEFAULT FOR TYPE uuid USING gist
+AS
+	OPERATOR	1	<   ,
+	OPERATOR	2	<=  ,
+	OPERATOR	3	=   ,
+	OPERATOR	4	>=  ,
+	OPERATOR	5	>   ,
+	FUNCTION	1	gbt_uuid_consistent (internal, uuid, int2, oid, internal),
+	FUNCTION	2	gbt_uuid_union (internal, internal),
+	FUNCTION	3	gbt_uuid_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_uuid_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_uuid_picksplit (internal, internal),
+	FUNCTION	7	gbt_uuid_same (gbtreekey32, gbtreekey32, internal),
+	STORAGE		gbtreekey32;
+
+-- These are "loose" in the opfamily for consistency with the rest of btree_gist
+ALTER OPERATOR FAMILY gist_uuid_ops USING gist ADD
+	OPERATOR	6	<>  (uuid, uuid) ,
+	FUNCTION	9 (uuid, uuid) gbt_uuid_fetch (internal) ;
+
+
+-- Add support for indexing macaddr8 columns
+
+-- define the GiST support methods
+CREATE FUNCTION gbt_macad8_consistent(internal,macaddr8,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_union(internal, internal)
+RETURNS gbtreekey16
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_macad8_same(gbtreekey16, gbtreekey16, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_macaddr8_ops
+DEFAULT FOR TYPE macaddr8 USING gist
+AS
+	OPERATOR	1	< ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	= ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	> ,
+	FUNCTION	1	gbt_macad8_consistent (internal, macaddr8, int2, oid, internal),
+	FUNCTION	2	gbt_macad8_union (internal, internal),
+	FUNCTION	3	gbt_macad8_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_macad8_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_macad8_picksplit (internal, internal),
+	FUNCTION	7	gbt_macad8_same (gbtreekey16, gbtreekey16, internal),
+	STORAGE		gbtreekey16;
+
+ALTER OPERATOR FAMILY gist_macaddr8_ops USING gist ADD
+	OPERATOR	6	<> (macaddr8, macaddr8) ,
+	FUNCTION	9 (macaddr8, macaddr8) gbt_macad8_fetch (internal);
+
+--
+--
+--
+-- enum ops
+--
+--
+--
+-- define the GiST support methods
+CREATE FUNCTION gbt_enum_consistent(internal,anyenum,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_enum_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_enum_ops
+DEFAULT FOR TYPE anyenum USING gist
+AS
+	OPERATOR	1	<  ,
+	OPERATOR	2	<= ,
+	OPERATOR	3	=  ,
+	OPERATOR	4	>= ,
+	OPERATOR	5	>  ,
+	FUNCTION	1	gbt_enum_consistent (internal, anyenum, int2, oid, internal),
+	FUNCTION	2	gbt_enum_union (internal, internal),
+	FUNCTION	3	gbt_enum_compress (internal),
+	FUNCTION	4	gbt_decompress (internal),
+	FUNCTION	5	gbt_enum_penalty (internal, internal, internal),
+	FUNCTION	6	gbt_enum_picksplit (internal, internal),
+	FUNCTION	7	gbt_enum_same (gbtreekey8, gbtreekey8, internal),
+	STORAGE		gbtreekey8;
+
+ALTER OPERATOR FAMILY gist_enum_ops USING gist ADD
+	OPERATOR	6	<> (anyenum, anyenum) ,
+	FUNCTION	9 (anyenum, anyenum) gbt_enum_fetch (internal) ;
diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control
index 81c8509..9ced3bc 100644
--- a/contrib/btree_gist/btree_gist.control
+++ b/contrib/btree_gist/btree_gist.control
@@ -1,5 +1,5 @@
 # btree_gist extension
 comment = 'support for indexing common datatypes in GiST'
-default_version = '1.5'
+default_version = '1.6'
 module_pathname = '$libdir/btree_gist'
 relocatable = true
diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c
index 7674e2d..2afc343 100644
--- a/contrib/btree_gist/btree_int2.c
+++ b/contrib/btree_gist/btree_int2.c
@@ -94,20 +94,7 @@ PG_FUNCTION_INFO_V1(int2_dist);
 Datum
 int2_dist(PG_FUNCTION_ARGS)
 {
-	int16		a = PG_GETARG_INT16(0);
-	int16		b = PG_GETARG_INT16(1);
-	int16		r;
-	int16		ra;
-
-	if (pg_sub_s16_overflow(a, b, &r) ||
-		r == PG_INT16_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("smallint out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT16(ra);
+	return int2dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c
index 80005ab..2361ce7 100644
--- a/contrib/btree_gist/btree_int4.c
+++ b/contrib/btree_gist/btree_int4.c
@@ -95,20 +95,7 @@ PG_FUNCTION_INFO_V1(int4_dist);
 Datum
 int4_dist(PG_FUNCTION_ARGS)
 {
-	int32		a = PG_GETARG_INT32(0);
-	int32		b = PG_GETARG_INT32(1);
-	int32		r;
-	int32		ra;
-
-	if (pg_sub_s32_overflow(a, b, &r) ||
-		r == PG_INT32_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("integer out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT32(ra);
+	return int4dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c
index b0fd3e1..182d7c4 100644
--- a/contrib/btree_gist/btree_int8.c
+++ b/contrib/btree_gist/btree_int8.c
@@ -95,20 +95,7 @@ PG_FUNCTION_INFO_V1(int8_dist);
 Datum
 int8_dist(PG_FUNCTION_ARGS)
 {
-	int64		a = PG_GETARG_INT64(0);
-	int64		b = PG_GETARG_INT64(1);
-	int64		r;
-	int64		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("bigint out of range")));
-
-	ra = Abs(r);
-
-	PG_RETURN_INT64(ra);
+	return int8dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_interval.c b/contrib/btree_gist/btree_interval.c
index 3a527a7..c68d48e 100644
--- a/contrib/btree_gist/btree_interval.c
+++ b/contrib/btree_gist/btree_interval.c
@@ -128,11 +128,7 @@ PG_FUNCTION_INFO_V1(interval_dist);
 Datum
 interval_dist(PG_FUNCTION_ARGS)
 {
-	Datum		diff = DirectFunctionCall2(interval_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+	return interval_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_oid.c b/contrib/btree_gist/btree_oid.c
index 00e7019..c702d51 100644
--- a/contrib/btree_gist/btree_oid.c
+++ b/contrib/btree_gist/btree_oid.c
@@ -100,15 +100,7 @@ PG_FUNCTION_INFO_V1(oid_dist);
 Datum
 oid_dist(PG_FUNCTION_ARGS)
 {
-	Oid			a = PG_GETARG_OID(0);
-	Oid			b = PG_GETARG_OID(1);
-	Oid			res;
-
-	if (a < b)
-		res = b - a;
-	else
-		res = a - b;
-	PG_RETURN_OID(res);
+	return oiddist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_time.c b/contrib/btree_gist/btree_time.c
index 90cf655..cfafd02 100644
--- a/contrib/btree_gist/btree_time.c
+++ b/contrib/btree_gist/btree_time.c
@@ -141,11 +141,7 @@ PG_FUNCTION_INFO_V1(time_dist);
 Datum
 time_dist(PG_FUNCTION_ARGS)
 {
-	Datum		diff = DirectFunctionCall2(time_mi_time,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+	return time_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c
index 49d1849..9e62f7d 100644
--- a/contrib/btree_gist/btree_ts.c
+++ b/contrib/btree_gist/btree_ts.c
@@ -146,48 +146,14 @@ PG_FUNCTION_INFO_V1(ts_dist);
 Datum
 ts_dist(PG_FUNCTION_ARGS)
 {
-	Timestamp	a = PG_GETARG_TIMESTAMP(0);
-	Timestamp	b = PG_GETARG_TIMESTAMP(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-	else
-		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-												  PG_GETARG_DATUM(0),
-												  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
+	return timestamp_distance(fcinfo);
 }
 
 PG_FUNCTION_INFO_V1(tstz_dist);
 Datum
 tstz_dist(PG_FUNCTION_ARGS)
 {
-	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
-	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-
-	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-											  PG_GETARG_DATUM(0),
-											  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
+	return timestamptz_distance(fcinfo);
 }
 
 
diff --git a/doc/src/sgml/btree-gist.sgml b/doc/src/sgml/btree-gist.sgml
index 774442f..6eb4a18 100644
--- a/doc/src/sgml/btree-gist.sgml
+++ b/doc/src/sgml/btree-gist.sgml
@@ -96,6 +96,19 @@ INSERT 0 1
  </sect2>
 
  <sect2>
+  <title>Upgrade notes for version 1.6</title>
+
+  <para>
+   In version 1.6 <literal>btree_gist</literal> switched to using in-core
+   distance operators, and its own implementations were removed.  References to
+   these operators in <literal>btree_gist</literal> opclasses will be updated
+   automatically during the extension upgrade, but if the user has created
+   objects referencing these operators or functions, then these objects must be
+   dropped manually before updating the extension.
+  </para>
+ </sect2>
+
+ <sect2>
   <title>Authors</title>
 
   <para>
0007-Add-regression-tests-for-kNN-btree-v07.patchtext/x-patch; name=0007-Add-regression-tests-for-kNN-btree-v07.patchDownload
diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index b21298a..0cefbcc 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -250,3 +250,1109 @@ select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass;
  {vacuum_cleanup_index_scale_factor=70.0}
 (1 row)
 
+---
+--- Test B-tree distance ordering
+---
+SET enable_bitmapscan = OFF;
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+          QUERY PLAN          
+------------------------------
+ Sort
+   Sort Key: ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((seqno <-> 0))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+                   QUERY PLAN                   
+------------------------------------------------
+ Sort
+   Sort Key: ((random <-> 0)), ((random <-> 1))
+   ->  Seq Scan on bt_i4_heap
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)))
+   Order By: (random <-> 4000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+                                                                                               QUERY PLAN                                                                                               
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)) AND (random = ANY ('{1809552,1919087,2321799,2648497,3000193,3013326,4157193,4488889,5257716,5593978,NULL}'::integer[])))
+   Order By: (random <-> 3000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+ seqno | random  
+-------+---------
+  5380 | 3000193
+  6262 | 3013326
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+  6448 | 4157193
+   210 | 1809552
+  4408 | 4488889
+  6320 | 5257716
+  1836 | 5593978
+(10 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+-- test parallel KNN scan
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+RESET enable_indexscan;
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, 1000000) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+EXPLAIN (COSTS OFF)
+SELECT i FROM bt_knn_test WHERE i > 8000000;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Gather
+   Workers Planned: 4
+   ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+         Index Cond: (i > 8000000)
+(4 rows)
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> 4000003) AS n, i * 10 AS i
+	FROM generate_series(1, 1000000) i;
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (t1.n = t2.n)
+   Join Filter: (t1.i <> t2.i)
+   ->  Subquery Scan on t1
+         ->  WindowAgg
+               ->  Gather Merge
+                     Workers Planned: 4
+                     ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                           Order By: (i <-> 4000003)
+   ->  Hash
+         ->  Gather
+               Workers Planned: 4
+               ->  Parallel Seq Scan on bt_knn_test2 t2
+(13 rows)
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+DROP TABLE bt_knn_test;
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, 100000) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (t1.n = ((i.i * 100000) + j.j))
+   Join Filter: (t1.i <> ('{4,3,2,7,8}'::integer[])[(i.i + 1)])
+   ->  Subquery Scan on t1
+         ->  WindowAgg
+               ->  Gather Merge
+                     Workers Planned: 4
+                     ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                           Index Cond: (i = ANY ('{3,4,7,8,2}'::integer[]))
+                           Order By: (i <-> 4)
+   ->  Hash
+         ->  Nested Loop
+               ->  Function Scan on generate_series i
+               ->  Function Scan on generate_series j
+(14 rows)
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+ROLLBACK;
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: (ROW(thousand, tenthous) >= ROW(997, 5000))
+   Order By: (thousand <-> 998)
+(3 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+        1 |     9001
+        1 |     8001
+        1 |     7001
+        1 |     6001
+        1 |     5001
+        1 |     4001
+        1 |     3001
+        1 |     2001
+        1 |     1001
+        1 |        1
+        0 |     9000
+        0 |     8000
+        0 |     7000
+        0 |     6000
+        0 |     5000
+        0 |     4000
+        0 |     3000
+        0 |     2000
+        0 |     1000
+        0 |        0
+          |        1
+          |        2
+          |        3
+(33 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: ((thousand > 100) AND (thousand < 800) AND (thousand = ANY ('{0,123,234,345,456,678,901,NULL}'::smallint[])))
+   Order By: (thousand <-> '300'::bigint)
+(3 rows)
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+ thousand | tenthous 
+----------+----------
+      345 |      345
+      345 |     1345
+      345 |     2345
+      345 |     3345
+      345 |     4345
+      345 |     5345
+      345 |     6345
+      345 |     7345
+      345 |     8345
+      345 |     9345
+      234 |      234
+      234 |     1234
+      234 |     2234
+      234 |     3234
+      234 |     4234
+      234 |     5234
+      234 |     6234
+      234 |     7234
+      234 |     8234
+      234 |     9234
+      456 |      456
+      456 |     1456
+      456 |     2456
+      456 |     3456
+      456 |     4456
+      456 |     5456
+      456 |     6456
+      456 |     7456
+      456 |     8456
+      456 |     9456
+      123 |      123
+      123 |     1123
+      123 |     2123
+      123 |     3123
+      123 |     4123
+      123 |     5123
+      123 |     6123
+      123 |     7123
+      123 |     8123
+      123 |     9123
+      678 |      678
+      678 |     1678
+      678 |     2678
+      678 |     3678
+      678 |     4678
+      678 |     5678
+      678 |     6678
+      678 |     7678
+      678 |     8678
+      678 |     9678
+(50 rows)
+
+DROP INDEX tenk3_idx;
+-- Test order by distance ordering on non-first column
+SET enable_sort = OFF;
+-- Ranges are not supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand > 120
+ORDER BY tenthous <-> 3500;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Sort
+   Sort Key: ((tenthous <-> 3500))
+   ->  Index Only Scan using tenk1_thous_tenthous on tenk1
+         Index Cond: (thousand > 120)
+(4 rows)
+
+-- Equality restriction on the first column is supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand = 120
+ORDER BY tenthous <-> 3500;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Index Only Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: (thousand = 120)
+   Order By: (tenthous <-> 3500)
+(3 rows)
+
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand = 120
+ORDER BY tenthous <-> 3500;
+ thousand | tenthous 
+----------+----------
+      120 |     3120
+      120 |     4120
+      120 |     2120
+      120 |     5120
+      120 |     1120
+      120 |     6120
+      120 |      120
+      120 |     7120
+      120 |     8120
+      120 |     9120
+(10 rows)
+
+-- IN restriction on the first column is not supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY tenthous <-> 3500;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Sort
+   Sort Key: ((tenthous <-> 3500))
+   ->  Index Only Scan using tenk1_thous_tenthous on tenk1
+         Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
+(4 rows)
+
+-- Test kNN search using 4-column index
+CREATE INDEX tenk1_knn_idx ON tenk1(ten, hundred, thousand, tenthous);
+-- Ordering by distance to 3rd column
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY thousand <-> 600;
+                  QUERY PLAN                  
+----------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = 3) AND (hundred = 43))
+   Order By: (thousand <-> 600)
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY thousand <-> 600;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      43 |      643 |      643
+   3 |      43 |      643 |     1643
+   3 |      43 |      643 |     2643
+   3 |      43 |      643 |     3643
+   3 |      43 |      643 |     4643
+   3 |      43 |      643 |     5643
+   3 |      43 |      643 |     6643
+   3 |      43 |      643 |     7643
+   3 |      43 |      643 |     8643
+   3 |      43 |      643 |     9643
+   3 |      43 |      543 |     9543
+   3 |      43 |      543 |     8543
+   3 |      43 |      543 |     7543
+   3 |      43 |      543 |     6543
+   3 |      43 |      543 |     5543
+   3 |      43 |      543 |     4543
+   3 |      43 |      543 |     3543
+   3 |      43 |      543 |     2543
+   3 |      43 |      543 |     1543
+   3 |      43 |      543 |      543
+   3 |      43 |      743 |      743
+   3 |      43 |      743 |     1743
+   3 |      43 |      743 |     2743
+   3 |      43 |      743 |     3743
+   3 |      43 |      743 |     4743
+   3 |      43 |      743 |     5743
+   3 |      43 |      743 |     6743
+   3 |      43 |      743 |     7743
+   3 |      43 |      743 |     8743
+   3 |      43 |      743 |     9743
+   3 |      43 |      443 |     9443
+   3 |      43 |      443 |     8443
+   3 |      43 |      443 |     7443
+   3 |      43 |      443 |     6443
+   3 |      43 |      443 |     5443
+   3 |      43 |      443 |     4443
+   3 |      43 |      443 |     3443
+   3 |      43 |      443 |     2443
+   3 |      43 |      443 |     1443
+   3 |      43 |      443 |      443
+   3 |      43 |      843 |      843
+   3 |      43 |      843 |     1843
+   3 |      43 |      843 |     2843
+   3 |      43 |      843 |     3843
+   3 |      43 |      843 |     4843
+   3 |      43 |      843 |     5843
+   3 |      43 |      843 |     6843
+   3 |      43 |      843 |     7843
+   3 |      43 |      843 |     8843
+   3 |      43 |      843 |     9843
+   3 |      43 |      343 |     9343
+   3 |      43 |      343 |     8343
+   3 |      43 |      343 |     7343
+   3 |      43 |      343 |     6343
+   3 |      43 |      343 |     5343
+   3 |      43 |      343 |     4343
+   3 |      43 |      343 |     3343
+   3 |      43 |      343 |     2343
+   3 |      43 |      343 |     1343
+   3 |      43 |      343 |      343
+   3 |      43 |      943 |      943
+   3 |      43 |      943 |     1943
+   3 |      43 |      943 |     2943
+   3 |      43 |      943 |     3943
+   3 |      43 |      943 |     4943
+   3 |      43 |      943 |     5943
+   3 |      43 |      943 |     6943
+   3 |      43 |      943 |     7943
+   3 |      43 |      943 |     8943
+   3 |      43 |      943 |     9943
+   3 |      43 |      243 |     9243
+   3 |      43 |      243 |     8243
+   3 |      43 |      243 |     7243
+   3 |      43 |      243 |     6243
+   3 |      43 |      243 |     5243
+   3 |      43 |      243 |     4243
+   3 |      43 |      243 |     3243
+   3 |      43 |      243 |     2243
+   3 |      43 |      243 |     1243
+   3 |      43 |      243 |      243
+   3 |      43 |      143 |     9143
+   3 |      43 |      143 |     8143
+   3 |      43 |      143 |     7143
+   3 |      43 |      143 |     6143
+   3 |      43 |      143 |     5143
+   3 |      43 |      143 |     4143
+   3 |      43 |      143 |     3143
+   3 |      43 |      143 |     2143
+   3 |      43 |      143 |     1143
+   3 |      43 |      143 |      143
+   3 |      43 |       43 |     9043
+   3 |      43 |       43 |     8043
+   3 |      43 |       43 |     7043
+   3 |      43 |       43 |     6043
+   3 |      43 |       43 |     5043
+   3 |      43 |       43 |     4043
+   3 |      43 |       43 |     3043
+   3 |      43 |       43 |     2043
+   3 |      43 |       43 |     1043
+   3 |      43 |       43 |       43
+(100 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND tenthous > 3000 ORDER BY thousand <-> 600;
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = 3) AND (hundred = 43) AND (tenthous > 3000))
+   Order By: (thousand <-> 600)
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND tenthous > 3000 ORDER BY thousand <-> 600;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      43 |      643 |     3643
+   3 |      43 |      643 |     4643
+   3 |      43 |      643 |     5643
+   3 |      43 |      643 |     6643
+   3 |      43 |      643 |     7643
+   3 |      43 |      643 |     8643
+   3 |      43 |      643 |     9643
+   3 |      43 |      543 |     9543
+   3 |      43 |      543 |     8543
+   3 |      43 |      543 |     7543
+   3 |      43 |      543 |     6543
+   3 |      43 |      543 |     5543
+   3 |      43 |      543 |     4543
+   3 |      43 |      543 |     3543
+   3 |      43 |      743 |     3743
+   3 |      43 |      743 |     4743
+   3 |      43 |      743 |     5743
+   3 |      43 |      743 |     6743
+   3 |      43 |      743 |     7743
+   3 |      43 |      743 |     8743
+   3 |      43 |      743 |     9743
+   3 |      43 |      443 |     9443
+   3 |      43 |      443 |     8443
+   3 |      43 |      443 |     7443
+   3 |      43 |      443 |     6443
+   3 |      43 |      443 |     5443
+   3 |      43 |      443 |     4443
+   3 |      43 |      443 |     3443
+   3 |      43 |      843 |     3843
+   3 |      43 |      843 |     4843
+   3 |      43 |      843 |     5843
+   3 |      43 |      843 |     6843
+   3 |      43 |      843 |     7843
+   3 |      43 |      843 |     8843
+   3 |      43 |      843 |     9843
+   3 |      43 |      343 |     9343
+   3 |      43 |      343 |     8343
+   3 |      43 |      343 |     7343
+   3 |      43 |      343 |     6343
+   3 |      43 |      343 |     5343
+   3 |      43 |      343 |     4343
+   3 |      43 |      343 |     3343
+   3 |      43 |      943 |     3943
+   3 |      43 |      943 |     4943
+   3 |      43 |      943 |     5943
+   3 |      43 |      943 |     6943
+   3 |      43 |      943 |     7943
+   3 |      43 |      943 |     8943
+   3 |      43 |      943 |     9943
+   3 |      43 |      243 |     9243
+   3 |      43 |      243 |     8243
+   3 |      43 |      243 |     7243
+   3 |      43 |      243 |     6243
+   3 |      43 |      243 |     5243
+   3 |      43 |      243 |     4243
+   3 |      43 |      243 |     3243
+   3 |      43 |      143 |     9143
+   3 |      43 |      143 |     8143
+   3 |      43 |      143 |     7143
+   3 |      43 |      143 |     6143
+   3 |      43 |      143 |     5143
+   3 |      43 |      143 |     4143
+   3 |      43 |      143 |     3143
+   3 |      43 |       43 |     9043
+   3 |      43 |       43 |     8043
+   3 |      43 |       43 |     7043
+   3 |      43 |       43 |     6043
+   3 |      43 |       43 |     5043
+   3 |      43 |       43 |     4043
+   3 |      43 |       43 |     3043
+(70 rows)
+
+-- Ordering by distance to 4th column
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = 3) AND (hundred = 43) AND (thousand = 643))
+   Order By: (tenthous <-> 4000)
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      43 |      643 |     3643
+   3 |      43 |      643 |     4643
+   3 |      43 |      643 |     2643
+   3 |      43 |      643 |     5643
+   3 |      43 |      643 |     1643
+   3 |      43 |      643 |     6643
+   3 |      43 |      643 |      643
+   3 |      43 |      643 |     7643
+   3 |      43 |      643 |     8643
+   3 |      43 |      643 |     9643
+(10 rows)
+
+-- Not supported by tenk1_knn_idx (not all previous columns are eq-restricted)
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: (thousand = 643)
+   Order By: (tenthous <-> 4000)
+   Filter: (hundred = 43)
+(4 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY tenthous <-> 4000;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Sort
+   Sort Key: ((tenthous <-> 4000))
+   ->  Index Only Scan using tenk1_knn_idx on tenk1
+         Index Cond: ((ten = 3) AND (hundred = 43))
+(4 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND thousand = 643 ORDER BY tenthous <-> 4000;
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: (thousand = 643)
+   Order By: (tenthous <-> 4000)
+   Filter: (ten = 3)
+(4 rows)
+
+DROP INDEX tenk1_knn_idx;
+RESET enable_sort;
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+        1 |        1
+        1 |     1001
+        1 |     2001
+        1 |     3001
+        1 |     4001
+        1 |     5001
+        1 |     6001
+        1 |     7001
+        1 |     8001
+        1 |     9001
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+        0 |        0
+        0 |     1000
+        0 |     2000
+        0 |     3000
+        0 |     4000
+        0 |     5000
+        0 |     6000
+        0 |     7000
+        0 |     8000
+        0 |     9000
+          |        3
+          |        2
+          |        1
+(33 rows)
+
+DROP INDEX tenk3_idx;
+DROP TABLE tenk3;
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |     ?column?      
+--------------------------+-------------------
+ Wed May 03 00:00:00 2017 | @ 2 days
+ Wed May 03 01:00:00 2017 | @ 2 days 1 hour
+ Wed May 03 02:00:00 2017 | @ 2 days 2 hours
+ Wed May 03 03:00:00 2017 | @ 2 days 3 hours
+ Wed May 03 04:00:00 2017 | @ 2 days 4 hours
+ Wed May 03 05:00:00 2017 | @ 2 days 5 hours
+ Wed May 03 06:00:00 2017 | @ 2 days 6 hours
+ Wed May 03 07:00:00 2017 | @ 2 days 7 hours
+ Wed May 03 08:00:00 2017 | @ 2 days 8 hours
+ Wed May 03 09:00:00 2017 | @ 2 days 9 hours
+ Wed May 03 10:00:00 2017 | @ 2 days 10 hours
+ Wed May 03 11:00:00 2017 | @ 2 days 11 hours
+ Wed May 03 12:00:00 2017 | @ 2 days 12 hours
+ Wed May 03 13:00:00 2017 | @ 2 days 13 hours
+ Wed May 03 14:00:00 2017 | @ 2 days 14 hours
+ Wed May 03 15:00:00 2017 | @ 2 days 15 hours
+ Wed May 03 16:00:00 2017 | @ 2 days 16 hours
+ Wed May 03 17:00:00 2017 | @ 2 days 17 hours
+ Wed May 03 18:00:00 2017 | @ 2 days 18 hours
+ Wed May 03 19:00:00 2017 | @ 2 days 19 hours
+(20 rows)
+
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |  ?column?  
+--------------------------+------------
+ Mon Jan 01 00:00:00 2018 | @ 0
+ Mon Jan 01 01:00:00 2018 | @ 1 hour
+ Sun Dec 31 23:00:00 2017 | @ 1 hour
+ Mon Jan 01 02:00:00 2018 | @ 2 hours
+ Sun Dec 31 22:00:00 2017 | @ 2 hours
+ Mon Jan 01 03:00:00 2018 | @ 3 hours
+ Sun Dec 31 21:00:00 2017 | @ 3 hours
+ Mon Jan 01 04:00:00 2018 | @ 4 hours
+ Sun Dec 31 20:00:00 2017 | @ 4 hours
+ Mon Jan 01 05:00:00 2018 | @ 5 hours
+ Sun Dec 31 19:00:00 2017 | @ 5 hours
+ Mon Jan 01 06:00:00 2018 | @ 6 hours
+ Sun Dec 31 18:00:00 2017 | @ 6 hours
+ Mon Jan 01 07:00:00 2018 | @ 7 hours
+ Sun Dec 31 17:00:00 2017 | @ 7 hours
+ Mon Jan 01 08:00:00 2018 | @ 8 hours
+ Sun Dec 31 16:00:00 2017 | @ 8 hours
+ Mon Jan 01 09:00:00 2018 | @ 9 hours
+ Sun Dec 31 15:00:00 2017 | @ 9 hours
+ Mon Jan 01 10:00:00 2018 | @ 10 hours
+(20 rows)
+
+DROP TABLE knn_btree_ts;
+RESET enable_bitmapscan;
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 2b087be..5e3bffe 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -129,3 +129,307 @@ create index btree_idx_err on btree_test(a) with (vacuum_cleanup_index_scale_fac
 -- Simple ALTER INDEX
 alter index btree_idx1 set (vacuum_cleanup_index_scale_factor = 70.0);
 select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass;
+
+---
+--- Test B-tree distance ordering
+---
+
+SET enable_bitmapscan = OFF;
+
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+-- test parallel KNN scan
+
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+
+RESET enable_indexscan;
+
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, 1000000) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+EXPLAIN (COSTS OFF)
+SELECT i FROM bt_knn_test WHERE i > 8000000;
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> 4000003) AS n, i * 10 AS i
+	FROM generate_series(1, 1000000) i;
+
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	ORDER BY i <-> 4000003
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+DROP TABLE bt_knn_test;
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, 100000) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * 100000 + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, 100000) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+
+ROLLBACK;
+
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+
+
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+DROP INDEX tenk3_idx;
+
+-- Test order by distance ordering on non-first column
+SET enable_sort = OFF;
+
+-- Ranges are not supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand > 120
+ORDER BY tenthous <-> 3500;
+
+-- Equality restriction on the first column is supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand = 120
+ORDER BY tenthous <-> 3500;
+
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand = 120
+ORDER BY tenthous <-> 3500;
+
+-- IN restriction on the first column is not supported
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous
+FROM tenk1
+WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY tenthous <-> 3500;
+
+-- Test kNN search using 4-column index
+CREATE INDEX tenk1_knn_idx ON tenk1(ten, hundred, thousand, tenthous);
+
+-- Ordering by distance to 3rd column
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY thousand <-> 600;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY thousand <-> 600;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND tenthous > 3000 ORDER BY thousand <-> 600;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND tenthous > 3000 ORDER BY thousand <-> 600;
+
+-- Ordering by distance to 4th column
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+
+-- Not supported by tenk1_knn_idx (not all previous columns are eq-restricted)
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND hundred = 43 ORDER BY tenthous <-> 4000;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten = 3 AND thousand = 643 ORDER BY tenthous <-> 4000;
+
+DROP INDEX tenk1_knn_idx;
+
+RESET enable_sort;
+
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+DROP INDEX tenk3_idx;
+
+DROP TABLE tenk3;
+
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+
+DROP TABLE knn_btree_ts;
+
+RESET enable_bitmapscan;
0008-Allow-ammatchorderby-to-return-pathkey-sublists-v07.patchtext/x-patch; name=0008-Allow-ammatchorderby-to-return-pathkey-sublists-v07.patchDownload
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 279d8ba..b88044b 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1014,8 +1014,25 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 								index_clauses,
 								&orderbyclauses,
 								&orderbyclausecols);
+
 		if (orderbyclauses)
-			useful_pathkeys = root->query_pathkeys;
+		{
+			int			norderbys = list_length(orderbyclauses);
+			int			npathkeys = list_length(root->query_pathkeys);
+
+			if (norderbys < npathkeys)
+			{
+				/*
+				 * We do not accept pathkey sublists until we implement
+				 * partial sorting.
+				 */
+				useful_pathkeys = NIL;
+				orderbyclauses = NIL;
+				orderbyclausecols = NIL;
+			}
+			else
+				useful_pathkeys = root->query_pathkeys;
+		}
 		else
 			useful_pathkeys = NIL;
 	}
@@ -3286,8 +3303,7 @@ expand_indexqual_rowcompare(RestrictInfo *rinfo,
  * index column numbers (zero based) that each clause would be used with.
  * NIL lists are returned if the ordering is not achievable this way.
  *
- * On success, the result list is ordered by pathkeys, and in fact is
- * one-to-one with the requested pathkeys.
+ * On success, the result list is ordered by pathkeys.
  */
 static void
 match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
@@ -3305,8 +3321,8 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 		ammatchorderby(index, pathkeys, index_clauses,
 					   &orderby_clauses, &orderby_clause_columns))
 	{
-		Assert(list_length(pathkeys) == list_length(orderby_clauses));
-		Assert(list_length(pathkeys) == list_length(orderby_clause_columns));
+		Assert(list_length(orderby_clauses) <= list_length(pathkeys));
+		Assert(list_length(orderby_clauses) == list_length(orderby_clause_columns));
 
 		*orderby_clauses_p = orderby_clauses;	/* success! */
 		*orderby_clause_columns_p = orderby_clause_columns;
0009-Add-support-of-array-ops-to-btree-kNN-v07.patchtext/x-patch; name=0009-Add-support-of-array-ops-to-btree-kNN-v07.patchDownload
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index fb413e7..3085fae 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -509,8 +509,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 
 	if (orderbys && scan->numberOfOrderBys > 0)
 		memmove(scan->orderByData,
-				orderbys,
-				scan->numberOfOrderBys * sizeof(ScanKeyData));
+				&orderbys[scan->numberOfOrderBys - 1],
+				1 /* scan->numberOfOrderBys */ * sizeof(ScanKeyData));
 
 	so->scanDirection = NoMovementScanDirection;
 	so->distanceTypeByVal = true;
@@ -1554,13 +1554,15 @@ static bool
 btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 			   List **orderby_clauses_p, List **orderby_clausecols_p)
 {
-	Expr	   *expr;
+	Expr	   *expr = NULL;
 	ListCell   *lc;
 	int			indexcol;
 	int			num_eq_cols = 0;
+	int			nsaops = 0;
+	int			last_saop_ord_col = -1;
+	bool		saops[INDEX_MAX_KEYS] = {0};
 
-	/* only one ORDER BY clause is supported */
-	if (list_length(pathkeys) != 1)
+	if (list_length(pathkeys) < 1)
 		return false;
 
 	/*
@@ -1584,6 +1586,7 @@ btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 			Expr	   *clause = rinfo->clause;
 			Oid			opno;
 			StrategyNumber strat;
+			bool		is_saop;
 
 			if (!clause)
 				continue;
@@ -1593,6 +1596,18 @@ btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 				OpExpr	   *opexpr = (OpExpr *) clause;
 
 				opno = opexpr->opno;
+				is_saop = false;
+			}
+			else if (IsA(clause, ScalarArrayOpExpr))
+			{
+				ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+
+				/* We only accept ANY clauses, not ALL */
+				if (!saop->useOr)
+					continue;
+
+				opno = saop->opno;
+				is_saop = true;
 			}
 			else
 			{
@@ -1603,19 +1618,67 @@ btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 			/* Check if the operator is btree equality operator. */
 			strat = get_op_opfamily_strategy(opno, index->opfamily[indexcol]);
 
-			if (strat == BTEqualStrategyNumber)
-				num_eq_cols = indexcol + 1;
+			if (strat != BTEqualStrategyNumber)
+				continue;
+
+			if (is_saop && indexcol == num_eq_cols)
+			{
+				saops[indexcol] = true;
+				nsaops++;
+			}
+			else if (!is_saop && saops[indexcol])
+			{
+				saops[indexcol] = false;
+				nsaops--;
+			}
+
+			num_eq_cols = indexcol + 1;
 		}
 	}
 
-	/*
-	 * If there are no equality columns try to match only the first column,
-	 * otherwise try all columns.
-	 */
-	indexcol = num_eq_cols ? -1 : 0;
+	foreach(lc, pathkeys)
+	{
+		PathKey    *pathkey = lfirst_node(PathKey, lc);
+
+		/*
+		 * If there are no equality columns try to match only the first column,
+		 * otherwise try all columns.
+		 */
+		indexcol = num_eq_cols ? -1 : 0;
+
+		if ((expr = match_orderbyop_pathkey(index, pathkey, &indexcol)))
+			break;	/* found order-by-operator pathkey */
+
+		if (!num_eq_cols)
+			return false;	/* first pathkey is not order-by-operator */
 
-	expr = match_orderbyop_pathkey(index, castNode(PathKey, linitial(pathkeys)),
-								   &indexcol);
+		indexcol = -1;
+
+		if (!(expr = match_pathkey_to_indexcol(index, pathkey, &indexcol)))
+			return false;
+
+		if (indexcol >= num_eq_cols)
+			return false;
+
+		if (saops[indexcol])
+		{
+			saops[indexcol] = false;
+			nsaops--;
+
+			/*
+			 * ORDER BY column numbers for array ops should go in
+			 * non-decreasing order.
+			 */
+			if (indexcol < last_saop_ord_col)
+				return false;
+
+			last_saop_ord_col = indexcol;
+		}
+		/* else: order of equality-restricted columns is arbitrary */
+
+		*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
+		*orderby_clausecols_p = lappend_int(*orderby_clausecols_p, -indexcol - 1);
+	}
 
 	if (!expr)
 		return false;
@@ -1627,6 +1690,19 @@ btmatchorderby(IndexOptInfo *index, List *pathkeys, List *index_clauses,
 	if (indexcol > num_eq_cols)
 		return false;
 
+	if (nsaops)
+	{
+		int			i;
+
+		/*
+		 * Check that all preceding array-op columns are included into
+		 * ORDER BY clause.
+		 */
+		for (i = 0; i < indexcol; i++)
+			if (saops[i])
+				return false;
+	}
+
 	/* Return first ORDER BY clause's expression and column. */
 	*orderby_clauses_p = lappend(*orderby_clauses_p, expr);
 	*orderby_clausecols_p = lappend_int(*orderby_clausecols_p, indexcol);
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 5b9294b..e980351 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -905,10 +905,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	{
 		ScanKey		ord = scan->orderByData;
 
-		if (scan->numberOfOrderBys > 1)
-			/* it should not happen, see btmatchorderby() */
-			elog(ERROR, "only one btree ordering operator is supported");
-
 		Assert(ord->sk_strategy == BtreeKNNSearchStrategyNumber);
 
 		/* use bidirectional kNN scan by default */
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 805abd2..034e3b8 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -1644,6 +1644,27 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
 								   InvalidOid,	/* no reg proc for this */
 								   (Datum) 0);	/* constant */
 		}
+		else if (IsA(clause, Var))
+		{
+			/* indexkey IS NULL or indexkey IS NOT NULL */
+			Var		   *var = (Var *) clause;
+
+			Assert(isorderby);
+
+			if (var->varno != INDEX_VAR)
+				elog(ERROR, "Var indexqual has wrong key");
+
+			varattno = var->varattno;
+
+			ScanKeyEntryInitialize(this_scan_key,
+								   SK_ORDER_BY | SK_SEARCHNOTNULL,
+								   varattno,	/* attribute number to scan */
+								   InvalidStrategy, /* no strategy */
+								   InvalidOid,	/* no strategy subtype */
+								   var->varcollid,	/* collation FIXME */
+								   InvalidOid,	/* no reg proc for this */
+								   (Datum) 0);	/* constant */
+		}
 		else
 			elog(ERROR, "unsupported indexqual type: %d",
 				 (int) nodeTag(clause));
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index b88044b..a1a36ad 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -2945,6 +2945,62 @@ match_rowcompare_to_indexcol(RestrictInfo *rinfo,
 	return NULL;
 }
 
+/*
+ * Try to match pathkey to the specified index column (*indexcol >= 0) or
+ * to all index columns (*indexcol < 0).
+ */
+Expr *
+match_pathkey_to_indexcol(IndexOptInfo *index, PathKey *pathkey, int *indexcol)
+{
+	ListCell   *lc;
+
+	/* Pathkey must request default sort order for the target opfamily */
+	if (pathkey->pk_strategy != BTLessStrategyNumber ||
+		pathkey->pk_nulls_first)
+		return NULL;
+
+	/* If eclass is volatile, no hope of using an indexscan */
+	if (pathkey->pk_eclass->ec_has_volatile)
+		return NULL;
+
+	/*
+	 * Try to match eclass member expression(s) to index.  Note that child
+	 * EC members are considered, but only when they belong to the target
+	 * relation.  (Unlike regular members, the same expression could be a
+	 * child member of more than one EC.  Therefore, the same index could
+	 * be considered to match more than one pathkey list, which is OK
+	 * here.  See also get_eclass_for_sort_expr.)
+	 */
+	foreach(lc, pathkey->pk_eclass->ec_members)
+	{
+		EquivalenceMember *member = lfirst_node(EquivalenceMember, lc);
+		Expr	   *expr = member->em_expr;
+
+		/* No possibility of match if it references other relations */
+		if (!bms_equal(member->em_relids, index->rel->relids))
+			continue;
+
+		/* If *indexcol is non-negative then try to match only to it */
+		if (*indexcol >= 0)
+		{
+			if (match_index_to_operand((Node *) expr, *indexcol, index))
+				/* don't want to look at remaining members */
+				return expr;
+		}
+		else	/* try to match all columns */
+		{
+			for (*indexcol = 0; *indexcol < index->nkeycolumns; ++*indexcol)
+			{
+				if (match_index_to_operand((Node *) expr, *indexcol, index))
+					/* don't want to look at remaining members */
+					return expr;
+			}
+		}
+	}
+
+	return NULL;
+}
+
 /****************************************************************************
  *				----  ROUTINES TO CHECK ORDERING OPERATORS	----
  ****************************************************************************/
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 236f506..307af39 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4555,7 +4555,11 @@ fix_indexqual_clause(PlannerInfo *root, IndexOptInfo *index, int indexcol,
 	 */
 	clause = replace_nestloop_params(root, clause);
 
-	if (IsA(clause, OpExpr))
+	if (indexcol < 0)
+	{
+		clause = fix_indexqual_operand(clause, index, -indexcol - 1);
+	}
+	else if (IsA(clause, OpExpr))
 	{
 		OpExpr	   *op = (OpExpr *) clause;
 
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 2e3fab6..ab52b93 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5284,10 +5284,12 @@ get_quals_from_indexclauses(List *indexclauses)
  * index key expression is on the left side of binary clauses.
  */
 Cost
-index_other_operands_eval_cost(PlannerInfo *root, List *indexquals)
+index_other_operands_eval_cost(PlannerInfo *root, List *indexquals,
+							   List *indexcolnos)
 {
 	Cost		qual_arg_cost = 0;
 	ListCell   *lc;
+	ListCell   *indexcolno_lc = indexcolnos ? list_head(indexcolnos) : NULL;
 
 	foreach(lc, indexquals)
 	{
@@ -5302,6 +5304,19 @@ index_other_operands_eval_cost(PlannerInfo *root, List *indexquals)
 		if (IsA(clause, RestrictInfo))
 			clause = ((RestrictInfo *) clause)->clause;
 
+		if (indexcolnos)
+		{
+			int			indexcol = lfirst_int(indexcolno_lc);
+
+			if (indexcol < 0)
+			{
+				/* FIXME */
+				continue;
+			}
+
+			indexcolno_lc = lnext(indexcolno_lc);
+		}
+
 		if (IsA(clause, OpExpr))
 		{
 			OpExpr	   *op = (OpExpr *) clause;
@@ -5346,6 +5361,7 @@ genericcostestimate(PlannerInfo *root,
 	IndexOptInfo *index = path->indexinfo;
 	List	   *indexQuals = get_quals_from_indexclauses(path->indexclauses);
 	List	   *indexOrderBys = path->indexorderbys;
+	List	   *indexOrderByCols = path->indexorderbycols;
 	Cost		indexStartupCost;
 	Cost		indexTotalCost;
 	Selectivity indexSelectivity;
@@ -5509,8 +5525,8 @@ genericcostestimate(PlannerInfo *root,
 	 * Detecting that that might be needed seems more expensive than it's
 	 * worth, though, considering all the other inaccuracies here ...
 	 */
-	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals) +
-		index_other_operands_eval_cost(root, indexOrderBys);
+	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals, NIL) +
+		index_other_operands_eval_cost(root, indexOrderBys, indexOrderByCols);
 	qual_op_cost = cpu_operator_cost *
 		(list_length(indexQuals) + list_length(indexOrderBys));
 
@@ -6629,7 +6645,7 @@ gincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	 * Add on index qual eval costs, much as in genericcostestimate.  But we
 	 * can disregard indexorderbys, since GIN doesn't support those.
 	 */
-	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals);
+	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals, NIL);
 	qual_op_cost = cpu_operator_cost * list_length(indexQuals);
 
 	*indexStartupCost += qual_arg_cost;
@@ -6809,7 +6825,7 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 	 * the index costs.  We can disregard indexorderbys, since BRIN doesn't
 	 * support those.
 	 */
-	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals);
+	qual_arg_cost = index_other_operands_eval_cost(root, indexQuals, NIL);
 
 	/*
 	 * Compute the startup cost as the cost to read the whole revmap
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index e9f4f75..6428932 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -79,6 +79,8 @@ extern bool indexcol_is_bool_constant_for_query(IndexOptInfo *index,
 extern bool match_index_to_operand(Node *operand, int indexcol,
 					   IndexOptInfo *index);
 extern void check_index_predicates(PlannerInfo *root, RelOptInfo *rel);
+extern Expr *match_pathkey_to_indexcol(IndexOptInfo *index, PathKey *pathkey,
+									 int *indexcol_p);
 extern Expr *match_orderbyop_pathkey(IndexOptInfo *index, PathKey *pathkey,
 						int *indexcol_p);
 extern bool match_orderbyop_pathkeys(IndexOptInfo *index, List *pathkeys,
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 0ce2175..636e280 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -189,7 +189,7 @@ extern void estimate_hash_bucket_stats(PlannerInfo *root,
 
 extern List *get_quals_from_indexclauses(List *indexclauses);
 extern Cost index_other_operands_eval_cost(PlannerInfo *root,
-							   List *indexquals);
+							   List *indexquals, List *indexcolnos);
 extern List *add_predicate_to_index_quals(IndexOptInfo *index,
 							 List *indexQuals);
 extern void genericcostestimate(PlannerInfo *root, IndexPath *path,
diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index 0cefbcc..724890b 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -876,11 +876,9 @@ ORDER BY tenthous <-> 3500;
       120 |     9120
 (10 rows)
 
--- IN restriction on the first column is not supported
+-- IN restriction on the first column is not supported without 'ORDER BY col1 ASC'
 EXPLAIN (COSTS OFF)
-SELECT thousand, tenthous
-FROM tenk1
-WHERE thousand IN (5, 120, 3456, 23)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
 ORDER BY tenthous <-> 3500;
                              QUERY PLAN                              
 ---------------------------------------------------------------------
@@ -890,6 +888,63 @@ ORDER BY tenthous <-> 3500;
          Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
 (4 rows)
 
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand DESC, tenthous <-> 3500;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Sort
+   Sort Key: thousand DESC, ((tenthous <-> 3500))
+   ->  Index Only Scan using tenk1_thous_tenthous on tenk1
+         Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
+(4 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand, tenthous <-> 3500;
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Index Only Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
+   Order By: (thousand AND (tenthous <-> 3500))
+(3 rows)
+
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand, tenthous <-> 3500;
+ thousand | tenthous 
+----------+----------
+        5 |     3005
+        5 |     4005
+        5 |     2005
+        5 |     5005
+        5 |     1005
+        5 |     6005
+        5 |        5
+        5 |     7005
+        5 |     8005
+        5 |     9005
+       23 |     3023
+       23 |     4023
+       23 |     2023
+       23 |     5023
+       23 |     1023
+       23 |     6023
+       23 |       23
+       23 |     7023
+       23 |     8023
+       23 |     9023
+      120 |     3120
+      120 |     4120
+      120 |     2120
+      120 |     5120
+      120 |     1120
+      120 |     6120
+      120 |      120
+      120 |     7120
+      120 |     8120
+      120 |     9120
+(30 rows)
+
 -- Test kNN search using 4-column index
 CREATE INDEX tenk1_knn_idx ON tenk1(ten, hundred, thousand, tenthous);
 -- Ordering by distance to 3rd column
@@ -1122,38 +1177,132 @@ WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
    3 |      43 |      643 |     9643
 (10 rows)
 
--- Not supported by tenk1_knn_idx (not all previous columns are eq-restricted)
+-- Array ops on non-first columns are not supported
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
-                   QUERY PLAN                   
-------------------------------------------------
- Index Scan using tenk1_thous_tenthous on tenk1
-   Index Cond: (thousand = 643)
-   Order By: (tenthous <-> 4000)
-   Filter: (hundred = 43)
+WHERE ten IN (3, 4, 5) AND hundred IN (23, 24, 35) AND thousand IN (843, 132, 623, 243)
+ORDER BY tenthous <-> 6000;
+                                                                          QUERY PLAN                                                                          
+--------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: ((tenthous <-> 6000))
+   ->  Index Only Scan using tenk1_knn_idx on tenk1
+         Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = ANY ('{23,24,35}'::integer[])) AND (thousand = ANY ('{843,132,623,243}'::integer[])))
 (4 rows)
 
+-- All array columns should be included into ORDER BY
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE ten = 3 AND hundred = 43 ORDER BY tenthous <-> 4000;
-                     QUERY PLAN                     
-----------------------------------------------------
- Sort
-   Sort Key: ((tenthous <-> 4000))
-   ->  Index Only Scan using tenk1_knn_idx on tenk1
-         Index Cond: ((ten = 3) AND (hundred = 43))
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY tenthous <-> 6000;
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: ((thousand = 123) AND (tenthous > 2000))
+   Order By: (tenthous <-> 6000)
+   Filter: ((hundred = 23) AND (ten = ANY ('{3,4,5}'::integer[])))
 (4 rows)
 
+-- Eq-restricted columns can be omitted from ORDER BY
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE ten = 3 AND thousand = 643 ORDER BY tenthous <-> 4000;
-                   QUERY PLAN                   
-------------------------------------------------
- Index Scan using tenk1_thous_tenthous on tenk1
-   Index Cond: (thousand = 643)
-   Order By: (tenthous <-> 4000)
-   Filter: (ten = 3)
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, tenthous <-> 6000;
+                                                    QUERY PLAN                                                    
+------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23) AND (thousand = 123) AND (tenthous > 2000))
+   Order By: (ten AND (tenthous <-> 6000))
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, tenthous <-> 6000;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      23 |      123 |     6123
+   3 |      23 |      123 |     5123
+   3 |      23 |      123 |     7123
+   3 |      23 |      123 |     4123
+   3 |      23 |      123 |     8123
+   3 |      23 |      123 |     3123
+   3 |      23 |      123 |     9123
+   3 |      23 |      123 |     2123
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, tenthous <-> 6000;
+                                                    QUERY PLAN                                                    
+------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23) AND (thousand = 123) AND (tenthous > 2000))
+   Order By: (ten AND (tenthous <-> 6000))
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, tenthous <-> 6000;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      23 |      123 |     6123
+   3 |      23 |      123 |     5123
+   3 |      23 |      123 |     7123
+   3 |      23 |      123 |     4123
+   3 |      23 |      123 |     8123
+   3 |      23 |      123 |     3123
+   3 |      23 |      123 |     9123
+   3 |      23 |      123 |     2123
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4 ,5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, thousand, tenthous <-> 6000;
+                                                    QUERY PLAN                                                    
+------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23) AND (thousand = 123) AND (tenthous > 2000))
+   Order By: (ten AND (tenthous <-> 6000))
+(3 rows)
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, thousand, tenthous <-> 6000;
+ ten | hundred | thousand | tenthous 
+-----+---------+----------+----------
+   3 |      23 |      123 |     6123
+   3 |      23 |      123 |     5123
+   3 |      23 |      123 |     7123
+   3 |      23 |      123 |     4123
+   3 |      23 |      123 |     8123
+   3 |      23 |      123 |     3123
+   3 |      23 |      123 |     9123
+   3 |      23 |      123 |     2123
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, thousand, tenthous <-> 6000;
+                                                    QUERY PLAN                                                    
+------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk1_knn_idx on tenk1
+   Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23) AND (thousand = 123) AND (tenthous > 2000))
+   Order By: (ten AND (tenthous <-> 6000))
+(3 rows)
+
+-- Extra ORDER BY columns after order-by-op are not supported
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 ORDER BY ten, thousand <-> 6000, tenthous;
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Sort
+   Sort Key: ten, ((thousand <-> 6000)), tenthous
+   ->  Index Only Scan using tenk1_knn_idx on tenk1
+         Index Cond: ((ten = ANY ('{3,4,5}'::integer[])) AND (hundred = 23))
 (4 rows)
 
 DROP INDEX tenk1_knn_idx;
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 5e3bffe..69c890e 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -345,13 +345,22 @@ FROM tenk1
 WHERE thousand = 120
 ORDER BY tenthous <-> 3500;
 
--- IN restriction on the first column is not supported
+-- IN restriction on the first column is not supported without 'ORDER BY col1 ASC'
 EXPLAIN (COSTS OFF)
-SELECT thousand, tenthous
-FROM tenk1
-WHERE thousand IN (5, 120, 3456, 23)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
 ORDER BY tenthous <-> 3500;
 
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand DESC, tenthous <-> 3500;
+
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand, tenthous <-> 3500;
+
+SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
+ORDER BY thousand, tenthous <-> 3500;
+
 -- Test kNN search using 4-column index
 CREATE INDEX tenk1_knn_idx ON tenk1(ten, hundred, thousand, tenthous);
 
@@ -378,18 +387,55 @@ WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
 SELECT ten, hundred, thousand, tenthous FROM tenk1
 WHERE ten = 3 AND hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
 
--- Not supported by tenk1_knn_idx (not all previous columns are eq-restricted)
+-- Array ops on non-first columns are not supported
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred IN (23, 24, 35) AND thousand IN (843, 132, 623, 243)
+ORDER BY tenthous <-> 6000;
+
+-- All array columns should be included into ORDER BY
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY tenthous <-> 6000;
+
+-- Eq-restricted columns can be omitted from ORDER BY
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE hundred = 43 AND thousand = 643 ORDER BY tenthous <-> 4000;
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, tenthous <-> 6000;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, tenthous <-> 6000;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, tenthous <-> 6000;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, tenthous <-> 6000;
+
+EXPLAIN (COSTS OFF)
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4 ,5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, thousand, tenthous <-> 6000;
+
+SELECT ten, hundred, thousand, tenthous FROM tenk1
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, thousand, tenthous <-> 6000;
 
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE ten = 3 AND hundred = 43 ORDER BY tenthous <-> 4000;
+WHERE ten IN (3, 4, 5) AND hundred = 23 AND thousand = 123 AND tenthous > 2000
+ORDER BY ten, hundred, thousand, tenthous <-> 6000;
 
+-- Extra ORDER BY columns after order-by-op are not supported
 EXPLAIN (COSTS OFF)
 SELECT ten, hundred, thousand, tenthous FROM tenk1
-WHERE ten = 3 AND thousand = 643 ORDER BY tenthous <-> 4000;
+WHERE ten IN (3, 4, 5) AND hundred = 23 ORDER BY ten, thousand <-> 6000, tenthous;
 
 DROP INDEX tenk1_knn_idx;
 
#23Anastasia Lubennikova
lubennikovaav@gmail.com
In reply to: Nikita Glukhov (#22)
Re: [PATCH] kNN for btree

The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: tested, passed

Hi,
thank you for your work on this patch.

Patch #1 is ready for commit.
It only fixes lack of refactoring after INCLUDE index patch.

Patches 2-7 are ready for committer's review.
I found no significant problems in algorithm or implementation.
Here are few suggestions to improve readability:

1) patch 0002.
* Returned matched index clause exression.
* Number of matched index column is returned in *indexcol_p.

Typos in comment:
exPression
index columnS

2) patch 0002. 
+			/*
+			 * We allow any column of this index to match each pathkey; they
+			 * don't have to match left-to-right as you might expect.
+			 */

Before refactoring this comment had a line about gist and sp-gist specific:

- * We allow any column of the index to match each pathkey; they
- * don't have to match left-to-right as you might expect. This is
- * correct for GiST, and it doesn't matter for SP-GiST because
- * that doesn't handle multiple columns anyway, and no other
- * existing AMs support amcanorderbyop. We might need different
- * logic in future for other implementations.

Now, when the code was moved to a separate function, it is not clear why the same logic is ok for b-tree and other index methods.
If I got it right, it doesn't affect the correctness of the algorithm, because b-tree specific checks are performed in another function.
Still it would be better to explain it in the comment for future readers.

3) patch 0004
if (!so->distanceTypeByVal)
{
so->state.currDistance = PointerGetDatum(NULL);
so->state.markDistance = PointerGetDatum(NULL);
}

Why do we reset these fields only for !distanceTypeByVal?

4) patch 0004
+ * _bt_next_item() -- Advance to next tuple on current page;
+ * or if there's no more, try to step to the next page with data.
+ *
+ * If there are no more matching records in the given direction
*/

Looks like the last sentence of the comment is unfinished.

5) patch 0004
_bt_start_knn_scan()

so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
/* Reset right flag if the left item is nearer. */
right = so->currRightIsNearest;

If this comment relates to the string above it?

6) patch 0004
_bt_parallel_seize()

+ scanPage = state == &so->state
+ ? &btscan->btps_scanPage
+ : &btscan->btps_knnScanPage;

This code looks a bit tricke to me. Why do we need to pass BTScanState state to _bt_parallel_seize() at all?
Won't it be enough to choose the page before function call and pass it?

7) patch 0006
+  <title>Upgrade notes for version 1.6</title>
+
+  <para>
+   In version 1.6 <literal>btree_gist</literal> switched to using in-core
+   distance operators, and its own implementations were removed.  References to
+   these operators in <literal>btree_gist</literal> opclasses will be updated
+   automatically during the extension upgrade, but if the user has created
+   objects referencing these operators or functions, then these objects must be
+   dropped manually before updating the extension.

Is this comment still relevant after the latest changes?
Functions are not removed, they're still in contrib.

Patches #8 and #9 need more review and tests.
I'll try to give a feedback on them in the week.

P.S. many thanks for splitting the code into separate patches. It made review a lot easier.

The new status of this patch is: Waiting on Author

#24Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Anastasia Lubennikova (#23)
9 attachment(s)
Re: [PATCH] kNN for btree

Attached 9th version of the patches.

On 03.03.2019 12:46, Anastasia Lubennikova wrote:

The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: tested, passed

Hi,
thank you for your work on this patch.

Thank you for you review.

Patch #1 is ready for commit.
It only fixes lack of refactoring after INCLUDE index patch.

Patches 2-7 are ready for committer's review.
I found no significant problems in algorithm or implementation.
Here are few suggestions to improve readability:

1) patch 0002.
* Returned matched index clause exression.
* Number of matched index column is returned in *indexcol_p.

Typos in comment:
exPression
index columnS

"exPression" is fixed.
But there should be "column" because only single index column is matched.

2) patch 0002.
+			/*
+			 * We allow any column of this index to match each pathkey; they
+			 * don't have to match left-to-right as you might expect.
+			 */

Before refactoring this comment had a line about gist and sp-gist specific:

- * We allow any column of the index to match each pathkey; they
- * don't have to match left-to-right as you might expect. This is
- * correct for GiST, and it doesn't matter for SP-GiST because
- * that doesn't handle multiple columns anyway, and no other
- * existing AMs support amcanorderbyop. We might need different
- * logic in future for other implementations.

Now, when the code was moved to a separate function, it is not clear why the
same logic is ok for b-tree and other index methods. If I got it right, it
doesn't affect the correctness of the algorithm, because b-tree specific
checks are performed in another function. Still it would be better to
explain it in the comment for future readers.

It seems that match_orderbyop_pathkey() is simply the wrong place for this
comment. I moved it into match_orderbyop_pathkeys() which is implementation of
ammatchorderby() for GiST an SP-GiST. Also I added old sentence about its
correctness for GiST and SP-GiST.

3) patch 0004
if (!so->distanceTypeByVal)
{
so->state.currDistance = PointerGetDatum(NULL);
so->state.markDistance = PointerGetDatum(NULL);
}

Why do we reset these fields only for !distanceTypeByVal?

These fields should be initialized (it is initialization, not reset) only for
by-ref types because before writing a new distance values to these fields,
the previous by-ref values are pfreed. The corresponding comment was added.

4) patch 0004
+ * _bt_next_item() -- Advance to next tuple on current page;
+ * or if there's no more, try to step to the next page with data.
+ *
+ * If there are no more matching records in the given direction
*/

Looks like the last sentence of the comment is unfinished.

Yes, "false is returned" is missing. Fixed.

5) patch 0004
_bt_start_knn_scan()

so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
/* Reset right flag if the left item is nearer. */
right = so->currRightIsNearest;

If this comment relates to the string above it?

No, it relates only to string below. 'right' flag determines later the selected
scan direction, so 'currRightIsNearest' should be assigned to it. This comment
was rewritten.

6) patch 0004
_bt_parallel_seize()

+ scanPage = state == &so->state
+ ? &btscan->btps_scanPage
+ : &btscan->btps_knnScanPage;

This code looks a bit tricke to me. Why do we need to pass BTScanState state
to _bt_parallel_seize() at all? Won't it be enough to choose the page before
function call and pass it?

If we will pass page, then we will have to pass it through the whole function
tree:
_bt_parallel_seize()
_bt_steppage()
_bt_next_item()
_bt_next_nearest()
_bt_load_first_page()
_bt_init_knn_scan()
_bt_readnextpage()
_bt_parallel_readpage()
_bt_first()

I decided simply to add flag 'isKnn' to BtScanState, so the code now looks like
this:
scanPage = state->isKnn
? &btscan->btps_scanPage
: &btscan->btps_knnScanPage;

I also can offer to add 'id' (0/1) to BtScanState instead, then the code will
look like this:
scanPage = &btscan->btps_scanPages[state->id];

7) patch 0006
+  <title>Upgrade notes for version 1.6</title>
+
+  <para>
+   In version 1.6 <literal>btree_gist</literal> switched to using in-core
+   distance operators, and its own implementations were removed.  References to
+   these operators in <literal>btree_gist</literal> opclasses will be updated
+   automatically during the extension upgrade, but if the user has created
+   objects referencing these operators or functions, then these objects must be
+   dropped manually before updating the extension.

Is this comment still relevant after the latest changes?
Functions are not removed, they're still in contrib.

Yes, comment is still relevant. SQL functions and operators are dropped,
but C functions remain (see [1]/messages/by-id/CAPpHfdstf812dYObwMeu54P5HijHgURNdoJRc3jKxRj2LsQJRg@mail.gmail.com).

Patches #8 and #9 need more review and tests.
I'll try to give a feedback on them in the week.

P.S. many thanks for splitting the code into separate patches. It made review a lot easier.

The new status of this patch is: Waiting on Author

[1]: /messages/by-id/CAPpHfdstf812dYObwMeu54P5HijHgURNdoJRc3jKxRj2LsQJRg@mail.gmail.com

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Fix-get_index_column_opclass-v09.patch.gzapplication/gzip; name=0001-Fix-get_index_column_opclass-v09.patch.gzDownload
0002-Introduce-ammatchorderby-function-v09.patch.gzapplication/gzip; name=0002-Introduce-ammatchorderby-function-v09.patch.gzDownload
0003-Extract-structure-BTScanState-v09.patch.gzapplication/gzip; name=0003-Extract-structure-BTScanState-v09.patch.gzDownload
0004-Add-kNN-support-to-btree-v09.patch.gzapplication/gzip; name=0004-Add-kNN-support-to-btree-v09.patch.gzDownload
0005-Add-btree-distance-operators-v09.patch.gzapplication/gzip; name=0005-Add-btree-distance-operators-v09.patch.gzDownload
0006-Remove-distance-operators-from-btree_gist-v09.patch.gzapplication/gzip; name=0006-Remove-distance-operators-from-btree_gist-v09.patch.gzDownload
0007-Add-regression-tests-for-kNN-btree-v09.patch.gzapplication/gzip; name=0007-Add-regression-tests-for-kNN-btree-v09.patch.gzDownload
0008-Allow-ammatchorderby-to-return-pathkey-sublists-v09.patch.gzapplication/gzip; name=0008-Allow-ammatchorderby-to-return-pathkey-sublists-v09.patch.gzDownload
0009-Add-support-of-array-ops-to-btree-kNN-v09.patch.gzapplication/gzip; name=0009-Add-support-of-array-ops-to-btree-kNN-v09.patch.gzDownload
#25Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Nikita Glukhov (#24)
Re: [PATCH] kNN for btree

Hi!

I've some questions regarding this patchset.

1) This comment needs to be revised. Now, B-tree supports both
ammatchorderby and amcanbackward. How do we guarantee that kNN is not
backwards scan?

/*
* Only forward scan is supported with reordering. Note: we can get away
* with just Asserting here because the system will not try to run the
* plan backwards if ExecSupportsBackwardScan() says it won't work.
* Currently, that is guaranteed because no index AMs support both
* ammatchorderby and amcanbackward; if any ever do,
* ExecSupportsBackwardScan() will need to consider indexorderbys
* explicitly.
*/

2) Why this should be so?

EXPLAIN (COSTS OFF)
SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
ORDER BY thousand DESC, tenthous <-> 3500;
QUERY PLAN
---------------------------------------------------------------------
Sort
Sort Key: thousand DESC, ((tenthous <-> 3500))
-> Index Only Scan using tenk1_thous_tenthous on tenk1
Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
(4 rows)

EXPLAIN (COSTS OFF)
SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
ORDER BY thousand, tenthous <-> 3500;
QUERY PLAN
---------------------------------------------------------------
Index Only Scan using tenk1_thous_tenthous on tenk1
Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
Order By: (thousand AND (tenthous <-> 3500))
(3 rows)

It seems that we restart bidirectional scan for each value specified
in IN clause. Then why does it matter whether it is forward or
backward scan?

3) What is idea of 8th patch? It doesn't seem to be really needed for
subsequent 9th patch, because we anyway ignore partial match pathkeys.
Then why bother producing them? Is it stub for further incremental
sort?

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#26Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Alexander Korotkov (#25)
10 attachment(s)
Re: [PATCH] kNN for btree

Attached 10th versions of the patches.

Fixed two bugs in patches 3 and 10 (see below).
Patch 3 was extracted from the main patch 5 (patch 4 in v9).

On 11.03.2019 20:42, Alexander Korotkov wrote:

Hi!

I've some questions regarding this patchset.

1) This comment needs to be revised. Now, B-tree supports both
ammatchorderby and amcanbackward. How do we guarantee that kNN is not
backwards scan?

/*
* Only forward scan is supported with reordering. Note: we can get away
* with just Asserting here because the system will not try to run the
* plan backwards if ExecSupportsBackwardScan() says it won't work.
* Currently, that is guaranteed because no index AMs support both
* ammatchorderby and amcanbackward; if any ever do,
* ExecSupportsBackwardScan() will need to consider indexorderbys
* explicitly.
*/

Yes, there was problem with backward kNN scans: they were not disabled in
ExecSupportsBackwardScan(). This can lead to incorrect results for backward
fetches from cursors. Corresponding regression test is included into patch #8.
And the comment was also fixed.

2) Why this should be so?

EXPLAIN (COSTS OFF)
SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
ORDER BY thousand DESC, tenthous <-> 3500;
QUERY PLAN
---------------------------------------------------------------------
Sort
Sort Key: thousand DESC, ((tenthous <-> 3500))
-> Index Only Scan using tenk1_thous_tenthous on tenk1
Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
(4 rows)

EXPLAIN (COSTS OFF)
SELECT thousand, tenthous FROM tenk1 WHERE thousand IN (5, 120, 3456, 23)
ORDER BY thousand, tenthous <-> 3500;
QUERY PLAN
---------------------------------------------------------------
Index Only Scan using tenk1_thous_tenthous on tenk1
Index Cond: (thousand = ANY ('{5,120,3456,23}'::integer[]))
Order By: (thousand AND (tenthous <-> 3500))
(3 rows)

It seems that we restart bidirectional scan for each value specified
in IN clause. Then why does it matter whether it is forward or
backward scan?

kNN scans now can only be forward, and in forward btree scans array iteration
order matches the index sort order. We could determine array iteration order
by ScanKey strategy, but ASC/DESC info flag is not passed now to the place of
ScanKeys initialization (see ExecIndexBuildScanKeys()). ASC/DESC passing needs
refactoring of whole passing of orderbyclauses/orderbyclausecols.

There also was a problem in btmmatchorderby()/match_patchkey_to_indexcol():
array keys were incorrectly matched to DESC index columns.

3) What is idea of 8th patch? It doesn't seem to be really needed for
subsequent 9th patch, because we anyway ignore partial match pathkeys.
Then why bother producing them? Is it stub for further incremental
sort?

Yes, this is a kind of stub for incremental sort. But also this simplifies
a bit ammatchorderby() functions, because they should not care about the length
of returned pathkey list, they simply return after the first unsupported
pathkey. I event think that ammacthorderby() should not depend on whether we
support incremental sorting or not.

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Fix-get_index_column_opclass-v10.patch.gzapplication/gzip; name=0001-Fix-get_index_column_opclass-v10.patch.gzDownload
0002-Introduce-ammatchorderby-function-v10.patch.gzapplication/gzip; name=0002-Introduce-ammatchorderby-function-v10.patch.gzDownload
0003-Enable-ORDER-BY-operator-scans-on-ordered-indexes-v10.patch.gzapplication/gzip; name=0003-Enable-ORDER-BY-operator-scans-on-ordered-indexes-v10.patch.gzDownload
0004-Extract-structure-BTScanState-v10.patch.gzapplication/gzip; name=0004-Extract-structure-BTScanState-v10.patch.gzDownload
0005-Add-kNN-support-to-btree-v10.patch.gzapplication/gzip; name=0005-Add-kNN-support-to-btree-v10.patch.gzDownload
0006-Add-btree-distance-operators-v10.patch.gzapplication/gzip; name=0006-Add-btree-distance-operators-v10.patch.gzDownload
0007-Remove-distance-operators-from-btree_gist-v10.patch.gzapplication/gzip; name=0007-Remove-distance-operators-from-btree_gist-v10.patch.gzDownload
0008-Add-regression-tests-for-kNN-btree-v10.patch.gzapplication/gzip; name=0008-Add-regression-tests-for-kNN-btree-v10.patch.gzDownload
0009-Allow-ammatchorderby-to-return-pathkey-sublists-v10.patch.gzapplication/gzip; name=0009-Allow-ammatchorderby-to-return-pathkey-sublists-v10.patch.gzDownload
0010-Add-support-of-array-ops-to-btree-kNN-v10.patch.gzapplication/gzip; name=0010-Add-support-of-array-ops-to-btree-kNN-v10.patch.gzDownload
#27David Steele
david@pgmasters.net
In reply to: Nikita Glukhov (#26)
Re: Re: [PATCH] kNN for btree

On 3/15/19 2:11 AM, Nikita Glukhov wrote:

Attached 10th versions of the patches.

Fixed two bugs in patches 3 and 10 (see below).
Patch 3 was extracted from the main patch 5 (patch 4 in v9).

This patch no longer applies so marking Waiting on Author.

Regards,
--
-David
david@pgmasters.net

#28Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: David Steele (#27)
10 attachment(s)
Re: [PATCH] kNN for btree

On 25.03.2019 11:17, David Steele wrote:

On 3/15/19 2:11 AM, Nikita Glukhov wrote:

Attached 10th versions of the patches.

Fixed two bugs in patches 3 and 10 (see below).
Patch 3 was extracted from the main patch 5 (patch 4 in v9).

This patch no longer applies so marking Waiting on Author.

Attached 11th version of the patches rebased onto current master.

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Fix-get_index_column_opclass-v11.patch.gzapplication/gzip; name=0001-Fix-get_index_column_opclass-v11.patch.gzDownload
0002-Introduce-ammatchorderby-function-v11.patch.gzapplication/gzip; name=0002-Introduce-ammatchorderby-function-v11.patch.gzDownload
0003-Enable-ORDER-BY-operator-scans-on-ordered-indexes-v11.patch.gzapplication/gzip; name=0003-Enable-ORDER-BY-operator-scans-on-ordered-indexes-v11.patch.gzDownload
0004-Extract-structure-BTScanState-v11.patch.gzapplication/gzip; name=0004-Extract-structure-BTScanState-v11.patch.gzDownload
0005-Add-kNN-support-to-btree-v11.patch.gzapplication/gzip; name=0005-Add-kNN-support-to-btree-v11.patch.gzDownload
0006-Add-btree-distance-operators-v11.patch.gzapplication/gzip; name=0006-Add-btree-distance-operators-v11.patch.gzDownload
0007-Remove-distance-operators-from-btree_gist-v11.patch.gzapplication/gzip; name=0007-Remove-distance-operators-from-btree_gist-v11.patch.gzDownload
0008-Add-regression-tests-for-kNN-btree-v11.patch.gzapplication/gzip; name=0008-Add-regression-tests-for-kNN-btree-v11.patch.gzDownload
0009-Allow-ammatchorderby-to-return-pathkey-sublists-v11.patch.gzapplication/gzip; name=0009-Allow-ammatchorderby-to-return-pathkey-sublists-v11.patch.gzDownload
0010-Add-support-of-array-ops-to-btree-kNN-v11.patch.gzapplication/gzip; name=0010-Add-support-of-array-ops-to-btree-kNN-v11.patch.gzDownload
#29Thomas Munro
thomas.munro@gmail.com
In reply to: Nikita Glukhov (#28)
Re: [PATCH] kNN for btree

On Tue, Mar 26, 2019 at 4:30 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

Fixed two bugs in patches 3 and 10 (see below).
Patch 3 was extracted from the main patch 5 (patch 4 in v9).

This patch no longer applies so marking Waiting on Author.

Attached 11th version of the patches rebased onto current master.

Hi Nikita,

Since a new Commitfest is here and this doesn't apply, could we please
have a fresh rebase?

Thanks,

--
Thomas Munro
https://enterprisedb.com

#30Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Thomas Munro (#29)
10 attachment(s)
Re: [PATCH] kNN for btree

On 01.07.2019 13:41, Thomas Munro wrote:

On Tue, Mar 26, 2019 at 4:30 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

Fixed two bugs in patches 3 and 10 (see below).
Patch 3 was extracted from the main patch 5 (patch 4 in v9).

This patch no longer applies so marking Waiting on Author.

Attached 11th version of the patches rebased onto current master.

Hi Nikita,

Since a new Commitfest is here and this doesn't apply, could we please
have a fresh rebase?

Attached 12th version of the patches rebased onto the current master.

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Fix-get_index_column_opclass-v12.patch.gzapplication/gzip; name=0001-Fix-get_index_column_opclass-v12.patch.gzDownload
0002-Introduce-ammatchorderby-function-v12.patch.gzapplication/gzip; name=0002-Introduce-ammatchorderby-function-v12.patch.gzDownload
0003-Enable-ORDER-BY-operator-scans-on-ordered-indexes-v12.patch.gzapplication/gzip; name=0003-Enable-ORDER-BY-operator-scans-on-ordered-indexes-v12.patch.gzDownload
0004-Extract-structure-BTScanState-v12.patch.gzapplication/gzip; name=0004-Extract-structure-BTScanState-v12.patch.gzDownload
��E]0004-Extract-structure-BTScanState-v12.patch�\ms����,�
�|h$�T�f[�O����4M2q������"!��4�KRv|����/R�,;���imK��b�������J�rp0����h~8X,���G������t1O���%M��\�����b4�^���mt��)^_.�k�}�������4/.2������O�^��|��\1�Z�@dx?���O�3{����eP<����?�`�d8�S������y���b�I���y�'����<oO�Y�d��2	��A ��I2/2)��~ ��_f%��S����K?�@�/1����u:;�[Q�#G	19D��DI�CY��_��v��G��q�du%�L�'��
p���(/LL����b��`�'2t��p �$�Y�I�uz��p�K�������b!<�"*��d1�wh���~���O�~�?s�91'\������6�?o4<t����|1���yq!�b��e��B�z%�@���+�c��V�@De�=�yOtX����[K������_���w����=�c�,(2�|&��S��>�I����=���}A��,����k��8��[����5��8������'����/��R����XF9�Nl�c1��������]&�
4�������_�#�]�>=|��u���������,�o�����;���>�����8
�U��2���	��'���,:�f(��:)P�),�k)��V���/�|���A������q��������I������rC)�+0��,B�0r�E93���$�r��p����cEd6/f�(��nO�N�.}%�B^�r���s'\���+��lS�����%C<}�0�zG�����Y��C�3:�������F�N?]��E����.��(n(�"q�|]�,��^��i�D
����0�Y$��nG�+�2�c���a����_���q���c��@�I��K����DZ:�R��eH��/�'�G�`�^f������.�O���=���Dt��{�\��tv�?�)Kq��vD�dc�;�6�?������h�=<���V4�x��	�C�� ���r
����W������z*������`���������&�:���Bl4��P�Sxa)���(D����Fh��#�&�g:j<��$����	l	�����1$b���#9����F��64��*�s�cd�R�����Qo�iE�b?/p���q��;�{�/�.4�g�Y��1L���(��[G���5�#3��4����b�z�����x�k��Y=Gw�+p���� �H�l��-�S)�V([��u6��g�(�S�Pn�������-L`����������*
���O5&�v�=IvS`:�S�E��K�8���d�3��_E��Z��f����yOC(2��O��^���4��W>h5�l�>U6T�J�D1>��	&���t�]nks�g���imS1;�,i
��dk�4�e�nA��!�P�[��\p�H�����i��j�� ����
�!�q���~��s�AY��N��_���f5�@���������`� *
y{T�YS�SJ��P�K�����+P ��YJ�2��a`O��5�|/M����p
&d�)ClT�$�+5\������9��^�`�;���"���\���1O��H3d���
�z�#o���k�g���?j�������F�R�k�P��?Z�~������3.pz��}<=v'_���,�YB��h��3
�u�{� NN�pF�g�4��zw#�s
������`�C������>�C��\e���4��#0��B8\�d/�vuQ��
&���6�n��tp4���+�?��8��9}@���	�a(�P�5c<�?8Qv�]%��@��aS���	L�l�>J���j��V$��4����v�/H�umQf���y7�4-���r	����N�|
�.�~Z��AD��SXQh�	�}��V�S��~�*�xD����p����Lx���i����b�i����2��Xf�=Z�b�,+�N�l��A�����:��� ���PtQ���2P�{��X�������d	�H���m�!�<�R(���hzhY�)'r����������B[N�>��R�K}�]
�um�6-�������&�k1Y���*�}����b`2V���n8pn�#5�P�R������V���f�s�voB�L1�E����r����V�<7����QS�	V��?���DH�?QhB����A�8?�����~C{��d�c\�G�KW�8:];��Q)!4~�+B!�o��A������_\�����}g�*h0Me���F07>�k�1���#�2kD����������/�������k���Qp��Z���
lg��ZO��5=� s���E�E��G,Vr�����X/�*�foi��}V��wx�2��Z&a��<�����Y���bnv�*,�;�=����,�����"��.�ZiF��':R��a�1H���O@���&���g8�m|�$�i�A-��D��D�n*OK���Wi����S:�����)#���H+����W��	xU:V@���~�W&ey��
�T{*���hx����C�3`z_P�9���v����J�[���!��kH<V������,�q�&�Q��F�!�hK��,K���z���;`�
����#�]�vZ�&�g���� W&�V�6��`��G�9��	o�m���K���P7�b����/N����_�d�{���8�k`bs��7l:9?�F����I�N	^p6�����N+�h[�9v�����mg����6pi��V��sP��&��/��?�a?4h��������G��g�$t�=��{hF�BC�����O�d$`����GX��^��4�
������G>�i��`M�E�N�����R��_B�}	�r4���u�r�vU�N�Q>�!e���������Q�+�4�)'P��5�]���
�]}������J�G���0��1�9�j�����Sq�pCQ��.�(��:���.��Z����"�����j������m�:m&��f�>h8,kF�L��Dp0�L��2��8Uv<��_`V]�v�)PkRkWQ�]�[$,�
���Y��G���|+��9%�Eo��dM�$�Z
����U��4�y�;)r����_�haS���b��2
�_$����P
,1[#6�+%��$�^P�rAI�U��5��W���k����D�e�\���6v�������F����U�����rs�	�0jqY���K��3�G���`9<iW��'�@��gn-�pU�ImlD���1��N��r��I���d&���?v��4�N���M��BO�AU�nk����u�u4���9����
%n����`+���u�Gm���"�~�^_�0D�g��d�bd�r)���y��55�	n���Y� @�h5_��!�=q�����������m�uv�q	�(���������'g�"��$���7�P!�tr�������{�dP����B��M|��	�����f���#Mb����C������T��=oq�TN��QR��B�N����s��5�g�O����V�/"�u�
�I�[Y��*Z�8���>�G9]D���D7�zo&7�V5X��Kd�w���E������j�N�r�������S9�G)���C5�i�U��Ko1���'�h�v��������s9��%���
����6�-���y���Z�>�jzA��������~�hp��V�	;Uv�{�F������G��m]>'1��5�n�mtk<��s���V��k��v)���x��$uLb�.��uC�."T7��<�L�f�r���}��D�n�[��:#Q����*�A]�2B�m�
�C����,����+���/Sp���&��������DI�&�(u���$�pF������q����be�4�L���1�����{0��#H�`�dU|�����L�H��P��Lb�j��k��Fd����� �/�	3���983:������u�����D�Z��K��GN�@}�w�r%��Q�O��T�}�C=�8L����J�T�9�<�}A%���wZ����rb9�
������T]�Xc��
���@aZ�I+E��!���h��B��/��X����
����u
E��K�g`(e�����M���>�������n�m�Ox�����l��%����]�������D�PJ���=�`n�}����H������/���{;i�z��8O��b~y��"-Bj>x��O"�SXx�A�dn
'�"H6}|��Z?>{
#��P3)�wF��I�l�
44��������%"���_��.B�1��8��s��x4����k��b��_j����u-��N�j��k/ev�Q*1uE����`�T�O�Nc5���b
�����IS���,�[
9�]EHk�n����$�U��2���k��b�w7������?~��y�����o�'N,m�K]���#�W�F����0y��iRM���z�������6;�
V��W��\��=�pW��7����.M�
q:&�U��������";Ga{�	&O��Q��}��j7
�1����=�{o$��t:� j�b6:.����s�8`������I�Ca���5����[^i�9/�8��V1���Y�~\-q�xl�I�r
�y���]w$Q��-��fx��@�gtN3���z�J�ptH�K����C�E[�(��t;�VS���������d�������Al��YL����'0�����-^�Z��'YX���8� �+v4oE���Akmd��M��U&

;�J�+p�����%�~Q�gu����|���X��Sw|��<����� ���	��d2���An�Q��g*�J5�6*������}���#��g�L�������;�:��9����F��cS�*i��ugo���H=����S�U�X�a�){�w?������5��������K�6����E1��|��v�'xO��8`G~�_t�P��(.D+������u������\���b��.�!=�F@��6@�3��6PKwAi�uo���x��V������v���F�R�Nnt�������8P���~
'�y�:��m�m3���������=�u*	�o��i�)1�Vn�Ul���b���|U������(��O��d�k2��A�Vs��o|'�6h9��2���S����f��RU��4o�T��5����5����
������������-�D��m��
��w�����h��R~���dK�q[j����}RU�wB{��'�LF>������t%�:���F���A��E��>|�.��������RFL���~WQ�EJ���H�!o[�b�{�8W
�6N��c��y��9��J�JP�T�B!/}d�d����LDrQB����2J������kU��=t��/���`�p2X%0��t+�<&��a\����<�C4��n	�������'�������^G�m9��r����T���%a<�/$���!��jS�\M]E	p���	��`�'�e����T������&�7$�EN�c�uQ�G������g�\���pD`j�?��/����|R[!s
A����c���3L���b��K�X��TeG���ZcCC
��W�u�\������9_�T�']�W�
�����v�*P�����������L^}~��pr`��������c�RV9E�X�dr�w�y��6��W��d**���_D�W�G)��_����HfF���;������c�%i!��m�3�����:T�.��M7�H������fx�����3{WcaAN��"��*_rU���Lp1�wml[8�O�
wu��W�m1��Go���T���7��#~����ZF�E�}1����<y|+�������+�mnr�H�E)��Avy_K����&,J�������4$�,�z�J6�[60��Ez�"m��"K]�����@�2�A^%��l�<+*���<��"D�:�E�i������I~%#r0����$T�JX�ii�/�M��@'I�NV�[�u;�*��\4\�<t�8�)}�0NF��)�]b���0i	���yx��Y�T�����^���Q����x�f.%������ )\UR��X]3�����Om,�o:��l��*Olv�!�	=���
F�o���*���hS�R��I'��"W��l�|�g�V9^�xI����Gq��Z6]
S9��0o���Qm���3Ey���l�
�Z���q��8u���%(�#{r��]������`��x|e\��h�G�P����6�m����@�!�A����3�����GR�Mg:�-�)�%�8l�����{�(�u'�������������I~�x����4����=�s#�L�r�5�������h*h&#�A���E����0hf�(/�xza�w���ne�?D�y�9�k��`�v ��=<<�Z��E)g�9�UAH���u�ak��uj_�G������Wi�lb����l>��e����`@�������I@
��	����
P��J>��3d8p�?2?����F q��q���F��Lg���A����e! v�D�-R�����!h�W����H����sc��|V��?���XYure�����"]�b�}#�D�u���_�3=0pa_�&
o\��Pp,E&/N0��Sv�VV�]�4���[����W�������)��u���/��u��	Y�u���0��v���N@���y"�%�[����H+�1X��%r��4����gX67��3C������3���MqJ��������Q�^���]�<�^�P�������a6��2�Z��+��f��I�#*yX� ��B�3��'%���p��(J3GX�u��E���v/���E�-�nGXm���[W����.�~d����U35��KW�G�oy|:C�3�Z!<��J�nl:�����[���l3����I���������Y�&�����/����^=���w���Y@6��
����i�#��t������*n2��"���OV,z��2�	�L��9�|�$����%3ns;<�k������P.�u�arm\}&�F���ra�>$���3B4�!GBnr��%��la-�|~(}:����S�����������������I����L��Z[�c�=��2r��l���V����0�a2r0�stT�����Z���B�x43�(d8��I0[�LY�v�u:�L����\GU8q�Rb�����'J�\�9[	�������O�Ly~�#��2��&�RI�
nL���a��U��M=��,����Gd�n�H���`��2���:���N�cd�T�I|���������+H`y<�]��T����a��)=:mU2��f�$B��f��TF�s��ZSQ�	o%�L%	<xf,9S�U������N/��2D�A`(!c�����t��w@D�x�2����C����E��o��;QMQ.�n��M��UX�J4�����.��<]�I2����J�h��0��7��o.^������yu{q}���7�����A3,��:��1��R�!�x����__�^����z�h6v�u�8}�m��2����P�1��W���s�L#wm���s���$	eZ�������P��2/�,�D�C0=S�&&Kz����MSE������I-�P�~9@~��G*�N�?^^�������^��OR�u9w�0���g=#C'�h��Z������D����O����3�,\[Y�����$�/~�f1�br�,�U�SY�YJiz�&O��TS�K������S4�`��c� 7���a��n��]��S���8����{0��b
U��	'X3���X�an�Vo��X|4�2�����&�\�9	r�.�KYM����kEpN,f��vrw6cBabv�
Cb������(C�>�����G�6���&�5w�(�r�csc���>s�-�5z3%����Y�lX���56�)���v���3���XA�m���E�L�GANm��a�flQ�hS��;���h���+�����_/��2)agj�-�8(�:��g��,�[{�zm$�!���U|�4�T5��j���"������W����f������M�������1�c�3�#���F�Eic(����1�0�&3as�.'��{�3����u�������!�b>�/__���P���a�Y�����j��0}u����x&4���_�T�*�Iq7��R���-1j���r?����;	����z`��Q~<�����U����lV��T�����f��l�j8>�.;yE6�]�����gg�f���'Y���C��yL�#��s��-�W�M2���W@o`��O�D�L�����0[b�n�?�j)n.s���|���<mX�Qtt�#�����o/���|�������c_��v�G:�����++��S�����B��ELY�V��U�UO������Y�r��M��%K���G�|�����:	�\���k�W�`�X�����z�Qt|B���YB� ���"*��
�q�����N���,�����J?��>�2xJ&k����t�#r����Ner��$}/Z������P�f��W�C�t���2�Q��t�6�����#����q�w<O^'�S!'( �p���gX�.j�*D�|��U:�,7�<�f\���5���v�1�v��Q��'��OdS:�l��#`��������g�����#U��WBMI���`������"�yn/_aZ�"9\qX����N�l�{�.H��Jd�����Bv�RP�,S��$�q���VD����\�� ���m�H����N�����g=�Pi;�����`�t�i��
�Nf3J�X����
uRW0�	NV����{o:Y�T����{n-����~�h�};�I�������>H9S�$�8"fC��`��2Ao@R'd�,Z�S���h%9%��+��O7W�d��E0+���N���E7��v�i��^������|LR*�t���$��3Q*��_��2�������R|�e*�l�p ��0���gp�O��]����u
���h�9�0��16�K�� �����eI��pp���d�,�tSosff�i����d,��-��9.���x��|<��hl���z'ZGu���6�����c�zF[Ep��
�Mp��&�����|��0?z��a�Z�~�m�%��v����qI���	�������K?�A6{kp��F����������/Qc6�q��������>CO�sOB�_��$����-�9W���hCb�n?gc�&�:��ttb$6m�v���B��Vv�h\�	��������]�����E�N��$8��J�o�Y�a�
R_s��p;	-��	sv'_[r���~����O:�QY�b`C�$�e�����S51��9��I��@���]��!k(H!��+����
A�X���$��t��P��IB=��W�����}�]�	!�G������\h�/�~�ds��7�]�����_1�
���a�-~d^	�,R^���}�jB���?�|{�w�Q����->�=�����PXr��0����=�d9W���['��(j4�89w��_�����k+���Q���jM��d�����j�
	�R�4�X6��t��� �$�_��*Y�SLEV�A)TF..l�$G��
�i�a���^�m����I#�X�D��2O	}S-���h�'���x��}�8����_1C`��w��0//���������~�cq�r	�S�Ox@�>`g�YxW�>�����V�Z��Rt��s��~��w}��9X����"u�����D�@��u2u����T5r��L`�����,�������'�c5��c�Y3�,_���h�b#���N�����z�N��^��Al}?�ZKI,�������%��/8E
��g@@��:�1]S�Y�f���~�RF!��-���7��\!��q�B���H>Q�?��"� �����QZ�z�9��J!��Vic���I��M������$�XNz<��a���h<'���^����/��Tg�dA��{#��.�x�YZ&�����"�������S���������p{=�u�����Qz,|���c�.�X�������pWz,T!P%dA��`/{t2m���7���S,S��I�%��T���Tn�x"�����l����jbL��X&���P�V`��8��8�������u��%�N~��qp��/n_�����"���4Z�J�=x�w���^6�s��J�6��p7�W�=5}`���&��������
{�)�����d%�Mu�,�u3^�\=��36��������G��a�GK�
�@f�4Y_&o�|>��d�>g�]6&����������n�6����u<�z5��h����*�a������Z�"0~:W����1���G2�/�q���#��0�������B}+"�
0005-Add-kNN-support-to-btree-v12.patch.gzapplication/gzip; name=0005-Add-kNN-support-to-btree-v12.patch.gzDownload
0006-Add-btree-distance-operators-v12.patch.gzapplication/gzip; name=0006-Add-btree-distance-operators-v12.patch.gzDownload
0007-Remove-distance-operators-from-btree_gist-v12.patch.gzapplication/gzip; name=0007-Remove-distance-operators-from-btree_gist-v12.patch.gzDownload
0008-Add-regression-tests-for-kNN-btree-v12.patch.gzapplication/gzip; name=0008-Add-regression-tests-for-kNN-btree-v12.patch.gzDownload
0009-Allow-ammatchorderby-to-return-pathkey-sublists-v12.patch.gzapplication/gzip; name=0009-Allow-ammatchorderby-to-return-pathkey-sublists-v12.patch.gzDownload
0010-Add-support-of-array-ops-to-btree-kNN-v12.patch.gzapplication/gzip; name=0010-Add-support-of-array-ops-to-btree-kNN-v12.patch.gzDownload
#31Thomas Munro
thomas.munro@gmail.com
In reply to: Nikita Glukhov (#30)
Re: [PATCH] kNN for btree

On Tue, Jul 2, 2019 at 5:47 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

Attached 12th version of the patches rebased onto the current master.

Hi Nikita,

make check-world fails for me, and in tmp_install/log/install.log I see:

btree_int2.c:97:9: error: implicit declaration of function 'int2dist'
is invalid in C99 [-Werror,-Wimplicit-function-declaration]
return int2dist(fcinfo);
^
btree_int2.c:97:9: note: did you mean 'int2_dist'?
btree_int2.c:95:1: note: 'int2_dist' declared here
int2_dist(PG_FUNCTION_ARGS)
^
1 error generated.

--
Thomas Munro
https://enterprisedb.com

#32Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Nikita Glukhov (#30)
Re: [PATCH] kNN for btree

On Mon, Jul 1, 2019 at 8:47 PM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

On 01.07.2019 13:41, Thomas Munro wrote:

On Tue, Mar 26, 2019 at 4:30 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

Fixed two bugs in patches 3 and 10 (see below).
Patch 3 was extracted from the main patch 5 (patch 4 in v9).

This patch no longer applies so marking Waiting on Author.

Attached 11th version of the patches rebased onto current master.

Hi Nikita,

Since a new Commitfest is here and this doesn't apply, could we please
have a fresh rebase?

Attached 12th version of the patches rebased onto the current master.

I have more thoughts about planner/executor infrastructure. It
appears that supporting "ORDER BY col1, col2 <-> val" is too complex
for initial version of patch. Handling of "ORDER BY col" and "ORDER
BY col <-> val" cases uses different code paths in optimizer.

So, I'd like to limit first version of this patch to support just most
simple "ORDER BY col <-> val" case. We would probably be able to
enhance it even in v13 release cycle, but let's start with just simple
case. I also think we can evade replacing amcanorderbyop flag with
method, but introduce just new boolean flag indicating knn supports
only first column.

------
Alexander Korotkov

Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#33Nikita Glukhov
n.gluhov@postgrespro.ru
In reply to: Alexander Korotkov (#32)
12 attachment(s)
Re: [PATCH] kNN for btree

Attached 13th version of the patches.

On 08.07.2019 21:09, Alexander Korotkov wrote:

I have more thoughts about planner/executor infrastructure. It
appears that supporting "ORDER BY col1, col2 <-> val" is too complex
for initial version of patch. Handling of "ORDER BY col" and "ORDER
BY col <-> val" cases uses different code paths in optimizer.

So, I'd like to limit first version of this patch to support just most
simple "ORDER BY col <-> val" case. We would probably be able to
enhance it even in v13 release cycle, but let's start with just simple
case. I also think we can evade replacing amcanorderbyop flag with
method, but introduce just new boolean flag indicating knn supports
only first column.

Now patches 1-8 implement only "ORDER BY col <-> const" case.
ammatchorderby() is replaced with amorderbyopfirstcol flag.

Patches 9-12 contain ammatchorderby() and other features, so they may
not be reviewed right now.

On 08.07.2019 11:07, Thomas Munro wrote:

make check-world fails for me, and in tmp_install/log/install.log I see:
btree_int2.c:97:9: error: implicit declaration of function 'int2dist'
is invalid in C99 [-Werror,-Wimplicit-function-declaration]
return int2dist(fcinfo);
^
btree_int2.c:97:9: note: did you mean 'int2_dist'?
btree_int2.c:95:1: note: 'int2_dist' declared here
int2_dist(PG_FUNCTION_ARGS)
^
1 error generated.

Fixed.

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Fix-get_index_column_opclass-v13.patch.gzapplication/gzip; name=0001-Fix-get_index_column_opclass-v13.patch.gzDownload
0002-Introduce-ammorderbyopfirstcol-v13.patch.gzapplication/gzip; name=0002-Introduce-ammorderbyopfirstcol-v13.patch.gzDownload
0003-Enable-ORDER-BY-operator-scans-on-ordered-indexes-v13.patch.gzapplication/gzip; name=0003-Enable-ORDER-BY-operator-scans-on-ordered-indexes-v13.patch.gzDownload
0004-Extract-structure-BTScanState-v13.patch.gzapplication/gzip; name=0004-Extract-structure-BTScanState-v13.patch.gzDownload
0005-Add-kNN-support-to-btree-v13.patch.gzapplication/gzip; name=0005-Add-kNN-support-to-btree-v13.patch.gzDownload
0006-Add-btree-distance-operators-v13.patch.gzapplication/gzip; name=0006-Add-btree-distance-operators-v13.patch.gzDownload
0007-Remove-distance-operators-from-btree_gist-v13.patch.gzapplication/gzip; name=0007-Remove-distance-operators-from-btree_gist-v13.patch.gzDownload
0008-Add-regression-tests-for-kNN-btree-v13.patch.gzapplication/gzip; name=0008-Add-regression-tests-for-kNN-btree-v13.patch.gzDownload
0009-Introduce-ammatchorderby-function-v13.patch.gzapplication/gzip; name=0009-Introduce-ammatchorderby-function-v13.patch.gzDownload
0010-Add-btree-support-of-kNN-on-non-first-column-v13.patch.gzapplication/gzip; name=0010-Add-btree-support-of-kNN-on-non-first-column-v13.patch.gzDownload
0011-Allow-ammatchorderby-to-return-pathkey-sublists-v13.patch.gzapplication/gzip; name=0011-Allow-ammatchorderby-to-return-pathkey-sublists-v13.patch.gzDownload
0012-Add-support-of-array-ops-to-btree-kNN-v13.patch.gzapplication/gzip; name=0012-Add-support-of-array-ops-to-btree-kNN-v13.patch.gzDownload
#34Thomas Munro
thomas.munro@gmail.com
In reply to: Nikita Glukhov (#33)
Re: [PATCH] kNN for btree

On Sat, Jul 13, 2019 at 5:32 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:

Attached 13th version of the patches.

While moving this to the next CF, I noticed that this needs to be
adjusted for the new pg_list.h API.

--
Thomas Munro
https://enterprisedb.com

#35Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Nikita Glukhov (#33)
Re: [PATCH] kNN for btree

On 2019-Jul-12, Nikita Glukhov wrote:

Attached 13th version of the patches.

Hello Nikita,

Can you please rebase this again?

Thanks,

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#36Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alvaro Herrera (#35)
12 attachment(s)
Re: [PATCH] kNN for btree

On Mon, Sep 2, 2019 at 9:11 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

On 2019-Jul-12, Nikita Glukhov wrote:

Attached 13th version of the patches.

Hello Nikita,

Can you please rebase this again?

Nikita is on vacation now. Rebased patchset is attached.

I think patches 0001-0008 are very clear and extends our index-AM
infrastructure in query straightforward way. I'm going to propose
them for commit after some further polishing.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0004-Extract-structure-BTScanState-v14.patch.gzapplication/x-gzip; name=0004-Extract-structure-BTScanState-v14.patch.gzDownload
0003-Enable-ORDER-BY-operator-scans-on-ordered-indexe-v14.patch.gzapplication/x-gzip; name=0003-Enable-ORDER-BY-operator-scans-on-ordered-indexe-v14.patch.gzDownload
0005-Add-kNN-support-to-btree-v14.patch.gzapplication/x-gzip; name=0005-Add-kNN-support-to-btree-v14.patch.gzDownload
���m]0005-Add-kNN-support-to-btree-v14.patch�}iwG��g�W�4�e@8�H���$sL�Z������+��B�*�����7����A�������dUfVfdd��vN�po�;v������{���^g2i�L&��d<�F���?���a ���h��f��O�����[�����������]�Q���~��,������ac�8�:qb��x;�j�����a�V_�����W�=Qm�4�[���o�(~%~�pxs��hv�[�_��x,�/.D����y,�P���nm���-1G����v�o���*���!DgWT��T7/{#7*��Z-���	z��
?�����������x��w���&�}uzx��4�)���^��1�{��\��W_�U���|t���C�t�Vk�U#-b���\��p����}t|o�"���P��`�/�n�[�.�;M
�T�(�;q��f���uu�������xm��f��.L������
��7j����l'�W����O�����������9��;�3��v�D�<�� *W+xn����]~R�lm���D���^,���S0,z�E�+�{{-�����q���v�;�f�����N�X��[��K�����:P�ZOT�GwG���[����
1w}�
4&�s�b�E8������R�s $p,�����3w��VU��_�B�����|w�1"�^;���|����(���%���n����"�k�Z��\w��]�/p�c�3sY`f�E�p|h��/������:��?������_o�G���w0�(���Y�	n��g��x�lIqNa}4�E�N}��#���,����#1]D1����#u���>����b:#�!X�"D�`���[�-	��;#������m��Z��Q�m��q��'�KN���3��V���`���0�}E�F1>�?�-�g�tG�����~k~75o���z����M������h�yx�m^T$L�
P�[6������	��@P��
�Y�2X-E�t�7E������������������_s�p���czv�������X�G���"��1��[���b
��������u���c�����{a���7C���ib�o��Q-���5�*����$2��x_�,�Q��`�x������������N���������x��m�;��D�I��#�$���������?@|�'� ���������	[o<�	]^�Q���}���1��dw�?�,|�w�M��-�	@x�y�L2��D+�(���_��{��^o�s��!7���5y��������)mmp{���/�'�7��lU�O�O�o�K�����p?�������+q�3�=@�]������v��.��q~��� ��U��W�>,3�)����3Yl�������(���[�����1}��"�=8�4�u!��E���: U��((4��)��7Z��<%e���p\D������Yo	�:$[|�fso���.��\���B9,|�����M��{;�Q��������p	V��� ���p��i�@���{��r�A�,���a��z�?����w��������P�>\����]��'0/������'xP!���s��:��R�����
�	
�:�zN%�V�Hw! Iy�;�������b���{���P�n=����l0.�Z��|��?Ba�h���%����?�91�'w�G0p���"����3�1���#���QE��0pI�#�%����	���@��(��$�a>DJ��Y%�z��c���D��x�n������wwo�o����$����}e3D��n��<�h7���!�<����q�v�FP�b@��]�BZ3��B���+��,$��j��S�S �_�L���: G����;SZ�B/�}�����w�#�����$�9pL��AG�[��#� l�@���p�(!�l��#���yl�v�br����������JE4sG���q�,�-��"�{D���Bk�[U��$5��p6`��<k�b�c������{����4O��������S��/�"��C�SdN��-9�=�h;�hd<�5�l����8�+�3,Y�V���:A����C�
C���8a�&@���Od���vf��0��g �5������@H�r���8�"�%����pB)1A�0�0u�h1w���	�4� ����0v��l���H�<�I_ �K_�	���cD���jr�<�4�o����H���p�1E:��oU�o$>��x��L�!`�3��3��<"r�?�X_��P��,$���;m�]F|T#I�Z��;��&�^���v��v���uI�n��
������%+�H��x��
���?��7��������,�=\x>�@�/��� ��
6��a�>��M���-����G���-@�yi�N�,�[�/8J��G7@��}����?q���;[��[�t������t���0�E�������K�;(�"k�b�����S�~��U�r��8��'���$ ������u�,�(��\D4i!8!8� �������@�������4���I��~�g���d
l���y�Lv�	������o��j�Ox4D5��y�����.�o�./�W��+�3����UD��3��_���s�^�c��l�Z�V��[���$��*����w>]KB��
cYD��>"�G�s����pe�4�@�.w?=O�~���
�(�H� ��J��.�?Y]=7��I��3/i������r�3d�o��� H�� O�3@M�@�y{���,��T�)����|s9sp ��e�Q���B�k_w�s��]^Da���@h%?�r�#|��q�a���{��j	Mq�����rBJ��c�ES�x!�%F<���sjWQ�J���O��.�j�
��&����Lo�_}�X�,��p��p@���?��P���������S4�y���{���$�XM��p	x={#.��@7P�M,�����tCP���?@`@Q��<��X��� �x���_h� 9��*t�����ab$~rQY�&6�~@�X�������>�(���
6H�<��n�^n�iU�8��~S��gd�Fg����,��Pq����O����g�ya����ZX_�3T�C�3[���%�X���7�m`���k������b(�A
]�I~k�c��|�Y@�[�FE$���cX��C x�Z"K|�B)q>����@��	�Q��v����v����'�#���9g6������aK���X�nL>"�a@]���~E*!��qE�	��vd-�[����/E1"�p����=H�"����5��~���'m�����]�~_"z��!
D�H&
��:m!:i\�r��I0������3�+d�s�Q�p��C�Pa�)����,�wwj}��{]�x	z�Tb������|��HJc(�
�J"O0�z?�l�TH����a���YZ��#X(���7o�P����X�8�d��g������S��/�n��/e���6i+�V��|:$� 6���J�~p�A9*�K�!���Cv����6�Ng��,II
<7���b��x�����������^�}$��}*�=�pp���#QK�2�B��h��CbcG��)z:�nA��aj�H��1'r�h:-������g	F�U����H|��VbT���z���o�'& &�$Y�T����E�M3^����Z�D����dbPks��q�Qc)�����I��g�
��.�����2��2!����P�f�;����Y.>"���D'��&}`C���NdGf[�Ng��<�e��\7?��H9}#���>4JE4�M{�dA
H���>?=��w����Z��}�_@�$-�E5:�����&�R������Z>���F���:�jb<z^@�>�����d��!��oP�(�v��w��Z���`B�FR�#�|��pR��9�pI�o�'HB�������.H�hY#FG!��c/��y"��bM��s�N�@����k�P�������V�����a%�a"y��6�u������W��^]�L�F,f,���):)BC-8�����}:{=��6iTp�qBTV�c���/;���x@!��=�<������k
(��m.��.��]�tV����o��<$`k�p�������i�!��HS�������U�O�@*(�	��V�:*9�49�|��r�eR�n�Kh������k���u�!�����G`��Ro�B[]�[��������WA)��E�x<�>�tzg�����}fK K��q���Y8g�yR����_.��79w�����$W������Vqu� W�	|�}T�b�5���X�e��*�W<N>���W������3��p������s��L���q�H���K
����+�-;t�{�=e�Y�+9*9�	��,~-!���-(���a����w���o+_6q�	�$u
���{9�Sg���O�p_EE�.��4m�O��HJ�}k��xE����,)�"�R��}��M���I���)V�vs��#���+^rt�B��(����A���~Y? ��t��h/�o�!�������;X���>���&��>^����..ogg7g��g���$�k�Q:�����9���W=�(e�^�4����45��6�(��~_����������x
�Z�����a#�Z�r��(e��;�����47�#h,��h��]���
B�B�)���uA�i��^��F����e�Z��A��N�O�,��K���o���5����To�����}���{2��K�����B��U����g����N<��O�Z���T��KuX�����?�^��S��@K�[��nB�$�$�Z�B��&���A������C'�:[i�E��������6+D�.=-����!���?���p�/�vrH�L��j����:r�d�m���CdO~<�8>�x��%k;� �hd#�p?y���+I���,����h��Z���P/K%DEt%����I������s�����dc������2IIC?E@��B������6��lZ[_����JeD�&�)��_Ag�];_mJ��g��R)�LS;�mPJ�^��T�_��kH�BIck�Q:;9?�����Jqx^<�#��XE�q�/���(���yl�4�)2���Mal\)����������ke\���xV�
�� f9~�=��Z�K|��
FI(-��6	,{;@�����r���'BA������rhu�f���w*�"
��u���xb�s����#�,7�5+k7��Nm��n��ie��p3�y��fJ^�B����^&��N�qO���7
�C/`g?H��H����|���0��"�H8�0:�G1�M�BLxq9��;����X�u9��V%������|��J�E��Y�����R!j*#d
�~����n0M�,�L43OB�aj����`�+}��������GfJhB\t}�&�����s��k�����`������u���L^y?
)���p=�/���s�������>��{��8Q�0�q��6��Ig<��K��~���q����c��z���F*���6��=�l��nV��^QjJ��0G{��2?/�@(G3[�s�����o�g)e3�����}<YW�	����
{�*]�jE.�j'����x8�G�~������h��t;�Q�����me2&���J�[q��;C4[W{�HN ��Vfpd�(?�I������[����Z2�3.�rft2I�*�C�'~��BZs�
�G��Z���~���8e*�*pf��u�_�6	p�2	��w6#x���R$��@�G
0�k/8y����k7'��!�$b�G�r6&���%�d�!BO�J��L����mcp�n�#����������ZF��5Y����3�H��H���B`V%$N�:,��r9���_"���tH�G���L�����U_P�%�����6-�`��kn{�9k�Bu0�<�(�zh��RZ=�8	���[*�1$K���y�{}�����Z�;j������yi+F�S�ETa	�����46���\^�(������l��p ��&&��EIi���HT��J�����B"(N�a��i�����X1M�o��|��f�%B������4�_q0��bJB�_7�*�v�����P_���`N������8�Q* ��&}����yY�a��-�^`
�2N�&Z*���u�%,���l���>q������
n�5�����=������/��@>�`��aPB��F�Z���4nQ�fr07�������@�$yv���swBQ��e���H��5��/��RxI� S�D7�,�}n�:"U�������Gt>��]e��Fx\\���o�����2�%��+�����&"�����n ���n.�	x8f�&�Iz����Z�KC1��_m=�m��x�/;Lc����f��S@�ZK�`��:Hf,9�l�P�]�;V�Og�����J���5��?�����,%�Qrh�����3�<sS_����F���������^��*:a��Ax����H�����*�E�/b*6&�>�<�l�/�u����'	��h��f�'���E��3���.�d���]��.�3/B���rCnsvp;�u�TS�!�Nl��L�)��5P�?G�eY�����m7Z�A+��d?K0������v�z�����c��3,
��?�����3HIR�&�^��|�����I���'\4�"�X��:�\S$s�do� O++�P��Lku^�)���%�cw:�1�t����S������	��������I	P�����`LB6���Q�V�����U!IU�a����C�����f�k���9�K��V�B�W!F���m�z|C��FL|�����E�O��S��gx�L/2=T�!�*f&g�h.i��m)P�����XU0QII%���rCh"�Jp�W����)F�Q�����FY{�exH��)���B���h'���Fh(;\L��3q�f��I��<8�Dg^*���o�R�t����8i��&'!;"�+��@�#c����D����_M�Jlz+�pK�$�0�Q�kM��e2���4��`��Kd����lc���eZ'�>g�C{�%���
��@k+�cz��l��ls|J����gB����k�H@�e�p�u�9=Fs��;!F I��N��L����VO���������"%���=O?I�r�UR���z
�8m��3��58�G�u4�A%�$d�<{s.�������P>�X)��O��u���&�����R�.��$��X3��������8t��T��/�WrxlAD��
�=7�"+���<�[)@���h=r�D��^H�d�+����c���$k�o�_������y�rM����'��?@��}�c%!l���r��q�xZ�1d�.�����X������Q�r���gEM�0k�K����?����.����U"�[D��]}�{����[�&�Y
�f�I��S����aG���������^X�*��?�J�K������DB�;b�,��i*�P�H*��=�j�m�J�b��|S�UV��Gx�w���j���n��`%��2�dvz1��D��C��#/�uV�b����uOQ2�R�(��T���e���K<�R6L�W=o��� �5jA�P_�N���Qj��G��o��|�t�r����n,��uH����Ce7��K�m�>As��)-�u��������-S%�s)���$x$mL�Nb�$'B��\
t���K{f �J)�d�b-$<�QN���68M{$��8M��H0�I��8���;��U6x�L��-�]�~����`���9Z��/����R�OE�-9c��,��$�rp��/�W��,a��	���l�`��=Y'�I$�j>��`Q�Xxq�k�[F��������9�����*R��A�T����~��i���)n"Y���#�T�`Rex��fz�X�+������	c@
��k���%]U��(^��Z������x��vQ�Gwa��gX-�s���T�������[M8��;(C��'��.�k�e��JlWe�?��q��������q-D���Ui07�]Sr���������:t�&�oC�e�J��"�������Q�:��-	(�x��*��sW_ji���%�����:)7��o=}��~E������m$^`������O��^�d���6=���[�@�|�k,=#���d�tpP����F��W��0R~u ���K�ZXz���S���9�y����#Cq)�j������E�:����t�Fy�A).��;_(����� �R���f�����}�Za"���q�t���T��9[���7W5iU��eh�I��>/���><�,J(��z�v�����'[XF��&)����,v����W�m11UO�I�qX�{9���!Y+?a�{�\�N��|����+�����&4uL���G����!�
�Z&����cv��e��$[���������`���d��C]�F�9&L����#
C�H2�v��K�;���������`4���o�
��y�25�R��>*c|� ��Q���p��E�����U�q���Voa�A�_cx���eT���$�W�1xfW��7V:�h'l�Z�7�(e��'����n(���@�|��Obd�@�l���#�G^�����W�b ��V���i�[�������*#�nY�M����G�Jf�ZZNl{����L�w�2�g7��s����8\�en�]/^�
wy�"e���v���A�^�|�w�&��]�g���\O����o���+"b����y����L��I�0�"~k��c�����z�?�2������4�um���gY�K�VmKFJ���S��=�: x��{����:��L���������D��%mU9��8H�L�����&�����T�W|���ij����+k�`�0�.�Q�^��k5���O���;qPJ5w���z�R��=���aT��	$����0�Zw)b���6�}�sU�5�:V���:������z����Cd��9���7�M+|\�H,e\�Xn�A!C��SG����.&���R��W^	��f�/�'k��Kvh��]d��L���b����b�b[����.��Vm7�}sl��Z���g��Y;�M�*����
�3��~��x������{mY��+���\+Q����{�t����)�>����z
_{:cSF[�����.(��!*�V��n����o
��I��)y"�5�U��8�����:{����%��9�e��/?z�2��$��7+�uA����w����O�t�(�k�9Pz���W�Z���h�'1��Qd���;�Z{;
,���\7���S���J�9	��.��������s4���O��v�Wk��6X��eUD���g�M�v��B���������������I�k$���X}n��Y�
K���=�W����z�X �d�<����{��:�C�wy(�����^����z��ze���%�u:�����p:����k������Ef6,7�����oOq�QHHr�6��}gE��_l�r4|���j4��,F����u��Q�;l4��~������
�����E��>���l�}�v�W��m�XCH�����=Y9��{�t��v{v;p��,�uN����6��[�uJ��z0�x���~s�����R���������Glt�^^�������K�#��e���1]?q1�!��������IX��x��z�m��G��y������������]�	����/���9gR�V��gOVZ�8���^��<|��z-"0�����cp��Ml{a��ML���A��q�*�c*G��b��QE�������'<0�@`�.F�Y�jd������b@.C���`���L�* d�Vw���J��kG�Og�h5<�~��&h�g����W�A08�[�KK��x9A�H��,�Z���b���� H�
�tvv�BAK�B}9��Mw�}k���j#}]�4q��A[WC�P�D-~�F���}w�����=M��!J�"V��(3�`0^���X�&_���u;_�HbWj��v��M�B\����p6����_"]�#��P�E�Wv���'z���N�8�(7Y���������uSz4���kg#��!1�J������d%�� �[�0s�61�f/�t�6�Y���4�[VF5�d6��M/�������-�
����+	&P_0
�;�������+j�������y�/fN��]t+�4�~1
���+�7��4��G`x�������j����^V���"��m������F�����*�[������q��(�M���-����)�i.����Q�d��jH�����l���{i�{��p@Z�^$.@�>1b>@2���W�?�)#j�%	Z��`&g��j�P�����1k0�]�%ee��,O�<7�����|��?n ^�C0a����M�^���N#[�i��,��+�t���kH8��9������/x���Ld����������Q�V��k]��Tf#���hFQ��A.MO��*F��rFM(�(�l	'���W���%�]i2A[���\:��4FV4h��������������1��
���BE���S��Ek2��������L��:P����C��I��(�0:�Uw
s��l����<��`:�����-��^��E�w����4:��8�OG�UZ�g/�������T+*����k~��Z[h�C��W���K�&�?�5�|~[��_�g�u+���%����F��&`�h�/��B�B�9�����Zo��$q	�Az�S�M2['������o�����7�����0�U���P�������������>@i��Y}+�-�J�L.��wk�g��V3��	��1��@��.z�s�w�����L���b���%.��7�b��uX�S;�Gs�t�r�{]���"�3Q|C���.V)h���?��pH���L��<�NC��������*[�#���VX3���,�o���e��>EO
��
H�����t�Y��k�
����_���T��RR�T]����<H�AY�7V�U��O
�l�#a�	��E��e��w�X<���a���q�q�j�H�2X����qL�x����(By�*yT�D��i�DY�����#Cxv���21������$�u~����h�K%^�[�^q��"��;T���vqrz�9���Ix�rnM1%�\���]�^X�F�/m*!XfqUmp�o���C��`�E���O�!1���fERY����]�s�
��fO���D_
���u�����eZ�\4��P���A�����}�U�m�"��]���y 1����P�kKwz_]�����5��!�����7��1���,��C���C��K/�S%�:J��nC����a�3��}M���V��<J��qR�,��3����s����w>%��W�b�A>pz����NyF{��Z�����o8���M�h��4f�����e*�:���0��X������fP��������-���������{C��$�'4R������f��<�\��*-\��Z�?�����D+�-��5�]��h�XJ'y^�U��QSuYu)
27����Z�r)��USB�A~��I�*h$�����T�������j�
*m��*<����.)
�\,���_��B��������y}l�`�1g�s��9��e���u��B�����8 yxM��t6<0�����JE�$uG�_�Rf�F$,�zV��o��?�>i�P�����l�Im������#��I��R�f����5	���	-u�d\Y�o����e�pT�L�IE�%RZY�#�d=y�k����P$����M��yL���Ac[�"c�K�	!���p	������o���I����7�;�����N
��]'p�����[+�LY��Mf��T/UI
�2��N������UK�Q�q��l���k��(�3�]�A��
�4��z�����U����y:�kH?��~)U�����y��^(yt���;V��>k���R*<(fv�_���w�\����=�"b}���r�����(�?
��H��PkH�"��!����^Y������=\N(i>9d �o�(�K6���5�g�w��6�>>�{�R�����q�/�����/}�Q�^����t����i�JY�7���-AC�5�t���9C���b�i����Y)�k��0w����h�Bs"`36�IR��7=��w
�TFD?�4l�U����x��*Y�L�i9bA�
e_N�nv�Z�v���>_��^@@��2��}2�7f3#����/1����>�I�JT�����089��9�8>�K������2�K���|'��%;���������� z�:����������`X���.�])�(E�2����T�W�R�����t�1X�+��l���Jt�t���QI��� 5>]���B��2e���J�b}M:��3�E�q�$s����?83����<3]/��f���'&�'u����
O?yQ��_~���:�9}�srB��������1�&��������S[��
C7[�T���Y�+Ie��}VTe���������GQ��A�]<�pT����	�w
o�����lH�I�t�9�M�����������.H�t��0U�����]=�\� ] }`���2�S^
ha\��\���tlhB���p�Q%sW�u�)��t��9�}�VI3K��mPTM��--�V��7��9rF�]
�:`�t|Xp�
$q mP!"���O��c�f.�g�-rBj��q�zV���fprz}L0�WSO���%��">�vaz�t�F~t1�DYWd1@�|Q$��EcFJ��"�c�u"�TY�0S
�f+9��H��Q�8��3�Z�.S$D���Es�8�:�|)���6��Ea�����R���5�4c��Wz���zS8�*�E�p+�[{��@�g��k\�T����x�+�����WD����&b8:��<�~DA76qY��H_����G.�}K���Lm^D�+���|'�W
��
��d��N��H���Ik�2��9L�{��&��?��(�tO�+���|��K�R�e��Us�85_�����=h0�U��!1��2��^�0������{y�f���P��+�����R*��	0�8�g�S�&)���<��D�%l��$,�y��*�����D��Pmz�e�U�R�Mxp���SX�65���u���d����)����tBt���O�	��R�����F�Qg�	�*^�+3n��T��	�SU(����&g���#��Q�B��&%��$a�e-��>�m�j�X��� fl;W�']����]����MTW�(�F��!S�y��|i�{V����78���d��:��_�^;T/�a\~�l��{�Z
Ug'�n�./���������W�y���������;��(���k�@L�� �}�	�S)�X��4���g���<�<�{��vS,J�`�!_bE~y*Y�y�e�E�_�M����Q����"�A������9�X��WYYZ$����N��[S"]��Z��^f��3���'o�T�gh6�Xi0@���������Z��QZZ&�=�f��x2�s�_��(���L���mM���/-�el
�|s�X���m�V���^��(i;�kf���^�2��Da\�
�EO�
��xwzK�����B�X���o	�a�V{P`�����%��ii4?�\Hgh[��mP�v����`)�����������T��2��ya��S�3������W$2'��8��_��3�_�����49�^�u�rg����$@��"���q�
\�`"����-m�L�J[@�l��
�0(�<^���q$*,a[�f�j�$.lh���P7.X.�i�BMn�^e�
�f�d#L�q�w�)�%�p��lhy�)%q��2�`25�s51�R�pn|�h���U9v�N��������-C���y�m5�/�n	O���W��P���}������%VnU�$���oav&�'+a�7Wy���/��<����
��?z��1����-u�����Uu��M\�����c}�N��	�����Ed��L����l2��e(���#�:p���@e����Wl�`�ZJ��������?oP�CZ]����u>����������F����M�;���x�:��k���Zs�,��ns�a�������S�9L��������~F��1������x�r�%�jT�c�N�\tts%����^���/<�ru�<�.�p�F�m��S��Z�����m��1t��g�#o��t�����l���m
jnu��V��y��P��kP��Q$���E�����~�"@���k��|e��N���|.K$l�b%��t-�2�:��	q�d\i�O�#�������3���j��Bt)� ��A^`[q
; �s����]P)�]��T�\ew>'9D�����������g'����<=Fw����34-����{`����������&6R�pl�?B7�#h�l!�~N���9�-�����7�Ci�P�M�k6�R�����5��Au�XAQI��7�:w�e�����%��z������
�tUL��.��Eh=� t�������7A�o����������)�m�	J�k���*�MB�
&��4H7X�� aM]�#��e���M�K�"�.C'�y���V�23�O�(�u	��$�lK�f�J��qr��h��+]�_��Z�+�d�~�]���m����������6����K��X�z���G����g'�j�P��F�mq(�D�BfP���<SK��i�3�E�=Bf�55���������� 
����1QIrC�S�Sk�A|n���<c������u=����9��9`�wEr�_�8�������eeu����������5�����-\5@fz��H�r����WM�v��Y*2�6MKL�'E+|�/-Vp����o�"���R��N��}�[�GL��;���9i�A�5{�Vw��7��E�������0��v����T�����Bn{@��c<��IjN��	N�
F2ZWq��L�"#�y��a�q�8l��;�����:�[2��9������t�L<�G��
���2�o�C��vk[�����l,��@g������[a"�E��q(�����}�� ��0�/"�$k��o	���{�oW+L�/��"�.U����aNQf��F���
����}d��#���9��9C�l���*����p�<��h�5����\(���Y�����tR3�N4��#]�V/�#�����<�2��Z�u_�Zv|������3�;���Li�x$k����^����q���DF1WER����8�x
<)W�#g�+�����>�g5fS��l��v�s7H�f��
���+��ef�8���������aSWF��gNyJ���gNG��3/�����N����Mw;]�I�`��d�a�Y�3�3eYz������;�d��k
`e��w�F\��n/������/��/MB���af�����p���Q���3����eyYm������|x{��(Xq��������V���4�"�J#�GEX.���\�xo��{������b����uf83����Xs�S�.�)q>�*����_8�� �g�(�/bo���]v6p�����&!�9{��+�t�3���:���u�-K��M�l���aR���H6��@���m��;��������;��Y)[#��VD��g��%�,������Z]���:9�RMJ�.
,�Y� Y�6�U-�f�	x��8����Uk�%�<�
����~ �[���#�����".���;'S�:m$�	�9���x�F
T��z_]Z��9����M1�]X�|��4#�h��a[2�����p��W�z��Is���
���LZ��x����u�5��n���.y
�Oxtuy~~tx�=`���������pzuxsy%��?;�Y�H�����.���Z���^�j,�h2VS��ON��J>E�wHNiW0�Y�����r�-A��W"e�{����d	'W=���kM�
Y8G���7Em�,K��v��i����UW�V�Jw���T����sn��sn����9�~��{[�Us�-�s��s�"8�1�B8��<�9s�#��'����3,������&��I��K��s���l��pq�v4�0
�q�=��2���[Wb4��>�P��T����Es�KQ����/[�zx�����m�[c�����P;!&~H��S^6��\��\3\�f��8
��)'c"��nF�{H��t�o{�[U�)s��O��;�7W����)+�8�<,����T/`X�2J��3Q{���N/�j���^���5����#����X��W����YH4~"���r��~J�'y�SnC)����x8l���q����	�g{�������,��������*�HJm��-��U�
�E�L��&������2�/��2A�/���_,�<����(a���M)��
6���6�h�d���d��j7Z������=��:
0002-Introduce-ammorderbyopfirstcol-v14.patch.gzapplication/x-gzip; name=0002-Introduce-ammorderbyopfirstcol-v14.patch.gzDownload
0001-Fix-get_index_column_opclass-v14.patch.gzapplication/x-gzip; name=0001-Fix-get_index_column_opclass-v14.patch.gzDownload
0006-Add-btree-distance-operators-v14.patch.gzapplication/x-gzip; name=0006-Add-btree-distance-operators-v14.patch.gzDownload
0007-Remove-distance-operators-from-btree_gist-v14.patch.gzapplication/x-gzip; name=0007-Remove-distance-operators-from-btree_gist-v14.patch.gzDownload
0009-Introduce-ammatchorderby-function-v14.patch.gzapplication/x-gzip; name=0009-Introduce-ammatchorderby-function-v14.patch.gzDownload
0008-Add-regression-tests-for-kNN-btree-v14.patch.gzapplication/x-gzip; name=0008-Add-regression-tests-for-kNN-btree-v14.patch.gzDownload
0010-Add-btree-support-of-kNN-on-non-first-column-v14.patch.gzapplication/x-gzip; name=0010-Add-btree-support-of-kNN-on-non-first-column-v14.patch.gzDownload
0011-Allow-ammatchorderby-to-return-pathkey-sublists-v14.patch.gzapplication/x-gzip; name=0011-Allow-ammatchorderby-to-return-pathkey-sublists-v14.patch.gzDownload
0012-Add-support-of-array-ops-to-btree-kNN-v14.patch.gzapplication/x-gzip; name=0012-Add-support-of-array-ops-to-btree-kNN-v14.patch.gzDownload
���m]0012-Add-support-of-array-ops-to-btree-kNN-v14.patch�=ks�����WL��n���ao|�q��o;�x���T��`�XHDv|����=���C�In�]����w����0���i[3�6Ts�����O�~�j:���13����)y�dLD�U�a�]U��s�C��7��o���<�$���3o	�~]Q|�h�p�Wf�t�<]�h:���P�!��;�pG��j�j}������y�z���w���o����h�XaL�����&�""q@�qH)�8:����v�D��=���;��m�(�������x}$���V�u+�e�z�����Va}O�e��~��C���#��3st�h~R�����_n/��|�����A��I����y��o�!<�G6�z����oG��-};Jq�7s�$����C�:�$���t��O��$k�g�)b��F�vH�c"����:��q�	�1���j�b����E��(����?H��TM%3����-��:
1
��~D��
���j*���z�7������f��>scbm���5��V������:]M�t��n�������S���"���f������J���U@$�M�h�N����1|z�	6)�������V_1���H��Z����|J��������j�[d����4�,B
��4�i$�qD�[�u �i�C�)���3[,z�=O�g���z�V���<��
>,����,{k5	J|]m�D����!�����z������D�wM�sJ����,�����QL��
��(���`��Y���'�g��7+6�&�[X�"25`^{/���<�pB�/&�'�F'��5���8����u�Y����A;3�N��FQ/8k�NN�O���_���ahh�B������)v�����*~b�E������d
pq��s���l����Ql�6=�^���p8��K���W3������f"��������[�}.��Y�x���l1�S�KX�l��c<,�����Y��2���@���-OvOJu�x�����{���al��)�D�/_����Pqmy6�����lK2m���w\����63��9��	l�@w[�~�Nysx�l����������oa���`���0�#O�"�������2}�1�'�x�?���M��	��W���C/C��,/������8�{���������jk��N(#N���.j����l�j�����D��]1i>���L�8~��a��C6���Z��hH?�m<;�6T����X;^$���,���d�I����xN�G���c�����S�	[?	e��=�� �!���c�W��W�&[|��+�g�O&�7�|�Vo��#���?���/�^T@��=p��di!��������j���qx����'�q��j���8A�D�S��,��� �\��wKP��u����e�
g8���bf�]�z	�n������x|��Fj��M�!8>�'���W�F�������H�h�)�G+��(�%Q�yH �~2k�X�k��0	-Y'W��V%_?��T�r�
YK�����&�d��^�?��2���&
����-�>�P�5�Q��ROf���Q|��!���C�L��a�L�'x������p�3 D�����D�K��54�'��Gt(�mf��TC�P�gK�N
7uT����LQ�~Z��5�&e+O�d�H�36�T��
�Y�n
58�]��4���_��It�����9��������P��'P�#�������l�3��t}7v-/��M����[�"a7�ys����8�����2�>$�JN�W�V������������D�Iet��g��}���<�zW�	"��0������������~����\����#:�S�(1�)y�B�V����L���!��h9�M���� ���
Wd]���$b����#��E����_�H�f�G	%SL`�i9�<L�k6���� �����]u}�2�%��������p���!�@��il,��"�L�1,���s>M����"��*1����Fc���d|��r|J����-��&�l3��� (���Q�] � F{��q�~	A{�g�n��'�e�6md������vvG`8���)����Q���V8uaka����k�������P�pT���A�E|�B��xA���"<35�V,�:�c�,�z��u��0�+B/�q��q��3wC���=]@LO�Lv0c"���[�Q�@@(��YS�:��������D�V����\��b1t�0�$'������#���(��/�M��������0W�R������[��b��Gh_5fS����T�f�I�3��|��FFRE�MESI�Y�[�4�HO6��)cHj���y��9�v�,�����o;QA)�^����U���H�1�����
� (+�t����D]�w����{&�K��O\����a\N����	�����L&r��L]G��,�d�M3�����bYb�?���H�$�Z��+�W�k
|l�gzW���3�tt�N�����|�`��W)Cd�Egi��g��.]��6�����q��
1�I�M�p�f���2�����(H��BzFPB����
��)
��r�!`���[��b6�2a�/K�h>�����1�i90�v|������.-�e������W7���z0���� r<I�����s����f�[Q��U��d��5�K�H����������/m Z'Ha�A��q�fR��#~��O~T &�c�j�t�)t�G���Gg�	��Q�).�R!���W�0H22ZN��-������Y������?_�n_��	�Sn`����%��Y�~�`N����!���$�}��IPO�3����������z�����C�����j������~���jf5���Y=�>4��h���
�����p�r��������r���L�����t���f�)2T�������+�"c��V6�V��#(��Navl�����H`����V�*=`�[�JRxM�,J5Y�����(�M�B������8&�`m1Y���j9Ue"'��G�	h����tV�|e�S�����2���������7!GAL��m��U��Fb��Q�\��kc@�<��s�b�k =���������	��u��{AQ��hY_�l`�i�]�q3�����&�X�!�������?��f
3%�,����������,��K
���AD�_p��7W
����9�'�<R'x�N����Nf�,��DG�wK�#�m��{K?��,��)�Z�������q�("���,@���	����������~���	Q�zf��tMX���,#]'R�8�c�M$�
�w.A�8�<����,x������l 
�f
�#�>�����2`N0�\
�:�_��Q�=��������+�;��`�)s��Q���H�%�U^����&�?���Y���E�Y�'��eb�.i��*+�L�|�%$p,|�t���X�����W���FKJN��8=<���19�}t����G����������1��6��^��(h�������Pm����p�;����i]��zky%�7����Qt����I��kz3��$q�DV�.>
�#�m+M2�����AGz��k�t��c��w
�5�����%D1:��!R�j���u�g�R#A����w����Q�8fH�N}�}�J�����:D����TMfS��Q���5���9[�2����R9���u*K��:r	�RD.X�0�G����L�hj�t
��:�?��xb������G�6u}������tDq��m�,��-X�K�V���������c>H�%C��� b�@��;�����`�r�r�O�UMAM<;s��?��39����.Iv�S���L��D*����	�TU�v�bI�$��0���`Im�)@�LN�B�a7�rES�����s����H�.��|�q|j��}�<�O�g��RZ�&���u�q����a44�:�����Qg(��H/���C���5*���j��o-J6�%�O��� Y%� R����4lnQY1)��4���I��`�|78�3��&^.���s��'������%:�|^� ��W1C�2���3�k���3#8yC�+H��sVB�S�w"���7X*
q��s��q��\A�r�!S�<�w� t�b^?�N�^���B ��':��0���.�E�����o����=K�I�g.��������}v������R�rK�b9��v���"�+a����z�#3_9�7�M��z��&��o��aa��g8���S��9A��K��)d���|�� |8�|�^KW�-��O� P
��A���9���_�-�o�G�b�
�0%^�m��6�*���LA�
4�[EeBZOHnw\K���B�U]������.0���j�vr==I��!}D�%�l��|��y�}���0�,�m�s�)i�a
W��������(;���~����<������Y�i����?���tPv�WCJ}��cG1
�2�����b�Fl��;Kl�[���j����"����[9����V@��%.�!6��%��w\� ���%�B�J� ��|nr8�������Ef�����ok�$p�z��K�Dk�{�f��3Z��[�������a
5mf��A���J@e�.
a��C�q�o)[�'U%������IlA3>o��(ITb�����L��9�I�����D�4@��wG�'�����)W�vv���1�
�zl�<��n�������y��mL�nW��}>���f=��9r=�){�7yuh0+��w!'�C1����/�=#z��t�=	I�>�ICSI\ah���X����B�T�,����z�~��������l��F����d������qp<>�������x�rtp����iy�z����+�z�����}t2JF��&>�*���=��D�+��Y�:�_���������qu�����M��������5�F�SPI��C	F��G[�?�
b�!E7>=��C�h��-����ZU�� 5Z�q��g���Ru��f���qZd��6�����!��10�S����1^f��&~K��7a�&�lQ&.)>��������]��e?�������H�Y�O�^gg�=#���0R�=�8��,�[9���)����f��[��W����Zek��?�[��c���l�n$g��rk��U�l5+[���^e+~,��+�*[�����0��(�v+[��V��U�l�U����~e����yQ ?j"@`��)�B���
����'��]''����#*���'�
��7��*�|�;�f�,G�8����k_����v0BY.��]�*���*��.��,S��5�����.o�(�4U� r�_HX��|�%����w�<C8E
�E���+f������M���'(�Z����+�'��Nl��I�2��
��}~�����b��n�����m�pS����d�N�\|�s��0����\�40�U����#v���C�!�r&h��^�j������n����������x�����/��vX*bzMUJ�m�i�++�JW1>�uRC�+�y���e/�	������BT�J2O�������Q"�W;+��]���}w��X��Y�}�t9����2�Y�X�(q�����th
�\�Mr�{�b���t�T#�Ixr����GF�cptT�$%�F$���Ez
�f�/���]�C��!J��c3�d�y���=)�_V�����3�a�oI@T��������W3T�)�Q����L>��b�o�u�rC�#���]K#xMD�7�2��_kTw�Q��Fk��5Jg��5s�_�����~h�g���|���(����������R^���C���^�}s�C�}���C���*��]D���Y�����E?;Wk��x)�u��.��a�[Q�����S�e���A7
)F����u�����4�WHk��W���v�K��S2���aI�F.����:� z|����ew+�Z�)7��k�[*���g6�XF�(iF�j��N�{�L=4}�}�������7#����������m�g��`������,J��\��;��t���o�M���r���"�'�!��N�nyh��S�xR�b�xT�o�v_��=G�t�m�����-�jT\��F�P��%���a|�Y�]��/��'L�n�������yD��z&�����5���Y.�]���V��H��u���
lz?*m�(y���o�Z��-��o!���o�>����o�7�qk&_K���^%�T�m�����r���
������u3�/��u2L�@$�>8���������j��_W��^���J�;�����������-�}
#37Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alexander Korotkov (#36)
Re: [PATCH] kNN for btree

On 2019-Sep-03, Alexander Korotkov wrote:

I think patches 0001-0008 are very clear and extends our index-AM
infrastructure in query straightforward way. I'm going to propose
them for commit after some further polishing.

Hmm. Why is 0001 needed? I see that 0005 introduces a call to that
function, but if attnum == 0 then it doesn't call it. Maybe it was
necessary in an older version of the patch?

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#38Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alvaro Herrera (#37)
Re: [PATCH] kNN for btree

On Tue, Sep 3, 2019 at 2:19 AM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

On 2019-Sep-03, Alexander Korotkov wrote:

I think patches 0001-0008 are very clear and extends our index-AM
infrastructure in query straightforward way. I'm going to propose
them for commit after some further polishing.

Hmm. Why is 0001 needed? I see that 0005 introduces a call to that
function, but if attnum == 0 then it doesn't call it. Maybe it was
necessary in an older version of the patch?

Regarding "attno >= 1" check I agree with you. It should be changed
to assert. But "attno <= rd_index->indnkeyatts" check appears to be
needed for current code already. It appears that gistproperty() can
ask get_index_column_opclass() for non-key attribute. Then
get_index_column_opclass() returns garbage past oidvector value.
Typically get_opclass_opfamily_and_input_type() doesn't find this
garbage opclass oid and gistproperty() returns null as expected. But
this is bug and needs to be fixed.

I'm going to push 0001 changing "attno >= 1" to assert.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#39Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#38)
7 attachment(s)
Re: [PATCH] kNN for btree

On Sun, Sep 8, 2019 at 11:35 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I'm going to push 0001 changing "attno >= 1" to assert.

Pushed. Rebased patchset is attached. I propose to limit
consideration during this commitfest to this set of 7 remaining
patches. The rest of patches could be considered later. I made some
minor editing in preparation to commit. But I decided I've couple
more notes to Nikita.

* 0003 extracts part of fields from BTScanOpaqueData struct into new
BTScanStateData struct. However, there is a big comment regarding
BTScanOpaqueData just before definition of BTScanPosItem. It needs to
be revised.
* 0004 adds "knnState" field to BTScanOpaqueData in addition to
"state" field. Wherein "knnState" might unused during knn scan if it
could be done in one direction. This seems counter-intuitive. Could
we rework this to have "forwardState" and "backwardState" fields
instead of "state" and "knnState"?

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0002-Enable-ORDER-BY-operator-scans-on-ordered-indexe-v15.patch.gzapplication/x-gzip; name=0002-Enable-ORDER-BY-operator-scans-on-ordered-indexe-v15.patch.gzDownload
0001-Introduce-ammorderbyopfirstcol-v15.patch.gzapplication/x-gzip; name=0001-Introduce-ammorderbyopfirstcol-v15.patch.gzDownload
0003-Extract-structure-BTScanState-v15.patch.gzapplication/x-gzip; name=0003-Extract-structure-BTScanState-v15.patch.gzDownload
���v]0003-Extract-structure-BTScanState-v15.patch�}�s�F����_��I i�����������7�J�@�)a\��I����nt��!����q�D$���w�{���J�z���]����q�_��r1����qo�?ug��t�K�s�3��#��|O��N���
��^�.������E|-~����
>�����<��2������?���WI�����U�t��;�~8��w,�N���;[M�K����oN>=�I��.^~���4KV�l�H�������2so��j��4�=���K����L�G�4K�T�3����`*����m��J?�]�
P��8Lk��VY���?����]�h�������?��y�,�����<��2�e�K9���ifz�����P,�P�bv�G�r���@Q*�,��t�;���#1���_Z{{�`���y�	��P�n�hW�Y�z��t:�e��vw1���yw+�v:��v�9����������5G��?G~���4;�Y�Z�r����B�3������A�	�� 9���#�����+)�X<��O���4�o����������'��4n=�/�aH\O��_���CA�38>�?��2a�����o����z�c���7����-��R���21�Ev�0;-�����T�	���e�@3I�y������'��mz�h��b���N���}#oS������8=M�����Z�VI?w�� �;���t��2#�.�k)��V�����t��~���%�^��� :����?��7�����L�G8�S�^>��P�VfM�}���d2�&� I����VB��O"���>�6w�C�=n>��fep�������2L%����8�5��`{�>��+��m����%�^�OIh�D�����H�b���f���1�k�Z�	1D~F�"��&�L���.G�� ���["������h�������F`(V�� -�8��Fc	����?�>!�2���K�rG��>p����������k������~�EY��F��������u;����vqn��D_��"+`�wI&�{L���@	�i�]��J��t��=����~"�P�uDB[����e5Q��QC0|��	��/?s��~��<���y�.�Qdp2����DZ��g�%x6�&��%���O$$R�*�01�l��>�����#v�#�T���U��x��S����"�S[O�^���M��"���E�����[���5�7f�Wqr�'s��\���t��=�@c+.������&���������y�VK�T���)�N	���
y�����am����&�|>�������Q���X���TcBj���I������G]�C��JPtq��e��Od�Dz�j�1N�2���Z�"v����5�����t�V��V�����W������q�B0�F!C;�r]�+?��6^m�l�2S0�%^�q*��RY���"�����;�
��m�{���j0�tdG�fwgv���w=4Z�o�,������#��G��t�gD6��!�/���L��#n�5rHV
T1���0����:�2�l��B�I��4A�f=�G�m��p2� 2�S���2A&Uk��:0���4HMLa��2W�[O?��F�#�������MP�D��`9�P��Kqm3��\�����[��H���
�R�����?�^��h��u`D��1
����������##;�p��L\��|�Cy�����h��e�8�~��k�5�a�oM���<D#j
!�<���S&��"���Q1M@kspv��"{g@�s�"O��aFV�	�w����t�$�����s����-^�!��&2/�d�:��(
;Q�} ����Y�/l�t�!�"YZ�j��:�k4��f�7��R{���X)�+N�r�{~��b&,P��u
�.�~:��'?�F�Oc8Qh����kqN����"������R@@r;5*���:�����R�����Sc�|c�2K�Q���������?��b#�E�
���������G(j���������!
8��[�I@��~q��|l�VK(��HY6eGN<j������F�3�AH�7�^�y�oi��{�F��2��:�,�	�eY����$Z�r����V�
L��UfV�6��h��n�(~)P��m��U�Y��*�uU��1��H+i�[r�������sb��J�����������#I�	��cr
H��?���$�N_��|�w����O��4D����h*'�N��0]A�Q.!d~�������R�?l�=���+�2�X��$^�hSI���5��5�1��,jN�����X��.�De����o���cl+)^�<�=4��YY��������bO>�Ty#����� 3V+��J|����q_��4^�6#�p���Q������S������,��Z�n��*�����O�Q�R�JV9���]���|����l��F� 4;y<4��I��p������q�Z��	"�X��j�'fI ��Wq�����:Y�@gV�����H+�).*_y�*����(]-��Mz�2)�%<P���@�X-`-��p<@V�R��i���s�5�����W�)�u|l���F���+#i1Z|B�Z�-���tTE��3�>��(&�r�f�����W�m��x���0]";
X��3O���� ���\��d��6�=�
;�����d�����.o��G�P��i�A��7q�/�2�{��9�h����7o�u��������>X:9xA���r��F.]�k�9������uw����u�����������e���Es������%��^�V�	�
yR�	����0�J�N��)�I�����y�2��YiB33[%��@wWG>�miJ��S[�Q��rY�L���X��8*N�[{�	3��~O��?��e��1���n��Q��Q5�	'P����V{\�C4����7���U2>�����3+��u�7�v-�����}/N#6c�*��32�0#���s��s`�J
������j�T_����S��5�XV��
}�qX�� 061"l8�'d[X�8Ev|����W]k�J-'C�
��j$��E����!�YJW��6�N�����vI|�dw/�qUdjCA��P��z������qD�+|8��%l����X���,����s�t}��fk�
�J��4�%���G� ����
��3��W����}�����X3qq�^��NC5�gO�P#����T�����rs�	��ji26����@xhk�X_��w��v��]��lN(��������"N
s�J
��������-���J�A�:����\z�^�z����
�rh��MH�����::�5��;:4�d�H�K?��W���6l�Z�=-���g���M�t2����=���c�������Imu�|��H!k�j5]��s6R����P���l�y;�����wgu��np�h��	��'��"���T?<����&�c%�vG�K��Wa�o&�$��;<V�6���s�+�.���V+��F��x�������py#���hZ[*TQ��� �T��O���/���a�Z7�g�O���Xr'/�u�����,�x#+�h&�0�!�D�o����TP��v{�Q���v�������w�A�f�-��M[��lv1����?��l����>P%���7��9��TN@��Q����D���b���"[h�i�/
������]-��J�[,@0��s���T�2��.�0`�5��8R*J�����������na�+k��U��f����:����z(4E	*�>�
�����u����D�nje��2��.�}��v�{���W];Ac����F�X�R���)�R�n"E`8��^NB���Tih����")�>T��q��$�B����D�d�)��t3�L��o�������mv)�uA���@���2�C���N��M�95��_���I��.��`d��:6��`^_�����-e���|����q1��!
8���`�U���C��M��+HZ��/d�=Mj_"����x��$�������<�����IW��V���R�G�Bn�|���+�����s8�Fy��P?2�����o��A������G��@�"���a�	��UN�X��?�Q�y��S�<&��`�~�F'�u���cMM��
��S��[Q��UK�.P*��2wVq����X9a�/$8Ic��H�uEs�
%��{�;�q0�����yF4�>��B��Zn�������$����o0��{�t��V�������F����X����OOD�����k����87����Q<���4�x�
��[��}����'U�j�5G�:*`8uLet�H����&$h	!�����X`o������r�4><g�&w&�-��D��R������ �=`�j��O�1W�mLX�DW�%�F���u��B���5��A��W��z�w���Tq�8j��������$�8�3z�����W�~�����r�Y�	���?�'
�Z�)��aa�����q �<�(��|�t.����X���7��7�Y\�3���k���p�MW�i�A��M��q&�m��44��O�db�����[�VX;�{V��U�7�������P��d%Z�Z�;��%#a�2&<�����VJn�\7�Mj ����GH*,WO�����8�>��b��������W/?N���f��;��^H�A��	����:������������Pq/��3��g�%]f\:���;v�
h�"���i�����A���X��������3,/�������������h����P����%�R��J�Qo��;���n��@ta�	������I4�2��;�UH��$�3?t�=yK;)�s|�s����)]aX���j����W=�+�	��v9��3��n���+�hZ����J�����@�Z�|M(�����Il��}��h�66���@gd9��sg�-�������GGV�����ls6^k�����Qm#;������|����U�V�T���e-M���J%�;�����,Y��1(��f������i&�I |�o|_j3	h�D 7��B_/���sQ4����q[�i9�(��b1[����<yg�$��`�@|#��5��/U%�����+*E�{�k�R@��\h�����,���i�)�y�A��
9�c�����`���k��dh@[�>�����A�W#c�C�9:��A��d�v{�����<;MU"�>6:��e�'�f�F�j�x[�����ZV
wA��]�_��w&/�.���R������L������b�6�qE9�j�j7���!"�u;�k�������&[
���2Q�X�Vf}]�t�7��yu=�>�������~'�����J.L���L���X�h�h	�{�@���G��ma��W�����0�Z��B�1�8|��"8�"��*�����`����D�����J��7�������Q��v&��`K�v[l�Y��]w��{L���'&��:i<������C]P��Q��H�o�"c�_>��D4�i4����Y����v�*91�X2�S`��V����r�4��
z6H����w��-��y9�)4��Og	j2�"Cpx��:�(�a[�%S
�Z�������8W�c<�`���gG��$3(A/w�k��(a���|n�h�\u��fn����Zw��7��;�4�y$�g�K���)Z*/��@����8��:��!��j������V���A��<��~����Q�SaNd6A2-��[����,�U�9���`t�����-�J������O�6sN��B&#G����W���j{����c��c"�'�+��#��p���f4<#>-���~�g��.�q��U�w�7���YqI�W�����'���e>�U�������N[��SA�J(2@�Zu������w�tMU����8(��h�jY�x�{�L��\��0�p�%�����o�;�}����te�w""���Ik��!��MJ%�1��0�^���F��M�K�����|��W��X����A�������6u���-�d�M���3�&8H��~v��*K��Q�J'���l����x�a�����mj|�8,�RT���<uQg���$J�T�?��cyiJ������l�F6l`�'Y|��AZ���:�%��#���Z���:rA��7��@t��}X��vI�xC�I�����W7%�bW��x�Y�`��8��m��9�E����(%���1O]
NdB
�J��@p}#��s]�T�6-�U�VOo����bf��3���Q9]?+���Y6��\����� ��*�������kY�Vf�T���[.���|��B�)��;�^|�:(LLx��F��T�gY�<���"@�6U���x	R@��B���P@����d����J��&��D��PV��^E��v�%��y�D��� �7���*�@�f����N��ZK#����f��5�|R��%9*�c ���[hO,Gl��r�g4��Y�1[]Q��D�\1�LN��[��[�[jh'<���������-
�UdKv�G>�Q��N�H���V��_E,_s( [a_;	�����Vp�4�S�M9�����)+s����Dw%�L���`U�'��B��dM�*�qKH	h����[]ghw�~5d~xR�O����-@g�������p*V��
*�j�P��,\�`�L�;�jl��a�P�c8����r�]�������O��g�l=�&(����.�#s�������DU1�3P�����?a�0�`_�&$�J�t��HAS$b�L���~F�e�����z@'������w����f����V��'�����V�:Q�B���V�p�g��^��"���8��)�w��q�����hx�L����{�<�������66�gF�����G�UZ7�C�6F����C}���
w)��������r���)�t������6���	�����������\i��PTM!��B��s���^[�e�"*N�B��r!��Cee�*����������p�����(V�M�|J�zJ;(�����
��X�n_�AUK()kk+5�Gij���U��PV�����a�n1��FXQTf}�*���r�B���<�R��i����6a��,HQ��� ��:[[9^X��F�z]F�c�����B�Q�r�����N�u[f���Gu����m��lq[;�a]����rd�1!������;�$�n#�F\�������3�7��!��\�����[8(�A�j��**��S���d=ISQ���Mu�C*Zz�g����:������8~���Z{����>5�*VZ��y>����3�=�(�zG���o���j&D/F8�A(���!������
�Bx�Ky(o+���w���F��/��x�M�mV�rE<e%a��������5�L�����SQ��:k]S��9=�����#��������n���|8��)��h���w�]��W��/��U���	`���<�e�
U����_��s����l���KTH��;���r>\�4���Z���P�`r%&�3M�e�c-����o/�e�t#�p@FJ��� 
��B�y���bO������^�?��TJ�S}��KY69�������	#��K7��(�%Y-�T�$�9��[]9]�m������^>3y��������?����������}���6�.�� L�-k�g)�y�i������������{u��>��'�f������I�����\�C�A���O����
4�BU��x���Y���z�6���@��!�ibe����;'��w�cz��UK�P~���+X�<�R�*��������O/N>��y��}>�]������T;���WD
Vt�FE������=�3<�v���"�u	[��@������mte�#������������vQ��>!Gm�k��gn�4+��:��Pmn����`�����
6��A�3����=��T=��*F����@T�`�[L!T_I`�J��1T.�������j�||���S���&B������9�,��-��� ���'vf�>�:���;1`��f���ZcG(]���5�0<.N
�b��j%f�TQ���������VnXk4�"�������E���
�C�����zPR�?~�V<~�Z�-��u�������*��$�zZ�E��������������`5@���Z\�����[��y��*8�*���J��A���.�V�Hz#������Z�
8T��"W������'���c���R�EqK`�=����Un��
��;NB�9�~?��o�2��*������1��(��0�g�E������������/j���09}�����F��9�.�,�[�&o�a0���	���b�0��?��:�]������Jq����cd����<��EI�������4�1���
�������U
������������Gl��C�P���t*����y�K{���7�:ak.Az�������G#���;�/��,��{�+%@o�H�G<a�Gk[;WqJ��'�1}��0�i�_}�W���������������1���� �R�p����P��B�x��cUE������������w���]�a�3���V�kjV}
v�5�`��w���]J�\,���}��d�����E�4�VT�)UM�Kj�9U�z�~�^���
������,  t���lj����V�~���{����5�Y�����N|��eYuJ������it�����>�i���B�~e�TE�BV�d��H�5�Z;i1n��,9�b,���D=�l)���(�\�U�ur� :!)����Uczk�E�Q���{�*�t���T3�����h��w���������do:�
�;�;H����tS����=���>���x�=I�]�����\2��["��	�����A�7�-�$�@�N_Px|"[+���pz�y���$��7@�����1��o�r�T0�G���,\S����~6�[����A��*7)PH��Yx���;}b��{g�
bR/N��0�L�����0����8� ���R�P��+O
�m�)��:6al�M7l�T;	�{���������w}�\�A��P%��/d*b^�����!}0���i�W�T�_�����x-S^��'�^��(<���w�]�vw��2��w�]V^3��]���.N��KTWpi�s�of�s���tpy�[U����4Pa����K��i��&^��A�����1^������������f&����D�W�����r*+�d������s��*c)W$p,�M�-U����j�m�VD.���Z�@�Cp)[�J�Q����BwX���X.������V��6L�P��W���B<�	��&$6���[	F9�.��@� r!�3��?��#��R�{��B���`����['��'Yj{���\�&
�@��fEUy��i��~������u���C/��w��sY��g�R��A�L��|����xmmk�����t��
w�9RN����I���

���N��"��S�'����N�&�O>>;y����gP��p���n
�I�M2;�U�s1������6^�t�j�f7���a�6���o]i��
�I��Qp_8g��>�����`V>*���A �����D�^-�d,��7��!�$�����GY�#hxh=����V��������%z[������)��^�{�+y�*uMU�����mQ|�O�v^��~��a�3����.�D��={�����*z�]{�t�J��nT��d�&�������&�y���aO�lM;�N�
-����h(;e3�n����k�9������(��v)�r��6Y�2�D��`|k2��?[]!:����\��J�d$���A��1�����)������&�m,L���*��s2����<����4����D��p~����,������N�K��/�J���+C,��)i�������|�2� ��D��b}���Q	��~+K��6xr�5]y�y�4���7���'��t����?D�Z.�$+��+��t0��V�t;�<jE�M��E6)NAvK�h�nz��0��[�����@�
Y=����)�������BT�����0�r���I���(}g{�^p�O�h�>��5���~����#���P}y�i�a��p\�0a3���qL���J���2���������	T/��})�3��C�:0�le�Y��������-`���|������VW�*�!��j4����M�N��@x&ez�z��1�X�@F�[^����w��4w����s���(�^��������Y�IE���LZ?8
k��|�;�������1��|L�um[>����y��c�����|���c���1����g\�91�L�K	���ti(������x4�aT�I��E�$�_����!ZSI�E�;ikm/�Z��Pf�]Y�/�4t`�����Mq�������v��������m?���v�S���Nf�S0w��>t!�z��0�����)b�q��wx�u1�=�	�`LQ�v���r"��?�U�d�b�;nf�F�	g-���H�����w���/�@>��Hxl�����U�t�K{V�i2Xy�hy�Xe����}���M�u�����������h����/��q��Y;Xn��m���"�!3������Xx����g���b�a)��	���K��%e���^����{{�E�jL�
0004-Add-kNN-support-to-btree-v15.patch.gzapplication/x-gzip; name=0004-Add-kNN-support-to-btree-v15.patch.gzDownload
���v]0004-Add-kNN-support-to-btree-v15.patch�}iw7��g�W���i.�"R�e+���&��')I�erx�dQ��X����5��w��Ev:��=OO$U(�������<���v��mv:��������v��������{n{2��{�IK�q��DkW4�����l����0���w���x�/�����u������ga���h6����������W�������������j��j��ln]/������������wbg{�Wq8���-f�p�8�x��[[�z}K���v4��������������+��j�����u�n��Hw���O��N�#�0C�1tF�n0�vF��h;��n_���?M
g���h��^���K��/�*��\g>��C�!:����h�H����d.��p����}t|o�"���P��`�/����q��NS?�+��N,��~�e��W�F���E,���O3@L&���|p���5�EL[������:���[{������H������	iv{�"w{a��<5{b��.?�W����d"��[/�v�)��"������t������k������90�fog�X��[��K�����:��ZOT�G�#���-X��w������
��S1	�"�����oW)f�9�8�������;[b�*^��U!nB�Mg�;u�c/��`��p>v��X��t��z�z�w�p^��5���Q�;���������1����,0��"Z8>4p������`w�����_��������x��;l���,
�����g��x�lIqNa}4�E�N}%=G�E
X��%S��t�8V��&��y�~���>�����`
�A����n�F�$����\g������?k�����L�c��'�KN���3��V���`���0�}E�F1>�?�-�g�tG�����~k~75o���z����M������h�yx����H�l'@�o�[
?o�&7A
�6Xg��`������;�S�k��>���N���M�]*�����Z��N0^b�Y
��`��0[o
���5�n�������9��U`����F�i�qx#/~��_o���n��F D���������o���P��}}�F���%��%���I��5�V��u�Ng<��6�;��D��F�r��(��vwP�� ���c�oN�����?����7��./��({��>JD�c@�;��M>�;��cR��:B�e�@�F� ��p7��J!
y���a��������Fdy��j�a�@^)�5�m�sgJ[��~�������/[�������R���|/�� yE�����J��P~���~�4��z���F���?���oU����?ESz��`& ��#8�S#EB�q��C;�[7���P$��'���B�.�Q�H���[���;��F>�7�F����,1t���7�56�m�#AB�d�O�l�M�����e��K��T(���$VO&�n���8N��hv&��p46�K��$�������v
�!���H-���������������:�xw*�No~:=�]����������8�8�y������?��
A���K����J�}��o�Lh ����p*1_�*'@@�I����A�?�X��]����P��+���el��3��	���������g)�#�	
���d��[�������;��8��hE�	�����x�M��Nr���"�D��
�����Nc��uE �x��L��0"�h��l�^i��l"��7��w�;� ���M�������|�`��+�!��v�5P��G��X�
!�&��G'�k���7�����������zTN�X	�f!���U3g����q��g��|��9��������bz�������^L&�'��c��:b����qa����@�.���D	��`��9���c��������&���H-L
W*��;�_D�3d`qoI�i�#��Z��`$��������%�YS�D=�h	��w���J������;�:����!2[=>E�D���#������F�3]S�V���I�C��9���lU�����i@>d�0to� ��6k��A�DF<�h�`6����?xp��_�}���9T��*�8A/�+2_�\�X'�4��`S��s���L���������{�gc_"�TX'}��/}�&\z���o���$yi���wWo������c�0t��oU�o$>��x��L�!`�3��3��<"r�?�_��P��,$���;m�]F|T#I�Z��;��&�^���v��v���uI�n��
��uH�������?��T<G��h����w��^E�Wd�.<p ����K�������0�\�t��&Sw��g|I�#C�^� ��4v'�
�-������ S���_���������-��-Q:�����b:t�b����X�~i�������O1�i�G�������f��ksP~���D�?������:�o�hl�."����_�W\sR^��?���G��IA@��_i�$PR?�3��,Y[G1} D�� ��a�$$6�j�i���
�C�}w^��n��������������
C�w�pz��{�L���7b�����-��PK���t���3�dY������kI(��a,�����G��Hy�p�a��L��h���������be��<�C������'��G����#ixp��%M�6S^�`��Sn~�,���Z�v��6c�i��1o�:|�����"%��rt�o.gN��7�l>�P��AH��tn���(��-�S(�=����G��_*�G�����	M.'��=F ^4����5�Y�6�#P���
�Pr���|zuuyUl��0�G�dz+h�����f�D�[=���'��a���<�G�L?x��������H�~'q�h�����������@��Z8-�!������������'x�-@�6�	[A����e��xAr�U�za����H�����M���(bf�2#~pb�� |���3N�A���7u�rN�� �!��{�*m<#��0:�~D�e���~��#<��rgh�-?���C���#�����:��"����=��"`�#��l#P��_�f������@!R��`H��X����b��6�("�/}�����c���J����]�"&Mh��U�w�h�hw{l�x9"���sf�y8�{��d��5#c@���I�G��� ��\��H%d��4�@�h�;!����t�����(F�N}�,s���������a
���fi�I[�������s������h4D����*���I�6�4.P9H�$F}NC���2���(N8��!�	�0��g�y�g���Zd�^$^�)���|���<��>��X�����������2[>�� d�$�E$g}�,��K�,��G��7@(@��C�P�H�fO�3j���X��L��SO��/e��^���~��j>����	%K?�����r�%�Nq�!;F�M�Gx�3Sl�������H1Cz<u����������k/�>,6m�J��n@�9z�#j��Y�]����p�6qd����`���-�i�05E�F���i4�����DV������m�{o$>��x��8���D"�(��	�I$IV7�$��uj����$���l���=�����~{�`�X
 9a��
��V����%���k���D�p5�N��^����H��Y.>"���D'��&}`C���NdGf[�Ng��<�e��\7�nO�����yD�"��Is�� �$`��>?=��;L�X\-���>�/ f������D��v]�Z�A^�v-��Q#z�BC�����t���#Y�q���+
�]���f����r0�y����)u#���h"\��;i�$!f��t�fs�A��#�#��g���p�<��&I�9u�S��R��5N�F�[RC�?F+ZQuS����0�<cQ����������������&\
#3q���aB
8�����}v�z�m86�������>�]@#���l|V������2D�x�H���C��1��Z����wQ�����8D�L~[��![��HV���6��H�iw�E��<>D�T��X��*|r�Py�@�M�]��P�Q��~��������,�B�t[]B�^���*���B�e���xvZ��J��
munsR$�[_U��xt],|��<{k����GK��#4���2@�����Q��p���I7��\p/or�=��6W������Vqu� W�	|�}T�b�M����!J��*�W<N>���W������3��p�SCi�9�p&�?��8��?�H������6�4��
oK�����L?Kw%G!?��'�_K�~��A�#(o�'-{��]r��������M��AB�����6�b/'`�L�>�I��������-���I���o�%^��2d�$K�����d_+r��4}�z�{����\9���#������50�s`�g�����_���8]��'����@H�gx8qr�����O�#�w��m���O��7����������}z��5O�(c�b��������}�2B�E��nSO
��GE~dG�/@x���y�@�x�X�������@�~��T��j�	�;����v���4�cl4��.\����E��vB�� J�����[/b��gqK������ rA'��}���%D���t��Ur�{�7S�	�>jA�=v�%�Un�H��r��`	{��3l��y[O��S��1g?'�R�����`�������2���Vs��P1I+�\kF�Q\�-,!���IO�<t�����[$�/
�����d��������z4�6��'������N)���S������@G��l����u�������g�0��dlgD�F&2�����`%����>�>������j�e�����d�=�>��oG0Ktk�9iCX�A�1�G��h��������"��t!��\|��[�~
d6��/�{yH�2�m���q���3�����&����A�
	K�d�%���Dc����"�o�W"�bm�^( il�4*�@g'�������T)���Gw���h7�������2>���Il����mSg#%"UVP����2�x��+����Y���,�O�'�Xkr�wI�`Ki�m�I`��-�fh��"����n����%�#3r���:������4T|E
��$D������A��#5,7�5+k7�M��6��^�W�ie��p3�y��fJ^5B����^&�%;*����p�)oN�^��~�2��r�	����qaMEH'�p@at��b�����r�#w��Ib1��8�Ze��)I�~��J�E���E�O@{�5�2�d?_�N�f7�&kK&��'!��0��U����J���?/b�|����]�F�����o����(��� 2�5&���t�(3]>�)�W��B
,�)\��K:�����y�}:���--N�#b����t��,�'ww�8�o7s4��`�|r��[���H�x3���V�'������U�+JM�Q��h�5Z����hf�}�7\�����,�l��Y>V��'���"A����a��@�kU��eR�dU��vvw�N��6�i�:�V�mn�F�G\#�J���T��?�(Jl�����l]�]
 9���[���i�������y���5�5dxg\����d�rU���O�P��>��"d��	���?8���w'q*�T6.&0T���;�����m�e�����>�J���%<j���X{����\\�9IU>)'�<
��1E�/�$�
z�W��dj��������|2Fi4�k�3������0hL�=�~��9R}�e���U	�S��iE�\�+�����4�!���g� ��$�)�G�Tr���=��E�l�b��M3g-P�f����T�>u@�B�'b'A�qK�8��dI��&�U�K�R:j������yi+F�S�ETa	�����46���\^�(�������D! ��@�7M�xv%�B�#QY\:*/0���8i�T���&�W�``�4a��K���o�-g��4p��/9i~��`���0���<��9/���B}�~[��� c-#qN�T@�/M�2y�K%�����7Z>���e�VM�T���^�,�lX6��Ql��8F��p~��
n�1�����=������/��@>�`�s���0�����O��E=����0Z�j��������>|���	EiF�]��#56����P{K�%�A�b�n�Y�7���uD�!�!�����c���oX��J!�����$��)�Uqe�KB+������&"�����n ���n.�	x8f�I.��p[���Z�KC1��_m<�M��x�/;Lc����f��S@�ZK�`��8H�Xr�R��,��w��=��0e���J���5��?�����,Y�(94���d{@2�<sS_����D��������8c6�c��0l� <��_\�p��_F��"�1��>�<�l�/�u����'	��h��f�'��V�"�I���Vna�J���]��.�3/B���rCnsvp3�u�TS�!�Nl��L�)��5P�?G�eX�����m6Z�A#��d>K0'o�=,����46�������3,
�5?�����K��:��M��:`r�d��+
&��1(O�h@EL�F�q���H�`��A �VVd�����8��PS�AWK�%��t?b����5%V�������	�U����G�4:���;	���l��G�,�@gYG5�B����R9�g�2.�����J�����p.m��
m_�l9����J`�
��1���d�\~(r}�?��>�S�1����
�#$S��$�L�%�X�-Jb,�H�U��T�X�N/7�&���xe�+�)���b��i��,o������|6e��T�q�D��
Ec��	{&�<�lb�'���K�$����2�=9(N����I��H�
�>yd,�:����M	�������R	���%���J��D�Y&�)*M31��x�,�b��6�����^���9���/��n`>�Z[Y��Mgs�8e��Sz���4>*��g8t'X�EJ/�3��(��Y0�3`��	�3�I�}�t�We2w��z������f��)�4�y��%��iTVI=J�ZG�!(������X��`m���������Q����;CF�CM��b���*���}�51���5�Jvq'�0��!<�������1�c<��}H�x�H���y�U���{n NEVD/>�y��R�����z"��V��^H�d�+����c���$k��`�fQy�c2���:t5�%����?@��}�c�6sDH9����M<��2j�}^��c�I�}q��Q������7��x����h�������dL�YU��J����{dV_����l���IiV�D������o\i���#C���!����^X�*��������KTIKBs"!��n�W�L*�P�H*��=�j�m1J�b��������C���c�S��^��|"Ir�L8��^L!#�����2��r�U������Dk�S��h�E�WT�n��2cA�%�k)&���7�gU��� �C�/h�L��(5E�#r�7�	e�[�
I9q�x|7�a�:�Nbh��2k��d�6R���������	�:Y|������-S�������X��&�H'�i�!�m�����l��=K �J)�d�b-$<�QN���68M{$�18M����$�I��8���;��U6x#���[r���Rc�A���9Z��/����R�OE�-9c��,�d9�Q��+��>Ko��u���6����$X��/��?�$S��3,
/s�r�hW���`B�/�r�3�9��U�B*s:����t��"�O�9�
&�M$�_��b`D�J�Q�gR�LO�y�x��T���0��Y�&=[�U����H��a��_	�7�i�kqtFn`?�j��]\��.�,����4���M2DX�8��|��M�M<U�`�*`(������W��H^w�����h!"��J����X�����d��N�/u���H�
��	+9�%��w
z�G�kx��n�$���Ew�d��]-|��y>��K�������:U����S��h1��Z����,psu��������+�����g��v�(��|��g����l��� ]��o����!F����~t��Nkg���S���9�y����#Cq)�j������E�:����t�Fy�A).��;_(����� �R��N�)[XO��k�������Q�KR���q���E���I��}.C��H���y�8���#��������n��~"`�����3�I�?�l�#��0���uSL�c���5�0�t/g4�;$ka��[F���U������<������kB�P����y���N�8DX\�o	�:f�.^��d+������t�L�z�l�R�bh���1��)�m;�0t�$#k7a8����#|�	���Y�
f@���1����Q��7.S)����J��� ����Q��,���py�w�~��5[�����[��Q�Z���^�c��]�^(�X�T��0�zlh�oQ�V�O����n(���@�|��Obd�@�l�����#/x�{���+i1��J+�Z�4�-�j�;���*#�nY�M����G�Jf�ZNls����������
*�E4=�2j�z�����]nQ���<���[6�����N��#�����}���~=�F���""��j�g�����t����*2��:�]�����Y�G�3/���O��@���k��=���E[�-Q$BP�����J������'�w�-����7����/�{kk��:�/��h��I&�A�8e���<��"���SU�_�Q��b8dbP+��,]Y{+�Qw1�� ��_��F�~.����Rjr��� ���!��3&@)�@��]yQ#�q�!��|�l#�W>W��Qc�]�ce��������i��7�
�9D�h���zC�������R�����2�<uq_H��a��-��%�� �o��z����d�Vi�E�/q�X���f'PU#���7t�v����v�gd�>!�0/��,&��v*��UZ���gZ���#��5�d�o7�:�2�WPb��r-��A#���`��CWS�36|L,Tl�R��)|���Mm9c���������PX����>�B�5��'I����$K�W!��.Nl�v�����Y�����
a��=I�Ze�����o���������G�A��5��	=A��+S
��-�������(2�o��{;
,���\������Zc�dN��������������w�S�n�vk����HnY��j>��k��7*��'c�k=n�����$�j,p���������W��m�6�Ua��w��YC�pU]�-�G��K������������8�z��Q`���7v�B��}�]�s@=�+3��)���1m�5���Y�t�^���k�������/���=�]G!!��m�S���6r3����h�����hd3U��������vw�h���v��3���6(F�\��jJ�@�d�&�m�W��m�XCH�����{�r��{�t��v{v;p��,�uN����6��[�uJ��z0�x���~s�����R��
��Z���Glt�^^�������K����2W�_�����8���m�No�g�$,M|��O�~�������,y������������]�	�I�9^L��s����8�gOVZ18���^��<|��z-"0�����cp��ML{�U�&&��z� p�8J��I*G��b��QE�������'<0�@`�.F��jd����V1
 ��k�v0	��?�b�1�
�������s��Q���%Z
O��5�	Z���z���Ao�����Rh"^N?��`��^BK�UY�����T���N�/4�}�P_��n�]~�#"��H@@_W.�n4L����P.=Q����n1}��v�`�eO��,F�����U*!��0�3y/V�����j��/\$�+5sC��T���^!.���A8����/�.��A(���+;i���^�(��.N2�M�x�F4�l��u�nJC���=�v6L3�$oz,�MV��lR�s1gj�i��1I�n���M�O�eU`TcJf�����1�m�oi��b�0�h��������Q0�3�(���������h�
i����b��I]��nE�&��/��7Q��1E�M.>�tG�����F���T�"�TP�����\$T@��-�^]]^���S:�^E�o���B5���������s9�d��4�Fu��(j�jK5�F���S6Wdb��4��lm8 �G/ R��1 �c���������-�C0���B�k��B�k��5��.�����2ov�'D�yXT|@>_�O ^�C0��I:,�'{YJd��&F2��4�Y�W��D#�	��pHCs:�VIM_�nT}���3*�K-���%�![���/��8-��3��QT�}�K�Sa��Qi��Q
7�+�F��y(��U}�zImW�L����p'��P�DVL�`�������������c2C5��d�9�0
3 *��dhK=�uu��R�u�$�c��4���aQ�at������9!
��k�!�|$��Voh�"�.J�K��MJ��0�C��q�\�Ez���kB�S����n�����km-��Q�\1��/��0�����m%_R|	������
���rd��jM 	���\^
%���sD�k��n��Y���:���d�Nn{��������7�����0�U���P	?H+[K���i#��}������V�[��h%��r����!BNX���' F�-l+�R%]��tA%���3��2����E�K\��o��|A1���v
����#�������Et�Di�
�2@��lX��%E8���C����g����w���\��d�V���a&%�����BV�L��[jC�i�=5�W+ �B���g���V�U�3+��[U��������X��y$
���"o������:0���G�\�#�T�*������x��yR���'_����m ��`\�o��'�I*���;nn�F��W��
'���I@�%���l82��gg\Y,���(�IH2_�'-1���\�f�T���Q�k�������������M�#�s�SR�e�m����Uid����eW�GQ�)�?D�\V�~j
���>�Ie�f_��fY��7P&�=a��J})�������/S�ier��C%ft�a��WsT�iWU����Fv������>����2\[z����������9=D>���&67Z	���i>�M[=�]���1;U����O�6���_q9���D��kU����O'���f�����	0w����{���$�&.�����������������`�~����m�DU�IV8k��B��S8Z������-]o�j�}���������.]*���7D�Kb����F>Ln����C���x���ez�a���~���\I�2�RM]��U����K����^�5U�Q����f���T�Q.e�1jJ�4����9��Dr5h�,�!���;�o#�n��*<����.)
�\,���_��B��������y}l�`�1g�s��9��e���u��@�����8 yxMZ��g���y�`:��	��>I����<����H���"���}���j�3�%#��&�yI�Kz���_$=�Ky����k�$�~�&��A�qeYx�!r�23�	�Q�0���K���&GB�z�:�BY%u�H�Y�������b����E�U/�'���;�%���O�E�L��f��9���nu���pR���:�;�/f��Z��`�*�l2[g��z�J2P0�Y�t���&�,Q����:�����V���83�ud����As���C,d�������� ��QX��@�i]�K��j�%'M�c6�B��-tz#��XQ����*RKYx���$���z~�e���2�~�I�����F�F�Q$����
�	IT�#U6������+����v1���	%�'�d�m�f�4����L�]�F����uoW�t��?�c���p��?����6��k�]���=�5�����~C�����4d^cI����3�]�.F�V[�N�H�\[����<.$@[���1N����!L�k��2"���a3�Z����' V��d�L��rT(�r�vs�O�r���5}�Ty,c�'zc63���������S����D��������������������O�X!��T,��w�(�dg�Q06|�@DX�2����ks<���+e��SF��p����T�?�S�N?e _�
XqX�����=*�Zv��O��d�P���2s����_��}�L���8[���r�����~�<3]/��f���'&�'u����
O?yQ��_~���:�9}��=���o@�����|��ZY�N���-�����-U*S	���Y�+Ie��yVTe���������GQ��A�]<�pTw����������������T�@����tK�������m�����TLw�S��(L����	����(cQ<�����Z$W��>,��sy/\�|T���f�t��9�){�q_�Q�����s�G��jK��U��
��w���oW��/���G3@IHT�H�����3�h��E���ENHm>� .Ao�J�NN��	F�j��=S���X^���.�*����]�1Q�Y�P�1_I$z����|���c��:U��+�T���J�.'�jTIQ��c�_�)"�����\�bD��eHJ����0gX��y�����5�4c��Wz���zS8�*�E�p+�[{��@�g��k\�T�5���:�W�oc�?�����OM$���/@���
�8�����G���e�>r��[b���6fj�( �"r_q�0��	8l���2�����0��Z���\e6�s�h�:9M^I(5�QT���9V�1u����&�������qj��/���{�`
�@�Bb�e��,a�3��������b��P��+�����R*��	0�8�g�S�&)���<��D�%l��X��<]T�q�L^���X�6����*���Ixpd��)�Z��X������z��Xc����[Xz^:!�h����Db�c�^�D�����k��������Cf�J����*s�_��Z4q��?*R(4�����$���E5�G���Y�����M�j�����S���~��Du����`�/J�:/;�/�r���pA:�'����4X�W�����A�r��7��h����Puvqr�����bp��������	x���G?���O����,��!Z��8$It���O�!9�T�'V09
����vq)�!O�������*�u��X�_�JV�y�zeQ��rS~i��ET!����|`�|�4�>gK��*+K�������&%�5���_���,�:���}C���=C�F�J�r%m=ge+[Kghq,/�Eii���t��!�����uf|�r<�t@�{2�R�P�M������Y����oNk��m�
����6���fV>�H�u+�P<�
�&��] �D���w�7���opl�@�~���f�a�6���\�h��F�!����9dT����&X
�n����xn��='����}^-��<�Lnar��I�+���)N�)�Wm���/��i���D�Q/�:�d�3C��Ec��Q�s��8�	\�`"����-mb'I�-��l��
�0(�<^���q$*,a[��|��I\��^�E�n\�\����&�mz�97�&�$���E�1���<�M3���e�d��9e6!�������H������Y��O:Te�B�<#�7{+z��[	=�����xDg���U$<-J/^���~��^^'�x-�r��$q
��3�=Y	3����\�~�e������n���������m������\U�a]��-��swB�
H�`Nw�,"��dzLT�f�9�,C!�)����n��*�l�__�bZ�j)E�B'����y�
����"IKY�����s���~��h�t�{�qg�3oP��s�RFk����s�m�<��n$U9t�b�2G�7=�,�p����Qnm���6��-���wvU����	���n����RR�k7��G�\n��g����M{�4\�Q����m�;�N0�,~�-���Q���
Y=�MA���������TC�u�5(h(Q$���E�����~�"@���k��|e���t
hK>�%6\1��A��R�����8	q�d\i�O�#�������3���j��Bt)� ��A^`[q
; �s����]P)�]��T�\ew>'9D�����������g'����<=Fw����34-����{`����������Il�>��F)~�ndF���B�l���9�-�����7�Ci�P���5h)QUOD��������(�����]t�F����r	����-�����1]S7���oZ�b�6��eE3w�M�������=W�L�5g
r�s��������}m�P�`���k4$����v�; ��t�)�cIU��e�Dr^���������$Jk]t(I8���/D2l��ll��m6�b���K�Xkz������2T"����u��B{A��c2�w�"�V�sv���������BMj����
@�!��V�����gj��&�y�U����G�����P���5�%�0���6��HLT��������� >��[M�1�n^P��\������d�0���"9��{��vlT����:RT�R��y����5�����-\5@fz��H�r����WMv�u�,�r��%��������+�t����_��g�)�n�
�I�����#�a����v�f�������M���z�E�$�CQ�;oa�#�hu����T�����Bn{@��c<��IjN��	N�
F2ZWq��L�"#�y��a�q�8l��;�����:�[2��9������t�L<�G��
���2�o�C��fkS��7�o7�E`���b����V��pQr{�l/�.j�E���;�6����9�6�[��������
S��K���H���{�{�ST�l���qtC�4�w�f����}���g�=�t5�
���67��+�~�&�T��6�<��7q��C�NjF����x�+�&K�r.�2^�}�i���@���2��E_�&���ygJ��#[\t�����o7_�ClesU$Uy�NG���k�I��9C_�
T�+�D�941�*�f�g�������VI0;W�����qL�%�S��=�����J�����v�g/99�/y�N���vR.�,n���E���J�������r�,K�#�1^U�`��,y�a�C���]2�������������K��K�P#�b�������4o�hT ?��g�)kY^V;Y^�i�[>�����8gt���a�en+��PCF���"�
���t.D�����^�-a�?�,5�s����k���F������@J�����=��G4���E��3�2���[�2��$=go7q�$�UA�/�1@:dg�0V��w��J&�1XL�$�����{�a����vZ�F�3����Ik�])#J�F�������m�>����Db�.�tJ;9�RMJ�.�,I��M�Dm��ZP�������D���[�j� ���1����R?�H��~ ����������g�xL��3��<�u�����s0CQ���t������z��e��b�:,�>���iFR�����
dNJ��&�%��z�Is�������;�������Y4^w����A(��K>�]]��}|uzxs*.?�^�\^������0z�v��g����oN�V5�F6�)�p�''"���)�|���v���q��aD)w���%Rv��y�yNvrr���a�������s�
{yS�&3���@����X��o�[u5i��t�^�
yy}�:���<�f���O�������U]5���9��>�/��s.�so�#�3g<r�<1�4��b�L�>9n���@OM��]*��;��g����c���Q��w���,i]�u%F����E?���\]~(��^��-��}���������l���d�;�j�����Y��fn���5��-k�� Z$�f�82��Q�����.�mNp��|
��?�SG���.������!a��-���[?q����F	�R�LL_�|��K��<�7 p
��5e���&�)����*8�68V�'By�,����!x�'<�6T�y�?[�f�h��d����qVHp�Co���K����D��R[�p��i���q�>��	��-���L������L������@�!O�~�+
AOF:`S
A��������_(�<Y.��#��<����N����U8=�#
0005-Add-btree-distance-operators-v15.patch.gzapplication/x-gzip; name=0005-Add-btree-distance-operators-v15.patch.gzDownload
���v]0005-Add-btree-distance-operators-v15.patch�}�w9��������y8�������#
�;������y�O-��������0����+��Ue;�������tw�+�J����.V]��XV5l��6O�4C�l[�4+���Bo�%zOVH1�,��H�e��4s���~���i���&���,����=_�Q|�&�jJ�����NLN���l���f�(6R��>Q-t$k�|�~���x�	��w�W/~F���?�����	A�,���GP�"k'����d29@~�Gk�������#�D���+B
�vtt�h��~#K�x���������H^��*wU���z%E�h�����`:q�	�+���lY����i���V��t���Z��N<[��bU�	������gKo��	H"v�����z�,��L.�W�U��6�*L���%���W�Yj�����A��nj<C*j�����VH��:1��	5������!���`mH�&��=u���xCA���2��y���tT�����J��jI-���j�ZM�fU�)B����3O�R���([�K���Y"��*���~4��M�ZO#g9���D4�)n�
��GO����W��Rs�
f��\��5H��~{��9Wt�R�*����J���J���]�T�)wb��,��N/�:U����t�Z�y��p��tC�(�U�BW������y��J�R�:L���oM���z�������f��:XF�lN"��8�k�CH���f����Y��FG�cd"��	�=9<8�gA�&��Y������m/;��wp������	�$�R��t��`L�
�iD����Wg/���	��cp��bw���>{q��W�o�H�q�.._�]�����<���3��<����:�{�^��9�8@O�a,�^;�9D�������O73����#��e��7#�C����D���&1�GB�
�w6Ubm�B+��?���-A+�i�=1�`������Yg�(��Dr	`�6���#h���lO��',�-��D��*\���v��5m$(�Y���'���^����~��9��x�n�'�p���
��z�'��������Y"p�w
C
���,�5�hf���,�A�'��ix]:���e��uH5YPt�����Oc����qQ1�
����{��u�dngq��]��"�aKR��@��b�F��u6��['uq�L=�5���?$1<z��E�n�� ����3'����>�E���7g��z;r�����h��'H>DO�����?)�����x���g��]��"�����a`��1VU����O)F�~���������������3������_.�N_���y�f��������K'�,�h�i��:����������"���
�x�24���b�P�.|u~�<�h��6�42�4�H��{>��;F��>D_���5e94}��
����=�w��PG=:�����;��z��>�7_\�<�����������N_�r6���jz�jzy������
�*��z�����hMc������5����F��F����w�If'�t�'��]�	\El��\��	�-�I�����������E�&�F��b6k���/O��N_^��e���S�����Q6��W�D.{=g���(z|��������5d&�6K�g/��\%�'��q�r������Pk���U��O$>_�p�����Z�f����/�yf�,���i�h��V��#���Qy�c
e����+TZ���U;���XM��=r�dd���,Av�����z�*��~�T�#(�	��	8���]�5�}\�m=�>KpW����N��K<IR�}mn���j�������(����5Q��Ae����d�?O�$�w�(�}!a0JKSMx���������7���Cr�D��LpE���haIg������y�4�jRb���)m
z���K���Q�\K�X����B���>c�gi�SX�=��f�k���}��������5��j����T��A�k�!�}�&��{t1��\}A%i'��h+6�E�4?Xvhm�w��k��ia�e�87��B�Z���N���C��z7~��������E�Q��������4��509~�9���DQ���F$�n�7�z��U���I����c�#[Pu�(�>1F���1$:hI>��1��D1��@qN���0������Kr��i�������Y����#��S:u��$_��8��Zb|�H_����f36��V��J�q��:a<�����3�Os����3��b���=k� �����.)OrH�tt���X�d���:�kb[$�L[�J"S6c+���W���O3?����O�������l|�j[��q��d�`�x��mQJ �%����l^��WI=>9�E3E|�^|�F|�"�V#�V_������U����~�X|�(�X��<�����i�A�.��C'���������b��c�v�0��������/~�r|J�����1��R��3M��<�V����,�x;]n�C��G�6sI�:[�&K��B�Y�s�x�,��Nf��`�<Dm2���t)�+FU�5�H� p�"����a6S1�f3��f3��3�B6tB����hB�������2c�����>�������G�h�[���B��k�n�
�7�e���nu:��V�SI���T���<���t��[�K�iA�P���Px�<���`�������QW�T�����ZhJ�qfV��06�7�W�4x3k'o����L,+38'_
g�F����*����u�[gR�D�
l*i����h�������K.��v#(w-n�[v��e�et����4�v��u�]x������,��|�2����]��@q]���v�\����vtD/|i�>B��������G�b��!�-A;]����������$R��n�T)�B��$=a���j.�PX2�H~����%��f�PX��
�m�-�$��-C	��lS���0-M#�����Z�P�pT��a�9Dv�����o��8s�:5�O�v����N��am��m���/M��:�8��O�������U�D��y�p�6Q�2M�J�	s���Z�y��i�T��I,:��3����6�y�B����qm)#���$M���Gr�_�w9����|�wD9��}�����C�UF�����]_}A-��-a[�;]���&IZ�^���Ou��i�����%]
�,���P5���Hl��������t��w�E
uQ1u��-R&�LQ;����)�>��5L����Y�97�CZ�'�u�7��,g������p���^f�#?��sr��J�|M��:Kf�8�`�,.+�B�!��o��/,�%���6����y\^��\U��i��7���9�~O�q�,j�RU�V��ys�/eM����-V���^���5���k�U����#Ta������ ��3[�����Y��-���I����=�"�}UM�u�K�%�Q[3���v��G�]P��������
���Y��0�?C�����d�4\E��dN���mf@4s}4���i�zv}S-cQL'g�y�zr���gT{�1�s��� �M���<B��	��!X���f�
#����?���o�=����{J%]w@�:�"�e�l/b��;����&�h�����0��o#�F,��=��[{�����U�Veo
���md������){��f�%����D��������b���������2z��A������[��h�oy���F{|W�=�����W�[\��[�����������������"z+��2�������"z��Eo����������]�~&}��T1�[�����n��wIn��+�o��e�7�>P�����x�g�r��%�=D|��#>��p���p�I��;��l�Fu�q��OG������:mam�A[�(�]f)���[]�"��I��f��Xo+�>����b]�t\���rI�=���6��q�o�m���TDLv�/�Z���Bk�u���Dl#���N?����QlL0����7��&�iE&n���	�FkU"��	�P������[5�+�4�o^V�����u�j�:l�O
���A�C��h��g��Y�]5a��`�����	{��
|h�n�ual��,�;�����/��M��LD~�QF�g[�	��(�64������^�|�xW�1=��K������A�q�2��*��U�*0�E#:��d����0H9�/{Q]��}��{Q����
�v*����J�A�6jR�ww�H��������!
���d5n��Q�U�2��=�J�����=b}��;+�x���6��^5�C,R��b���}��H����������
oiw,;D����h��������z�������>���S F�]�� a��������M~�A�:F�n��t���4���H���j����M��������|sWs�f�����qr�i3�d��a�M����K��+#}�XU�V�Az)��!��KAf���:MYdGq�j�
>+��k��ZfZa�~��*������a��^��s|����-�o9�����=��M�I��=2�1���E
��(�V<�L�6������Y����I�<�T:���i�"T���mX�����bI��;�KT�zF��^�sJ%H�f�90vU����.���x
��L�oz�@��7�0Kw����}V��N�+�����Y���?4�
���j���O"��>wI���%�qsP��Wg	8\'K^��'�)��H��n(���u+�����E����]�v2�g�{�q��	4�;$7�������	��*wH��L��D��H`���N��L�zWf{�J���Ye��{E��]XU�����I������u�bu����"��mWJ��m���wJl��f{4�mP����&b[�V�+b�@��4
(�Z�[���T�,��R'��+R�%�������-m-�P�^��N�yWT��V��$��Q���JKa�R���Sj�%�����������aM���qTg��EE�Y�f&����l�������b�3����
�i5�(P+����!�(�S�QOg6c8a9U_�~1���-�n��� �n��)T����4A��(�E�C��66G$�����yB��)T����Y4���Ce���-2���]�����B�%�OsgqswF�]�������B����%�k���D-����%Q�-��K���)�.B��<����~��@�2T���b�>�oB�������,r��$v��P��,|gq�P2�{�o��pgA�`b�[v-�e�H��r�(dS(g��+�1�|����E�<m�J\�J\����L���DV�V���^uJ!�'bX���$)��[����&��������A��L�ct����#��q���g���N�aHY�k�/��5;���
��5-�*���ji���$/]���X�JS�2[��!��KU�h	J��fYS�f}��c��1n��$.w�[:�;�Z;�Z;�Pn�;�Z:6�����9q~�Kd�RA&[��B;�� L�����uD
k�n��y�g^�������:
V
-b(���(X(���(�����P�\�aQ���"��Og�(���a��u,���l�VDItQ@��^��ByV�	���`
Z%S�4�m��P���Lo�k��
V�.��58����m����rX��`��:����2�:���������fl[F �����j�H��x!��#��v���y,P[�������-�����[F^�����\����T�6�|n>����[��j��hp����]���jQ3���g���v�}�kc���^�D[�M�~+'i���@?������|���<�����������EY�f�{�MS�n���t��l��z��GM���j��i�8�0�7���:

��y�e��C���u�m�op�{��2���~����O������J6^-N��r��|���dy�8���[�g�5�2|��I?������LvN=��u���������:.�E�(^o��_����3z����������Z��,�N>R��/���y��Fb��}�����c/�N�����%(A��1�����F�?��n{d�I�Ex��t��Q������<�������p�m{Km�+���kc���Z���{��$>[��MvgD�^�O�T���i��/�Sp`���y�V?��5_��Ne�	������D���@^�X9�Y.��p�[��p]����G!�Q[5zj}�w�����`�,�~eP�)�m,b���d4�UfCCcu�ZY>@�'B~S�JN�1��NDoV%��R���?��9�����E�L���gh�f31`x�&�kE����b�;��=���Kl��U���3TI�}[V�kkf��g��-������Mt������	5�u��s���|M��:�Q����a� ��;9P*�p��5sm�P�L;�9x��#(�#?��eT��n����^(Fj�Y�)?�E��A�Z�T���:���b��vh�E������[ ���ei���DG�����d���j����%WX/���������)��,��'��)+��>��qJ�\BH���2*8���
*�U�=Jo�&���Y��r����z���+b�*|i4�@t���N���%C6,��}�s\I���9*����
Ys}�B�_)nb~�6\
\����a9�H��H����b��9�'����B�lH�D��?��V���*D��6uP�*>���sp4�4n;}����z���G_������1
�d��er��*��DV'���^&
=D�./��@zz��k��P�H?B�����0�L5���r���m�To"�������r��i���X��No�M7q�&�d����t�N��A���kA�
�9Rt�?E�t��G�y�[�#��U;����?�x+�fiw~JD>����9�sn�H��b����.��
O�d<��0����C�A��������������A���JZWUV!B�(VCi�acmX
0=���V�	�e�i
r����Zg�S8aC�}��j�&'\a`��	W��U�MO�ZAT!�@Qxc |J�.F�x
K�h��
t���P4Y�x�/�F�9X:�ax��)��:�d��
l_qU����`_�Z�oP����fxK�)�}$c����W�/N�0w0+'��jX7L�LT��R�����MR�����=����
B����2Z5sdP3��~X�������T*�Q�op��p
K�n(�fK
��Mx���pK2r���T�o�w#�Dm�o�6�t2��$Y�������1d!y$������n��s�`�Tt��-��U��L	Y������&&�
��CA�H����s"����=���!��rM��Mj�i�N%�ON�&j�l�F�KN:|�%�����r����'I��[��y���r������27���p��f�������1�x����x
_s��R���9����7*<&=U�B���z(���0����6�����+�_I�K�j���[�����X��V[��O�����������n�WJ��H�N��#����f����'�Q�MY��U#Y�'������Y���h{�+
j��D�u�}/����7J��������>�]�{�I<S�E�n�\����:{�$�hv���1���1�6	++���������`V��8�MC��a~��#�C6���y���Fg>�K��y�j������?�]�Q=y�����NL|M�����
�^�����Z�Mu=����#�]�-S��+�wz�:�],8�N�Zhk$���6����t�^�^o^��������^�^�5��e����D����'�~xr��cf���xI�E��]�wL��!��R35��h�������<Y!���-yp���=�{2���X���;�M����j������57��{�b����$]Y>��'�bS�R��'�b0�������_O��<�$��G�l�V�����%�<q����~�����1���aM;oR�I3D�37����*�������b���A�c����#���Z��k����e���y��9w[H�������u|�AG������&��e��l1[V�Bp��4@��D�)�3t�2�a��
 ��l���<,�����_

W�-A��F��TM?��B����5�kK�ru�>�V��K�op�t>���������=����7�+����{�u/��I�h�7}P�L�B�F^C��V:���d������2�d,+ju����][w��3\e� �7)0���,w�y�U���:��u�H���*=������+�w~F ��%�d���T}��
*H�k��2��]���u��������B���NbI���~E��Y=���l6.�?�Y.Xi�V)�1Q[A�C�])6�~6A=���
���6z{��z��v
0R�R�}E�rRaE(�q�( ��;��!dn�o��N����y�	��8�:�G@-^\��^����7�������J"�^�B#P��'��������+
v�{���/g�E��=��k�Tj���8�m�.��g���E
5o��JsH��Reg&���Lt���M�3S�G�N�pW�GC�t
*T�E3\�i�?����n�Z����
�g���,�D*i����|�����>Mz���
�+*�p���-�I����=���@�tM�l����
-m�Od)��_%C�����7k�4��)����,`O�C���4�����z<;��@��J����k��+}��*����l-����Au.��#�w�mH���EQ� �"�Z�@�e�?, �j�GU�$?�<�)�)�a�F��r<�B)�JP�&�bt/����������el���z9�Y�������k�'�V�
�$U�uL� ����h�����92:�zC/��Sf��u<J���/��y9�._��{�U�Q�J��t������^,��B@~�^�>���v�;f@�i��K0�&">r?���]}x��L���W��@��CTp�9���W+�=�����,������h&�������N�:��*��CH������o��i}]�S�&���T|���=���\\�-���{�2���ab ��W5���<������%��W��P
,��R��+���?�U5#�|�B�2l��>G�?B�\���5p���s�r�@)�+)�'����������w� �������^���<��MK��?y���9g�mY�l[�c��TjH��>{�`2�������DP�o��F6�RqC������p����<`�?8�n���C*���������I<J��A��C�i�	ciL����|����#�8���E�(p *f��7������b�R�t���N��I"�����^����2@��A���
[fO�zR����,g6��?��i�P���/4��������RN�("��Md���|>'��<�J�/O���=@?C�(�7s�!�����/�4	���?c�eGV�_�
Z��jI`l�<�:>�y J��='�AMgc����W���@E�#+9%���>�-�iS�A�jD�ZgY!���?o@�-6@@N� 4���YQx:�u&�����Z�Kn�"MA�FP�

/!�?�PC�����"��
OI`Z����h�	P����1���A�x8h1U�?��D�H�xS�
K&�&��[r������e�Mw�C�AE��<"���d�,�L�/|PeC�<V�1�!y�u��L:���o��%��!��^�h�R�,�!z(������������~���l>�c�����[���`1;����#d��w���,
�aE����{XO�0��'{����
���J�`}0��Z�5v21���JO�>���a�.��&
�j�3Q��,J2\K����+�`���T��V�#U��f�Zk��F9h��o&X��~Ojr���_TqPET��8�%j
���n�����e��uE5-C(��E�SYe�	mX3mS\��<�t9}:Y1��8	]
[h�F��k?h���Y��P,*n��8�0�&�,Ee�&�V��]�x@��\#Ck��Q���x?��"�]�����7�����a$����w������xiZ{X/��,�O��XWc�;��.�Y��G��}���V�V����-���j��29�V�T[��j�J+d��o.�on�����b�UT��A��_�5A�/p1��.��������B��O`��c����q1�O��J���xk	K#���A��_�.av����Mj�h�d��*�,M��Y�/`
��}�kh�?��S��wA@<��S�_�����Ts�����i7����)��B_c��s.�M'����;��N�Zj���wG2��
�����
�\�7�F��y@&��<���|2�����f���2V�M�}!Xk�9��e��0u,9n�e�(4K�k����0�h� �4���[�4GHPN�C:Rq��_v����,���`��6M�,����&�^�6hq��:�b�0t�T�E|��M�L����a(2�$K���Z��
?�RmUx�T��>��kl���J���Q�(f����&t�S���h{��C=���H_����w�9��
A��,�t�b${�( z���.�S]��n7T����%�d�L����*��������1�@���t�[��*h����&��l�R���[��
4���v�v���i���]��a�;��wbeX�������O���Mk{���{��{��E���u���zb��ci�|Qd;V��s;�Z]2i_���v,=���D���R�4W�o+���%�+�v8�����������������kkXv����=��������"���[�;��wn����8nc���C���n6��7M���m&�!�7w	G�����4�~sW9?���%�>����������������K�V�by��{�kcG��m�����c������RK-�J0����������*l�b/�����V�����Vm��Mj4�)�"�1E^j�P�������n��|�PU8�]�y�%�-��x�~�KN��P�"}�������$�3mE�}70�+���z(�|6;����6����W8E��6S�,������3��x@q��j���VA#D*�����J�#{����=���V�FX�F({�3������%�~���+�hQ�A���A��=��"�CJ��U��
iy�>F�u����$��}���t!k�C2��j!�{.t�`���\t���OU���S�qD�>��|tfZZ.>J~dpBZ�9�������=W�5U�1/4��F`�[YK�Z]��	��w��;ku��=��y�gg��I��8�q�&Z�9.����l��qU3I��@w}b9�-���cr�<��<�=V�G7}�/��jr� �T-z�
!.M*Ez
aR��\���~�+����u�
7�r�qV��>Zkg|��8�=������8K�r<[�>(�c����=��_�pB������&=
��u��������g��
_����m!����3{��
� ��d���d~n
���R�������w8����T-*	~`U��d��p���j�-K��}����"^2o�C5r �*?���-�gY
�Se`�;O�J=b��t��yf���\h���I���cR���9�mX���$���[5MdJ"m�C�) ���Z��!6cqG�iZO���v;�k����9��!?Z�K2%���3U5I`��L�BK�/�W|�P��.9�!Y\��_�����G��	M��U:!-OT��E7T��O�4%�5Mv[P����g0��j5+M�59�b��Y����)?��=H���?����N�	�(�u����3O��2�	]����Nd�`���9��#Y���O��i��U�WU]O����%[����\��z����Teg���R?-6�������������^�9���_������g�������(=�)���%���4���;��;/�C��\�����?��V�i|�/-����}���
������s���U�3
�pMvO0�l���!��4(b�	(��w�p�`����e0a���.6$B��["��������@�2���0����-�i���E�)�.�Bq������uw�[��l�;P%K����1���H
0006-Remove-distance-operators-from-btree_gist-v15.patch.gzapplication/x-gzip; name=0006-Remove-distance-operators-from-btree_gist-v15.patch.gzDownload
0007-Add-regression-tests-for-kNN-btree-v15.patch.gzapplication/x-gzip; name=0007-Add-regression-tests-for-kNN-btree-v15.patch.gzDownload
#40Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#39)
5 attachment(s)
Re: [PATCH] kNN for btree

On Mon, Sep 9, 2019 at 11:28 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Sun, Sep 8, 2019 at 11:35 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I'm going to push 0001 changing "attno >= 1" to assert.

Pushed. Rebased patchset is attached. I propose to limit
consideration during this commitfest to this set of 7 remaining
patches. The rest of patches could be considered later. I made some
minor editing in preparation to commit. But I decided I've couple
more notes to Nikita.

* 0003 extracts part of fields from BTScanOpaqueData struct into new
BTScanStateData struct. However, there is a big comment regarding
BTScanOpaqueData just before definition of BTScanPosItem. It needs to
be revised.
* 0004 adds "knnState" field to BTScanOpaqueData in addition to
"state" field. Wherein "knnState" might unused during knn scan if it
could be done in one direction. This seems counter-intuitive. Could
we rework this to have "forwardState" and "backwardState" fields
instead of "state" and "knnState"?

I have reordered patchset into fewer more self-consistent patches.

Besides comments, code beautification and other improvements, now
there are dedicated fields for forward and backward scan states. The
forward scan state is the pointer to data structure, which is used in
ordinal unidirectional scan. So, it's mostly cosmetic change, but it
improves the code readability.

One thing bothers me. We have to move scalar distance operators from
btree_gist to core. btree_gist extension upgrade script have to
qualify operators with schema in order to distinguish core and
extension implementations. So, I have to use @extschema@. But it's
forbidden for relocatable extensions. For now, I marken btree_gist as
non-relocatable. Our comment in extension.c says "For a relocatable
extension, we needn't do this. There cannot be any need for
@extschema@, else it wouldn't be relocatable.". Is it really true? I
think now we have pretty valid example for relocatable extension,
which needs @extschema@ in upgrade script. Any thoughts?

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0002-Allow-ordering-by-operator-in-ordered-indexes-v16.patch.gzapplication/x-gzip; name=0002-Allow-ordering-by-operator-in-ordered-indexes-v16.patch.gzDownload
���]0002-Allow-ordering-by-operator-in-ordered-indexes-v16.patch�W�s�6�l��ud[�D��\=Q���]�d,w:777\Z�(�@�j{��-��n�)�#��b��}��h6�� ��]6�$�Y��i&q;���vyg�t�.����a��}:3�����{b3��t�j�g�|&��yp�����J�IT��,/�wL����a�Yf�%B8u���.��f���������y|��h5���q��%�L�;�V 
�L	i��1��Py��RJ�u�=C9}��������5���uX�R>�X��_jPeQ����Q�u�HE	3�����*�H>�3���Ur9����u$�����Y���o�4��	���IdsEk[�t�����E���X0
���	y��G����%���%������pe�V����]�x
"��K&cw��\ ��Px	`�Y�VDR9'g:���L�i�K�r9b�L<s�m,�X.r�5�Teh���[3"K����D"���.���p�P5
��.����v�o'���E�����n��3���Ns����0���n�>�%�~������c���vXN@�Li�R��<��{F�>��X�/!��E��}��
�4rf� �K��~�i��z~���}��E�6�&>�`u��B�*���Y�"�`>����jWR���<�!I3
����:�B
�"��U�����1f��3���$��#���3�F�xK��0n��m�[�v?�a�a���t�W����i��{��n�]��y�]���h{�rB���>!��>g��&g���H���!a��W�5�������yD�8�5#�t���x��-�M8�H�\l����t�@�V����3�:���]6S����=�i���?f������>QM��[���<��#�����l|���yvd�y�7��uz� �E,��w#��2|������W�pa�|Kp���T�n��}����`*�����4
�
1,�H�Tr���A���!9t�V��%��T���!3jY!g%��j�4.�6�,�4�-@��_�cT�$]�(����|���D��v������
��Rw����%�,�h�h�S.��7���mT��cb�e�^Q���w�WF;�������;����r���i��Y�|(���d@@i�o�S�H��e�rIf�
+�q2V.^{������^��RI!u�����
��TP�1���8�g&=�Lx�w���2��/QM���9w4y������U����[�E������Ts�g��W��e��5	<�4��1�De��S����)��r�Wy"�\
��p���BW+��]��)���2����'D�^�����:�MV�f�&?g-#7��t�JB�W��"'�=I�Y%�H9�-\_}xE�xf��:�v�x��L�Qr5u���f������0������8�<y;���S�>��"eOizF9ULS'i��n	��k���G�6�!E�EU��l����a�>���o��Wj*����(K��zD�}���7�:b��	���n,�����h��n���e�����Z<����(+�D��R��������4�(�
��	�fP����'e����j���qn~	����E���*�6m�.��]���l�n�}T=����8+;����%er&��P$;����M����������v}C������b6�L�p�v'����}{ww����;ZUV���U�U+���5Ye�iE)B[�����_����a�@d�e;��3�s7�
�������~lc�����(L�c�ka'h{��:�p
0003-Extract-BTScanState-from-BTScanOpaqueData-v16.patch.gzapplication/x-gzip; name=0003-Extract-BTScanState-from-BTScanOpaqueData-v16.patch.gzDownload
���]0003-Extract-BTScanState-from-BTScanOpaqueData-v16.patch�}isG��g�W�'��$q��,����J��<~���^6���)����yTUW�$�7/v�#�3+���z������w��������q�}2��z'��i{��H���t�����8r!����zB�':�V{�
�D����?�����F|5/�|�a'��R&�e�\�����S�D�Yu����X�0J{(��'������Z�Vk�b5�/9I���>�~z�����?�K���.&~t��Hb���/>,��^I���{�Z.e����o�*������Y���i{���*	��r�?�����g"]JI�5��t��"H����ODc�D����R\�At)���S��Z%�>	���i�����0�/�I<��Q���?�\	�M0���F�!��X���^�Wa�G2^%�����!�A*$+S�t�.W�tK\����V�G�����Zh6|�;��ml����
����5�Qso�U�LVI?=Wi�H�1Z4er4?��n_������ktg�ic0�I�?�eo���^�����Uz/���w.oy+�����8��r���|?��O�)�Zuq���$����XFQp���u(?����x/���k4{"YN����ZF�#2�Ir���?��������5�wtO���\�����u���r�U�	.�O!zCA#p� ����t�4�D�v���G�ar��&Qz��r*gI3�u�����Y�N���RN��;8�H�����|�c*C��4��f3�h\��G[@y�E�=\�g1����t�������������%��������j�=��v����ht�������c_��{@��2MW�P�����~^�dB�W��!6 ��=��������x&����{�yL=��j.e��>�&q�9������j@C��yt�������0N��8<�mup?^�s�����j�P3�_����b�9V���y����D?����u�B3��h��3'N���
�x���3����V����_�E~������89K�����ZM@T�W�z"H����g� ]�"`�3���#A���*J�7&��k�ql3�`�U:���u����������pC(�`��e��a��3�^>��Pn-�:~?��P
2��Y�L��t���A*�$��y5�
����jV7�1`x� �>nq�&�D��0�zlg���F�r��|���)	
�2��S�J����x�22I�?5c�Z��"?#W�h��L���.C�� ���[&�������~� ��!AU��P,��AJZ�qhI���!����&�}B&���)�����98��~K#n�v���oNg��e!N��{Z��SV��7�:���l��G}!����o�&�0�0�`=%�A:��������U����D,��������M���@/�b�~�`�*m
�J��O�����"O�vl���v�No�h"��"PY���
@0�R	���Z�?�*�\��
���Q5��_��H�e4�y�x#�;��z�{�H������v/�e��&�C�Px��X�I��Z����p�j�o�po�����:����B���3�vp[q�(���^����h)���Gj�N��l�2���{� ���n�[�V_Z-l`�O�#Zm��5��aY3���jLH�y�<	v�a����
���]�����&\����*"���c�a�N�
E��)����6^^'��N��NU�2o�2���`t�B�v���6sy�m��6��e6�`K�X#�Xf��<G�E(�-�do��qT�=�v������`1NGv����pf�]����F����������K�y$V��(]�7"����XGS&W�7�j$�*����Q���_UT6q{%��,��4A�f=�G�:�U8)d�)FlP� ��5ju��O�-�&&0�j�)�����Q#M�	�U���B�&(G"^���u���zU��j�tz��,���G�4_�Y�0+���8��Y\�3���G�"]�rxr���;l�{@��LW���##;�p��D���t�Cy�����	��F�8	p����W7k��������@�QS8�?�G��[<e���G���%h�`N�Vd/��z
V��6���<�n����l8�	������\jo$�A��~Hj��	��5Y��G�7�@�N�c_A�2<ljV�K[9�}�H�����j�r�Y�
b����T�-"��:�
������������	��l�B����N���g����	����6�qN����"������R@@r;3*�3�h;�����R�����Sa�|c�2�Q������d��?��b#�U�
���������G(j���������!
8����I@��~qX�|l�FC(��HY;d�v�t��z��^���j�*�$��K1/�WR'�^��,�����/d�CYV��e9.��\����Qf�j���
�*���[)�_rTmq>kE�FoV2��z]�e��.�J�����6j������T'!���&�z��Oz�P$1%��uN�	�#�����J��8�����G~��d�C4\�!����q��t	��+� R.!d~�������BN?l�=���9bn,�w/���e���5�[�5�1���kN�����X�O���84���w/�^�',���VP�r#x�{h������Q�'�����Dy#�5^$b�j%�[�/���l&��
KK�El2�������mkM+0�~<���bN�
�f[����S�Q�)0�L
�X�U���yc@�xI��#m)[���1��A���c�n�3��f�4$]U;F-���]�m�:�q~1Ky����K��+����tfe�o,��R������,S�������[�K�Q&ev��
��(�L�A���*T�0M�_c�2 ����*;������0���4ve$
F�O(@+]#,`��MHGU4�<C�#/�b�,M����k{��f���wK9�Kd�k�z��	U� ���#W""����nGx��I��c"�~�����;�RP��i�A�3�7q�/2�{��9�h����7o�u��������>X:9xA���r���\��V^slkmW�a��0�����K[��d�[5��d}�����!�C	F�'J���b-�&���#��1
a0�ti8��HAL/�}L3����`�J����J9�pu�\mKS���
�z�j��Je�����#T�Qr���3�H��]�{���I%.���mw+��r�X��IM8��v����+�Z�atW��%�d����Gf�|�Yi���~�h����~���8���!��VpL���8�h�v���1V��0�N��VL@�e\��=�9^��ee�,�����~���zB�����Sd�%)z�����r2�����F��Q,�.�����q��k����J}~k��Mv��"W5@�2D����i��~w��A#|�G>_����>��Py�����>L��X`�f,�������u=�3r����0�:���O�}��UY%�-�b��^�����jz����F\����n�������~C�����[���1���c}���
f���v��9����V��uq��5Pj����N�\�l�H}EW2�����n��W����Q\�@*l�0(�v+���/������N}(���^��BF���1��+���q�D-���
E����j>B=M�7D�kF/�$�Xd����&B5AR[����))d5P����z�F����J��i��5�q��������.����
��\;A����Y4��!�����w����Io������{IX�*L��$���w���J�]����������������w�bi<E��`�]���/�L@4�-�(@Xz��Q�C�&4��qv�c���
�����B��Xr'��M�����,��
FV��T�a�C8��Z�#2Z�SA�'����m�%��NGv�{C��;e����6m9���� Z�����%K�'�@�l������p���������wa��S����=E���� �5^�������Z`�+���X�`����m��eZ�]�a�pkx�q�T���9���g��)����W�Z�+Ys�Z�;��\��V+��PY$����2��;j{�W�J�j!������(.���K��e�����	j��H�'5��f�*��O��M)��n��z�Y��JC����_$W1H1��l��u�C�� A"�5HD 2@'�'H%��U$�M�������L�l�6�=����Idj s�m��!@�#S'��:��������$��JJ0�m}��`0��b���@������K>	���������q�b0�*����!�����$-W�������&�o)����x��$�������4�����IW�u�{�t)��F!�N��#�
�|-��\N1�o�g�
�3 c����a���{ �y��{ !x��$�/�k*���X���%��u���j<�cD�	H���lTq��q���?��4��0��2
����Zu�R	��b���{>p�t��q�}!�I�G�����U(������M��8��*RY�	b�TP����r+�}�7��%	
��~x�q��#����\�F��W5�.�W�B�8}z&rN
�^�w�]����<��������Y�{e(��L��SD�L?9(U��9R�Q���@`*��F�0���0"AK���\aJ��r��2gVN����L���������XQ�7PX�i��	�A6�����S�>c�����l��
bK������D]�#k���=o�����W�� q����/_)OIpq8e��
�����,*��c����8LMh02O
�" pS�����#���@�i@Pj'���\��37c����tB�xfq����nd�fO�	�i \=z��%g7�"����]z���Z>����9��<n�Zb���Yf?dW-�8�
��F$�Cu����h�k��0�����Q���0Vw#:[i�&s�$6��4��v��TX�����I	p�}~�0�/�@��7o^���}x�v��{�����6�C:V��)�t�#=�-��v��{���^�3RePk�H1J���t���w
��
�DI����N�������JS�F�%[fX^��M�;��_79��V��U3��A7b�k���������������� �������q}+\-���]�x�w�VH��Q�~�F{:��v����6�4A-��ta�ae�C�Z�K\���,Gtv��dt�;}�p���v����i=X��u�W:V.fX��U��+B)-�=Nb�^v�������7:#�!�?�;�o��?��:�}tde�,�6�a���*���U6�C���J)�����Xl%H�j�X����1��Tr����}�%�~9��[���~�'0��6��o���Kmf	3��U0�������\��m�u��|�<��g�X��t�i�6O��#I�0v ����e���*��yQm��������5G) �p.4��V�	��o�Y�oo���rK��X+;$��0��n��Jy24 �-^�b�\N_� ����������� *u2l��=U��F�r~;KT"�>6:��e�'��f5G�j�x[�����V
wN��]�_>�w&/�*���B������L������b��R��r
7�hTn�yaCD��v��p�����N�l��~�Dub�[��U�������MTY���*�������$W���c��6����E���W,O�I
���X�`��#��ma��W�����0�Zw��chp�����0��*]���",�z�UX�6�R�L�*M����j`�W��F*s���z�-�:����EVqc��+��-���I��NOe���o�P�k��&�;9KY�����U�kQD�S
&Y�?K;���.[E#�"�Z�@b5GP"��b������n��c��y��y���B�J��S�BS>�d�����"Cpx��:'/�a[�%U
�Z�������8U�c��`�v�cG��$3(A/s�k��(a�:�|j�h�Lu��fn�����(
7��;�4Ty$�g�O���)Z(/��@����8�����!���&��u��V���^���ke~�G���Q�QaFd6A2-��[����d�ps
%����	��)[�������O�6sN��B&#G��������j{����c��c"�g�+��#��p���f4<#>-���~���.�q��U�w�����Yq��b����'���e>�U�j������N[0�SA�J(2@�Zu����56w-uM�����8(��h�jY�x�{�L�����0�p�������w[�}����te�w""���Ik��!��MJ%�1��0�^���F��M�KK5�UV�����+�g,�����s���������h��[8�����xo"Mp$*��~v��*K��Q�J'%��l���Z��8z����61>H�A)J��Fv���3I�r%�+������4%���yVB6L"6���4��j�V����NkI��H��%�A
�����V� U���O
j ���>,�N��N����d��D���P�+�j<�$^b��8��m��9�E����(%���1O]NdB
�
��@ps#��3]�P�6-�U����b�u}13k�7���S9]?-���Y6��\����� J�*�������kQ��f����[.���|��BO(���o�_|�:(LLx��F��T��i�<���"@�6U���xR@��B���P@���������J��&��D��PV��^E�Vv�%���b�*
��+@�o���U����X�7��j��F�����v�G��T)KrT8�@��)(���Y���1�r���
g�lyEY�]p��29�2o��
��BC;��(��]]=�n��$[��?n��
Z��pb�@.�6O�*b�b�C�r���Hh�y<%���������c8���1���R1m���JtW �d�HV��j�(/4}�l�UF8n	�<m1��s����/��������#q?�����r r]_SN����A�\�����k��)r�\��5lJy��]�Un���{����)�����(��Y���tdNY��@1MT�5y�j����?�k��DQ���U�i�DL�)������A��"��R������;������mX�{$�U{�	�������N��P�5��.�������w��t�]+Ndk
B��=�C+\�;�3)�-�����A>O;:.����������0d���#�J���!iwF�=��Cu���
w)��-�����2���)�4���l�=�ur�b�i�X;�������+-|��)d�Zh�r�u`���l]D������P,�`�y(��Qf���VQ����4��^�#��%�*���M��Oie�2_�����K5�j	emc��(�B
���*��J��l�-F��K��l.]��XnW�!��v�WJ9;-T~0�&���)r5 �.�sw���/,��z�.��	~��mG!c�(f�y���E/�M[f���Gu����m��lq[9�aU����bd�1!������{�$�n#�F\���\`��������B��m�k�-����MGt�){Mu����)��d��:�!-��g����:������8~���Z{�����j�U����'�|�E9�3f0��;�Qw���(_���j&D/F8�A(���!�����%
�Bx�Ky(o+�������F���Rf���6��6+g�$���0��Uaf?��e5�L�����SQ��:kSS��9=����> ����wwq���Q��p�R`���m'��J3�\>-.?_��+������ZY���J+x5#��E�f[m��'��
��������|�*�i����"1Ri�F��JLPg�@�F�Z;�=�^f���F�������gA$W�$"����b������_�?���J�S}��KQ69�������	#��K7��(�e�Z���I�s�	��r���(�A	��?�~�v������������|�t���>:�/��b�f����	����q��B��F��O_)�l
T�'��9�W5|����}�I
���p���da�:���;t��_�Q���8��@�)Te����A�Uj�Wa��	d�B��&6Q&N���s�N�y[�~�W�����
��`'T]������e	NGo��/>�:�t���?��@t����v�S����^�X�w*�|��'��	��I���	����������������J*'�B�2�E	���U�i[���,���8�C���V+K�Q`F��*��j�t;��x8R��S�	�s�W�����B%�@�\�kU:N��r���������k�OAo���[;**�T��v��#h����>��	�]��t2h�b���aRt�qc`s,k�q�t�������8
(��U�� J�l��\�����g������h6E�C�1�w+e��W�P
G�����z��S���+��Xl����#:\��g��Fr�X�iAEwO���z'��u8��[
V��y��j���n�gfu���`�<�e�M���u��TG�1���V�2vP��2A�ZJ-
�|�����.4���Q�&�!n9�X�v��L��$��#���}|
���+^�R��s����`�)i#���3�������B,�������_���0���o��D�]�u����&�~��e�����l��0�w�O6� +��>�GB��A��S��8Z�%�b7'�z���N��*�������V!ds?����2�j�vx�����B�;��}�t��S���.=������(�	�p	�i�zT���>���Q��_������}GE�,�	�P=Z����	=]Onc�X����Q/��"	���������Kp�A������s���]���?�a(n\�_<V����cBmW���t:}����6�e�bX��)��U����U_�]}�:X�����{��K)�������K���J��^�M�mI��B�����S%�������N����B�������F
����^�[E�9�������k��dw�Q��x��eYuJ�u���w�)*j�mNt�&�*�_�-U�&�U(��1�m��6N����.K�����1Q�-[
=B-?�?�x%q�\� H�N�@
!�<h����pQgT ��_Z�A���x�j���rx����A���l��������.o��x:H7���V�������.�����e|���#���k�%C��%����9]����I&��t�������R�	���w@o`I2*{����������6)a�G���,\������6�����A��*6�QH��Yx����>�e��3Dr�s����<���>68�u����sU������V1�"��b������@�������W��_��t����*
�&�*Q|!U�&��*�����&xLu��8�E��k����x�WQx�w������Qe���n
�(�f��3�3�]��)��(��R��_f�s���tpy�[U����0Pn����K��Y
��&^��A�����)^�����Y|�ivMe3����v"�+��XT9��d2z���R�w���c)V$p,��F[�
-Y���-�\����@�Cp)[y.�(�uyt�;,gcR	,�m�Ag�]�ei�Y(����UcQ!�U�DQ�d���]��E ������V��\���3N)�=�~!��u�es�����x��,�=��p.}I:PG'�YFY�yZ�����1��qU�{��7���J�\�s����n�W:��.��4�^[�Z0�a�1��)eC�
{�t<���;���Ba$��5��D�	�����������/N|MH�3��L8`p����b�a��iY�L��v:�`g��W!������Fw0�w���z��k����=
��s�/����,
f��2J�
�7�]�=$R�j&c�����
�\.SJ�}�.}
�g��?����88���o,�ka@�#�OF���z0�T���m��5U�3�kh<�w��A�m�?I�yPw��Ua3���N���84��x�����*:O]{�t)K��nT��`�&������W�&��U�����w�q{2m6O���p&��A�hV��YU-#�����P�k�^������W��|�4{��2��!�:C
gi,���9]'�R��Q��C]rO���b�K�]b�@�X�(@w�w~�����S��`���l�Y��p�4����[$L��G8y�Hd��>�w������/Y��
����B`3.[�Af9�y-�}����~D�)�������#�B���@#,�A��m��A������9�YD4Y�<����)�6�j/��
�R�����S���OC�:�z�#�A���R�^�I
+��Y�����`�9��C����W2!O��
��6�A�����"�_����=���9X�(p�$��\��[����Q�	k�l����G�5;���~K���
�kc��4HH��[m��?���oA��*����S��W�
���oCW�mV������������N��O�!��b/��L��<Oc��6��-�^�V���y�<�I�p
�5���&�~�
`���.b�c�V�(�J���=�^4�$����%]��
2���\��8it���l�\8�a�&y��%X������Q���&P�`&��"bC��x4�c�����L���Pu��-�E�PQkBRt�(�9�I�����-��p�4����G3p+����\ou%����J�[��s����)[�I�n4�U{b��Y0	d4Y�]����5�M����A4���T���X��d���}��z�'����9��pV��?��jw>�=��y�c����zp[>�=��y�c�����|���c���1����g�X)l�EN-���D��_
TA��,U�����wO�-T�O�u�F�����]W���53�X�0m�c��6�2O�g�^��{w;����\��UQ�)���`��W��wl����w:��_|z����������	~L���]���U=�be4�hJ��m��9�������sP������'�����>[����f�Z��Z��=��UEH���b�?��A6��Hx�S�o#�#�`�����h�����hq�X�G�z�f�~���M��>8�v�'c0�O��n���v&��=)Z������8"������RTP����Zs���g�yl�?L�+S����b�����94�^���5�{{�s�>�d�
0004-Move-scalar-distance-operators-from-btree_gist-t-v16.patch.gzapplication/x-gzip; name=0004-Move-scalar-distance-operators-from-btree_gist-t-v16.patch.gzDownload
0005-Add-knn-support-to-btree-indexes-v16.patch.gzapplication/x-gzip; name=0005-Add-knn-support-to-btree-indexes-v16.patch.gzDownload
0001-Introduce-IndexAmRoutine.ammorderbyopfirstcol-v16.patch.gzapplication/x-gzip; name=0001-Introduce-IndexAmRoutine.ammorderbyopfirstcol-v16.patch.gzDownload
#41Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#40)
5 attachment(s)
Re: [PATCH] kNN for btree

On Tue, Dec 3, 2019 at 3:00 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Mon, Sep 9, 2019 at 11:28 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Sun, Sep 8, 2019 at 11:35 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I'm going to push 0001 changing "attno >= 1" to assert.

Pushed. Rebased patchset is attached. I propose to limit
consideration during this commitfest to this set of 7 remaining
patches. The rest of patches could be considered later. I made some
minor editing in preparation to commit. But I decided I've couple
more notes to Nikita.

* 0003 extracts part of fields from BTScanOpaqueData struct into new
BTScanStateData struct. However, there is a big comment regarding
BTScanOpaqueData just before definition of BTScanPosItem. It needs to
be revised.
* 0004 adds "knnState" field to BTScanOpaqueData in addition to
"state" field. Wherein "knnState" might unused during knn scan if it
could be done in one direction. This seems counter-intuitive. Could
we rework this to have "forwardState" and "backwardState" fields
instead of "state" and "knnState"?

I have reordered patchset into fewer more self-consistent patches.

Besides comments, code beautification and other improvements, now
there are dedicated fields for forward and backward scan states. The
forward scan state is the pointer to data structure, which is used in
ordinal unidirectional scan. So, it's mostly cosmetic change, but it
improves the code readability.

One thing bothers me. We have to move scalar distance operators from
btree_gist to core. btree_gist extension upgrade script have to
qualify operators with schema in order to distinguish core and
extension implementations. So, I have to use @extschema@. But it's
forbidden for relocatable extensions. For now, I marken btree_gist as
non-relocatable. Our comment in extension.c says "For a relocatable
extension, we needn't do this. There cannot be any need for
@extschema@, else it wouldn't be relocatable.". Is it really true? I
think now we have pretty valid example for relocatable extension,
which needs @extschema@ in upgrade script. Any thoughts?

I've rebased the patchset to the current master and made some
refactoring. I hope it would be possible to bring it to committable
shape during this CF. This need more refactoring though.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Introduce-IndexAmRoutine.ammorderbyopfirstcol-v17.patch.gzapplication/x-gzip; name=0001-Introduce-IndexAmRoutine.ammorderbyopfirstcol-v17.patch.gzDownload
0002-Allow-ordering-by-operator-in-ordered-indexes-v17.patch.gzapplication/x-gzip; name=0002-Allow-ordering-by-operator-in-ordered-indexes-v17.patch.gzDownload
0004-Move-scalar-distance-operators-from-btree_gist-t-v17.patch.gzapplication/x-gzip; name=0004-Move-scalar-distance-operators-from-btree_gist-t-v17.patch.gzDownload
0003-Extract-BTScanState-from-BTScanOpaqueData-v17.patch.gzapplication/x-gzip; name=0003-Extract-BTScanState-from-BTScanOpaqueData-v17.patch.gzDownload
0005-Add-knn-support-to-btree-indexes-v17.patch.gzapplication/x-gzip; name=0005-Add-knn-support-to-btree-indexes-v17.patch.gzDownload
In reply to: Alexander Korotkov (#41)
Re: [PATCH] kNN for btree

On Mon, Mar 2, 2020 at 1:27 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I've rebased the patchset to the current master and made some
refactoring. I hope it would be possible to bring it to committable
shape during this CF. This need more refactoring though.

This patch doesn't change anything about the B-Tree on-disk format -- right?

--
Peter Geoghegan

#43Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Peter Geoghegan (#42)
Re: [PATCH] kNN for btree

On Wed, Mar 4, 2020 at 4:58 AM Peter Geoghegan <pg@bowt.ie> wrote:

On Mon, Mar 2, 2020 at 1:27 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I've rebased the patchset to the current master and made some
refactoring. I hope it would be possible to bring it to committable
shape during this CF. This need more refactoring though.

This patch doesn't change anything about the B-Tree on-disk format -- right?

Yes, this is correct. No on-disk format changes, just new scanning strategy.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#44Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#43)
Re: [PATCH] kNN for btree

On Wed, Mar 4, 2020 at 2:39 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Wed, Mar 4, 2020 at 4:58 AM Peter Geoghegan <pg@bowt.ie> wrote:

On Mon, Mar 2, 2020 at 1:27 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I've rebased the patchset to the current master and made some
refactoring. I hope it would be possible to bring it to committable
shape during this CF. This need more refactoring though.

This patch doesn't change anything about the B-Tree on-disk format -- right?

Yes, this is correct. No on-disk format changes, just new scanning strategy.

After another try to polish this patch I figured out that the way it's
implemented is unnatural. I see the two reasonable ways to implement
knn for B-tree, but current implementation matches none of them.

1) Implement knn as two simultaneous scans over B-tree: forward and
backward. It's similar to what current patchset does. But putting
this logic to B-tree seems artificial. What B-tree does here is still
unidirectional scan. On top of that we merge results of two
unidirectional scans. The appropriate place to do this high-level
work is IndexScan node or even Optimizer/Executor (Merge node over to
IndexScan nodes), but not B-tree itself.
2) Implement arbitrary scans in B-tree using priority queue like GiST
and SP-GiST do. That would lead to much better support for KNN. We
can end up in supporting interesting cases like "ORDER BY col1 DESC,
col2 <> val1, col2 ASC" or something. But that's requires way more
hacking in B-tree core.

So, I'm marking this patch RWF. We should try re-implement this for v14.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#45Anton A. Melnikov
a.melnikov@postgrespro.ru
In reply to: Alexander Korotkov (#44)
5 attachment(s)
Re: [PATCH] kNN for btree

Hi!

On 16.03.2020 16:17, Alexander Korotkov wrote:

After another try to polish this patch I figured out that the way it's
implemented is unnatural. I see the two reasonable ways to implement
knn for B-tree, but current implementation matches none of them.

1) Implement knn as two simultaneous scans over B-tree: forward and
backward. It's similar to what current patchset does. But putting
this logic to B-tree seems artificial. What B-tree does here is still
unidirectional scan. On top of that we merge results of two
unidirectional scans. The appropriate place to do this high-level
work is IndexScan node or even Optimizer/Executor (Merge node over to
IndexScan nodes), but not B-tree itself.
2) Implement arbitrary scans in B-tree using priority queue like GiST
and SP-GiST do. That would lead to much better support for KNN. We
can end up in supporting interesting cases like "ORDER BY col1 DESC,
col2 <> val1, col2 ASC" or something. But that's requires way more
hacking in B-tree core.

I've rebased and fixed the 17th version of this patch to work
with current master as a starting point for further development.

At first i'm going to implement p.1). I think it's preferable for now
because it seems easier and faster to get a working version.

If there are any ideas pro and contra would be glad to discuss them.

With the best wishes!

--
Anton A. Melnikov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v18-0001-Introduce-IndexAmRoutine.ammorderbyopfirstcol.patchtext/x-patch; charset=UTF-8; name=v18-0001-Introduce-IndexAmRoutine.ammorderbyopfirstcol.patchDownload
From 42d50af82b7d721729b0ee0839c3b9d3f3f5cb75 Mon Sep 17 00:00:00 2001
From: "Anton A. Melnikov" <a.melnikov@postgrespro.ru>
Date: Sun, 31 Dec 2023 14:10:23 +0300
Subject: [PATCH 1/5] Introduce IndexAmRoutine.ammorderbyopfirstcol

Currently IndexAmRoutine.amcanorderbyop property means that index access method
supports "column op const" ordering for every indexed column.  Upcoming
knn-btree supports it only for the first column.  In future we will probably
need to a callback to check whether particular ordering is supported.  But now
there are no potential use-cases around.  So, don't overengineer it and leave
with just boolean property.

Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru
Author: Nikita Glukhov
Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov
---
 contrib/bloom/blutils.c               |  1 +
 doc/src/sgml/indexam.sgml             |  9 ++++++++-
 src/backend/access/brin/brin.c        |  1 +
 src/backend/access/gin/ginutil.c      |  1 +
 src/backend/access/gist/gist.c        |  1 +
 src/backend/access/hash/hash.c        |  1 +
 src/backend/access/nbtree/nbtree.c    |  1 +
 src/backend/access/spgist/spgutils.c  |  1 +
 src/backend/optimizer/path/indxpath.c | 22 +++++++++++++++-------
 src/include/access/amapi.h            |  6 ++++++
 src/include/nodes/pathnodes.h         |  2 ++
 11 files changed, 38 insertions(+), 8 deletions(-)

diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 6836129c90..916734f675 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -112,6 +112,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amoptsprocnum = BLOOM_OPTIONS_PROC;
 	amroutine->amcanorder = false;
 	amroutine->amcanorderbyop = false;
+	amroutine->amorderbyopfirstcol = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml
index cc4135e394..422ad047da 100644
--- a/doc/src/sgml/indexam.sgml
+++ b/doc/src/sgml/indexam.sgml
@@ -103,6 +103,11 @@ typedef struct IndexAmRoutine
     bool        amcanorder;
     /* does AM support ORDER BY result of an operator on indexed column? */
     bool        amcanorderbyop;
+    /*
+     * Does AM support only the one ORDER BY operator on first indexed column?
+     * amcanorderbyop is implied.
+     */
+    bool        amorderbyopfirstcol;
     /* does AM support backward scanning? */
     bool        amcanbackward;
     /* does AM support UNIQUE indexes? */
@@ -924,7 +929,9 @@ amparallelrescan (IndexScanDesc scan);
        an order satisfying <literal>ORDER BY</literal> <replaceable>index_key</replaceable>
        <replaceable>operator</replaceable> <replaceable>constant</replaceable>.  Scan modifiers
        of that form can be passed to <function>amrescan</function> as described
-       previously.
+       previously.  If the access method supports the only one ORDER BY operator
+       on the first indexed column, then it should set
+       <structfield>amorderbyopfirstcol</structfield> to true.
       </para>
      </listitem>
     </itemizedlist>
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 1087a9011e..2010e4d40f 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -251,6 +251,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amoptsprocnum = BRIN_PROCNUM_OPTIONS;
 	amroutine->amcanorder = false;
 	amroutine->amcanorderbyop = false;
+	amroutine->amorderbyopfirstcol = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 6d05e7bdcd..2d894bae9a 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -44,6 +44,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amoptsprocnum = GIN_OPTIONS_PROC;
 	amroutine->amcanorder = false;
 	amroutine->amcanorderbyop = false;
+	amroutine->amorderbyopfirstcol = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 943ae91019..d5e4bdcf71 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -66,6 +66,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amoptsprocnum = GIST_OPTIONS_PROC;
 	amroutine->amcanorder = false;
 	amroutine->amcanorderbyop = true;
+	amroutine->amorderbyopfirstcol = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index fa5b59a150..3aa03083a3 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -63,6 +63,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amoptsprocnum = HASHOPTIONS_PROC;
 	amroutine->amcanorder = false;
 	amroutine->amcanorderbyop = false;
+	amroutine->amorderbyopfirstcol = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 696d79c085..4425432fac 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -102,6 +102,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amoptsprocnum = BTOPTIONS_PROC;
 	amroutine->amcanorder = true;
 	amroutine->amcanorderbyop = false;
+	amroutine->amorderbyopfirstcol = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = true;
 	amroutine->amcanmulticol = true;
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 5b5e6e82d3..8f3dab831d 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -50,6 +50,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amoptsprocnum = SPGIST_OPTIONS_PROC;
 	amroutine->amcanorder = false;
 	amroutine->amcanorderbyop = true;
+	amroutine->amorderbyopfirstcol = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 32c6a8bbdc..4c4ca3c7a7 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -3084,6 +3084,10 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 	if (!index->amcanorderbyop)
 		return;
 
+	/* Only the one pathkey is supported when amorderbyopfirstcol is true */
+	if (index->amorderbyopfirstcol && list_length(pathkeys) != 1)
+		return;
+
 	foreach(lc1, pathkeys)
 	{
 		PathKey    *pathkey = (PathKey *) lfirst(lc1);
@@ -3112,20 +3116,24 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 		{
 			EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
 			int			indexcol;
+			int			ncolumns;
 
 			/* No possibility of match if it references other relations */
 			if (!bms_equal(member->em_relids, index->rel->relids))
 				continue;
 
 			/*
-			 * We allow any column of the index to match each pathkey; they
-			 * don't have to match left-to-right as you might expect.  This is
-			 * correct for GiST, and it doesn't matter for SP-GiST because
-			 * that doesn't handle multiple columns anyway, and no other
-			 * existing AMs support amcanorderbyop.  We might need different
-			 * logic in future for other implementations.
+			 * We allow any column or only the first of the index to match
+			 * each pathkey; they don't have to match left-to-right as you
+			 * might expect.  This is correct for GiST, and it doesn't matter
+			 * for SP-GiST and B-Tree because they do not handle multiple
+			 * columns anyway, and no other existing AMs support
+			 * amcanorderbyop.  We might need different logic in future for
+			 * other implementations.
 			 */
-			for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
+			ncolumns = index->amorderbyopfirstcol ? 1 : index->nkeycolumns;
+
+			for (indexcol = 0; indexcol < ncolumns; indexcol++)
 			{
 				Expr	   *expr;
 
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 2c6c307efc..df9bc9b374 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -223,6 +223,12 @@ typedef struct IndexAmRoutine
 	bool		amcanorder;
 	/* does AM support ORDER BY result of an operator on indexed column? */
 	bool		amcanorderbyop;
+
+	/*
+	 * Does AM support only the one ORDER BY operator on first indexed column?
+	 * amcanorderbyop is implied.
+	 */
+	bool		amorderbyopfirstcol;
 	/* does AM support backward scanning? */
 	bool		amcanbackward;
 	/* does AM support UNIQUE indexes? */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index b9713ec9aa..e0300ff56f 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1173,6 +1173,8 @@ struct IndexOptInfo
 	 * (IndexAmRoutine).  These fields are not set for partitioned indexes.
 	 */
 	bool		amcanorderbyop;
+	bool		amorderbyopfirstcol;	/* order by op is supported only on
+										 * first column? */
 	bool		amoptionalkey;
 	bool		amsearcharray;
 	bool		amsearchnulls;
-- 
2.43.0

v18-0002-Allow-ordering-by-operator-in-ordered-indexes.patchtext/x-patch; charset=UTF-8; name=v18-0002-Allow-ordering-by-operator-in-ordered-indexes.patchDownload
From 681963062cfc33bd5365c94c0edc845211027f25 Mon Sep 17 00:00:00 2001
From: "Anton A. Melnikov" <a.melnikov@postgrespro.ru>
Date: Sun, 31 Dec 2023 14:34:57 +0300
Subject: [PATCH 2/5] Allow ordering by operator in ordered indexes

Currently the only ordered index access method is btree, which doesn't support
ordering by operator.  So, optimizer has an assumption that ordered index access
method can't support ordering by operator.  Upcoming knn-btree is going to
break this assumption.  This commit prepares optimizer for that.  Now we assume
following.

 * Index scan ordered by operator doesn't support backward scan independently
   on amcanbackward.
 * If index native ordering matches query needs then we don't consider possible
   operator ordering.

Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru
Author: Nikita Glukhov
Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov
---
 src/backend/executor/execAmi.c        |  6 ++++++
 src/backend/executor/nodeIndexscan.c  |  6 ++----
 src/backend/optimizer/path/indxpath.c | 19 ++++++++++---------
 3 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index a33696efc5..9051cf490d 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -555,9 +555,15 @@ ExecSupportsBackwardScan(Plan *node)
 			return false;
 
 		case T_IndexScan:
+			/* Backward ORDER BY operator scans are not supported. */
+			if (((IndexScan *) node)->indexorderby)
+				return false;
 			return IndexSupportsBackwardScan(((IndexScan *) node)->indexid);
 
 		case T_IndexOnlyScan:
+			/* Backward ORDER BY operator scans are not supported. */
+			if (((IndexOnlyScan *) node)->indexorderby)
+				return false;
 			return IndexSupportsBackwardScan(((IndexOnlyScan *) node)->indexid);
 
 		case T_SubqueryScan:
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 03142b4a94..15ba597110 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -184,10 +184,8 @@ IndexNextWithReorder(IndexScanState *node)
 	 * Only forward scan is supported with reordering.  Note: we can get away
 	 * with just Asserting here because the system will not try to run the
 	 * plan backwards if ExecSupportsBackwardScan() says it won't work.
-	 * Currently, that is guaranteed because no index AMs support both
-	 * amcanorderbyop and amcanbackward; if any ever do,
-	 * ExecSupportsBackwardScan() will need to consider indexorderbys
-	 * explicitly.
+	 * Currently, ExecSupportsBackwardScan() simply returns false for index
+	 * plans with indexorderbys.
 	 */
 	Assert(!ScanDirectionIsBackward(((IndexScan *) node->ss.ps.plan)->indexorderdir));
 	Assert(ScanDirectionIsForward(estate->es_direction));
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 4c4ca3c7a7..110426f994 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -959,6 +959,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	 * if we are only trying to build bitmap indexscans, nor if we have to
 	 * assume the scan is unordered.
 	 */
+	useful_pathkeys = NIL;
+	orderbyclauses = NIL;
+	orderbyclausecols = NIL;
+
 	pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN &&
 								!found_lower_saop_clause &&
 								has_useful_pathkeys(root, rel));
@@ -969,16 +973,19 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 											  ForwardScanDirection);
 		useful_pathkeys = truncate_useless_pathkeys(root, rel,
 													index_pathkeys);
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
 	}
-	else if (index->amcanorderbyop && pathkeys_possibly_useful)
+
+	if (useful_pathkeys == NIL &&
+		index->amcanorderbyop && pathkeys_possibly_useful)
 	{
 		/*
 		 * See if we can generate ordering operators for query_pathkeys or at
 		 * least some prefix thereof.  Matching to just a prefix of the
 		 * query_pathkeys will allow an incremental sort to be considered on
 		 * the index's partially sorted results.
+		 * Index  access method can be both ordered and supporting ordering by
+		 * operator.  We're looking for ordering by operator only when native
+		 * ordering doesn't match.
 		 */
 		match_pathkeys_to_index(index, root->query_pathkeys,
 								&orderbyclauses,
@@ -989,12 +996,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 			useful_pathkeys = list_copy_head(root->query_pathkeys,
 											 list_length(orderbyclauses));
 	}
-	else
-	{
-		useful_pathkeys = NIL;
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
-	}
 
 	/*
 	 * 3. Check if an index-only scan is possible.  If we're not building
-- 
2.43.0

v18-0003-Extract-BTScanState-from-BTScanOpaqueData.patchtext/x-patch; charset=UTF-8; name=v18-0003-Extract-BTScanState-from-BTScanOpaqueData.patchDownload
From 45c849a37f608ec512553fbe9e36115422ac25a8 Mon Sep 17 00:00:00 2001
From: "Anton A. Melnikov" <a.melnikov@postgrespro.ru>
Date: Sun, 31 Dec 2023 19:45:35 +0300
Subject: [PATCH 3/5] Extract BTScanState from BTScanOpaqueData

Currently BTScanOpaqueData holds both information about scan keys and state
of tree scan.  That is OK as soon as we're going to scan btree just in single
direction.  Upcoming knn-btree patch provides btree scan in two directions
simultaneously.  This commit extracts data structure representing tree scan
state in a single direction into separate BTScanState struct in preparation
for knn-btree.

Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru
Author: Nikita Glukhov
Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov
---
 src/backend/access/nbtree/nbtree.c    | 191 ++++++------
 src/backend/access/nbtree/nbtsearch.c | 412 +++++++++++++-------------
 src/backend/access/nbtree/nbtutils.c  |  51 ++--
 src/include/access/nbtree.h           |  48 +--
 4 files changed, 377 insertions(+), 325 deletions(-)

diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 4425432fac..97a4e423f7 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -217,6 +217,7 @@ bool
 btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 	bool		res;
 
 	/* btree indexes are never lossy */
@@ -227,7 +228,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(so->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
@@ -244,7 +245,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(so->currPos))
+		if (!BTScanPosIsValid(state->currPos))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -262,11 +263,11 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 				 * trying to optimize that, so we don't detect it, but instead
 				 * just forget any excess entries.
 				 */
-				if (so->killedItems == NULL)
-					so->killedItems = (int *)
+				if (state->killedItems == NULL)
+					state->killedItems = (int *)
 						palloc(MaxTIDsPerBTreePage * sizeof(int));
-				if (so->numKilled < MaxTIDsPerBTreePage)
-					so->killedItems[so->numKilled++] = so->currPos.itemIndex;
+				if (state->numKilled < MaxTIDsPerBTreePage)
+					state->killedItems[state->numKilled++] = state->currPos.itemIndex;
 			}
 
 			/*
@@ -291,6 +292,7 @@ int64
 btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	int64		ntids = 0;
 	ItemPointer heapTid;
 
@@ -323,7 +325,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				 * Advance to next tuple within page.  This is the same as the
 				 * easy case in _bt_next().
 				 */
-				if (++so->currPos.itemIndex > so->currPos.lastItem)
+				if (++currPos->itemIndex > currPos->lastItem)
 				{
 					/* let _bt_next do the heavy lifting */
 					if (!_bt_next(scan, ForwardScanDirection))
@@ -331,7 +333,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				}
 
 				/* Save tuple ID, and continue scanning */
-				heapTid = &so->currPos.items[so->currPos.itemIndex].heapTid;
+				heapTid = &currPos->items[currPos->itemIndex].heapTid;
 				tbm_add_tuples(tbm, heapTid, 1, false);
 				ntids++;
 			}
@@ -359,8 +361,8 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 
 	/* allocate private workspace */
 	so = (BTScanOpaque) palloc(sizeof(BTScanOpaqueData));
-	BTScanPosInvalidate(so->currPos);
-	BTScanPosInvalidate(so->markPos);
+	BTScanPosInvalidate(so->state.currPos);
+	BTScanPosInvalidate(so->state.markPos);
 	if (scan->numberOfKeys > 0)
 		so->keyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData));
 	else
@@ -372,15 +374,15 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	so->arrayKeys = NULL;
 	so->arrayContext = NULL;
 
-	so->killedItems = NULL;		/* until needed */
-	so->numKilled = 0;
+	so->state.killedItems = NULL;	/* until needed */
+	so->state.numKilled = 0;
 
 	/*
 	 * We don't know yet whether the scan will be index-only, so we do not
 	 * allocate the tuple workspace arrays until btrescan.  However, we set up
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
-	so->currTuples = so->markTuples = NULL;
+	so->state.currTuples = so->state.markTuples = NULL;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -389,6 +391,45 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	return scan;
 }
 
+static void
+_bt_release_current_position(BTScanState state, Relation indexRelation,
+							 bool invalidate)
+{
+	/* we aren't holding any read locks, but gotta drop the pins */
+	if (BTScanPosIsValid(state->currPos))
+	{
+		/* Before leaving current page, deal with any killed items */
+		if (state->numKilled > 0)
+			_bt_killitems(state, indexRelation);
+
+		BTScanPosUnpinIfPinned(state->currPos);
+
+		if (invalidate)
+			BTScanPosInvalidate(state->currPos);
+	}
+}
+
+static void
+_bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
+{
+	/* No need to invalidate positions, if the RAM is about to be freed. */
+	_bt_release_current_position(state, scan->indexRelation, !free);
+
+	state->markItemIndex = -1;
+	BTScanPosUnpinIfPinned(state->markPos);
+
+	if (free)
+	{
+		if (state->killedItems != NULL)
+			pfree(state->killedItems);
+		if (state->currTuples != NULL)
+			pfree(state->currTuples);
+		/* markTuples should not be pfree'd (_bt_allocate_tuple_workspaces) */
+	}
+	else
+		BTScanPosInvalidate(state->markPos);
+}
+
 /*
  *	btrescan() -- rescan an index relation
  */
@@ -397,21 +438,11 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 		 ScanKey orderbys, int norderbys)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-		BTScanPosInvalidate(so->currPos);
-	}
+	_bt_release_scan_state(scan, state, false);
 
-	so->markItemIndex = -1;
 	so->arrayKeyCount = 0;
-	BTScanPosUnpinIfPinned(so->markPos);
-	BTScanPosInvalidate(so->markPos);
 
 	/*
 	 * Allocate tuple workspace arrays, if needed for an index-only scan and
@@ -429,11 +460,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 	 * a SIGSEGV is not possible.  Yeah, this is ugly as sin, but it beats
 	 * adding special-case treatment for name_ops elsewhere.
 	 */
-	if (scan->xs_want_itup && so->currTuples == NULL)
-	{
-		so->currTuples = (char *) palloc(BLCKSZ * 2);
-		so->markTuples = so->currTuples + BLCKSZ;
-	}
+	if (scan->xs_want_itup && state->currTuples == NULL)
+		_bt_allocate_tuple_workspaces(state);
 
 	/*
 	 * Reset the scan keys
@@ -456,19 +484,7 @@ btendscan(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-	}
-
-	so->markItemIndex = -1;
-	BTScanPosUnpinIfPinned(so->markPos);
-
-	/* No need to invalidate positions, the RAM is about to be freed. */
+	_bt_release_scan_state(scan, &so->state, true);
 
 	/* Release storage */
 	if (so->keyData != NULL)
@@ -476,24 +492,15 @@ btendscan(IndexScanDesc scan)
 	/* so->arrayKeyData and so->arrayKeys are in arrayContext */
 	if (so->arrayContext != NULL)
 		MemoryContextDelete(so->arrayContext);
-	if (so->killedItems != NULL)
-		pfree(so->killedItems);
-	if (so->currTuples != NULL)
-		pfree(so->currTuples);
-	/* so->markTuples should not be pfree'd, see btrescan */
+
 	pfree(so);
 }
 
-/*
- *	btmarkpos() -- save current scan position
- */
-void
-btmarkpos(IndexScanDesc scan)
+static void
+_bt_mark_current_position(BTScanState state)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-
 	/* There may be an old mark with a pin (but no lock). */
-	BTScanPosUnpinIfPinned(so->markPos);
+	BTScanPosUnpinIfPinned(state->markPos);
 
 	/*
 	 * Just record the current itemIndex.  If we later step to next page
@@ -501,32 +508,34 @@ btmarkpos(IndexScanDesc scan)
 	 * the currPos struct in markPos.  If (as often happens) the mark is moved
 	 * before we leave the page, we don't have to do that work.
 	 */
-	if (BTScanPosIsValid(so->currPos))
-		so->markItemIndex = so->currPos.itemIndex;
+	if (BTScanPosIsValid(state->currPos))
+		state->markItemIndex = state->currPos.itemIndex;
 	else
 	{
-		BTScanPosInvalidate(so->markPos);
-		so->markItemIndex = -1;
+		BTScanPosInvalidate(state->markPos);
+		state->markItemIndex = -1;
 	}
-
-	/* Also record the current positions of any array keys */
-	if (so->numArrayKeys)
-		_bt_mark_array_keys(scan);
 }
 
 /*
- *	btrestrpos() -- restore scan to last saved position
+ *	btmarkpos() -- save current scan position
  */
 void
-btrestrpos(IndexScanDesc scan)
+btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* Restore the marked positions of any array keys */
+	_bt_mark_current_position(&so->state);
+
+	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
-		_bt_restore_array_keys(scan);
+		_bt_mark_array_keys(scan);
+}
 
-	if (so->markItemIndex >= 0)
+static void
+_bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
+{
+	if (state->markItemIndex >= 0)
 	{
 		/*
 		 * The scan has never moved to a new page since the last mark.  Just
@@ -535,7 +544,7 @@ btrestrpos(IndexScanDesc scan)
 		 * NB: In this case we can't count on anything in so->markPos to be
 		 * accurate.
 		 */
-		so->currPos.itemIndex = so->markItemIndex;
+		state->currPos.itemIndex = state->markItemIndex;
 	}
 	else
 	{
@@ -545,28 +554,21 @@ btrestrpos(IndexScanDesc scan)
 		 * locks, but if we're still holding the pin for the current position,
 		 * we must drop it.
 		 */
-		if (BTScanPosIsValid(so->currPos))
-		{
-			/* Before leaving current page, deal with any killed items */
-			if (so->numKilled > 0)
-				_bt_killitems(scan);
-			BTScanPosUnpinIfPinned(so->currPos);
-		}
+		_bt_release_current_position(state, scan->indexRelation,
+									 !BTScanPosIsValid(state->markPos));
 
-		if (BTScanPosIsValid(so->markPos))
+		if (BTScanPosIsValid(state->markPos))
 		{
 			/* bump pin on mark buffer for assignment to current buffer */
-			if (BTScanPosIsPinned(so->markPos))
-				IncrBufferRefCount(so->markPos.buf);
-			memcpy(&so->currPos, &so->markPos,
+			if (BTScanPosIsPinned(state->markPos))
+				IncrBufferRefCount(state->markPos.buf);
+			memcpy(&state->currPos, &state->markPos,
 				   offsetof(BTScanPosData, items[1]) +
-				   so->markPos.lastItem * sizeof(BTScanPosItem));
-			if (so->currTuples)
-				memcpy(so->currTuples, so->markTuples,
-					   so->markPos.nextTupleOffset);
+				   state->markPos.lastItem * sizeof(BTScanPosItem));
+			if (state->currTuples)
+				memcpy(state->currTuples, state->markTuples,
+					   state->markPos.nextTupleOffset);
 		}
-		else
-			BTScanPosInvalidate(so->currPos);
 	}
 }
 
@@ -781,6 +783,21 @@ _bt_parallel_advance_array_keys(IndexScanDesc scan)
 	SpinLockRelease(&btscan->btps_mutex);
 }
 
+/*
+ *	btrestrpos() -- restore scan to last saved position
+ */
+void
+btrestrpos(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
+	/* Restore the marked positions of any array keys */
+	if (so->numArrayKeys)
+		_bt_restore_array_keys(scan);
+
+	_bt_restore_marked_position(scan, &so->state);
+}
+
 /*
  * Bulk deletion of all index entries pointing to a set of heap tuples.
  * The set of target tuples is specified via a callback routine that tells
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 63ee9ba225..160392dcb6 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -29,23 +29,26 @@ static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
 static OffsetNumber _bt_binsrch(Relation rel, BTScanInsert key, Buffer buf);
 static int	_bt_binsrch_posting(BTScanInsert key, Page page,
 								OffsetNumber offnum);
-static bool _bt_readpage(IndexScanDesc scan, ScanDirection dir,
-						 OffsetNumber offnum, bool firstPage);
-static void _bt_saveitem(BTScanOpaque so, int itemIndex,
+static bool _bt_readpage(IndexScanDesc scan, BTScanState state,
+						 ScanDirection dir, OffsetNumber offnum,
+						 bool firstPage);
+static void _bt_saveitem(BTScanState state, int itemIndex,
 						 OffsetNumber offnum, IndexTuple itup);
-static int	_bt_setuppostingitems(BTScanOpaque so, int itemIndex,
+static int	_bt_setuppostingitems(BTScanState state, int itemIndex,
 								  OffsetNumber offnum, ItemPointer heapTid,
 								  IndexTuple itup);
-static inline void _bt_savepostingitem(BTScanOpaque so, int itemIndex,
+static inline void _bt_savepostingitem(BTScanState state, int itemIndex,
 									   OffsetNumber offnum,
 									   ItemPointer heapTid, int tupleOffset);
-static bool _bt_steppage(IndexScanDesc scan, ScanDirection dir);
-static bool _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir);
+static bool _bt_steppage(IndexScanDesc scan, BTScanState state,
+						 ScanDirection dir);
+static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
+							 BlockNumber blkno, ScanDirection dir);
 static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
 								  ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
-static inline void _bt_initialize_more_data(BTScanOpaque so, ScanDirection dir);
+static inline void _bt_initialize_more_data(BTScanState state, ScanDirection dir);
 
 
 /*
@@ -852,6 +855,58 @@ _bt_compare(Relation rel,
 	return 0;
 }
 
+/*
+ * _bt_return_current_item() -- Prepare current scan state item for return.
+ *
+ * This function is used only in "return _bt_return_current_item();" statements
+ * and always returns true.
+ */
+static inline bool
+_bt_return_current_item(IndexScanDesc scan, BTScanState state)
+{
+	BTScanPosItem *currItem = &state->currPos.items[state->currPos.itemIndex];
+
+	scan->xs_heaptid = currItem->heapTid;
+
+	if (scan->xs_want_itup)
+		scan->xs_itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+
+	return true;
+}
+
+/*
+ * _bt_load_first_page() -- Load data from the first page of the scan.
+ *
+ * Caller must have pinned and read-locked state->currPos.buf.
+ *
+ * On success exit, state->currPos is updated to contain data from the next
+ * interesting page.  For success on a scan using a non-MVCC snapshot we hold
+ * a pin, but not a read lock, on that page.  If we do not hold the pin, we
+ * set state->currPos.buf to InvalidBuffer.  We return true to indicate success.
+ *
+ * If there are no more matching records in the given direction at all,
+ * we drop all locks and pins, set state->currPos.buf to InvalidBuffer,
+ * and return false.
+ */
+static bool
+_bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+					OffsetNumber offnum)
+{
+	if (!_bt_readpage(scan, state, dir, offnum, true))
+	{
+		/*
+		 * There's no actually-matching data on this page.  Try to advance to
+		 * the next page.  Return false if there's no matching data at all.
+		 */
+		LockBuffer(state->currPos.buf, BUFFER_LOCK_UNLOCK);
+		return _bt_steppage(scan, state, dir);
+	}
+
+	/* Drop the lock, and maybe the pin, on the current page */
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
+	return true;
+}
+
 /*
  *	_bt_first() -- Find the first item in a scan.
  *
@@ -877,6 +932,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	BTStack		stack;
 	OffsetNumber offnum;
@@ -888,10 +944,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	int			i;
 	bool		status;
 	StrategyNumber strat_total;
-	BTScanPosItem *currItem;
 	BlockNumber blkno;
 
-	Assert(!BTScanPosIsValid(so->currPos));
+	Assert(!BTScanPosIsValid(*currPos));
 
 	pgstat_count_index_scan(rel);
 
@@ -1382,19 +1437,19 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			 * their scan.
 			 */
 			_bt_parallel_done(scan);
-			BTScanPosInvalidate(so->currPos);
+			BTScanPosInvalidate(*currPos);
 			return false;
 		}
 	}
 
 	PredicateLockPage(rel, BufferGetBlockNumber(buf), scan->xs_snapshot);
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
 	/* position to the precise item on the page */
 	offnum = _bt_binsrch(rel, &inskey, buf);
-	Assert(!BTScanPosIsValid(so->currPos));
-	so->currPos.buf = buf;
+	Assert(!BTScanPosIsValid(*currPos));
+	currPos->buf = buf;
 
 	/*
 	 * Now load data from the first page of the scan.
@@ -1415,30 +1470,33 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 * for the page.  For example, when inskey is both < the leaf page's high
 	 * key and > all of its non-pivot tuples, offnum will be "maxoff + 1".
 	 */
-	if (!_bt_readpage(scan, dir, offnum, true))
+	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
+		return false;
+
+readcomplete:
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
+}
+
+/*
+ *	Advance to next tuple on current page; or if there's no more,
+ *	try to step to the next page with data.
+ */
+static bool
+_bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
+{
+	if (ScanDirectionIsForward(dir))
 	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		_bt_unlockbuf(scan->indexRelation, so->currPos.buf);
-		if (!_bt_steppage(scan, dir))
-			return false;
+		if (++state->currPos.itemIndex <= state->currPos.lastItem)
+			return true;
 	}
 	else
 	{
-		/* We have at least one item to return as scan's first item */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+		if (--state->currPos.itemIndex >= state->currPos.firstItem)
+			return true;
 	}
 
-readcomplete:
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_heaptid = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_steppage(scan, state, dir);
 }
 
 /*
@@ -1459,44 +1517,20 @@ bool
 _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-	BTScanPosItem *currItem;
 
-	/*
-	 * Advance to next tuple on current page; or if there's no more, try to
-	 * step to the next page with data.
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (++so->currPos.itemIndex > so->currPos.lastItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
-	else
-	{
-		if (--so->currPos.itemIndex < so->currPos.firstItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
+	if (!_bt_next_item(scan, &so->state, dir))
+		return false;
 
 	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_heaptid = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
  *	_bt_readpage() -- Load data from current index page into so->currPos
  *
- * Caller must have pinned and read-locked so->currPos.buf; the buffer's state
- * is not changed here.  Also, currPos.moreLeft and moreRight must be valid;
- * they are updated as appropriate.  All other fields of so->currPos are
+ * Caller must have pinned and read-locked pos->buf; the buffer's state
+ * is not changed here.  Also, pos->moreLeft and moreRight must be valid;
+ * they are updated as appropriate.  All other fields of pos are
  * initialized from scratch here.
  *
  * We scan the current page starting at offnum and moving in the indicated
@@ -1519,10 +1553,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
  * Returns true if any matching items found on the page, false if none.
  */
 static bool
-_bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
+_bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir, OffsetNumber offnum,
 			 bool firstPage)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
@@ -1537,9 +1571,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 	 * We must have the buffer pinned and locked, but the usual macro can't be
 	 * used here; this function is what makes it good for currPos.
 	 */
-	Assert(BufferIsValid(so->currPos.buf));
+	Assert(BufferIsValid(pos->buf));
 
-	page = BufferGetPage(so->currPos.buf);
+	page = BufferGetPage(pos->buf);
 	opaque = BTPageGetOpaque(page);
 
 	/* allow next page be processed by parallel worker */
@@ -1548,7 +1582,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 		if (ScanDirectionIsForward(dir))
 			_bt_parallel_release(scan, opaque->btpo_next);
 		else
-			_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+			_bt_parallel_release(scan, BufferGetBlockNumber(pos->buf));
 	}
 
 	continuescan = true;		/* default assumption */
@@ -1560,30 +1594,30 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 	 * We note the buffer's block number so that we can release the pin later.
 	 * This allows us to re-read the buffer if it is needed again for hinting.
 	 */
-	so->currPos.currPage = BufferGetBlockNumber(so->currPos.buf);
+	pos->currPage = BufferGetBlockNumber(pos->buf);
 
 	/*
 	 * We save the LSN of the page as we read it, so that we know whether it
 	 * safe to apply LP_DEAD hints to the page later.  This allows us to drop
 	 * the pin for MVCC scans, which allows vacuum to avoid blocking.
 	 */
-	so->currPos.lsn = BufferGetLSNAtomic(so->currPos.buf);
+	pos->lsn = BufferGetLSNAtomic(pos->buf);
 
 	/*
 	 * we must save the page's right-link while scanning it; this tells us
 	 * where to step right to after we're done with these items.  There is no
 	 * corresponding need for the left-link, since splits always go right.
 	 */
-	so->currPos.nextPage = opaque->btpo_next;
+	pos->nextPage = opaque->btpo_next;
 
 	/* initialize tuple workspace to empty */
-	so->currPos.nextTupleOffset = 0;
+	pos->nextTupleOffset = 0;
 
 	/*
 	 * Now that the current page has been made consistent, the macro should be
 	 * good.
 	 */
-	Assert(BTScanPosIsPinned(so->currPos));
+	Assert(BTScanPosIsPinned(*pos));
 
 	/*
 	 * Prechecking the value of the continuescan flag for the last item on the
@@ -1669,7 +1703,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 				if (!BTreeTupleIsPosting(itup))
 				{
 					/* Remember it */
-					_bt_saveitem(so, itemIndex, offnum, itup);
+					_bt_saveitem(state, itemIndex, offnum, itup);
 					itemIndex++;
 				}
 				else
@@ -1681,14 +1715,14 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 					 * TID
 					 */
 					tupleOffset =
-						_bt_setuppostingitems(so, itemIndex, offnum,
+						_bt_setuppostingitems(state, itemIndex, offnum,
 											  BTreeTupleGetPostingN(itup, 0),
 											  itup);
 					itemIndex++;
 					/* Remember additional TIDs */
 					for (int i = 1; i < BTreeTupleGetNPosting(itup); i++)
 					{
-						_bt_savepostingitem(so, itemIndex, offnum,
+						_bt_savepostingitem(state, itemIndex, offnum,
 											BTreeTupleGetPostingN(itup, i),
 											tupleOffset);
 						itemIndex++;
@@ -1724,12 +1758,12 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 		}
 
 		if (!continuescan)
-			so->currPos.moreRight = false;
+			pos->moreRight = false;
 
 		Assert(itemIndex <= MaxTIDsPerBTreePage);
-		so->currPos.firstItem = 0;
-		so->currPos.lastItem = itemIndex - 1;
-		so->currPos.itemIndex = 0;
+		pos->firstItem = 0;
+		pos->lastItem = itemIndex - 1;
+		pos->itemIndex = 0;
 	}
 	else
 	{
@@ -1793,7 +1827,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 				{
 					/* Remember it */
 					itemIndex--;
-					_bt_saveitem(so, itemIndex, offnum, itup);
+					_bt_saveitem(state, itemIndex, offnum, itup);
 				}
 				else
 				{
@@ -1811,14 +1845,14 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 					 */
 					itemIndex--;
 					tupleOffset =
-						_bt_setuppostingitems(so, itemIndex, offnum,
+						_bt_setuppostingitems(state, itemIndex, offnum,
 											  BTreeTupleGetPostingN(itup, 0),
 											  itup);
 					/* Remember additional TIDs */
 					for (int i = 1; i < BTreeTupleGetNPosting(itup); i++)
 					{
 						itemIndex--;
-						_bt_savepostingitem(so, itemIndex, offnum,
+						_bt_savepostingitem(state, itemIndex, offnum,
 											BTreeTupleGetPostingN(itup, i),
 											tupleOffset);
 					}
@@ -1827,7 +1861,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 			if (!continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreLeft = false;
+				pos->moreLeft = false;
 				break;
 			}
 
@@ -1835,39 +1869,40 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 		}
 
 		Assert(itemIndex >= 0);
-		so->currPos.firstItem = itemIndex;
-		so->currPos.lastItem = MaxTIDsPerBTreePage - 1;
-		so->currPos.itemIndex = MaxTIDsPerBTreePage - 1;
+		pos->firstItem = itemIndex;
+		pos->lastItem = MaxTIDsPerBTreePage - 1;
+		pos->itemIndex = MaxTIDsPerBTreePage - 1;
 	}
 
-	return (so->currPos.firstItem <= so->currPos.lastItem);
+	return (pos->firstItem <= pos->lastItem);
 }
 
 /* Save an index item into so->currPos.items[itemIndex] */
 static void
-_bt_saveitem(BTScanOpaque so, int itemIndex,
+_bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup)
 {
-	BTScanPosItem *currItem = &so->currPos.items[itemIndex];
+	BTScanPosItem *currItem = &state->currPos.items[itemIndex];
 
 	Assert(!BTreeTupleIsPivot(itup) && !BTreeTupleIsPosting(itup));
 
 	currItem->heapTid = itup->t_tid;
 	currItem->indexOffset = offnum;
-	if (so->currTuples)
+	if (state->currTuples)
 	{
 		Size		itupsz = IndexTupleSize(itup);
 
-		currItem->tupleOffset = so->currPos.nextTupleOffset;
-		memcpy(so->currTuples + so->currPos.nextTupleOffset, itup, itupsz);
-		so->currPos.nextTupleOffset += MAXALIGN(itupsz);
+		currItem->tupleOffset = state->currPos.nextTupleOffset;
+		memcpy(state->currTuples + state->currPos.nextTupleOffset,
+			   itup, itupsz);
+		state->currPos.nextTupleOffset += MAXALIGN(itupsz);
 	}
 }
 
 /*
  * Setup state to save TIDs/items from a single posting list tuple.
  *
- * Saves an index item into so->currPos.items[itemIndex] for TID that is
+ * Saves an index item into state->currPos.items[itemIndex] for TID that is
  * returned to scan first.  Second or subsequent TIDs for posting list should
  * be saved by calling _bt_savepostingitem().
  *
@@ -1875,29 +1910,29 @@ _bt_saveitem(BTScanOpaque so, int itemIndex,
  * needed.
  */
 static int
-_bt_setuppostingitems(BTScanOpaque so, int itemIndex, OffsetNumber offnum,
+_bt_setuppostingitems(BTScanState state, int itemIndex, OffsetNumber offnum,
 					  ItemPointer heapTid, IndexTuple itup)
 {
-	BTScanPosItem *currItem = &so->currPos.items[itemIndex];
+	BTScanPosItem *currItem = &state->currPos.items[itemIndex];
 
 	Assert(BTreeTupleIsPosting(itup));
 
 	currItem->heapTid = *heapTid;
 	currItem->indexOffset = offnum;
-	if (so->currTuples)
+	if (state->currTuples)
 	{
 		/* Save base IndexTuple (truncate posting list) */
 		IndexTuple	base;
 		Size		itupsz = BTreeTupleGetPostingOffset(itup);
 
 		itupsz = MAXALIGN(itupsz);
-		currItem->tupleOffset = so->currPos.nextTupleOffset;
-		base = (IndexTuple) (so->currTuples + so->currPos.nextTupleOffset);
+		currItem->tupleOffset = state->currPos.nextTupleOffset;
+		base = (IndexTuple) (state->currTuples + state->currPos.nextTupleOffset);
 		memcpy(base, itup, itupsz);
 		/* Defensively reduce work area index tuple header size */
 		base->t_info &= ~INDEX_SIZE_MASK;
 		base->t_info |= itupsz;
-		so->currPos.nextTupleOffset += itupsz;
+		state->currPos.nextTupleOffset += itupsz;
 
 		return currItem->tupleOffset;
 	}
@@ -1906,17 +1941,17 @@ _bt_setuppostingitems(BTScanOpaque so, int itemIndex, OffsetNumber offnum,
 }
 
 /*
- * Save an index item into so->currPos.items[itemIndex] for current posting
+ * Save an index item into state->currPos.items[itemIndex] for current posting
  * tuple.
  *
  * Assumes that _bt_setuppostingitems() has already been called for current
  * posting list tuple.  Caller passes its return value as tupleOffset.
  */
 static inline void
-_bt_savepostingitem(BTScanOpaque so, int itemIndex, OffsetNumber offnum,
+_bt_savepostingitem(BTScanState state, int itemIndex, OffsetNumber offnum,
 					ItemPointer heapTid, int tupleOffset)
 {
-	BTScanPosItem *currItem = &so->currPos.items[itemIndex];
+	BTScanPosItem *currItem = &state->currPos.items[itemIndex];
 
 	currItem->heapTid = *heapTid;
 	currItem->indexOffset = offnum;
@@ -1925,7 +1960,7 @@ _bt_savepostingitem(BTScanOpaque so, int itemIndex, OffsetNumber offnum,
 	 * Have index-only scans return the same base IndexTuple for every TID
 	 * that originates from the same posting list
 	 */
-	if (so->currTuples)
+	if (state->currTuples)
 		currItem->tupleOffset = tupleOffset;
 }
 
@@ -1941,35 +1976,36 @@ _bt_savepostingitem(BTScanOpaque so, int itemIndex, OffsetNumber offnum,
  * to InvalidBuffer.  We return true to indicate success.
  */
 static bool
-_bt_steppage(IndexScanDesc scan, ScanDirection dir)
+_bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
+	Relation	rel = scan->indexRelation;
 	BlockNumber blkno = InvalidBlockNumber;
 	bool		status;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(*currPos));
 
 	/* Before leaving current page, deal with any killed items */
-	if (so->numKilled > 0)
-		_bt_killitems(scan);
+	if (state->numKilled > 0)
+		_bt_killitems(state, rel);
 
 	/*
 	 * Before we modify currPos, make a copy of the page data if there was a
 	 * mark position that needs it.
 	 */
-	if (so->markItemIndex >= 0)
+	if (state->markItemIndex >= 0)
 	{
 		/* bump pin on current buffer for assignment to mark buffer */
-		if (BTScanPosIsPinned(so->currPos))
-			IncrBufferRefCount(so->currPos.buf);
-		memcpy(&so->markPos, &so->currPos,
+		if (BTScanPosIsPinned(*currPos))
+			IncrBufferRefCount(currPos->buf);
+		memcpy(&state->markPos, currPos,
 			   offsetof(BTScanPosData, items[1]) +
-			   so->currPos.lastItem * sizeof(BTScanPosItem));
-		if (so->markTuples)
-			memcpy(so->markTuples, so->currTuples,
-				   so->currPos.nextTupleOffset);
-		so->markPos.itemIndex = so->markItemIndex;
-		so->markItemIndex = -1;
+			   currPos->lastItem * sizeof(BTScanPosItem));
+		if (state->markTuples)
+			memcpy(state->markTuples, state->currTuples,
+				   currPos->nextTupleOffset);
+		state->markPos.itemIndex = state->markItemIndex;
+		state->markItemIndex = -1;
 	}
 
 	if (ScanDirectionIsForward(dir))
@@ -1985,27 +2021,27 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
-				BTScanPosUnpinIfPinned(so->currPos);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosUnpinIfPinned(*currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so use the previously-saved nextPage link. */
-			blkno = so->currPos.nextPage;
+			blkno = currPos->nextPage;
 		}
 
 		/* Remember we left a page with data */
-		so->currPos.moreLeft = true;
+		currPos->moreLeft = true;
 
 		/* release the previous buffer, if pinned */
-		BTScanPosUnpinIfPinned(so->currPos);
+		BTScanPosUnpinIfPinned(*currPos);
 	}
 	else
 	{
 		/* Remember we left a page with data */
-		so->currPos.moreRight = true;
+		currPos->moreRight = true;
 
 		if (scan->parallel_scan != NULL)
 		{
@@ -2014,25 +2050,25 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			 * ended already, bail out.
 			 */
 			status = _bt_parallel_seize(scan, &blkno);
-			BTScanPosUnpinIfPinned(so->currPos);
+			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so just use our own notion of the current page */
-			blkno = so->currPos.currPage;
+			blkno = currPos->currPage;
 		}
 	}
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
 	/* We have at least one item to return as scan's next item */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, currPos);
 
 	return true;
 }
@@ -2048,9 +2084,10 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
  * locks and pins, set so->currPos.buf to InvalidBuffer, and return false.
  */
 static bool
-_bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+				 ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
 	Relation	rel;
 	Page		page;
 	BTPageOpaque opaque;
@@ -2066,17 +2103,17 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * if we're at end of scan, give up and mark parallel scan as
 			 * done, so that all the workers can finish their scan
 			 */
-			if (blkno == P_NONE || !so->currPos.moreRight)
+			if (blkno == P_NONE || !currPos->moreRight)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 			/* check for interrupts while we're not holding any buffer lock */
 			CHECK_FOR_INTERRUPTS();
 			/* step right one page */
-			so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
-			page = BufferGetPage(so->currPos.buf);
+			currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
+			page = BufferGetPage(currPos->buf);
 			opaque = BTPageGetOpaque(page);
 			/* check for deleted page */
 			if (!P_IGNORE(opaque))
@@ -2084,7 +2121,7 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 				PredicateLockPage(rel, blkno, scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreRight if we can stop */
-				if (_bt_readpage(scan, dir, P_FIRSTDATAKEY(opaque), false))
+				if (_bt_readpage(scan, state, dir, P_FIRSTDATAKEY(opaque), false))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
@@ -2096,18 +2133,18 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
 			}
 			else
 			{
 				blkno = opaque->btpo_next;
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 			}
 		}
 	}
@@ -2117,10 +2154,10 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * Should only happen in parallel cases, when some other backend
 		 * advanced the scan.
 		 */
-		if (so->currPos.currPage != blkno)
+		if (currPos->currPage != blkno)
 		{
-			BTScanPosUnpinIfPinned(so->currPos);
-			so->currPos.currPage = blkno;
+			BTScanPosUnpinIfPinned(*currPos);
+			currPos->currPage = blkno;
 		}
 
 		/*
@@ -2136,30 +2173,30 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * optimistically starting there (rather than pinning the page twice).
 		 * It is not clear that this would be worth the complexity.
 		 */
-		if (BTScanPosIsPinned(so->currPos))
-			_bt_lockbuf(rel, so->currPos.buf, BT_READ);
+		if (BTScanPosIsPinned(*currPos))
+			_bt_lockbuf(rel, currPos->buf, BT_READ);
 		else
-			so->currPos.buf = _bt_getbuf(rel, so->currPos.currPage, BT_READ);
+			currPos->buf = _bt_getbuf(rel, currPos->currPage, BT_READ);
 
 		for (;;)
 		{
 			/* Done if we know there are no matching keys to the left */
-			if (!so->currPos.moreLeft)
+			if (!currPos->moreLeft)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
 			/* Step to next physical page */
-			so->currPos.buf = _bt_walk_left(rel, so->currPos.buf);
+			currPos->buf = _bt_walk_left(rel, currPos->buf);
 
 			/* if we're physically at end of index, return failure */
-			if (so->currPos.buf == InvalidBuffer)
+			if (currPos->buf == InvalidBuffer)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
@@ -2168,20 +2205,20 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * it's not half-dead and contains matching tuples. Else loop back
 			 * and do it all again.
 			 */
-			page = BufferGetPage(so->currPos.buf);
+			page = BufferGetPage(currPos->buf);
 			opaque = BTPageGetOpaque(page);
 			if (!P_IGNORE(opaque))
 			{
-				PredicateLockPage(rel, BufferGetBlockNumber(so->currPos.buf), scan->xs_snapshot);
+				PredicateLockPage(rel, BufferGetBlockNumber(currPos->buf), scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreLeft if we can stop */
-				if (_bt_readpage(scan, dir, PageGetMaxOffsetNumber(page), false))
+				if (_bt_readpage(scan, state, dir, PageGetMaxOffsetNumber(page), false))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -2192,14 +2229,14 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
-				so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
+				currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
 			}
 		}
 	}
@@ -2218,13 +2255,13 @@ _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
 		return false;
 
 	/* We have at least one item to return as scan's next item */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
 
 	return true;
 }
@@ -2442,11 +2479,11 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber start;
-	BTScanPosItem *currItem;
 
 	/*
 	 * Scan down to the leftmost or rightmost leaf page.  This is a simplified
@@ -2462,7 +2499,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 		 * exists.
 		 */
 		PredicateLockRelation(rel, scan->xs_snapshot);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 		return false;
 	}
 
@@ -2491,36 +2528,15 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 	}
 
 	/* remember which buffer we have pinned */
-	so->currPos.buf = buf;
+	currPos->buf = buf;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(&so->state, dir);
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, start, false))
-	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		_bt_unlockbuf(scan->indexRelation, so->currPos.buf);
-		if (!_bt_steppage(scan, dir))
-			return false;
-	}
-	else
-	{
-		/* We have at least one item to return as scan's first item */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
-	}
-
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_heaptid = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
+	if (!_bt_load_first_page(scan, &so->state, dir, start))
+		return false;
 
-	return true;
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
@@ -2528,19 +2544,19 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
  * for scan direction
  */
 static inline void
-_bt_initialize_more_data(BTScanOpaque so, ScanDirection dir)
+_bt_initialize_more_data(BTScanState state, ScanDirection dir)
 {
 	/* initialize moreLeft/moreRight appropriately for scan direction */
 	if (ScanDirectionIsForward(dir))
 	{
-		so->currPos.moreLeft = false;
-		so->currPos.moreRight = true;
+		state->currPos.moreLeft = false;
+		state->currPos.moreRight = true;
 	}
 	else
 	{
-		so->currPos.moreLeft = true;
-		so->currPos.moreRight = false;
+		state->currPos.moreLeft = true;
+		state->currPos.moreRight = false;
 	}
-	so->numKilled = 0;			/* just paranoia */
-	so->markItemIndex = -1;		/* ditto */
+	state->numKilled = 0;		/* just paranoia */
+	state->markItemIndex = -1;	/* ditto */
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 2e6fc14d7a..f7867d4720 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -1774,27 +1774,27 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, int tupnatts,
  * away and the TID was re-used by a completely different heap tuple.
  */
 void
-_bt_killitems(IndexScanDesc scan)
+_bt_killitems(BTScanState state, Relation indexRelation)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
 	OffsetNumber maxoff;
 	int			i;
-	int			numKilled = so->numKilled;
+	int			numKilled = state->numKilled;
 	bool		killedsomething = false;
 	bool		droppedpin PG_USED_FOR_ASSERTS_ONLY;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(state->currPos));
 
 	/*
 	 * Always reset the scan state, so we don't look for same items on other
 	 * pages.
 	 */
-	so->numKilled = 0;
+	state->numKilled = 0;
 
-	if (BTScanPosIsPinned(so->currPos))
+	if (BTScanPosIsPinned(*pos))
 	{
 		/*
 		 * We have held the pin on this page since we read the index tuples,
@@ -1803,9 +1803,7 @@ _bt_killitems(IndexScanDesc scan)
 		 * LSN.
 		 */
 		droppedpin = false;
-		_bt_lockbuf(scan->indexRelation, so->currPos.buf, BT_READ);
-
-		page = BufferGetPage(so->currPos.buf);
+		_bt_lockbuf(indexRelation, pos->buf, BT_READ);
 	}
 	else
 	{
@@ -1813,31 +1811,31 @@ _bt_killitems(IndexScanDesc scan)
 
 		droppedpin = true;
 		/* Attempt to re-read the buffer, getting pin and lock. */
-		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ);
+		buf = _bt_getbuf(indexRelation, pos->currPage, BT_READ);
 
-		page = BufferGetPage(buf);
-		if (BufferGetLSNAtomic(buf) == so->currPos.lsn)
-			so->currPos.buf = buf;
+		if (BufferGetLSNAtomic(buf) == pos->lsn)
+			pos->buf = buf;
 		else
 		{
 			/* Modified while not pinned means hinting is not safe. */
-			_bt_relbuf(scan->indexRelation, buf);
+			_bt_relbuf(indexRelation, buf);
 			return;
 		}
 	}
 
+	page = BufferGetPage(pos->buf);
 	opaque = BTPageGetOpaque(page);
 	minoff = P_FIRSTDATAKEY(opaque);
 	maxoff = PageGetMaxOffsetNumber(page);
 
 	for (i = 0; i < numKilled; i++)
 	{
-		int			itemIndex = so->killedItems[i];
-		BTScanPosItem *kitem = &so->currPos.items[itemIndex];
+		int			itemIndex = state->killedItems[i];
+		BTScanPosItem *kitem = &pos->items[itemIndex];
 		OffsetNumber offnum = kitem->indexOffset;
 
-		Assert(itemIndex >= so->currPos.firstItem &&
-			   itemIndex <= so->currPos.lastItem);
+		Assert(itemIndex >= pos->firstItem &&
+			   itemIndex <= pos->lastItem);
 		if (offnum < minoff)
 			continue;			/* pure paranoia */
 		while (offnum <= maxoff)
@@ -1895,7 +1893,7 @@ _bt_killitems(IndexScanDesc scan)
 					 * correctly -- posting tuple still gets killed).
 					 */
 					if (pi < numKilled)
-						kitem = &so->currPos.items[so->killedItems[pi++]];
+						kitem = &state->currPos.items[state->killedItems[pi++]];
 				}
 
 				/*
@@ -1942,10 +1940,10 @@ _bt_killitems(IndexScanDesc scan)
 	if (killedsomething)
 	{
 		opaque->btpo_flags |= BTP_HAS_GARBAGE;
-		MarkBufferDirtyHint(so->currPos.buf, true);
+		MarkBufferDirtyHint(pos->buf, true);
 	}
 
-	_bt_unlockbuf(scan->indexRelation, so->currPos.buf);
+	_bt_unlockbuf(indexRelation, pos->buf);
 }
 
 
@@ -2782,3 +2780,14 @@ _bt_allequalimage(Relation rel, bool debugmessage)
 
 	return allequalimage;
 }
+
+/*
+ * _bt_allocate_tuple_workspaces() -- Allocate buffers for saving index tuples
+ * 		in index-only scans.
+ */
+void
+_bt_allocate_tuple_workspaces(BTScanState state)
+{
+	state->currTuples = (char *) palloc(BLCKSZ * 2);
+	state->markTuples = state->currTuples + BLCKSZ;
+}
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 6eb162052e..c62f1ea15d 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -916,7 +916,8 @@ typedef BTVacuumPostingData *BTVacuumPosting;
 /*
  * BTScanOpaqueData is the btree-private state needed for an indexscan.
  * This consists of preprocessed scan keys (see _bt_preprocess_keys() for
- * details of the preprocessing), information about the current location
+ * details of the preprocessing), and tree scan state itself (BTScanStateData).
+ * In turn, BTScanStateData contains information about the current location
  * of the scan, and information about the marked location, if any.  (We use
  * BTScanPosData to represent the data needed for each of current and marked
  * locations.)	In addition we can remember some known-killed index entries
@@ -1029,24 +1030,8 @@ typedef struct BTArrayKeyInfo
 	Datum	   *elem_values;	/* array of num_elems Datums */
 } BTArrayKeyInfo;
 
-typedef struct BTScanOpaqueData
+typedef struct BTScanStateData
 {
-	/* these fields are set by _bt_preprocess_keys(): */
-	bool		qual_ok;		/* false if qual can never be satisfied */
-	int			numberOfKeys;	/* number of preprocessed scan keys */
-	ScanKey		keyData;		/* array of preprocessed scan keys */
-
-	/* workspace for SK_SEARCHARRAY support */
-	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
-	bool		arraysStarted;	/* Started array keys, but have yet to "reach
-								 * past the end" of all arrays? */
-	int			numArrayKeys;	/* number of equality-type array keys (-1 if
-								 * there are any unsatisfiable array keys) */
-	int			arrayKeyCount;	/* count indicating number of array scan keys
-								 * processed */
-	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
-	MemoryContext arrayContext; /* scan-lifespan context for array data */
-
 	/* info about killed items if any (killedItems is NULL if never used) */
 	int		   *killedItems;	/* currPos.items indexes of killed items */
 	int			numKilled;		/* number of currently stored items */
@@ -1071,6 +1056,30 @@ typedef struct BTScanOpaqueData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+} BTScanStateData;
+
+typedef BTScanStateData *BTScanState;
+
+typedef struct BTScanOpaqueData
+{
+	/* these fields are set by _bt_preprocess_keys(): */
+	bool		qual_ok;		/* false if qual can never be satisfied */
+	int			numberOfKeys;	/* number of preprocessed scan keys */
+	ScanKey		keyData;		/* array of preprocessed scan keys */
+
+	/* workspace for SK_SEARCHARRAY support */
+	ScanKey		arrayKeyData;	/* modified copy of scan->keyData */
+	bool		arraysStarted;	/* Started array keys, but have yet to "reach
+								 * past the end" of all arrays? */
+	int			numArrayKeys;	/* number of equality-type array keys (-1 if
+								 * there are any unsatisfiable array keys) */
+	int			arrayKeyCount;	/* count indicating number of array scan keys
+								 * processed */
+	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
+	MemoryContext arrayContext; /* scan-lifespan context for array data */
+
+	/* the state of tree scan */
+	BTScanStateData state;
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -1252,7 +1261,7 @@ extern void _bt_preprocess_keys(IndexScanDesc scan);
 extern bool _bt_checkkeys(IndexScanDesc scan, IndexTuple tuple,
 						  int tupnatts, ScanDirection dir, bool *continuescan,
 						  bool requiredMatchedByPrecheck, bool haveFirstMatch);
-extern void _bt_killitems(IndexScanDesc scan);
+extern void _bt_killitems(BTScanState state, Relation indexRelation);
 extern BTCycleId _bt_vacuum_cycleid(Relation rel);
 extern BTCycleId _bt_start_vacuum(Relation rel);
 extern void _bt_end_vacuum(Relation rel);
@@ -1273,6 +1282,7 @@ extern bool _bt_check_natts(Relation rel, bool heapkeyspace, Page page,
 extern void _bt_check_third_page(Relation rel, Relation heap,
 								 bool needheaptidspace, Page page, IndexTuple newtup);
 extern bool _bt_allequalimage(Relation rel, bool debugmessage);
+extern void _bt_allocate_tuple_workspaces(BTScanState state);
 
 /*
  * prototypes for functions in nbtvalidate.c
-- 
2.43.0

v18-0004-Move-scalar-distance-operators-from-btree_gist-to-co.patchtext/x-patch; charset=UTF-8; name=v18-0004-Move-scalar-distance-operators-from-btree_gist-to-co.patchDownload
From b9b5b7a509a0c28ded9c7a1cb16378ed5f8d7c25 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 25 Nov 2019 01:22:21 +0300
Subject: [PATCH 4/5] Move scalar distance operators from btree_gist to core

Currently btree_gist is the only way to have knn search for scalar datatypes.
This is why distance operators for those types are defined inside btree_gist
as well.  Upcoming knn-btree needs these distance operators to be defined in
core.  This commit moves them from btree_gist to core.

Assuming that extension shared library should still work with non-upgraded
extension catalog, we btree_gist still provides wrappers over core functions.
Extension upgrade scripts switch opclasses to core operators and drops extension
operators.  Extension upgrade script has to refer @extschema@ to distinguish
between operators with same name.  Have to mark btree_gist as non-relocatable
in order to do that.

Catversion is bumped.
btree_gist extension version is bumped.

Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru
Author: Nikita Glukhov
Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov
---
 contrib/btree_gist/Makefile                 |   3 +-
 contrib/btree_gist/btree_cash.c             |  16 +-
 contrib/btree_gist/btree_date.c             |   7 +-
 contrib/btree_gist/btree_float4.c           |  11 +-
 contrib/btree_gist/btree_float8.c           |  11 +-
 contrib/btree_gist/btree_gist--1.7--1.8.sql |  90 +++++
 contrib/btree_gist/btree_gist.control       |   6 +-
 contrib/btree_gist/btree_int2.c             |  16 +-
 contrib/btree_gist/btree_int4.c             |  16 +-
 contrib/btree_gist/btree_int8.c             |  16 +-
 contrib/btree_gist/btree_interval.c         |   6 +-
 contrib/btree_gist/btree_oid.c              |  11 +-
 contrib/btree_gist/btree_time.c             |   6 +-
 contrib/btree_gist/btree_ts.c               |  38 +-
 doc/src/sgml/btree-gist.sgml                |  13 +
 src/backend/utils/adt/cash.c                |  20 +
 src/backend/utils/adt/date.c                | 112 ++++++
 src/backend/utils/adt/float.c               |  49 +++
 src/backend/utils/adt/int.c                 |  51 +++
 src/backend/utils/adt/int8.c                |  44 ++
 src/backend/utils/adt/oid.c                 |  22 +
 src/backend/utils/adt/timestamp.c           | 103 +++++
 src/include/catalog/pg_operator.dat         | 108 +++++
 src/include/catalog/pg_proc.dat             |  86 ++++
 src/include/utils/datetime.h                |   2 +
 src/include/utils/timestamp.h               |   4 +-
 src/test/regress/expected/date.out          |  64 +++
 src/test/regress/expected/float4.out        |  20 +
 src/test/regress/expected/float8.out        |  21 +
 src/test/regress/expected/int2.out          |  33 ++
 src/test/regress/expected/int4.out          |  32 ++
 src/test/regress/expected/int8.out          |  31 ++
 src/test/regress/expected/interval.out      |  17 +
 src/test/regress/expected/money.out         |   6 +
 src/test/regress/expected/oid.out           |  13 +
 src/test/regress/expected/time.out          |  16 +
 src/test/regress/expected/timestamp.out     | 421 ++++++++++++++++++++
 src/test/regress/expected/timestamptz.out   | 421 ++++++++++++++++++++
 src/test/regress/sql/date.sql               |   5 +
 src/test/regress/sql/float4.sql             |   3 +
 src/test/regress/sql/float8.sql             |   3 +
 src/test/regress/sql/int2.sql               |  10 +
 src/test/regress/sql/int4.sql               |  10 +
 src/test/regress/sql/int8.sql               |   5 +
 src/test/regress/sql/interval.sql           |   2 +
 src/test/regress/sql/money.sql              |   1 +
 src/test/regress/sql/oid.sql                |   2 +
 src/test/regress/sql/time.sql               |   3 +
 src/test/regress/sql/timestamp.sql          |   9 +
 src/test/regress/sql/timestamptz.sql        |   8 +
 50 files changed, 1883 insertions(+), 140 deletions(-)
 create mode 100644 contrib/btree_gist/btree_gist--1.7--1.8.sql

diff --git a/contrib/btree_gist/Makefile b/contrib/btree_gist/Makefile
index 073dcc745c..d54c615e96 100644
--- a/contrib/btree_gist/Makefile
+++ b/contrib/btree_gist/Makefile
@@ -33,7 +33,8 @@ EXTENSION = btree_gist
 DATA = btree_gist--1.0--1.1.sql \
        btree_gist--1.1--1.2.sql btree_gist--1.2.sql btree_gist--1.2--1.3.sql \
        btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql \
-       btree_gist--1.5--1.6.sql btree_gist--1.6--1.7.sql
+       btree_gist--1.5--1.6.sql btree_gist--1.6--1.7.sql \
+	   btree_gist--1.7--1.8.sql
 PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes"
 
 REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \
diff --git a/contrib/btree_gist/btree_cash.c b/contrib/btree_gist/btree_cash.c
index 546b948ea4..398282732b 100644
--- a/contrib/btree_gist/btree_cash.c
+++ b/contrib/btree_gist/btree_cash.c
@@ -6,6 +6,7 @@
 #include "btree_gist.h"
 #include "btree_utils_num.h"
 #include "common/int.h"
+#include "utils/builtins.h"
 #include "utils/cash.h"
 
 typedef struct
@@ -95,20 +96,7 @@ PG_FUNCTION_INFO_V1(cash_dist);
 Datum
 cash_dist(PG_FUNCTION_ARGS)
 {
-	Cash		a = PG_GETARG_CASH(0);
-	Cash		b = PG_GETARG_CASH(1);
-	Cash		r;
-	Cash		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("money out of range")));
-
-	ra = i64abs(r);
-
-	PG_RETURN_CASH(ra);
+	return cash_distance(fcinfo);
 }
 
 /**************************************************
diff --git a/contrib/btree_gist/btree_date.c b/contrib/btree_gist/btree_date.c
index 68a4107dbf..0262478265 100644
--- a/contrib/btree_gist/btree_date.c
+++ b/contrib/btree_gist/btree_date.c
@@ -118,12 +118,7 @@ PG_FUNCTION_INFO_V1(date_dist);
 Datum
 date_dist(PG_FUNCTION_ARGS)
 {
-	/* we assume the difference can't overflow */
-	Datum		diff = DirectFunctionCall2(date_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INT32(abs(DatumGetInt32(diff)));
+	return date_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index 84ca5eee50..7c9934feb8 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -6,6 +6,7 @@
 #include "btree_gist.h"
 #include "btree_utils_num.h"
 #include "utils/float.h"
+#include "utils/builtins.h"
 
 typedef struct float4key
 {
@@ -94,15 +95,7 @@ PG_FUNCTION_INFO_V1(float4_dist);
 Datum
 float4_dist(PG_FUNCTION_ARGS)
 {
-	float4		a = PG_GETARG_FLOAT4(0);
-	float4		b = PG_GETARG_FLOAT4(1);
-	float4		r;
-
-	r = a - b;
-	if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
-		float_overflow_error();
-
-	PG_RETURN_FLOAT4(fabsf(r));
+	return float4dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index 081a719b00..612f300059 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -6,6 +6,7 @@
 #include "btree_gist.h"
 #include "btree_utils_num.h"
 #include "utils/float.h"
+#include "utils/builtins.h"
 
 typedef struct float8key
 {
@@ -102,15 +103,7 @@ PG_FUNCTION_INFO_V1(float8_dist);
 Datum
 float8_dist(PG_FUNCTION_ARGS)
 {
-	float8		a = PG_GETARG_FLOAT8(0);
-	float8		b = PG_GETARG_FLOAT8(1);
-	float8		r;
-
-	r = a - b;
-	if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
-		float_overflow_error();
-
-	PG_RETURN_FLOAT8(fabs(r));
+	return float8dist(fcinfo);
 }
 
 /**************************************************
diff --git a/contrib/btree_gist/btree_gist--1.7--1.8.sql b/contrib/btree_gist/btree_gist--1.7--1.8.sql
new file mode 100644
index 0000000000..4dbdd13f4b
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.7--1.8.sql
@@ -0,0 +1,90 @@
+/* contrib/btree_gist/btree_gist--1.7--1.8.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.8'" to load this file. \quit
+
+-- drop btree_gist distance operators from opfamilies
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist DROP OPERATOR 15 (int2, int2);
+ALTER OPERATOR FAMILY gist_int4_ops USING gist DROP OPERATOR 15 (int4, int4);
+ALTER OPERATOR FAMILY gist_int8_ops USING gist DROP OPERATOR 15 (int8, int8);
+ALTER OPERATOR FAMILY gist_float4_ops USING gist DROP OPERATOR 15 (float4, float4);
+ALTER OPERATOR FAMILY gist_float8_ops USING gist DROP OPERATOR 15 (float8, float8);
+ALTER OPERATOR FAMILY gist_oid_ops USING gist DROP OPERATOR 15 (oid, oid);
+ALTER OPERATOR FAMILY gist_cash_ops USING gist DROP OPERATOR 15 (money, money);
+ALTER OPERATOR FAMILY gist_date_ops USING gist DROP OPERATOR 15 (date, date);
+ALTER OPERATOR FAMILY gist_time_ops USING gist DROP OPERATOR 15 (time, time);
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist DROP OPERATOR 15 (timestamp, timestamp);
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist DROP OPERATOR 15 (timestamptz, timestamptz);
+ALTER OPERATOR FAMILY gist_interval_ops USING gist DROP OPERATOR 15 (interval, interval);
+
+-- add pg_catalog distance operators to opfamilies
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops;
+ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops;
+ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops;
+ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (money, money) FOR ORDER BY pg_catalog.money_ops;
+ALTER OPERATOR FAMILY gist_date_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (date, date) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_time_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (time, time) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops;
+
+-- drop distance operators
+
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (int2, int2);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (int4, int4);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (int8, int8);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (float4, float4);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (float8, float8);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (oid, oid);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (money, money);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (date, date);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (time, time);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (interval, interval);
+
+DROP OPERATOR @extschema@.<-> (int2, int2);
+DROP OPERATOR @extschema@.<-> (int4, int4);
+DROP OPERATOR @extschema@.<-> (int8, int8);
+DROP OPERATOR @extschema@.<-> (float4, float4);
+DROP OPERATOR @extschema@.<-> (float8, float8);
+DROP OPERATOR @extschema@.<-> (oid, oid);
+DROP OPERATOR @extschema@.<-> (money, money);
+DROP OPERATOR @extschema@.<-> (date, date);
+DROP OPERATOR @extschema@.<-> (time, time);
+DROP OPERATOR @extschema@.<-> (timestamp, timestamp);
+DROP OPERATOR @extschema@.<-> (timestamptz, timestamptz);
+DROP OPERATOR @extschema@.<-> (interval, interval);
+
+-- drop distance functions
+
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.int2_dist(int2, int2);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.int4_dist(int4, int4);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.int8_dist(int8, int8);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.float4_dist(float4, float4);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.float8_dist(float8, float8);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.oid_dist(oid, oid);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.cash_dist(money, money);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.date_dist(date, date);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.time_dist(time, time);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.ts_dist(timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.tstz_dist(timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.interval_dist(interval, interval);
+
+DROP FUNCTION @extschema@.int2_dist(int2, int2);
+DROP FUNCTION @extschema@.int4_dist(int4, int4);
+DROP FUNCTION @extschema@.int8_dist(int8, int8);
+DROP FUNCTION @extschema@.float4_dist(float4, float4);
+DROP FUNCTION @extschema@.float8_dist(float8, float8);
+DROP FUNCTION @extschema@.oid_dist(oid, oid);
+DROP FUNCTION @extschema@.cash_dist(money, money);
+DROP FUNCTION @extschema@.date_dist(date, date);
+DROP FUNCTION @extschema@.time_dist(time, time);
+DROP FUNCTION @extschema@.ts_dist(timestamp, timestamp);
+DROP FUNCTION @extschema@.tstz_dist(timestamptz, timestamptz);
+DROP FUNCTION @extschema@.interval_dist(interval, interval);
diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control
index fa9171a80a..4c93737560 100644
--- a/contrib/btree_gist/btree_gist.control
+++ b/contrib/btree_gist/btree_gist.control
@@ -1,6 +1,6 @@
 # btree_gist extension
 comment = 'support for indexing common datatypes in GiST'
-default_version = '1.7'
+default_version = '1.8'
 module_pathname = '$libdir/btree_gist'
-relocatable = true
-trusted = true
+relocatable = false
+trusted = true
\ No newline at end of file
diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c
index fdbf156586..88214454b5 100644
--- a/contrib/btree_gist/btree_int2.c
+++ b/contrib/btree_gist/btree_int2.c
@@ -6,6 +6,7 @@
 #include "btree_gist.h"
 #include "btree_utils_num.h"
 #include "common/int.h"
+#include "utils/builtins.h"
 
 typedef struct int16key
 {
@@ -94,20 +95,7 @@ PG_FUNCTION_INFO_V1(int2_dist);
 Datum
 int2_dist(PG_FUNCTION_ARGS)
 {
-	int16		a = PG_GETARG_INT16(0);
-	int16		b = PG_GETARG_INT16(1);
-	int16		r;
-	int16		ra;
-
-	if (pg_sub_s16_overflow(a, b, &r) ||
-		r == PG_INT16_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("smallint out of range")));
-
-	ra = abs(r);
-
-	PG_RETURN_INT16(ra);
+	return int2dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c
index 8915fb5d08..2a4e2165f5 100644
--- a/contrib/btree_gist/btree_int4.c
+++ b/contrib/btree_gist/btree_int4.c
@@ -6,6 +6,7 @@
 #include "btree_gist.h"
 #include "btree_utils_num.h"
 #include "common/int.h"
+#include "utils/builtins.h"
 
 typedef struct int32key
 {
@@ -95,20 +96,7 @@ PG_FUNCTION_INFO_V1(int4_dist);
 Datum
 int4_dist(PG_FUNCTION_ARGS)
 {
-	int32		a = PG_GETARG_INT32(0);
-	int32		b = PG_GETARG_INT32(1);
-	int32		r;
-	int32		ra;
-
-	if (pg_sub_s32_overflow(a, b, &r) ||
-		r == PG_INT32_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("integer out of range")));
-
-	ra = abs(r);
-
-	PG_RETURN_INT32(ra);
+	return int4dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c
index 7c63a5b6dc..11f34d6506 100644
--- a/contrib/btree_gist/btree_int8.c
+++ b/contrib/btree_gist/btree_int8.c
@@ -6,6 +6,7 @@
 #include "btree_gist.h"
 #include "btree_utils_num.h"
 #include "common/int.h"
+#include "utils/builtins.h"
 
 typedef struct int64key
 {
@@ -95,20 +96,7 @@ PG_FUNCTION_INFO_V1(int8_dist);
 Datum
 int8_dist(PG_FUNCTION_ARGS)
 {
-	int64		a = PG_GETARG_INT64(0);
-	int64		b = PG_GETARG_INT64(1);
-	int64		r;
-	int64		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("bigint out of range")));
-
-	ra = i64abs(r);
-
-	PG_RETURN_INT64(ra);
+	return int8dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_interval.c b/contrib/btree_gist/btree_interval.c
index b0afdf02bb..bd9bdf960b 100644
--- a/contrib/btree_gist/btree_interval.c
+++ b/contrib/btree_gist/btree_interval.c
@@ -128,11 +128,7 @@ PG_FUNCTION_INFO_V1(interval_dist);
 Datum
 interval_dist(PG_FUNCTION_ARGS)
 {
-	Datum		diff = DirectFunctionCall2(interval_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+	return interval_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_oid.c b/contrib/btree_gist/btree_oid.c
index 3cc7d4245d..0561d86c69 100644
--- a/contrib/btree_gist/btree_oid.c
+++ b/contrib/btree_gist/btree_oid.c
@@ -5,6 +5,7 @@
 
 #include "btree_gist.h"
 #include "btree_utils_num.h"
+#include "utils/builtins.h"
 
 typedef struct
 {
@@ -100,15 +101,7 @@ PG_FUNCTION_INFO_V1(oid_dist);
 Datum
 oid_dist(PG_FUNCTION_ARGS)
 {
-	Oid			a = PG_GETARG_OID(0);
-	Oid			b = PG_GETARG_OID(1);
-	Oid			res;
-
-	if (a < b)
-		res = b - a;
-	else
-		res = a - b;
-	PG_RETURN_OID(res);
+	return oiddist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_time.c b/contrib/btree_gist/btree_time.c
index d89401c0f5..777082792f 100644
--- a/contrib/btree_gist/btree_time.c
+++ b/contrib/btree_gist/btree_time.c
@@ -141,11 +141,7 @@ PG_FUNCTION_INFO_V1(time_dist);
 Datum
 time_dist(PG_FUNCTION_ARGS)
 {
-	Datum		diff = DirectFunctionCall2(time_mi_time,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+	return time_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c
index 3f5ba91891..db0e5304fb 100644
--- a/contrib/btree_gist/btree_ts.c
+++ b/contrib/btree_gist/btree_ts.c
@@ -146,48 +146,14 @@ PG_FUNCTION_INFO_V1(ts_dist);
 Datum
 ts_dist(PG_FUNCTION_ARGS)
 {
-	Timestamp	a = PG_GETARG_TIMESTAMP(0);
-	Timestamp	b = PG_GETARG_TIMESTAMP(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-	else
-		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-												  PG_GETARG_DATUM(0),
-												  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
+	return timestamp_distance(fcinfo);
 }
 
 PG_FUNCTION_INFO_V1(tstz_dist);
 Datum
 tstz_dist(PG_FUNCTION_ARGS)
 {
-	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
-	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-
-	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-											  PG_GETARG_DATUM(0),
-											  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
+	return timestamptz_distance(fcinfo);
 }
 
 
diff --git a/doc/src/sgml/btree-gist.sgml b/doc/src/sgml/btree-gist.sgml
index 31e7c78aae..9d5c57c3e7 100644
--- a/doc/src/sgml/btree-gist.sgml
+++ b/doc/src/sgml/btree-gist.sgml
@@ -102,6 +102,19 @@ INSERT 0 1
  </sect2>
 
  <sect2 id="btree-gist-authors">
+  <title>Upgrade notes for version 1.6</title>
+
+  <para>
+   In version 1.6 <literal>btree_gist</literal> switched to using in-core
+   distance operators, and its own implementations were removed.  References to
+   these operators in <literal>btree_gist</literal> opclasses will be updated
+   automatically during the extension upgrade, but if the user has created
+   objects referencing these operators or functions, then these objects must be
+   dropped manually before updating the extension.
+  </para>
+ </sect2>
+
+ <sect2>
   <title>Authors</title>
 
   <para>
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index 32fbad2f57..4543cddac9 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -29,6 +29,7 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+#define SAMESIGN(a,b)	(((a) < 0) == ((b) < 0))
 
 /*************************************************************************
  * Private routines
@@ -1174,3 +1175,22 @@ int8_cash(PG_FUNCTION_ARGS)
 
 	PG_RETURN_CASH(result);
 }
+
+Datum
+cash_distance(PG_FUNCTION_ARGS)
+{
+	Cash		a = PG_GETARG_CASH(0);
+	Cash		b = PG_GETARG_CASH(1);
+	Cash		r;
+	Cash		ra;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("money out of range")));
+
+	ra = i64abs(r);
+
+	PG_RETURN_CASH(ra);
+}
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 9c854e0e5c..73e1a4f5b6 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -546,6 +546,16 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+Datum
+date_distance(PG_FUNCTION_ARGS)
+{
+	/* we assume the difference can't overflow */
+	Datum		diff = DirectFunctionCall2(date_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INT32(abs(DatumGetInt32(diff)));
+}
 
 /*
  * Promote date to timestamp.
@@ -840,6 +850,29 @@ date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2)
 	return timestamptz_cmp_internal(dt1, dt2);
 }
 
+Datum
+date_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+	Timestamp	dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
 Datum
 date_eq_timestamptz(PG_FUNCTION_ARGS)
 {
@@ -903,6 +936,30 @@ date_cmp_timestamptz(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(date_cmp_timestamptz_internal(dateVal, dt2));
 }
 
+Datum
+date_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
+
+
 Datum
 timestamp_eq_date(PG_FUNCTION_ARGS)
 {
@@ -966,6 +1023,29 @@ timestamp_cmp_date(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(-date_cmp_timestamp_internal(dateVal, dt1));
 }
 
+Datum
+timestamp_dist_date(PG_FUNCTION_ARGS)
+{
+	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	Timestamp	dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
 Datum
 timestamptz_eq_date(PG_FUNCTION_ARGS)
 {
@@ -1058,6 +1138,28 @@ in_range_date_interval(PG_FUNCTION_ARGS)
 							   BoolGetDatum(less));
 }
 
+Datum
+timestamptz_dist_date(PG_FUNCTION_ARGS)
+{
+	TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	TimestampTz dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
 
 /* extract_date()
  * Extract specified field from date type.
@@ -2251,6 +2353,16 @@ extract_time(PG_FUNCTION_ARGS)
 	return time_part_common(fcinfo, true);
 }
 
+Datum
+time_distance(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(time_mi_time,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
+
 
 /*****************************************************************************
  *	 Time With Time Zone ADT
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 901edcc896..08694b25d2 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -4175,3 +4175,52 @@ width_bucket_float8(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT32(result);
 }
+
+Datum
+float4dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float4		r;
+
+	r = a - b;
+	if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
+		float_overflow_error();
+
+	PG_RETURN_FLOAT4(fabsf(r));
+}
+
+Datum
+float8dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r;
+
+	r = a - b;
+	if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
+		float_overflow_error();
+
+	PG_RETURN_FLOAT8(fabs(r));
+}
+
+
+Datum
+float48dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(fabs(r));
+}
+
+Datum
+float84dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(fabs(r));
+}
\ No newline at end of file
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 234f20796b..87664cf2e6 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -1647,3 +1647,54 @@ generate_series_int4_support(PG_FUNCTION_ARGS)
 
 	PG_RETURN_POINTER(ret);
 }
+
+Datum
+int2dist(PG_FUNCTION_ARGS)
+{
+	int16		a = PG_GETARG_INT16(0);
+	int16		b = PG_GETARG_INT16(1);
+	int16		r;
+	int16		ra;
+
+	if (pg_sub_s16_overflow(a, b, &r) ||
+		r == PG_INT16_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("smallint out of range")));
+
+	ra = abs(r);
+
+	PG_RETURN_INT16(ra);
+}
+
+static int32
+int44_dist(int32 a, int32 b)
+{
+	int32		r;
+
+	if (pg_sub_s32_overflow(a, b, &r) ||
+		r == PG_INT32_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("integer out of range")));
+
+	return abs(r);
+}
+
+Datum
+int4dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int24dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT16(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int42dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT16(1)));
+}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index ede14086ae..482a74199a 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1522,3 +1522,47 @@ generate_series_int8_support(PG_FUNCTION_ARGS)
 
 	PG_RETURN_POINTER(ret);
 }
+
+static int64
+int88_dist(int64 a, int64 b)
+{
+	int64		r;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("bigint out of range")));
+
+	return i64abs(r);
+}
+
+Datum
+int8dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int82dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT16(1)));
+}
+
+Datum
+int84dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT32(1)));
+}
+
+Datum
+int28dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT16(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int48dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT32(0), PG_GETARG_INT64(1)));
+}
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index 62bcfc5b56..e9bedf648f 100644
--- a/src/backend/utils/adt/oid.c
+++ b/src/backend/utils/adt/oid.c
@@ -18,6 +18,7 @@
 #include <limits.h>
 
 #include "catalog/pg_type.h"
+#include "common/int.h"
 #include "libpq/pqformat.h"
 #include "nodes/miscnodes.h"
 #include "nodes/value.h"
@@ -390,3 +391,24 @@ oidvectorgt(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(cmp > 0);
 }
+
+Datum
+oiddist(PG_FUNCTION_ARGS)
+{
+	Oid			a = PG_GETARG_OID(0);
+	Oid			b = PG_GETARG_OID(1);
+	Oid			res;
+	bool		overflow;
+
+	if (a < b)
+		overflow = pg_sub_u32_overflow(b, a, &res);
+	else
+		overflow = pg_sub_u32_overflow(a, b, &res);
+
+	if (overflow)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("oid out of range")));
+
+	PG_RETURN_OID(res);
+}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 866fdd5af8..c5aafa29ad 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -2859,6 +2859,86 @@ timestamp_mi(PG_FUNCTION_ARGS)
 	PG_RETURN_INTERVAL_P(result);
 }
 
+Datum
+timestamp_distance(PG_FUNCTION_ARGS)
+{
+	Timestamp	a = PG_GETARG_TIMESTAMP(0);
+	Timestamp	b = PG_GETARG_TIMESTAMP(1);
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		Interval   *p = palloc(sizeof(Interval));
+
+		p->day = INT_MAX;
+		p->month = INT_MAX;
+		p->time = PG_INT64_MAX;
+		PG_RETURN_INTERVAL_P(p);
+	}
+	else
+		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+												  PG_GETARG_DATUM(0),
+												  PG_GETARG_DATUM(1)));
+	PG_RETURN_INTERVAL_P(abs_interval(r));
+}
+
+Interval *
+timestamp_dist_internal(Timestamp a, Timestamp b)
+{
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		return r;
+	}
+
+	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+											  TimestampGetDatum(a),
+											  TimestampGetDatum(b)));
+
+	return abs_interval(r);
+}
+
+Datum
+timestamptz_distance(PG_FUNCTION_ARGS)
+{
+	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
+	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(a, b));
+}
+
+Datum
+timestamp_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	Timestamp	ts1 = PG_GETARG_TIMESTAMP(0);
+	TimestampTz tstz2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz tstz1;
+
+	tstz1 = timestamp2timestamptz(ts1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+Datum
+timestamptz_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	TimestampTz tstz1 = PG_GETARG_TIMESTAMPTZ(0);
+	Timestamp	ts2 = PG_GETARG_TIMESTAMP(1);
+	TimestampTz tstz2;
+
+	tstz2 = timestamp2timestamptz(ts2);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+
 /*
  *	interval_justify_interval()
  *
@@ -4200,6 +4280,29 @@ interval_sum(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INTERVAL_P(result);
 }
+Interval *
+abs_interval(Interval *a)
+{
+	static Interval zero = {0, 0, 0};
+
+	if (DatumGetBool(DirectFunctionCall2(interval_lt,
+										 IntervalPGetDatum(a),
+										 IntervalPGetDatum(&zero))))
+		a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
+												  IntervalPGetDatum(a)));
+
+	return a;
+}
+
+Datum
+interval_distance(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(interval_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
 
 /* timestamp_age()
  * Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0e7511dde1..0d45443cb4 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2845,6 +2845,114 @@
   oprname => '-', oprleft => 'pg_lsn', oprright => 'numeric',
   oprresult => 'pg_lsn', oprcode => 'pg_lsn_mii' },
 
+# distance operators
+{ oid => '9447', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int2', oprresult => 'int2',
+  oprcode => 'int2dist'},
+{ oid => '9448', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int4', oprresult => 'int4',
+  oprcode => 'int4dist'},
+{ oid => '9449', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int8', oprresult => 'int8',
+  oprcode => 'int8dist'},
+{ oid => '9450', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'oid',
+  oprright => 'oid', oprresult => 'oid',
+  oprcode => 'oiddist'},
+{ oid => '9451', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float4', oprresult => 'float4',
+  oprcode => 'float4dist'},
+{ oid => '9452', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float8', oprresult => 'float8',
+  oprcode => 'float8dist'},
+{ oid => '9453', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'money',
+  oprright => 'money', oprresult => 'money',
+  oprcode => 'cash_distance'},
+{ oid => '9454', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'date', oprresult => 'int4',
+  oprcode => 'date_distance'},
+{ oid => '9455', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'time',
+  oprright => 'time', oprresult => 'interval',
+  oprcode => 'time_distance'},
+{ oid => '9456', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamp', oprresult => 'interval',
+  oprcode => 'timestamp_distance'},
+{ oid => '9457', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamptz', oprresult => 'interval',
+  oprcode => 'timestamptz_distance'},
+{ oid => '9458', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'interval',
+  oprright => 'interval', oprresult => 'interval',
+  oprcode => 'interval_distance'},
+
+# cross-type distance operators
+{ oid => '9432', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int2)',
+  oprcode => 'int24dist'},
+{ oid => '9433', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int2', oprresult => 'int4', oprcom => '<->(int2,int4)',
+  oprcode => 'int42dist'},
+{ oid => '9434', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int2)',
+  oprcode => 'int28dist'},
+{ oid => '9435', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int2', oprresult => 'int8', oprcom => '<->(int2,int8)',
+  oprcode => 'int82dist'},
+{ oid => '9436', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int4)',
+  oprcode => 'int48dist'},
+{ oid => '9437', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int4', oprresult => 'int8', oprcom => '<->(int4,int8)',
+  oprcode => 'int84dist'},
+{ oid => '9438', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float4)',
+  oprcode => 'float48dist'},
+{ oid => '9439', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float4', oprresult => 'float8', oprcom => '<->(float4,float8)',
+  oprcode => 'float84dist'},
+{ oid => '9440', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,date)',
+  oprcode => 'date_dist_timestamp'},
+{ oid => '9441', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamp)',
+  oprcode => 'timestamp_dist_date'},
+{ oid => '9442', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,date)',
+  oprcode => 'date_dist_timestamptz'},
+{ oid => '9443', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamptz)',
+  oprcode => 'timestamptz_dist_date'},
+{ oid => '9444', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamp)',
+  oprcode => 'timestamp_dist_timestamptz'},
+{ oid => '9445', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamptz)',
+  oprcode => 'timestamptz_dist_timestamp'},
+
 # enum operators
 { oid => '3516', descr => 'equal',
   oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'anyenum',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 58811a6530..add53c03ff 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12152,4 +12152,90 @@
   proargnames => '{summarized_tli,summarized_lsn,pending_lsn,summarizer_pid}',
   prosrc => 'pg_get_wal_summarizer_state' },
 
+# distance functions
+{ oid => '9406',
+  proname => 'int2dist', prorettype => 'int2',
+  proargtypes => 'int2 int2', prosrc => 'int2dist' },
+{ oid => '9407',
+  proname => 'int4dist', prorettype => 'int4',
+  proargtypes => 'int4 int4', prosrc => 'int4dist' },
+{ oid => '9408',
+  proname => 'int8dist', prorettype => 'int8',
+  proargtypes => 'int8 int8', prosrc => 'int8dist' },
+{ oid => '9409',
+  proname => 'oiddist', prorettype => 'oid',
+  proargtypes => 'oid oid', prosrc => 'oiddist' },
+{ oid => '9410',
+  proname => 'float4dist', prorettype => 'float4',
+  proargtypes => 'float4 float4', prosrc => 'float4dist' },
+{ oid => '9411',
+  proname => 'float8dist', prorettype => 'float8',
+  proargtypes => 'float8 float8', prosrc => 'float8dist' },
+{ oid => '9412',
+  proname => 'cash_distance', prorettype => 'money',
+  proargtypes => 'money money', prosrc => 'cash_distance' },
+{ oid => '9413',
+  proname => 'date_distance', prorettype => 'int4',
+  proargtypes => 'date date', prosrc => 'date_distance' },
+{ oid => '9414',
+  proname => 'time_distance', prorettype => 'interval',
+  proargtypes => 'time time', prosrc => 'time_distance' },
+{ oid => '9415',
+  proname => 'timestamp_distance', prorettype => 'interval',
+  proargtypes => 'timestamp timestamp', prosrc => 'timestamp_distance' },
+{ oid => '9416',
+  proname => 'timestamptz_distance', prorettype => 'interval',
+  proargtypes => 'timestamptz timestamptz', prosrc => 'timestamptz_distance' },
+{ oid => '9417',
+  proname => 'interval_distance', prorettype => 'interval',
+  proargtypes => 'interval interval', prosrc => 'interval_distance' },
+
+# cross-type distance functions
+{ oid => '9418',
+  proname => 'int24dist', prorettype => 'int4',
+  proargtypes => 'int2 int4', prosrc => 'int24dist' },
+{ oid => '9419',
+  proname => 'int28dist', prorettype => 'int8',
+  proargtypes => 'int2 int8', prosrc => 'int28dist' },
+{ oid => '9420',
+  proname => 'int42dist', prorettype => 'int4',
+  proargtypes => 'int4 int2', prosrc => 'int42dist' },
+{ oid => '9221',
+  proname => 'int48dist', prorettype => 'int8',
+  proargtypes => 'int4 int8', prosrc => 'int48dist' },
+{ oid => '9422',
+  proname => 'int82dist', prorettype => 'int8',
+  proargtypes => 'int8 int2', prosrc => 'int82dist' },
+{ oid => '9423',
+  proname => 'int84dist', prorettype => 'int8',
+  proargtypes => 'int8 int4', prosrc => 'int84dist' },
+{ oid => '9424',
+  proname => 'float48dist', prorettype => 'float8',
+  proargtypes => 'float4 float8', prosrc => 'float48dist' },
+{ oid => '9425',
+  proname => 'float84dist', prorettype => 'float8',
+  proargtypes => 'float8 float4', prosrc => 'float84dist' },
+{ oid => '9426',
+  proname => 'date_dist_timestamp', prorettype => 'interval',
+  proargtypes => 'date timestamp', prosrc => 'date_dist_timestamp' },
+{ oid => '9427',
+  proname => 'date_dist_timestamptz', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'date timestamptz',
+  prosrc => 'date_dist_timestamptz' },
+{ oid => '9428',
+  proname => 'timestamp_dist_date', prorettype => 'interval',
+  proargtypes => 'timestamp date', prosrc => 'timestamp_dist_date' },
+{ oid => '9429',
+  proname => 'timestamp_dist_timestamptz', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamp timestamptz',
+  prosrc => 'timestamp_dist_timestamptz' },
+{ oid => '9430',
+  proname => 'timestamptz_dist_date', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamptz date',
+  prosrc => 'timestamptz_dist_date' },
+{ oid => '9431',
+  proname => 'timestamptz_dist_timestamp', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamptz timestamp',
+  prosrc => 'timestamptz_dist_timestamp' },
+
 ]
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 460c75cfdd..682d3fa82f 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -361,4 +361,6 @@ extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 extern bool AdjustTimestampForTypmod(Timestamp *time, int32 typmod,
 									 struct Node *escontext);
 
+extern Interval *abs_interval(Interval *a);
+
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index a6ce03ed46..27015a36bb 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -126,9 +126,11 @@ extern Timestamp SetEpochTimestamp(void);
 extern void GetEpochTime(struct pg_tm *tm);
 
 extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern Interval *timestamp_dist_internal(Timestamp a, Timestamp b);
 
-/* timestamp comparison works for timestamptz also */
+/* timestamp comparison and distance works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
+#define timestamptz_dist_internal(dt1,dt2)	timestamp_dist_internal(dt1, dt2)
 
 extern TimestampTz timestamp2timestamptz_opt_overflow(Timestamp timestamp,
 													  int *overflow);
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
index f5949f3d17..611c669137 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -1532,3 +1532,67 @@ select make_time(10, 55, 100.1);
 ERROR:  time field value out of range: 10:55:100.1
 select make_time(24, 0, 2.1);
 ERROR:  time field value out of range: 24:00:2.1
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+ Fifteen | Distance 
+---------+----------
+         |    16006
+         |    15941
+         |     1802
+         |     1801
+         |     1800
+         |     1799
+         |     1436
+         |     1435
+         |     1434
+         |      308
+         |      307
+         |      306
+         |    13578
+         |    13944
+         |    14311
+         |  1475514
+(16 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+ Fifteen |               Distance                
+---------+---------------------------------------
+         | @ 16006 days 1 hour 23 mins 45 secs
+         | @ 15941 days 1 hour 23 mins 45 secs
+         | @ 1802 days 1 hour 23 mins 45 secs
+         | @ 1801 days 1 hour 23 mins 45 secs
+         | @ 1800 days 1 hour 23 mins 45 secs
+         | @ 1799 days 1 hour 23 mins 45 secs
+         | @ 1436 days 1 hour 23 mins 45 secs
+         | @ 1435 days 1 hour 23 mins 45 secs
+         | @ 1434 days 1 hour 23 mins 45 secs
+         | @ 308 days 1 hour 23 mins 45 secs
+         | @ 307 days 1 hour 23 mins 45 secs
+         | @ 306 days 1 hour 23 mins 45 secs
+         | @ 13577 days 22 hours 36 mins 15 secs
+         | @ 13943 days 22 hours 36 mins 15 secs
+         | @ 14310 days 22 hours 36 mins 15 secs
+         | @ 1475514 days 1 hour 23 mins 45 secs
+(16 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
+ Fifteen |                Distance                 
+---------+-----------------------------------------
+         | @ 16005 days 14 hours 23 mins 45 secs
+         | @ 15940 days 14 hours 23 mins 45 secs
+         | @ 1801 days 14 hours 23 mins 45 secs
+         | @ 1800 days 14 hours 23 mins 45 secs
+         | @ 1799 days 14 hours 23 mins 45 secs
+         | @ 1798 days 14 hours 23 mins 45 secs
+         | @ 1435 days 14 hours 23 mins 45 secs
+         | @ 1434 days 14 hours 23 mins 45 secs
+         | @ 1433 days 14 hours 23 mins 45 secs
+         | @ 307 days 14 hours 23 mins 45 secs
+         | @ 306 days 14 hours 23 mins 45 secs
+         | @ 305 days 15 hours 23 mins 45 secs
+         | @ 13578 days 8 hours 36 mins 15 secs
+         | @ 13944 days 8 hours 36 mins 15 secs
+         | @ 14311 days 8 hours 36 mins 15 secs
+         | @ 1475513 days 14 hours 23 mins 45 secs
+(16 rows)
+
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index 65ee82caae..a04ea173ca 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -318,6 +318,26 @@ SELECT * FROM FLOAT4_TBL;
  -1.2345679e-20
 (5 rows)
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+ five |       f1       |     dist      
+------+----------------+---------------
+      |              0 |        1004.3
+      |         -34.84 |       1039.14
+      |        -1004.3 |        2008.6
+      | -1.2345679e+20 | 1.2345679e+20
+      | -1.2345679e-20 |        1004.3
+(5 rows)
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+ five |       f1       |          dist          
+------+----------------+------------------------
+      |              0 |                 1004.3
+      |         -34.84 |     1039.1400001525878
+      |        -1004.3 |     2008.5999877929687
+      | -1.2345679e+20 | 1.2345678955701443e+20
+      | -1.2345679e-20 |                 1004.3
+(5 rows)
+
 -- test edge-case coercions to integer
 SELECT '32767.4'::float4::int2;
  int2  
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
index 344d6b7d6d..7fcf3b7d6e 100644
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -617,6 +617,27 @@ SELECT f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
  1.2345678901234e-200 |  2.3112042409018e-67
 (5 rows)
 
+-- distance
+SELECT f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+          f1          |         dist         
+----------------------+----------------------
+                    0 |               1004.3
+               1004.3 |                    0
+               -34.84 |              1039.14
+ 1.2345678901234e+200 | 1.2345678901234e+200
+ 1.2345678901234e-200 |               1004.3
+(5 rows)
+
+SELECT f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
+          f1          |         dist         
+----------------------+----------------------
+                    0 |     1004.29998779297
+               1004.3 | 1.22070312045253e-05
+               -34.84 |     1039.13998779297
+ 1.2345678901234e+200 | 1.2345678901234e+200
+ 1.2345678901234e-200 |     1004.29998779297
+(5 rows)
+
 SELECT * FROM FLOAT8_TBL;
           f1          
 ----------------------
diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out
index 4e03a5faee..0631e028c6 100644
--- a/src/test/regress/expected/int2.out
+++ b/src/test/regress/expected/int2.out
@@ -284,6 +284,39 @@ SELECT i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
  -32767 | -16383
 (5 rows)
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+ERROR:  smallint out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+ four |  f1   |   x   
+------+-------+-------
+      |     0 |     2
+      |  1234 |  1232
+      | -1234 |  1236
+      | 32767 | 32765
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
   text  
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
index b1a15888ef..261bc5b263 100644
--- a/src/test/regress/expected/int4.out
+++ b/src/test/regress/expected/int4.out
@@ -266,6 +266,38 @@ SELECT i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
  -2147483647 | -1073741823
 (5 rows)
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+ERROR:  integer out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+ five |     f1      |     x      
+------+-------------+------------
+      |           0 |          2
+      |      123456 |     123454
+      |     -123456 |     123458
+      |  2147483647 | 2147483645
+      | -2147483647 | 2147483649
+(5 rows)
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index fddc09f630..412ba3e4f4 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -452,6 +452,37 @@ SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 A
  4567890123457035 | -4567890123456543 | 1123700970370370094 |     0
 (5 rows)
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
 SELECT q2, abs(q2) FROM INT8_TBL;
         q2         |       abs        
 -------------------+------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index b79b6fcd4d..e4266786e9 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -325,6 +325,23 @@ SELECT -('-9223372036854775807 us'::interval); -- ok
 
 SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
 ERROR:  interval out of range
+SELECT f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+          ?column?          
+----------------------------
+ 2 days 02:59:00
+ 2 days -02:00:00
+ 8 days -03:00:00
+ 34 years -2 days -03:00:00
+ 3 mons -2 days -03:00:00
+ 2 days 03:00:14
+ 1 day 00:56:56
+ 6 years -2 days -03:00:00
+ 5 mons -2 days -03:00:00
+ 5 mons -2 days +09:00:00
+ infinity
+ infinity
+(12 rows)
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index 7fd4e31804..0336b743fe 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -125,6 +125,12 @@ SELECT m / 2::float4 FROM money_data;
    $61.50
 (1 row)
 
+SELECT m <-> '$123.45' FROM money_data;
+ ?column? 
+----------
+    $0.45
+(1 row)
+
 -- All true
 SELECT m = '$123.00' FROM money_data;
  ?column? 
diff --git a/src/test/regress/expected/oid.out b/src/test/regress/expected/oid.out
index b80cb47e0c..1d705042c2 100644
--- a/src/test/regress/expected/oid.out
+++ b/src/test/regress/expected/oid.out
@@ -181,4 +181,17 @@ SELECT o.* FROM OID_TBL o WHERE o.f1 > '1234';
    99999999
 (3 rows)
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+ eight |     f1     |  ?column?  
+-------+------------+------------
+       |       1234 |       1111
+       |       1235 |       1112
+       |        987 |        864
+       | 4294966256 | 4294966133
+       |   99999999 |   99999876
+       |          5 |        118
+       |         10 |        113
+       |         15 |        108
+(8 rows)
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out
index 4247fae941..38280d4449 100644
--- a/src/test/regress/expected/time.out
+++ b/src/test/regress/expected/time.out
@@ -229,3 +229,19 @@ SELECT date_part('epoch',       TIME '2020-05-26 13:30:25.575401');
  48625.575401
 (1 row)
 
+-- distance
+SELECT f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
+           Distance            
+-------------------------------
+ @ 1 hour 23 mins 45 secs
+ @ 23 mins 45 secs
+ @ 39 mins 15 secs
+ @ 10 hours 35 mins 15 secs
+ @ 10 hours 36 mins 15 secs
+ @ 10 hours 37 mins 15 secs
+ @ 22 hours 35 mins 15 secs
+ @ 22 hours 36 mins 14.99 secs
+ @ 14 hours 12 mins 54 secs
+ @ 14 hours 12 mins 54 secs
+(10 rows)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 835f0e5762..cfb299b6f1 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2187,3 +2187,424 @@ select age(timestamp '-infinity', timestamp 'infinity');
 
 select age(timestamp '-infinity', timestamp '-infinity');
 ERROR:  interval out of range
+SELECT make_timestamp(2014,12,28,6,30,45.887);
+        make_timestamp        
+------------------------------
+ Sun Dec 28 06:30:45.887 2014
+(1 row)
+
+-- distance operators
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+               Distance                
+---------------------------------------
+ infinity
+ infinity
+ @ 11356 days
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 58 secs
+ @ 1453 days 6 hours 27 mins 58.6 secs
+ @ 1453 days 6 hours 27 mins 58.5 secs
+ @ 1453 days 6 hours 27 mins 58.4 secs
+ @ 1493 days
+ @ 1492 days 20 hours 55 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1333 days 6 hours 27 mins 59 secs
+ @ 231 days 18 hours 19 mins 20 secs
+ @ 324 days 15 hours 45 mins 59 secs
+ @ 324 days 10 hours 45 mins 58 secs
+ @ 324 days 11 hours 45 mins 57 secs
+ @ 324 days 20 hours 45 mins 56 secs
+ @ 324 days 21 hours 45 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 28 mins
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1333 days 5 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1452 days 6 hours 27 mins 59 secs
+ @ 1451 days 6 hours 27 mins 59 secs
+ @ 1450 days 6 hours 27 mins 59 secs
+ @ 1449 days 6 hours 27 mins 59 secs
+ @ 1448 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 765901 days 6 hours 27 mins 59 secs
+ @ 695407 days 6 hours 27 mins 59 secs
+ @ 512786 days 6 hours 27 mins 59 secs
+ @ 330165 days 6 hours 27 mins 59 secs
+ @ 111019 days 6 hours 27 mins 59 secs
+ @ 74495 days 6 hours 27 mins 59 secs
+ @ 37971 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 35077 days 17 hours 32 mins 1 sec
+ @ 1801 days 6 hours 27 mins 59 secs
+ @ 1800 days 6 hours 27 mins 59 secs
+ @ 1799 days 6 hours 27 mins 59 secs
+ @ 1495 days 6 hours 27 mins 59 secs
+ @ 1494 days 6 hours 27 mins 59 secs
+ @ 1493 days 6 hours 27 mins 59 secs
+ @ 1435 days 6 hours 27 mins 59 secs
+ @ 1434 days 6 hours 27 mins 59 secs
+ @ 1130 days 6 hours 27 mins 59 secs
+ @ 1129 days 6 hours 27 mins 59 secs
+ @ 399 days 6 hours 27 mins 59 secs
+ @ 398 days 6 hours 27 mins 59 secs
+ @ 33 days 6 hours 27 mins 59 secs
+ @ 32 days 6 hours 27 mins 59 secs
+(65 rows)
+
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+               Distance                
+---------------------------------------
+ @ 11356 days
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 58 secs
+ @ 1453 days 6 hours 27 mins 58.6 secs
+ @ 1453 days 6 hours 27 mins 58.5 secs
+ @ 1453 days 6 hours 27 mins 58.4 secs
+ @ 1493 days
+ @ 1492 days 20 hours 55 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1333 days 6 hours 27 mins 59 secs
+ @ 231 days 18 hours 19 mins 20 secs
+ @ 324 days 15 hours 45 mins 59 secs
+ @ 324 days 10 hours 45 mins 58 secs
+ @ 324 days 11 hours 45 mins 57 secs
+ @ 324 days 20 hours 45 mins 56 secs
+ @ 324 days 21 hours 45 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 28 mins
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1333 days 5 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1452 days 6 hours 27 mins 59 secs
+ @ 1451 days 6 hours 27 mins 59 secs
+ @ 1450 days 6 hours 27 mins 59 secs
+ @ 1449 days 6 hours 27 mins 59 secs
+ @ 1448 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 765901 days 6 hours 27 mins 59 secs
+ @ 695407 days 6 hours 27 mins 59 secs
+ @ 512786 days 6 hours 27 mins 59 secs
+ @ 330165 days 6 hours 27 mins 59 secs
+ @ 111019 days 6 hours 27 mins 59 secs
+ @ 74495 days 6 hours 27 mins 59 secs
+ @ 37971 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 35077 days 17 hours 32 mins 1 sec
+ @ 1801 days 6 hours 27 mins 59 secs
+ @ 1800 days 6 hours 27 mins 59 secs
+ @ 1799 days 6 hours 27 mins 59 secs
+ @ 1495 days 6 hours 27 mins 59 secs
+ @ 1494 days 6 hours 27 mins 59 secs
+ @ 1493 days 6 hours 27 mins 59 secs
+ @ 1435 days 6 hours 27 mins 59 secs
+ @ 1434 days 6 hours 27 mins 59 secs
+ @ 1130 days 6 hours 27 mins 59 secs
+ @ 1129 days 6 hours 27 mins 59 secs
+ @ 399 days 6 hours 27 mins 59 secs
+ @ 398 days 6 hours 27 mins 59 secs
+ @ 33 days 6 hours 27 mins 59 secs
+ @ 32 days 6 hours 27 mins 59 secs
+(63 rows)
+
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+               Distance                
+---------------------------------------
+ infinity
+ infinity
+ @ 11356 days 1 hour 23 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 43 secs
+ @ 1453 days 7 hours 51 mins 43.6 secs
+ @ 1453 days 7 hours 51 mins 43.5 secs
+ @ 1453 days 7 hours 51 mins 43.4 secs
+ @ 1493 days 1 hour 23 mins 45 secs
+ @ 1492 days 22 hours 19 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1333 days 7 hours 51 mins 44 secs
+ @ 231 days 16 hours 55 mins 35 secs
+ @ 324 days 17 hours 9 mins 44 secs
+ @ 324 days 12 hours 9 mins 43 secs
+ @ 324 days 13 hours 9 mins 42 secs
+ @ 324 days 22 hours 9 mins 41 secs
+ @ 324 days 23 hours 9 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1333 days 6 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1452 days 7 hours 51 mins 44 secs
+ @ 1451 days 7 hours 51 mins 44 secs
+ @ 1450 days 7 hours 51 mins 44 secs
+ @ 1449 days 7 hours 51 mins 44 secs
+ @ 1448 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 765901 days 7 hours 51 mins 44 secs
+ @ 695407 days 7 hours 51 mins 44 secs
+ @ 512786 days 7 hours 51 mins 44 secs
+ @ 330165 days 7 hours 51 mins 44 secs
+ @ 111019 days 7 hours 51 mins 44 secs
+ @ 74495 days 7 hours 51 mins 44 secs
+ @ 37971 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 35077 days 16 hours 8 mins 16 secs
+ @ 1801 days 7 hours 51 mins 44 secs
+ @ 1800 days 7 hours 51 mins 44 secs
+ @ 1799 days 7 hours 51 mins 44 secs
+ @ 1495 days 7 hours 51 mins 44 secs
+ @ 1494 days 7 hours 51 mins 44 secs
+ @ 1493 days 7 hours 51 mins 44 secs
+ @ 1435 days 7 hours 51 mins 44 secs
+ @ 1434 days 7 hours 51 mins 44 secs
+ @ 1130 days 7 hours 51 mins 44 secs
+ @ 1129 days 7 hours 51 mins 44 secs
+ @ 399 days 7 hours 51 mins 44 secs
+ @ 398 days 7 hours 51 mins 44 secs
+ @ 33 days 7 hours 51 mins 44 secs
+ @ 32 days 7 hours 51 mins 44 secs
+(65 rows)
+
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+               Distance                
+---------------------------------------
+ @ 11356 days 1 hour 23 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 43 secs
+ @ 1453 days 7 hours 51 mins 43.6 secs
+ @ 1453 days 7 hours 51 mins 43.5 secs
+ @ 1453 days 7 hours 51 mins 43.4 secs
+ @ 1493 days 1 hour 23 mins 45 secs
+ @ 1492 days 22 hours 19 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1333 days 7 hours 51 mins 44 secs
+ @ 231 days 16 hours 55 mins 35 secs
+ @ 324 days 17 hours 9 mins 44 secs
+ @ 324 days 12 hours 9 mins 43 secs
+ @ 324 days 13 hours 9 mins 42 secs
+ @ 324 days 22 hours 9 mins 41 secs
+ @ 324 days 23 hours 9 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1333 days 6 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1452 days 7 hours 51 mins 44 secs
+ @ 1451 days 7 hours 51 mins 44 secs
+ @ 1450 days 7 hours 51 mins 44 secs
+ @ 1449 days 7 hours 51 mins 44 secs
+ @ 1448 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 765901 days 7 hours 51 mins 44 secs
+ @ 695407 days 7 hours 51 mins 44 secs
+ @ 512786 days 7 hours 51 mins 44 secs
+ @ 330165 days 7 hours 51 mins 44 secs
+ @ 111019 days 7 hours 51 mins 44 secs
+ @ 74495 days 7 hours 51 mins 44 secs
+ @ 37971 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 35077 days 16 hours 8 mins 16 secs
+ @ 1801 days 7 hours 51 mins 44 secs
+ @ 1800 days 7 hours 51 mins 44 secs
+ @ 1799 days 7 hours 51 mins 44 secs
+ @ 1495 days 7 hours 51 mins 44 secs
+ @ 1494 days 7 hours 51 mins 44 secs
+ @ 1493 days 7 hours 51 mins 44 secs
+ @ 1435 days 7 hours 51 mins 44 secs
+ @ 1434 days 7 hours 51 mins 44 secs
+ @ 1130 days 7 hours 51 mins 44 secs
+ @ 1129 days 7 hours 51 mins 44 secs
+ @ 399 days 7 hours 51 mins 44 secs
+ @ 398 days 7 hours 51 mins 44 secs
+ @ 33 days 7 hours 51 mins 44 secs
+ @ 32 days 7 hours 51 mins 44 secs
+(63 rows)
+
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+                Distance                
+----------------------------------------
+ infinity
+ infinity
+ @ 11355 days 14 hours 23 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 43 secs
+ @ 1452 days 20 hours 51 mins 43.6 secs
+ @ 1452 days 20 hours 51 mins 43.5 secs
+ @ 1452 days 20 hours 51 mins 43.4 secs
+ @ 1492 days 14 hours 23 mins 45 secs
+ @ 1492 days 11 hours 19 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1332 days 21 hours 51 mins 44 secs
+ @ 232 days 2 hours 55 mins 35 secs
+ @ 324 days 6 hours 9 mins 44 secs
+ @ 324 days 1 hour 9 mins 43 secs
+ @ 324 days 2 hours 9 mins 42 secs
+ @ 324 days 11 hours 9 mins 41 secs
+ @ 324 days 12 hours 9 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1332 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1451 days 20 hours 51 mins 44 secs
+ @ 1450 days 20 hours 51 mins 44 secs
+ @ 1449 days 20 hours 51 mins 44 secs
+ @ 1448 days 20 hours 51 mins 44 secs
+ @ 1447 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 765900 days 20 hours 51 mins 44 secs
+ @ 695406 days 20 hours 51 mins 44 secs
+ @ 512785 days 20 hours 51 mins 44 secs
+ @ 330164 days 20 hours 51 mins 44 secs
+ @ 111018 days 20 hours 51 mins 44 secs
+ @ 74494 days 20 hours 51 mins 44 secs
+ @ 37970 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 35078 days 3 hours 8 mins 16 secs
+ @ 1800 days 20 hours 51 mins 44 secs
+ @ 1799 days 20 hours 51 mins 44 secs
+ @ 1798 days 20 hours 51 mins 44 secs
+ @ 1494 days 20 hours 51 mins 44 secs
+ @ 1493 days 20 hours 51 mins 44 secs
+ @ 1492 days 20 hours 51 mins 44 secs
+ @ 1434 days 20 hours 51 mins 44 secs
+ @ 1433 days 20 hours 51 mins 44 secs
+ @ 1129 days 20 hours 51 mins 44 secs
+ @ 1128 days 20 hours 51 mins 44 secs
+ @ 398 days 20 hours 51 mins 44 secs
+ @ 397 days 20 hours 51 mins 44 secs
+ @ 32 days 20 hours 51 mins 44 secs
+ @ 31 days 20 hours 51 mins 44 secs
+(65 rows)
+
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+                Distance                
+----------------------------------------
+ @ 11355 days 14 hours 23 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 43 secs
+ @ 1452 days 20 hours 51 mins 43.6 secs
+ @ 1452 days 20 hours 51 mins 43.5 secs
+ @ 1452 days 20 hours 51 mins 43.4 secs
+ @ 1492 days 14 hours 23 mins 45 secs
+ @ 1492 days 11 hours 19 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1332 days 21 hours 51 mins 44 secs
+ @ 232 days 2 hours 55 mins 35 secs
+ @ 324 days 6 hours 9 mins 44 secs
+ @ 324 days 1 hour 9 mins 43 secs
+ @ 324 days 2 hours 9 mins 42 secs
+ @ 324 days 11 hours 9 mins 41 secs
+ @ 324 days 12 hours 9 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1332 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1451 days 20 hours 51 mins 44 secs
+ @ 1450 days 20 hours 51 mins 44 secs
+ @ 1449 days 20 hours 51 mins 44 secs
+ @ 1448 days 20 hours 51 mins 44 secs
+ @ 1447 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 765900 days 20 hours 51 mins 44 secs
+ @ 695406 days 20 hours 51 mins 44 secs
+ @ 512785 days 20 hours 51 mins 44 secs
+ @ 330164 days 20 hours 51 mins 44 secs
+ @ 111018 days 20 hours 51 mins 44 secs
+ @ 74494 days 20 hours 51 mins 44 secs
+ @ 37970 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 35078 days 3 hours 8 mins 16 secs
+ @ 1800 days 20 hours 51 mins 44 secs
+ @ 1799 days 20 hours 51 mins 44 secs
+ @ 1798 days 20 hours 51 mins 44 secs
+ @ 1494 days 20 hours 51 mins 44 secs
+ @ 1493 days 20 hours 51 mins 44 secs
+ @ 1492 days 20 hours 51 mins 44 secs
+ @ 1434 days 20 hours 51 mins 44 secs
+ @ 1433 days 20 hours 51 mins 44 secs
+ @ 1129 days 20 hours 51 mins 44 secs
+ @ 1128 days 20 hours 51 mins 44 secs
+ @ 398 days 20 hours 51 mins 44 secs
+ @ 397 days 20 hours 51 mins 44 secs
+ @ 32 days 20 hours 51 mins 44 secs
+ @ 31 days 20 hours 51 mins 44 secs
+(63 rows)
+
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index a084357480..1cf5959216 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -3272,3 +3272,424 @@ SELECT age(timestamptz '-infinity', timestamptz 'infinity');
 
 SELECT age(timestamptz '-infinity', timestamptz '-infinity');
 ERROR:  interval out of range
+-- distance operators
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+               Distance                
+---------------------------------------
+ infinity
+ infinity
+ @ 11356 days 8 hours
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 58 secs
+ @ 1453 days 6 hours 27 mins 58.6 secs
+ @ 1453 days 6 hours 27 mins 58.5 secs
+ @ 1453 days 6 hours 27 mins 58.4 secs
+ @ 1493 days
+ @ 1492 days 20 hours 55 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1333 days 7 hours 27 mins 59 secs
+ @ 231 days 17 hours 19 mins 20 secs
+ @ 324 days 15 hours 45 mins 59 secs
+ @ 324 days 19 hours 45 mins 58 secs
+ @ 324 days 21 hours 45 mins 57 secs
+ @ 324 days 20 hours 45 mins 56 secs
+ @ 324 days 22 hours 45 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 28 mins
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 14 hours 27 mins 59 secs
+ @ 1453 days 14 hours 27 mins 59 secs
+ @ 1453 days 14 hours 27 mins 59 secs
+ @ 1453 days 9 hours 27 mins 59 secs
+ @ 1303 days 10 hours 27 mins 59 secs
+ @ 1333 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1452 days 6 hours 27 mins 59 secs
+ @ 1451 days 6 hours 27 mins 59 secs
+ @ 1450 days 6 hours 27 mins 59 secs
+ @ 1449 days 6 hours 27 mins 59 secs
+ @ 1448 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 765901 days 6 hours 27 mins 59 secs
+ @ 695407 days 6 hours 27 mins 59 secs
+ @ 512786 days 6 hours 27 mins 59 secs
+ @ 330165 days 6 hours 27 mins 59 secs
+ @ 111019 days 6 hours 27 mins 59 secs
+ @ 74495 days 6 hours 27 mins 59 secs
+ @ 37971 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 35077 days 17 hours 32 mins 1 sec
+ @ 1801 days 6 hours 27 mins 59 secs
+ @ 1800 days 6 hours 27 mins 59 secs
+ @ 1799 days 6 hours 27 mins 59 secs
+ @ 1495 days 6 hours 27 mins 59 secs
+ @ 1494 days 6 hours 27 mins 59 secs
+ @ 1493 days 6 hours 27 mins 59 secs
+ @ 1435 days 6 hours 27 mins 59 secs
+ @ 1434 days 6 hours 27 mins 59 secs
+ @ 1130 days 6 hours 27 mins 59 secs
+ @ 1129 days 6 hours 27 mins 59 secs
+ @ 399 days 6 hours 27 mins 59 secs
+ @ 398 days 6 hours 27 mins 59 secs
+ @ 33 days 6 hours 27 mins 59 secs
+ @ 32 days 6 hours 27 mins 59 secs
+(66 rows)
+
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+               Distance                
+---------------------------------------
+ @ 11356 days 8 hours
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 58 secs
+ @ 1453 days 6 hours 27 mins 58.6 secs
+ @ 1453 days 6 hours 27 mins 58.5 secs
+ @ 1453 days 6 hours 27 mins 58.4 secs
+ @ 1493 days
+ @ 1492 days 20 hours 55 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1333 days 7 hours 27 mins 59 secs
+ @ 231 days 17 hours 19 mins 20 secs
+ @ 324 days 15 hours 45 mins 59 secs
+ @ 324 days 19 hours 45 mins 58 secs
+ @ 324 days 21 hours 45 mins 57 secs
+ @ 324 days 20 hours 45 mins 56 secs
+ @ 324 days 22 hours 45 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 28 mins
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 14 hours 27 mins 59 secs
+ @ 1453 days 14 hours 27 mins 59 secs
+ @ 1453 days 14 hours 27 mins 59 secs
+ @ 1453 days 9 hours 27 mins 59 secs
+ @ 1303 days 10 hours 27 mins 59 secs
+ @ 1333 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1452 days 6 hours 27 mins 59 secs
+ @ 1451 days 6 hours 27 mins 59 secs
+ @ 1450 days 6 hours 27 mins 59 secs
+ @ 1449 days 6 hours 27 mins 59 secs
+ @ 1448 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 765901 days 6 hours 27 mins 59 secs
+ @ 695407 days 6 hours 27 mins 59 secs
+ @ 512786 days 6 hours 27 mins 59 secs
+ @ 330165 days 6 hours 27 mins 59 secs
+ @ 111019 days 6 hours 27 mins 59 secs
+ @ 74495 days 6 hours 27 mins 59 secs
+ @ 37971 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 35077 days 17 hours 32 mins 1 sec
+ @ 1801 days 6 hours 27 mins 59 secs
+ @ 1800 days 6 hours 27 mins 59 secs
+ @ 1799 days 6 hours 27 mins 59 secs
+ @ 1495 days 6 hours 27 mins 59 secs
+ @ 1494 days 6 hours 27 mins 59 secs
+ @ 1493 days 6 hours 27 mins 59 secs
+ @ 1435 days 6 hours 27 mins 59 secs
+ @ 1434 days 6 hours 27 mins 59 secs
+ @ 1130 days 6 hours 27 mins 59 secs
+ @ 1129 days 6 hours 27 mins 59 secs
+ @ 399 days 6 hours 27 mins 59 secs
+ @ 398 days 6 hours 27 mins 59 secs
+ @ 33 days 6 hours 27 mins 59 secs
+ @ 32 days 6 hours 27 mins 59 secs
+(64 rows)
+
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+               Distance                
+---------------------------------------
+ infinity
+ infinity
+ @ 11356 days 9 hours 23 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 43 secs
+ @ 1453 days 7 hours 51 mins 43.6 secs
+ @ 1453 days 7 hours 51 mins 43.5 secs
+ @ 1453 days 7 hours 51 mins 43.4 secs
+ @ 1493 days 1 hour 23 mins 45 secs
+ @ 1492 days 22 hours 19 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1333 days 8 hours 51 mins 44 secs
+ @ 231 days 15 hours 55 mins 35 secs
+ @ 324 days 17 hours 9 mins 44 secs
+ @ 324 days 21 hours 9 mins 43 secs
+ @ 324 days 23 hours 9 mins 42 secs
+ @ 324 days 22 hours 9 mins 41 secs
+ @ 325 days 9 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 15 hours 51 mins 44 secs
+ @ 1453 days 15 hours 51 mins 44 secs
+ @ 1453 days 15 hours 51 mins 44 secs
+ @ 1453 days 10 hours 51 mins 44 secs
+ @ 1303 days 11 hours 51 mins 44 secs
+ @ 1333 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1452 days 7 hours 51 mins 44 secs
+ @ 1451 days 7 hours 51 mins 44 secs
+ @ 1450 days 7 hours 51 mins 44 secs
+ @ 1449 days 7 hours 51 mins 44 secs
+ @ 1448 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 765901 days 7 hours 51 mins 44 secs
+ @ 695407 days 7 hours 51 mins 44 secs
+ @ 512786 days 7 hours 51 mins 44 secs
+ @ 330165 days 7 hours 51 mins 44 secs
+ @ 111019 days 7 hours 51 mins 44 secs
+ @ 74495 days 7 hours 51 mins 44 secs
+ @ 37971 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 35077 days 16 hours 8 mins 16 secs
+ @ 1801 days 7 hours 51 mins 44 secs
+ @ 1800 days 7 hours 51 mins 44 secs
+ @ 1799 days 7 hours 51 mins 44 secs
+ @ 1495 days 7 hours 51 mins 44 secs
+ @ 1494 days 7 hours 51 mins 44 secs
+ @ 1493 days 7 hours 51 mins 44 secs
+ @ 1435 days 7 hours 51 mins 44 secs
+ @ 1434 days 7 hours 51 mins 44 secs
+ @ 1130 days 7 hours 51 mins 44 secs
+ @ 1129 days 7 hours 51 mins 44 secs
+ @ 399 days 7 hours 51 mins 44 secs
+ @ 398 days 7 hours 51 mins 44 secs
+ @ 33 days 7 hours 51 mins 44 secs
+ @ 32 days 7 hours 51 mins 44 secs
+(66 rows)
+
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+               Distance                
+---------------------------------------
+ @ 11356 days 9 hours 23 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 43 secs
+ @ 1453 days 7 hours 51 mins 43.6 secs
+ @ 1453 days 7 hours 51 mins 43.5 secs
+ @ 1453 days 7 hours 51 mins 43.4 secs
+ @ 1493 days 1 hour 23 mins 45 secs
+ @ 1492 days 22 hours 19 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1333 days 8 hours 51 mins 44 secs
+ @ 231 days 15 hours 55 mins 35 secs
+ @ 324 days 17 hours 9 mins 44 secs
+ @ 324 days 21 hours 9 mins 43 secs
+ @ 324 days 23 hours 9 mins 42 secs
+ @ 324 days 22 hours 9 mins 41 secs
+ @ 325 days 9 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 15 hours 51 mins 44 secs
+ @ 1453 days 15 hours 51 mins 44 secs
+ @ 1453 days 15 hours 51 mins 44 secs
+ @ 1453 days 10 hours 51 mins 44 secs
+ @ 1303 days 11 hours 51 mins 44 secs
+ @ 1333 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1452 days 7 hours 51 mins 44 secs
+ @ 1451 days 7 hours 51 mins 44 secs
+ @ 1450 days 7 hours 51 mins 44 secs
+ @ 1449 days 7 hours 51 mins 44 secs
+ @ 1448 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 765901 days 7 hours 51 mins 44 secs
+ @ 695407 days 7 hours 51 mins 44 secs
+ @ 512786 days 7 hours 51 mins 44 secs
+ @ 330165 days 7 hours 51 mins 44 secs
+ @ 111019 days 7 hours 51 mins 44 secs
+ @ 74495 days 7 hours 51 mins 44 secs
+ @ 37971 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 35077 days 16 hours 8 mins 16 secs
+ @ 1801 days 7 hours 51 mins 44 secs
+ @ 1800 days 7 hours 51 mins 44 secs
+ @ 1799 days 7 hours 51 mins 44 secs
+ @ 1495 days 7 hours 51 mins 44 secs
+ @ 1494 days 7 hours 51 mins 44 secs
+ @ 1493 days 7 hours 51 mins 44 secs
+ @ 1435 days 7 hours 51 mins 44 secs
+ @ 1434 days 7 hours 51 mins 44 secs
+ @ 1130 days 7 hours 51 mins 44 secs
+ @ 1129 days 7 hours 51 mins 44 secs
+ @ 399 days 7 hours 51 mins 44 secs
+ @ 398 days 7 hours 51 mins 44 secs
+ @ 33 days 7 hours 51 mins 44 secs
+ @ 32 days 7 hours 51 mins 44 secs
+(64 rows)
+
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+                Distance                
+----------------------------------------
+ infinity
+ infinity
+ @ 11355 days 22 hours 23 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 43 secs
+ @ 1452 days 20 hours 51 mins 43.6 secs
+ @ 1452 days 20 hours 51 mins 43.5 secs
+ @ 1452 days 20 hours 51 mins 43.4 secs
+ @ 1492 days 14 hours 23 mins 45 secs
+ @ 1492 days 11 hours 19 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1332 days 21 hours 51 mins 44 secs
+ @ 232 days 2 hours 55 mins 35 secs
+ @ 324 days 6 hours 9 mins 44 secs
+ @ 324 days 10 hours 9 mins 43 secs
+ @ 324 days 12 hours 9 mins 42 secs
+ @ 324 days 11 hours 9 mins 41 secs
+ @ 324 days 13 hours 9 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1453 days 4 hours 51 mins 44 secs
+ @ 1453 days 4 hours 51 mins 44 secs
+ @ 1453 days 4 hours 51 mins 44 secs
+ @ 1452 days 23 hours 51 mins 44 secs
+ @ 1303 days 51 mins 44 secs
+ @ 1332 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1451 days 20 hours 51 mins 44 secs
+ @ 1450 days 20 hours 51 mins 44 secs
+ @ 1449 days 20 hours 51 mins 44 secs
+ @ 1448 days 20 hours 51 mins 44 secs
+ @ 1447 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 765900 days 20 hours 51 mins 44 secs
+ @ 695406 days 20 hours 51 mins 44 secs
+ @ 512785 days 20 hours 51 mins 44 secs
+ @ 330164 days 20 hours 51 mins 44 secs
+ @ 111018 days 20 hours 51 mins 44 secs
+ @ 74494 days 20 hours 51 mins 44 secs
+ @ 37970 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 35078 days 3 hours 8 mins 16 secs
+ @ 1800 days 20 hours 51 mins 44 secs
+ @ 1799 days 20 hours 51 mins 44 secs
+ @ 1798 days 20 hours 51 mins 44 secs
+ @ 1494 days 20 hours 51 mins 44 secs
+ @ 1493 days 20 hours 51 mins 44 secs
+ @ 1492 days 20 hours 51 mins 44 secs
+ @ 1434 days 20 hours 51 mins 44 secs
+ @ 1433 days 20 hours 51 mins 44 secs
+ @ 1129 days 20 hours 51 mins 44 secs
+ @ 1128 days 20 hours 51 mins 44 secs
+ @ 398 days 20 hours 51 mins 44 secs
+ @ 397 days 20 hours 51 mins 44 secs
+ @ 32 days 20 hours 51 mins 44 secs
+ @ 31 days 20 hours 51 mins 44 secs
+(66 rows)
+
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+                Distance                
+----------------------------------------
+ @ 11355 days 22 hours 23 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 43 secs
+ @ 1452 days 20 hours 51 mins 43.6 secs
+ @ 1452 days 20 hours 51 mins 43.5 secs
+ @ 1452 days 20 hours 51 mins 43.4 secs
+ @ 1492 days 14 hours 23 mins 45 secs
+ @ 1492 days 11 hours 19 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1332 days 21 hours 51 mins 44 secs
+ @ 232 days 2 hours 55 mins 35 secs
+ @ 324 days 6 hours 9 mins 44 secs
+ @ 324 days 10 hours 9 mins 43 secs
+ @ 324 days 12 hours 9 mins 42 secs
+ @ 324 days 11 hours 9 mins 41 secs
+ @ 324 days 13 hours 9 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1453 days 4 hours 51 mins 44 secs
+ @ 1453 days 4 hours 51 mins 44 secs
+ @ 1453 days 4 hours 51 mins 44 secs
+ @ 1452 days 23 hours 51 mins 44 secs
+ @ 1303 days 51 mins 44 secs
+ @ 1332 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1451 days 20 hours 51 mins 44 secs
+ @ 1450 days 20 hours 51 mins 44 secs
+ @ 1449 days 20 hours 51 mins 44 secs
+ @ 1448 days 20 hours 51 mins 44 secs
+ @ 1447 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 765900 days 20 hours 51 mins 44 secs
+ @ 695406 days 20 hours 51 mins 44 secs
+ @ 512785 days 20 hours 51 mins 44 secs
+ @ 330164 days 20 hours 51 mins 44 secs
+ @ 111018 days 20 hours 51 mins 44 secs
+ @ 74494 days 20 hours 51 mins 44 secs
+ @ 37970 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 35078 days 3 hours 8 mins 16 secs
+ @ 1800 days 20 hours 51 mins 44 secs
+ @ 1799 days 20 hours 51 mins 44 secs
+ @ 1798 days 20 hours 51 mins 44 secs
+ @ 1494 days 20 hours 51 mins 44 secs
+ @ 1493 days 20 hours 51 mins 44 secs
+ @ 1492 days 20 hours 51 mins 44 secs
+ @ 1434 days 20 hours 51 mins 44 secs
+ @ 1433 days 20 hours 51 mins 44 secs
+ @ 1129 days 20 hours 51 mins 44 secs
+ @ 1128 days 20 hours 51 mins 44 secs
+ @ 398 days 20 hours 51 mins 44 secs
+ @ 397 days 20 hours 51 mins 44 secs
+ @ 32 days 20 hours 51 mins 44 secs
+ @ 31 days 20 hours 51 mins 44 secs
+(64 rows)
+
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
index 1c58ff6966..43bdd65417 100644
--- a/src/test/regress/sql/date.sql
+++ b/src/test/regress/sql/date.sql
@@ -373,3 +373,8 @@ select make_date(2013, 13, 1);
 select make_date(2013, 11, -1);
 select make_time(10, 55, 100.1);
 select make_time(24, 0, 2.1);
+
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
diff --git a/src/test/regress/sql/float4.sql b/src/test/regress/sql/float4.sql
index 8fb12368c3..594ac2fadf 100644
--- a/src/test/regress/sql/float4.sql
+++ b/src/test/regress/sql/float4.sql
@@ -100,6 +100,9 @@ UPDATE FLOAT4_TBL
 
 SELECT * FROM FLOAT4_TBL;
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+
 -- test edge-case coercions to integer
 SELECT '32767.4'::float4::int2;
 SELECT '32767.6'::float4::int2;
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
index 98e9926c9e..e477534a59 100644
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -176,6 +176,9 @@ SELECT ||/ float8 '27' AS three;
 
 SELECT f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
 
+-- distance
+SELECT f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+SELECT f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
 
 SELECT * FROM FLOAT8_TBL;
 
diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql
index df1e46d4e2..30678e0a13 100644
--- a/src/test/regress/sql/int2.sql
+++ b/src/test/regress/sql/int2.sql
@@ -87,6 +87,16 @@ SELECT i.f1, i.f1 / int2 '2' AS x FROM INT2_TBL i;
 
 SELECT i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
 SELECT ((-1::int2<<15)+1::int2)::text;
diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql
index e9d89e8111..888da6eede 100644
--- a/src/test/regress/sql/int4.sql
+++ b/src/test/regress/sql/int4.sql
@@ -87,6 +87,16 @@ SELECT i.f1, i.f1 / int2 '2' AS x FROM INT4_TBL i;
 
 SELECT i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql
index fffb28906a..4e759a9838 100644
--- a/src/test/regress/sql/int8.sql
+++ b/src/test/regress/sql/int8.sql
@@ -90,6 +90,11 @@ SELECT q1 + 42::int2 AS "8plus2", q1 - 42::int2 AS "8minus2", q1 * 42::int2 AS "
 -- int2 op int8
 SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 AS "2mul8", 246::int2 / q1 AS "2div8" FROM INT8_TBL;
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+
 SELECT q2, abs(q2) FROM INT8_TBL;
 SELECT min(q1), min(q2) FROM INT8_TBL;
 SELECT max(q1), max(q2) FROM INT8_TBL;
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 5566ad0e51..89f409c976 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -81,6 +81,8 @@ SELECT -('-9223372036854775808 us'::interval); -- should fail
 SELECT -('-9223372036854775807 us'::interval); -- ok
 SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
 
+SELECT f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql
index 81c92dd960..2cd65c1f59 100644
--- a/src/test/regress/sql/money.sql
+++ b/src/test/regress/sql/money.sql
@@ -27,6 +27,7 @@ SELECT m / 2::float8 FROM money_data;
 SELECT m * 2::float4 FROM money_data;
 SELECT 2::float4 * m FROM money_data;
 SELECT m / 2::float4 FROM money_data;
+SELECT m <-> '$123.45' FROM money_data;
 
 -- All true
 SELECT m = '$123.00' FROM money_data;
diff --git a/src/test/regress/sql/oid.sql b/src/test/regress/sql/oid.sql
index a96b2aa1e3..223c082c73 100644
--- a/src/test/regress/sql/oid.sql
+++ b/src/test/regress/sql/oid.sql
@@ -54,4 +54,6 @@ SELECT o.* FROM OID_TBL o WHERE o.f1 >= '1234';
 
 SELECT o.* FROM OID_TBL o WHERE o.f1 > '1234';
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/sql/time.sql b/src/test/regress/sql/time.sql
index eb375a36e9..4c40158669 100644
--- a/src/test/regress/sql/time.sql
+++ b/src/test/regress/sql/time.sql
@@ -77,3 +77,6 @@ SELECT date_part('microsecond', TIME '2020-05-26 13:30:25.575401');
 SELECT date_part('millisecond', TIME '2020-05-26 13:30:25.575401');
 SELECT date_part('second',      TIME '2020-05-26 13:30:25.575401');
 SELECT date_part('epoch',       TIME '2020-05-26 13:30:25.575401');
+
+-- distance
+SELECT f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index ea12ffd18d..d449b5d3f1 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -416,3 +416,12 @@ select age(timestamp 'infinity', timestamp 'infinity');
 select age(timestamp 'infinity', timestamp '-infinity');
 select age(timestamp '-infinity', timestamp 'infinity');
 select age(timestamp '-infinity', timestamp '-infinity');
+SELECT make_timestamp(2014,12,28,6,30,45.887);
+
+-- distance operators
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index a2dcd5f5d8..fdd9a9a72c 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -660,3 +660,11 @@ SELECT age(timestamptz 'infinity', timestamptz 'infinity');
 SELECT age(timestamptz 'infinity', timestamptz '-infinity');
 SELECT age(timestamptz '-infinity', timestamptz 'infinity');
 SELECT age(timestamptz '-infinity', timestamptz '-infinity');
+
+-- distance operators
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
-- 
2.43.0

v18-0005-Add-knn-support-to-btree-indexes.patchtext/x-patch; charset=UTF-8; name=v18-0005-Add-knn-support-to-btree-indexes.patchDownload
From db9cbf72b9bdc2a0318eb1d8148cc26d62300624 Mon Sep 17 00:00:00 2001
From: "Anton A. Melnikov" <a.melnikov@postgrespro.ru>
Date: Mon, 1 Jan 2024 05:02:10 +0300
Subject: [PATCH 5/5] Add knn support to btree indexes

This commit implements support for knn scans in btree indexes.  When knn search
is requested, btree index is traversed ascending and descending simultaneously.
At each step the closest tuple is returned.  Filtering operators can reduce
knn to regular ordered scan.

Ordering operators are added to opfamilies of scalar datatypes.  No extra
supporting functions are required: knn-btree algorithm works using comparison
function and ordering operator itself.

Distance operators are not leakproof, because they throw error on overflow.
Therefore we relax opr_sanity check for btree ordering operators.  It's OK for
them to be leaky while comparison function is leakproof.

Catversion is bumped.

Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru
Author: Nikita Glukhov
Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov
---
 doc/src/sgml/btree.sgml                     |  47 +
 doc/src/sgml/indices.sgml                   |  11 +
 doc/src/sgml/xindex.sgml                    |   7 +-
 src/backend/access/brin/brin_minmax.c       |   6 +-
 src/backend/access/nbtree/README            |  22 +
 src/backend/access/nbtree/nbtree.c          | 206 ++++-
 src/backend/access/nbtree/nbtsearch.c       | 363 +++++++-
 src/backend/access/nbtree/nbtutils.c        | 453 +++++++++-
 src/backend/access/nbtree/nbtvalidate.c     |  45 +-
 src/backend/partitioning/partprune.c        |   4 +-
 src/include/access/nbtree.h                 |  32 +-
 src/include/access/stratnum.h               |   7 +-
 src/include/catalog/pg_amop.dat             | 104 +++
 src/test/regress/expected/alter_generic.out |  13 +-
 src/test/regress/expected/amutils.out       |   6 +-
 src/test/regress/expected/btree_index.out   | 954 ++++++++++++++++++++
 src/test/regress/expected/opr_sanity.out    |  10 +-
 src/test/regress/expected/psql.out          |  52 +-
 src/test/regress/sql/alter_generic.sql      |   8 +-
 src/test/regress/sql/btree_index.sql        | 312 +++++++
 src/test/regress/sql/opr_sanity.sql         |   7 +-
 21 files changed, 2498 insertions(+), 171 deletions(-)

diff --git a/doc/src/sgml/btree.sgml b/doc/src/sgml/btree.sgml
index 6f608a14bf..fb67e0ad22 100644
--- a/doc/src/sgml/btree.sgml
+++ b/doc/src/sgml/btree.sgml
@@ -200,6 +200,53 @@
   planner relies on them for optimization purposes.
  </para>
 
+ <para>
+  In order to implement the distance ordered (nearest-neighbor) search,
+  one needs to define a distance operator (usually it's called
+  <literal>&lt;-&gt;</literal>) with a correpsonding operator family for
+  distance comparison in the operator class.  These operators must
+  satisfy the following assumptions for all non-null values
+  <replaceable>A</replaceable>, <replaceable>B</replaceable>,
+  <replaceable>C</replaceable> of the data type:
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     <replaceable>A</replaceable> <literal>&lt;-&gt;</literal>
+     <replaceable>B</replaceable> <literal>=</literal>
+     <replaceable>B</replaceable> <literal>&lt;-&gt;</literal>
+     <replaceable>A</replaceable>
+     (<firstterm>symmetric law</firstterm>)
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     if <replaceable>A</replaceable> <literal>=</literal>
+     <replaceable>B</replaceable>, then <replaceable>A</replaceable>
+     <literal>&lt;-&gt;</literal> <replaceable>C</replaceable>
+     <literal>=</literal> <replaceable>B</replaceable>
+     <literal>&lt;-&gt;</literal> <replaceable>C</replaceable>
+     (<firstterm>distance equivalence</firstterm>)
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      if (<replaceable>A</replaceable> <literal>&lt;=</literal>
+      <replaceable>B</replaceable> and <replaceable>B</replaceable>
+      <literal>&lt;=</literal> <replaceable>C</replaceable>) or
+      (<replaceable>A</replaceable> <literal>&gt;=</literal>
+      <replaceable>B</replaceable> and <replaceable>B</replaceable>
+      <literal>&gt;=</literal> <replaceable>C</replaceable>),
+      then <replaceable>A</replaceable> <literal>&lt;-&gt;</literal>
+      <replaceable>B</replaceable> <literal>&lt;=</literal>
+      <replaceable>A</replaceable> <literal>&lt;-&gt;</literal>
+      <replaceable>C</replaceable>
+     (<firstterm>monotonicity</firstterm>)
+    </para>
+   </listitem>
+  </itemizedlist>
+ </para>
+
 </sect1>
 
 <sect1 id="btree-support-funcs">
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 6d731e0701..b841e1f0a5 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -1193,6 +1193,17 @@ SELECT x FROM tab WHERE x = 'key' AND z &lt; 42;
    make this type of scan very useful in practice.
   </para>
 
+  <para>
+   B-tree indexes are also capable of optimizing <quote>nearest-neighbor</quote>
+   searches, such as
+<programlisting><![CDATA[
+SELECT * FROM events ORDER BY event_date <-> date '2017-05-05' LIMIT 10;
+]]>
+</programlisting>
+   which finds the ten events closest to a given target date.  The ability
+   to do this is again dependent on the particular operator class being used.
+  </para>
+
   <para>
    <indexterm>
     <primary><literal>INCLUDE</literal></primary>
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 22d8ad1aac..4636dce2a9 100644
--- a/doc/src/sgml/xindex.sgml
+++ b/doc/src/sgml/xindex.sgml
@@ -131,6 +131,10 @@
        <entry>greater than</entry>
        <entry>5</entry>
       </row>
+      <row>
+       <entry>distance</entry>
+       <entry>6</entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
@@ -1320,7 +1324,8 @@ SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
   <title>Ordering Operators</title>
 
   <para>
-   Some index access methods (currently, only GiST and SP-GiST) support the concept of
+   Some index access methods (currently, B-tree, GiST and SP-GiST)
+   support the concept of
    <firstterm>ordering operators</firstterm>.  What we have been discussing so far
    are <firstterm>search operators</firstterm>.  A search operator is one for which
    the index can be searched to find all rows satisfying
diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c
index 6b3dd21e42..effaa949d7 100644
--- a/src/backend/access/brin/brin_minmax.c
+++ b/src/backend/access/brin/brin_minmax.c
@@ -25,7 +25,7 @@
 typedef struct MinmaxOpaque
 {
 	Oid			cached_subtype;
-	FmgrInfo	strategy_procinfos[BTMaxStrategyNumber];
+	FmgrInfo	strategy_procinfos[BTMaxSearchStrategyNumber];
 } MinmaxOpaque;
 
 static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno,
@@ -266,7 +266,7 @@ minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype,
 	MinmaxOpaque *opaque;
 
 	Assert(strategynum >= 1 &&
-		   strategynum <= BTMaxStrategyNumber);
+		   strategynum <= BTMaxSearchStrategyNumber);
 
 	opaque = (MinmaxOpaque *) bdesc->bd_info[attno - 1]->oi_opaque;
 
@@ -279,7 +279,7 @@ minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype,
 	{
 		uint16		i;
 
-		for (i = 1; i <= BTMaxStrategyNumber; i++)
+		for (i = 1; i <= BTMaxSearchStrategyNumber; i++)
 			opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid;
 		opaque->cached_subtype = subtype;
 	}
diff --git a/src/backend/access/nbtree/README b/src/backend/access/nbtree/README
index 52e646c7f7..0db79b64d6 100644
--- a/src/backend/access/nbtree/README
+++ b/src/backend/access/nbtree/README
@@ -1081,3 +1081,25 @@ item is irrelevant, and need not be stored at all.  This arrangement
 corresponds to the fact that an L&Y non-leaf page has one more pointer
 than key.  Suffix truncation's negative infinity attributes behave in
 the same way.
+
+Nearest-neighbor search
+-----------------------
+
+B-tree supports a special scan strategy for nearest-neighbor (kNN) search,
+which is used for queries with "ORDER BY indexed_column operator constant"
+clause.  See the following example.
+
+  SELECT * FROM tab WHERE col > const1 ORDER BY col <-> const2 LIMIT k
+
+Unlike GiST and SP-GiST, B-tree supports kNN by the only one ordering operator
+applied to the first indexed column.
+
+At the beginning of kNN scan, we determine the scan strategy to use: normal
+unidirectional or special bidirectional.  If the second distance operand falls
+into the scan range, then we use bidirectional scan, otherwise we use normal
+unidirectional scan.
+
+The bidirectional scan algorithm is quite simple.  We start both forward and
+backward scans starting from the tree location corresponding to the second
+distance operand.  Each time we need the next tuple, we return the nearest
+tuple from two directions and advance scan in corresponding direction.
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 97a4e423f7..e0465d9957 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -35,6 +35,7 @@
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/index_selfuncs.h"
 #include "utils/memutils.h"
 
@@ -65,7 +66,8 @@ typedef enum
  */
 typedef struct BTParallelScanDescData
 {
-	BlockNumber btps_scanPage;	/* latest or next page to be scanned */
+	BlockNumber btps_forwardScanPage;	/* latest or next page to be scanned */
+	BlockNumber btps_backwardScanPage;	/* secondary kNN page to be scanned */
 	BTPS_State	btps_pageStatus;	/* indicates whether next page is
 									 * available for scan. see above for
 									 * possible states of parallel scan. */
@@ -101,8 +103,8 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amsupport = BTNProcs;
 	amroutine->amoptsprocnum = BTOPTIONS_PROC;
 	amroutine->amcanorder = true;
-	amroutine->amcanorderbyop = false;
-	amroutine->amorderbyopfirstcol = false;
+	amroutine->amcanorderbyop = true;
+	amroutine->amorderbyopfirstcol = true;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = true;
 	amroutine->amcanmulticol = true;
@@ -218,23 +220,32 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTScanState state = &so->state;
+	ScanDirection arraydir = dir;
 	bool		res;
 
+	if (scan->numberOfOrderBys > 0 && !ScanDirectionIsForward(dir))
+		elog(ERROR, "btree does not support backward order-by-distance scanning");
+
 	/* btree indexes are never lossy */
 	scan->xs_recheck = false;
+	scan->xs_recheckorderby = false;
+
+	if (so->scanDirection != NoMovementScanDirection)
+		dir = so->scanDirection;
 
 	/*
 	 * If we have any array keys, initialize them during first call for a
 	 * scan.  We can't do this in btrescan because we don't know the scan
 	 * direction at that time.
 	 */
-	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos))
+	if (so->numArrayKeys && !BTScanPosIsValid(state->currPos) &&
+		(!so->backwardState || !BTScanPosIsValid(so->backwardState->currPos)))
 	{
 		/* punt if we have any unsatisfiable array keys */
 		if (so->numArrayKeys < 0)
 			return false;
 
-		_bt_start_array_keys(scan, dir);
+		_bt_start_array_keys(scan, arraydir);
 	}
 
 	/* This loop handles advancing to the next array elements, if any */
@@ -245,7 +256,8 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(state->currPos))
+		if (!BTScanPosIsValid(state->currPos) &&
+			(!so->backwardState || !BTScanPosIsValid(so->backwardState->currPos)))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -280,7 +292,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		if (res)
 			break;
 		/* ... otherwise see if we have more array keys to deal with */
-	} while (so->numArrayKeys && _bt_advance_array_keys(scan, dir));
+	} while (so->numArrayKeys && _bt_advance_array_keys(scan, arraydir));
 
 	return res;
 }
@@ -353,9 +365,6 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	IndexScanDesc scan;
 	BTScanOpaque so;
 
-	/* no order by operators allowed */
-	Assert(norderbys == 0);
-
 	/* get the scan */
 	scan = RelationGetIndexScan(rel, nkeys, norderbys);
 
@@ -383,6 +392,9 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
 	so->state.currTuples = so->state.markTuples = NULL;
+	so->backwardState = NULL;
+	so->distanceTypeByVal = true;
+	so->scanDirection = NoMovementScanDirection;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -412,6 +424,8 @@ _bt_release_current_position(BTScanState state, Relation indexRelation,
 static void
 _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	/* No need to invalidate positions, if the RAM is about to be freed. */
 	_bt_release_current_position(state, scan->indexRelation, !free);
 
@@ -428,6 +442,18 @@ _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 	}
 	else
 		BTScanPosInvalidate(state->markPos);
+
+	if (!so->distanceTypeByVal)
+	{
+		if (DatumGetPointer(state->currDistance))
+			pfree(DatumGetPointer(state->currDistance));
+
+		if (DatumGetPointer(state->markDistance))
+			pfree(DatumGetPointer(state->markDistance));
+	}
+
+	state->currDistance = (Datum) 0;
+	state->markDistance = (Datum) 0;
 }
 
 /*
@@ -442,6 +468,13 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 
 	_bt_release_scan_state(scan, state, false);
 
+	if (so->backwardState)
+	{
+		_bt_release_scan_state(scan, so->backwardState, true);
+		pfree(so->backwardState);
+		so->backwardState = NULL;
+	}
+
 	so->arrayKeyCount = 0;
 
 	/*
@@ -472,6 +505,14 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 				scan->numberOfKeys * sizeof(ScanKeyData));
 	so->numberOfKeys = 0;		/* until _bt_preprocess_keys sets it */
 
+	if (orderbys && scan->numberOfOrderBys > 0)
+		memmove(scan->orderByData,
+				orderbys,
+				scan->numberOfOrderBys * sizeof(ScanKeyData));
+
+	so->scanDirection = NoMovementScanDirection;
+	so->distanceTypeByVal = true;
+
 	/* If any keys are SK_SEARCHARRAY type, set up array-key info */
 	_bt_preprocess_array_keys(scan);
 }
@@ -486,6 +527,12 @@ btendscan(IndexScanDesc scan)
 
 	_bt_release_scan_state(scan, &so->state, true);
 
+	if (so->backwardState)
+	{
+		_bt_release_scan_state(scan, so->backwardState, true);
+		pfree(so->backwardState);
+	}
+
 	/* Release storage */
 	if (so->keyData != NULL)
 		pfree(so->keyData);
@@ -497,7 +544,7 @@ btendscan(IndexScanDesc scan)
 }
 
 static void
-_bt_mark_current_position(BTScanState state)
+_bt_mark_current_position(BTScanOpaque so, BTScanState state)
 {
 	/* There may be an old mark with a pin (but no lock). */
 	BTScanPosUnpinIfPinned(state->markPos);
@@ -515,6 +562,25 @@ _bt_mark_current_position(BTScanState state)
 		BTScanPosInvalidate(state->markPos);
 		state->markItemIndex = -1;
 	}
+
+	if (so->backwardState)
+	{
+		if (!so->distanceTypeByVal && DatumGetPointer(state->markDistance))
+			pfree(DatumGetPointer(state->markDistance));
+
+		if (!BTScanPosIsValid(state->currPos) || state->currIsNull)
+		{
+			state->markIsNull = true;
+			state->markDistance = (Datum) 0;
+		}
+		else
+		{
+			state->markIsNull = false;
+			state->markDistance = datumCopy(state->currDistance,
+											so->distanceTypeByVal,
+											so->distanceTypeLen);
+		}
+	}
 }
 
 /*
@@ -525,7 +591,13 @@ btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_mark_current_position(&so->state);
+	_bt_mark_current_position(so, &so->state);
+
+	if (so->backwardState)
+	{
+		_bt_mark_current_position(so, so->backwardState);
+		so->markRightIsNearest = so->currRightIsNearest;
+	}
 
 	/* Also record the current positions of any array keys */
 	if (so->numArrayKeys)
@@ -535,6 +607,8 @@ btmarkpos(IndexScanDesc scan)
 static void
 _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	if (state->markItemIndex >= 0)
 	{
 		/*
@@ -570,6 +644,22 @@ _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 					   state->markPos.nextTupleOffset);
 		}
 	}
+
+	/*
+	 * For bidirectional nearest neighbor scan we also need to restore the
+	 * distance to the current item.
+	 */
+	if (so->useBidirectionalKnnScan)
+	{
+		if (!so->distanceTypeByVal && DatumGetPointer(state->currDistance))
+			pfree(DatumGetPointer(state->currDistance));
+
+		state->currIsNull = state->markIsNull;
+		state->currDistance = state->markIsNull ? (Datum) 0 :
+			datumCopy(state->markDistance,
+					  so->distanceTypeByVal,
+					  so->distanceTypeLen);
+	}
 }
 
 /*
@@ -590,7 +680,8 @@ btinitparallelscan(void *target)
 	BTParallelScanDesc bt_target = (BTParallelScanDesc) target;
 
 	SpinLockInit(&bt_target->btps_mutex);
-	bt_target->btps_scanPage = InvalidBlockNumber;
+	bt_target->btps_forwardScanPage = InvalidBlockNumber;
+	bt_target->btps_backwardScanPage = InvalidBlockNumber;
 	bt_target->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	bt_target->btps_arrayKeyCount = 0;
 	ConditionVariableInit(&bt_target->btps_cv);
@@ -616,7 +707,8 @@ btparallelrescan(IndexScanDesc scan)
 	 * consistency.
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
-	btscan->btps_scanPage = InvalidBlockNumber;
+	btscan->btps_forwardScanPage = InvalidBlockNumber;
+	btscan->btps_backwardScanPage = InvalidBlockNumber;
 	btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	btscan->btps_arrayKeyCount = 0;
 	SpinLockRelease(&btscan->btps_mutex);
@@ -641,7 +733,7 @@ btparallelrescan(IndexScanDesc scan)
  * Callers should ignore the value of pageno if the return value is false.
  */
 bool
-_bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
+_bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTPS_State	pageStatus;
@@ -649,12 +741,17 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
 	bool		status = true;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
 
 	*pageno = P_NONE;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	scanPage = state == so->backwardState ?
+		&btscan->btps_backwardScanPage :
+		&btscan->btps_forwardScanPage;
+
 	while (1)
 	{
 		SpinLockAcquire(&btscan->btps_mutex);
@@ -680,7 +777,7 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
 			 * of advancing it to a new page!
 			 */
 			btscan->btps_pageStatus = BTPARALLEL_ADVANCING;
-			*pageno = btscan->btps_scanPage;
+			*pageno = *scanPage;
 			exit_loop = true;
 		}
 		SpinLockRelease(&btscan->btps_mutex);
@@ -699,19 +796,44 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
  *		can now begin advancing the scan.
  */
 void
-_bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
+_bt_parallel_release(IndexScanDesc scan, BTScanState state,
+					 BlockNumber scan_page)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
+	bool		status_changed = false;
+	bool		knnScan = so->useBidirectionalKnnScan;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	Assert(state);
+	if (state != so->backwardState)
+	{
+		scanPage = &btscan->btps_forwardScanPage;
+		otherScanPage = &btscan->btps_backwardScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_backwardScanPage;
+		otherScanPage = &btscan->btps_forwardScanPage;
+	}
+
 	SpinLockAcquire(&btscan->btps_mutex);
-	btscan->btps_scanPage = scan_page;
-	btscan->btps_pageStatus = BTPARALLEL_IDLE;
+	*scanPage = scan_page;
+	/* switch to idle state only if both KNN pages are initialized */
+	if (!knnScan || *otherScanPage != InvalidBlockNumber)
+	{
+		btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		status_changed = true;
+	}
 	SpinLockRelease(&btscan->btps_mutex);
-	ConditionVariableSignal(&btscan->btps_cv);
+
+	if (status_changed)
+		ConditionVariableSignal(&btscan->btps_cv);
 }
 
 /*
@@ -722,12 +844,15 @@ _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
  * advance to the next page.
  */
 void
-_bt_parallel_done(IndexScanDesc scan)
+_bt_parallel_done(IndexScanDesc scan, BTScanState state)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
 	bool		status_changed = false;
+	bool		knnScan = so->useBidirectionalKnnScan;
 
 	/* Do nothing, for non-parallel scans */
 	if (parallel_scan == NULL)
@@ -736,18 +861,42 @@ _bt_parallel_done(IndexScanDesc scan)
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	Assert(state);
+	if (state != so->backwardState)
+	{
+		scanPage = &btscan->btps_forwardScanPage;
+		otherScanPage = &btscan->btps_backwardScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_backwardScanPage;
+		otherScanPage = &btscan->btps_forwardScanPage;
+	}
+
 	/*
 	 * Mark the parallel scan as done for this combination of scan keys,
 	 * unless some other process already did so.  See also
 	 * _bt_advance_array_keys.
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
-	if (so->arrayKeyCount >= btscan->btps_arrayKeyCount &&
-		btscan->btps_pageStatus != BTPARALLEL_DONE)
+
+	Assert(btscan->btps_pageStatus == BTPARALLEL_ADVANCING);
+
+	if (so->arrayKeyCount >= btscan->btps_arrayKeyCount)
 	{
-		btscan->btps_pageStatus = BTPARALLEL_DONE;
+		*scanPage = P_NONE;
 		status_changed = true;
+
+		/* switch to "done" state only if both KNN scans are done */
+		if (!knnScan || *otherScanPage == P_NONE)
+			btscan->btps_pageStatus = BTPARALLEL_DONE;
+		/* else switch to "idle" state only if both KNN scans are initialized */
+		else if (*otherScanPage != InvalidBlockNumber)
+			btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		else
+			status_changed = false;
 	}
+
 	SpinLockRelease(&btscan->btps_mutex);
 
 	/* wake up all the workers associated with this parallel scan */
@@ -776,7 +925,8 @@ _bt_parallel_advance_array_keys(IndexScanDesc scan)
 	SpinLockAcquire(&btscan->btps_mutex);
 	if (btscan->btps_pageStatus == BTPARALLEL_DONE)
 	{
-		btscan->btps_scanPage = InvalidBlockNumber;
+		btscan->btps_forwardScanPage = InvalidBlockNumber;
+		btscan->btps_backwardScanPage = InvalidBlockNumber;
 		btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 		btscan->btps_arrayKeyCount++;
 	}
@@ -796,6 +946,12 @@ btrestrpos(IndexScanDesc scan)
 		_bt_restore_array_keys(scan);
 
 	_bt_restore_marked_position(scan, &so->state);
+
+	if (so->backwardState)
+	{
+		_bt_restore_marked_position(scan, so->backwardState);
+		so->currRightIsNearest = so->markRightIsNearest;
+	}
 }
 
 /*
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 160392dcb6..88a458ea90 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -44,11 +44,13 @@ static bool _bt_steppage(IndexScanDesc scan, BTScanState state,
 						 ScanDirection dir);
 static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
 							 BlockNumber blkno, ScanDirection dir);
-static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
-								  ScanDirection dir);
+static bool _bt_parallel_readpage(IndexScanDesc scan, BTScanState state,
+								  BlockNumber blkno, ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static inline void _bt_initialize_more_data(BTScanState state, ScanDirection dir);
+static BTScanState _bt_alloc_knn_scan(IndexScanDesc scan);
+static bool _bt_start_knn_scan(IndexScanDesc scan, bool left, bool right);
 
 
 /*
@@ -890,9 +892,11 @@ _bt_return_current_item(IndexScanDesc scan, BTScanState state)
  */
 static bool
 _bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
-					OffsetNumber offnum)
+					OffsetNumber offnum, bool *readPageStatus)
 {
-	if (!_bt_readpage(scan, state, dir, offnum, true))
+	if (!(readPageStatus ?
+		  *readPageStatus :
+		  _bt_readpage(scan, state, dir, offnum, true)))
 	{
 		/*
 		 * There's no actually-matching data on this page.  Try to advance to
@@ -907,6 +911,173 @@ _bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 	return true;
 }
 
+/*
+ *  _bt_calc_current_dist() -- Calculate distance from the current item
+ *		of the scan state to the target order-by ScanKey argument.
+ */
+static void
+_bt_calc_current_dist(IndexScanDesc scan, BTScanState state)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPosItem *currItem = &state->currPos.items[state->currPos.itemIndex];
+	IndexTuple	itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+	ScanKey		scankey = &scan->orderByData[0];
+	Datum		value;
+
+	value = index_getattr(itup, 1, scan->xs_itupdesc, &state->currIsNull);
+
+	if (state->currIsNull)
+		return;					/* NULL distance */
+
+	value = FunctionCall2Coll(&scankey->sk_func,
+							  scankey->sk_collation,
+							  value,
+							  scankey->sk_argument);
+
+	/* free previous distance value for by-ref types */
+	if (!so->distanceTypeByVal && DatumGetPointer(state->currDistance))
+		pfree(DatumGetPointer(state->currDistance));
+
+	state->currDistance = value;
+}
+
+/*
+ *  _bt_compare_current_dist() -- Compare current distances of the left and
+ *right scan states.
+ *
+ *  NULL distances are considered to be greater than any non-NULL distances.
+ *
+ *  Returns true if right distance is lesser than left, otherwise false.
+ */
+static bool
+_bt_compare_current_dist(BTScanOpaque so, BTScanState rstate, BTScanState lstate)
+{
+	if (lstate->currIsNull)
+		return true;			/* non-NULL < NULL */
+
+	if (rstate->currIsNull)
+		return false;			/* NULL > non-NULL */
+
+	return DatumGetBool(FunctionCall2Coll(&so->distanceCmpProc,
+										  InvalidOid,	/* XXX collation for
+														 * distance comparison */
+										  rstate->currDistance,
+										  lstate->currDistance));
+}
+
+/*
+ * _bt_alloc_knn_backward_scan() -- Allocate additional backward scan state for KNN.
+ */
+static BTScanState
+_bt_alloc_knn_scan(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState lstate = (BTScanState) palloc(sizeof(BTScanStateData));
+
+	_bt_allocate_tuple_workspaces(lstate);
+
+	if (!scan->xs_want_itup)
+	{
+		/* We need to request index tuples for distance comparison. */
+		scan->xs_want_itup = true;
+		_bt_allocate_tuple_workspaces(&so->state);
+	}
+
+	BTScanPosInvalidate(lstate->currPos);
+	lstate->currPos.moreLeft = false;
+	lstate->currPos.moreRight = false;
+	BTScanPosInvalidate(lstate->markPos);
+	lstate->markItemIndex = -1;
+	lstate->killedItems = NULL;
+	lstate->numKilled = 0;
+	lstate->currDistance = (Datum) 0;
+	lstate->markDistance = (Datum) 0;
+
+	return so->backwardState = lstate;
+}
+
+static bool
+_bt_start_knn_scan(IndexScanDesc scan, bool left, bool right)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState rstate;			/* right (forward) main scan state */
+	BTScanState lstate;			/* additional left (backward) KNN scan state */
+
+	if (!left && !right)
+		return false;			/* empty result */
+
+	rstate = &so->state;
+	lstate = so->backwardState;
+
+	if (left && right)
+	{
+		/*
+		 * We have found items in both scan directions, determine nearest item
+		 * to return.
+		 */
+		_bt_calc_current_dist(scan, rstate);
+		_bt_calc_current_dist(scan, lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+
+		/*
+		 * 'right' flag determines the selected scan direction; right
+		 * direction is selected if the right item is nearest.
+		 */
+		right = so->currRightIsNearest;
+	}
+
+	/* Return current item of the selected scan direction. */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
+ * _bt_init_knn_scan() -- Init additional scan state for KNN search.
+ *
+ * Caller must pin and read-lock scan->state.currPos.buf buffer.
+ *
+ * If empty result was found returned false.
+ * Otherwise prepared current item, and returned true.
+ */
+static bool
+_bt_init_knn_scan(IndexScanDesc scan, OffsetNumber offnum)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState rstate = &so->state;	/* right (forward) main scan state */
+	BTScanState lstate;			/* additional left (backward) KNN scan state */
+	Buffer		buf = rstate->currPos.buf;
+	bool		left,
+				right;
+	ScanDirection rdir = ForwardScanDirection;
+	ScanDirection ldir = BackwardScanDirection;
+	OffsetNumber roffnum = offnum;
+	OffsetNumber loffnum = OffsetNumberPrev(offnum);
+
+	lstate = _bt_alloc_knn_scan(scan);
+
+	/* Bump pin and lock count before BTScanPosData copying. */
+	IncrBufferRefCount(buf);
+	LockBuffer(buf, BT_READ);
+
+	memcpy(&lstate->currPos, &rstate->currPos, sizeof(BTScanPosData));
+	lstate->currPos.moreLeft = true;
+	lstate->currPos.moreRight = false;
+
+	/*
+	 * Load first pages from the both scans.
+	 *
+	 * _bt_load_first_page(right) can step to next page, and then
+	 * _bt_parallel_seize() will deadlock if the left page number is not yet
+	 * initialized in BTParallelScanDesc.  So we must first read the left page
+	 * using _bt_readpage(), and _bt_parallel_release() which is called inside
+	 * will save the next page number in BTParallelScanDesc.
+	 */
+	left = _bt_readpage(scan, lstate, ldir, loffnum, true);
+	right = _bt_load_first_page(scan, rstate, rdir, roffnum, NULL);
+	left = _bt_load_first_page(scan, lstate, ldir, loffnum, &left);
+
+	return _bt_start_knn_scan(scan, left, right);
+}
+
 /*
  *	_bt_first() -- Find the first item in a scan.
  *
@@ -963,10 +1134,19 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	if (!so->qual_ok)
 	{
 		/* Notify any other workers that we're done with this scan key. */
-		_bt_parallel_done(scan);
+		_bt_parallel_done(scan, &so->state);
 		return false;
 	}
 
+	if (scan->numberOfOrderBys > 0)
+	{
+		if (so->useBidirectionalKnnScan)
+			_bt_init_distance_comparison(scan);
+		else if (so->scanDirection != NoMovementScanDirection)
+			/* use selected KNN scan direction */
+			dir = so->scanDirection;
+	}
+
 	/*
 	 * For parallel scans, get the starting page from shared state. If the
 	 * scan has not started, proceed to find out first leaf page in the usual
@@ -975,19 +1155,50 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 */
 	if (scan->parallel_scan != NULL)
 	{
-		status = _bt_parallel_seize(scan, &blkno);
+		status = _bt_parallel_seize(scan, &so->state, &blkno);
 		if (!status)
 			return false;
-		else if (blkno == P_NONE)
-		{
-			_bt_parallel_done(scan);
-			return false;
-		}
 		else if (blkno != InvalidBlockNumber)
 		{
-			if (!_bt_parallel_readpage(scan, blkno, dir))
-				return false;
-			goto readcomplete;
+			bool		knn = so->useBidirectionalKnnScan;
+			bool		right;
+			bool		left;
+
+			if (knn)
+				_bt_alloc_knn_scan(scan);
+
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, &so->state);
+				right = false;
+			}
+			else
+				right = _bt_parallel_readpage(scan, &so->state, blkno,
+											  knn ? ForwardScanDirection : dir);
+
+			if (!knn)
+				return right && _bt_return_current_item(scan, &so->state);
+
+			/* seize additional backward KNN scan */
+			left = _bt_parallel_seize(scan, so->backwardState, &blkno);
+
+			if (left)
+			{
+				if (blkno == P_NONE)
+				{
+					_bt_parallel_done(scan, so->backwardState);
+					left = false;
+				}
+				else
+				{
+					/* backward scan should be already initialized */
+					Assert(blkno != InvalidBlockNumber);
+					left = _bt_parallel_readpage(scan, so->backwardState, blkno,
+												 BackwardScanDirection);
+				}
+			}
+
+			return _bt_start_knn_scan(scan, left, right);
 		}
 	}
 
@@ -1037,14 +1248,20 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 * storing their addresses into the local startKeys[] array.
 	 *----------
 	 */
-	strat_total = BTEqualStrategyNumber;
-	if (so->numberOfKeys > 0)
+	if (so->useBidirectionalKnnScan)
+	{
+		keysz = _bt_init_knn_start_keys(scan, startKeys, notnullkeys);
+		strat_total = BTNearestStrategyNumber;
+	}
+	else if (so->numberOfKeys > 0)
 	{
 		AttrNumber	curattr;
 		ScanKey		chosen;
 		ScanKey		impliesNN;
 		ScanKey		cur;
 
+		strat_total = BTEqualStrategyNumber;
+
 		/*
 		 * chosen is the so-far-chosen key for the current attribute, if any.
 		 * We don't cast the decision in stone until we reach keys for the
@@ -1178,7 +1395,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		if (!match)
 		{
 			/* No match, so mark (parallel) scan finished */
-			_bt_parallel_done(scan);
+			_bt_parallel_done(scan, NULL);
 		}
 
 		return match;
@@ -1214,7 +1431,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			Assert(subkey->sk_flags & SK_ROW_MEMBER);
 			if (subkey->sk_flags & SK_ISNULL)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, NULL);
 				return false;
 			}
 			memcpy(inskey.scankeys + i, subkey, sizeof(ScanKeyData));
@@ -1379,6 +1596,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			break;
 
 		case BTGreaterEqualStrategyNumber:
+		case BTMaxStrategyNumber:
 
 			/*
 			 * Find first item >= scankey
@@ -1436,7 +1654,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			 * Mark parallel scan as done, so that all the workers can finish
 			 * their scan.
 			 */
-			_bt_parallel_done(scan);
+			_bt_parallel_done(scan, NULL);
 			BTScanPosInvalidate(*currPos);
 			return false;
 		}
@@ -1470,17 +1688,22 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 * for the page.  For example, when inskey is both < the leaf page's high
 	 * key and > all of its non-pivot tuples, offnum will be "maxoff + 1".
 	 */
-	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
-		return false;
+	if (strat_total == BTNearestStrategyNumber)
+		return _bt_init_knn_scan(scan, offnum);
+
+	if (!_bt_load_first_page(scan, &so->state, dir, offnum, NULL))
+		return false;			/* empty result */
 
-readcomplete:
 	/* OK, currPos->itemIndex says what to return */
 	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
- *	Advance to next tuple on current page; or if there's no more,
- *	try to step to the next page with data.
+ *	_bt_next_item() -- Advance to next tuple on current page;
+ *		or if there's no more, try to step to the next page with data.
+ *
+ *	If there are any matching records in the given direction true is
+ *	returned, otherwise false.
  */
 static bool
 _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
@@ -1499,6 +1722,51 @@ _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 	return _bt_steppage(scan, state, dir);
 }
 
+/*
+ *	_bt_next_nearest() -- Return next nearest item from bidirectional KNN scan.
+ */
+static bool
+_bt_next_nearest(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState rstate = &so->state;
+	BTScanState lstate = so->backwardState;
+	bool		right = BTScanPosIsValid(rstate->currPos);
+	bool		left = BTScanPosIsValid(lstate->currPos);
+	bool		advanceRight;
+
+	if (right && left)
+		advanceRight = so->currRightIsNearest;
+	else if (right)
+		advanceRight = true;
+	else if (left)
+		advanceRight = false;
+	else
+		return false;			/* end of the scan */
+
+	if (advanceRight)
+		right = _bt_next_item(scan, rstate, ForwardScanDirection);
+	else
+		left = _bt_next_item(scan, lstate, BackwardScanDirection);
+
+	if (!left && !right)
+		return false;			/* end of the scan */
+
+	if (left && right)
+	{
+		/*
+		 * If there are items in both scans we must recalculate distance in
+		 * the advanced scan.
+		 */
+		_bt_calc_current_dist(scan, advanceRight ? rstate : lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+		right = so->currRightIsNearest;
+	}
+
+	/* return nearest item */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
 /*
  *	_bt_next() -- Get the next item in a scan.
  *
@@ -1518,6 +1786,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
+	if (so->backwardState)
+		/* return next neareset item from KNN scan */
+		return _bt_next_nearest(scan);
+
 	if (!_bt_next_item(scan, &so->state, dir))
 		return false;
 
@@ -1580,9 +1852,9 @@ _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir, OffsetNum
 	if (scan->parallel_scan)
 	{
 		if (ScanDirectionIsForward(dir))
-			_bt_parallel_release(scan, opaque->btpo_next);
+			_bt_parallel_release(scan, state, opaque->btpo_next);
 		else
-			_bt_parallel_release(scan, BufferGetBlockNumber(pos->buf));
+			_bt_parallel_release(scan, state, BufferGetBlockNumber(pos->buf));
 	}
 
 	continuescan = true;		/* default assumption */
@@ -2017,7 +2289,7 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the next block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno);
+			status = _bt_parallel_seize(scan, state, &blkno);
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
@@ -2049,13 +2321,19 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the current block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno);
+			status = _bt_parallel_seize(scan, state, &blkno);
 			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, state);
+				BTScanPosInvalidate(*currPos);
+				return false;
+			}
 		}
 		else
 		{
@@ -2105,7 +2383,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			 */
 			if (blkno == P_NONE || !currPos->moreRight)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -2127,14 +2405,14 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, opaque->btpo_next);
+				_bt_parallel_release(scan, state, opaque->btpo_next);
 			}
 
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno);
+				status = _bt_parallel_seize(scan, state, &blkno);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -2184,7 +2462,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (!currPos->moreLeft)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -2195,7 +2473,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			/* if we're physically at end of index, return failure */
 			if (currPos->buf == InvalidBuffer)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -2218,7 +2496,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
+				_bt_parallel_release(scan, state, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -2230,7 +2508,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno);
+				status = _bt_parallel_seize(scan, state, &blkno);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -2251,17 +2529,16 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
  * indicate success.
  */
 static bool
-_bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_parallel_readpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+					  ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-
-	_bt_initialize_more_data(&so->state, dir);
+	_bt_initialize_more_data(state, dir);
 
-	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
-	/* We have at least one item to return as scan's next item */
-	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
+	/* Drop the lock, and maybe the pin, on the current page */
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
 
 	return true;
 }
@@ -2532,7 +2809,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 
 	_bt_initialize_more_data(&so->state, dir);
 
-	if (!_bt_load_first_page(scan, &so->state, dir, start))
+	if (!_bt_load_first_page(scan, &so->state, dir, start, NULL))
 		return false;
 
 	/* OK, currPos->itemIndex says what to return */
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index f7867d4720..40baf49004 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -21,6 +21,7 @@
 #include "access/reloptions.h"
 #include "access/relscan.h"
 #include "catalog/catalog.h"
+#include "catalog/pg_amop.h"
 #include "commands/progress.h"
 #include "lib/qunique.h"
 #include "miscadmin.h"
@@ -29,11 +30,15 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 
 
 typedef struct BTSortArrayContext
 {
 	FmgrInfo	flinfo;
+	FmgrInfo	distflinfo;
+	FmgrInfo	distcmpflinfo;
+	ScanKey		distkey;
 	Oid			collation;
 	bool		reverse;
 } BTSortArrayContext;
@@ -55,6 +60,11 @@ static bool _bt_check_rowcompare(ScanKey skey,
 								 ScanDirection dir, bool *continuescan);
 static int	_bt_keep_natts(Relation rel, IndexTuple lastleft,
 						   IndexTuple firstright, BTScanInsert itup_key);
+static inline StrategyNumber _bt_select_knn_strategy_for_key(IndexScanDesc scan,
+															 ScanKey cond);
+static void _bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily,
+									  Oid leftargtype, FmgrInfo *finfo,
+									  int16 *typlen, bool *typbyval);
 
 
 /*
@@ -444,6 +454,7 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 {
 	Relation	rel = scan->indexRelation;
 	Oid			elemtype;
+	Oid			opfamily;
 	RegProcedure cmp_proc;
 	BTSortArrayContext cxt;
 
@@ -459,6 +470,54 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 	if (elemtype == InvalidOid)
 		elemtype = rel->rd_opcintype[skey->sk_attno - 1];
 
+	opfamily = rel->rd_opfamily[skey->sk_attno - 1];
+
+	if (scan->numberOfOrderBys <= 0 ||
+		scan->orderByData[0].sk_attno != skey->sk_attno)
+	{
+		cxt.distkey = NULL;
+		cxt.reverse = reverse;
+	}
+	else
+	{
+		/* Init procedures for distance calculation and comparison. */
+		ScanKey		distkey = &scan->orderByData[0];
+		ScanKeyData distkey2;
+		Oid			disttype = distkey->sk_subtype;
+		Oid			distopr;
+		RegProcedure distproc;
+
+		if (!OidIsValid(disttype))
+			disttype = rel->rd_opcintype[skey->sk_attno - 1];
+
+		/* Lookup distance operator in index column's operator family. */
+		distopr = get_opfamily_member(opfamily,
+									  elemtype,
+									  disttype,
+									  distkey->sk_strategy);
+
+		if (!OidIsValid(distopr))
+			elog(ERROR, "missing operator (%u,%u) for strategy %d in opfamily %u",
+				 elemtype, disttype, BTMaxStrategyNumber, opfamily);
+
+		distproc = get_opcode(distopr);
+
+		if (!RegProcedureIsValid(distproc))
+			elog(ERROR, "missing code for operator %u", distopr);
+
+		fmgr_info(distproc, &cxt.distflinfo);
+
+		distkey2 = *distkey;
+		fmgr_info_copy(&distkey2.sk_func, &cxt.distflinfo, CurrentMemoryContext);
+		distkey2.sk_subtype = disttype;
+
+		_bt_get_distance_cmp_proc(&distkey2, opfamily, elemtype,
+								  &cxt.distcmpflinfo, NULL, NULL);
+
+		cxt.distkey = distkey;
+		cxt.reverse = false;	/* supported only ascending ordering */
+	}
+
 	/*
 	 * Look up the appropriate comparison function in the opfamily.
 	 *
@@ -467,19 +526,17 @@ _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
 	 * non-cross-type support functions for any datatype that it supports at
 	 * all.
 	 */
-	cmp_proc = get_opfamily_proc(rel->rd_opfamily[skey->sk_attno - 1],
+	cmp_proc = get_opfamily_proc(opfamily,
 								 elemtype,
 								 elemtype,
 								 BTORDER_PROC);
 	if (!RegProcedureIsValid(cmp_proc))
 		elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
-			 BTORDER_PROC, elemtype, elemtype,
-			 rel->rd_opfamily[skey->sk_attno - 1]);
+			 BTORDER_PROC, elemtype, elemtype, opfamily);
 
 	/* Sort the array elements */
 	fmgr_info(cmp_proc, &cxt.flinfo);
 	cxt.collation = skey->sk_collation;
-	cxt.reverse = reverse;
 	qsort_arg(elems, nelems, sizeof(Datum),
 			  _bt_compare_array_elements, &cxt);
 
@@ -499,6 +556,24 @@ _bt_compare_array_elements(const void *a, const void *b, void *arg)
 	BTSortArrayContext *cxt = (BTSortArrayContext *) arg;
 	int32		compare;
 
+	if (cxt->distkey)
+	{
+		Datum		dista = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  da,
+											  cxt->distkey->sk_argument);
+		Datum		distb = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  db,
+											  cxt->distkey->sk_argument);
+		bool		cmp = DatumGetBool(FunctionCall2Coll(&cxt->distcmpflinfo,
+														 cxt->collation,
+														 dista,
+														 distb));
+
+		return cmp ? -1 : 1;
+	}
+
 	compare = DatumGetInt32(FunctionCall2Coll(&cxt->flinfo,
 											  cxt->collation,
 											  da, db));
@@ -667,6 +742,69 @@ _bt_restore_array_keys(IndexScanDesc scan)
 	}
 }
 
+/*
+ * _bt_emit_scan_key() -- Emit one prepared scan key
+ *
+ * Push the scan key into the so->keyData[] array, and then mark it if it is
+ * required.  Also update selected kNN strategy.
+ */
+static void
+_bt_emit_scan_key(IndexScanDesc scan, ScanKey skey, int numberOfEqualCols)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	ScanKey		outkey = &so->keyData[so->numberOfKeys++];
+
+	memcpy(outkey, skey, sizeof(ScanKeyData));
+
+	/*
+	 * We can mark the qual as required (possibly only in one direction) if
+	 * all attrs before this one had "=".
+	 */
+	if (outkey->sk_attno - 1 == numberOfEqualCols)
+		_bt_mark_scankey_required(outkey);
+
+	/* Update kNN strategy if it is not already selected. */
+	if (so->useBidirectionalKnnScan)
+	{
+		switch (_bt_select_knn_strategy_for_key(scan, outkey))
+		{
+			case BTLessStrategyNumber:
+			case BTLessEqualStrategyNumber:
+
+				/*
+				 * Ordering key argument is greater than all values in scan
+				 * range, select backward scan direction.
+				 */
+				so->scanDirection = BackwardScanDirection;
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BTEqualStrategyNumber:
+				/* Use default unidirectional scan direction. */
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+
+				/*
+				 * Ordering key argument is lesser than all values in scan
+				 * range, select forward scan direction.
+				 */
+				so->scanDirection = ForwardScanDirection;
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BTMaxStrategyNumber:
+
+				/*
+				 * Ordering key argument falls into scan range, keep using
+				 * bidirectional scan.
+				 */
+				break;
+		}
+	}
+}
 
 /*
  *	_bt_preprocess_keys() -- Preprocess scan keys
@@ -758,17 +896,34 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	int			numberOfKeys = scan->numberOfKeys;
 	int16	   *indoption = scan->indexRelation->rd_indoption;
-	int			new_numberOfKeys;
 	int			numberOfEqualCols;
 	ScanKey		inkeys;
-	ScanKey		outkeys;
 	ScanKey		cur;
-	ScanKey		xform[BTMaxStrategyNumber];
+	ScanKey		xform[BTMaxSearchStrategyNumber];
 	bool		test_result;
 	int			i,
 				j;
 	AttrNumber	attno;
 
+	if (scan->numberOfOrderBys > 0)
+	{
+		ScanKey		ord = scan->orderByData;
+
+		if (scan->numberOfOrderBys > 1 || ord->sk_attno != 1)
+			/* it should not happen, see btmatchorderby() */
+			elog(ERROR, "only one btree ordering operator "
+				 "for the first index column is supported");
+
+		Assert(ord->sk_strategy == BTMaxStrategyNumber);
+
+		/* use bidirectional kNN scan by default */
+		so->useBidirectionalKnnScan = true;
+	}
+	else
+	{
+		so->useBidirectionalKnnScan = false;
+	}
+
 	/* initialize result variables */
 	so->qual_ok = true;
 	so->numberOfKeys = 0;
@@ -784,7 +939,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	else
 		inkeys = scan->keyData;
 
-	outkeys = so->keyData;
 	cur = &inkeys[0];
 	/* we check that input keys are correctly ordered */
 	if (cur->sk_attno < 1)
@@ -796,18 +950,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 		/* Apply indoption to scankey (might change sk_strategy!) */
 		if (!_bt_fix_scankey_strategy(cur, indoption))
 			so->qual_ok = false;
-		memcpy(outkeys, cur, sizeof(ScanKeyData));
-		so->numberOfKeys = 1;
-		/* We can mark the qual as required if it's for first index col */
-		if (cur->sk_attno == 1)
-			_bt_mark_scankey_required(outkeys);
+
+		_bt_emit_scan_key(scan, cur, 0);
 		return;
 	}
 
 	/*
 	 * Otherwise, do the full set of pushups.
 	 */
-	new_numberOfKeys = 0;
 	numberOfEqualCols = 0;
 
 	/*
@@ -864,7 +1014,7 @@ _bt_preprocess_keys(IndexScanDesc scan)
 			{
 				ScanKey		eq = xform[BTEqualStrategyNumber - 1];
 
-				for (j = BTMaxStrategyNumber; --j >= 0;)
+				for (j = BTMaxSearchStrategyNumber; --j >= 0;)
 				{
 					ScanKey		chk = xform[j];
 
@@ -931,20 +1081,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 			}
 
 			/*
-			 * Emit the cleaned-up keys into the outkeys[] array, and then
+			 * Emit the cleaned-up keys into the so->keyData[] array, and then
 			 * mark them if they are required.  They are required (possibly
 			 * only in one direction) if all attrs before this one had "=".
 			 */
-			for (j = BTMaxStrategyNumber; --j >= 0;)
+			for (j = BTMaxSearchStrategyNumber; --j >= 0;)
 			{
 				if (xform[j])
-				{
-					ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-					memcpy(outkey, xform[j], sizeof(ScanKeyData));
-					if (priorNumberOfEqualCols == attno - 1)
-						_bt_mark_scankey_required(outkey);
-				}
+					_bt_emit_scan_key(scan, xform[j], priorNumberOfEqualCols);
 			}
 
 			/*
@@ -964,17 +1108,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
 		/* if row comparison, push it directly to the output array */
 		if (cur->sk_flags & SK_ROW_HEADER)
 		{
-			ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-			memcpy(outkey, cur, sizeof(ScanKeyData));
-			if (numberOfEqualCols == attno - 1)
-				_bt_mark_scankey_required(outkey);
+			_bt_emit_scan_key(scan, cur, numberOfEqualCols);
 
 			/*
 			 * We don't support RowCompare using equality; such a qual would
 			 * mess up the numberOfEqualCols tracking.
 			 */
 			Assert(j != (BTEqualStrategyNumber - 1));
+
 			continue;
 		}
 
@@ -1007,16 +1148,10 @@ _bt_preprocess_keys(IndexScanDesc scan)
 				 * previous one in xform[j] and push this one directly to the
 				 * output array.
 				 */
-				ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-				memcpy(outkey, cur, sizeof(ScanKeyData));
-				if (numberOfEqualCols == attno - 1)
-					_bt_mark_scankey_required(outkey);
+				_bt_emit_scan_key(scan, cur, numberOfEqualCols);
 			}
 		}
 	}
-
-	so->numberOfKeys = new_numberOfKeys;
 }
 
 /*
@@ -2195,6 +2330,39 @@ btproperty(Oid index_oid, int attno,
 			*res = true;
 			return true;
 
+		case AMPROP_DISTANCE_ORDERABLE:
+			{
+				Oid			opclass,
+							opfamily,
+							opcindtype;
+
+				/* answer only for columns, not AM or whole index */
+				if (attno == 0)
+					return false;
+
+				opclass = get_index_column_opclass(index_oid, attno);
+
+				if (!OidIsValid(opclass))
+				{
+					*res = false;	/* non-key attribute */
+					return true;
+				}
+
+				if (!get_opclass_opfamily_and_input_type(opclass,
+														 &opfamily, &opcindtype))
+				{
+					*isnull = true;
+					return true;
+				}
+
+				*res = SearchSysCacheExists(AMOPSTRATEGY,
+											ObjectIdGetDatum(opfamily),
+											ObjectIdGetDatum(opcindtype),
+											ObjectIdGetDatum(opcindtype),
+											Int16GetDatum(BTMaxStrategyNumber));
+				return true;
+			}
+
 		default:
 			return false;		/* punt to generic code */
 	}
@@ -2791,3 +2959,216 @@ _bt_allocate_tuple_workspaces(BTScanState state)
 	state->currTuples = (char *) palloc(BLCKSZ * 2);
 	state->markTuples = state->currTuples + BLCKSZ;
 }
+
+static bool
+_bt_compare_row_key_with_ordering_key(ScanKey row, ScanKey ord, bool *result)
+{
+	ScanKey		subkey = (ScanKey) DatumGetPointer(row->sk_argument);
+	int32		cmpresult;
+
+	Assert(subkey->sk_attno == 1);
+	Assert(subkey->sk_flags & SK_ROW_MEMBER);
+
+	if (subkey->sk_flags & SK_ISNULL)
+		return false;
+
+	/* Perform the test --- three-way comparison not bool operator */
+	cmpresult = DatumGetInt32(FunctionCall2Coll(&subkey->sk_func,
+												subkey->sk_collation,
+												ord->sk_argument,
+												subkey->sk_argument));
+
+	if (subkey->sk_flags & SK_BT_DESC)
+		cmpresult = -cmpresult;
+
+	/*
+	 * At this point cmpresult indicates the overall result of the row
+	 * comparison, and subkey points to the deciding column (or the last
+	 * column if the result is "=").
+	 */
+	switch (subkey->sk_strategy)
+	{
+			/* EQ and NE cases aren't allowed here */
+		case BTLessStrategyNumber:
+			*result = cmpresult < 0;
+			break;
+		case BTLessEqualStrategyNumber:
+			*result = cmpresult <= 0;
+			break;
+		case BTGreaterEqualStrategyNumber:
+			*result = cmpresult >= 0;
+			break;
+		case BTGreaterStrategyNumber:
+			*result = cmpresult > 0;
+			break;
+		default:
+			elog(ERROR, "unrecognized RowCompareType: %d",
+				 (int) subkey->sk_strategy);
+			*result = false;	/* keep compiler quiet */
+	}
+
+	return true;
+}
+
+/*
+ * _bt_select_knn_strategy_for_key() -- Determine which kNN scan strategy to use:
+ *		bidirectional or unidirectional.  We are checking here if the
+ *		ordering scankey argument falls into the scan range: if it falls
+ *		we must use bidirectional scan, otherwise we use unidirectional.
+ *
+ *	Returns BTMaxStrategyNumber for bidirectional scan or
+ *	strategy number of non-matched scankey for unidirectional.
+ */
+static inline StrategyNumber
+_bt_select_knn_strategy_for_key(IndexScanDesc scan, ScanKey cond)
+{
+	ScanKey		ord = scan->orderByData;
+	bool		result;
+
+	/* only interesting in the first index attribute */
+	if (cond->sk_attno != 1)
+		return BTMaxStrategyNumber;
+
+	if (cond->sk_strategy == BTEqualStrategyNumber)
+		/* always use simple unidirectional scan for equals operators */
+		return BTEqualStrategyNumber;
+
+	if (cond->sk_flags & SK_ROW_HEADER)
+	{
+		if (!_bt_compare_row_key_with_ordering_key(cond, ord, &result))
+			return BTEqualStrategyNumber;	/* ROW(fist_index_attr, ...) IS
+											 * NULL */
+	}
+	else
+	{
+		if (!_bt_compare_scankey_args(scan, cond, ord, cond, &result))
+			elog(ERROR, "could not compare ordering key");
+	}
+
+	if (!result)
+
+		/*
+		 * Ordering scankey argument is out of scan range, use unidirectional
+		 * scan.
+		 */
+		return cond->sk_strategy;
+
+	return BTMaxStrategyNumber;
+}
+
+int
+_bt_init_knn_start_keys(IndexScanDesc scan, ScanKey *startKeys, ScanKey bufKeys)
+{
+	ScanKey		ord = scan->orderByData;
+	int			indopt = scan->indexRelation->rd_indoption[ord->sk_attno - 1];
+	int			flags = (indopt << SK_BT_INDOPTION_SHIFT) |
+	SK_ORDER_BY |
+	SK_SEARCHNULL;				/* only for invalid procedure oid, see assert
+								 * in ScanKeyEntryInitialize() */
+	int			keysCount = 0;
+
+	/* Init btree search key with ordering key argument. */
+	ScanKeyEntryInitialize(&bufKeys[0],
+						   flags,
+						   ord->sk_attno,
+						   BTMaxStrategyNumber,
+						   ord->sk_subtype,
+						   ord->sk_collation,
+						   InvalidOid,
+						   ord->sk_argument);
+
+	startKeys[keysCount++] = &bufKeys[0];
+
+	return keysCount;
+}
+
+static Oid
+_bt_get_sortfamily_for_opfamily_op(Oid opfamily, Oid lefttype, Oid righttype,
+								   StrategyNumber strategy)
+{
+	HeapTuple	tp;
+	Form_pg_amop amop_tup;
+	Oid			sortfamily;
+
+	tp = SearchSysCache4(AMOPSTRATEGY,
+						 ObjectIdGetDatum(opfamily),
+						 ObjectIdGetDatum(lefttype),
+						 ObjectIdGetDatum(righttype),
+						 Int16GetDatum(strategy));
+	if (!HeapTupleIsValid(tp))
+		return InvalidOid;
+	amop_tup = (Form_pg_amop) GETSTRUCT(tp);
+	sortfamily = amop_tup->amopsortfamily;
+	ReleaseSysCache(tp);
+
+	return sortfamily;
+}
+
+/*
+ * _bt_get_distance_cmp_proc() -- Init procedure for comparsion of distances
+ *		between "leftargtype" and "distkey".
+ */
+static void
+_bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype,
+						  FmgrInfo *finfo, int16 *typlen, bool *typbyval)
+{
+	RegProcedure opcode;
+	Oid			sortfamily;
+	Oid			opno;
+	Oid			distanceType;
+
+	distanceType = get_func_rettype(distkey->sk_func.fn_oid);
+
+	sortfamily = _bt_get_sortfamily_for_opfamily_op(opfamily, leftargtype,
+													distkey->sk_subtype,
+													distkey->sk_strategy);
+
+	if (!OidIsValid(sortfamily))
+		elog(ERROR, "could not find sort family for btree ordering operator");
+
+	opno = get_opfamily_member(sortfamily,
+							   distanceType,
+							   distanceType,
+							   BTLessEqualStrategyNumber);
+
+	if (!OidIsValid(opno))
+		elog(ERROR, "could not find operator for btree distance comparison");
+
+	opcode = get_opcode(opno);
+
+	if (!RegProcedureIsValid(opcode))
+		elog(ERROR,
+			 "could not find procedure for btree distance comparison operator");
+
+	fmgr_info(opcode, finfo);
+
+	if (typlen)
+		get_typlenbyval(distanceType, typlen, typbyval);
+}
+
+/*
+ *  _bt_init_distance_comparison() -- Init distance typlen/typbyval and its
+ *  	comparison procedure.
+ */
+void
+_bt_init_distance_comparison(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	Relation	rel = scan->indexRelation;
+	ScanKey		ord = scan->orderByData;
+
+	_bt_get_distance_cmp_proc(ord,
+							  rel->rd_opfamily[ord->sk_attno - 1],
+							  rel->rd_opcintype[ord->sk_attno - 1],
+							  &so->distanceCmpProc,
+							  &so->distanceTypeLen,
+							  &so->distanceTypeByVal);
+
+	/*
+	 * In fact, distance values need to be initialized only for by-ref types,
+	 * because previous distance values are pfreed before writing new ones
+	 * (see _bt_calc_current_dist()).
+	 */
+	so->state.currDistance = (Datum) 0;
+	so->state.markDistance = (Datum) 0;
+}
diff --git a/src/backend/access/nbtree/nbtvalidate.c b/src/backend/access/nbtree/nbtvalidate.c
index e9d4cd60de..3c91d74512 100644
--- a/src/backend/access/nbtree/nbtvalidate.c
+++ b/src/backend/access/nbtree/nbtvalidate.c
@@ -28,6 +28,13 @@
 #include "utils/regproc.h"
 #include "utils/syscache.h"
 
+#define BTRequiredOperatorSet \
+		((1 << BTLessStrategyNumber) | \
+		 (1 << BTLessEqualStrategyNumber) | \
+		 (1 << BTEqualStrategyNumber) | \
+		 (1 << BTGreaterEqualStrategyNumber) | \
+		 (1 << BTGreaterStrategyNumber))
+
 
 /*
  * Validator for a btree opclass.
@@ -142,6 +149,7 @@ btvalidate(Oid opclassoid)
 	{
 		HeapTuple	oprtup = &oprlist->members[i]->tuple;
 		Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
+		Oid			op_rettype;
 
 		/* Check that only allowed strategy numbers exist */
 		if (oprform->amopstrategy < 1 ||
@@ -156,20 +164,29 @@ btvalidate(Oid opclassoid)
 			result = false;
 		}
 
-		/* btree doesn't support ORDER BY operators */
-		if (oprform->amoppurpose != AMOP_SEARCH ||
-			OidIsValid(oprform->amopsortfamily))
+		/* btree supports ORDER BY operators */
+		if (oprform->amoppurpose != AMOP_SEARCH)
 		{
-			ereport(INFO,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
-							opfamilyname, "btree",
-							format_operator(oprform->amopopr))));
-			result = false;
+			/* ... and operator result must match the claimed btree opfamily */
+			op_rettype = get_op_rettype(oprform->amopopr);
+			if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype))
+			{
+				ereport(INFO,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
+								opfamilyname, "btree",
+								format_operator(oprform->amopopr))));
+				result = false;
+			}
+		}
+		else
+		{
+			/* Search operators must always return bool */
+			op_rettype = BOOLOID;
 		}
 
 		/* Check operator signature --- same for all btree strategies */
-		if (!check_amop_signature(oprform->amopopr, BOOLOID,
+		if (!check_amop_signature(oprform->amopopr, op_rettype,
 								  oprform->amoplefttype,
 								  oprform->amoprighttype))
 		{
@@ -224,12 +241,8 @@ btvalidate(Oid opclassoid)
 		 * or support functions for this datatype pair.  The sortsupport,
 		 * in_range, and equalimage functions are considered optional.
 		 */
-		if (thisgroup->operatorset !=
-			((1 << BTLessStrategyNumber) |
-			 (1 << BTLessEqualStrategyNumber) |
-			 (1 << BTEqualStrategyNumber) |
-			 (1 << BTGreaterEqualStrategyNumber) |
-			 (1 << BTGreaterStrategyNumber)))
+		if ((thisgroup->operatorset & BTRequiredOperatorSet) !=
+			BTRequiredOperatorSet)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index 6b635e8ad1..2b6ccc5367 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -1386,7 +1386,7 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
 {
 	PartitionScheme part_scheme = context->rel->part_scheme;
 	List	   *opsteps = NIL;
-	List	   *btree_clauses[BTMaxStrategyNumber + 1],
+	List	   *btree_clauses[BTMaxSearchStrategyNumber + 1],
 			   *hash_clauses[HTMaxStrategyNumber + 1];
 	int			i;
 	ListCell   *lc;
@@ -1498,7 +1498,7 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
 				 * combinations of expressions of different keys, which
 				 * get_steps_using_prefix takes care of for us.
 				 */
-				for (strat = 1; strat <= BTMaxStrategyNumber; strat++)
+				for (strat = 1; strat <= BTMaxSearchStrategyNumber; strat++)
 				{
 					foreach(lc, btree_clauses[strat])
 					{
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index c62f1ea15d..ab27ce542f 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -682,7 +682,7 @@ BTreeTupleGetMaxHeapTID(IndexTuple itup)
  *	The strategy numbers are chosen so that we can commute them by
  *	subtraction, thus:
  */
-#define BTCommuteStrategyNumber(strat)	(BTMaxStrategyNumber + 1 - (strat))
+#define BTCommuteStrategyNumber(strat)	(BTMaxSearchStrategyNumber + 1 - (strat))
 
 /*
  *	When a new operator class is declared, we require that the user
@@ -1056,6 +1056,12 @@ typedef struct BTScanStateData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+
+	/* KNN-search fields: */
+	Datum		currDistance;	/* distance to the current item */
+	Datum		markDistance;	/* distance to the marked item */
+	bool		currIsNull;		/* current item is NULL */
+	bool		markIsNull;		/* marked item is NULL */
 } BTScanStateData;
 
 typedef BTScanStateData *BTScanState;
@@ -1078,8 +1084,20 @@ typedef struct BTScanOpaqueData
 	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
 	MemoryContext arrayContext; /* scan-lifespan context for array data */
 
-	/* the state of tree scan */
+	/* the state of main tree scan */
 	BTScanStateData state;
+
+	/* kNN-search fields: */
+	bool		useBidirectionalKnnScan;	/* use bidirectional kNN scan? */
+	BTScanState forwardState;
+	BTScanState backwardState;	/* optional scan state for kNN search */
+	ScanDirection scanDirection;	/* selected scan direction for
+									 * unidirectional kNN scan */
+	FmgrInfo	distanceCmpProc;	/* distance comparison procedure */
+	int16		distanceTypeLen;	/* distance typlen */
+	bool		distanceTypeByVal;	/* distance typebyval */
+	bool		currRightIsNearest; /* current right item is nearest */
+	bool		markRightIsNearest; /* marked right item is nearest */
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -1158,11 +1176,12 @@ extern bool btcanreturn(Relation index, int attno);
 /*
  * prototypes for internal functions in nbtree.c
  */
-extern bool _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno);
-extern void _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page);
-extern void _bt_parallel_done(IndexScanDesc scan);
+extern bool _bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno);
+extern void _bt_parallel_release(IndexScanDesc scan, BTScanState state, BlockNumber scan_page);
+extern void _bt_parallel_done(IndexScanDesc scan, BTScanState state);
 extern void _bt_parallel_advance_array_keys(IndexScanDesc scan);
 
+
 /*
  * prototypes for functions in nbtdedup.c
  */
@@ -1283,6 +1302,9 @@ extern void _bt_check_third_page(Relation rel, Relation heap,
 								 bool needheaptidspace, Page page, IndexTuple newtup);
 extern bool _bt_allequalimage(Relation rel, bool debugmessage);
 extern void _bt_allocate_tuple_workspaces(BTScanState state);
+extern void _bt_init_distance_comparison(IndexScanDesc scan);
+extern int	_bt_init_knn_start_keys(IndexScanDesc scan, ScanKey *startKeys,
+									ScanKey bufKeys);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/include/access/stratnum.h b/src/include/access/stratnum.h
index 8a47d3c9ec..ccf2e0b926 100644
--- a/src/include/access/stratnum.h
+++ b/src/include/access/stratnum.h
@@ -32,7 +32,12 @@ typedef uint16 StrategyNumber;
 #define BTGreaterEqualStrategyNumber	4
 #define BTGreaterStrategyNumber			5
 
-#define BTMaxStrategyNumber				5
+#define BTMaxSearchStrategyNumber		5	/* number of B-tree search
+											 * strategies */
+
+#define BTNearestStrategyNumber			6	/* for ordering by <-> operator */
+#define BTMaxStrategyNumber				6	/* total numer of B-tree
+											 * strategies */
 
 /*
  *	Strategy numbers for hash indexes. There's only one valid strategy for
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index d8a05214b1..805ae021e4 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -30,6 +30,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int2,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int24
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -47,6 +51,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int2,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int28
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -64,6 +72,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int2,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int4
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -81,6 +93,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int4,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int42
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -98,6 +114,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int4,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int48
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -115,6 +135,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int4,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int8
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -132,6 +156,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int8,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int82
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -149,6 +177,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int8,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int84
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -166,6 +198,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int8,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # btree oid_ops
 
@@ -179,6 +215,10 @@
   amopstrategy => '4', amopopr => '>=(oid,oid)', amopmethod => 'btree' },
 { amopfamily => 'btree/oid_ops', amoplefttype => 'oid', amoprighttype => 'oid',
   amopstrategy => '5', amopopr => '>(oid,oid)', amopmethod => 'btree' },
+{ amopfamily => 'btree/oid_ops', amoplefttype => 'oid',
+  amoprighttype => 'oid', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(oid,oid)', amopmethod => 'btree',
+  amopsortfamily => 'btree/oid_ops' },
 
 # btree xid8_ops
 
@@ -247,6 +287,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float4,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float48
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
@@ -264,6 +308,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float4,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # default operators float8
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -281,6 +329,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float8,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float84
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -298,6 +350,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float8,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # btree char_ops
 
@@ -434,6 +490,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(date,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -451,6 +511,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(date,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -468,6 +532,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(date,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -485,6 +553,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamp,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -502,6 +574,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(timestamp,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -519,6 +595,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamp,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -536,6 +616,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -553,6 +637,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'date', amopstrategy => '5',
   amopopr => '>(timestamptz,date)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -570,6 +658,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree time_ops
 
@@ -588,6 +680,10 @@
 { amopfamily => 'btree/time_ops', amoplefttype => 'time',
   amoprighttype => 'time', amopstrategy => '5', amopopr => '>(time,time)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/time_ops', amoplefttype => 'time',
+  amoprighttype => 'time', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(time,time)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree timetz_ops
 
@@ -624,6 +720,10 @@
 { amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
   amoprighttype => 'interval', amopstrategy => '5',
   amopopr => '>(interval,interval)', amopmethod => 'btree' },
+{ amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
+  amoprighttype => 'interval', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(interval,interval)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree macaddr
 
@@ -799,6 +899,10 @@
 { amopfamily => 'btree/money_ops', amoplefttype => 'money',
   amoprighttype => 'money', amopstrategy => '5', amopopr => '>(money,money)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/money_ops', amoplefttype => 'money',
+  amoprighttype => 'money', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(money,money)', amopmethod => 'btree',
+  amopsortfamily => 'btree/money_ops' },
 
 # btree array_ops
 
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index ae54cb254f..0b08c29beb 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -355,10 +355,10 @@ ROLLBACK;
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
 ERROR:  access method "invalid_index_method" does not exist
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 6, must be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 0, must be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 7, must be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 0, must be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ERROR:  operator argument types must be specified in ALTER OPERATOR FAMILY
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- invalid options parsing function
@@ -405,11 +405,12 @@ DROP OPERATOR FAMILY alt_opf8 USING btree;
 CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
-ERROR:  access method "btree" does not support ordering operators
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
 ALTER OPERATOR FAMILY alt_opf11 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index 7ab6113c61..1b39abccbf 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -24,7 +24,7 @@ select prop,
  nulls_first        |    |       | f
  nulls_last         |    |       | t
  orderable          |    |       | t
- distance_orderable |    |       | f
+ distance_orderable |    |       | t
  returnable         |    |       | t
  search_array       |    |       | t
  search_nulls       |    |       | t
@@ -100,7 +100,7 @@ select prop,
  nulls_first        | f     | f    | f    | f            | f           | f   | f
  nulls_last         | t     | f    | f    | f            | f           | f   | f
  orderable          | t     | f    | f    | f            | f           | f   | f
- distance_orderable | f     | f    | t    | f            | t           | f   | f
+ distance_orderable | t     | f    | t    | f            | t           | f   | f
  returnable         | t     | f    | f    | t            | t           | f   | f
  search_array       | t     | f    | f    | f            | f           | f   | f
  search_nulls       | t     | f    | t    | t            | t           | f   | t
@@ -231,7 +231,7 @@ select col, prop, pg_index_column_has_property(o, col, prop)
    1 | desc               | f
    1 | nulls_first        | f
    1 | nulls_last         | t
-   1 | distance_orderable | f
+   1 | distance_orderable | t
    1 | returnable         | t
    1 | bogus              | 
    2 | orderable          | f
diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index 8311a03c3d..adfc9f44c2 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -434,3 +434,957 @@ ALTER INDEX btree_part_idx ALTER COLUMN id SET (n_distinct=100);
 ERROR:  ALTER action ALTER COLUMN ... SET cannot be performed on relation "btree_part_idx"
 DETAIL:  This operation is not supported for partitioned indexes.
 DROP TABLE btree_part;
+---
+--- Test B-tree distance ordering
+---
+SET enable_bitmapscan = OFF;
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Order By: (seqno <-> 0)
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Order By: ((random <-> 0) AND (seqno <-> 0))
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Order By: ((random <-> 0) AND (random <-> 1))
+(2 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)))
+   Order By: (random <-> 4000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+                                                                                               QUERY PLAN                                                                                               
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)) AND (random = ANY ('{1809552,1919087,2321799,2648497,3000193,3013326,4157193,4488889,5257716,5593978,NULL}'::integer[])))
+   Order By: (random <-> 3000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+ seqno | random  
+-------+---------
+  5380 | 3000193
+  6262 | 3013326
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+  6448 | 4157193
+   210 | 1809552
+  4408 | 4488889
+  6320 | 5257716
+  1836 | 5593978
+(10 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+-- test parallel KNN scan
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+RESET enable_indexscan;
+\set bt_knn_row_count 100000
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, :bt_knn_row_count) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+-- set the point inside the range
+\set bt_knn_point (4 * :bt_knn_row_count + 3)
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i
+	FROM generate_series(1, :bt_knn_row_count) i;
+SET enable_sort = OFF;
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: ((row_number() OVER (?)) = t2.n)
+   Join Filter: (bt_knn_test.i <> t2.i)
+   ->  WindowAgg
+         ->  Gather Merge
+               Workers Planned: 4
+               ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                     Order By: (i <-> 400003)
+   ->  Hash
+         ->  Gather
+               Workers Planned: 4
+               ->  Parallel Seq Scan on bt_knn_test2 t2
+(12 rows)
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+RESET enable_sort;
+DROP TABLE bt_knn_test2;
+-- set the point to the right of the range
+\set bt_knn_point (11 * :bt_knn_row_count)
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i
+	FROM generate_series(1, :bt_knn_row_count) i;
+SET enable_sort = OFF;
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: ((row_number() OVER (?)) = t2.n)
+   Join Filter: (bt_knn_test.i <> t2.i)
+   ->  WindowAgg
+         ->  Gather Merge
+               Workers Planned: 4
+               ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                     Order By: (i <-> 1100000)
+   ->  Hash
+         ->  Gather
+               Workers Planned: 4
+               ->  Parallel Seq Scan on bt_knn_test2 t2
+(12 rows)
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+RESET enable_sort;
+DROP TABLE bt_knn_test2;
+-- set the point to the left of the range
+\set bt_knn_point (-:bt_knn_row_count)
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i
+	FROM generate_series(1, :bt_knn_row_count) i;
+SET enable_sort = OFF;
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: ((row_number() OVER (?)) = t2.n)
+   Join Filter: (bt_knn_test.i <> t2.i)
+   ->  WindowAgg
+         ->  Gather Merge
+               Workers Planned: 4
+               ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                     Order By: (i <-> '-100000'::integer)
+   ->  Hash
+         ->  Gather
+               Workers Planned: 4
+               ->  Parallel Seq Scan on bt_knn_test2 t2
+(12 rows)
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+RESET enable_sort;
+DROP TABLE bt_knn_test;
+\set knn_row_count 30000
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, :knn_row_count) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+SET enable_sort = OFF;
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * :knn_row_count + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, :knn_row_count) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: ((row_number() OVER (?)) = ((i.i * 30000) + j.j))
+   Join Filter: (bt_knn_test.i <> ('{4,3,2,7,8}'::integer[])[(i.i + 1)])
+   ->  WindowAgg
+         ->  Gather Merge
+               Workers Planned: 4
+               ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                     Index Cond: (i = ANY ('{3,4,7,8,2}'::integer[]))
+                     Order By: (i <-> 4)
+   ->  Hash
+         ->  Nested Loop
+               ->  Function Scan on generate_series i
+               ->  Function Scan on generate_series j
+(13 rows)
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * :knn_row_count + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, :knn_row_count) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+RESET enable_sort;
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+ROLLBACK;
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: (ROW(thousand, tenthous) >= ROW(997, 5000))
+   Order By: (thousand <-> 998)
+(3 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+        1 |     9001
+        1 |     8001
+        1 |     7001
+        1 |     6001
+        1 |     5001
+        1 |     4001
+        1 |     3001
+        1 |     2001
+        1 |     1001
+        1 |        1
+        0 |     9000
+        0 |     8000
+        0 |     7000
+        0 |     6000
+        0 |     5000
+        0 |     4000
+        0 |     3000
+        0 |     2000
+        0 |     1000
+        0 |        0
+          |        1
+          |        2
+          |        3
+(33 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: ((thousand > 100) AND (thousand < 800) AND (thousand = ANY ('{0,123,234,345,456,678,901,NULL}'::smallint[])))
+   Order By: (thousand <-> '300'::bigint)
+(3 rows)
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+ thousand | tenthous 
+----------+----------
+      345 |      345
+      345 |     1345
+      345 |     2345
+      345 |     3345
+      345 |     4345
+      345 |     5345
+      345 |     6345
+      345 |     7345
+      345 |     8345
+      345 |     9345
+      234 |      234
+      234 |     1234
+      234 |     2234
+      234 |     3234
+      234 |     4234
+      234 |     5234
+      234 |     6234
+      234 |     7234
+      234 |     8234
+      234 |     9234
+      456 |      456
+      456 |     1456
+      456 |     2456
+      456 |     3456
+      456 |     4456
+      456 |     5456
+      456 |     6456
+      456 |     7456
+      456 |     8456
+      456 |     9456
+      123 |      123
+      123 |     1123
+      123 |     2123
+      123 |     3123
+      123 |     4123
+      123 |     5123
+      123 |     6123
+      123 |     7123
+      123 |     8123
+      123 |     9123
+      678 |      678
+      678 |     1678
+      678 |     2678
+      678 |     3678
+      678 |     4678
+      678 |     5678
+      678 |     6678
+      678 |     7678
+      678 |     8678
+      678 |     9678
+(50 rows)
+
+DROP INDEX tenk3_idx;
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+        1 |        1
+        1 |     1001
+        1 |     2001
+        1 |     3001
+        1 |     4001
+        1 |     5001
+        1 |     6001
+        1 |     7001
+        1 |     8001
+        1 |     9001
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+        0 |        0
+        0 |     1000
+        0 |     2000
+        0 |     3000
+        0 |     4000
+        0 |     5000
+        0 |     6000
+        0 |     7000
+        0 |     8000
+        0 |     9000
+          |        3
+          |        2
+          |        1
+(33 rows)
+
+DROP INDEX tenk3_idx;
+DROP TABLE tenk3;
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |     ?column?      
+--------------------------+-------------------
+ Wed May 03 00:00:00 2017 | @ 2 days
+ Wed May 03 01:00:00 2017 | @ 2 days 1 hour
+ Wed May 03 02:00:00 2017 | @ 2 days 2 hours
+ Wed May 03 03:00:00 2017 | @ 2 days 3 hours
+ Wed May 03 04:00:00 2017 | @ 2 days 4 hours
+ Wed May 03 05:00:00 2017 | @ 2 days 5 hours
+ Wed May 03 06:00:00 2017 | @ 2 days 6 hours
+ Wed May 03 07:00:00 2017 | @ 2 days 7 hours
+ Wed May 03 08:00:00 2017 | @ 2 days 8 hours
+ Wed May 03 09:00:00 2017 | @ 2 days 9 hours
+ Wed May 03 10:00:00 2017 | @ 2 days 10 hours
+ Wed May 03 11:00:00 2017 | @ 2 days 11 hours
+ Wed May 03 12:00:00 2017 | @ 2 days 12 hours
+ Wed May 03 13:00:00 2017 | @ 2 days 13 hours
+ Wed May 03 14:00:00 2017 | @ 2 days 14 hours
+ Wed May 03 15:00:00 2017 | @ 2 days 15 hours
+ Wed May 03 16:00:00 2017 | @ 2 days 16 hours
+ Wed May 03 17:00:00 2017 | @ 2 days 17 hours
+ Wed May 03 18:00:00 2017 | @ 2 days 18 hours
+ Wed May 03 19:00:00 2017 | @ 2 days 19 hours
+(20 rows)
+
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |  ?column?  
+--------------------------+------------
+ Mon Jan 01 00:00:00 2018 | @ 0
+ Mon Jan 01 01:00:00 2018 | @ 1 hour
+ Sun Dec 31 23:00:00 2017 | @ 1 hour
+ Mon Jan 01 02:00:00 2018 | @ 2 hours
+ Sun Dec 31 22:00:00 2017 | @ 2 hours
+ Mon Jan 01 03:00:00 2018 | @ 3 hours
+ Sun Dec 31 21:00:00 2017 | @ 3 hours
+ Mon Jan 01 04:00:00 2018 | @ 4 hours
+ Sun Dec 31 20:00:00 2017 | @ 4 hours
+ Mon Jan 01 05:00:00 2018 | @ 5 hours
+ Sun Dec 31 19:00:00 2017 | @ 5 hours
+ Mon Jan 01 06:00:00 2018 | @ 6 hours
+ Sun Dec 31 18:00:00 2017 | @ 6 hours
+ Mon Jan 01 07:00:00 2018 | @ 7 hours
+ Sun Dec 31 17:00:00 2017 | @ 7 hours
+ Mon Jan 01 08:00:00 2018 | @ 8 hours
+ Sun Dec 31 16:00:00 2017 | @ 8 hours
+ Mon Jan 01 09:00:00 2018 | @ 9 hours
+ Sun Dec 31 15:00:00 2017 | @ 9 hours
+ Mon Jan 01 10:00:00 2018 | @ 10 hours
+(20 rows)
+
+DROP TABLE knn_btree_ts;
+RESET enable_bitmapscan;
+-- Test backward kNN scan
+SET enable_sort = OFF;
+EXPLAIN (COSTS OFF) SELECT thousand, tenthous FROM tenk1 ORDER BY thousand  <-> 510;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Index Only Scan using tenk1_thous_tenthous on tenk1
+   Order By: (thousand <-> 510)
+(2 rows)
+
+BEGIN work;
+DECLARE knn SCROLL CURSOR FOR
+SELECT thousand, tenthous FROM tenk1 ORDER BY thousand <-> 510;
+FETCH LAST FROM knn;
+ thousand | tenthous 
+----------+----------
+        0 |        0
+(1 row)
+
+FETCH BACKWARD 15 FROM knn;
+ thousand | tenthous 
+----------+----------
+        0 |     1000
+        0 |     2000
+        0 |     3000
+        0 |     4000
+        0 |     5000
+        0 |     6000
+        0 |     7000
+        0 |     8000
+        0 |     9000
+        1 |        1
+        1 |     1001
+        1 |     2001
+        1 |     3001
+        1 |     4001
+        1 |     5001
+(15 rows)
+
+FETCH RELATIVE -200 FROM knn;
+ thousand | tenthous 
+----------+----------
+       21 |     5021
+(1 row)
+
+FETCH BACKWARD 20 FROM knn;
+ thousand | tenthous 
+----------+----------
+       21 |     6021
+       21 |     7021
+       21 |     8021
+       21 |     9021
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+       22 |       22
+       22 |     1022
+       22 |     2022
+       22 |     3022
+       22 |     4022
+       22 |     5022
+(20 rows)
+
+FETCH FIRST FROM knn;
+ thousand | tenthous 
+----------+----------
+      510 |      510
+(1 row)
+
+FETCH LAST FROM knn;
+ thousand | tenthous 
+----------+----------
+        0 |        0
+(1 row)
+
+FETCH RELATIVE -215 FROM knn;
+ thousand | tenthous 
+----------+----------
+       21 |     5021
+(1 row)
+
+FETCH BACKWARD 20 FROM knn;
+ thousand | tenthous 
+----------+----------
+       21 |     6021
+       21 |     7021
+       21 |     8021
+       21 |     9021
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+       22 |       22
+       22 |     1022
+       22 |     2022
+       22 |     3022
+       22 |     4022
+       22 |     5022
+(20 rows)
+
+ROLLBACK work;
+RESET enable_sort;
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 7610b011d6..7a84710ac9 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1430,6 +1430,8 @@ WHERE o1.oprnegate = o2.oid AND p1.oid = o1.oprcode AND p2.oid = o2.oprcode AND
 
 -- Btree comparison operators' functions should have the same volatility
 -- and leakproofness markings as the associated comparison support function.
+-- Btree ordering operators' functions may be not leakproof, while the
+-- associated comparison support function is leakproof.
 SELECT pp.oid::regprocedure as proc, pp.provolatile as vp, pp.proleakproof as lp,
        po.oid::regprocedure as opr, po.provolatile as vo, po.proleakproof as lo
 FROM pg_proc pp, pg_proc po, pg_operator o, pg_amproc ap, pg_amop ao
@@ -1440,7 +1442,10 @@ WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND
     ao.amoprighttype = ap.amprocrighttype AND
     ap.amprocnum = 1 AND
     (pp.provolatile != po.provolatile OR
-     pp.proleakproof != po.proleakproof)
+     (pp.proleakproof != po.proleakproof AND
+      ao.amoppurpose = 's') OR
+     (pp.proleakproof < po.proleakproof AND
+      ao.amoppurpose = 'o'))
 ORDER BY 1;
  proc | vp | lp | opr | vo | lo 
 ------+----+----+-----+----+----
@@ -1978,6 +1983,7 @@ ORDER BY 1, 2, 3;
         403 |            5 | *>
         403 |            5 | >
         403 |            5 | ~>~
+        403 |            6 | <->
         405 |            1 | =
         783 |            1 | <<
         783 |            1 | @@
@@ -2088,7 +2094,7 @@ ORDER BY 1, 2, 3;
        4000 |           28 | ^@
        4000 |           29 | <^
        4000 |           30 | >^
-(124 rows)
+(125 rows)
 
 -- Check that all opclass search operators have selectivity estimators.
 -- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 4f3fd46420..4452d2ea39 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5079,30 +5079,34 @@ List of access methods
 (1 row)
 
 \dAo+ btree float_ops
-                                List of operators of operator families
-  AM   | Operator family |               Operator                | Strategy | Purpose | Sort opfamily 
--------+-----------------+---------------------------------------+----------+---------+---------------
- btree | float_ops       | <(double precision,double precision)  |        1 | search  | 
- btree | float_ops       | <=(double precision,double precision) |        2 | search  | 
- btree | float_ops       | =(double precision,double precision)  |        3 | search  | 
- btree | float_ops       | >=(double precision,double precision) |        4 | search  | 
- btree | float_ops       | >(double precision,double precision)  |        5 | search  | 
- btree | float_ops       | <(real,real)                          |        1 | search  | 
- btree | float_ops       | <=(real,real)                         |        2 | search  | 
- btree | float_ops       | =(real,real)                          |        3 | search  | 
- btree | float_ops       | >=(real,real)                         |        4 | search  | 
- btree | float_ops       | >(real,real)                          |        5 | search  | 
- btree | float_ops       | <(double precision,real)              |        1 | search  | 
- btree | float_ops       | <=(double precision,real)             |        2 | search  | 
- btree | float_ops       | =(double precision,real)              |        3 | search  | 
- btree | float_ops       | >=(double precision,real)             |        4 | search  | 
- btree | float_ops       | >(double precision,real)              |        5 | search  | 
- btree | float_ops       | <(real,double precision)              |        1 | search  | 
- btree | float_ops       | <=(real,double precision)             |        2 | search  | 
- btree | float_ops       | =(real,double precision)              |        3 | search  | 
- btree | float_ops       | >=(real,double precision)             |        4 | search  | 
- btree | float_ops       | >(real,double precision)              |        5 | search  | 
-(20 rows)
+                                 List of operators of operator families
+  AM   | Operator family |                Operator                | Strategy | Purpose  | Sort opfamily 
+-------+-----------------+----------------------------------------+----------+----------+---------------
+ btree | float_ops       | <(double precision,double precision)   |        1 | search   | 
+ btree | float_ops       | <=(double precision,double precision)  |        2 | search   | 
+ btree | float_ops       | =(double precision,double precision)   |        3 | search   | 
+ btree | float_ops       | >=(double precision,double precision)  |        4 | search   | 
+ btree | float_ops       | >(double precision,double precision)   |        5 | search   | 
+ btree | float_ops       | <->(double precision,double precision) |        6 | ordering | float_ops
+ btree | float_ops       | <(real,real)                           |        1 | search   | 
+ btree | float_ops       | <=(real,real)                          |        2 | search   | 
+ btree | float_ops       | =(real,real)                           |        3 | search   | 
+ btree | float_ops       | >=(real,real)                          |        4 | search   | 
+ btree | float_ops       | >(real,real)                           |        5 | search   | 
+ btree | float_ops       | <->(real,real)                         |        6 | ordering | float_ops
+ btree | float_ops       | <(double precision,real)               |        1 | search   | 
+ btree | float_ops       | <=(double precision,real)              |        2 | search   | 
+ btree | float_ops       | =(double precision,real)               |        3 | search   | 
+ btree | float_ops       | >=(double precision,real)              |        4 | search   | 
+ btree | float_ops       | >(double precision,real)               |        5 | search   | 
+ btree | float_ops       | <->(double precision,real)             |        6 | ordering | float_ops
+ btree | float_ops       | <(real,double precision)               |        1 | search   | 
+ btree | float_ops       | <=(real,double precision)              |        2 | search   | 
+ btree | float_ops       | =(real,double precision)               |        3 | search   | 
+ btree | float_ops       | >=(real,double precision)              |        4 | search   | 
+ btree | float_ops       | >(real,double precision)               |        5 | search   | 
+ btree | float_ops       | <->(real,double precision)             |        6 | ordering | float_ops
+(24 rows)
 
 \dAo * pg_catalog.jsonb_path_ops
              List of operators of operator families
diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql
index de58d268d3..bd3710d631 100644
--- a/src/test/regress/sql/alter_generic.sql
+++ b/src/test/regress/sql/alter_generic.sql
@@ -306,8 +306,8 @@ ROLLBACK;
 -- Should fail. Invalid values for ALTER OPERATOR FAMILY .. ADD / DROP
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- invalid options parsing function
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 6 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -351,10 +351,12 @@ CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
 
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index ef84354234..b4729d4454 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -267,3 +267,315 @@ CREATE TABLE btree_part (id int4) PARTITION BY RANGE (id);
 CREATE INDEX btree_part_idx ON btree_part(id);
 ALTER INDEX btree_part_idx ALTER COLUMN id SET (n_distinct=100);
 DROP TABLE btree_part;
+
+---
+--- Test B-tree distance ordering
+---
+
+SET enable_bitmapscan = OFF;
+
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+-- test parallel KNN scan
+
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+
+RESET enable_indexscan;
+
+\set bt_knn_row_count 100000
+
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, :bt_knn_row_count) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+-- set the point inside the range
+\set bt_knn_point (4 * :bt_knn_row_count + 3)
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i
+	FROM generate_series(1, :bt_knn_row_count) i;
+
+SET enable_sort = OFF;
+
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+RESET enable_sort;
+
+DROP TABLE bt_knn_test2;
+
+-- set the point to the right of the range
+\set bt_knn_point (11 * :bt_knn_row_count)
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i
+	FROM generate_series(1, :bt_knn_row_count) i;
+
+SET enable_sort = OFF;
+
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+RESET enable_sort;
+
+DROP TABLE bt_knn_test2;
+
+-- set the point to the left of the range
+\set bt_knn_point (-:bt_knn_row_count)
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i
+	FROM generate_series(1, :bt_knn_row_count) i;
+
+SET enable_sort = OFF;
+
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+RESET enable_sort;
+
+DROP TABLE bt_knn_test;
+
+\set knn_row_count 30000
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, :knn_row_count) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+SET enable_sort = OFF;
+
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * :knn_row_count + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, :knn_row_count) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * :knn_row_count + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, :knn_row_count) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+RESET enable_sort;
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+
+ROLLBACK;
+
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+
+
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+DROP INDEX tenk3_idx;
+
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+DROP INDEX tenk3_idx;
+
+DROP TABLE tenk3;
+
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+
+DROP TABLE knn_btree_ts;
+
+RESET enable_bitmapscan;
+
+-- Test backward kNN scan
+
+SET enable_sort = OFF;
+
+EXPLAIN (COSTS OFF) SELECT thousand, tenthous FROM tenk1 ORDER BY thousand  <-> 510;
+
+BEGIN work;
+
+DECLARE knn SCROLL CURSOR FOR
+SELECT thousand, tenthous FROM tenk1 ORDER BY thousand <-> 510;
+
+FETCH LAST FROM knn;
+FETCH BACKWARD 15 FROM knn;
+FETCH RELATIVE -200 FROM knn;
+FETCH BACKWARD 20 FROM knn;
+FETCH FIRST FROM knn;
+FETCH LAST FROM knn;
+FETCH RELATIVE -215 FROM knn;
+FETCH BACKWARD 20 FROM knn;
+
+ROLLBACK work;
+
+RESET enable_sort;
\ No newline at end of file
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 2fe7b6dcc4..d871c80866 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -814,6 +814,8 @@ WHERE o1.oprnegate = o2.oid AND p1.oid = o1.oprcode AND p2.oid = o2.oprcode AND
 
 -- Btree comparison operators' functions should have the same volatility
 -- and leakproofness markings as the associated comparison support function.
+-- Btree ordering operators' functions may be not leakproof, while the
+-- associated comparison support function is leakproof.
 SELECT pp.oid::regprocedure as proc, pp.provolatile as vp, pp.proleakproof as lp,
        po.oid::regprocedure as opr, po.provolatile as vo, po.proleakproof as lo
 FROM pg_proc pp, pg_proc po, pg_operator o, pg_amproc ap, pg_amop ao
@@ -824,7 +826,10 @@ WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND
     ao.amoprighttype = ap.amprocrighttype AND
     ap.amprocnum = 1 AND
     (pp.provolatile != po.provolatile OR
-     pp.proleakproof != po.proleakproof)
+     (pp.proleakproof != po.proleakproof AND
+      ao.amoppurpose = 's') OR
+     (pp.proleakproof < po.proleakproof AND
+      ao.amoppurpose = 'o'))
 ORDER BY 1;
 
 
-- 
2.43.0

#46Andrey M. Borodin
x4mmm@yandex-team.ru
In reply to: Anton A. Melnikov (#45)
Re: [PATCH] kNN for btree

On 15 Jan 2024, at 13:11, Anton A. Melnikov <a.melnikov@postgrespro.ru> wrote:

If there are any ideas pro and contra would be glad to discuss them.

Hi, Anton!

This is kind of ancient thread. I've marked CF entry [0]https://commitfest.postgresql.org/47/4871/ as "Needs review" and you as an author (seems like you are going to be a point of correspondence on this feature).

At this point it's obvious that the feature won't make it to 17, so let's move to July CF. Given 7 years of history in this thread, you might want to start a new one with a summarisation of this thread. This will attract more reviewers, either way they have to dig on their own.

Thanks!

Best regards, Andrey Borodin.

[0]: https://commitfest.postgresql.org/47/4871/

#47Anton A. Melnikov
a.melnikov@postgrespro.ru
In reply to: Andrey M. Borodin (#46)
Re: [PATCH] kNN for btree

Hi, Andrey!

On 31.03.2024 12:22, Andrey M. Borodin wrote:

On 15 Jan 2024, at 13:11, Anton A. Melnikov <a.melnikov@postgrespro.ru> wrote:

If there are any ideas pro and contra would be glad to discuss them.

Hi, Anton!

This is kind of ancient thread. I've marked CF entry [0] as "Needs review" and you as an author (seems like you are going to be a point of correspondence on this feature).

That's right, i would like to bring the work on this feature to a positive result.

First of all, let me share a simple test that allows one to estimate the effect of applying this patch and,
i hope, can be considered as a criterion for future versions.

For all the tests below, one should set the following settings:
set enable_seqscan to false;
set enable_indexscan to true;
set enable_bitmapscan to false;
set enable_indexonlyscan to true;
set max_parallel_workers_per_gather = 0;

To carry out the test, one can use the table "events" mentioned in the first message of this thread, linked here [1]https://github.com/antamel/postgres/raw/test-base-events/test-rel/events.dump.gz.
psql -f events.dump
Then perform a query like that:
explain (costs off, analyze on) SELECT date FROM events ORDER BY date <-> '1957-10-04'::date ASC LIMIT 100000;

When using the existing btree_gist extension and preliminary commands executing:
create extension btree_gist;

CREATE INDEX event_date_idx ON events USING gist (date);

the test query gives:

postgres=# explain (costs off, analyze on) SELECT date FROM events ORDER BY date <-> '1957-10-04'::date ASC LIMIT 100000;

QUERY PLAN

------------------------------------------------------------------------------------------------------

Limit (actual time=0.759..102.992 rows=100000 loops=1)

-> Index Only Scan using event_date_idx on events (actual time=0.757..97.021 rows=100000 loops=1)

Order By: (date <-> '1957-10-04'::date)

Heap Fetches: 0

Planning Time: 0.504 ms

Execution Time: 108.311 ms

Average value on my PC was 107+-1 ms.

When using an existing patch from [1]https://github.com/antamel/postgres/raw/test-base-events/test-rel/events.dump.gz and creating a btree index:

CREATE INDEX event_date_idx ON events USING btree (date);

instead of btree_gist one, the test query gives:

postgres=# explain (costs off, analyze on) SELECT date FROM events ORDER BY date <-> '1957-10-04'::date ASC LIMIT 100000;

QUERY PLAN

------------------------------------------------------------------------------------------------------

Limit (actual time=0.120..48.817 rows=100000 loops=1)

-> Index Only Scan using event_date_idx on events (actual time=0.117..42.610 rows=100000 loops=1)

Order By: (date <-> '1957-10-04'::date)

Heap Fetches: 0

Planning Time: 0.487 ms

Execution Time: 54.463 ms

55+-1 ms on average.
The execution time is reduced by ~2 times. So the effect is obvious
but the implementation problems are reasonable too.

On 15.01.2024 11:11, Anton A. Melnikov wrote:

On 16.03.2020 16:17, Alexander Korotkov wrote:

After another try to polish this patch I figured out that the way it's
implemented is unnatural. I see the two reasonable ways to implement
knn for B-tree, but current implementation matches none of them.

1) Implement knn as two simultaneous scans over B-tree: forward and
backward. It's similar to what current patchset does. But putting
this logic to B-tree seems artificial. What B-tree does here is still
unidirectional scan. On top of that we merge results of two
unidirectional scans. The appropriate place to do this high-level
work is IndexScan node or even Optimizer/Executor (Merge node over to
IndexScan nodes), but not B-tree itself.
2) Implement arbitrary scans in B-tree using priority queue like GiST
and SP-GiST do. That would lead to much better support for KNN. We
can end up in supporting interesting cases like "ORDER BY col1 DESC,
col2 <> val1, col2 ASC" or something. But that's requires way more
hacking in B-tree core.

At first i'm going to implement p.1). I think it's preferable for now
because it seems easier and faster to get a working version.

I was wrong here. Firstly, this variant turned out to be not so easy and fast,
and secondly, when i received the desired query plan, i was not happy with the results:

In the case of btree_gist, splitting the query into two scans at the optimizer level
and adding MergeAppend on the top of it resulted in a ~13% slowdown in query execution.
The average time became ~121 ms.

Limit (actual time=1.205..117.689 rows=100000 loops=1)

-> Merge Append (actual time=1.202..112.260 rows=100000 loops=1)

Sort Key: ((events.date <-> '1957-10-04'::date))

-> Index Only Scan using event_date_idx on events (actual time=0.713..43.372 rows=42585 loops=1)

Index Cond: (date < '1957-10-04'::date)

Order By: (date <-> '1957-10-04'::date)

Heap Fetches: 0

-> Index Only Scan using event_date_idx on events events_1 (actual time=0.486..58.015 rows=57416 loops=1)

Index Cond: (date >= '1957-10-04'::date)

Order By: (date <-> '1957-10-04'::date)

Heap Fetches: 0

Planning Time: 1.212 ms

Execution Time: 120.517 ms

When using the btree index and the existing v18 patch, the slowdown from dividing the request
into two scans was less, ~3-4%, but i'm not sure about the correctness of the comparison in this case,
since the btree low level in the first variant proposed by Alexander
should work differently, like unpatched one.

Overall in terms of efficiency, the implementation of the first variant
turns out to be worse than the existing version of the patch.
IMO there is an additional argument pro the second variant proposed by Alexander.
The existing version of the patch does not support sorting in descending order.
Adding DESC to the test query gives:

postgres=# explain (costs off, analyze on) SELECT date FROM events ORDER BY date <-> '1957-10-04'::date DESC LIMIT 100000;

QUERY PLAN

--------------------------------------------------------------------------------

Limit (actual time=113.455..133.790 rows=100000 loops=1)

-> Sort (actual time=113.453..128.446 rows=100000 loops=1)

Sort Key: ((date <-> '1957-10-04'::date)) DESC

Sort Method: external merge Disk: 2680kB

-> Seq Scan on events (actual time=0.032..43.613 rows=151643 loops=1)

Planning Time: 0.514 ms

Execution Time: 142.278 ms

IndexOnlyScan disappears from the plan, and the query execution time increases by ~2.5 times.

For that regard, the existing implementation in btree_gist behaves more adequately:

postgres=# explain (costs off, analyze on) SELECT date FROM events ORDER BY date <-> '1957-10-04'::date DESC LIMIT 100000;

QUERY PLAN

------------------------------------------------------------------------------------------------------------

Limit (actual time=144.409..163.660 rows=100000 loops=1)

-> Sort (actual time=144.406..158.267 rows=100000 loops=1)

Sort Key: ((date <-> '1957-10-04'::date)) DESC

Sort Method: external merge Disk: 2680kB

-> Index Only Scan using event_date_idx on events (actual time=0.553..81.035 rows=151643 loops=1)

Heap Fetches: 0

Planning Time: 0.525 ms

Execution Time: 172.201 ms

IndexOnlyScan remains in the request, the query execution time increases by ~1.5 times in comparison with ASC order.

It seems that it would be better if the both sorting directions won't give essentially different plans
and not differ greatly from each other in execution time.

On 31.03.2024 12:22, Andrey M. Borodin wrote:

At this point it's obvious that the feature won't make it to 17, so let's move to July CF.

Of course. IMHO, this is the most suitable solution at the moment.
Thank you!

With the best wishes!

--
Anton A. Melnikov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

[1]: https://github.com/antamel/postgres/raw/test-base-events/test-rel/events.dump.gz

#48Anton A. Melnikov
a.melnikov@postgrespro.ru
In reply to: Anton A. Melnikov (#47)
5 attachment(s)
Re: [PATCH] kNN for btree

Hi!

Rebased existing patch on current master to have an actual working version.
There is an inconsistency with commit 5bf748b86.

Reproduction:
CREATE TABLE test (a int4);
INSERT INTO test VALUES (2), (3);
CREATE INDEX test_idx ON test USING btree(a);
SET enable_seqscan = OFF;
SELECT * FROM test WHERE a IN (2, 3) ORDER BY a <-> 5;

Actual result:
postgres=# SELECT * FROM test WHERE a IN (2, 3) ORDER BY a <-> 5;
a
---
3
(1 row)

Correct expected result:
postgres=# SELECT * FROM test WHERE a IN (2, 3) ORDER BY a <-> 5;
a
---
3
2
(2 rows)

Reported an issue in the thread corresponding to commit 5bf748b86.

With the best regards,

--
Anton A. Melnikov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v19_0001-Introduce-IndexAmRoutine.ammorderbyopfirstcol.patchtext/x-patch; charset=UTF-8; name=v19_0001-Introduce-IndexAmRoutine.ammorderbyopfirstcol.patchDownload
From 240dde791039ef5bac163e453da4daa1c382dd94 Mon Sep 17 00:00:00 2001
From: "Anton A. Melnikov" <a.melnikov@postgrespro.ru>
Date: Sun, 31 Dec 2023 14:10:23 +0300
Subject: [PATCH 1/5] Introduce IndexAmRoutine.ammorderbyopfirstcol

Currently IndexAmRoutine.amcanorderbyop property means that index access method
supports "column op const" ordering for every indexed column.  Upcoming
knn-btree supports it only for the first column.  In future we will probably
need to a callback to check whether particular ordering is supported.  But now
there are no potential use-cases around.  So, don't overengineer it and leave
with just boolean property.

Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru
Author: Nikita Glukhov
Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov
---
 contrib/bloom/blutils.c               |  1 +
 doc/src/sgml/indexam.sgml             |  9 ++++++++-
 src/backend/access/brin/brin.c        |  1 +
 src/backend/access/gin/ginutil.c      |  1 +
 src/backend/access/gist/gist.c        |  1 +
 src/backend/access/hash/hash.c        |  1 +
 src/backend/access/nbtree/nbtree.c    |  1 +
 src/backend/access/spgist/spgutils.c  |  1 +
 src/backend/optimizer/path/indxpath.c | 22 +++++++++++++++-------
 src/include/access/amapi.h            |  6 ++++++
 src/include/nodes/pathnodes.h         |  2 ++
 11 files changed, 38 insertions(+), 8 deletions(-)

diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 6836129c90d..916734f675c 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -112,6 +112,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amoptsprocnum = BLOOM_OPTIONS_PROC;
 	amroutine->amcanorder = false;
 	amroutine->amcanorderbyop = false;
+	amroutine->amorderbyopfirstcol = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml
index e3c1539a1e3..a69135cb780 100644
--- a/doc/src/sgml/indexam.sgml
+++ b/doc/src/sgml/indexam.sgml
@@ -103,6 +103,11 @@ typedef struct IndexAmRoutine
     bool        amcanorder;
     /* does AM support ORDER BY result of an operator on indexed column? */
     bool        amcanorderbyop;
+    /*
+     * Does AM support only the one ORDER BY operator on first indexed column?
+     * amcanorderbyop is implied.
+     */
+    bool        amorderbyopfirstcol;
     /* does AM support backward scanning? */
     bool        amcanbackward;
     /* does AM support UNIQUE indexes? */
@@ -932,7 +937,9 @@ amparallelrescan (IndexScanDesc scan);
        an order satisfying <literal>ORDER BY</literal> <replaceable>index_key</replaceable>
        <replaceable>operator</replaceable> <replaceable>constant</replaceable>.  Scan modifiers
        of that form can be passed to <function>amrescan</function> as described
-       previously.
+       previously.  If the access method supports the only one ORDER BY operator
+       on the first indexed column, then it should set
+       <structfield>amorderbyopfirstcol</structfield> to true.
       </para>
      </listitem>
     </itemizedlist>
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 6467bed604a..c371f726979 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -253,6 +253,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amoptsprocnum = BRIN_PROCNUM_OPTIONS;
 	amroutine->amcanorder = false;
 	amroutine->amcanorderbyop = false;
+	amroutine->amorderbyopfirstcol = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 5747ae6a4ca..e9536fd9238 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -43,6 +43,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amoptsprocnum = GIN_OPTIONS_PROC;
 	amroutine->amcanorder = false;
 	amroutine->amcanorderbyop = false;
+	amroutine->amorderbyopfirstcol = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index ed4ffa63a77..f6ed5023ef0 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -65,6 +65,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amoptsprocnum = GIST_OPTIONS_PROC;
 	amroutine->amcanorder = false;
 	amroutine->amcanorderbyop = true;
+	amroutine->amorderbyopfirstcol = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 01d06b7c328..5a090907bb3 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -63,6 +63,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amoptsprocnum = HASHOPTIONS_PROC;
 	amroutine->amcanorder = false;
 	amroutine->amcanorderbyop = false;
+	amroutine->amorderbyopfirstcol = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 686a3206f72..2d65adb22f7 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -107,6 +107,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amoptsprocnum = BTOPTIONS_PROC;
 	amroutine->amcanorder = true;
 	amroutine->amcanorderbyop = false;
+	amroutine->amorderbyopfirstcol = false;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = true;
 	amroutine->amcanmulticol = true;
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 76b80146ff0..2ba8cbc966b 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -50,6 +50,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amoptsprocnum = SPGIST_OPTIONS_PROC;
 	amroutine->amcanorder = false;
 	amroutine->amcanorderbyop = true;
+	amroutine->amorderbyopfirstcol = false;
 	amroutine->amcanbackward = false;
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = false;
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index c0fcc7d78df..c9685a86196 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -3030,6 +3030,10 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 	if (!index->amcanorderbyop)
 		return;
 
+	/* Only the one pathkey is supported when amorderbyopfirstcol is true */
+	if (index->amorderbyopfirstcol && list_length(pathkeys) != 1)
+		return;
+
 	foreach(lc1, pathkeys)
 	{
 		PathKey    *pathkey = (PathKey *) lfirst(lc1);
@@ -3058,20 +3062,24 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys,
 		{
 			EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
 			int			indexcol;
+			int			ncolumns;
 
 			/* No possibility of match if it references other relations */
 			if (!bms_equal(member->em_relids, index->rel->relids))
 				continue;
 
 			/*
-			 * We allow any column of the index to match each pathkey; they
-			 * don't have to match left-to-right as you might expect.  This is
-			 * correct for GiST, and it doesn't matter for SP-GiST because
-			 * that doesn't handle multiple columns anyway, and no other
-			 * existing AMs support amcanorderbyop.  We might need different
-			 * logic in future for other implementations.
+			 * We allow any column or only the first of the index to match
+			 * each pathkey; they don't have to match left-to-right as you
+			 * might expect.  This is correct for GiST, and it doesn't matter
+			 * for SP-GiST and B-Tree because they do not handle multiple
+			 * columns anyway, and no other existing AMs support
+			 * amcanorderbyop.  We might need different logic in future for
+			 * other implementations.
 			 */
-			for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
+			ncolumns = index->amorderbyopfirstcol ? 1 : index->nkeycolumns;
+
+			for (indexcol = 0; indexcol < ncolumns; indexcol++)
 			{
 				Expr	   *expr;
 
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index f25c9d58a7d..21b5d3149f8 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -224,6 +224,12 @@ typedef struct IndexAmRoutine
 	bool		amcanorder;
 	/* does AM support ORDER BY result of an operator on indexed column? */
 	bool		amcanorderbyop;
+
+	/*
+	 * Does AM support only the one ORDER BY operator on first indexed column?
+	 * amcanorderbyop is implied.
+	 */
+	bool		amorderbyopfirstcol;
 	/* does AM support backward scanning? */
 	bool		amcanbackward;
 	/* does AM support UNIQUE indexes? */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 14ccfc1ac1c..b7267f3ecfd 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1188,6 +1188,8 @@ struct IndexOptInfo
 	 * (IndexAmRoutine).  These fields are not set for partitioned indexes.
 	 */
 	bool		amcanorderbyop;
+	bool		amorderbyopfirstcol;	/* order by op is supported only on
+										 * first column? */
 	bool		amoptionalkey;
 	bool		amsearcharray;
 	bool		amsearchnulls;
-- 
2.45.2

v19_0002-Allow-ordering-by-operator-in-ordered-indexes.patchtext/x-patch; charset=UTF-8; name=v19_0002-Allow-ordering-by-operator-in-ordered-indexes.patchDownload
From 5e7ee4427856aa5dde4445a62337889a926c6bcb Mon Sep 17 00:00:00 2001
From: "Anton A. Melnikov" <a.melnikov@postgrespro.ru>
Date: Sun, 31 Dec 2023 14:34:57 +0300
Subject: [PATCH 2/5] Allow ordering by operator in ordered indexes

Currently the only ordered index access method is btree, which doesn't support
ordering by operator.  So, optimizer has an assumption that ordered index access
method can't support ordering by operator.  Upcoming knn-btree is going to
break this assumption.  This commit prepares optimizer for that.  Now we assume
following.

 * Index scan ordered by operator doesn't support backward scan independently
   on amcanbackward.
 * If index native ordering matches query needs then we don't consider possible
   operator ordering.

Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru
Author: Nikita Glukhov
Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov
---
 src/backend/executor/execAmi.c        |  6 ++++++
 src/backend/executor/nodeIndexscan.c  |  6 ++----
 src/backend/optimizer/path/indxpath.c | 19 ++++++++++---------
 3 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 3289e3e0219..a6545d90046 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -553,9 +553,15 @@ ExecSupportsBackwardScan(Plan *node)
 			return false;
 
 		case T_IndexScan:
+			/* Backward ORDER BY operator scans are not supported. */
+			if (((IndexScan *) node)->indexorderby)
+				return false;
 			return IndexSupportsBackwardScan(((IndexScan *) node)->indexid);
 
 		case T_IndexOnlyScan:
+			/* Backward ORDER BY operator scans are not supported. */
+			if (((IndexOnlyScan *) node)->indexorderby)
+				return false;
 			return IndexSupportsBackwardScan(((IndexOnlyScan *) node)->indexid);
 
 		case T_SubqueryScan:
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 8000feff4c9..b2332c03dd9 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -183,10 +183,8 @@ IndexNextWithReorder(IndexScanState *node)
 	 * Only forward scan is supported with reordering.  Note: we can get away
 	 * with just Asserting here because the system will not try to run the
 	 * plan backwards if ExecSupportsBackwardScan() says it won't work.
-	 * Currently, that is guaranteed because no index AMs support both
-	 * amcanorderbyop and amcanbackward; if any ever do,
-	 * ExecSupportsBackwardScan() will need to consider indexorderbys
-	 * explicitly.
+	 * Currently, ExecSupportsBackwardScan() simply returns false for index
+	 * plans with indexorderbys.
 	 */
 	Assert(!ScanDirectionIsBackward(((IndexScan *) node->ss.ps.plan)->indexorderdir));
 	Assert(ScanDirectionIsForward(estate->es_direction));
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index c9685a86196..36bfaab77fc 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -907,6 +907,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 	 * many of them are actually useful for this query.  This is not relevant
 	 * if we are only trying to build bitmap indexscans.
 	 */
+	useful_pathkeys = NIL;
+	orderbyclauses = NIL;
+	orderbyclausecols = NIL;
+
 	pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN &&
 								has_useful_pathkeys(root, rel));
 	index_is_ordered = (index->sortopfamily != NULL);
@@ -916,16 +920,19 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 											  ForwardScanDirection);
 		useful_pathkeys = truncate_useless_pathkeys(root, rel,
 													index_pathkeys);
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
 	}
-	else if (index->amcanorderbyop && pathkeys_possibly_useful)
+
+	if (useful_pathkeys == NIL &&
+		index->amcanorderbyop && pathkeys_possibly_useful)
 	{
 		/*
 		 * See if we can generate ordering operators for query_pathkeys or at
 		 * least some prefix thereof.  Matching to just a prefix of the
 		 * query_pathkeys will allow an incremental sort to be considered on
 		 * the index's partially sorted results.
+		 * Index  access method can be both ordered and supporting ordering by
+		 * operator.  We're looking for ordering by operator only when native
+		 * ordering doesn't match.
 		 */
 		match_pathkeys_to_index(index, root->query_pathkeys,
 								&orderbyclauses,
@@ -936,12 +943,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 			useful_pathkeys = list_copy_head(root->query_pathkeys,
 											 list_length(orderbyclauses));
 	}
-	else
-	{
-		useful_pathkeys = NIL;
-		orderbyclauses = NIL;
-		orderbyclausecols = NIL;
-	}
 
 	/*
 	 * 3. Check if an index-only scan is possible.  If we're not building
-- 
2.45.2

v19_0003-Extract-BTScanState-from-BTScanOpaqueData.patchtext/x-patch; charset=UTF-8; name=v19_0003-Extract-BTScanState-from-BTScanOpaqueData.patchDownload
From d33e1ef30e9f934385e4e5d73a60cc74ee595ea0 Mon Sep 17 00:00:00 2001
From: "Anton A. Melnikov" <a.melnikov@postgrespro.ru>
Date: Sun, 31 Dec 2023 19:45:35 +0300
Subject: [PATCH 3/5] Extract BTScanState from BTScanOpaqueData

Currently BTScanOpaqueData holds both information about scan keys and state
of tree scan.  That is OK as soon as we're going to scan btree just in single
direction.  Upcoming knn-btree patch provides btree scan in two directions
simultaneously.  This commit extracts data structure representing tree scan
state in a single direction into separate BTScanState struct in preparation
for knn-btree.

Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru
Author: Nikita Glukhov
Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov
---
 src/backend/access/nbtree/nbtree.c    | 181 ++++++-----
 src/backend/access/nbtree/nbtsearch.c | 424 ++++++++++++++------------
 src/backend/access/nbtree/nbtutils.c  |  51 ++--
 src/include/access/nbtree.h           |  42 ++-
 4 files changed, 378 insertions(+), 320 deletions(-)

diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 2d65adb22f7..fc3954cc157 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -207,6 +207,7 @@ bool
 btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 	bool		res;
 
 	/* btree indexes are never lossy */
@@ -220,7 +221,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(so->currPos))
+		if (!BTScanPosIsValid(state->currPos))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -238,11 +239,11 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 				 * trying to optimize that, so we don't detect it, but instead
 				 * just forget any excess entries.
 				 */
-				if (so->killedItems == NULL)
-					so->killedItems = (int *)
+				if (state->killedItems == NULL)
+					state->killedItems = (int *)
 						palloc(MaxTIDsPerBTreePage * sizeof(int));
-				if (so->numKilled < MaxTIDsPerBTreePage)
-					so->killedItems[so->numKilled++] = so->currPos.itemIndex;
+				if (state->numKilled < MaxTIDsPerBTreePage)
+					state->killedItems[state->numKilled++] = state->currPos.itemIndex;
 			}
 
 			/*
@@ -267,6 +268,7 @@ int64
 btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	int64		ntids = 0;
 	ItemPointer heapTid;
 
@@ -287,7 +289,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				 * Advance to next tuple within page.  This is the same as the
 				 * easy case in _bt_next().
 				 */
-				if (++so->currPos.itemIndex > so->currPos.lastItem)
+				if (++currPos->itemIndex > currPos->lastItem)
 				{
 					/* let _bt_next do the heavy lifting */
 					if (!_bt_next(scan, ForwardScanDirection))
@@ -295,7 +297,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 				}
 
 				/* Save tuple ID, and continue scanning */
-				heapTid = &so->currPos.items[so->currPos.itemIndex].heapTid;
+				heapTid = &currPos->items[currPos->itemIndex].heapTid;
 				tbm_add_tuples(tbm, heapTid, 1, false);
 				ntids++;
 			}
@@ -323,8 +325,8 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 
 	/* allocate private workspace */
 	so = (BTScanOpaque) palloc(sizeof(BTScanOpaqueData));
-	BTScanPosInvalidate(so->currPos);
-	BTScanPosInvalidate(so->markPos);
+	BTScanPosInvalidate(so->state.currPos);
+	BTScanPosInvalidate(so->state.markPos);
 	if (scan->numberOfKeys > 0)
 		so->keyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData));
 	else
@@ -336,15 +338,15 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	so->orderProcs = NULL;
 	so->arrayContext = NULL;
 
-	so->killedItems = NULL;		/* until needed */
-	so->numKilled = 0;
+	so->state.killedItems = NULL;	/* until needed */
+	so->state.numKilled = 0;
 
 	/*
 	 * We don't know yet whether the scan will be index-only, so we do not
 	 * allocate the tuple workspace arrays until btrescan.  However, we set up
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
-	so->currTuples = so->markTuples = NULL;
+	so->state.currTuples = so->state.markTuples = NULL;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -353,6 +355,45 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	return scan;
 }
 
+static void
+_bt_release_current_position(BTScanState state, Relation indexRelation,
+							 bool invalidate)
+{
+	/* we aren't holding any read locks, but gotta drop the pins */
+	if (BTScanPosIsValid(state->currPos))
+	{
+		/* Before leaving current page, deal with any killed items */
+		if (state->numKilled > 0)
+			_bt_killitems(state, indexRelation);
+
+		BTScanPosUnpinIfPinned(state->currPos);
+
+		if (invalidate)
+			BTScanPosInvalidate(state->currPos);
+	}
+}
+
+static void
+_bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
+{
+	/* No need to invalidate positions, if the RAM is about to be freed. */
+	_bt_release_current_position(state, scan->indexRelation, !free);
+
+	state->markItemIndex = -1;
+	BTScanPosUnpinIfPinned(state->markPos);
+
+	if (free)
+	{
+		if (state->killedItems != NULL)
+			pfree(state->killedItems);
+		if (state->currTuples != NULL)
+			pfree(state->currTuples);
+		/* markTuples should not be pfree'd (_bt_allocate_tuple_workspaces) */
+	}
+	else
+		BTScanPosInvalidate(state->markPos);
+}
+
 /*
  *	btrescan() -- rescan an index relation
  */
@@ -361,22 +402,12 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 		 ScanKey orderbys, int norderbys)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-		BTScanPosInvalidate(so->currPos);
-	}
+	_bt_release_scan_state(scan, state, false);
 
-	so->markItemIndex = -1;
 	so->needPrimScan = false;
 	so->scanBehind = false;
-	BTScanPosUnpinIfPinned(so->markPos);
-	BTScanPosInvalidate(so->markPos);
 
 	/*
 	 * Allocate tuple workspace arrays, if needed for an index-only scan and
@@ -394,11 +425,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 	 * a SIGSEGV is not possible.  Yeah, this is ugly as sin, but it beats
 	 * adding special-case treatment for name_ops elsewhere.
 	 */
-	if (scan->xs_want_itup && so->currTuples == NULL)
-	{
-		so->currTuples = (char *) palloc(BLCKSZ * 2);
-		so->markTuples = so->currTuples + BLCKSZ;
-	}
+	if (scan->xs_want_itup && state->currTuples == NULL)
+		_bt_allocate_tuple_workspaces(state);
 
 	/*
 	 * Reset the scan keys
@@ -419,19 +447,7 @@ btendscan(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	/* we aren't holding any read locks, but gotta drop the pins */
-	if (BTScanPosIsValid(so->currPos))
-	{
-		/* Before leaving current page, deal with any killed items */
-		if (so->numKilled > 0)
-			_bt_killitems(scan);
-		BTScanPosUnpinIfPinned(so->currPos);
-	}
-
-	so->markItemIndex = -1;
-	BTScanPosUnpinIfPinned(so->markPos);
-
-	/* No need to invalidate positions, the RAM is about to be freed. */
+	_bt_release_scan_state(scan, &so->state, true);
 
 	/* Release storage */
 	if (so->keyData != NULL)
@@ -439,24 +455,15 @@ btendscan(IndexScanDesc scan)
 	/* so->arrayKeys and so->orderProcs are in arrayContext */
 	if (so->arrayContext != NULL)
 		MemoryContextDelete(so->arrayContext);
-	if (so->killedItems != NULL)
-		pfree(so->killedItems);
-	if (so->currTuples != NULL)
-		pfree(so->currTuples);
-	/* so->markTuples should not be pfree'd, see btrescan */
+
 	pfree(so);
 }
 
-/*
- *	btmarkpos() -- save current scan position
- */
-void
-btmarkpos(IndexScanDesc scan)
+static void
+_bt_mark_current_position(BTScanState state)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-
 	/* There may be an old mark with a pin (but no lock). */
-	BTScanPosUnpinIfPinned(so->markPos);
+	BTScanPosUnpinIfPinned(state->markPos);
 
 	/*
 	 * Just record the current itemIndex.  If we later step to next page
@@ -464,24 +471,32 @@ btmarkpos(IndexScanDesc scan)
 	 * the currPos struct in markPos.  If (as often happens) the mark is moved
 	 * before we leave the page, we don't have to do that work.
 	 */
-	if (BTScanPosIsValid(so->currPos))
-		so->markItemIndex = so->currPos.itemIndex;
+	if (BTScanPosIsValid(state->currPos))
+		state->markItemIndex = state->currPos.itemIndex;
 	else
 	{
-		BTScanPosInvalidate(so->markPos);
-		so->markItemIndex = -1;
+		BTScanPosInvalidate(state->markPos);
+		state->markItemIndex = -1;
 	}
 }
 
 /*
- *	btrestrpos() -- restore scan to last saved position
+ *	btmarkpos() -- save current scan position
  */
 void
-btrestrpos(IndexScanDesc scan)
+btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	if (so->markItemIndex >= 0)
+	_bt_mark_current_position(&so->state);
+}
+
+static void
+_bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
+	if (state->markItemIndex >= 0)
 	{
 		/*
 		 * The scan has never moved to a new page since the last mark.  Just
@@ -490,7 +505,7 @@ btrestrpos(IndexScanDesc scan)
 		 * NB: In this case we can't count on anything in so->markPos to be
 		 * accurate.
 		 */
-		so->currPos.itemIndex = so->markItemIndex;
+		state->currPos.itemIndex = state->markItemIndex;
 	}
 	else
 	{
@@ -500,34 +515,27 @@ btrestrpos(IndexScanDesc scan)
 		 * locks, but if we're still holding the pin for the current position,
 		 * we must drop it.
 		 */
-		if (BTScanPosIsValid(so->currPos))
-		{
-			/* Before leaving current page, deal with any killed items */
-			if (so->numKilled > 0)
-				_bt_killitems(scan);
-			BTScanPosUnpinIfPinned(so->currPos);
-		}
+		_bt_release_current_position(state, scan->indexRelation,
+									 !BTScanPosIsValid(state->markPos));
 
-		if (BTScanPosIsValid(so->markPos))
+		if (BTScanPosIsValid(state->markPos))
 		{
 			/* bump pin on mark buffer for assignment to current buffer */
-			if (BTScanPosIsPinned(so->markPos))
-				IncrBufferRefCount(so->markPos.buf);
-			memcpy(&so->currPos, &so->markPos,
+			if (BTScanPosIsPinned(state->markPos))
+				IncrBufferRefCount(state->markPos.buf);
+			memcpy(&state->currPos, &state->markPos,
 				   offsetof(BTScanPosData, items[1]) +
-				   so->markPos.lastItem * sizeof(BTScanPosItem));
-			if (so->currTuples)
-				memcpy(so->currTuples, so->markTuples,
-					   so->markPos.nextTupleOffset);
+				   state->markPos.lastItem * sizeof(BTScanPosItem));
+			if (state->currTuples)
+				memcpy(state->currTuples, state->markTuples,
+					   state->markPos.nextTupleOffset);
 			/* Reset the scan's array keys (see _bt_steppage for why) */
 			if (so->numArrayKeys)
 			{
-				_bt_start_array_keys(scan, so->currPos.dir);
+				_bt_start_array_keys(scan, state->currPos.dir);
 				so->needPrimScan = false;
 			}
 		}
-		else
-			BTScanPosInvalidate(so->currPos);
 	}
 }
 
@@ -798,6 +806,17 @@ _bt_parallel_primscan_schedule(IndexScanDesc scan, BlockNumber prev_scan_page)
 	SpinLockRelease(&btscan->btps_mutex);
 }
 
+/*
+ *	btrestrpos() -- restore scan to last saved position
+ */
+void
+btrestrpos(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
+	_bt_restore_marked_position(scan, &so->state);
+}
+
 /*
  * Bulk deletion of all index entries pointing to a set of heap tuples.
  * The set of target tuples is specified via a callback routine that tells
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 57bcfc7e4c6..368857d3aa5 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -29,23 +29,26 @@ static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
 static OffsetNumber _bt_binsrch(Relation rel, BTScanInsert key, Buffer buf);
 static int	_bt_binsrch_posting(BTScanInsert key, Page page,
 								OffsetNumber offnum);
-static bool _bt_readpage(IndexScanDesc scan, ScanDirection dir,
-						 OffsetNumber offnum, bool firstPage);
-static void _bt_saveitem(BTScanOpaque so, int itemIndex,
+static bool _bt_readpage(IndexScanDesc scan, BTScanState state,
+						 ScanDirection dir, OffsetNumber offnum,
+						 bool firstPage);
+static void _bt_saveitem(BTScanState state, int itemIndex,
 						 OffsetNumber offnum, IndexTuple itup);
-static int	_bt_setuppostingitems(BTScanOpaque so, int itemIndex,
+static int	_bt_setuppostingitems(BTScanState state, int itemIndex,
 								  OffsetNumber offnum, ItemPointer heapTid,
 								  IndexTuple itup);
-static inline void _bt_savepostingitem(BTScanOpaque so, int itemIndex,
+static inline void _bt_savepostingitem(BTScanState state, int itemIndex,
 									   OffsetNumber offnum,
 									   ItemPointer heapTid, int tupleOffset);
-static bool _bt_steppage(IndexScanDesc scan, ScanDirection dir);
-static bool _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir);
+static bool _bt_steppage(IndexScanDesc scan, BTScanState state,
+						 ScanDirection dir);
+static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
+							 BlockNumber blkno, ScanDirection dir);
 static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
 								  ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
-static inline void _bt_initialize_more_data(BTScanOpaque so, ScanDirection dir);
+static inline void _bt_initialize_more_data(IndexScanDesc scan, BTScanState state, ScanDirection dir);
 
 
 /*
@@ -852,6 +855,58 @@ _bt_compare(Relation rel,
 	return 0;
 }
 
+/*
+ * _bt_return_current_item() -- Prepare current scan state item for return.
+ *
+ * This function is used only in "return _bt_return_current_item();" statements
+ * and always returns true.
+ */
+static inline bool
+_bt_return_current_item(IndexScanDesc scan, BTScanState state)
+{
+	BTScanPosItem *currItem = &state->currPos.items[state->currPos.itemIndex];
+
+	scan->xs_heaptid = currItem->heapTid;
+
+	if (scan->xs_want_itup)
+		scan->xs_itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+
+	return true;
+}
+
+/*
+ * _bt_load_first_page() -- Load data from the first page of the scan.
+ *
+ * Caller must have pinned and read-locked state->currPos.buf.
+ *
+ * On success exit, state->currPos is updated to contain data from the next
+ * interesting page.  For success on a scan using a non-MVCC snapshot we hold
+ * a pin, but not a read lock, on that page.  If we do not hold the pin, we
+ * set state->currPos.buf to InvalidBuffer.  We return true to indicate success.
+ *
+ * If there are no more matching records in the given direction at all,
+ * we drop all locks and pins, set state->currPos.buf to InvalidBuffer,
+ * and return false.
+ */
+static bool
+_bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
+					OffsetNumber offnum)
+{
+	if (!_bt_readpage(scan, state, dir, offnum, true))
+	{
+		/*
+		 * There's no actually-matching data on this page.  Try to advance to
+		 * the next page.  Return false if there's no matching data at all.
+		 */
+		LockBuffer(state->currPos.buf, BUFFER_LOCK_UNLOCK);
+		return _bt_steppage(scan, state, dir);
+	}
+
+	/* Drop the lock, and maybe the pin, on the current page */
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
+	return true;
+}
+
 /*
  *	_bt_first() -- Find the first item in a scan.
  *
@@ -877,6 +932,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	BTStack		stack;
 	OffsetNumber offnum;
@@ -888,10 +944,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	int			i;
 	bool		status;
 	StrategyNumber strat_total;
-	BTScanPosItem *currItem;
 	BlockNumber blkno;
 
-	Assert(!BTScanPosIsValid(so->currPos));
+	Assert(!BTScanPosIsValid(*currPos));
 
 	pgstat_count_index_scan(rel);
 
@@ -1415,19 +1470,19 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			 * their scan.
 			 */
 			_bt_parallel_done(scan);
-			BTScanPosInvalidate(so->currPos);
+			BTScanPosInvalidate(*currPos);
 			return false;
 		}
 	}
 
 	PredicateLockPage(rel, BufferGetBlockNumber(buf), scan->xs_snapshot);
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(scan, &so->state, dir);
 
 	/* position to the precise item on the page */
 	offnum = _bt_binsrch(rel, &inskey, buf);
-	Assert(!BTScanPosIsValid(so->currPos));
-	so->currPos.buf = buf;
+	Assert(!BTScanPosIsValid(*currPos));
+	currPos->buf = buf;
 
 	/*
 	 * Now load data from the first page of the scan.
@@ -1448,30 +1503,33 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 * for the page.  For example, when inskey is both < the leaf page's high
 	 * key and > all of its non-pivot tuples, offnum will be "maxoff + 1".
 	 */
-	if (!_bt_readpage(scan, dir, offnum, true))
+	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
+		return false;
+
+readcomplete:
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
+}
+
+/*
+ *	Advance to next tuple on current page; or if there's no more,
+ *	try to step to the next page with data.
+ */
+static bool
+_bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
+{
+	if (ScanDirectionIsForward(dir))
 	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		_bt_unlockbuf(scan->indexRelation, so->currPos.buf);
-		if (!_bt_steppage(scan, dir))
-			return false;
+		if (++state->currPos.itemIndex <= state->currPos.lastItem)
+			return true;
 	}
 	else
 	{
-		/* We have at least one item to return as scan's first item */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+		if (--state->currPos.itemIndex >= state->currPos.firstItem)
+			return true;
 	}
 
-readcomplete:
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_heaptid = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_steppage(scan, state, dir);
 }
 
 /*
@@ -1492,44 +1550,20 @@ bool
 _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
-	BTScanPosItem *currItem;
 
-	/*
-	 * Advance to next tuple on current page; or if there's no more, try to
-	 * step to the next page with data.
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (++so->currPos.itemIndex > so->currPos.lastItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
-	else
-	{
-		if (--so->currPos.itemIndex < so->currPos.firstItem)
-		{
-			if (!_bt_steppage(scan, dir))
-				return false;
-		}
-	}
+	if (!_bt_next_item(scan, &so->state, dir))
+		return false;
 
 	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_heaptid = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
-
-	return true;
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
  *	_bt_readpage() -- Load data from current index page into so->currPos
  *
- * Caller must have pinned and read-locked so->currPos.buf; the buffer's state
- * is not changed here.  Also, currPos.moreLeft and moreRight must be valid;
- * they are updated as appropriate.  All other fields of so->currPos are
+ * Caller must have pinned and read-locked pos->buf; the buffer's state
+ * is not changed here.  Also, pos->moreLeft and moreRight must be valid;
+ * they are updated as appropriate.  All other fields of pos are
  * initialized from scratch here.
  *
  * We scan the current page starting at offnum and moving in the indicated
@@ -1553,10 +1587,11 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
  * Returns true if any matching items found on the page, false if none.
  */
 static bool
-_bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
+_bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir, OffsetNumber offnum,
 			 bool firstPage)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
@@ -1570,9 +1605,9 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 	 * We must have the buffer pinned and locked, but the usual macro can't be
 	 * used here; this function is what makes it good for currPos.
 	 */
-	Assert(BufferIsValid(so->currPos.buf));
+	Assert(BufferIsValid(pos->buf));
 
-	page = BufferGetPage(so->currPos.buf);
+	page = BufferGetPage(pos->buf);
 	opaque = BTPageGetOpaque(page);
 
 	/* allow next page be processed by parallel worker */
@@ -1581,7 +1616,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 		if (ScanDirectionIsForward(dir))
 			pstate.prev_scan_page = opaque->btpo_next;
 		else
-			pstate.prev_scan_page = BufferGetBlockNumber(so->currPos.buf);
+			pstate.prev_scan_page = BufferGetBlockNumber(pos->buf);
 
 		_bt_parallel_release(scan, pstate.prev_scan_page);
 	}
@@ -1609,30 +1644,30 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 	 * We note the buffer's block number so that we can release the pin later.
 	 * This allows us to re-read the buffer if it is needed again for hinting.
 	 */
-	so->currPos.currPage = BufferGetBlockNumber(so->currPos.buf);
+	pos->currPage = BufferGetBlockNumber(pos->buf);
 
 	/*
 	 * We save the LSN of the page as we read it, so that we know whether it
 	 * safe to apply LP_DEAD hints to the page later.  This allows us to drop
 	 * the pin for MVCC scans, which allows vacuum to avoid blocking.
 	 */
-	so->currPos.lsn = BufferGetLSNAtomic(so->currPos.buf);
+	pos->lsn = BufferGetLSNAtomic(pos->buf);
 
 	/*
 	 * we must save the page's right-link while scanning it; this tells us
 	 * where to step right to after we're done with these items.  There is no
 	 * corresponding need for the left-link, since splits always go right.
 	 */
-	so->currPos.nextPage = opaque->btpo_next;
+	pos->nextPage = opaque->btpo_next;
 
 	/* initialize tuple workspace to empty */
-	so->currPos.nextTupleOffset = 0;
+	pos->nextTupleOffset = 0;
 
 	/*
 	 * Now that the current page has been made consistent, the macro should be
 	 * good.
 	 */
-	Assert(BTScanPosIsPinned(so->currPos));
+	Assert(BTScanPosIsPinned(*pos));
 
 	/*
 	 * Prechecking the value of the continuescan flag for the last item on the
@@ -1747,7 +1782,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 				if (!BTreeTupleIsPosting(itup))
 				{
 					/* Remember it */
-					_bt_saveitem(so, itemIndex, offnum, itup);
+					_bt_saveitem(state, itemIndex, offnum, itup);
 					itemIndex++;
 				}
 				else
@@ -1759,14 +1794,14 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 					 * TID
 					 */
 					tupleOffset =
-						_bt_setuppostingitems(so, itemIndex, offnum,
+						_bt_setuppostingitems(state, itemIndex, offnum,
 											  BTreeTupleGetPostingN(itup, 0),
 											  itup);
 					itemIndex++;
 					/* Remember additional TIDs */
 					for (int i = 1; i < BTreeTupleGetNPosting(itup); i++)
 					{
-						_bt_savepostingitem(so, itemIndex, offnum,
+						_bt_savepostingitem(state, itemIndex, offnum,
 											BTreeTupleGetPostingN(itup, i),
 											tupleOffset);
 						itemIndex++;
@@ -1803,12 +1838,12 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 		}
 
 		if (!pstate.continuescan)
-			so->currPos.moreRight = false;
+			pos->moreRight = false;
 
 		Assert(itemIndex <= MaxTIDsPerBTreePage);
-		so->currPos.firstItem = 0;
-		so->currPos.lastItem = itemIndex - 1;
-		so->currPos.itemIndex = 0;
+		pos->firstItem = 0;
+		pos->lastItem = itemIndex - 1;
+		pos->itemIndex = 0;
 	}
 	else
 	{
@@ -1885,7 +1920,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 				{
 					/* Remember it */
 					itemIndex--;
-					_bt_saveitem(so, itemIndex, offnum, itup);
+					_bt_saveitem(state, itemIndex, offnum, itup);
 				}
 				else
 				{
@@ -1903,14 +1938,14 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 					 */
 					itemIndex--;
 					tupleOffset =
-						_bt_setuppostingitems(so, itemIndex, offnum,
+						_bt_setuppostingitems(state, itemIndex, offnum,
 											  BTreeTupleGetPostingN(itup, 0),
 											  itup);
 					/* Remember additional TIDs */
 					for (int i = 1; i < BTreeTupleGetNPosting(itup); i++)
 					{
 						itemIndex--;
-						_bt_savepostingitem(so, itemIndex, offnum,
+						_bt_savepostingitem(state, itemIndex, offnum,
 											BTreeTupleGetPostingN(itup, i),
 											tupleOffset);
 					}
@@ -1919,7 +1954,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 			if (!pstate.continuescan)
 			{
 				/* there can't be any more matches, so stop */
-				so->currPos.moreLeft = false;
+				pos->moreLeft = false;
 				break;
 			}
 
@@ -1927,39 +1962,40 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 		}
 
 		Assert(itemIndex >= 0);
-		so->currPos.firstItem = itemIndex;
-		so->currPos.lastItem = MaxTIDsPerBTreePage - 1;
-		so->currPos.itemIndex = MaxTIDsPerBTreePage - 1;
+		pos->firstItem = itemIndex;
+		pos->lastItem = MaxTIDsPerBTreePage - 1;
+		pos->itemIndex = MaxTIDsPerBTreePage - 1;
 	}
 
-	return (so->currPos.firstItem <= so->currPos.lastItem);
+	return (pos->firstItem <= pos->lastItem);
 }
 
 /* Save an index item into so->currPos.items[itemIndex] */
 static void
-_bt_saveitem(BTScanOpaque so, int itemIndex,
+_bt_saveitem(BTScanState state, int itemIndex,
 			 OffsetNumber offnum, IndexTuple itup)
 {
-	BTScanPosItem *currItem = &so->currPos.items[itemIndex];
+	BTScanPosItem *currItem = &state->currPos.items[itemIndex];
 
 	Assert(!BTreeTupleIsPivot(itup) && !BTreeTupleIsPosting(itup));
 
 	currItem->heapTid = itup->t_tid;
 	currItem->indexOffset = offnum;
-	if (so->currTuples)
+	if (state->currTuples)
 	{
 		Size		itupsz = IndexTupleSize(itup);
 
-		currItem->tupleOffset = so->currPos.nextTupleOffset;
-		memcpy(so->currTuples + so->currPos.nextTupleOffset, itup, itupsz);
-		so->currPos.nextTupleOffset += MAXALIGN(itupsz);
+		currItem->tupleOffset = state->currPos.nextTupleOffset;
+		memcpy(state->currTuples + state->currPos.nextTupleOffset,
+			   itup, itupsz);
+		state->currPos.nextTupleOffset += MAXALIGN(itupsz);
 	}
 }
 
 /*
  * Setup state to save TIDs/items from a single posting list tuple.
  *
- * Saves an index item into so->currPos.items[itemIndex] for TID that is
+ * Saves an index item into state->currPos.items[itemIndex] for TID that is
  * returned to scan first.  Second or subsequent TIDs for posting list should
  * be saved by calling _bt_savepostingitem().
  *
@@ -1967,29 +2003,29 @@ _bt_saveitem(BTScanOpaque so, int itemIndex,
  * needed.
  */
 static int
-_bt_setuppostingitems(BTScanOpaque so, int itemIndex, OffsetNumber offnum,
+_bt_setuppostingitems(BTScanState state, int itemIndex, OffsetNumber offnum,
 					  ItemPointer heapTid, IndexTuple itup)
 {
-	BTScanPosItem *currItem = &so->currPos.items[itemIndex];
+	BTScanPosItem *currItem = &state->currPos.items[itemIndex];
 
 	Assert(BTreeTupleIsPosting(itup));
 
 	currItem->heapTid = *heapTid;
 	currItem->indexOffset = offnum;
-	if (so->currTuples)
+	if (state->currTuples)
 	{
 		/* Save base IndexTuple (truncate posting list) */
 		IndexTuple	base;
 		Size		itupsz = BTreeTupleGetPostingOffset(itup);
 
 		itupsz = MAXALIGN(itupsz);
-		currItem->tupleOffset = so->currPos.nextTupleOffset;
-		base = (IndexTuple) (so->currTuples + so->currPos.nextTupleOffset);
+		currItem->tupleOffset = state->currPos.nextTupleOffset;
+		base = (IndexTuple) (state->currTuples + state->currPos.nextTupleOffset);
 		memcpy(base, itup, itupsz);
 		/* Defensively reduce work area index tuple header size */
 		base->t_info &= ~INDEX_SIZE_MASK;
 		base->t_info |= itupsz;
-		so->currPos.nextTupleOffset += itupsz;
+		state->currPos.nextTupleOffset += itupsz;
 
 		return currItem->tupleOffset;
 	}
@@ -1998,17 +2034,17 @@ _bt_setuppostingitems(BTScanOpaque so, int itemIndex, OffsetNumber offnum,
 }
 
 /*
- * Save an index item into so->currPos.items[itemIndex] for current posting
+ * Save an index item into state->currPos.items[itemIndex] for current posting
  * tuple.
  *
  * Assumes that _bt_setuppostingitems() has already been called for current
  * posting list tuple.  Caller passes its return value as tupleOffset.
  */
 static inline void
-_bt_savepostingitem(BTScanOpaque so, int itemIndex, OffsetNumber offnum,
+_bt_savepostingitem(BTScanState state, int itemIndex, OffsetNumber offnum,
 					ItemPointer heapTid, int tupleOffset)
 {
-	BTScanPosItem *currItem = &so->currPos.items[itemIndex];
+	BTScanPosItem *currItem = &state->currPos.items[itemIndex];
 
 	currItem->heapTid = *heapTid;
 	currItem->indexOffset = offnum;
@@ -2017,7 +2053,7 @@ _bt_savepostingitem(BTScanOpaque so, int itemIndex, OffsetNumber offnum,
 	 * Have index-only scans return the same base IndexTuple for every TID
 	 * that originates from the same posting list
 	 */
-	if (so->currTuples)
+	if (state->currTuples)
 		currItem->tupleOffset = tupleOffset;
 }
 
@@ -2033,35 +2069,37 @@ _bt_savepostingitem(BTScanOpaque so, int itemIndex, OffsetNumber offnum,
  * to InvalidBuffer.  We return true to indicate success.
  */
 static bool
-_bt_steppage(IndexScanDesc scan, ScanDirection dir)
+_bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
+	Relation	rel = scan->indexRelation;
 	BlockNumber blkno = InvalidBlockNumber;
 	bool		status;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(*currPos));
 
 	/* Before leaving current page, deal with any killed items */
-	if (so->numKilled > 0)
-		_bt_killitems(scan);
+	if (state->numKilled > 0)
+		_bt_killitems(state, rel);
 
 	/*
 	 * Before we modify currPos, make a copy of the page data if there was a
 	 * mark position that needs it.
 	 */
-	if (so->markItemIndex >= 0)
+	if (state->markItemIndex >= 0)
 	{
 		/* bump pin on current buffer for assignment to mark buffer */
-		if (BTScanPosIsPinned(so->currPos))
-			IncrBufferRefCount(so->currPos.buf);
-		memcpy(&so->markPos, &so->currPos,
+		if (BTScanPosIsPinned(*currPos))
+			IncrBufferRefCount(currPos->buf);
+		memcpy(&state->markPos, currPos,
 			   offsetof(BTScanPosData, items[1]) +
-			   so->currPos.lastItem * sizeof(BTScanPosItem));
-		if (so->markTuples)
-			memcpy(so->markTuples, so->currTuples,
-				   so->currPos.nextTupleOffset);
-		so->markPos.itemIndex = so->markItemIndex;
-		so->markItemIndex = -1;
+			   currPos->lastItem * sizeof(BTScanPosItem));
+		if (state->markTuples)
+			memcpy(state->markTuples, state->currTuples,
+				   currPos->nextTupleOffset);
+		state->markPos.itemIndex = state->markItemIndex;
+		state->markItemIndex = -1;
 
 		/*
 		 * If we're just about to start the next primitive index scan
@@ -2079,13 +2117,13 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 		 * In effect, btrestpos leaves advancing the arrays up to the first
 		 * _bt_readpage call (that takes place after it has restored markPos).
 		 */
-		Assert(so->markPos.dir == dir);
+		Assert(state->markPos.dir == dir);
 		if (so->needPrimScan)
 		{
 			if (ScanDirectionIsForward(dir))
-				so->markPos.moreRight = true;
+				state->markPos.moreRight = true;
 			else
-				so->markPos.moreLeft = true;
+				state->markPos.moreLeft = true;
 		}
 	}
 
@@ -2102,27 +2140,27 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
-				BTScanPosUnpinIfPinned(so->currPos);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosUnpinIfPinned(*currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so use the previously-saved nextPage link. */
-			blkno = so->currPos.nextPage;
+			blkno = currPos->nextPage;
 		}
 
 		/* Remember we left a page with data */
-		so->currPos.moreLeft = true;
+		currPos->moreLeft = true;
 
 		/* release the previous buffer, if pinned */
-		BTScanPosUnpinIfPinned(so->currPos);
+		BTScanPosUnpinIfPinned(*currPos);
 	}
 	else
 	{
 		/* Remember we left a page with data */
-		so->currPos.moreRight = true;
+		currPos->moreRight = true;
 
 		if (scan->parallel_scan != NULL)
 		{
@@ -2131,25 +2169,25 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
 			 * ended already, bail out.
 			 */
 			status = _bt_parallel_seize(scan, &blkno, false);
-			BTScanPosUnpinIfPinned(so->currPos);
+			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 		}
 		else
 		{
 			/* Not parallel, so just use our own notion of the current page */
-			blkno = so->currPos.currPage;
+			blkno = currPos->currPage;
 		}
 	}
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
 	/* We have at least one item to return as scan's next item */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, currPos);
 
 	return true;
 }
@@ -2165,9 +2203,10 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
  * locks and pins, set so->currPos.buf to InvalidBuffer, and return false.
  */
 static bool
-_bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+				 ScanDirection dir)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &state->currPos;
 	Relation	rel;
 	Page		page;
 	BTPageOpaque opaque;
@@ -2183,17 +2222,17 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * if we're at end of scan, give up and mark parallel scan as
 			 * done, so that all the workers can finish their scan
 			 */
-			if (blkno == P_NONE || !so->currPos.moreRight)
+			if (blkno == P_NONE || !currPos->moreRight)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 			/* check for interrupts while we're not holding any buffer lock */
 			CHECK_FOR_INTERRUPTS();
 			/* step right one page */
-			so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
-			page = BufferGetPage(so->currPos.buf);
+			currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
+			page = BufferGetPage(currPos->buf);
 			opaque = BTPageGetOpaque(page);
 			/* check for deleted page */
 			if (!P_IGNORE(opaque))
@@ -2201,7 +2240,7 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 				PredicateLockPage(rel, blkno, scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreRight if we can stop */
-				if (_bt_readpage(scan, dir, P_FIRSTDATAKEY(opaque), false))
+				if (_bt_readpage(scan, state, dir, P_FIRSTDATAKEY(opaque), false))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
@@ -2213,18 +2252,18 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno, false);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
 			}
 			else
 			{
 				blkno = opaque->btpo_next;
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 			}
 		}
 	}
@@ -2234,10 +2273,10 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * Should only happen in parallel cases, when some other backend
 		 * advanced the scan.
 		 */
-		if (so->currPos.currPage != blkno)
+		if (currPos->currPage != blkno)
 		{
-			BTScanPosUnpinIfPinned(so->currPos);
-			so->currPos.currPage = blkno;
+			BTScanPosUnpinIfPinned(*currPos);
+			currPos->currPage = blkno;
 		}
 
 		/*
@@ -2253,30 +2292,30 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 		 * optimistically starting there (rather than pinning the page twice).
 		 * It is not clear that this would be worth the complexity.
 		 */
-		if (BTScanPosIsPinned(so->currPos))
-			_bt_lockbuf(rel, so->currPos.buf, BT_READ);
+		if (BTScanPosIsPinned(*currPos))
+			_bt_lockbuf(rel, currPos->buf, BT_READ);
 		else
-			so->currPos.buf = _bt_getbuf(rel, so->currPos.currPage, BT_READ);
+			currPos->buf = _bt_getbuf(rel, currPos->currPage, BT_READ);
 
 		for (;;)
 		{
 			/* Done if we know there are no matching keys to the left */
-			if (!so->currPos.moreLeft)
+			if (!currPos->moreLeft)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
 			/* Step to next physical page */
-			so->currPos.buf = _bt_walk_left(rel, so->currPos.buf);
+			currPos->buf = _bt_walk_left(rel, currPos->buf);
 
 			/* if we're physically at end of index, return failure */
-			if (so->currPos.buf == InvalidBuffer)
+			if (currPos->buf == InvalidBuffer)
 			{
 				_bt_parallel_done(scan);
-				BTScanPosInvalidate(so->currPos);
+				BTScanPosInvalidate(*currPos);
 				return false;
 			}
 
@@ -2285,20 +2324,20 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 * it's not half-dead and contains matching tuples. Else loop back
 			 * and do it all again.
 			 */
-			page = BufferGetPage(so->currPos.buf);
+			page = BufferGetPage(currPos->buf);
 			opaque = BTPageGetOpaque(page);
 			if (!P_IGNORE(opaque))
 			{
-				PredicateLockPage(rel, BufferGetBlockNumber(so->currPos.buf), scan->xs_snapshot);
+				PredicateLockPage(rel, BufferGetBlockNumber(currPos->buf), scan->xs_snapshot);
 				/* see if there are any matches on this page */
 				/* note that this will clear moreLeft if we can stop */
-				if (_bt_readpage(scan, dir, PageGetMaxOffsetNumber(page), false))
+				if (_bt_readpage(scan, state, dir, PageGetMaxOffsetNumber(page), false))
 					break;
 			}
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
+				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -2309,14 +2348,14 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 			 */
 			if (scan->parallel_scan != NULL)
 			{
-				_bt_relbuf(rel, so->currPos.buf);
+				_bt_relbuf(rel, currPos->buf);
 				status = _bt_parallel_seize(scan, &blkno, false);
 				if (!status)
 				{
-					BTScanPosInvalidate(so->currPos);
+					BTScanPosInvalidate(*currPos);
 					return false;
 				}
-				so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
+				currPos->buf = _bt_getbuf(rel, blkno, BT_READ);
 			}
 		}
 	}
@@ -2337,13 +2376,13 @@ _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 
 	Assert(!so->needPrimScan);
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(scan, &so->state, dir);
 
-	if (!_bt_readnextpage(scan, blkno, dir))
+	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
 		return false;
 
 	/* We have at least one item to return as scan's next item */
-	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
 
 	return true;
 }
@@ -2561,11 +2600,11 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 {
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	currPos = &so->state.currPos;
 	Buffer		buf;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber start;
-	BTScanPosItem *currItem;
 
 	/*
 	 * Scan down to the leftmost or rightmost leaf page.  This is a simplified
@@ -2581,7 +2620,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 		 * exists.
 		 */
 		PredicateLockRelation(rel, scan->xs_snapshot);
-		BTScanPosInvalidate(so->currPos);
+		BTScanPosInvalidate(*currPos);
 		return false;
 	}
 
@@ -2610,36 +2649,15 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 	}
 
 	/* remember which buffer we have pinned */
-	so->currPos.buf = buf;
+	currPos->buf = buf;
 
-	_bt_initialize_more_data(so, dir);
+	_bt_initialize_more_data(scan, &so->state, dir);
 
-	/*
-	 * Now load data from the first page of the scan.
-	 */
-	if (!_bt_readpage(scan, dir, start, true))
-	{
-		/*
-		 * There's no actually-matching data on this page.  Try to advance to
-		 * the next page.  Return false if there's no matching data at all.
-		 */
-		_bt_unlockbuf(scan->indexRelation, so->currPos.buf);
-		if (!_bt_steppage(scan, dir))
-			return false;
-	}
-	else
-	{
-		/* We have at least one item to return as scan's first item */
-		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
-	}
-
-	/* OK, itemIndex says what to return */
-	currItem = &so->currPos.items[so->currPos.itemIndex];
-	scan->xs_heaptid = currItem->heapTid;
-	if (scan->xs_want_itup)
-		scan->xs_itup = (IndexTuple) (so->currTuples + currItem->tupleOffset);
+	if (!_bt_load_first_page(scan, &so->state, dir, start))
+		return false;
 
-	return true;
+	/* OK, currPos->itemIndex says what to return */
+	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
@@ -2647,27 +2665,29 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
  * from currPos
  */
 static inline void
-_bt_initialize_more_data(BTScanOpaque so, ScanDirection dir)
+_bt_initialize_more_data(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 {
-	so->currPos.dir = dir;
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
+	state->currPos.dir = dir;
 	if (so->needPrimScan)
 	{
 		Assert(so->numArrayKeys);
 
-		so->currPos.moreLeft = true;
-		so->currPos.moreRight = true;
+		state->currPos.moreLeft = true;
+		state->currPos.moreRight = true;
 		so->needPrimScan = false;
 	}
 	else if (ScanDirectionIsForward(dir))
 	{
-		so->currPos.moreLeft = false;
-		so->currPos.moreRight = true;
+		state->currPos.moreLeft = false;
+		state->currPos.moreRight = true;
 	}
 	else
 	{
-		so->currPos.moreLeft = true;
-		so->currPos.moreRight = false;
+		state->currPos.moreLeft = true;
+		state->currPos.moreRight = false;
 	}
-	so->numKilled = 0;			/* just paranoia */
-	so->markItemIndex = -1;		/* ditto */
+	state->numKilled = 0;		/* just paranoia */
+	state->markItemIndex = -1;	/* ditto */
 }
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index d6de2072d40..a82b2638d82 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -4162,27 +4162,27 @@ _bt_checkkeys_look_ahead(IndexScanDesc scan, BTReadPageState *pstate,
  * away and the TID was re-used by a completely different heap tuple.
  */
 void
-_bt_killitems(IndexScanDesc scan)
+_bt_killitems(BTScanState state, Relation indexRelation)
 {
-	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPos	pos = &state->currPos;
 	Page		page;
 	BTPageOpaque opaque;
 	OffsetNumber minoff;
 	OffsetNumber maxoff;
 	int			i;
-	int			numKilled = so->numKilled;
+	int			numKilled = state->numKilled;
 	bool		killedsomething = false;
 	bool		droppedpin PG_USED_FOR_ASSERTS_ONLY;
 
-	Assert(BTScanPosIsValid(so->currPos));
+	Assert(BTScanPosIsValid(state->currPos));
 
 	/*
 	 * Always reset the scan state, so we don't look for same items on other
 	 * pages.
 	 */
-	so->numKilled = 0;
+	state->numKilled = 0;
 
-	if (BTScanPosIsPinned(so->currPos))
+	if (BTScanPosIsPinned(*pos))
 	{
 		/*
 		 * We have held the pin on this page since we read the index tuples,
@@ -4191,9 +4191,7 @@ _bt_killitems(IndexScanDesc scan)
 		 * LSN.
 		 */
 		droppedpin = false;
-		_bt_lockbuf(scan->indexRelation, so->currPos.buf, BT_READ);
-
-		page = BufferGetPage(so->currPos.buf);
+		_bt_lockbuf(indexRelation, pos->buf, BT_READ);
 	}
 	else
 	{
@@ -4201,31 +4199,31 @@ _bt_killitems(IndexScanDesc scan)
 
 		droppedpin = true;
 		/* Attempt to re-read the buffer, getting pin and lock. */
-		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ);
+		buf = _bt_getbuf(indexRelation, pos->currPage, BT_READ);
 
-		page = BufferGetPage(buf);
-		if (BufferGetLSNAtomic(buf) == so->currPos.lsn)
-			so->currPos.buf = buf;
+		if (BufferGetLSNAtomic(buf) == pos->lsn)
+			pos->buf = buf;
 		else
 		{
 			/* Modified while not pinned means hinting is not safe. */
-			_bt_relbuf(scan->indexRelation, buf);
+			_bt_relbuf(indexRelation, buf);
 			return;
 		}
 	}
 
+	page = BufferGetPage(pos->buf);
 	opaque = BTPageGetOpaque(page);
 	minoff = P_FIRSTDATAKEY(opaque);
 	maxoff = PageGetMaxOffsetNumber(page);
 
 	for (i = 0; i < numKilled; i++)
 	{
-		int			itemIndex = so->killedItems[i];
-		BTScanPosItem *kitem = &so->currPos.items[itemIndex];
+		int			itemIndex = state->killedItems[i];
+		BTScanPosItem *kitem = &pos->items[itemIndex];
 		OffsetNumber offnum = kitem->indexOffset;
 
-		Assert(itemIndex >= so->currPos.firstItem &&
-			   itemIndex <= so->currPos.lastItem);
+		Assert(itemIndex >= pos->firstItem &&
+			   itemIndex <= pos->lastItem);
 		if (offnum < minoff)
 			continue;			/* pure paranoia */
 		while (offnum <= maxoff)
@@ -4283,7 +4281,7 @@ _bt_killitems(IndexScanDesc scan)
 					 * correctly -- posting tuple still gets killed).
 					 */
 					if (pi < numKilled)
-						kitem = &so->currPos.items[so->killedItems[pi++]];
+						kitem = &state->currPos.items[state->killedItems[pi++]];
 				}
 
 				/*
@@ -4330,10 +4328,10 @@ _bt_killitems(IndexScanDesc scan)
 	if (killedsomething)
 	{
 		opaque->btpo_flags |= BTP_HAS_GARBAGE;
-		MarkBufferDirtyHint(so->currPos.buf, true);
+		MarkBufferDirtyHint(pos->buf, true);
 	}
 
-	_bt_unlockbuf(scan->indexRelation, so->currPos.buf);
+	_bt_unlockbuf(indexRelation, pos->buf);
 }
 
 
@@ -5170,3 +5168,14 @@ _bt_allequalimage(Relation rel, bool debugmessage)
 
 	return allequalimage;
 }
+
+/*
+ * _bt_allocate_tuple_workspaces() -- Allocate buffers for saving index tuples
+ * 		in index-only scans.
+ */
+void
+_bt_allocate_tuple_workspaces(BTScanState state)
+{
+	state->currTuples = (char *) palloc(BLCKSZ * 2);
+	state->markTuples = state->currTuples + BLCKSZ;
+}
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 74930433480..c60cecf722a 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -916,7 +916,8 @@ typedef BTVacuumPostingData *BTVacuumPosting;
 /*
  * BTScanOpaqueData is the btree-private state needed for an indexscan.
  * This consists of preprocessed scan keys (see _bt_preprocess_keys() for
- * details of the preprocessing), information about the current location
+ * details of the preprocessing), and tree scan state itself (BTScanStateData).
+ * In turn, BTScanStateData contains information about the current location
  * of the scan, and information about the marked location, if any.  (We use
  * BTScanPosData to represent the data needed for each of current and marked
  * locations.)	In addition we can remember some known-killed index entries
@@ -1037,21 +1038,8 @@ typedef struct BTArrayKeyInfo
 	Datum	   *elem_values;	/* array of num_elems Datums */
 } BTArrayKeyInfo;
 
-typedef struct BTScanOpaqueData
+typedef struct BTScanStateData
 {
-	/* these fields are set by _bt_preprocess_keys(): */
-	bool		qual_ok;		/* false if qual can never be satisfied */
-	int			numberOfKeys;	/* number of preprocessed scan keys */
-	ScanKey		keyData;		/* array of preprocessed scan keys */
-
-	/* workspace for SK_SEARCHARRAY support */
-	int			numArrayKeys;	/* number of equality-type array keys */
-	bool		needPrimScan;	/* New prim scan to continue in current dir? */
-	bool		scanBehind;		/* Last array advancement matched -inf attr? */
-	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
-	FmgrInfo   *orderProcs;		/* ORDER procs for required equality keys */
-	MemoryContext arrayContext; /* scan-lifespan context for array data */
-
 	/* info about killed items if any (killedItems is NULL if never used) */
 	int		   *killedItems;	/* currPos.items indexes of killed items */
 	int			numKilled;		/* number of currently stored items */
@@ -1076,6 +1064,27 @@ typedef struct BTScanOpaqueData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+} BTScanStateData;
+
+typedef BTScanStateData *BTScanState;
+
+typedef struct BTScanOpaqueData
+{
+	/* these fields are set by _bt_preprocess_keys(): */
+	bool		qual_ok;		/* false if qual can never be satisfied */
+	int			numberOfKeys;	/* number of preprocessed scan keys */
+	ScanKey		keyData;		/* array of preprocessed scan keys */
+
+	/* workspace for SK_SEARCHARRAY support */
+	int			numArrayKeys;	/* number of equality-type array keys */
+	bool		needPrimScan;	/* New prim scan to continue in current dir? */
+	bool		scanBehind;		/* Last array advancement matched -inf attr? */
+	BTArrayKeyInfo *arrayKeys;	/* info about each equality-type array key */
+	FmgrInfo   *orderProcs;		/* ORDER procs for required equality keys */
+	MemoryContext arrayContext; /* scan-lifespan context for array data */
+
+	/* the state of tree scan */
+	BTScanStateData state;
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -1291,7 +1300,7 @@ extern void _bt_start_array_keys(IndexScanDesc scan, ScanDirection dir);
 extern void _bt_preprocess_keys(IndexScanDesc scan);
 extern bool _bt_checkkeys(IndexScanDesc scan, BTReadPageState *pstate, bool arrayKeys,
 						  IndexTuple tuple, int tupnatts);
-extern void _bt_killitems(IndexScanDesc scan);
+extern void _bt_killitems(BTScanState state, Relation indexRelation);
 extern BTCycleId _bt_vacuum_cycleid(Relation rel);
 extern BTCycleId _bt_start_vacuum(Relation rel);
 extern void _bt_end_vacuum(Relation rel);
@@ -1312,6 +1321,7 @@ extern bool _bt_check_natts(Relation rel, bool heapkeyspace, Page page,
 extern void _bt_check_third_page(Relation rel, Relation heap,
 								 bool needheaptidspace, Page page, IndexTuple newtup);
 extern bool _bt_allequalimage(Relation rel, bool debugmessage);
+extern void _bt_allocate_tuple_workspaces(BTScanState state);
 
 /*
  * prototypes for functions in nbtvalidate.c
-- 
2.45.2

v19_0004-Move-scalar-distance-operators-from-btree_gist-to-co.patchtext/x-patch; charset=UTF-8; name=v19_0004-Move-scalar-distance-operators-from-btree_gist-to-co.patchDownload
From 9de46f3d7bae0659284741129486f6aad7bf8fbb Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 25 Nov 2019 01:22:21 +0300
Subject: [PATCH 4/5] Move scalar distance operators from btree_gist to core

Currently btree_gist is the only way to have knn search for scalar datatypes.
This is why distance operators for those types are defined inside btree_gist
as well.  Upcoming knn-btree needs these distance operators to be defined in
core.  This commit moves them from btree_gist to core.

Assuming that extension shared library should still work with non-upgraded
extension catalog, we btree_gist still provides wrappers over core functions.
Extension upgrade scripts switch opclasses to core operators and drops extension
operators.  Extension upgrade script has to refer @extschema@ to distinguish
between operators with same name.  Have to mark btree_gist as non-relocatable
in order to do that.

Catversion is bumped.
btree_gist extension version is bumped.

Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru
Author: Nikita Glukhov
Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov
---
 contrib/btree_gist/Makefile                 |   3 +-
 contrib/btree_gist/btree_cash.c             |  16 +-
 contrib/btree_gist/btree_date.c             |   7 +-
 contrib/btree_gist/btree_float4.c           |  11 +-
 contrib/btree_gist/btree_float8.c           |  11 +-
 contrib/btree_gist/btree_gist--1.7--1.8.sql |  90 +++++
 contrib/btree_gist/btree_gist.control       |   6 +-
 contrib/btree_gist/btree_int2.c             |  16 +-
 contrib/btree_gist/btree_int4.c             |  16 +-
 contrib/btree_gist/btree_int8.c             |  16 +-
 contrib/btree_gist/btree_interval.c         |   6 +-
 contrib/btree_gist/btree_oid.c              |  11 +-
 contrib/btree_gist/btree_time.c             |   6 +-
 contrib/btree_gist/btree_ts.c               |  38 +-
 doc/src/sgml/btree-gist.sgml                |  13 +
 src/backend/utils/adt/cash.c                |  20 +
 src/backend/utils/adt/date.c                | 112 ++++++
 src/backend/utils/adt/float.c               |  49 +++
 src/backend/utils/adt/int.c                 |  51 +++
 src/backend/utils/adt/int8.c                |  44 ++
 src/backend/utils/adt/oid.c                 |  21 +
 src/backend/utils/adt/timestamp.c           | 103 +++++
 src/include/catalog/pg_operator.dat         | 108 +++++
 src/include/catalog/pg_proc.dat             |  86 ++++
 src/include/utils/datetime.h                |   2 +
 src/include/utils/timestamp.h               |   4 +-
 src/test/regress/expected/date.out          |  64 +++
 src/test/regress/expected/float4.out        |  20 +
 src/test/regress/expected/float8.out        |  21 +
 src/test/regress/expected/int2.out          |  33 ++
 src/test/regress/expected/int4.out          |  32 ++
 src/test/regress/expected/int8.out          |  31 ++
 src/test/regress/expected/interval.out      |  17 +
 src/test/regress/expected/money.out         |   6 +
 src/test/regress/expected/oid.out           |  13 +
 src/test/regress/expected/time.out          |  16 +
 src/test/regress/expected/timestamp.out     | 421 ++++++++++++++++++++
 src/test/regress/expected/timestamptz.out   | 421 ++++++++++++++++++++
 src/test/regress/sql/date.sql               |   5 +
 src/test/regress/sql/float4.sql             |   3 +
 src/test/regress/sql/float8.sql             |   3 +
 src/test/regress/sql/int2.sql               |  10 +
 src/test/regress/sql/int4.sql               |  10 +
 src/test/regress/sql/int8.sql               |   5 +
 src/test/regress/sql/interval.sql           |   2 +
 src/test/regress/sql/money.sql              |   1 +
 src/test/regress/sql/oid.sql                |   2 +
 src/test/regress/sql/time.sql               |   3 +
 src/test/regress/sql/timestamp.sql          |   9 +
 src/test/regress/sql/timestamptz.sql        |   8 +
 50 files changed, 1882 insertions(+), 140 deletions(-)
 create mode 100644 contrib/btree_gist/btree_gist--1.7--1.8.sql

diff --git a/contrib/btree_gist/Makefile b/contrib/btree_gist/Makefile
index 073dcc745c4..d54c615e967 100644
--- a/contrib/btree_gist/Makefile
+++ b/contrib/btree_gist/Makefile
@@ -33,7 +33,8 @@ EXTENSION = btree_gist
 DATA = btree_gist--1.0--1.1.sql \
        btree_gist--1.1--1.2.sql btree_gist--1.2.sql btree_gist--1.2--1.3.sql \
        btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql \
-       btree_gist--1.5--1.6.sql btree_gist--1.6--1.7.sql
+       btree_gist--1.5--1.6.sql btree_gist--1.6--1.7.sql \
+	   btree_gist--1.7--1.8.sql
 PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes"
 
 REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \
diff --git a/contrib/btree_gist/btree_cash.c b/contrib/btree_gist/btree_cash.c
index 546b948ea40..398282732b4 100644
--- a/contrib/btree_gist/btree_cash.c
+++ b/contrib/btree_gist/btree_cash.c
@@ -6,6 +6,7 @@
 #include "btree_gist.h"
 #include "btree_utils_num.h"
 #include "common/int.h"
+#include "utils/builtins.h"
 #include "utils/cash.h"
 
 typedef struct
@@ -95,20 +96,7 @@ PG_FUNCTION_INFO_V1(cash_dist);
 Datum
 cash_dist(PG_FUNCTION_ARGS)
 {
-	Cash		a = PG_GETARG_CASH(0);
-	Cash		b = PG_GETARG_CASH(1);
-	Cash		r;
-	Cash		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("money out of range")));
-
-	ra = i64abs(r);
-
-	PG_RETURN_CASH(ra);
+	return cash_distance(fcinfo);
 }
 
 /**************************************************
diff --git a/contrib/btree_gist/btree_date.c b/contrib/btree_gist/btree_date.c
index 68a4107dbf0..0262478265b 100644
--- a/contrib/btree_gist/btree_date.c
+++ b/contrib/btree_gist/btree_date.c
@@ -118,12 +118,7 @@ PG_FUNCTION_INFO_V1(date_dist);
 Datum
 date_dist(PG_FUNCTION_ARGS)
 {
-	/* we assume the difference can't overflow */
-	Datum		diff = DirectFunctionCall2(date_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INT32(abs(DatumGetInt32(diff)));
+	return date_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c
index 84ca5eee501..7c9934feb88 100644
--- a/contrib/btree_gist/btree_float4.c
+++ b/contrib/btree_gist/btree_float4.c
@@ -6,6 +6,7 @@
 #include "btree_gist.h"
 #include "btree_utils_num.h"
 #include "utils/float.h"
+#include "utils/builtins.h"
 
 typedef struct float4key
 {
@@ -94,15 +95,7 @@ PG_FUNCTION_INFO_V1(float4_dist);
 Datum
 float4_dist(PG_FUNCTION_ARGS)
 {
-	float4		a = PG_GETARG_FLOAT4(0);
-	float4		b = PG_GETARG_FLOAT4(1);
-	float4		r;
-
-	r = a - b;
-	if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
-		float_overflow_error();
-
-	PG_RETURN_FLOAT4(fabsf(r));
+	return float4dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c
index 081a719b006..612f300059e 100644
--- a/contrib/btree_gist/btree_float8.c
+++ b/contrib/btree_gist/btree_float8.c
@@ -6,6 +6,7 @@
 #include "btree_gist.h"
 #include "btree_utils_num.h"
 #include "utils/float.h"
+#include "utils/builtins.h"
 
 typedef struct float8key
 {
@@ -102,15 +103,7 @@ PG_FUNCTION_INFO_V1(float8_dist);
 Datum
 float8_dist(PG_FUNCTION_ARGS)
 {
-	float8		a = PG_GETARG_FLOAT8(0);
-	float8		b = PG_GETARG_FLOAT8(1);
-	float8		r;
-
-	r = a - b;
-	if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
-		float_overflow_error();
-
-	PG_RETURN_FLOAT8(fabs(r));
+	return float8dist(fcinfo);
 }
 
 /**************************************************
diff --git a/contrib/btree_gist/btree_gist--1.7--1.8.sql b/contrib/btree_gist/btree_gist--1.7--1.8.sql
new file mode 100644
index 00000000000..4dbdd13f4b1
--- /dev/null
+++ b/contrib/btree_gist/btree_gist--1.7--1.8.sql
@@ -0,0 +1,90 @@
+/* contrib/btree_gist/btree_gist--1.7--1.8.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.8'" to load this file. \quit
+
+-- drop btree_gist distance operators from opfamilies
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist DROP OPERATOR 15 (int2, int2);
+ALTER OPERATOR FAMILY gist_int4_ops USING gist DROP OPERATOR 15 (int4, int4);
+ALTER OPERATOR FAMILY gist_int8_ops USING gist DROP OPERATOR 15 (int8, int8);
+ALTER OPERATOR FAMILY gist_float4_ops USING gist DROP OPERATOR 15 (float4, float4);
+ALTER OPERATOR FAMILY gist_float8_ops USING gist DROP OPERATOR 15 (float8, float8);
+ALTER OPERATOR FAMILY gist_oid_ops USING gist DROP OPERATOR 15 (oid, oid);
+ALTER OPERATOR FAMILY gist_cash_ops USING gist DROP OPERATOR 15 (money, money);
+ALTER OPERATOR FAMILY gist_date_ops USING gist DROP OPERATOR 15 (date, date);
+ALTER OPERATOR FAMILY gist_time_ops USING gist DROP OPERATOR 15 (time, time);
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist DROP OPERATOR 15 (timestamp, timestamp);
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist DROP OPERATOR 15 (timestamptz, timestamptz);
+ALTER OPERATOR FAMILY gist_interval_ops USING gist DROP OPERATOR 15 (interval, interval);
+
+-- add pg_catalog distance operators to opfamilies
+
+ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (float4, float4) FOR ORDER BY pg_catalog.float_ops;
+ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (float8, float8) FOR ORDER BY pg_catalog.float_ops;
+ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops;
+ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (money, money) FOR ORDER BY pg_catalog.money_ops;
+ALTER OPERATOR FAMILY gist_date_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (date, date) FOR ORDER BY pg_catalog.integer_ops;
+ALTER OPERATOR FAMILY gist_time_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (time, time) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops;
+ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD OPERATOR 15 pg_catalog.<-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops;
+
+-- drop distance operators
+
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (int2, int2);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (int4, int4);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (int8, int8);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (float4, float4);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (float8, float8);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (oid, oid);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (money, money);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (date, date);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (time, time);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP OPERATOR @extschema@.<-> (interval, interval);
+
+DROP OPERATOR @extschema@.<-> (int2, int2);
+DROP OPERATOR @extschema@.<-> (int4, int4);
+DROP OPERATOR @extschema@.<-> (int8, int8);
+DROP OPERATOR @extschema@.<-> (float4, float4);
+DROP OPERATOR @extschema@.<-> (float8, float8);
+DROP OPERATOR @extschema@.<-> (oid, oid);
+DROP OPERATOR @extschema@.<-> (money, money);
+DROP OPERATOR @extschema@.<-> (date, date);
+DROP OPERATOR @extschema@.<-> (time, time);
+DROP OPERATOR @extschema@.<-> (timestamp, timestamp);
+DROP OPERATOR @extschema@.<-> (timestamptz, timestamptz);
+DROP OPERATOR @extschema@.<-> (interval, interval);
+
+-- drop distance functions
+
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.int2_dist(int2, int2);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.int4_dist(int4, int4);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.int8_dist(int8, int8);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.float4_dist(float4, float4);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.float8_dist(float8, float8);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.oid_dist(oid, oid);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.cash_dist(money, money);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.date_dist(date, date);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.time_dist(time, time);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.ts_dist(timestamp, timestamp);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.tstz_dist(timestamptz, timestamptz);
+ALTER EXTENSION btree_gist DROP FUNCTION @extschema@.interval_dist(interval, interval);
+
+DROP FUNCTION @extschema@.int2_dist(int2, int2);
+DROP FUNCTION @extschema@.int4_dist(int4, int4);
+DROP FUNCTION @extschema@.int8_dist(int8, int8);
+DROP FUNCTION @extschema@.float4_dist(float4, float4);
+DROP FUNCTION @extschema@.float8_dist(float8, float8);
+DROP FUNCTION @extschema@.oid_dist(oid, oid);
+DROP FUNCTION @extschema@.cash_dist(money, money);
+DROP FUNCTION @extschema@.date_dist(date, date);
+DROP FUNCTION @extschema@.time_dist(time, time);
+DROP FUNCTION @extschema@.ts_dist(timestamp, timestamp);
+DROP FUNCTION @extschema@.tstz_dist(timestamptz, timestamptz);
+DROP FUNCTION @extschema@.interval_dist(interval, interval);
diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control
index fa9171a80a2..4c93737560b 100644
--- a/contrib/btree_gist/btree_gist.control
+++ b/contrib/btree_gist/btree_gist.control
@@ -1,6 +1,6 @@
 # btree_gist extension
 comment = 'support for indexing common datatypes in GiST'
-default_version = '1.7'
+default_version = '1.8'
 module_pathname = '$libdir/btree_gist'
-relocatable = true
-trusted = true
+relocatable = false
+trusted = true
\ No newline at end of file
diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c
index fdbf156586c..88214454b56 100644
--- a/contrib/btree_gist/btree_int2.c
+++ b/contrib/btree_gist/btree_int2.c
@@ -6,6 +6,7 @@
 #include "btree_gist.h"
 #include "btree_utils_num.h"
 #include "common/int.h"
+#include "utils/builtins.h"
 
 typedef struct int16key
 {
@@ -94,20 +95,7 @@ PG_FUNCTION_INFO_V1(int2_dist);
 Datum
 int2_dist(PG_FUNCTION_ARGS)
 {
-	int16		a = PG_GETARG_INT16(0);
-	int16		b = PG_GETARG_INT16(1);
-	int16		r;
-	int16		ra;
-
-	if (pg_sub_s16_overflow(a, b, &r) ||
-		r == PG_INT16_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("smallint out of range")));
-
-	ra = abs(r);
-
-	PG_RETURN_INT16(ra);
+	return int2dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c
index 8915fb5d087..2a4e2165f5b 100644
--- a/contrib/btree_gist/btree_int4.c
+++ b/contrib/btree_gist/btree_int4.c
@@ -6,6 +6,7 @@
 #include "btree_gist.h"
 #include "btree_utils_num.h"
 #include "common/int.h"
+#include "utils/builtins.h"
 
 typedef struct int32key
 {
@@ -95,20 +96,7 @@ PG_FUNCTION_INFO_V1(int4_dist);
 Datum
 int4_dist(PG_FUNCTION_ARGS)
 {
-	int32		a = PG_GETARG_INT32(0);
-	int32		b = PG_GETARG_INT32(1);
-	int32		r;
-	int32		ra;
-
-	if (pg_sub_s32_overflow(a, b, &r) ||
-		r == PG_INT32_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("integer out of range")));
-
-	ra = abs(r);
-
-	PG_RETURN_INT32(ra);
+	return int4dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c
index 7c63a5b6dc1..11f34d6506d 100644
--- a/contrib/btree_gist/btree_int8.c
+++ b/contrib/btree_gist/btree_int8.c
@@ -6,6 +6,7 @@
 #include "btree_gist.h"
 #include "btree_utils_num.h"
 #include "common/int.h"
+#include "utils/builtins.h"
 
 typedef struct int64key
 {
@@ -95,20 +96,7 @@ PG_FUNCTION_INFO_V1(int8_dist);
 Datum
 int8_dist(PG_FUNCTION_ARGS)
 {
-	int64		a = PG_GETARG_INT64(0);
-	int64		b = PG_GETARG_INT64(1);
-	int64		r;
-	int64		ra;
-
-	if (pg_sub_s64_overflow(a, b, &r) ||
-		r == PG_INT64_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-				 errmsg("bigint out of range")));
-
-	ra = i64abs(r);
-
-	PG_RETURN_INT64(ra);
+	return int8dist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_interval.c b/contrib/btree_gist/btree_interval.c
index b0afdf02bb5..bd9bdf960b2 100644
--- a/contrib/btree_gist/btree_interval.c
+++ b/contrib/btree_gist/btree_interval.c
@@ -128,11 +128,7 @@ PG_FUNCTION_INFO_V1(interval_dist);
 Datum
 interval_dist(PG_FUNCTION_ARGS)
 {
-	Datum		diff = DirectFunctionCall2(interval_mi,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+	return interval_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_oid.c b/contrib/btree_gist/btree_oid.c
index 3cc7d4245d4..0561d86c697 100644
--- a/contrib/btree_gist/btree_oid.c
+++ b/contrib/btree_gist/btree_oid.c
@@ -5,6 +5,7 @@
 
 #include "btree_gist.h"
 #include "btree_utils_num.h"
+#include "utils/builtins.h"
 
 typedef struct
 {
@@ -100,15 +101,7 @@ PG_FUNCTION_INFO_V1(oid_dist);
 Datum
 oid_dist(PG_FUNCTION_ARGS)
 {
-	Oid			a = PG_GETARG_OID(0);
-	Oid			b = PG_GETARG_OID(1);
-	Oid			res;
-
-	if (a < b)
-		res = b - a;
-	else
-		res = a - b;
-	PG_RETURN_OID(res);
+	return oiddist(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_time.c b/contrib/btree_gist/btree_time.c
index d89401c0f51..777082792fc 100644
--- a/contrib/btree_gist/btree_time.c
+++ b/contrib/btree_gist/btree_time.c
@@ -141,11 +141,7 @@ PG_FUNCTION_INFO_V1(time_dist);
 Datum
 time_dist(PG_FUNCTION_ARGS)
 {
-	Datum		diff = DirectFunctionCall2(time_mi_time,
-										   PG_GETARG_DATUM(0),
-										   PG_GETARG_DATUM(1));
-
-	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+	return time_distance(fcinfo);
 }
 
 
diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c
index 3f5ba91891d..db0e5304fbb 100644
--- a/contrib/btree_gist/btree_ts.c
+++ b/contrib/btree_gist/btree_ts.c
@@ -146,48 +146,14 @@ PG_FUNCTION_INFO_V1(ts_dist);
 Datum
 ts_dist(PG_FUNCTION_ARGS)
 {
-	Timestamp	a = PG_GETARG_TIMESTAMP(0);
-	Timestamp	b = PG_GETARG_TIMESTAMP(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-	else
-		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-												  PG_GETARG_DATUM(0),
-												  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
+	return timestamp_distance(fcinfo);
 }
 
 PG_FUNCTION_INFO_V1(tstz_dist);
 Datum
 tstz_dist(PG_FUNCTION_ARGS)
 {
-	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
-	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
-	Interval   *r;
-
-	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
-	{
-		Interval   *p = palloc(sizeof(Interval));
-
-		p->day = INT_MAX;
-		p->month = INT_MAX;
-		p->time = PG_INT64_MAX;
-		PG_RETURN_INTERVAL_P(p);
-	}
-
-	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
-											  PG_GETARG_DATUM(0),
-											  PG_GETARG_DATUM(1)));
-	PG_RETURN_INTERVAL_P(abs_interval(r));
+	return timestamptz_distance(fcinfo);
 }
 
 
diff --git a/doc/src/sgml/btree-gist.sgml b/doc/src/sgml/btree-gist.sgml
index 31e7c78aaef..9d5c57c3e70 100644
--- a/doc/src/sgml/btree-gist.sgml
+++ b/doc/src/sgml/btree-gist.sgml
@@ -102,6 +102,19 @@ INSERT 0 1
  </sect2>
 
  <sect2 id="btree-gist-authors">
+  <title>Upgrade notes for version 1.6</title>
+
+  <para>
+   In version 1.6 <literal>btree_gist</literal> switched to using in-core
+   distance operators, and its own implementations were removed.  References to
+   these operators in <literal>btree_gist</literal> opclasses will be updated
+   automatically during the extension upgrade, but if the user has created
+   objects referencing these operators or functions, then these objects must be
+   dropped manually before updating the extension.
+  </para>
+ </sect2>
+
+ <sect2>
   <title>Authors</title>
 
   <para>
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index ec3c08acfc2..94bf236880f 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -30,6 +30,7 @@
 #include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
+#define SAMESIGN(a,b)	(((a) < 0) == ((b) < 0))
 
 /*************************************************************************
  * Private routines
@@ -1191,3 +1192,22 @@ int8_cash(PG_FUNCTION_ARGS)
 
 	PG_RETURN_CASH(result);
 }
+
+Datum
+cash_distance(PG_FUNCTION_ARGS)
+{
+	Cash		a = PG_GETARG_CASH(0);
+	Cash		b = PG_GETARG_CASH(1);
+	Cash		r;
+	Cash		ra;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("money out of range")));
+
+	ra = i64abs(r);
+
+	PG_RETURN_CASH(ra);
+}
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 9c854e0e5c3..73e1a4f5b6a 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -546,6 +546,16 @@ date_mii(PG_FUNCTION_ARGS)
 	PG_RETURN_DATEADT(result);
 }
 
+Datum
+date_distance(PG_FUNCTION_ARGS)
+{
+	/* we assume the difference can't overflow */
+	Datum		diff = DirectFunctionCall2(date_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INT32(abs(DatumGetInt32(diff)));
+}
 
 /*
  * Promote date to timestamp.
@@ -840,6 +850,29 @@ date_cmp_timestamptz_internal(DateADT dateVal, TimestampTz dt2)
 	return timestamptz_cmp_internal(dt1, dt2);
 }
 
+Datum
+date_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	Timestamp	dt2 = PG_GETARG_TIMESTAMP(1);
+	Timestamp	dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
 Datum
 date_eq_timestamptz(PG_FUNCTION_ARGS)
 {
@@ -903,6 +936,30 @@ date_cmp_timestamptz(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(date_cmp_timestamptz_internal(dateVal, dt2));
 }
 
+Datum
+date_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	DateADT		dateVal = PG_GETARG_DATEADT(0);
+	TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz dt1;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt2))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt1 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
+
+
 Datum
 timestamp_eq_date(PG_FUNCTION_ARGS)
 {
@@ -966,6 +1023,29 @@ timestamp_cmp_date(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(-date_cmp_timestamp_internal(dateVal, dt1));
 }
 
+Datum
+timestamp_dist_date(PG_FUNCTION_ARGS)
+{
+	Timestamp	dt1 = PG_GETARG_TIMESTAMP(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	Timestamp	dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamp(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(dt1, dt2));
+}
+
 Datum
 timestamptz_eq_date(PG_FUNCTION_ARGS)
 {
@@ -1058,6 +1138,28 @@ in_range_date_interval(PG_FUNCTION_ARGS)
 							   BoolGetDatum(less));
 }
 
+Datum
+timestamptz_dist_date(PG_FUNCTION_ARGS)
+{
+	TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
+	DateADT		dateVal = PG_GETARG_DATEADT(1);
+	TimestampTz dt2;
+
+	if (DATE_NOT_FINITE(dateVal) || TIMESTAMP_NOT_FINITE(dt1))
+	{
+		Interval   *r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		PG_RETURN_INTERVAL_P(r);
+	}
+
+	dt2 = date2timestamptz(dateVal);
+
+	PG_RETURN_INTERVAL_P(timestamptz_dist_internal(dt1, dt2));
+}
 
 /* extract_date()
  * Extract specified field from date type.
@@ -2251,6 +2353,16 @@ extract_time(PG_FUNCTION_ARGS)
 	return time_part_common(fcinfo, true);
 }
 
+Datum
+time_distance(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(time_mi_time,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
+
 
 /*****************************************************************************
  *	 Time With Time Zone ADT
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index f709c21e1fe..b66359b6f21 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -4088,3 +4088,52 @@ width_bucket_float8(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT32(result);
 }
+
+Datum
+float4dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float4		r;
+
+	r = a - b;
+	if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
+		float_overflow_error();
+
+	PG_RETURN_FLOAT4(fabsf(r));
+}
+
+Datum
+float8dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r;
+
+	r = a - b;
+	if (unlikely(isinf(r)) && !isinf(a) && !isinf(b))
+		float_overflow_error();
+
+	PG_RETURN_FLOAT8(fabs(r));
+}
+
+
+Datum
+float48dist(PG_FUNCTION_ARGS)
+{
+	float4		a = PG_GETARG_FLOAT4(0);
+	float8		b = PG_GETARG_FLOAT8(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(fabs(r));
+}
+
+Datum
+float84dist(PG_FUNCTION_ARGS)
+{
+	float8		a = PG_GETARG_FLOAT8(0);
+	float4		b = PG_GETARG_FLOAT4(1);
+	float8		r = float8_mi(a, b);
+
+	PG_RETURN_FLOAT8(fabs(r));
+}
\ No newline at end of file
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 234f20796b7..87664cf2e6c 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -1647,3 +1647,54 @@ generate_series_int4_support(PG_FUNCTION_ARGS)
 
 	PG_RETURN_POINTER(ret);
 }
+
+Datum
+int2dist(PG_FUNCTION_ARGS)
+{
+	int16		a = PG_GETARG_INT16(0);
+	int16		b = PG_GETARG_INT16(1);
+	int16		r;
+	int16		ra;
+
+	if (pg_sub_s16_overflow(a, b, &r) ||
+		r == PG_INT16_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("smallint out of range")));
+
+	ra = abs(r);
+
+	PG_RETURN_INT16(ra);
+}
+
+static int32
+int44_dist(int32 a, int32 b)
+{
+	int32		r;
+
+	if (pg_sub_s32_overflow(a, b, &r) ||
+		r == PG_INT32_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("integer out of range")));
+
+	return abs(r);
+}
+
+Datum
+int4dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int24dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT16(0), PG_GETARG_INT32(1)));
+}
+
+Datum
+int42dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT32(int44_dist(PG_GETARG_INT32(0), PG_GETARG_INT16(1)));
+}
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 54fa3bc3799..e81c68e47e1 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1521,3 +1521,47 @@ generate_series_int8_support(PG_FUNCTION_ARGS)
 
 	PG_RETURN_POINTER(ret);
 }
+
+static int64
+int88_dist(int64 a, int64 b)
+{
+	int64		r;
+
+	if (pg_sub_s64_overflow(a, b, &r) ||
+		r == PG_INT64_MIN)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("bigint out of range")));
+
+	return i64abs(r);
+}
+
+Datum
+int8dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int82dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT16(1)));
+}
+
+Datum
+int84dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist(PG_GETARG_INT64(0), (int64) PG_GETARG_INT32(1)));
+}
+
+Datum
+int28dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT16(0), PG_GETARG_INT64(1)));
+}
+
+Datum
+int48dist(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(int88_dist((int64) PG_GETARG_INT32(0), PG_GETARG_INT64(1)));
+}
diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c
index 56fb1fd77ce..65521a6ce9e 100644
--- a/src/backend/utils/adt/oid.c
+++ b/src/backend/utils/adt/oid.c
@@ -387,3 +387,24 @@ oidvectorgt(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(cmp > 0);
 }
+
+Datum
+oiddist(PG_FUNCTION_ARGS)
+{
+	Oid			a = PG_GETARG_OID(0);
+	Oid			b = PG_GETARG_OID(1);
+	Oid			res;
+	bool		overflow;
+
+	if (a < b)
+		overflow = pg_sub_u32_overflow(b, a, &res);
+	else
+		overflow = pg_sub_u32_overflow(a, b, &res);
+
+	if (overflow)
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("oid out of range")));
+
+	PG_RETURN_OID(res);
+}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 69fe7860ede..280092bbf42 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -2865,6 +2865,86 @@ timestamp_mi(PG_FUNCTION_ARGS)
 	PG_RETURN_INTERVAL_P(result);
 }
 
+Datum
+timestamp_distance(PG_FUNCTION_ARGS)
+{
+	Timestamp	a = PG_GETARG_TIMESTAMP(0);
+	Timestamp	b = PG_GETARG_TIMESTAMP(1);
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		Interval   *p = palloc(sizeof(Interval));
+
+		p->day = INT_MAX;
+		p->month = INT_MAX;
+		p->time = PG_INT64_MAX;
+		PG_RETURN_INTERVAL_P(p);
+	}
+	else
+		r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+												  PG_GETARG_DATUM(0),
+												  PG_GETARG_DATUM(1)));
+	PG_RETURN_INTERVAL_P(abs_interval(r));
+}
+
+Interval *
+timestamp_dist_internal(Timestamp a, Timestamp b)
+{
+	Interval   *r;
+
+	if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b))
+	{
+		r = palloc(sizeof(Interval));
+
+		r->day = INT_MAX;
+		r->month = INT_MAX;
+		r->time = PG_INT64_MAX;
+
+		return r;
+	}
+
+	r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi,
+											  TimestampGetDatum(a),
+											  TimestampGetDatum(b)));
+
+	return abs_interval(r);
+}
+
+Datum
+timestamptz_distance(PG_FUNCTION_ARGS)
+{
+	TimestampTz a = PG_GETARG_TIMESTAMPTZ(0);
+	TimestampTz b = PG_GETARG_TIMESTAMPTZ(1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(a, b));
+}
+
+Datum
+timestamp_dist_timestamptz(PG_FUNCTION_ARGS)
+{
+	Timestamp	ts1 = PG_GETARG_TIMESTAMP(0);
+	TimestampTz tstz2 = PG_GETARG_TIMESTAMPTZ(1);
+	TimestampTz tstz1;
+
+	tstz1 = timestamp2timestamptz(ts1);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+Datum
+timestamptz_dist_timestamp(PG_FUNCTION_ARGS)
+{
+	TimestampTz tstz1 = PG_GETARG_TIMESTAMPTZ(0);
+	Timestamp	ts2 = PG_GETARG_TIMESTAMP(1);
+	TimestampTz tstz2;
+
+	tstz2 = timestamp2timestamptz(ts2);
+
+	PG_RETURN_INTERVAL_P(timestamp_dist_internal(tstz1, tstz2));
+}
+
+
 /*
  *	interval_justify_interval()
  *
@@ -4237,6 +4317,29 @@ interval_sum(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INTERVAL_P(result);
 }
+Interval *
+abs_interval(Interval *a)
+{
+	static Interval zero = {0, 0, 0};
+
+	if (DatumGetBool(DirectFunctionCall2(interval_lt,
+										 IntervalPGetDatum(a),
+										 IntervalPGetDatum(&zero))))
+		a = DatumGetIntervalP(DirectFunctionCall1(interval_um,
+												  IntervalPGetDatum(a)));
+
+	return a;
+}
+
+Datum
+interval_distance(PG_FUNCTION_ARGS)
+{
+	Datum		diff = DirectFunctionCall2(interval_mi,
+										   PG_GETARG_DATUM(0),
+										   PG_GETARG_DATUM(1));
+
+	PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff)));
+}
 
 /* timestamp_age()
  * Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0e7511dde1c..0d45443cb43 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2845,6 +2845,114 @@
   oprname => '-', oprleft => 'pg_lsn', oprright => 'numeric',
   oprresult => 'pg_lsn', oprcode => 'pg_lsn_mii' },
 
+# distance operators
+{ oid => '9447', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int2', oprresult => 'int2',
+  oprcode => 'int2dist'},
+{ oid => '9448', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int4', oprresult => 'int4',
+  oprcode => 'int4dist'},
+{ oid => '9449', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int8', oprresult => 'int8',
+  oprcode => 'int8dist'},
+{ oid => '9450', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'oid',
+  oprright => 'oid', oprresult => 'oid',
+  oprcode => 'oiddist'},
+{ oid => '9451', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float4', oprresult => 'float4',
+  oprcode => 'float4dist'},
+{ oid => '9452', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float8', oprresult => 'float8',
+  oprcode => 'float8dist'},
+{ oid => '9453', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'money',
+  oprright => 'money', oprresult => 'money',
+  oprcode => 'cash_distance'},
+{ oid => '9454', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'date', oprresult => 'int4',
+  oprcode => 'date_distance'},
+{ oid => '9455', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'time',
+  oprright => 'time', oprresult => 'interval',
+  oprcode => 'time_distance'},
+{ oid => '9456', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamp', oprresult => 'interval',
+  oprcode => 'timestamp_distance'},
+{ oid => '9457', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamptz', oprresult => 'interval',
+  oprcode => 'timestamptz_distance'},
+{ oid => '9458', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'interval',
+  oprright => 'interval', oprresult => 'interval',
+  oprcode => 'interval_distance'},
+
+# cross-type distance operators
+{ oid => '9432', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int4', oprresult => 'int4', oprcom => '<->(int4,int2)',
+  oprcode => 'int24dist'},
+{ oid => '9433', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int2', oprresult => 'int4', oprcom => '<->(int2,int4)',
+  oprcode => 'int42dist'},
+{ oid => '9434', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int2',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int2)',
+  oprcode => 'int28dist'},
+{ oid => '9435', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int2', oprresult => 'int8', oprcom => '<->(int2,int8)',
+  oprcode => 'int82dist'},
+{ oid => '9436', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int4',
+  oprright => 'int8', oprresult => 'int8', oprcom => '<->(int8,int4)',
+  oprcode => 'int48dist'},
+{ oid => '9437', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'int8',
+  oprright => 'int4', oprresult => 'int8', oprcom => '<->(int4,int8)',
+  oprcode => 'int84dist'},
+{ oid => '9438', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float4',
+  oprright => 'float8', oprresult => 'float8', oprcom => '<->(float8,float4)',
+  oprcode => 'float48dist'},
+{ oid => '9439', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'float8',
+  oprright => 'float4', oprresult => 'float8', oprcom => '<->(float4,float8)',
+  oprcode => 'float84dist'},
+{ oid => '9440', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,date)',
+  oprcode => 'date_dist_timestamp'},
+{ oid => '9441', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamp)',
+  oprcode => 'timestamp_dist_date'},
+{ oid => '9442', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'date',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,date)',
+  oprcode => 'date_dist_timestamptz'},
+{ oid => '9443', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'date', oprresult => 'interval', oprcom => '<->(date,timestamptz)',
+  oprcode => 'timestamptz_dist_date'},
+{ oid => '9444', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamp',
+  oprright => 'timestamptz', oprresult => 'interval', oprcom => '<->(timestamptz,timestamp)',
+  oprcode => 'timestamp_dist_timestamptz'},
+{ oid => '9445', descr => 'distance between',
+  oprname => '<->', oprcanmerge => 'f', oprcanhash => 'f', oprleft => 'timestamptz',
+  oprright => 'timestamp', oprresult => 'interval', oprcom => '<->(timestamp,timestamptz)',
+  oprcode => 'timestamptz_dist_timestamp'},
+
 # enum operators
 { oid => '3516', descr => 'equal',
   oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'anyenum',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 06b2f4ba66c..c722d196a54 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12235,4 +12235,90 @@
   proargnames => '{summarized_tli,summarized_lsn,pending_lsn,summarizer_pid}',
   prosrc => 'pg_get_wal_summarizer_state' },
 
+# distance functions
+{ oid => '9406',
+  proname => 'int2dist', prorettype => 'int2',
+  proargtypes => 'int2 int2', prosrc => 'int2dist' },
+{ oid => '9407',
+  proname => 'int4dist', prorettype => 'int4',
+  proargtypes => 'int4 int4', prosrc => 'int4dist' },
+{ oid => '9408',
+  proname => 'int8dist', prorettype => 'int8',
+  proargtypes => 'int8 int8', prosrc => 'int8dist' },
+{ oid => '9409',
+  proname => 'oiddist', prorettype => 'oid',
+  proargtypes => 'oid oid', prosrc => 'oiddist' },
+{ oid => '9410',
+  proname => 'float4dist', prorettype => 'float4',
+  proargtypes => 'float4 float4', prosrc => 'float4dist' },
+{ oid => '9411',
+  proname => 'float8dist', prorettype => 'float8',
+  proargtypes => 'float8 float8', prosrc => 'float8dist' },
+{ oid => '9412',
+  proname => 'cash_distance', prorettype => 'money',
+  proargtypes => 'money money', prosrc => 'cash_distance' },
+{ oid => '9413',
+  proname => 'date_distance', prorettype => 'int4',
+  proargtypes => 'date date', prosrc => 'date_distance' },
+{ oid => '9414',
+  proname => 'time_distance', prorettype => 'interval',
+  proargtypes => 'time time', prosrc => 'time_distance' },
+{ oid => '9415',
+  proname => 'timestamp_distance', prorettype => 'interval',
+  proargtypes => 'timestamp timestamp', prosrc => 'timestamp_distance' },
+{ oid => '9416',
+  proname => 'timestamptz_distance', prorettype => 'interval',
+  proargtypes => 'timestamptz timestamptz', prosrc => 'timestamptz_distance' },
+{ oid => '9417',
+  proname => 'interval_distance', prorettype => 'interval',
+  proargtypes => 'interval interval', prosrc => 'interval_distance' },
+
+# cross-type distance functions
+{ oid => '9418',
+  proname => 'int24dist', prorettype => 'int4',
+  proargtypes => 'int2 int4', prosrc => 'int24dist' },
+{ oid => '9419',
+  proname => 'int28dist', prorettype => 'int8',
+  proargtypes => 'int2 int8', prosrc => 'int28dist' },
+{ oid => '9420',
+  proname => 'int42dist', prorettype => 'int4',
+  proargtypes => 'int4 int2', prosrc => 'int42dist' },
+{ oid => '9221',
+  proname => 'int48dist', prorettype => 'int8',
+  proargtypes => 'int4 int8', prosrc => 'int48dist' },
+{ oid => '9422',
+  proname => 'int82dist', prorettype => 'int8',
+  proargtypes => 'int8 int2', prosrc => 'int82dist' },
+{ oid => '9423',
+  proname => 'int84dist', prorettype => 'int8',
+  proargtypes => 'int8 int4', prosrc => 'int84dist' },
+{ oid => '9424',
+  proname => 'float48dist', prorettype => 'float8',
+  proargtypes => 'float4 float8', prosrc => 'float48dist' },
+{ oid => '9425',
+  proname => 'float84dist', prorettype => 'float8',
+  proargtypes => 'float8 float4', prosrc => 'float84dist' },
+{ oid => '9426',
+  proname => 'date_dist_timestamp', prorettype => 'interval',
+  proargtypes => 'date timestamp', prosrc => 'date_dist_timestamp' },
+{ oid => '9427',
+  proname => 'date_dist_timestamptz', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'date timestamptz',
+  prosrc => 'date_dist_timestamptz' },
+{ oid => '9428',
+  proname => 'timestamp_dist_date', prorettype => 'interval',
+  proargtypes => 'timestamp date', prosrc => 'timestamp_dist_date' },
+{ oid => '9429',
+  proname => 'timestamp_dist_timestamptz', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamp timestamptz',
+  prosrc => 'timestamp_dist_timestamptz' },
+{ oid => '9430',
+  proname => 'timestamptz_dist_date', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamptz date',
+  prosrc => 'timestamptz_dist_date' },
+{ oid => '9431',
+  proname => 'timestamptz_dist_timestamp', provolatile => 's',
+  prorettype => 'interval', proargtypes => 'timestamptz timestamp',
+  prosrc => 'timestamptz_dist_timestamp' },
+
 ]
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index e4ac2b8e7f6..b334b61e6ff 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -364,4 +364,6 @@ extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
 extern bool AdjustTimestampForTypmod(Timestamp *time, int32 typmod,
 									 struct Node *escontext);
 
+extern Interval *abs_interval(Interval *a);
+
 #endif							/* DATETIME_H */
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index a6ce03ed460..27015a36bbd 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -126,9 +126,11 @@ extern Timestamp SetEpochTimestamp(void);
 extern void GetEpochTime(struct pg_tm *tm);
 
 extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern Interval *timestamp_dist_internal(Timestamp a, Timestamp b);
 
-/* timestamp comparison works for timestamptz also */
+/* timestamp comparison and distance works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
+#define timestamptz_dist_internal(dt1,dt2)	timestamp_dist_internal(dt1, dt2)
 
 extern TimestampTz timestamp2timestamptz_opt_overflow(Timestamp timestamp,
 													  int *overflow);
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
index f5949f3d174..611c669137a 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -1532,3 +1532,67 @@ select make_time(10, 55, 100.1);
 ERROR:  time field value out of range: 10:55:100.1
 select make_time(24, 0, 2.1);
 ERROR:  time field value out of range: 24:00:2.1
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+ Fifteen | Distance 
+---------+----------
+         |    16006
+         |    15941
+         |     1802
+         |     1801
+         |     1800
+         |     1799
+         |     1436
+         |     1435
+         |     1434
+         |      308
+         |      307
+         |      306
+         |    13578
+         |    13944
+         |    14311
+         |  1475514
+(16 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+ Fifteen |               Distance                
+---------+---------------------------------------
+         | @ 16006 days 1 hour 23 mins 45 secs
+         | @ 15941 days 1 hour 23 mins 45 secs
+         | @ 1802 days 1 hour 23 mins 45 secs
+         | @ 1801 days 1 hour 23 mins 45 secs
+         | @ 1800 days 1 hour 23 mins 45 secs
+         | @ 1799 days 1 hour 23 mins 45 secs
+         | @ 1436 days 1 hour 23 mins 45 secs
+         | @ 1435 days 1 hour 23 mins 45 secs
+         | @ 1434 days 1 hour 23 mins 45 secs
+         | @ 308 days 1 hour 23 mins 45 secs
+         | @ 307 days 1 hour 23 mins 45 secs
+         | @ 306 days 1 hour 23 mins 45 secs
+         | @ 13577 days 22 hours 36 mins 15 secs
+         | @ 13943 days 22 hours 36 mins 15 secs
+         | @ 14310 days 22 hours 36 mins 15 secs
+         | @ 1475514 days 1 hour 23 mins 45 secs
+(16 rows)
+
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
+ Fifteen |                Distance                 
+---------+-----------------------------------------
+         | @ 16005 days 14 hours 23 mins 45 secs
+         | @ 15940 days 14 hours 23 mins 45 secs
+         | @ 1801 days 14 hours 23 mins 45 secs
+         | @ 1800 days 14 hours 23 mins 45 secs
+         | @ 1799 days 14 hours 23 mins 45 secs
+         | @ 1798 days 14 hours 23 mins 45 secs
+         | @ 1435 days 14 hours 23 mins 45 secs
+         | @ 1434 days 14 hours 23 mins 45 secs
+         | @ 1433 days 14 hours 23 mins 45 secs
+         | @ 307 days 14 hours 23 mins 45 secs
+         | @ 306 days 14 hours 23 mins 45 secs
+         | @ 305 days 15 hours 23 mins 45 secs
+         | @ 13578 days 8 hours 36 mins 15 secs
+         | @ 13944 days 8 hours 36 mins 15 secs
+         | @ 14311 days 8 hours 36 mins 15 secs
+         | @ 1475513 days 14 hours 23 mins 45 secs
+(16 rows)
+
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index 65ee82caaee..a04ea173cae 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -318,6 +318,26 @@ SELECT * FROM FLOAT4_TBL;
  -1.2345679e-20
 (5 rows)
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+ five |       f1       |     dist      
+------+----------------+---------------
+      |              0 |        1004.3
+      |         -34.84 |       1039.14
+      |        -1004.3 |        2008.6
+      | -1.2345679e+20 | 1.2345679e+20
+      | -1.2345679e-20 |        1004.3
+(5 rows)
+
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+ five |       f1       |          dist          
+------+----------------+------------------------
+      |              0 |                 1004.3
+      |         -34.84 |     1039.1400001525878
+      |        -1004.3 |     2008.5999877929687
+      | -1.2345679e+20 | 1.2345678955701443e+20
+      | -1.2345679e-20 |                 1004.3
+(5 rows)
+
 -- test edge-case coercions to integer
 SELECT '32767.4'::float4::int2;
  int2  
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
index 344d6b7d6d7..7fcf3b7d6e6 100644
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -617,6 +617,27 @@ SELECT f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
  1.2345678901234e-200 |  2.3112042409018e-67
 (5 rows)
 
+-- distance
+SELECT f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+          f1          |         dist         
+----------------------+----------------------
+                    0 |               1004.3
+               1004.3 |                    0
+               -34.84 |              1039.14
+ 1.2345678901234e+200 | 1.2345678901234e+200
+ 1.2345678901234e-200 |               1004.3
+(5 rows)
+
+SELECT f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
+          f1          |         dist         
+----------------------+----------------------
+                    0 |     1004.29998779297
+               1004.3 | 1.22070312045253e-05
+               -34.84 |     1039.13998779297
+ 1.2345678901234e+200 | 1.2345678901234e+200
+ 1.2345678901234e-200 |     1004.29998779297
+(5 rows)
+
 SELECT * FROM FLOAT8_TBL;
           f1          
 ----------------------
diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out
index 4e03a5faee0..0631e028c64 100644
--- a/src/test/regress/expected/int2.out
+++ b/src/test/regress/expected/int2.out
@@ -284,6 +284,39 @@ SELECT i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
  -32767 | -16383
 (5 rows)
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+ERROR:  smallint out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+ four |  f1   |   x   
+------+-------+-------
+      |     0 |     2
+      |  1234 |  1232
+      | -1234 |  1236
+      | 32767 | 32765
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+ five |   f1   |   x   
+------+--------+-------
+      |      0 |     2
+      |   1234 |  1232
+      |  -1234 |  1236
+      |  32767 | 32765
+      | -32767 | 32769
+(5 rows)
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
   text  
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
index b1a15888ef8..261bc5b263c 100644
--- a/src/test/regress/expected/int4.out
+++ b/src/test/regress/expected/int4.out
@@ -266,6 +266,38 @@ SELECT i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
  -2147483647 | -1073741823
 (5 rows)
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+ERROR:  integer out of range
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+ four |     f1     |     x      
+------+------------+------------
+      |          0 |          2
+      |     123456 |     123454
+      |    -123456 |     123458
+      | 2147483647 | 2147483645
+(4 rows)
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+ five |     f1      |     x      
+------+-------------+------------
+      |           0 |          2
+      |      123456 |     123454
+      |     -123456 |     123458
+      |  2147483647 | 2147483645
+      | -2147483647 | 2147483649
+(5 rows)
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index fddc09f6305..412ba3e4f4c 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -452,6 +452,37 @@ SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 A
  4567890123457035 | -4567890123456543 | 1123700970370370094 |     0
 (5 rows)
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+ five |        q2         |       dist       
+------+-------------------+------------------
+      |               456 |              333
+      |  4567890123456789 | 4567890123456666
+      |               123 |                0
+      |  4567890123456789 | 4567890123456666
+      | -4567890123456789 | 4567890123456912
+(5 rows)
+
 SELECT q2, abs(q2) FROM INT8_TBL;
         q2         |       abs        
 -------------------+------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 51ae010c7ba..a055a5d42a0 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -325,6 +325,23 @@ SELECT -('-9223372036854775807 us'::interval); -- ok
 
 SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
 ERROR:  interval out of range
+SELECT f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+          ?column?          
+----------------------------
+ 2 days 02:59:00
+ 2 days -02:00:00
+ 8 days -03:00:00
+ 34 years -2 days -03:00:00
+ 3 mons -2 days -03:00:00
+ 2 days 03:00:14
+ 1 day 00:56:56
+ 6 years -2 days -03:00:00
+ 5 mons -2 days -03:00:00
+ 5 mons -2 days +09:00:00
+ infinity
+ infinity
+(12 rows)
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index cc2ff4d96e8..b2cbc86aa0d 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -125,6 +125,12 @@ SELECT m / 2::float4 FROM money_data;
    $61.50
 (1 row)
 
+SELECT m <-> '$123.45' FROM money_data;
+ ?column? 
+----------
+    $0.45
+(1 row)
+
 -- All true
 SELECT m = '$123.00' FROM money_data;
  ?column? 
diff --git a/src/test/regress/expected/oid.out b/src/test/regress/expected/oid.out
index b80cb47e0c1..1d705042c2a 100644
--- a/src/test/regress/expected/oid.out
+++ b/src/test/regress/expected/oid.out
@@ -181,4 +181,17 @@ SELECT o.* FROM OID_TBL o WHERE o.f1 > '1234';
    99999999
 (3 rows)
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+ eight |     f1     |  ?column?  
+-------+------------+------------
+       |       1234 |       1111
+       |       1235 |       1112
+       |        987 |        864
+       | 4294966256 | 4294966133
+       |   99999999 |   99999876
+       |          5 |        118
+       |         10 |        113
+       |         15 |        108
+(8 rows)
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out
index 4247fae9412..38280d4449a 100644
--- a/src/test/regress/expected/time.out
+++ b/src/test/regress/expected/time.out
@@ -229,3 +229,19 @@ SELECT date_part('epoch',       TIME '2020-05-26 13:30:25.575401');
  48625.575401
 (1 row)
 
+-- distance
+SELECT f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
+           Distance            
+-------------------------------
+ @ 1 hour 23 mins 45 secs
+ @ 23 mins 45 secs
+ @ 39 mins 15 secs
+ @ 10 hours 35 mins 15 secs
+ @ 10 hours 36 mins 15 secs
+ @ 10 hours 37 mins 15 secs
+ @ 22 hours 35 mins 15 secs
+ @ 22 hours 36 mins 14.99 secs
+ @ 14 hours 12 mins 54 secs
+ @ 14 hours 12 mins 54 secs
+(10 rows)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index cf337da517e..2c7cab01d52 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2201,3 +2201,424 @@ select age(timestamp '-infinity', timestamp 'infinity');
 
 select age(timestamp '-infinity', timestamp '-infinity');
 ERROR:  interval out of range
+SELECT make_timestamp(2014,12,28,6,30,45.887);
+        make_timestamp        
+------------------------------
+ Sun Dec 28 06:30:45.887 2014
+(1 row)
+
+-- distance operators
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+               Distance                
+---------------------------------------
+ infinity
+ infinity
+ @ 11356 days
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 58 secs
+ @ 1453 days 6 hours 27 mins 58.6 secs
+ @ 1453 days 6 hours 27 mins 58.5 secs
+ @ 1453 days 6 hours 27 mins 58.4 secs
+ @ 1493 days
+ @ 1492 days 20 hours 55 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1333 days 6 hours 27 mins 59 secs
+ @ 231 days 18 hours 19 mins 20 secs
+ @ 324 days 15 hours 45 mins 59 secs
+ @ 324 days 10 hours 45 mins 58 secs
+ @ 324 days 11 hours 45 mins 57 secs
+ @ 324 days 20 hours 45 mins 56 secs
+ @ 324 days 21 hours 45 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 28 mins
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1333 days 5 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1452 days 6 hours 27 mins 59 secs
+ @ 1451 days 6 hours 27 mins 59 secs
+ @ 1450 days 6 hours 27 mins 59 secs
+ @ 1449 days 6 hours 27 mins 59 secs
+ @ 1448 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 765901 days 6 hours 27 mins 59 secs
+ @ 695407 days 6 hours 27 mins 59 secs
+ @ 512786 days 6 hours 27 mins 59 secs
+ @ 330165 days 6 hours 27 mins 59 secs
+ @ 111019 days 6 hours 27 mins 59 secs
+ @ 74495 days 6 hours 27 mins 59 secs
+ @ 37971 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 35077 days 17 hours 32 mins 1 sec
+ @ 1801 days 6 hours 27 mins 59 secs
+ @ 1800 days 6 hours 27 mins 59 secs
+ @ 1799 days 6 hours 27 mins 59 secs
+ @ 1495 days 6 hours 27 mins 59 secs
+ @ 1494 days 6 hours 27 mins 59 secs
+ @ 1493 days 6 hours 27 mins 59 secs
+ @ 1435 days 6 hours 27 mins 59 secs
+ @ 1434 days 6 hours 27 mins 59 secs
+ @ 1130 days 6 hours 27 mins 59 secs
+ @ 1129 days 6 hours 27 mins 59 secs
+ @ 399 days 6 hours 27 mins 59 secs
+ @ 398 days 6 hours 27 mins 59 secs
+ @ 33 days 6 hours 27 mins 59 secs
+ @ 32 days 6 hours 27 mins 59 secs
+(65 rows)
+
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+               Distance                
+---------------------------------------
+ @ 11356 days
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 58 secs
+ @ 1453 days 6 hours 27 mins 58.6 secs
+ @ 1453 days 6 hours 27 mins 58.5 secs
+ @ 1453 days 6 hours 27 mins 58.4 secs
+ @ 1493 days
+ @ 1492 days 20 hours 55 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1333 days 6 hours 27 mins 59 secs
+ @ 231 days 18 hours 19 mins 20 secs
+ @ 324 days 15 hours 45 mins 59 secs
+ @ 324 days 10 hours 45 mins 58 secs
+ @ 324 days 11 hours 45 mins 57 secs
+ @ 324 days 20 hours 45 mins 56 secs
+ @ 324 days 21 hours 45 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 28 mins
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1333 days 5 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1452 days 6 hours 27 mins 59 secs
+ @ 1451 days 6 hours 27 mins 59 secs
+ @ 1450 days 6 hours 27 mins 59 secs
+ @ 1449 days 6 hours 27 mins 59 secs
+ @ 1448 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 765901 days 6 hours 27 mins 59 secs
+ @ 695407 days 6 hours 27 mins 59 secs
+ @ 512786 days 6 hours 27 mins 59 secs
+ @ 330165 days 6 hours 27 mins 59 secs
+ @ 111019 days 6 hours 27 mins 59 secs
+ @ 74495 days 6 hours 27 mins 59 secs
+ @ 37971 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 35077 days 17 hours 32 mins 1 sec
+ @ 1801 days 6 hours 27 mins 59 secs
+ @ 1800 days 6 hours 27 mins 59 secs
+ @ 1799 days 6 hours 27 mins 59 secs
+ @ 1495 days 6 hours 27 mins 59 secs
+ @ 1494 days 6 hours 27 mins 59 secs
+ @ 1493 days 6 hours 27 mins 59 secs
+ @ 1435 days 6 hours 27 mins 59 secs
+ @ 1434 days 6 hours 27 mins 59 secs
+ @ 1130 days 6 hours 27 mins 59 secs
+ @ 1129 days 6 hours 27 mins 59 secs
+ @ 399 days 6 hours 27 mins 59 secs
+ @ 398 days 6 hours 27 mins 59 secs
+ @ 33 days 6 hours 27 mins 59 secs
+ @ 32 days 6 hours 27 mins 59 secs
+(63 rows)
+
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+               Distance                
+---------------------------------------
+ infinity
+ infinity
+ @ 11356 days 1 hour 23 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 43 secs
+ @ 1453 days 7 hours 51 mins 43.6 secs
+ @ 1453 days 7 hours 51 mins 43.5 secs
+ @ 1453 days 7 hours 51 mins 43.4 secs
+ @ 1493 days 1 hour 23 mins 45 secs
+ @ 1492 days 22 hours 19 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1333 days 7 hours 51 mins 44 secs
+ @ 231 days 16 hours 55 mins 35 secs
+ @ 324 days 17 hours 9 mins 44 secs
+ @ 324 days 12 hours 9 mins 43 secs
+ @ 324 days 13 hours 9 mins 42 secs
+ @ 324 days 22 hours 9 mins 41 secs
+ @ 324 days 23 hours 9 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1333 days 6 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1452 days 7 hours 51 mins 44 secs
+ @ 1451 days 7 hours 51 mins 44 secs
+ @ 1450 days 7 hours 51 mins 44 secs
+ @ 1449 days 7 hours 51 mins 44 secs
+ @ 1448 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 765901 days 7 hours 51 mins 44 secs
+ @ 695407 days 7 hours 51 mins 44 secs
+ @ 512786 days 7 hours 51 mins 44 secs
+ @ 330165 days 7 hours 51 mins 44 secs
+ @ 111019 days 7 hours 51 mins 44 secs
+ @ 74495 days 7 hours 51 mins 44 secs
+ @ 37971 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 35077 days 16 hours 8 mins 16 secs
+ @ 1801 days 7 hours 51 mins 44 secs
+ @ 1800 days 7 hours 51 mins 44 secs
+ @ 1799 days 7 hours 51 mins 44 secs
+ @ 1495 days 7 hours 51 mins 44 secs
+ @ 1494 days 7 hours 51 mins 44 secs
+ @ 1493 days 7 hours 51 mins 44 secs
+ @ 1435 days 7 hours 51 mins 44 secs
+ @ 1434 days 7 hours 51 mins 44 secs
+ @ 1130 days 7 hours 51 mins 44 secs
+ @ 1129 days 7 hours 51 mins 44 secs
+ @ 399 days 7 hours 51 mins 44 secs
+ @ 398 days 7 hours 51 mins 44 secs
+ @ 33 days 7 hours 51 mins 44 secs
+ @ 32 days 7 hours 51 mins 44 secs
+(65 rows)
+
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+               Distance                
+---------------------------------------
+ @ 11356 days 1 hour 23 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 43 secs
+ @ 1453 days 7 hours 51 mins 43.6 secs
+ @ 1453 days 7 hours 51 mins 43.5 secs
+ @ 1453 days 7 hours 51 mins 43.4 secs
+ @ 1493 days 1 hour 23 mins 45 secs
+ @ 1492 days 22 hours 19 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1333 days 7 hours 51 mins 44 secs
+ @ 231 days 16 hours 55 mins 35 secs
+ @ 324 days 17 hours 9 mins 44 secs
+ @ 324 days 12 hours 9 mins 43 secs
+ @ 324 days 13 hours 9 mins 42 secs
+ @ 324 days 22 hours 9 mins 41 secs
+ @ 324 days 23 hours 9 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1333 days 6 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1452 days 7 hours 51 mins 44 secs
+ @ 1451 days 7 hours 51 mins 44 secs
+ @ 1450 days 7 hours 51 mins 44 secs
+ @ 1449 days 7 hours 51 mins 44 secs
+ @ 1448 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 765901 days 7 hours 51 mins 44 secs
+ @ 695407 days 7 hours 51 mins 44 secs
+ @ 512786 days 7 hours 51 mins 44 secs
+ @ 330165 days 7 hours 51 mins 44 secs
+ @ 111019 days 7 hours 51 mins 44 secs
+ @ 74495 days 7 hours 51 mins 44 secs
+ @ 37971 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 35077 days 16 hours 8 mins 16 secs
+ @ 1801 days 7 hours 51 mins 44 secs
+ @ 1800 days 7 hours 51 mins 44 secs
+ @ 1799 days 7 hours 51 mins 44 secs
+ @ 1495 days 7 hours 51 mins 44 secs
+ @ 1494 days 7 hours 51 mins 44 secs
+ @ 1493 days 7 hours 51 mins 44 secs
+ @ 1435 days 7 hours 51 mins 44 secs
+ @ 1434 days 7 hours 51 mins 44 secs
+ @ 1130 days 7 hours 51 mins 44 secs
+ @ 1129 days 7 hours 51 mins 44 secs
+ @ 399 days 7 hours 51 mins 44 secs
+ @ 398 days 7 hours 51 mins 44 secs
+ @ 33 days 7 hours 51 mins 44 secs
+ @ 32 days 7 hours 51 mins 44 secs
+(63 rows)
+
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+                Distance                
+----------------------------------------
+ infinity
+ infinity
+ @ 11355 days 14 hours 23 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 43 secs
+ @ 1452 days 20 hours 51 mins 43.6 secs
+ @ 1452 days 20 hours 51 mins 43.5 secs
+ @ 1452 days 20 hours 51 mins 43.4 secs
+ @ 1492 days 14 hours 23 mins 45 secs
+ @ 1492 days 11 hours 19 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1332 days 21 hours 51 mins 44 secs
+ @ 232 days 2 hours 55 mins 35 secs
+ @ 324 days 6 hours 9 mins 44 secs
+ @ 324 days 1 hour 9 mins 43 secs
+ @ 324 days 2 hours 9 mins 42 secs
+ @ 324 days 11 hours 9 mins 41 secs
+ @ 324 days 12 hours 9 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1332 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1451 days 20 hours 51 mins 44 secs
+ @ 1450 days 20 hours 51 mins 44 secs
+ @ 1449 days 20 hours 51 mins 44 secs
+ @ 1448 days 20 hours 51 mins 44 secs
+ @ 1447 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 765900 days 20 hours 51 mins 44 secs
+ @ 695406 days 20 hours 51 mins 44 secs
+ @ 512785 days 20 hours 51 mins 44 secs
+ @ 330164 days 20 hours 51 mins 44 secs
+ @ 111018 days 20 hours 51 mins 44 secs
+ @ 74494 days 20 hours 51 mins 44 secs
+ @ 37970 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 35078 days 3 hours 8 mins 16 secs
+ @ 1800 days 20 hours 51 mins 44 secs
+ @ 1799 days 20 hours 51 mins 44 secs
+ @ 1798 days 20 hours 51 mins 44 secs
+ @ 1494 days 20 hours 51 mins 44 secs
+ @ 1493 days 20 hours 51 mins 44 secs
+ @ 1492 days 20 hours 51 mins 44 secs
+ @ 1434 days 20 hours 51 mins 44 secs
+ @ 1433 days 20 hours 51 mins 44 secs
+ @ 1129 days 20 hours 51 mins 44 secs
+ @ 1128 days 20 hours 51 mins 44 secs
+ @ 398 days 20 hours 51 mins 44 secs
+ @ 397 days 20 hours 51 mins 44 secs
+ @ 32 days 20 hours 51 mins 44 secs
+ @ 31 days 20 hours 51 mins 44 secs
+(65 rows)
+
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+                Distance                
+----------------------------------------
+ @ 11355 days 14 hours 23 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 43 secs
+ @ 1452 days 20 hours 51 mins 43.6 secs
+ @ 1452 days 20 hours 51 mins 43.5 secs
+ @ 1452 days 20 hours 51 mins 43.4 secs
+ @ 1492 days 14 hours 23 mins 45 secs
+ @ 1492 days 11 hours 19 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1332 days 21 hours 51 mins 44 secs
+ @ 232 days 2 hours 55 mins 35 secs
+ @ 324 days 6 hours 9 mins 44 secs
+ @ 324 days 1 hour 9 mins 43 secs
+ @ 324 days 2 hours 9 mins 42 secs
+ @ 324 days 11 hours 9 mins 41 secs
+ @ 324 days 12 hours 9 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1332 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1451 days 20 hours 51 mins 44 secs
+ @ 1450 days 20 hours 51 mins 44 secs
+ @ 1449 days 20 hours 51 mins 44 secs
+ @ 1448 days 20 hours 51 mins 44 secs
+ @ 1447 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 765900 days 20 hours 51 mins 44 secs
+ @ 695406 days 20 hours 51 mins 44 secs
+ @ 512785 days 20 hours 51 mins 44 secs
+ @ 330164 days 20 hours 51 mins 44 secs
+ @ 111018 days 20 hours 51 mins 44 secs
+ @ 74494 days 20 hours 51 mins 44 secs
+ @ 37970 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 35078 days 3 hours 8 mins 16 secs
+ @ 1800 days 20 hours 51 mins 44 secs
+ @ 1799 days 20 hours 51 mins 44 secs
+ @ 1798 days 20 hours 51 mins 44 secs
+ @ 1494 days 20 hours 51 mins 44 secs
+ @ 1493 days 20 hours 51 mins 44 secs
+ @ 1492 days 20 hours 51 mins 44 secs
+ @ 1434 days 20 hours 51 mins 44 secs
+ @ 1433 days 20 hours 51 mins 44 secs
+ @ 1129 days 20 hours 51 mins 44 secs
+ @ 1128 days 20 hours 51 mins 44 secs
+ @ 398 days 20 hours 51 mins 44 secs
+ @ 397 days 20 hours 51 mins 44 secs
+ @ 32 days 20 hours 51 mins 44 secs
+ @ 31 days 20 hours 51 mins 44 secs
+(63 rows)
+
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index bfb3825ff6c..850686c7cb1 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -3286,3 +3286,424 @@ SELECT age(timestamptz '-infinity', timestamptz 'infinity');
 
 SELECT age(timestamptz '-infinity', timestamptz '-infinity');
 ERROR:  interval out of range
+-- distance operators
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+               Distance                
+---------------------------------------
+ infinity
+ infinity
+ @ 11356 days 8 hours
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 58 secs
+ @ 1453 days 6 hours 27 mins 58.6 secs
+ @ 1453 days 6 hours 27 mins 58.5 secs
+ @ 1453 days 6 hours 27 mins 58.4 secs
+ @ 1493 days
+ @ 1492 days 20 hours 55 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1333 days 7 hours 27 mins 59 secs
+ @ 231 days 17 hours 19 mins 20 secs
+ @ 324 days 15 hours 45 mins 59 secs
+ @ 324 days 19 hours 45 mins 58 secs
+ @ 324 days 21 hours 45 mins 57 secs
+ @ 324 days 20 hours 45 mins 56 secs
+ @ 324 days 22 hours 45 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 28 mins
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 14 hours 27 mins 59 secs
+ @ 1453 days 14 hours 27 mins 59 secs
+ @ 1453 days 14 hours 27 mins 59 secs
+ @ 1453 days 9 hours 27 mins 59 secs
+ @ 1303 days 10 hours 27 mins 59 secs
+ @ 1333 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1452 days 6 hours 27 mins 59 secs
+ @ 1451 days 6 hours 27 mins 59 secs
+ @ 1450 days 6 hours 27 mins 59 secs
+ @ 1449 days 6 hours 27 mins 59 secs
+ @ 1448 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 765901 days 6 hours 27 mins 59 secs
+ @ 695407 days 6 hours 27 mins 59 secs
+ @ 512786 days 6 hours 27 mins 59 secs
+ @ 330165 days 6 hours 27 mins 59 secs
+ @ 111019 days 6 hours 27 mins 59 secs
+ @ 74495 days 6 hours 27 mins 59 secs
+ @ 37971 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 35077 days 17 hours 32 mins 1 sec
+ @ 1801 days 6 hours 27 mins 59 secs
+ @ 1800 days 6 hours 27 mins 59 secs
+ @ 1799 days 6 hours 27 mins 59 secs
+ @ 1495 days 6 hours 27 mins 59 secs
+ @ 1494 days 6 hours 27 mins 59 secs
+ @ 1493 days 6 hours 27 mins 59 secs
+ @ 1435 days 6 hours 27 mins 59 secs
+ @ 1434 days 6 hours 27 mins 59 secs
+ @ 1130 days 6 hours 27 mins 59 secs
+ @ 1129 days 6 hours 27 mins 59 secs
+ @ 399 days 6 hours 27 mins 59 secs
+ @ 398 days 6 hours 27 mins 59 secs
+ @ 33 days 6 hours 27 mins 59 secs
+ @ 32 days 6 hours 27 mins 59 secs
+(66 rows)
+
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+               Distance                
+---------------------------------------
+ @ 11356 days 8 hours
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 58 secs
+ @ 1453 days 6 hours 27 mins 58.6 secs
+ @ 1453 days 6 hours 27 mins 58.5 secs
+ @ 1453 days 6 hours 27 mins 58.4 secs
+ @ 1493 days
+ @ 1492 days 20 hours 55 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1333 days 7 hours 27 mins 59 secs
+ @ 231 days 17 hours 19 mins 20 secs
+ @ 324 days 15 hours 45 mins 59 secs
+ @ 324 days 19 hours 45 mins 58 secs
+ @ 324 days 21 hours 45 mins 57 secs
+ @ 324 days 20 hours 45 mins 56 secs
+ @ 324 days 22 hours 45 mins 55 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 28 mins
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1453 days 14 hours 27 mins 59 secs
+ @ 1453 days 14 hours 27 mins 59 secs
+ @ 1453 days 14 hours 27 mins 59 secs
+ @ 1453 days 9 hours 27 mins 59 secs
+ @ 1303 days 10 hours 27 mins 59 secs
+ @ 1333 days 6 hours 27 mins 59 secs
+ @ 1453 days 6 hours 27 mins 59 secs
+ @ 1452 days 6 hours 27 mins 59 secs
+ @ 1451 days 6 hours 27 mins 59 secs
+ @ 1450 days 6 hours 27 mins 59 secs
+ @ 1449 days 6 hours 27 mins 59 secs
+ @ 1448 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 765901 days 6 hours 27 mins 59 secs
+ @ 695407 days 6 hours 27 mins 59 secs
+ @ 512786 days 6 hours 27 mins 59 secs
+ @ 330165 days 6 hours 27 mins 59 secs
+ @ 111019 days 6 hours 27 mins 59 secs
+ @ 74495 days 6 hours 27 mins 59 secs
+ @ 37971 days 6 hours 27 mins 59 secs
+ @ 1447 days 6 hours 27 mins 59 secs
+ @ 35077 days 17 hours 32 mins 1 sec
+ @ 1801 days 6 hours 27 mins 59 secs
+ @ 1800 days 6 hours 27 mins 59 secs
+ @ 1799 days 6 hours 27 mins 59 secs
+ @ 1495 days 6 hours 27 mins 59 secs
+ @ 1494 days 6 hours 27 mins 59 secs
+ @ 1493 days 6 hours 27 mins 59 secs
+ @ 1435 days 6 hours 27 mins 59 secs
+ @ 1434 days 6 hours 27 mins 59 secs
+ @ 1130 days 6 hours 27 mins 59 secs
+ @ 1129 days 6 hours 27 mins 59 secs
+ @ 399 days 6 hours 27 mins 59 secs
+ @ 398 days 6 hours 27 mins 59 secs
+ @ 33 days 6 hours 27 mins 59 secs
+ @ 32 days 6 hours 27 mins 59 secs
+(64 rows)
+
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+               Distance                
+---------------------------------------
+ infinity
+ infinity
+ @ 11356 days 9 hours 23 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 43 secs
+ @ 1453 days 7 hours 51 mins 43.6 secs
+ @ 1453 days 7 hours 51 mins 43.5 secs
+ @ 1453 days 7 hours 51 mins 43.4 secs
+ @ 1493 days 1 hour 23 mins 45 secs
+ @ 1492 days 22 hours 19 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1333 days 8 hours 51 mins 44 secs
+ @ 231 days 15 hours 55 mins 35 secs
+ @ 324 days 17 hours 9 mins 44 secs
+ @ 324 days 21 hours 9 mins 43 secs
+ @ 324 days 23 hours 9 mins 42 secs
+ @ 324 days 22 hours 9 mins 41 secs
+ @ 325 days 9 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 15 hours 51 mins 44 secs
+ @ 1453 days 15 hours 51 mins 44 secs
+ @ 1453 days 15 hours 51 mins 44 secs
+ @ 1453 days 10 hours 51 mins 44 secs
+ @ 1303 days 11 hours 51 mins 44 secs
+ @ 1333 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1452 days 7 hours 51 mins 44 secs
+ @ 1451 days 7 hours 51 mins 44 secs
+ @ 1450 days 7 hours 51 mins 44 secs
+ @ 1449 days 7 hours 51 mins 44 secs
+ @ 1448 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 765901 days 7 hours 51 mins 44 secs
+ @ 695407 days 7 hours 51 mins 44 secs
+ @ 512786 days 7 hours 51 mins 44 secs
+ @ 330165 days 7 hours 51 mins 44 secs
+ @ 111019 days 7 hours 51 mins 44 secs
+ @ 74495 days 7 hours 51 mins 44 secs
+ @ 37971 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 35077 days 16 hours 8 mins 16 secs
+ @ 1801 days 7 hours 51 mins 44 secs
+ @ 1800 days 7 hours 51 mins 44 secs
+ @ 1799 days 7 hours 51 mins 44 secs
+ @ 1495 days 7 hours 51 mins 44 secs
+ @ 1494 days 7 hours 51 mins 44 secs
+ @ 1493 days 7 hours 51 mins 44 secs
+ @ 1435 days 7 hours 51 mins 44 secs
+ @ 1434 days 7 hours 51 mins 44 secs
+ @ 1130 days 7 hours 51 mins 44 secs
+ @ 1129 days 7 hours 51 mins 44 secs
+ @ 399 days 7 hours 51 mins 44 secs
+ @ 398 days 7 hours 51 mins 44 secs
+ @ 33 days 7 hours 51 mins 44 secs
+ @ 32 days 7 hours 51 mins 44 secs
+(66 rows)
+
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+               Distance                
+---------------------------------------
+ @ 11356 days 9 hours 23 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 43 secs
+ @ 1453 days 7 hours 51 mins 43.6 secs
+ @ 1453 days 7 hours 51 mins 43.5 secs
+ @ 1453 days 7 hours 51 mins 43.4 secs
+ @ 1493 days 1 hour 23 mins 45 secs
+ @ 1492 days 22 hours 19 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1333 days 8 hours 51 mins 44 secs
+ @ 231 days 15 hours 55 mins 35 secs
+ @ 324 days 17 hours 9 mins 44 secs
+ @ 324 days 21 hours 9 mins 43 secs
+ @ 324 days 23 hours 9 mins 42 secs
+ @ 324 days 22 hours 9 mins 41 secs
+ @ 325 days 9 mins 40 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 45 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1453 days 15 hours 51 mins 44 secs
+ @ 1453 days 15 hours 51 mins 44 secs
+ @ 1453 days 15 hours 51 mins 44 secs
+ @ 1453 days 10 hours 51 mins 44 secs
+ @ 1303 days 11 hours 51 mins 44 secs
+ @ 1333 days 7 hours 51 mins 44 secs
+ @ 1453 days 7 hours 51 mins 44 secs
+ @ 1452 days 7 hours 51 mins 44 secs
+ @ 1451 days 7 hours 51 mins 44 secs
+ @ 1450 days 7 hours 51 mins 44 secs
+ @ 1449 days 7 hours 51 mins 44 secs
+ @ 1448 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 765901 days 7 hours 51 mins 44 secs
+ @ 695407 days 7 hours 51 mins 44 secs
+ @ 512786 days 7 hours 51 mins 44 secs
+ @ 330165 days 7 hours 51 mins 44 secs
+ @ 111019 days 7 hours 51 mins 44 secs
+ @ 74495 days 7 hours 51 mins 44 secs
+ @ 37971 days 7 hours 51 mins 44 secs
+ @ 1447 days 7 hours 51 mins 44 secs
+ @ 35077 days 16 hours 8 mins 16 secs
+ @ 1801 days 7 hours 51 mins 44 secs
+ @ 1800 days 7 hours 51 mins 44 secs
+ @ 1799 days 7 hours 51 mins 44 secs
+ @ 1495 days 7 hours 51 mins 44 secs
+ @ 1494 days 7 hours 51 mins 44 secs
+ @ 1493 days 7 hours 51 mins 44 secs
+ @ 1435 days 7 hours 51 mins 44 secs
+ @ 1434 days 7 hours 51 mins 44 secs
+ @ 1130 days 7 hours 51 mins 44 secs
+ @ 1129 days 7 hours 51 mins 44 secs
+ @ 399 days 7 hours 51 mins 44 secs
+ @ 398 days 7 hours 51 mins 44 secs
+ @ 33 days 7 hours 51 mins 44 secs
+ @ 32 days 7 hours 51 mins 44 secs
+(64 rows)
+
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+                Distance                
+----------------------------------------
+ infinity
+ infinity
+ @ 11355 days 22 hours 23 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 43 secs
+ @ 1452 days 20 hours 51 mins 43.6 secs
+ @ 1452 days 20 hours 51 mins 43.5 secs
+ @ 1452 days 20 hours 51 mins 43.4 secs
+ @ 1492 days 14 hours 23 mins 45 secs
+ @ 1492 days 11 hours 19 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1332 days 21 hours 51 mins 44 secs
+ @ 232 days 2 hours 55 mins 35 secs
+ @ 324 days 6 hours 9 mins 44 secs
+ @ 324 days 10 hours 9 mins 43 secs
+ @ 324 days 12 hours 9 mins 42 secs
+ @ 324 days 11 hours 9 mins 41 secs
+ @ 324 days 13 hours 9 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1453 days 4 hours 51 mins 44 secs
+ @ 1453 days 4 hours 51 mins 44 secs
+ @ 1453 days 4 hours 51 mins 44 secs
+ @ 1452 days 23 hours 51 mins 44 secs
+ @ 1303 days 51 mins 44 secs
+ @ 1332 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1451 days 20 hours 51 mins 44 secs
+ @ 1450 days 20 hours 51 mins 44 secs
+ @ 1449 days 20 hours 51 mins 44 secs
+ @ 1448 days 20 hours 51 mins 44 secs
+ @ 1447 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 765900 days 20 hours 51 mins 44 secs
+ @ 695406 days 20 hours 51 mins 44 secs
+ @ 512785 days 20 hours 51 mins 44 secs
+ @ 330164 days 20 hours 51 mins 44 secs
+ @ 111018 days 20 hours 51 mins 44 secs
+ @ 74494 days 20 hours 51 mins 44 secs
+ @ 37970 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 35078 days 3 hours 8 mins 16 secs
+ @ 1800 days 20 hours 51 mins 44 secs
+ @ 1799 days 20 hours 51 mins 44 secs
+ @ 1798 days 20 hours 51 mins 44 secs
+ @ 1494 days 20 hours 51 mins 44 secs
+ @ 1493 days 20 hours 51 mins 44 secs
+ @ 1492 days 20 hours 51 mins 44 secs
+ @ 1434 days 20 hours 51 mins 44 secs
+ @ 1433 days 20 hours 51 mins 44 secs
+ @ 1129 days 20 hours 51 mins 44 secs
+ @ 1128 days 20 hours 51 mins 44 secs
+ @ 398 days 20 hours 51 mins 44 secs
+ @ 397 days 20 hours 51 mins 44 secs
+ @ 32 days 20 hours 51 mins 44 secs
+ @ 31 days 20 hours 51 mins 44 secs
+(66 rows)
+
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+                Distance                
+----------------------------------------
+ @ 11355 days 22 hours 23 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 43 secs
+ @ 1452 days 20 hours 51 mins 43.6 secs
+ @ 1452 days 20 hours 51 mins 43.5 secs
+ @ 1452 days 20 hours 51 mins 43.4 secs
+ @ 1492 days 14 hours 23 mins 45 secs
+ @ 1492 days 11 hours 19 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1332 days 21 hours 51 mins 44 secs
+ @ 232 days 2 hours 55 mins 35 secs
+ @ 324 days 6 hours 9 mins 44 secs
+ @ 324 days 10 hours 9 mins 43 secs
+ @ 324 days 12 hours 9 mins 42 secs
+ @ 324 days 11 hours 9 mins 41 secs
+ @ 324 days 13 hours 9 mins 40 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 45 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1453 days 4 hours 51 mins 44 secs
+ @ 1453 days 4 hours 51 mins 44 secs
+ @ 1453 days 4 hours 51 mins 44 secs
+ @ 1452 days 23 hours 51 mins 44 secs
+ @ 1303 days 51 mins 44 secs
+ @ 1332 days 20 hours 51 mins 44 secs
+ @ 1452 days 20 hours 51 mins 44 secs
+ @ 1451 days 20 hours 51 mins 44 secs
+ @ 1450 days 20 hours 51 mins 44 secs
+ @ 1449 days 20 hours 51 mins 44 secs
+ @ 1448 days 20 hours 51 mins 44 secs
+ @ 1447 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 765900 days 20 hours 51 mins 44 secs
+ @ 695406 days 20 hours 51 mins 44 secs
+ @ 512785 days 20 hours 51 mins 44 secs
+ @ 330164 days 20 hours 51 mins 44 secs
+ @ 111018 days 20 hours 51 mins 44 secs
+ @ 74494 days 20 hours 51 mins 44 secs
+ @ 37970 days 20 hours 51 mins 44 secs
+ @ 1446 days 20 hours 51 mins 44 secs
+ @ 35078 days 3 hours 8 mins 16 secs
+ @ 1800 days 20 hours 51 mins 44 secs
+ @ 1799 days 20 hours 51 mins 44 secs
+ @ 1798 days 20 hours 51 mins 44 secs
+ @ 1494 days 20 hours 51 mins 44 secs
+ @ 1493 days 20 hours 51 mins 44 secs
+ @ 1492 days 20 hours 51 mins 44 secs
+ @ 1434 days 20 hours 51 mins 44 secs
+ @ 1433 days 20 hours 51 mins 44 secs
+ @ 1129 days 20 hours 51 mins 44 secs
+ @ 1128 days 20 hours 51 mins 44 secs
+ @ 398 days 20 hours 51 mins 44 secs
+ @ 397 days 20 hours 51 mins 44 secs
+ @ 32 days 20 hours 51 mins 44 secs
+ @ 31 days 20 hours 51 mins 44 secs
+(64 rows)
+
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
index 1c58ff6966d..43bdd65417e 100644
--- a/src/test/regress/sql/date.sql
+++ b/src/test/regress/sql/date.sql
@@ -373,3 +373,8 @@ select make_date(2013, 13, 1);
 select make_date(2013, 11, -1);
 select make_time(10, 55, 100.1);
 select make_time(24, 0, 2.1);
+
+-- distance operators
+SELECT '' AS "Fifteen", f1 <-> date '2001-02-03' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM DATE_TBL;
+SELECT '' AS "Fifteen", f1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM DATE_TBL;
diff --git a/src/test/regress/sql/float4.sql b/src/test/regress/sql/float4.sql
index 8fb12368c39..594ac2fadf2 100644
--- a/src/test/regress/sql/float4.sql
+++ b/src/test/regress/sql/float4.sql
@@ -100,6 +100,9 @@ UPDATE FLOAT4_TBL
 
 SELECT * FROM FLOAT4_TBL;
 
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3' AS dist FROM FLOAT4_TBL f;
+SELECT '' AS five, f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT4_TBL f;
+
 -- test edge-case coercions to integer
 SELECT '32767.4'::float4::int2;
 SELECT '32767.6'::float4::int2;
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
index 98e9926c9e0..e477534a59b 100644
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -176,6 +176,9 @@ SELECT ||/ float8 '27' AS three;
 
 SELECT f.f1, ||/f.f1 AS cbrt_f1 FROM FLOAT8_TBL f;
 
+-- distance
+SELECT f.f1, f.f1 <-> '1004.3'::float8 AS dist FROM FLOAT8_TBL f;
+SELECT f.f1, f.f1 <-> '1004.3'::float4 AS dist FROM FLOAT8_TBL f;
 
 SELECT * FROM FLOAT8_TBL;
 
diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql
index df1e46d4e2e..30678e0a13b 100644
--- a/src/test/regress/sql/int2.sql
+++ b/src/test/regress/sql/int2.sql
@@ -87,6 +87,16 @@ SELECT i.f1, i.f1 / int2 '2' AS x FROM INT2_TBL i;
 
 SELECT i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
 
+-- distance
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT2_TBL i
+WHERE f1 > -32767;
+
+SELECT '' AS five, i.f1, i.f1 <-> int4 '2' AS x FROM INT2_TBL i;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT2_TBL i;
+
 -- corner cases
 SELECT (-1::int2<<15)::text;
 SELECT ((-1::int2<<15)+1::int2)::text;
diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql
index e9d89e8111f..888da6eedec 100644
--- a/src/test/regress/sql/int4.sql
+++ b/src/test/regress/sql/int4.sql
@@ -87,6 +87,16 @@ SELECT i.f1, i.f1 / int2 '2' AS x FROM INT4_TBL i;
 
 SELECT i.f1, i.f1 / int4 '2' AS x FROM INT4_TBL i;
 
+SELECT '' AS five, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i;
+
+SELECT '' AS four, i.f1, i.f1 <-> int2 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS four, i.f1, i.f1 <-> int4 '2' AS x FROM INT4_TBL i
+WHERE f1 > -2147483647;
+
+SELECT '' AS five, i.f1, i.f1 <-> int8 '2' AS x FROM INT4_TBL i;
+
 --
 -- more complex expressions
 --
diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql
index fffb28906a1..4e759a9838d 100644
--- a/src/test/regress/sql/int8.sql
+++ b/src/test/regress/sql/int8.sql
@@ -90,6 +90,11 @@ SELECT q1 + 42::int2 AS "8plus2", q1 - 42::int2 AS "8minus2", q1 * 42::int2 AS "
 -- int2 op int8
 SELECT 246::int2 + q1 AS "2plus8", 246::int2 - q1 AS "2minus8", 246::int2 * q1 AS "2mul8", 246::int2 / q1 AS "2div8" FROM INT8_TBL;
 
+-- distance
+SELECT '' AS five, q2, q2 <-> int2 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int4 '123' AS dist FROM INT8_TBL i;
+SELECT '' AS five, q2, q2 <-> int8 '123' AS dist FROM INT8_TBL i;
+
 SELECT q2, abs(q2) FROM INT8_TBL;
 SELECT min(q1), min(q2) FROM INT8_TBL;
 SELECT max(q1), max(q2) FROM INT8_TBL;
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index fbf6e064d66..6737a278b43 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -81,6 +81,8 @@ SELECT -('-9223372036854775808 us'::interval); -- should fail
 SELECT -('-9223372036854775807 us'::interval); -- ok
 SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
 
+SELECT f1 <-> interval '@ 2 day 3 hours' FROM INTERVAL_TBL;
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql
index b888ec21c30..0fafd2e66eb 100644
--- a/src/test/regress/sql/money.sql
+++ b/src/test/regress/sql/money.sql
@@ -27,6 +27,7 @@ SELECT m / 2::float8 FROM money_data;
 SELECT m * 2::float4 FROM money_data;
 SELECT 2::float4 * m FROM money_data;
 SELECT m / 2::float4 FROM money_data;
+SELECT m <-> '$123.45' FROM money_data;
 
 -- All true
 SELECT m = '$123.00' FROM money_data;
diff --git a/src/test/regress/sql/oid.sql b/src/test/regress/sql/oid.sql
index a96b2aa1e3d..223c082c730 100644
--- a/src/test/regress/sql/oid.sql
+++ b/src/test/regress/sql/oid.sql
@@ -54,4 +54,6 @@ SELECT o.* FROM OID_TBL o WHERE o.f1 >= '1234';
 
 SELECT o.* FROM OID_TBL o WHERE o.f1 > '1234';
 
+SELECT '' AS eight, f1, f1 <-> oid '123' FROM OID_TBL;
+
 DROP TABLE OID_TBL;
diff --git a/src/test/regress/sql/time.sql b/src/test/regress/sql/time.sql
index eb375a36e9a..4c401586691 100644
--- a/src/test/regress/sql/time.sql
+++ b/src/test/regress/sql/time.sql
@@ -77,3 +77,6 @@ SELECT date_part('microsecond', TIME '2020-05-26 13:30:25.575401');
 SELECT date_part('millisecond', TIME '2020-05-26 13:30:25.575401');
 SELECT date_part('second',      TIME '2020-05-26 13:30:25.575401');
 SELECT date_part('epoch',       TIME '2020-05-26 13:30:25.575401');
+
+-- distance
+SELECT f1 <-> time '01:23:45' AS "Distance" FROM TIME_TBL;
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index 820ef7752ac..d670c889571 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -424,3 +424,12 @@ select age(timestamp 'infinity', timestamp 'infinity');
 select age(timestamp 'infinity', timestamp '-infinity');
 select age(timestamp '-infinity', timestamp 'infinity');
 select age(timestamp '-infinity', timestamp '-infinity');
+SELECT make_timestamp(2014,12,28,6,30,45.887);
+
+-- distance operators
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL;
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMP_TBL WHERE isfinite(d1);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index ccfd90d6467..a0c8889e770 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -668,3 +668,11 @@ SELECT age(timestamptz 'infinity', timestamptz 'infinity');
 SELECT age(timestamptz 'infinity', timestamptz '-infinity');
 SELECT age(timestamptz '-infinity', timestamptz 'infinity');
 SELECT age(timestamptz '-infinity', timestamptz '-infinity');
+
+-- distance operators
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT d1 <-> date '2001-02-03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT d1 <-> timestamp '2001-02-03 01:23:45' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL;
+SELECT d1 <-> timestamptz '2001-02-03 01:23:45+03' AS "Distance" FROM TIMESTAMPTZ_TBL WHERE isfinite(d1);
-- 
2.45.2

v19_0005-Add-knn-support-to-btree-indexes.patchtext/x-patch; charset=UTF-8; name=v19_0005-Add-knn-support-to-btree-indexes.patchDownload
From 794429c6636bc663b15673f25b57d2db862cf2d2 Mon Sep 17 00:00:00 2001
From: "Anton A. Melnikov" <a.melnikov@postgrespro.ru>
Date: Mon, 1 Jan 2024 05:02:10 +0300
Subject: [PATCH 5/5] Add knn support to btree indexes

This commit implements support for knn scans in btree indexes.  When knn search
is requested, btree index is traversed ascending and descending simultaneously.
At each step the closest tuple is returned.  Filtering operators can reduce
knn to regular ordered scan.

Ordering operators are added to opfamilies of scalar datatypes.  No extra
supporting functions are required: knn-btree algorithm works using comparison
function and ordering operator itself.

Distance operators are not leakproof, because they throw error on overflow.
Therefore we relax opr_sanity check for btree ordering operators.  It's OK for
them to be leaky while comparison function is leakproof.

Catversion is bumped.

Discussion: https://postgr.es/m/ce35e97b-cf34-3f5d-6b99-2c25bae49999%40postgrespro.ru
Author: Nikita Glukhov
Reviewed-by: Robert Haas, Tom Lane, Anastasia Lubennikova, Alexander Korotkov
---
 doc/src/sgml/btree.sgml                     |  47 +
 doc/src/sgml/indices.sgml                   |  11 +
 doc/src/sgml/xindex.sgml                    |   7 +-
 src/backend/access/brin/brin_minmax.c       |   6 +-
 src/backend/access/nbtree/README            |  22 +
 src/backend/access/nbtree/nbtree.c          | 214 ++++-
 src/backend/access/nbtree/nbtsearch.c       | 361 +++++++-
 src/backend/access/nbtree/nbtutils.c        | 475 +++++++++-
 src/backend/access/nbtree/nbtvalidate.c     |  45 +-
 src/backend/partitioning/partprune.c        |   4 +-
 src/include/access/nbtree.h                 |  32 +-
 src/include/access/stratnum.h               |   7 +-
 src/include/catalog/pg_amop.dat             | 104 +++
 src/test/regress/expected/alter_generic.out |  13 +-
 src/test/regress/expected/amutils.out       |   6 +-
 src/test/regress/expected/btree_index.out   | 954 ++++++++++++++++++++
 src/test/regress/expected/opr_sanity.out    |  10 +-
 src/test/regress/expected/psql.out          |  52 +-
 src/test/regress/sql/alter_generic.sql      |   8 +-
 src/test/regress/sql/btree_index.sql        | 312 +++++++
 src/test/regress/sql/opr_sanity.sql         |   7 +-
 21 files changed, 2526 insertions(+), 171 deletions(-)

diff --git a/doc/src/sgml/btree.sgml b/doc/src/sgml/btree.sgml
index 2b3997988cf..642e26d764b 100644
--- a/doc/src/sgml/btree.sgml
+++ b/doc/src/sgml/btree.sgml
@@ -200,6 +200,53 @@
   planner relies on them for optimization purposes.
  </para>
 
+ <para>
+  In order to implement the distance ordered (nearest-neighbor) search,
+  one needs to define a distance operator (usually it's called
+  <literal>&lt;-&gt;</literal>) with a correpsonding operator family for
+  distance comparison in the operator class.  These operators must
+  satisfy the following assumptions for all non-null values
+  <replaceable>A</replaceable>, <replaceable>B</replaceable>,
+  <replaceable>C</replaceable> of the data type:
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     <replaceable>A</replaceable> <literal>&lt;-&gt;</literal>
+     <replaceable>B</replaceable> <literal>=</literal>
+     <replaceable>B</replaceable> <literal>&lt;-&gt;</literal>
+     <replaceable>A</replaceable>
+     (<firstterm>symmetric law</firstterm>)
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     if <replaceable>A</replaceable> <literal>=</literal>
+     <replaceable>B</replaceable>, then <replaceable>A</replaceable>
+     <literal>&lt;-&gt;</literal> <replaceable>C</replaceable>
+     <literal>=</literal> <replaceable>B</replaceable>
+     <literal>&lt;-&gt;</literal> <replaceable>C</replaceable>
+     (<firstterm>distance equivalence</firstterm>)
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      if (<replaceable>A</replaceable> <literal>&lt;=</literal>
+      <replaceable>B</replaceable> and <replaceable>B</replaceable>
+      <literal>&lt;=</literal> <replaceable>C</replaceable>) or
+      (<replaceable>A</replaceable> <literal>&gt;=</literal>
+      <replaceable>B</replaceable> and <replaceable>B</replaceable>
+      <literal>&gt;=</literal> <replaceable>C</replaceable>),
+      then <replaceable>A</replaceable> <literal>&lt;-&gt;</literal>
+      <replaceable>B</replaceable> <literal>&lt;=</literal>
+      <replaceable>A</replaceable> <literal>&lt;-&gt;</literal>
+      <replaceable>C</replaceable>
+     (<firstterm>monotonicity</firstterm>)
+    </para>
+   </listitem>
+  </itemizedlist>
+ </para>
+
 </sect2>
 
 <sect2 id="btree-support-funcs">
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 6d731e0701f..b841e1f0a54 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -1193,6 +1193,17 @@ SELECT x FROM tab WHERE x = 'key' AND z &lt; 42;
    make this type of scan very useful in practice.
   </para>
 
+  <para>
+   B-tree indexes are also capable of optimizing <quote>nearest-neighbor</quote>
+   searches, such as
+<programlisting><![CDATA[
+SELECT * FROM events ORDER BY event_date <-> date '2017-05-05' LIMIT 10;
+]]>
+</programlisting>
+   which finds the ten events closest to a given target date.  The ability
+   to do this is again dependent on the particular operator class being used.
+  </para>
+
   <para>
    <indexterm>
     <primary><literal>INCLUDE</literal></primary>
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 22d8ad1aac4..4636dce2a9a 100644
--- a/doc/src/sgml/xindex.sgml
+++ b/doc/src/sgml/xindex.sgml
@@ -131,6 +131,10 @@
        <entry>greater than</entry>
        <entry>5</entry>
       </row>
+      <row>
+       <entry>distance</entry>
+       <entry>6</entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
@@ -1320,7 +1324,8 @@ SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
   <title>Ordering Operators</title>
 
   <para>
-   Some index access methods (currently, only GiST and SP-GiST) support the concept of
+   Some index access methods (currently, B-tree, GiST and SP-GiST)
+   support the concept of
    <firstterm>ordering operators</firstterm>.  What we have been discussing so far
    are <firstterm>search operators</firstterm>.  A search operator is one for which
    the index can be searched to find all rows satisfying
diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c
index caf6991eb1b..2617545b8c8 100644
--- a/src/backend/access/brin/brin_minmax.c
+++ b/src/backend/access/brin/brin_minmax.c
@@ -23,7 +23,7 @@
 typedef struct MinmaxOpaque
 {
 	Oid			cached_subtype;
-	FmgrInfo	strategy_procinfos[BTMaxStrategyNumber];
+	FmgrInfo	strategy_procinfos[BTMaxSearchStrategyNumber];
 } MinmaxOpaque;
 
 static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno,
@@ -264,7 +264,7 @@ minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype,
 	MinmaxOpaque *opaque;
 
 	Assert(strategynum >= 1 &&
-		   strategynum <= BTMaxStrategyNumber);
+		   strategynum <= BTMaxSearchStrategyNumber);
 
 	opaque = (MinmaxOpaque *) bdesc->bd_info[attno - 1]->oi_opaque;
 
@@ -277,7 +277,7 @@ minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype,
 	{
 		uint16		i;
 
-		for (i = 1; i <= BTMaxStrategyNumber; i++)
+		for (i = 1; i <= BTMaxSearchStrategyNumber; i++)
 			opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid;
 		opaque->cached_subtype = subtype;
 	}
diff --git a/src/backend/access/nbtree/README b/src/backend/access/nbtree/README
index 52e646c7f75..0db79b64d61 100644
--- a/src/backend/access/nbtree/README
+++ b/src/backend/access/nbtree/README
@@ -1081,3 +1081,25 @@ item is irrelevant, and need not be stored at all.  This arrangement
 corresponds to the fact that an L&Y non-leaf page has one more pointer
 than key.  Suffix truncation's negative infinity attributes behave in
 the same way.
+
+Nearest-neighbor search
+-----------------------
+
+B-tree supports a special scan strategy for nearest-neighbor (kNN) search,
+which is used for queries with "ORDER BY indexed_column operator constant"
+clause.  See the following example.
+
+  SELECT * FROM tab WHERE col > const1 ORDER BY col <-> const2 LIMIT k
+
+Unlike GiST and SP-GiST, B-tree supports kNN by the only one ordering operator
+applied to the first indexed column.
+
+At the beginning of kNN scan, we determine the scan strategy to use: normal
+unidirectional or special bidirectional.  If the second distance operand falls
+into the scan range, then we use bidirectional scan, otherwise we use normal
+unidirectional scan.
+
+The bidirectional scan algorithm is quite simple.  We start both forward and
+backward scans starting from the tree location corresponding to the second
+distance operand.  Each time we need the next tuple, we return the nearest
+tuple from two directions and advance scan in corresponding direction.
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index fc3954cc157..7bf6b151631 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -32,6 +32,8 @@
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/fmgrprotos.h"
 #include "utils/index_selfuncs.h"
 #include "utils/memutils.h"
@@ -66,7 +68,8 @@ typedef enum
  */
 typedef struct BTParallelScanDescData
 {
-	BlockNumber btps_scanPage;	/* latest or next page to be scanned */
+	BlockNumber btps_forwardScanPage;	/* latest or next page to be scanned */
+	BlockNumber btps_backwardScanPage;	/* secondary kNN page to be scanned */
 	BTPS_State	btps_pageStatus;	/* indicates whether next page is
 									 * available for scan. see above for
 									 * possible states of parallel scan. */
@@ -106,8 +109,8 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amsupport = BTNProcs;
 	amroutine->amoptsprocnum = BTOPTIONS_PROC;
 	amroutine->amcanorder = true;
-	amroutine->amcanorderbyop = false;
-	amroutine->amorderbyopfirstcol = false;
+	amroutine->amcanorderbyop = true;
+	amroutine->amorderbyopfirstcol = true;
 	amroutine->amcanbackward = true;
 	amroutine->amcanunique = true;
 	amroutine->amcanmulticol = true;
@@ -208,10 +211,18 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	BTScanState state = &so->state;
+	ScanDirection arraydir = dir;
 	bool		res;
 
+	if (scan->numberOfOrderBys > 0 && !ScanDirectionIsForward(dir))
+		elog(ERROR, "btree does not support backward order-by-distance scanning");
+
 	/* btree indexes are never lossy */
 	scan->xs_recheck = false;
+	scan->xs_recheckorderby = false;
+
+	if (so->scanDirection != NoMovementScanDirection)
+		dir = so->scanDirection;
 
 	/* Each loop iteration performs another primitive index scan */
 	do
@@ -221,7 +232,8 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		 * the appropriate direction.  If we haven't done so yet, we call
 		 * _bt_first() to get the first item in the scan.
 		 */
-		if (!BTScanPosIsValid(state->currPos))
+		if (!BTScanPosIsValid(state->currPos) &&
+			(!so->backwardState || !BTScanPosIsValid(so->backwardState->currPos)))
 			res = _bt_first(scan, dir);
 		else
 		{
@@ -256,7 +268,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
 		if (res)
 			break;
 		/* ... otherwise see if we need another primitive index scan */
-	} while (so->numArrayKeys && _bt_start_prim_scan(scan, dir));
+	} while (so->numArrayKeys && _bt_start_prim_scan(scan, arraydir));
 
 	return res;
 }
@@ -317,9 +329,6 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	IndexScanDesc scan;
 	BTScanOpaque so;
 
-	/* no order by operators allowed */
-	Assert(norderbys == 0);
-
 	/* get the scan */
 	scan = RelationGetIndexScan(rel, nkeys, norderbys);
 
@@ -347,6 +356,9 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
 	 */
 	so->state.currTuples = so->state.markTuples = NULL;
+	so->backwardState = NULL;
+	so->distanceTypeByVal = true;
+	so->scanDirection = NoMovementScanDirection;
 
 	scan->xs_itupdesc = RelationGetDescr(rel);
 
@@ -376,6 +388,8 @@ _bt_release_current_position(BTScanState state, Relation indexRelation,
 static void
 _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+
 	/* No need to invalidate positions, if the RAM is about to be freed. */
 	_bt_release_current_position(state, scan->indexRelation, !free);
 
@@ -392,6 +406,18 @@ _bt_release_scan_state(IndexScanDesc scan, BTScanState state, bool free)
 	}
 	else
 		BTScanPosInvalidate(state->markPos);
+
+	if (!so->distanceTypeByVal)
+	{
+		if (DatumGetPointer(state->currDistance))
+			pfree(DatumGetPointer(state->currDistance));
+
+		if (DatumGetPointer(state->markDistance))
+			pfree(DatumGetPointer(state->markDistance));
+	}
+
+	state->currDistance = (Datum) 0;
+	state->markDistance = (Datum) 0;
 }
 
 /*
@@ -406,6 +432,13 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 
 	_bt_release_scan_state(scan, state, false);
 
+	if (so->backwardState)
+	{
+		_bt_release_scan_state(scan, so->backwardState, true);
+		pfree(so->backwardState);
+		so->backwardState = NULL;
+	}
+
 	so->needPrimScan = false;
 	so->scanBehind = false;
 
@@ -437,6 +470,14 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 				scan->numberOfKeys * sizeof(ScanKeyData));
 	so->numberOfKeys = 0;		/* until _bt_preprocess_keys sets it */
 	so->numArrayKeys = 0;		/* ditto */
+
+	if (orderbys && scan->numberOfOrderBys > 0)
+		memmove(scan->orderByData,
+				orderbys,
+				scan->numberOfOrderBys * sizeof(ScanKeyData));
+
+	so->scanDirection = NoMovementScanDirection;
+	so->distanceTypeByVal = true;
 }
 
 /*
@@ -449,6 +490,12 @@ btendscan(IndexScanDesc scan)
 
 	_bt_release_scan_state(scan, &so->state, true);
 
+	if (so->backwardState)
+	{
+		_bt_release_scan_state(scan, so->backwardState, true);
+		pfree(so->backwardState);
+	}
+
 	/* Release storage */
 	if (so->keyData != NULL)
 		pfree(so->keyData);
@@ -460,7 +507,7 @@ btendscan(IndexScanDesc scan)
 }
 
 static void
-_bt_mark_current_position(BTScanState state)
+_bt_mark_current_position(BTScanOpaque so, BTScanState state)
 {
 	/* There may be an old mark with a pin (but no lock). */
 	BTScanPosUnpinIfPinned(state->markPos);
@@ -478,6 +525,25 @@ _bt_mark_current_position(BTScanState state)
 		BTScanPosInvalidate(state->markPos);
 		state->markItemIndex = -1;
 	}
+
+	if (so->backwardState)
+	{
+		if (!so->distanceTypeByVal && DatumGetPointer(state->markDistance))
+			pfree(DatumGetPointer(state->markDistance));
+
+		if (!BTScanPosIsValid(state->currPos) || state->currIsNull)
+		{
+			state->markIsNull = true;
+			state->markDistance = (Datum) 0;
+		}
+		else
+		{
+			state->markIsNull = false;
+			state->markDistance = datumCopy(state->currDistance,
+											so->distanceTypeByVal,
+											so->distanceTypeLen);
+		}
+	}
 }
 
 /*
@@ -488,7 +554,13 @@ btmarkpos(IndexScanDesc scan)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
-	_bt_mark_current_position(&so->state);
+	_bt_mark_current_position(so, &so->state);
+
+	if (so->backwardState)
+	{
+		_bt_mark_current_position(so, so->backwardState);
+		so->markRightIsNearest = so->currRightIsNearest;
+	}
 }
 
 static void
@@ -537,6 +609,22 @@ _bt_restore_marked_position(IndexScanDesc scan, BTScanState state)
 			}
 		}
 	}
+
+	/*
+	 * For bidirectional nearest neighbor scan we also need to restore the
+	 * distance to the current item.
+	 */
+	if (so->useBidirectionalKnnScan)
+	{
+		if (!so->distanceTypeByVal && DatumGetPointer(state->currDistance))
+			pfree(DatumGetPointer(state->currDistance));
+
+		state->currIsNull = state->markIsNull;
+		state->currDistance = state->markIsNull ? (Datum) 0 :
+			datumCopy(state->markDistance,
+					  so->distanceTypeByVal,
+					  so->distanceTypeLen);
+	}
 }
 
 /*
@@ -558,7 +646,8 @@ btinitparallelscan(void *target)
 	BTParallelScanDesc bt_target = (BTParallelScanDesc) target;
 
 	SpinLockInit(&bt_target->btps_mutex);
-	bt_target->btps_scanPage = InvalidBlockNumber;
+	bt_target->btps_forwardScanPage = InvalidBlockNumber;
+	bt_target->btps_backwardScanPage = InvalidBlockNumber;
 	bt_target->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	ConditionVariableInit(&bt_target->btps_cv);
 }
@@ -583,7 +672,8 @@ btparallelrescan(IndexScanDesc scan)
 	 * consistency.
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
-	btscan->btps_scanPage = InvalidBlockNumber;
+	btscan->btps_forwardScanPage = InvalidBlockNumber;
+	btscan->btps_backwardScanPage = InvalidBlockNumber;
 	btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
 	SpinLockRelease(&btscan->btps_mutex);
 }
@@ -611,13 +701,14 @@ btparallelrescan(IndexScanDesc scan)
  * for first=false callers that require another primitive index scan.
  */
 bool
-_bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno, bool first)
+_bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno, bool first)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	bool		exit_loop = false;
 	bool		status = true;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
 
 	*pageno = P_NONE;
 
@@ -649,6 +740,10 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno, bool first)
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	scanPage = state == so->backwardState ?
+		&btscan->btps_backwardScanPage :
+		&btscan->btps_forwardScanPage;
+
 	while (1)
 	{
 		SpinLockAcquire(&btscan->btps_mutex);
@@ -690,7 +785,7 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno, bool first)
 			 * of advancing it to a new page!
 			 */
 			btscan->btps_pageStatus = BTPARALLEL_ADVANCING;
-			*pageno = btscan->btps_scanPage;
+			*pageno = *scanPage;
 			exit_loop = true;
 		}
 		SpinLockRelease(&btscan->btps_mutex);
@@ -715,19 +810,44 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno, bool first)
  * scan lands on scan_page).
  */
 void
-_bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
+_bt_parallel_release(IndexScanDesc scan, BTScanState state,
+					 BlockNumber scan_page)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
+	bool		status_changed = false;
+	bool		knnScan = so->useBidirectionalKnnScan;
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	Assert(state);
+	if (state != so->backwardState)
+	{
+		scanPage = &btscan->btps_forwardScanPage;
+		otherScanPage = &btscan->btps_backwardScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_backwardScanPage;
+		otherScanPage = &btscan->btps_forwardScanPage;
+	}
+
 	SpinLockAcquire(&btscan->btps_mutex);
-	btscan->btps_scanPage = scan_page;
-	btscan->btps_pageStatus = BTPARALLEL_IDLE;
+	*scanPage = scan_page;
+	/* switch to idle state only if both KNN pages are initialized */
+	if (!knnScan || *otherScanPage != InvalidBlockNumber)
+	{
+		btscan->btps_pageStatus = BTPARALLEL_IDLE;
+		status_changed = true;
+	}
 	SpinLockRelease(&btscan->btps_mutex);
-	ConditionVariableSignal(&btscan->btps_cv);
+
+	if (status_changed)
+		ConditionVariableSignal(&btscan->btps_cv);
 }
 
 /*
@@ -738,11 +858,15 @@ _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
  * advance to the next page.
  */
 void
-_bt_parallel_done(IndexScanDesc scan)
+_bt_parallel_done(IndexScanDesc scan, BTScanState state)
 {
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scanPage;
+	BlockNumber *otherScanPage;
 	bool		status_changed = false;
+	bool		knnScan = so->useBidirectionalKnnScan;
 
 	/* Do nothing, for non-parallel scans */
 	if (parallel_scan == NULL)
@@ -751,16 +875,43 @@ _bt_parallel_done(IndexScanDesc scan)
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	Assert(state);
+	if (state != so->backwardState)
+	{
+		scanPage = &btscan->btps_forwardScanPage;
+		otherScanPage = &btscan->btps_backwardScanPage;
+	}
+	else
+	{
+		scanPage = &btscan->btps_backwardScanPage;
+		otherScanPage = &btscan->btps_forwardScanPage;
+	}
+
 	/*
 	 * Mark the parallel scan as done, unless some other process did so
 	 * already
 	 */
 	SpinLockAcquire(&btscan->btps_mutex);
-	if (btscan->btps_pageStatus != BTPARALLEL_DONE)
+
+	Assert(!knnScan || btscan->btps_pageStatus == BTPARALLEL_ADVANCING);
+
+	*scanPage = P_NONE;
+	status_changed = true;
+
+	/* switch to "done" state only if both KNN scans are done */
+	if (!knnScan || *otherScanPage == P_NONE)
 	{
+		if (btscan->btps_pageStatus == BTPARALLEL_DONE)
+			status_changed = false;
+
 		btscan->btps_pageStatus = BTPARALLEL_DONE;
-		status_changed = true;
 	}
+	/* else switch to "idle" state only if both KNN scans are initialized */
+	else if (*otherScanPage != InvalidBlockNumber)
+		btscan->btps_pageStatus = BTPARALLEL_IDLE;
+	else
+		status_changed = false;
+
 	SpinLockRelease(&btscan->btps_mutex);
 
 	/* wake up all the workers associated with this parallel scan */
@@ -780,19 +931,30 @@ void
 _bt_parallel_primscan_schedule(IndexScanDesc scan, BlockNumber prev_scan_page)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState state = &so->state;
 	ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
 	BTParallelScanDesc btscan;
+	BlockNumber *scan_page;
 
 	Assert(so->numArrayKeys);
 
 	btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
 												  parallel_scan->ps_offset);
 
+	if (state != so->backwardState)
+	{
+		scan_page = &btscan->btps_forwardScanPage;
+	}
+	else
+	{
+		scan_page = &btscan->btps_backwardScanPage;
+	}
+
 	SpinLockAcquire(&btscan->btps_mutex);
-	if (btscan->btps_scanPage == prev_scan_page &&
+	if (*scan_page == prev_scan_page &&
 		btscan->btps_pageStatus == BTPARALLEL_IDLE)
 	{
-		btscan->btps_scanPage = InvalidBlockNumber;
+		*scan_page = InvalidBlockNumber;
 		btscan->btps_pageStatus = BTPARALLEL_NEED_PRIMSCAN;
 
 		/* Serialize scan's current array keys */
@@ -815,6 +977,12 @@ btrestrpos(IndexScanDesc scan)
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
 	_bt_restore_marked_position(scan, &so->state);
+
+	if (so->backwardState)
+	{
+		_bt_restore_marked_position(scan, so->backwardState);
+		so->currRightIsNearest = so->markRightIsNearest;
+	}
 }
 
 /*
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 368857d3aa5..b19802789bc 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -44,11 +44,13 @@ static bool _bt_steppage(IndexScanDesc scan, BTScanState state,
 						 ScanDirection dir);
 static bool _bt_readnextpage(IndexScanDesc scan, BTScanState state,
 							 BlockNumber blkno, ScanDirection dir);
-static bool _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno,
-								  ScanDirection dir);
+static bool _bt_parallel_readpage(IndexScanDesc scan, BTScanState state,
+								  BlockNumber blkno, ScanDirection dir);
 static Buffer _bt_walk_left(Relation rel, Buffer buf);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static inline void _bt_initialize_more_data(IndexScanDesc scan, BTScanState state, ScanDirection dir);
+static BTScanState _bt_alloc_knn_scan(IndexScanDesc scan);
+static bool _bt_start_knn_scan(IndexScanDesc scan, bool left, bool right);
 
 
 /*
@@ -890,9 +892,11 @@ _bt_return_current_item(IndexScanDesc scan, BTScanState state)
  */
 static bool
 _bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
-					OffsetNumber offnum)
+					OffsetNumber offnum, bool *readPageStatus)
 {
-	if (!_bt_readpage(scan, state, dir, offnum, true))
+	if (!(readPageStatus ?
+		  *readPageStatus :
+		  _bt_readpage(scan, state, dir, offnum, true)))
 	{
 		/*
 		 * There's no actually-matching data on this page.  Try to advance to
@@ -907,6 +911,173 @@ _bt_load_first_page(IndexScanDesc scan, BTScanState state, ScanDirection dir,
 	return true;
 }
 
+/*
+ *  _bt_calc_current_dist() -- Calculate distance from the current item
+ *		of the scan state to the target order-by ScanKey argument.
+ */
+static void
+_bt_calc_current_dist(IndexScanDesc scan, BTScanState state)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanPosItem *currItem = &state->currPos.items[state->currPos.itemIndex];
+	IndexTuple	itup = (IndexTuple) (state->currTuples + currItem->tupleOffset);
+	ScanKey		scankey = &scan->orderByData[0];
+	Datum		value;
+
+	value = index_getattr(itup, 1, scan->xs_itupdesc, &state->currIsNull);
+
+	if (state->currIsNull)
+		return;					/* NULL distance */
+
+	value = FunctionCall2Coll(&scankey->sk_func,
+							  scankey->sk_collation,
+							  value,
+							  scankey->sk_argument);
+
+	/* free previous distance value for by-ref types */
+	if (!so->distanceTypeByVal && DatumGetPointer(state->currDistance))
+		pfree(DatumGetPointer(state->currDistance));
+
+	state->currDistance = value;
+}
+
+/*
+ *  _bt_compare_current_dist() -- Compare current distances of the left and
+ *right scan states.
+ *
+ *  NULL distances are considered to be greater than any non-NULL distances.
+ *
+ *  Returns true if right distance is lesser than left, otherwise false.
+ */
+static bool
+_bt_compare_current_dist(BTScanOpaque so, BTScanState rstate, BTScanState lstate)
+{
+	if (lstate->currIsNull)
+		return true;			/* non-NULL < NULL */
+
+	if (rstate->currIsNull)
+		return false;			/* NULL > non-NULL */
+
+	return DatumGetBool(FunctionCall2Coll(&so->distanceCmpProc,
+										  InvalidOid,	/* XXX collation for
+														 * distance comparison */
+										  rstate->currDistance,
+										  lstate->currDistance));
+}
+
+/*
+ * _bt_alloc_knn_backward_scan() -- Allocate additional backward scan state for KNN.
+ */
+static BTScanState
+_bt_alloc_knn_scan(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState lstate = (BTScanState) palloc(sizeof(BTScanStateData));
+
+	_bt_allocate_tuple_workspaces(lstate);
+
+	if (!scan->xs_want_itup)
+	{
+		/* We need to request index tuples for distance comparison. */
+		scan->xs_want_itup = true;
+		_bt_allocate_tuple_workspaces(&so->state);
+	}
+
+	BTScanPosInvalidate(lstate->currPos);
+	lstate->currPos.moreLeft = false;
+	lstate->currPos.moreRight = false;
+	BTScanPosInvalidate(lstate->markPos);
+	lstate->markItemIndex = -1;
+	lstate->killedItems = NULL;
+	lstate->numKilled = 0;
+	lstate->currDistance = (Datum) 0;
+	lstate->markDistance = (Datum) 0;
+
+	return so->backwardState = lstate;
+}
+
+static bool
+_bt_start_knn_scan(IndexScanDesc scan, bool left, bool right)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState rstate;			/* right (forward) main scan state */
+	BTScanState lstate;			/* additional left (backward) KNN scan state */
+
+	if (!left && !right)
+		return false;			/* empty result */
+
+	rstate = &so->state;
+	lstate = so->backwardState;
+
+	if (left && right)
+	{
+		/*
+		 * We have found items in both scan directions, determine nearest item
+		 * to return.
+		 */
+		_bt_calc_current_dist(scan, rstate);
+		_bt_calc_current_dist(scan, lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+
+		/*
+		 * 'right' flag determines the selected scan direction; right
+		 * direction is selected if the right item is nearest.
+		 */
+		right = so->currRightIsNearest;
+	}
+
+	/* Return current item of the selected scan direction. */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
+/*
+ * _bt_init_knn_scan() -- Init additional scan state for KNN search.
+ *
+ * Caller must pin and read-lock scan->state.currPos.buf buffer.
+ *
+ * If empty result was found returned false.
+ * Otherwise prepared current item, and returned true.
+ */
+static bool
+_bt_init_knn_scan(IndexScanDesc scan, OffsetNumber offnum)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState rstate = &so->state;	/* right (forward) main scan state */
+	BTScanState lstate;			/* additional left (backward) KNN scan state */
+	Buffer		buf = rstate->currPos.buf;
+	bool		left,
+				right;
+	ScanDirection rdir = ForwardScanDirection;
+	ScanDirection ldir = BackwardScanDirection;
+	OffsetNumber roffnum = offnum;
+	OffsetNumber loffnum = OffsetNumberPrev(offnum);
+
+	lstate = _bt_alloc_knn_scan(scan);
+
+	/* Bump pin and lock count before BTScanPosData copying. */
+	IncrBufferRefCount(buf);
+	LockBuffer(buf, BT_READ);
+
+	memcpy(&lstate->currPos, &rstate->currPos, sizeof(BTScanPosData));
+	lstate->currPos.moreLeft = true;
+	lstate->currPos.moreRight = false;
+
+	/*
+	 * Load first pages from the both scans.
+	 *
+	 * _bt_load_first_page(right) can step to next page, and then
+	 * _bt_parallel_seize() will deadlock if the left page number is not yet
+	 * initialized in BTParallelScanDesc.  So we must first read the left page
+	 * using _bt_readpage(), and _bt_parallel_release() which is called inside
+	 * will save the next page number in BTParallelScanDesc.
+	 */
+	left = _bt_readpage(scan, lstate, ldir, loffnum, true);
+	right = _bt_load_first_page(scan, rstate, rdir, roffnum, NULL);
+	left = _bt_load_first_page(scan, lstate, ldir, loffnum, &left);
+
+	return _bt_start_knn_scan(scan, left, right);
+}
+
 /*
  *	_bt_first() -- Find the first item in a scan.
  *
@@ -962,10 +1133,19 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 */
 	if (!so->qual_ok)
 	{
-		_bt_parallel_done(scan);
+		_bt_parallel_done(scan, &so->state);
 		return false;
 	}
 
+	if (scan->numberOfOrderBys > 0)
+	{
+		if (so->useBidirectionalKnnScan)
+			_bt_init_distance_comparison(scan);
+		else if (so->scanDirection != NoMovementScanDirection)
+			/* use selected KNN scan direction */
+			dir = so->scanDirection;
+	}
+
 	/*
 	 * For parallel scans, get the starting page from shared state. If the
 	 * scan has not started, proceed to find out first leaf page in the usual
@@ -978,7 +1158,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 */
 	if (scan->parallel_scan != NULL)
 	{
-		status = _bt_parallel_seize(scan, &blkno, true);
+		status = _bt_parallel_seize(scan, &so->state, &blkno, true);
 
 		/*
 		 * Initialize arrays (when _bt_parallel_seize didn't already set up
@@ -989,16 +1169,47 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 
 		if (!status)
 			return false;
-		else if (blkno == P_NONE)
-		{
-			_bt_parallel_done(scan);
-			return false;
-		}
 		else if (blkno != InvalidBlockNumber)
 		{
-			if (!_bt_parallel_readpage(scan, blkno, dir))
-				return false;
-			goto readcomplete;
+			bool		knn = so->useBidirectionalKnnScan;
+			bool		right;
+			bool		left;
+
+			if (knn)
+				_bt_alloc_knn_scan(scan);
+
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, &so->state);
+				right = false;
+			}
+			else
+				right = _bt_parallel_readpage(scan, &so->state, blkno,
+											  knn ? ForwardScanDirection : dir);
+
+			if (!knn)
+				return right && _bt_return_current_item(scan, &so->state);
+
+			/* seize additional backward KNN scan */
+			left = _bt_parallel_seize(scan, so->backwardState, &blkno, true);
+
+			if (left)
+			{
+				if (blkno == P_NONE)
+				{
+					_bt_parallel_done(scan, so->backwardState);
+					left = false;
+				}
+				else
+				{
+					/* backward scan should be already initialized */
+					Assert(blkno != InvalidBlockNumber);
+					left = _bt_parallel_readpage(scan, so->backwardState, blkno,
+												 BackwardScanDirection);
+				}
+			}
+
+			return _bt_start_knn_scan(scan, left, right);
 		}
 	}
 	else if (so->numArrayKeys && !so->needPrimScan)
@@ -1070,14 +1281,20 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 * need to be kept in sync.
 	 *----------
 	 */
-	strat_total = BTEqualStrategyNumber;
-	if (so->numberOfKeys > 0)
+	if (so->useBidirectionalKnnScan)
+	{
+		keysz = _bt_init_knn_start_keys(scan, startKeys, notnullkeys);
+		strat_total = BTNearestStrategyNumber;
+	}
+	else if (so->numberOfKeys > 0)
 	{
 		AttrNumber	curattr;
 		ScanKey		chosen;
 		ScanKey		impliesNN;
 		ScanKey		cur;
 
+		strat_total = BTEqualStrategyNumber;
+
 		/*
 		 * chosen is the so-far-chosen key for the current attribute, if any.
 		 * We don't cast the decision in stone until we reach keys for the
@@ -1211,7 +1428,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 		if (!match)
 		{
 			/* No match, so mark (parallel) scan finished */
-			_bt_parallel_done(scan);
+			_bt_parallel_done(scan, &so->state);
 		}
 
 		return match;
@@ -1247,7 +1464,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			Assert(subkey->sk_flags & SK_ROW_MEMBER);
 			if (subkey->sk_flags & SK_ISNULL)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, &so->state);
 				return false;
 			}
 			memcpy(inskey.scankeys + i, subkey, sizeof(ScanKeyData));
@@ -1412,6 +1629,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			break;
 
 		case BTGreaterEqualStrategyNumber:
+		case BTMaxStrategyNumber:
 
 			/*
 			 * Find first item >= scankey
@@ -1469,7 +1687,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 			 * Mark parallel scan as done, so that all the workers can finish
 			 * their scan.
 			 */
-			_bt_parallel_done(scan);
+			_bt_parallel_done(scan, &so->state);
 			BTScanPosInvalidate(*currPos);
 			return false;
 		}
@@ -1503,17 +1721,22 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	 * for the page.  For example, when inskey is both < the leaf page's high
 	 * key and > all of its non-pivot tuples, offnum will be "maxoff + 1".
 	 */
-	if (!_bt_load_first_page(scan, &so->state, dir, offnum))
-		return false;
+	if (strat_total == BTNearestStrategyNumber)
+		return _bt_init_knn_scan(scan, offnum);
+
+	if (!_bt_load_first_page(scan, &so->state, dir, offnum, NULL))
+		return false;			/* empty result */
 
-readcomplete:
 	/* OK, currPos->itemIndex says what to return */
 	return _bt_return_current_item(scan, &so->state);
 }
 
 /*
- *	Advance to next tuple on current page; or if there's no more,
- *	try to step to the next page with data.
+ *	_bt_next_item() -- Advance to next tuple on current page;
+ *		or if there's no more, try to step to the next page with data.
+ *
+ *	If there are any matching records in the given direction true is
+ *	returned, otherwise false.
  */
 static bool
 _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
@@ -1532,6 +1755,51 @@ _bt_next_item(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 	return _bt_steppage(scan, state, dir);
 }
 
+/*
+ *	_bt_next_nearest() -- Return next nearest item from bidirectional KNN scan.
+ */
+static bool
+_bt_next_nearest(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	BTScanState rstate = &so->state;
+	BTScanState lstate = so->backwardState;
+	bool		right = BTScanPosIsValid(rstate->currPos);
+	bool		left = BTScanPosIsValid(lstate->currPos);
+	bool		advanceRight;
+
+	if (right && left)
+		advanceRight = so->currRightIsNearest;
+	else if (right)
+		advanceRight = true;
+	else if (left)
+		advanceRight = false;
+	else
+		return false;			/* end of the scan */
+
+	if (advanceRight)
+		right = _bt_next_item(scan, rstate, ForwardScanDirection);
+	else
+		left = _bt_next_item(scan, lstate, BackwardScanDirection);
+
+	if (!left && !right)
+		return false;			/* end of the scan */
+
+	if (left && right)
+	{
+		/*
+		 * If there are items in both scans we must recalculate distance in
+		 * the advanced scan.
+		 */
+		_bt_calc_current_dist(scan, advanceRight ? rstate : lstate);
+		so->currRightIsNearest = _bt_compare_current_dist(so, rstate, lstate);
+		right = so->currRightIsNearest;
+	}
+
+	/* return nearest item */
+	return _bt_return_current_item(scan, right ? rstate : lstate);
+}
+
 /*
  *	_bt_next() -- Get the next item in a scan.
  *
@@ -1551,6 +1819,10 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
+	if (so->backwardState)
+		/* return next neareset item from KNN scan */
+		return _bt_next_nearest(scan);
+
 	if (!_bt_next_item(scan, &so->state, dir))
 		return false;
 
@@ -1618,7 +1890,7 @@ _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir, OffsetNum
 		else
 			pstate.prev_scan_page = BufferGetBlockNumber(pos->buf);
 
-		_bt_parallel_release(scan, pstate.prev_scan_page);
+		_bt_parallel_release(scan, state, pstate.prev_scan_page);
 	}
 
 	indnatts = IndexRelationGetNumberOfAttributes(scan->indexRelation);
@@ -1709,7 +1981,7 @@ _bt_readpage(IndexScanDesc scan, BTScanState state, ScanDirection dir, OffsetNum
 	 * required < or <= strategy scan keys) during the precheck, we can safely
 	 * assume that this must also be true of all earlier tuples from the page.
 	 */
-	if (!firstPage && !so->scanBehind && minoff < maxoff)
+	if (!so->useBidirectionalKnnScan && !firstPage && !so->scanBehind && minoff < maxoff)
 	{
 		ItemId		iid;
 		IndexTuple	itup;
@@ -2136,7 +2408,7 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the next block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno, false);
+			status = _bt_parallel_seize(scan, state, &blkno, false);
 			if (!status)
 			{
 				/* release the previous buffer, if pinned */
@@ -2168,13 +2440,19 @@ _bt_steppage(IndexScanDesc scan, BTScanState state, ScanDirection dir)
 			 * Seize the scan to get the current block number; if the scan has
 			 * ended already, bail out.
 			 */
-			status = _bt_parallel_seize(scan, &blkno, false);
+			status = _bt_parallel_seize(scan, state, &blkno, false);
 			BTScanPosUnpinIfPinned(*currPos);
 			if (!status)
 			{
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
+			if (blkno == P_NONE)
+			{
+				_bt_parallel_done(scan, state);
+				BTScanPosInvalidate(*currPos);
+				return false;
+			}
 		}
 		else
 		{
@@ -2224,7 +2502,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			 */
 			if (blkno == P_NONE || !currPos->moreRight)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -2246,14 +2524,14 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, opaque->btpo_next);
+				_bt_parallel_release(scan, state, opaque->btpo_next);
 			}
 
 			/* nope, keep going */
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno, false);
+				status = _bt_parallel_seize(scan, state, &blkno, false);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -2303,7 +2581,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (!currPos->moreLeft)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -2314,7 +2592,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			/* if we're physically at end of index, return failure */
 			if (currPos->buf == InvalidBuffer)
 			{
-				_bt_parallel_done(scan);
+				_bt_parallel_done(scan, state);
 				BTScanPosInvalidate(*currPos);
 				return false;
 			}
@@ -2337,7 +2615,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			else if (scan->parallel_scan != NULL)
 			{
 				/* allow next page be processed by parallel worker */
-				_bt_parallel_release(scan, BufferGetBlockNumber(currPos->buf));
+				_bt_parallel_release(scan, state, BufferGetBlockNumber(currPos->buf));
 			}
 
 			/*
@@ -2349,7 +2627,7 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
 			if (scan->parallel_scan != NULL)
 			{
 				_bt_relbuf(rel, currPos->buf);
-				status = _bt_parallel_seize(scan, &blkno, false);
+				status = _bt_parallel_seize(scan, state, &blkno, false);
 				if (!status)
 				{
 					BTScanPosInvalidate(*currPos);
@@ -2370,19 +2648,20 @@ _bt_readnextpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
  * indicate success.
  */
 static bool
-_bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
+_bt_parallel_readpage(IndexScanDesc scan, BTScanState state, BlockNumber blkno,
+					  ScanDirection dir)
 {
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
 	Assert(!so->needPrimScan);
 
-	_bt_initialize_more_data(scan, &so->state, dir);
+	_bt_initialize_more_data(scan, state, dir);
 
-	if (!_bt_readnextpage(scan, &so->state, blkno, dir))
+	if (!_bt_readnextpage(scan, state, blkno, dir))
 		return false;
 
-	/* We have at least one item to return as scan's next item */
-	_bt_drop_lock_and_maybe_pin(scan, &so->state.currPos);
+	/* Drop the lock, and maybe the pin, on the current page */
+	_bt_drop_lock_and_maybe_pin(scan, &state->currPos);
 
 	return true;
 }
@@ -2653,7 +2932,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 
 	_bt_initialize_more_data(scan, &so->state, dir);
 
-	if (!_bt_load_first_page(scan, &so->state, dir, start))
+	if (!_bt_load_first_page(scan, &so->state, dir, start, NULL))
 		return false;
 
 	/* OK, currPos->itemIndex says what to return */
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index a82b2638d82..4f825325174 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -20,6 +20,7 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "catalog/pg_amop.h"
 #include "commands/progress.h"
 #include "lib/qunique.h"
 #include "miscadmin.h"
@@ -28,6 +29,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 
 #define LOOK_AHEAD_REQUIRED_RECHECKS 	3
 #define LOOK_AHEAD_DEFAULT_DISTANCE 	5
@@ -35,6 +37,9 @@
 typedef struct BTSortArrayContext
 {
 	FmgrInfo   *sortproc;
+	FmgrInfo	distflinfo;
+	FmgrInfo	distcmpflinfo;
+	ScanKey		distkey;
 	Oid			collation;
 	bool		reverse;
 } BTSortArrayContext;
@@ -51,7 +56,7 @@ static void _bt_setup_array_cmp(IndexScanDesc scan, ScanKey skey, Oid elemtype,
 static Datum _bt_find_extreme_element(IndexScanDesc scan, ScanKey skey,
 									  Oid elemtype, StrategyNumber strat,
 									  Datum *elems, int nelems);
-static int	_bt_sort_array_elements(ScanKey skey, FmgrInfo *sortproc,
+static int	_bt_sort_array_elements(IndexScanDesc scan, ScanKey skey, FmgrInfo *sortproc,
 									bool reverse, Datum *elems, int nelems);
 static bool _bt_merge_arrays(IndexScanDesc scan, ScanKey skey,
 							 FmgrInfo *sortproc, bool reverse,
@@ -102,6 +107,11 @@ static void _bt_checkkeys_look_ahead(IndexScanDesc scan, BTReadPageState *pstate
 									 int tupnatts, TupleDesc tupdesc);
 static int	_bt_keep_natts(Relation rel, IndexTuple lastleft,
 						   IndexTuple firstright, BTScanInsert itup_key);
+static inline StrategyNumber _bt_select_knn_strategy_for_key(IndexScanDesc scan,
+															 ScanKey cond);
+static void _bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily,
+									  Oid leftargtype, FmgrInfo *finfo,
+									  int16 *typlen, bool *typbyval);
 
 
 /*
@@ -441,7 +451,7 @@ _bt_preprocess_array_keys(IndexScanDesc scan)
 		 * the index's key space.
 		 */
 		reverse = (indoption[cur->sk_attno - 1] & INDOPTION_DESC) != 0;
-		num_elems = _bt_sort_array_elements(cur, sortprocp, reverse,
+		num_elems = _bt_sort_array_elements(scan, cur, sortprocp, reverse,
 											elem_values, num_nonnulls);
 
 		if (origarrayatt == cur->sk_attno)
@@ -846,18 +856,77 @@ _bt_find_extreme_element(IndexScanDesc scan, ScanKey skey, Oid elemtype,
  * we sort in descending order.
  */
 static int
-_bt_sort_array_elements(ScanKey skey, FmgrInfo *sortproc, bool reverse,
+_bt_sort_array_elements(IndexScanDesc scan, ScanKey skey, FmgrInfo *sortproc, bool reverse,
 						Datum *elems, int nelems)
 {
+	Relation	rel = scan->indexRelation;
+	Oid			elemtype;
+	Oid			opfamily;
 	BTSortArrayContext cxt;
 
 	if (nelems <= 1)
 		return nelems;			/* no work to do */
 
+	/*
+	 * Determine the nominal datatype of the array elements.  We have to
+	 * support the convention that sk_subtype == InvalidOid means the opclass
+	 * input type; this is a hack to simplify life for ScanKeyInit().
+	 */
+	elemtype = skey->sk_subtype;
+	if (elemtype == InvalidOid)
+		elemtype = rel->rd_opcintype[skey->sk_attno - 1];
+
+	opfamily = rel->rd_opfamily[skey->sk_attno - 1];
+
+	if (scan->numberOfOrderBys <= 0 ||
+		scan->orderByData[0].sk_attno != skey->sk_attno)
+	{
+		cxt.distkey = NULL;
+		cxt.reverse = reverse;
+	}
+	else
+	{
+		/* Init procedures for distance calculation and comparison. */
+		ScanKey		distkey = &scan->orderByData[0];
+		ScanKeyData distkey2;
+		Oid			disttype = distkey->sk_subtype;
+		Oid			distopr;
+		RegProcedure distproc;
+
+		if (!OidIsValid(disttype))
+			disttype = rel->rd_opcintype[skey->sk_attno - 1];
+
+		/* Lookup distance operator in index column's operator family. */
+		distopr = get_opfamily_member(opfamily,
+									  elemtype,
+									  disttype,
+									  distkey->sk_strategy);
+
+		if (!OidIsValid(distopr))
+			elog(ERROR, "missing operator (%u,%u) for strategy %d in opfamily %u",
+				 elemtype, disttype, BTMaxStrategyNumber, opfamily);
+
+		distproc = get_opcode(distopr);
+
+		if (!RegProcedureIsValid(distproc))
+			elog(ERROR, "missing code for operator %u", distopr);
+
+		fmgr_info(distproc, &cxt.distflinfo);
+
+		distkey2 = *distkey;
+		fmgr_info_copy(&distkey2.sk_func, &cxt.distflinfo, CurrentMemoryContext);
+		distkey2.sk_subtype = disttype;
+
+		_bt_get_distance_cmp_proc(&distkey2, opfamily, elemtype,
+								  &cxt.distcmpflinfo, NULL, NULL);
+
+		cxt.distkey = distkey;
+		cxt.reverse = false;	/* supported only ascending ordering */
+	}
+
 	/* Sort the array elements */
 	cxt.sortproc = sortproc;
 	cxt.collation = skey->sk_collation;
-	cxt.reverse = reverse;
 	qsort_arg(elems, nelems, sizeof(Datum),
 			  _bt_compare_array_elements, &cxt);
 
@@ -930,6 +999,7 @@ _bt_merge_arrays(IndexScanDesc scan, ScanKey skey, FmgrInfo *sortproc,
 	cxt.sortproc = mergeproc;
 	cxt.collation = skey->sk_collation;
 	cxt.reverse = reverse;
+	cxt.distkey = NULL;
 
 	for (int i = 0, j = 0; i < nelems_orig_start && j < nelems_next;)
 	{
@@ -1103,6 +1173,24 @@ _bt_compare_array_elements(const void *a, const void *b, void *arg)
 	BTSortArrayContext *cxt = (BTSortArrayContext *) arg;
 	int32		compare;
 
+	if (cxt->distkey)
+	{
+		Datum		dista = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  da,
+											  cxt->distkey->sk_argument);
+		Datum		distb = FunctionCall2Coll(&cxt->distflinfo,
+											  cxt->collation,
+											  db,
+											  cxt->distkey->sk_argument);
+		bool		cmp = DatumGetBool(FunctionCall2Coll(&cxt->distcmpflinfo,
+														 cxt->collation,
+														 dista,
+														 distb));
+
+		return cmp ? -1 : 1;
+	}
+
 	compare = DatumGetInt32(FunctionCall2Coll(cxt->sortproc,
 											  cxt->collation,
 											  da, db));
@@ -1721,7 +1809,7 @@ _bt_start_prim_scan(IndexScanDesc scan, ScanDirection dir)
 
 	/* The top-level index scan ran out of tuples in this scan direction */
 	if (scan->parallel_scan != NULL)
-		_bt_parallel_done(scan);
+		_bt_parallel_done(scan, &so->state);
 
 	return false;
 }
@@ -2456,6 +2544,69 @@ end_toplevel_scan:
 	/* Caller's tuple doesn't match any qual */
 	return false;
 }
+/*
+ * _bt_emit_scan_key() -- Emit one prepared scan key
+ *
+ * Push the scan key into the so->keyData[] array, and then mark it if it is
+ * required.  Also update selected kNN strategy.
+ */
+static void
+_bt_emit_scan_key(IndexScanDesc scan, ScanKey skey, int numberOfEqualCols)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	ScanKey		outkey = &so->keyData[so->numberOfKeys++];
+
+	memcpy(outkey, skey, sizeof(ScanKeyData));
+
+	/*
+	 * We can mark the qual as required (possibly only in one direction) if
+	 * all attrs before this one had "=".
+	 */
+	if (outkey->sk_attno - 1 == numberOfEqualCols)
+		_bt_mark_scankey_required(outkey);
+
+	/* Update kNN strategy if it is not already selected. */
+	if (so->useBidirectionalKnnScan)
+	{
+		switch (_bt_select_knn_strategy_for_key(scan, outkey))
+		{
+			case BTLessStrategyNumber:
+			case BTLessEqualStrategyNumber:
+
+				/*
+				 * Ordering key argument is greater than all values in scan
+				 * range, select backward scan direction.
+				 */
+				so->scanDirection = BackwardScanDirection;
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BTEqualStrategyNumber:
+				/* Use default unidirectional scan direction. */
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+
+				/*
+				 * Ordering key argument is lesser than all values in scan
+				 * range, select forward scan direction.
+				 */
+				so->scanDirection = ForwardScanDirection;
+				so->useBidirectionalKnnScan = false;
+				break;
+
+			case BTMaxStrategyNumber:
+
+				/*
+				 * Ordering key argument falls into scan range, keep using
+				 * bidirectional scan.
+				 */
+				break;
+		}
+	}
+}
 
 /*
  *	_bt_preprocess_keys() -- Preprocess scan keys
@@ -2548,12 +2699,10 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 	int			numberOfKeys = scan->numberOfKeys;
 	int16	   *indoption = scan->indexRelation->rd_indoption;
-	int			new_numberOfKeys;
 	int			numberOfEqualCols;
 	ScanKey		inkeys;
-	ScanKey		outkeys;
 	ScanKey		cur;
-	BTScanKeyPreproc xform[BTMaxStrategyNumber];
+	BTScanKeyPreproc xform[BTMaxSearchStrategyNumber];
 	bool		test_result;
 	int			i,
 				j;
@@ -2576,6 +2725,25 @@ _bt_preprocess_keys(IndexScanDesc scan)
 		return;
 	}
 
+	if (scan->numberOfOrderBys > 0)
+	{
+		ScanKey		ord = scan->orderByData;
+
+		if (scan->numberOfOrderBys > 1 || ord->sk_attno != 1)
+			/* it should not happen, see btmatchorderby() */
+			elog(ERROR, "only one btree ordering operator "
+				 "for the first index column is supported");
+
+		Assert(ord->sk_strategy == BTMaxStrategyNumber);
+
+		/* use bidirectional kNN scan by default */
+		so->useBidirectionalKnnScan = true;
+	}
+	else
+	{
+		so->useBidirectionalKnnScan = false;
+	}
+
 	/* initialize result variables */
 	so->qual_ok = true;
 	so->numberOfKeys = 0;
@@ -2607,7 +2775,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
 	else
 		inkeys = scan->keyData;
 
-	outkeys = so->keyData;
 	cur = &inkeys[0];
 	/* we check that input keys are correctly ordered */
 	if (cur->sk_attno < 1)
@@ -2619,11 +2786,9 @@ _bt_preprocess_keys(IndexScanDesc scan)
 		/* Apply indoption to scankey (might change sk_strategy!) */
 		if (!_bt_fix_scankey_strategy(cur, indoption))
 			so->qual_ok = false;
-		memcpy(outkeys, cur, sizeof(ScanKeyData));
-		so->numberOfKeys = 1;
-		/* We can mark the qual as required if it's for first index col */
-		if (cur->sk_attno == 1)
-			_bt_mark_scankey_required(outkeys);
+
+		_bt_emit_scan_key(scan, cur, 0);
+
 		if (arrayKeyData)
 		{
 			/*
@@ -2636,14 +2801,12 @@ _bt_preprocess_keys(IndexScanDesc scan)
 				   (so->arrayKeys[0].scan_key == 0 &&
 					OidIsValid(so->orderProcs[0].fn_oid)));
 		}
-
 		return;
 	}
 
 	/*
 	 * Otherwise, do the full set of pushups.
 	 */
-	new_numberOfKeys = 0;
 	numberOfEqualCols = 0;
 
 	/*
@@ -2716,7 +2879,7 @@ _bt_preprocess_keys(IndexScanDesc scan)
 					Assert(OidIsValid(orderproc->fn_oid));
 				}
 
-				for (j = BTMaxStrategyNumber; --j >= 0;)
+				for (j = BTMaxSearchStrategyNumber; --j >= 0;)
 				{
 					ScanKey		chk = xform[j].skey;
 
@@ -2786,21 +2949,17 @@ _bt_preprocess_keys(IndexScanDesc scan)
 			}
 
 			/*
-			 * Emit the cleaned-up keys into the outkeys[] array, and then
+			 * Emit the cleaned-up keys into the so->keyData[] array, and then
 			 * mark them if they are required.  They are required (possibly
 			 * only in one direction) if all attrs before this one had "=".
 			 */
-			for (j = BTMaxStrategyNumber; --j >= 0;)
+			for (j = BTMaxSearchStrategyNumber; --j >= 0;)
 			{
 				if (xform[j].skey)
 				{
-					ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-					memcpy(outkey, xform[j].skey, sizeof(ScanKeyData));
+					_bt_emit_scan_key(scan, xform[j].skey, priorNumberOfEqualCols);
 					if (arrayKeyData)
-						keyDataMap[new_numberOfKeys - 1] = xform[j].ikey;
-					if (priorNumberOfEqualCols == attno - 1)
-						_bt_mark_scankey_required(outkey);
+						keyDataMap[so->numberOfKeys - 1] = xform[j].ikey;
 				}
 			}
 
@@ -2821,19 +2980,16 @@ _bt_preprocess_keys(IndexScanDesc scan)
 		/* if row comparison, push it directly to the output array */
 		if (cur->sk_flags & SK_ROW_HEADER)
 		{
-			ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-			memcpy(outkey, cur, sizeof(ScanKeyData));
+			_bt_emit_scan_key(scan, cur, numberOfEqualCols);
 			if (arrayKeyData)
-				keyDataMap[new_numberOfKeys - 1] = i;
-			if (numberOfEqualCols == attno - 1)
-				_bt_mark_scankey_required(outkey);
+				keyDataMap[so->numberOfKeys - 1] = i;
 
 			/*
 			 * We don't support RowCompare using equality; such a qual would
 			 * mess up the numberOfEqualCols tracking.
 			 */
 			Assert(j != (BTEqualStrategyNumber - 1));
+
 			continue;
 		}
 
@@ -2959,22 +3115,15 @@ _bt_preprocess_keys(IndexScanDesc scan)
 				 * even with incomplete opfamilies.  _bt_advance_array_keys
 				 * depends on this.
 				 */
-				ScanKey		outkey = &outkeys[new_numberOfKeys++];
-
-				memcpy(outkey, xform[j].skey, sizeof(ScanKeyData));
+				_bt_emit_scan_key(scan, xform[j].skey, numberOfEqualCols);
 				if (arrayKeyData)
-					keyDataMap[new_numberOfKeys - 1] = xform[j].ikey;
-				if (numberOfEqualCols == attno - 1)
-					_bt_mark_scankey_required(outkey);
+					keyDataMap[so->numberOfKeys - 1] = xform[j].ikey;
 				xform[j].skey = cur;
 				xform[j].ikey = i;
 				xform[j].arrayidx = arrayidx;
 			}
 		}
 	}
-
-	so->numberOfKeys = new_numberOfKeys;
-
 	/*
 	 * Now that we've built a temporary mapping from so->keyData[] (output
 	 * scan keys) to scan->keyData[] (input scan keys), fix array->scan_key
@@ -4583,6 +4732,39 @@ btproperty(Oid index_oid, int attno,
 			*res = true;
 			return true;
 
+		case AMPROP_DISTANCE_ORDERABLE:
+			{
+				Oid			opclass,
+							opfamily,
+							opcindtype;
+
+				/* answer only for columns, not AM or whole index */
+				if (attno == 0)
+					return false;
+
+				opclass = get_index_column_opclass(index_oid, attno);
+
+				if (!OidIsValid(opclass))
+				{
+					*res = false;	/* non-key attribute */
+					return true;
+				}
+
+				if (!get_opclass_opfamily_and_input_type(opclass,
+														 &opfamily, &opcindtype))
+				{
+					*isnull = true;
+					return true;
+				}
+
+				*res = SearchSysCacheExists(AMOPSTRATEGY,
+											ObjectIdGetDatum(opfamily),
+											ObjectIdGetDatum(opcindtype),
+											ObjectIdGetDatum(opcindtype),
+											Int16GetDatum(BTMaxStrategyNumber));
+				return true;
+			}
+
 		default:
 			return false;		/* punt to generic code */
 	}
@@ -5179,3 +5361,216 @@ _bt_allocate_tuple_workspaces(BTScanState state)
 	state->currTuples = (char *) palloc(BLCKSZ * 2);
 	state->markTuples = state->currTuples + BLCKSZ;
 }
+
+static bool
+_bt_compare_row_key_with_ordering_key(ScanKey row, ScanKey ord, bool *result)
+{
+	ScanKey		subkey = (ScanKey) DatumGetPointer(row->sk_argument);
+	int32		cmpresult;
+
+	Assert(subkey->sk_attno == 1);
+	Assert(subkey->sk_flags & SK_ROW_MEMBER);
+
+	if (subkey->sk_flags & SK_ISNULL)
+		return false;
+
+	/* Perform the test --- three-way comparison not bool operator */
+	cmpresult = DatumGetInt32(FunctionCall2Coll(&subkey->sk_func,
+												subkey->sk_collation,
+												ord->sk_argument,
+												subkey->sk_argument));
+
+	if (subkey->sk_flags & SK_BT_DESC)
+		cmpresult = -cmpresult;
+
+	/*
+	 * At this point cmpresult indicates the overall result of the row
+	 * comparison, and subkey points to the deciding column (or the last
+	 * column if the result is "=").
+	 */
+	switch (subkey->sk_strategy)
+	{
+			/* EQ and NE cases aren't allowed here */
+		case BTLessStrategyNumber:
+			*result = cmpresult < 0;
+			break;
+		case BTLessEqualStrategyNumber:
+			*result = cmpresult <= 0;
+			break;
+		case BTGreaterEqualStrategyNumber:
+			*result = cmpresult >= 0;
+			break;
+		case BTGreaterStrategyNumber:
+			*result = cmpresult > 0;
+			break;
+		default:
+			elog(ERROR, "unrecognized RowCompareType: %d",
+				 (int) subkey->sk_strategy);
+			*result = false;	/* keep compiler quiet */
+	}
+
+	return true;
+}
+
+/*
+ * _bt_select_knn_strategy_for_key() -- Determine which kNN scan strategy to use:
+ *		bidirectional or unidirectional.  We are checking here if the
+ *		ordering scankey argument falls into the scan range: if it falls
+ *		we must use bidirectional scan, otherwise we use unidirectional.
+ *
+ *	Returns BTMaxStrategyNumber for bidirectional scan or
+ *	strategy number of non-matched scankey for unidirectional.
+ */
+static inline StrategyNumber
+_bt_select_knn_strategy_for_key(IndexScanDesc scan, ScanKey cond)
+{
+	ScanKey		ord = scan->orderByData;
+	bool		result;
+
+	/* only interesting in the first index attribute */
+	if (cond->sk_attno != 1)
+		return BTMaxStrategyNumber;
+
+	if (cond->sk_strategy == BTEqualStrategyNumber)
+		/* always use simple unidirectional scan for equals operators */
+		return BTEqualStrategyNumber;
+
+	if (cond->sk_flags & SK_ROW_HEADER)
+	{
+		if (!_bt_compare_row_key_with_ordering_key(cond, ord, &result))
+			return BTEqualStrategyNumber;	/* ROW(fist_index_attr, ...) IS
+											 * NULL */
+	}
+	else
+	{
+		if (!_bt_compare_scankey_args(scan, cond, ord, cond, NULL, NULL, &result))
+			elog(ERROR, "could not compare ordering key");
+	}
+
+	if (!result)
+
+		/*
+		 * Ordering scankey argument is out of scan range, use unidirectional
+		 * scan.
+		 */
+		return cond->sk_strategy;
+
+	return BTMaxStrategyNumber;
+}
+
+int
+_bt_init_knn_start_keys(IndexScanDesc scan, ScanKey *startKeys, ScanKey bufKeys)
+{
+	ScanKey		ord = scan->orderByData;
+	int			indopt = scan->indexRelation->rd_indoption[ord->sk_attno - 1];
+	int			flags = (indopt << SK_BT_INDOPTION_SHIFT) |
+	SK_ORDER_BY |
+	SK_SEARCHNULL;				/* only for invalid procedure oid, see assert
+								 * in ScanKeyEntryInitialize() */
+	int			keysCount = 0;
+
+	/* Init btree search key with ordering key argument. */
+	ScanKeyEntryInitialize(&bufKeys[0],
+						   flags,
+						   ord->sk_attno,
+						   BTMaxStrategyNumber,
+						   ord->sk_subtype,
+						   ord->sk_collation,
+						   InvalidOid,
+						   ord->sk_argument);
+
+	startKeys[keysCount++] = &bufKeys[0];
+
+	return keysCount;
+}
+
+static Oid
+_bt_get_sortfamily_for_opfamily_op(Oid opfamily, Oid lefttype, Oid righttype,
+								   StrategyNumber strategy)
+{
+	HeapTuple	tp;
+	Form_pg_amop amop_tup;
+	Oid			sortfamily;
+
+	tp = SearchSysCache4(AMOPSTRATEGY,
+						 ObjectIdGetDatum(opfamily),
+						 ObjectIdGetDatum(lefttype),
+						 ObjectIdGetDatum(righttype),
+						 Int16GetDatum(strategy));
+	if (!HeapTupleIsValid(tp))
+		return InvalidOid;
+	amop_tup = (Form_pg_amop) GETSTRUCT(tp);
+	sortfamily = amop_tup->amopsortfamily;
+	ReleaseSysCache(tp);
+
+	return sortfamily;
+}
+
+/*
+ * _bt_get_distance_cmp_proc() -- Init procedure for comparsion of distances
+ *		between "leftargtype" and "distkey".
+ */
+static void
+_bt_get_distance_cmp_proc(ScanKey distkey, Oid opfamily, Oid leftargtype,
+						  FmgrInfo *finfo, int16 *typlen, bool *typbyval)
+{
+	RegProcedure opcode;
+	Oid			sortfamily;
+	Oid			opno;
+	Oid			distanceType;
+
+	distanceType = get_func_rettype(distkey->sk_func.fn_oid);
+
+	sortfamily = _bt_get_sortfamily_for_opfamily_op(opfamily, leftargtype,
+													distkey->sk_subtype,
+													distkey->sk_strategy);
+
+	if (!OidIsValid(sortfamily))
+		elog(ERROR, "could not find sort family for btree ordering operator");
+
+	opno = get_opfamily_member(sortfamily,
+							   distanceType,
+							   distanceType,
+							   BTLessEqualStrategyNumber);
+
+	if (!OidIsValid(opno))
+		elog(ERROR, "could not find operator for btree distance comparison");
+
+	opcode = get_opcode(opno);
+
+	if (!RegProcedureIsValid(opcode))
+		elog(ERROR,
+			 "could not find procedure for btree distance comparison operator");
+
+	fmgr_info(opcode, finfo);
+
+	if (typlen)
+		get_typlenbyval(distanceType, typlen, typbyval);
+}
+
+/*
+ *  _bt_init_distance_comparison() -- Init distance typlen/typbyval and its
+ *  	comparison procedure.
+ */
+void
+_bt_init_distance_comparison(IndexScanDesc scan)
+{
+	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+	Relation	rel = scan->indexRelation;
+	ScanKey		ord = scan->orderByData;
+
+	_bt_get_distance_cmp_proc(ord,
+							  rel->rd_opfamily[ord->sk_attno - 1],
+							  rel->rd_opcintype[ord->sk_attno - 1],
+							  &so->distanceCmpProc,
+							  &so->distanceTypeLen,
+							  &so->distanceTypeByVal);
+
+	/*
+	 * In fact, distance values need to be initialized only for by-ref types,
+	 * because previous distance values are pfreed before writing new ones
+	 * (see _bt_calc_current_dist()).
+	 */
+	so->state.currDistance = (Datum) 0;
+	so->state.markDistance = (Datum) 0;
+}
diff --git a/src/backend/access/nbtree/nbtvalidate.c b/src/backend/access/nbtree/nbtvalidate.c
index e9d4cd60de3..3c91d74512f 100644
--- a/src/backend/access/nbtree/nbtvalidate.c
+++ b/src/backend/access/nbtree/nbtvalidate.c
@@ -28,6 +28,13 @@
 #include "utils/regproc.h"
 #include "utils/syscache.h"
 
+#define BTRequiredOperatorSet \
+		((1 << BTLessStrategyNumber) | \
+		 (1 << BTLessEqualStrategyNumber) | \
+		 (1 << BTEqualStrategyNumber) | \
+		 (1 << BTGreaterEqualStrategyNumber) | \
+		 (1 << BTGreaterStrategyNumber))
+
 
 /*
  * Validator for a btree opclass.
@@ -142,6 +149,7 @@ btvalidate(Oid opclassoid)
 	{
 		HeapTuple	oprtup = &oprlist->members[i]->tuple;
 		Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
+		Oid			op_rettype;
 
 		/* Check that only allowed strategy numbers exist */
 		if (oprform->amopstrategy < 1 ||
@@ -156,20 +164,29 @@ btvalidate(Oid opclassoid)
 			result = false;
 		}
 
-		/* btree doesn't support ORDER BY operators */
-		if (oprform->amoppurpose != AMOP_SEARCH ||
-			OidIsValid(oprform->amopsortfamily))
+		/* btree supports ORDER BY operators */
+		if (oprform->amoppurpose != AMOP_SEARCH)
 		{
-			ereport(INFO,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
-							opfamilyname, "btree",
-							format_operator(oprform->amopopr))));
-			result = false;
+			/* ... and operator result must match the claimed btree opfamily */
+			op_rettype = get_op_rettype(oprform->amopopr);
+			if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype))
+			{
+				ereport(INFO,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
+								opfamilyname, "btree",
+								format_operator(oprform->amopopr))));
+				result = false;
+			}
+		}
+		else
+		{
+			/* Search operators must always return bool */
+			op_rettype = BOOLOID;
 		}
 
 		/* Check operator signature --- same for all btree strategies */
-		if (!check_amop_signature(oprform->amopopr, BOOLOID,
+		if (!check_amop_signature(oprform->amopopr, op_rettype,
 								  oprform->amoplefttype,
 								  oprform->amoprighttype))
 		{
@@ -224,12 +241,8 @@ btvalidate(Oid opclassoid)
 		 * or support functions for this datatype pair.  The sortsupport,
 		 * in_range, and equalimage functions are considered optional.
 		 */
-		if (thisgroup->operatorset !=
-			((1 << BTLessStrategyNumber) |
-			 (1 << BTLessEqualStrategyNumber) |
-			 (1 << BTEqualStrategyNumber) |
-			 (1 << BTGreaterEqualStrategyNumber) |
-			 (1 << BTGreaterStrategyNumber)))
+		if ((thisgroup->operatorset & BTRequiredOperatorSet) !=
+			BTRequiredOperatorSet)
 		{
 			ereport(INFO,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index 9a1a7faac7a..1b6b2ff299d 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -1385,7 +1385,7 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
 {
 	PartitionScheme part_scheme = context->rel->part_scheme;
 	List	   *opsteps = NIL;
-	List	   *btree_clauses[BTMaxStrategyNumber + 1],
+	List	   *btree_clauses[BTMaxSearchStrategyNumber + 1],
 			   *hash_clauses[HTMaxStrategyNumber + 1];
 	int			i;
 	ListCell   *lc;
@@ -1497,7 +1497,7 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
 				 * combinations of expressions of different keys, which
 				 * get_steps_using_prefix takes care of for us.
 				 */
-				for (strat = 1; strat <= BTMaxStrategyNumber; strat++)
+				for (strat = 1; strat <= BTMaxSearchStrategyNumber; strat++)
 				{
 					foreach(lc, btree_clauses[strat])
 					{
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index c60cecf722a..d2da1b964f2 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -682,7 +682,7 @@ BTreeTupleGetMaxHeapTID(IndexTuple itup)
  *	The strategy numbers are chosen so that we can commute them by
  *	subtraction, thus:
  */
-#define BTCommuteStrategyNumber(strat)	(BTMaxStrategyNumber + 1 - (strat))
+#define BTCommuteStrategyNumber(strat)	(BTMaxSearchStrategyNumber + 1 - (strat))
 
 /*
  *	When a new operator class is declared, we require that the user
@@ -1064,6 +1064,12 @@ typedef struct BTScanStateData
 	/* keep these last in struct for efficiency */
 	BTScanPosData currPos;		/* current position data */
 	BTScanPosData markPos;		/* marked position, if any */
+
+	/* KNN-search fields: */
+	Datum		currDistance;	/* distance to the current item */
+	Datum		markDistance;	/* distance to the marked item */
+	bool		currIsNull;		/* current item is NULL */
+	bool		markIsNull;		/* marked item is NULL */
 } BTScanStateData;
 
 typedef BTScanStateData *BTScanState;
@@ -1083,8 +1089,20 @@ typedef struct BTScanOpaqueData
 	FmgrInfo   *orderProcs;		/* ORDER procs for required equality keys */
 	MemoryContext arrayContext; /* scan-lifespan context for array data */
 
-	/* the state of tree scan */
+	/* the state of main tree scan */
 	BTScanStateData state;
+
+	/* kNN-search fields: */
+	bool		useBidirectionalKnnScan;	/* use bidirectional kNN scan? */
+	BTScanState forwardState;
+	BTScanState backwardState;	/* optional scan state for kNN search */
+	ScanDirection scanDirection;	/* selected scan direction for
+									 * unidirectional kNN scan */
+	FmgrInfo	distanceCmpProc;	/* distance comparison procedure */
+	int16		distanceTypeLen;	/* distance typlen */
+	bool		distanceTypeByVal;	/* distance typebyval */
+	bool		currRightIsNearest; /* current right item is nearest */
+	bool		markRightIsNearest; /* marked right item is nearest */
 } BTScanOpaqueData;
 
 typedef BTScanOpaqueData *BTScanOpaque;
@@ -1199,13 +1217,14 @@ extern bool btcanreturn(Relation index, int attno);
 /*
  * prototypes for internal functions in nbtree.c
  */
-extern bool _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno,
+extern bool _bt_parallel_seize(IndexScanDesc scan, BTScanState state, BlockNumber *pageno,
 							   bool first);
-extern void _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page);
-extern void _bt_parallel_done(IndexScanDesc scan);
+extern void _bt_parallel_release(IndexScanDesc scan, BTScanState state, BlockNumber scan_page);
+extern void _bt_parallel_done(IndexScanDesc scan, BTScanState state);
 extern void _bt_parallel_primscan_schedule(IndexScanDesc scan,
 										   BlockNumber prev_scan_page);
 
+
 /*
  * prototypes for functions in nbtdedup.c
  */
@@ -1322,6 +1341,9 @@ extern void _bt_check_third_page(Relation rel, Relation heap,
 								 bool needheaptidspace, Page page, IndexTuple newtup);
 extern bool _bt_allequalimage(Relation rel, bool debugmessage);
 extern void _bt_allocate_tuple_workspaces(BTScanState state);
+extern void _bt_init_distance_comparison(IndexScanDesc scan);
+extern int	_bt_init_knn_start_keys(IndexScanDesc scan, ScanKey *startKeys,
+									ScanKey bufKeys);
 
 /*
  * prototypes for functions in nbtvalidate.c
diff --git a/src/include/access/stratnum.h b/src/include/access/stratnum.h
index 8a47d3c9ec8..ccf2e0b9269 100644
--- a/src/include/access/stratnum.h
+++ b/src/include/access/stratnum.h
@@ -32,7 +32,12 @@ typedef uint16 StrategyNumber;
 #define BTGreaterEqualStrategyNumber	4
 #define BTGreaterStrategyNumber			5
 
-#define BTMaxStrategyNumber				5
+#define BTMaxSearchStrategyNumber		5	/* number of B-tree search
+											 * strategies */
+
+#define BTNearestStrategyNumber			6	/* for ordering by <-> operator */
+#define BTMaxStrategyNumber				6	/* total numer of B-tree
+											 * strategies */
 
 /*
  *	Strategy numbers for hash indexes. There's only one valid strategy for
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index d8a05214b11..805ae021e4c 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -30,6 +30,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int2,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int24
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -47,6 +51,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int2,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int28
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
@@ -64,6 +72,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int2,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int2',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int2,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int4
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -81,6 +93,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int4,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int42
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -98,6 +114,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int4,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int48
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
@@ -115,6 +135,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int4,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int4',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int4,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # default operators int8
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -132,6 +156,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int8', amopstrategy => '5', amopopr => '>(int8,int8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int82
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -149,6 +177,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int2', amopstrategy => '5', amopopr => '>(int8,int2)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int2', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int2)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators int84
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
@@ -166,6 +198,10 @@
 { amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
   amoprighttype => 'int4', amopstrategy => '5', amopopr => '>(int8,int4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/integer_ops', amoplefttype => 'int8',
+  amoprighttype => 'int4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(int8,int4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # btree oid_ops
 
@@ -179,6 +215,10 @@
   amopstrategy => '4', amopopr => '>=(oid,oid)', amopmethod => 'btree' },
 { amopfamily => 'btree/oid_ops', amoplefttype => 'oid', amoprighttype => 'oid',
   amopstrategy => '5', amopopr => '>(oid,oid)', amopmethod => 'btree' },
+{ amopfamily => 'btree/oid_ops', amoplefttype => 'oid',
+  amoprighttype => 'oid', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(oid,oid)', amopmethod => 'btree',
+  amopsortfamily => 'btree/oid_ops' },
 
 # btree xid8_ops
 
@@ -247,6 +287,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float4,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float48
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
@@ -264,6 +308,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float4',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float4,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float4',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float4,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # default operators float8
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -281,6 +329,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float8', amopstrategy => '5', amopopr => '>(float8,float8)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float8', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float8)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # crosstype operators float84
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
@@ -298,6 +350,10 @@
 { amopfamily => 'btree/float_ops', amoplefttype => 'float8',
   amoprighttype => 'float4', amopstrategy => '5', amopopr => '>(float8,float4)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/float_ops', amoplefttype => 'float8',
+  amoprighttype => 'float4', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(float8,float4)', amopmethod => 'btree',
+  amopsortfamily => 'btree/float_ops' },
 
 # btree char_ops
 
@@ -434,6 +490,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(date,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/integer_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -451,6 +511,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(date,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
@@ -468,6 +532,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(date,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'date',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(date,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -485,6 +553,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamp,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -502,6 +574,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'date', amopstrategy => '5', amopopr => '>(timestamp,date)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
@@ -519,6 +595,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamp,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamp',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamp,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # default operators timestamptz
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -536,6 +616,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamptz', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamptz)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamptz', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamptz)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs date
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -553,6 +637,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'date', amopstrategy => '5',
   amopopr => '>(timestamptz,date)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'date', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,date)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # crosstype operators vs timestamp
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
@@ -570,6 +658,10 @@
 { amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
   amoprighttype => 'timestamp', amopstrategy => '5',
   amopopr => '>(timestamptz,timestamp)', amopmethod => 'btree' },
+{ amopfamily => 'btree/datetime_ops', amoplefttype => 'timestamptz',
+  amoprighttype => 'timestamp', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(timestamptz,timestamp)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree time_ops
 
@@ -588,6 +680,10 @@
 { amopfamily => 'btree/time_ops', amoplefttype => 'time',
   amoprighttype => 'time', amopstrategy => '5', amopopr => '>(time,time)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/time_ops', amoplefttype => 'time',
+  amoprighttype => 'time', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(time,time)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree timetz_ops
 
@@ -624,6 +720,10 @@
 { amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
   amoprighttype => 'interval', amopstrategy => '5',
   amopopr => '>(interval,interval)', amopmethod => 'btree' },
+{ amopfamily => 'btree/interval_ops', amoplefttype => 'interval',
+  amoprighttype => 'interval', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(interval,interval)', amopmethod => 'btree',
+  amopsortfamily => 'btree/interval_ops' },
 
 # btree macaddr
 
@@ -799,6 +899,10 @@
 { amopfamily => 'btree/money_ops', amoplefttype => 'money',
   amoprighttype => 'money', amopstrategy => '5', amopopr => '>(money,money)',
   amopmethod => 'btree' },
+{ amopfamily => 'btree/money_ops', amoplefttype => 'money',
+  amoprighttype => 'money', amopstrategy => '6', amoppurpose => 'o',
+  amopopr => '<->(money,money)', amopmethod => 'btree',
+  amopsortfamily => 'btree/money_ops' },
 
 # btree array_ops
 
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index ae54cb254f9..0b08c29bebb 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -355,10 +355,10 @@ ROLLBACK;
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
 ERROR:  access method "invalid_index_method" does not exist
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 6, must be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
-ERROR:  invalid operator number 0, must be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 7, must be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
+ERROR:  invalid operator number 0, must be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ERROR:  operator argument types must be specified in ALTER OPERATOR FAMILY
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- invalid options parsing function
@@ -405,11 +405,12 @@ DROP OPERATOR FAMILY alt_opf8 USING btree;
 CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
-ERROR:  access method "btree" does not support ordering operators
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
 ALTER OPERATOR FAMILY alt_opf11 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index 7ab6113c619..1b39abccbf8 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -24,7 +24,7 @@ select prop,
  nulls_first        |    |       | f
  nulls_last         |    |       | t
  orderable          |    |       | t
- distance_orderable |    |       | f
+ distance_orderable |    |       | t
  returnable         |    |       | t
  search_array       |    |       | t
  search_nulls       |    |       | t
@@ -100,7 +100,7 @@ select prop,
  nulls_first        | f     | f    | f    | f            | f           | f   | f
  nulls_last         | t     | f    | f    | f            | f           | f   | f
  orderable          | t     | f    | f    | f            | f           | f   | f
- distance_orderable | f     | f    | t    | f            | t           | f   | f
+ distance_orderable | t     | f    | t    | f            | t           | f   | f
  returnable         | t     | f    | f    | t            | t           | f   | f
  search_array       | t     | f    | f    | f            | f           | f   | f
  search_nulls       | t     | f    | t    | t            | t           | f   | t
@@ -231,7 +231,7 @@ select col, prop, pg_index_column_has_property(o, col, prop)
    1 | desc               | f
    1 | nulls_first        | f
    1 | nulls_last         | t
-   1 | distance_orderable | f
+   1 | distance_orderable | t
    1 | returnable         | t
    1 | bogus              | 
    2 | orderable          | f
diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index 510646cbce7..66f94af1211 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -486,3 +486,957 @@ ALTER INDEX btree_part_idx ALTER COLUMN id SET (n_distinct=100);
 ERROR:  ALTER action ALTER COLUMN ... SET cannot be performed on relation "btree_part_idx"
 DETAIL:  This operation is not supported for partitioned indexes.
 DROP TABLE btree_part;
+---
+--- Test B-tree distance ordering
+---
+SET enable_bitmapscan = OFF;
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Order By: (seqno <-> 0)
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Order By: ((random <-> 0) AND (seqno <-> 0))
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Order By: ((random <-> 0) AND (random <-> 1))
+(2 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)))
+   Order By: (random <-> 4000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+                                                                                               QUERY PLAN                                                                                               
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using bt_i4_heap_random_idx on bt_i4_heap
+   Index Cond: ((random > 1000000) AND (ROW(random, seqno) < ROW(6000000, 0)) AND (random = ANY ('{1809552,1919087,2321799,2648497,3000193,3013326,4157193,4488889,5257716,5593978,NULL}'::integer[])))
+   Order By: (random <-> 3000000)
+(3 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+ seqno | random  
+-------+---------
+  5380 | 3000193
+  6262 | 3013326
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+  6448 | 4157193
+   210 | 1809552
+  4408 | 4488889
+  6320 | 5257716
+  1836 | 5593978
+(10 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+ seqno | random  
+-------+---------
+  6448 | 4157193
+  9004 | 3783884
+  4408 | 4488889
+  8391 | 4825069
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2859 | 5224694
+  6320 | 5257716
+  2126 | 2648497
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+ seqno | random  
+-------+---------
+  1836 | 5593978
+  6862 | 5556001
+  8729 | 5450460
+  6320 | 5257716
+  2859 | 5224694
+  8391 | 4825069
+  4408 | 4488889
+  6448 | 4157193
+  9004 | 3783884
+  8984 | 3148979
+  1829 | 3053937
+  6262 | 3013326
+  5380 | 3000193
+  9142 | 2847247
+  8411 | 2809541
+  2126 | 2648497
+  2681 | 2321799
+  2893 | 1919087
+   210 | 1809552
+(19 rows)
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+ seqno | random  
+-------+---------
+   210 | 1809552
+  2893 | 1919087
+  2681 | 2321799
+  2126 | 2648497
+  8411 | 2809541
+  9142 | 2847247
+  5380 | 3000193
+  6262 | 3013326
+  1829 | 3053937
+  8984 | 3148979
+  9004 | 3783884
+  6448 | 4157193
+  4408 | 4488889
+  8391 | 4825069
+  2859 | 5224694
+  6320 | 5257716
+  8729 | 5450460
+  6862 | 5556001
+  1836 | 5593978
+(19 rows)
+
+DROP INDEX bt_i4_heap_random_idx;
+-- test parallel KNN scan
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+RESET enable_indexscan;
+\set bt_knn_row_count 100000
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, :bt_knn_row_count) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+-- set the point inside the range
+\set bt_knn_point (4 * :bt_knn_row_count + 3)
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i
+	FROM generate_series(1, :bt_knn_row_count) i;
+SET enable_sort = OFF;
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: ((row_number() OVER (?)) = t2.n)
+   Join Filter: (bt_knn_test.i <> t2.i)
+   ->  WindowAgg
+         ->  Gather Merge
+               Workers Planned: 4
+               ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                     Order By: (i <-> 400003)
+   ->  Hash
+         ->  Gather
+               Workers Planned: 4
+               ->  Parallel Seq Scan on bt_knn_test2 t2
+(12 rows)
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+RESET enable_sort;
+DROP TABLE bt_knn_test2;
+-- set the point to the right of the range
+\set bt_knn_point (11 * :bt_knn_row_count)
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i
+	FROM generate_series(1, :bt_knn_row_count) i;
+SET enable_sort = OFF;
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: ((row_number() OVER (?)) = t2.n)
+   Join Filter: (bt_knn_test.i <> t2.i)
+   ->  WindowAgg
+         ->  Gather Merge
+               Workers Planned: 4
+               ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                     Order By: (i <-> 1100000)
+   ->  Hash
+         ->  Gather
+               Workers Planned: 4
+               ->  Parallel Seq Scan on bt_knn_test2 t2
+(12 rows)
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+RESET enable_sort;
+DROP TABLE bt_knn_test2;
+-- set the point to the left of the range
+\set bt_knn_point (-:bt_knn_row_count)
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i
+	FROM generate_series(1, :bt_knn_row_count) i;
+SET enable_sort = OFF;
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: ((row_number() OVER (?)) = t2.n)
+   Join Filter: (bt_knn_test.i <> t2.i)
+   ->  WindowAgg
+         ->  Gather Merge
+               Workers Planned: 4
+               ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                     Order By: (i <-> '-100000'::integer)
+   ->  Hash
+         ->  Gather
+               Workers Planned: 4
+               ->  Parallel Seq Scan on bt_knn_test2 t2
+(12 rows)
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+RESET enable_sort;
+DROP TABLE bt_knn_test;
+\set knn_row_count 30000
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, :knn_row_count) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+SET enable_sort = OFF;
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * :knn_row_count + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, :knn_row_count) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: ((row_number() OVER (?)) = ((i.i * 30000) + j.j))
+   Join Filter: (bt_knn_test.i <> ('{4,3,2,7,8}'::integer[])[(i.i + 1)])
+   ->  WindowAgg
+         ->  Gather Merge
+               Workers Planned: 4
+               ->  Parallel Index Only Scan using bt_knn_test_idx on bt_knn_test
+                     Index Cond: (i = ANY ('{3,4,7,8,2}'::integer[]))
+                     Order By: (i <-> 4)
+   ->  Hash
+         ->  Nested Loop
+               ->  Function Scan on generate_series i
+               ->  Function Scan on generate_series j
+(13 rows)
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * :knn_row_count + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, :knn_row_count) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+ n | i | i 
+---+---+---
+(0 rows)
+
+RESET enable_sort;
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+ROLLBACK;
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: (ROW(thousand, tenthous) >= ROW(997, 5000))
+   Order By: (thousand <-> 998)
+(3 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+        1 |     9001
+        1 |     8001
+        1 |     7001
+        1 |     6001
+        1 |     5001
+        1 |     4001
+        1 |     3001
+        1 |     2001
+        1 |     1001
+        1 |        1
+        0 |     9000
+        0 |     8000
+        0 |     7000
+        0 |     6000
+        0 |     5000
+        0 |     4000
+        0 |     3000
+        0 |     2000
+        0 |     1000
+        0 |        0
+          |        1
+          |        2
+          |        3
+(33 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Index Only Scan using tenk3_idx on tenk3
+   Index Cond: ((thousand > 100) AND (thousand < 800) AND (thousand = ANY ('{0,123,234,345,456,678,901,NULL}'::smallint[])))
+   Order By: (thousand <-> '300'::bigint)
+(3 rows)
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+ thousand | tenthous 
+----------+----------
+      345 |      345
+      345 |     1345
+      345 |     2345
+      345 |     3345
+      345 |     4345
+      345 |     5345
+      345 |     6345
+      345 |     7345
+      345 |     8345
+      345 |     9345
+      234 |      234
+      234 |     1234
+      234 |     2234
+      234 |     3234
+      234 |     4234
+      234 |     5234
+      234 |     6234
+      234 |     7234
+      234 |     8234
+      234 |     9234
+      456 |      456
+      456 |     1456
+      456 |     2456
+      456 |     3456
+      456 |     4456
+      456 |     5456
+      456 |     6456
+      456 |     7456
+      456 |     8456
+      456 |     9456
+      123 |      123
+      123 |     1123
+      123 |     2123
+      123 |     3123
+      123 |     4123
+      123 |     5123
+      123 |     6123
+      123 |     7123
+      123 |     8123
+      123 |     9123
+      678 |      678
+      678 |     1678
+      678 |     2678
+      678 |     3678
+      678 |     4678
+      678 |     5678
+      678 |     6678
+      678 |     7678
+      678 |     8678
+      678 |     9678
+(50 rows)
+
+DROP INDEX tenk3_idx;
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+ thousand | tenthous 
+----------+----------
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+ thousand | tenthous 
+----------+----------
+      997 |     9997
+      997 |     8997
+      997 |     7997
+      997 |     6997
+      997 |     5997
+      998 |     9998
+      998 |     8998
+      998 |     7998
+      998 |     6998
+      998 |     5998
+      998 |     4998
+      998 |     3998
+      998 |     2998
+      998 |     1998
+      998 |      998
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+ thousand | tenthous 
+----------+----------
+      999 |      999
+      999 |     1999
+      999 |     2999
+      999 |     3999
+      999 |     4999
+      999 |     5999
+      999 |     6999
+      999 |     7999
+      999 |     8999
+      999 |     9999
+      998 |      998
+      998 |     1998
+      998 |     2998
+      998 |     3998
+      998 |     4998
+      998 |     5998
+      998 |     6998
+      998 |     7998
+      998 |     8998
+      998 |     9998
+      997 |     5997
+      997 |     6997
+      997 |     7997
+      997 |     8997
+      997 |     9997
+(25 rows)
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+ thousand | tenthous 
+----------+----------
+        1 |        1
+        1 |     1001
+        1 |     2001
+        1 |     3001
+        1 |     4001
+        1 |     5001
+        1 |     6001
+        1 |     7001
+        1 |     8001
+        1 |     9001
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+        0 |        0
+        0 |     1000
+        0 |     2000
+        0 |     3000
+        0 |     4000
+        0 |     5000
+        0 |     6000
+        0 |     7000
+        0 |     8000
+        0 |     9000
+          |        3
+          |        2
+          |        1
+(33 rows)
+
+DROP INDEX tenk3_idx;
+DROP TABLE tenk3;
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |     ?column?      
+--------------------------+-------------------
+ Wed May 03 00:00:00 2017 | @ 2 days
+ Wed May 03 01:00:00 2017 | @ 2 days 1 hour
+ Wed May 03 02:00:00 2017 | @ 2 days 2 hours
+ Wed May 03 03:00:00 2017 | @ 2 days 3 hours
+ Wed May 03 04:00:00 2017 | @ 2 days 4 hours
+ Wed May 03 05:00:00 2017 | @ 2 days 5 hours
+ Wed May 03 06:00:00 2017 | @ 2 days 6 hours
+ Wed May 03 07:00:00 2017 | @ 2 days 7 hours
+ Wed May 03 08:00:00 2017 | @ 2 days 8 hours
+ Wed May 03 09:00:00 2017 | @ 2 days 9 hours
+ Wed May 03 10:00:00 2017 | @ 2 days 10 hours
+ Wed May 03 11:00:00 2017 | @ 2 days 11 hours
+ Wed May 03 12:00:00 2017 | @ 2 days 12 hours
+ Wed May 03 13:00:00 2017 | @ 2 days 13 hours
+ Wed May 03 14:00:00 2017 | @ 2 days 14 hours
+ Wed May 03 15:00:00 2017 | @ 2 days 15 hours
+ Wed May 03 16:00:00 2017 | @ 2 days 16 hours
+ Wed May 03 17:00:00 2017 | @ 2 days 17 hours
+ Wed May 03 18:00:00 2017 | @ 2 days 18 hours
+ Wed May 03 19:00:00 2017 | @ 2 days 19 hours
+(20 rows)
+
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+            ts            |  ?column?  
+--------------------------+------------
+ Mon Jan 01 00:00:00 2018 | @ 0
+ Mon Jan 01 01:00:00 2018 | @ 1 hour
+ Sun Dec 31 23:00:00 2017 | @ 1 hour
+ Mon Jan 01 02:00:00 2018 | @ 2 hours
+ Sun Dec 31 22:00:00 2017 | @ 2 hours
+ Mon Jan 01 03:00:00 2018 | @ 3 hours
+ Sun Dec 31 21:00:00 2017 | @ 3 hours
+ Mon Jan 01 04:00:00 2018 | @ 4 hours
+ Sun Dec 31 20:00:00 2017 | @ 4 hours
+ Mon Jan 01 05:00:00 2018 | @ 5 hours
+ Sun Dec 31 19:00:00 2017 | @ 5 hours
+ Mon Jan 01 06:00:00 2018 | @ 6 hours
+ Sun Dec 31 18:00:00 2017 | @ 6 hours
+ Mon Jan 01 07:00:00 2018 | @ 7 hours
+ Sun Dec 31 17:00:00 2017 | @ 7 hours
+ Mon Jan 01 08:00:00 2018 | @ 8 hours
+ Sun Dec 31 16:00:00 2017 | @ 8 hours
+ Mon Jan 01 09:00:00 2018 | @ 9 hours
+ Sun Dec 31 15:00:00 2017 | @ 9 hours
+ Mon Jan 01 10:00:00 2018 | @ 10 hours
+(20 rows)
+
+DROP TABLE knn_btree_ts;
+RESET enable_bitmapscan;
+-- Test backward kNN scan
+SET enable_sort = OFF;
+EXPLAIN (COSTS OFF) SELECT thousand, tenthous FROM tenk1 ORDER BY thousand  <-> 510;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Index Only Scan using tenk1_thous_tenthous on tenk1
+   Order By: (thousand <-> 510)
+(2 rows)
+
+BEGIN work;
+DECLARE knn SCROLL CURSOR FOR
+SELECT thousand, tenthous FROM tenk1 ORDER BY thousand <-> 510;
+FETCH LAST FROM knn;
+ thousand | tenthous 
+----------+----------
+        0 |        0
+(1 row)
+
+FETCH BACKWARD 15 FROM knn;
+ thousand | tenthous 
+----------+----------
+        0 |     1000
+        0 |     2000
+        0 |     3000
+        0 |     4000
+        0 |     5000
+        0 |     6000
+        0 |     7000
+        0 |     8000
+        0 |     9000
+        1 |        1
+        1 |     1001
+        1 |     2001
+        1 |     3001
+        1 |     4001
+        1 |     5001
+(15 rows)
+
+FETCH RELATIVE -200 FROM knn;
+ thousand | tenthous 
+----------+----------
+       21 |     5021
+(1 row)
+
+FETCH BACKWARD 20 FROM knn;
+ thousand | tenthous 
+----------+----------
+       21 |     6021
+       21 |     7021
+       21 |     8021
+       21 |     9021
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+       22 |       22
+       22 |     1022
+       22 |     2022
+       22 |     3022
+       22 |     4022
+       22 |     5022
+(20 rows)
+
+FETCH FIRST FROM knn;
+ thousand | tenthous 
+----------+----------
+      510 |      510
+(1 row)
+
+FETCH LAST FROM knn;
+ thousand | tenthous 
+----------+----------
+        0 |        0
+(1 row)
+
+FETCH RELATIVE -215 FROM knn;
+ thousand | tenthous 
+----------+----------
+       21 |     5021
+(1 row)
+
+FETCH BACKWARD 20 FROM knn;
+ thousand | tenthous 
+----------+----------
+       21 |     6021
+       21 |     7021
+       21 |     8021
+       21 |     9021
+      999 |     9999
+      999 |     8999
+      999 |     7999
+      999 |     6999
+      999 |     5999
+      999 |     4999
+      999 |     3999
+      999 |     2999
+      999 |     1999
+      999 |      999
+       22 |       22
+       22 |     1022
+       22 |     2022
+       22 |     3022
+       22 |     4022
+       22 |     5022
+(20 rows)
+
+ROLLBACK work;
+RESET enable_sort;
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 9d047b21b88..4a512417927 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1432,6 +1432,8 @@ WHERE o1.oprnegate = o2.oid AND p1.oid = o1.oprcode AND p2.oid = o2.oprcode AND
 
 -- Btree comparison operators' functions should have the same volatility
 -- and leakproofness markings as the associated comparison support function.
+-- Btree ordering operators' functions may be not leakproof, while the
+-- associated comparison support function is leakproof.
 SELECT pp.oid::regprocedure as proc, pp.provolatile as vp, pp.proleakproof as lp,
        po.oid::regprocedure as opr, po.provolatile as vo, po.proleakproof as lo
 FROM pg_proc pp, pg_proc po, pg_operator o, pg_amproc ap, pg_amop ao
@@ -1442,7 +1444,10 @@ WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND
     ao.amoprighttype = ap.amprocrighttype AND
     ap.amprocnum = 1 AND
     (pp.provolatile != po.provolatile OR
-     pp.proleakproof != po.proleakproof)
+     (pp.proleakproof != po.proleakproof AND
+      ao.amoppurpose = 's') OR
+     (pp.proleakproof < po.proleakproof AND
+      ao.amoppurpose = 'o'))
 ORDER BY 1;
  proc | vp | lp | opr | vo | lo 
 ------+----+----+-----+----+----
@@ -1980,6 +1985,7 @@ ORDER BY 1, 2, 3;
         403 |            5 | *>
         403 |            5 | >
         403 |            5 | ~>~
+        403 |            6 | <->
         405 |            1 | =
         783 |            1 | <<
         783 |            1 | @@
@@ -2090,7 +2096,7 @@ ORDER BY 1, 2, 3;
        4000 |           28 | ^@
        4000 |           29 | <^
        4000 |           30 | >^
-(124 rows)
+(125 rows)
 
 -- Check that all opclass search operators have selectivity estimators.
 -- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 3bbe4c5f974..c7f7f6bd3a3 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5083,30 +5083,34 @@ List of access methods
 (1 row)
 
 \dAo+ btree float_ops
-                                List of operators of operator families
-  AM   | Operator family |               Operator                | Strategy | Purpose | Sort opfamily 
--------+-----------------+---------------------------------------+----------+---------+---------------
- btree | float_ops       | <(double precision,double precision)  |        1 | search  | 
- btree | float_ops       | <=(double precision,double precision) |        2 | search  | 
- btree | float_ops       | =(double precision,double precision)  |        3 | search  | 
- btree | float_ops       | >=(double precision,double precision) |        4 | search  | 
- btree | float_ops       | >(double precision,double precision)  |        5 | search  | 
- btree | float_ops       | <(real,real)                          |        1 | search  | 
- btree | float_ops       | <=(real,real)                         |        2 | search  | 
- btree | float_ops       | =(real,real)                          |        3 | search  | 
- btree | float_ops       | >=(real,real)                         |        4 | search  | 
- btree | float_ops       | >(real,real)                          |        5 | search  | 
- btree | float_ops       | <(double precision,real)              |        1 | search  | 
- btree | float_ops       | <=(double precision,real)             |        2 | search  | 
- btree | float_ops       | =(double precision,real)              |        3 | search  | 
- btree | float_ops       | >=(double precision,real)             |        4 | search  | 
- btree | float_ops       | >(double precision,real)              |        5 | search  | 
- btree | float_ops       | <(real,double precision)              |        1 | search  | 
- btree | float_ops       | <=(real,double precision)             |        2 | search  | 
- btree | float_ops       | =(real,double precision)              |        3 | search  | 
- btree | float_ops       | >=(real,double precision)             |        4 | search  | 
- btree | float_ops       | >(real,double precision)              |        5 | search  | 
-(20 rows)
+                                 List of operators of operator families
+  AM   | Operator family |                Operator                | Strategy | Purpose  | Sort opfamily 
+-------+-----------------+----------------------------------------+----------+----------+---------------
+ btree | float_ops       | <(double precision,double precision)   |        1 | search   | 
+ btree | float_ops       | <=(double precision,double precision)  |        2 | search   | 
+ btree | float_ops       | =(double precision,double precision)   |        3 | search   | 
+ btree | float_ops       | >=(double precision,double precision)  |        4 | search   | 
+ btree | float_ops       | >(double precision,double precision)   |        5 | search   | 
+ btree | float_ops       | <->(double precision,double precision) |        6 | ordering | float_ops
+ btree | float_ops       | <(real,real)                           |        1 | search   | 
+ btree | float_ops       | <=(real,real)                          |        2 | search   | 
+ btree | float_ops       | =(real,real)                           |        3 | search   | 
+ btree | float_ops       | >=(real,real)                          |        4 | search   | 
+ btree | float_ops       | >(real,real)                           |        5 | search   | 
+ btree | float_ops       | <->(real,real)                         |        6 | ordering | float_ops
+ btree | float_ops       | <(double precision,real)               |        1 | search   | 
+ btree | float_ops       | <=(double precision,real)              |        2 | search   | 
+ btree | float_ops       | =(double precision,real)               |        3 | search   | 
+ btree | float_ops       | >=(double precision,real)              |        4 | search   | 
+ btree | float_ops       | >(double precision,real)               |        5 | search   | 
+ btree | float_ops       | <->(double precision,real)             |        6 | ordering | float_ops
+ btree | float_ops       | <(real,double precision)               |        1 | search   | 
+ btree | float_ops       | <=(real,double precision)              |        2 | search   | 
+ btree | float_ops       | =(real,double precision)               |        3 | search   | 
+ btree | float_ops       | >=(real,double precision)              |        4 | search   | 
+ btree | float_ops       | >(real,double precision)               |        5 | search   | 
+ btree | float_ops       | <->(real,double precision)             |        6 | ordering | float_ops
+(24 rows)
 
 \dAo * pg_catalog.jsonb_path_ops
              List of operators of operator families
diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql
index de58d268d31..bd3710d631b 100644
--- a/src/test/regress/sql/alter_generic.sql
+++ b/src/test/regress/sql/alter_generic.sql
@@ -306,8 +306,8 @@ ROLLBACK;
 -- Should fail. Invalid values for ALTER OPERATOR FAMILY .. ADD / DROP
 CREATE OPERATOR FAMILY alt_opf4 USING btree;
 ALTER OPERATOR FAMILY alt_opf4 USING invalid_index_method ADD  OPERATOR 1 < (int4, int2); -- invalid indexing_method
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 6 < (int4, int2); -- operator number should be between 1 and 5
-ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 5
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 7 < (int4, int2); -- operator number should be between 1 and 6
+ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 0 < (int4, int2); -- operator number should be between 1 and 6
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD OPERATOR 1 < ; -- operator without argument types
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 0 btint42cmp(int4, int2); -- invalid options parsing function
 ALTER OPERATOR FAMILY alt_opf4 USING btree ADD FUNCTION 6 btint42cmp(int4, int2); -- function number should be between 1 and 5
@@ -351,10 +351,12 @@ CREATE OPERATOR FAMILY alt_opf9 USING gist;
 ALTER OPERATOR FAMILY alt_opf9 USING gist ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf9 USING gist;
 
--- Should fail. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+-- Should work. Ensure correct ordering methods in ALTER OPERATOR FAMILY ... ADD OPERATOR .. FOR ORDER BY
+BEGIN TRANSACTION;
 CREATE OPERATOR FAMILY alt_opf10 USING btree;
 ALTER OPERATOR FAMILY alt_opf10 USING btree ADD OPERATOR 1 < (int4, int4) FOR ORDER BY float_ops;
 DROP OPERATOR FAMILY alt_opf10 USING btree;
+ROLLBACK;
 
 -- Should work. Textbook case of ALTER OPERATOR FAMILY ... ADD OPERATOR with FOR ORDER BY
 CREATE OPERATOR FAMILY alt_opf11 USING gist;
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 0d2a33f3705..98899492235 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -282,3 +282,315 @@ CREATE TABLE btree_part (id int4) PARTITION BY RANGE (id);
 CREATE INDEX btree_part_idx ON btree_part(id);
 ALTER INDEX btree_part_idx ALTER COLUMN id SET (n_distinct=100);
 DROP TABLE btree_part;
+
+---
+--- Test B-tree distance ordering
+---
+
+SET enable_bitmapscan = OFF;
+
+-- temporarily disable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = false WHERE indexrelid = 'bt_i4_index'::regclass;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random, seqno);
+
+-- test unsupported orderings (by non-first index attribute or by more than one order keys)
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, seqno <-> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM bt_i4_heap ORDER BY random <-> 0, random <-> 1;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+SELECT * FROM bt_i4_heap
+WHERE
+	random > 1000000 AND (random, seqno) < (6000000, 0) AND
+	random IN (1809552, 1919087, 2321799, 2648497, 3000193, 3013326, 4157193, 4488889, 5257716, 5593978, NULL)
+ORDER BY random <-> 3000000;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+CREATE INDEX bt_i4_heap_random_idx ON bt_i4_heap USING btree(random DESC, seqno);
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 4000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 10000000;
+
+SELECT * FROM bt_i4_heap
+WHERE random > 1000000 AND (random, seqno) < (6000000, 0)
+ORDER BY random <-> 0;
+
+DROP INDEX bt_i4_heap_random_idx;
+
+-- test parallel KNN scan
+
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+BEGIN ISOLATION LEVEL REPEATABLE READ;
+
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers = 4;
+SET max_parallel_workers_per_gather = 4;
+SET cpu_operator_cost = 0;
+
+RESET enable_indexscan;
+
+\set bt_knn_row_count 100000
+
+CREATE TABLE bt_knn_test AS SELECT i * 10 AS i FROM generate_series(1, :bt_knn_row_count) i;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+-- set the point inside the range
+\set bt_knn_point (4 * :bt_knn_row_count + 3)
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i
+	FROM generate_series(1, :bt_knn_row_count) i;
+
+SET enable_sort = OFF;
+
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+RESET enable_sort;
+
+DROP TABLE bt_knn_test2;
+
+-- set the point to the right of the range
+\set bt_knn_point (11 * :bt_knn_row_count)
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i
+	FROM generate_series(1, :bt_knn_row_count) i;
+
+SET enable_sort = OFF;
+
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+RESET enable_sort;
+
+DROP TABLE bt_knn_test2;
+
+-- set the point to the left of the range
+\set bt_knn_point (-:bt_knn_row_count)
+
+CREATE TABLE bt_knn_test2 AS
+	SELECT row_number() OVER (ORDER BY i * 10 <-> :bt_knn_point) AS n, i * 10 AS i
+	FROM generate_series(1, :bt_knn_row_count) i;
+
+SET enable_sort = OFF;
+
+EXPLAIN (COSTS OFF)
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH bt_knn_test1 AS (
+	SELECT row_number() OVER (ORDER BY i <-> :bt_knn_point) AS n, i FROM bt_knn_test
+)
+SELECT * FROM bt_knn_test1 t1 JOIN bt_knn_test2 t2 USING (n) WHERE t1.i <> t2.i;
+
+RESET enable_sort;
+
+DROP TABLE bt_knn_test;
+
+\set knn_row_count 30000
+CREATE TABLE bt_knn_test AS SELECT i FROM generate_series(1, 10) i, generate_series(1, :knn_row_count) j;
+CREATE INDEX bt_knn_test_idx ON bt_knn_test (i);
+ALTER TABLE bt_knn_test SET (parallel_workers = 4);
+ANALYZE bt_knn_test;
+
+SET enable_sort = OFF;
+
+EXPLAIN (COSTS OFF)
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * :knn_row_count + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, :knn_row_count) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+WITH
+t1 AS (
+	SELECT row_number() OVER () AS n, i
+	FROM bt_knn_test
+	WHERE i IN (3, 4, 7, 8, 2)
+	ORDER BY i <-> 4
+),
+t2 AS (
+	SELECT i * :knn_row_count + j AS n, (ARRAY[4, 3, 2, 7, 8])[i + 1] AS i
+	FROM generate_series(0, 4) i, generate_series(1, :knn_row_count) j
+)
+SELECT * FROM t1 JOIN t2 USING (n) WHERE t1.i <> t2.i;
+
+RESET enable_sort;
+
+RESET parallel_setup_cost;
+RESET parallel_tuple_cost;
+RESET min_parallel_table_scan_size;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET cpu_operator_cost;
+
+ROLLBACK;
+
+-- enable bt_i4_index index on bt_i4_heap(seqno)
+UPDATE pg_index SET indisvalid = true WHERE indexrelid = 'bt_i4_index'::regclass;
+
+
+CREATE TABLE tenk3 AS SELECT thousand, tenthous FROM tenk1;
+
+INSERT INTO tenk3 VALUES (NULL, 1), (NULL, 2), (NULL, 3);
+
+-- Test distance ordering by ASC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand, tenthous);
+
+EXPLAIN (COSTS OFF)
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+SELECT * FROM tenk3
+WHERE thousand > 100 AND thousand < 800 AND
+	thousand = ANY(ARRAY[0, 123, 234, 345, 456, 678, 901, NULL]::int2[])
+ORDER BY thousand <-> 300::int8;
+
+DROP INDEX tenk3_idx;
+
+-- Test distance ordering by DESC index
+CREATE INDEX tenk3_idx ON tenk3 USING btree(thousand DESC, tenthous);
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 998;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000)
+ORDER BY thousand <-> 0;
+
+SELECT thousand, tenthous FROM tenk3
+WHERE (thousand, tenthous) >= (997, 5000) AND thousand < 1000
+ORDER BY thousand <-> 10000;
+
+SELECT thousand, tenthous FROM tenk3
+ORDER BY thousand <-> 500
+OFFSET 9970;
+
+DROP INDEX tenk3_idx;
+
+DROP TABLE tenk3;
+
+-- Test distance ordering on by-ref types
+CREATE TABLE knn_btree_ts (ts timestamp);
+
+INSERT INTO knn_btree_ts
+SELECT timestamp '2017-05-03 00:00:00' + tenthous * interval '1 hour'
+FROM tenk1;
+
+CREATE INDEX knn_btree_ts_idx ON knn_btree_ts USING btree(ts);
+
+SELECT ts, ts <-> timestamp '2017-05-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+SELECT ts, ts <-> timestamp '2018-01-01 00:00:00' FROM knn_btree_ts ORDER BY 2 LIMIT 20;
+
+DROP TABLE knn_btree_ts;
+
+RESET enable_bitmapscan;
+
+-- Test backward kNN scan
+
+SET enable_sort = OFF;
+
+EXPLAIN (COSTS OFF) SELECT thousand, tenthous FROM tenk1 ORDER BY thousand  <-> 510;
+
+BEGIN work;
+
+DECLARE knn SCROLL CURSOR FOR
+SELECT thousand, tenthous FROM tenk1 ORDER BY thousand <-> 510;
+
+FETCH LAST FROM knn;
+FETCH BACKWARD 15 FROM knn;
+FETCH RELATIVE -200 FROM knn;
+FETCH BACKWARD 20 FROM knn;
+FETCH FIRST FROM knn;
+FETCH LAST FROM knn;
+FETCH RELATIVE -215 FROM knn;
+FETCH BACKWARD 20 FROM knn;
+
+ROLLBACK work;
+
+RESET enable_sort;
\ No newline at end of file
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 2fe7b6dcc49..d871c80866d 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -814,6 +814,8 @@ WHERE o1.oprnegate = o2.oid AND p1.oid = o1.oprcode AND p2.oid = o2.oprcode AND
 
 -- Btree comparison operators' functions should have the same volatility
 -- and leakproofness markings as the associated comparison support function.
+-- Btree ordering operators' functions may be not leakproof, while the
+-- associated comparison support function is leakproof.
 SELECT pp.oid::regprocedure as proc, pp.provolatile as vp, pp.proleakproof as lp,
        po.oid::regprocedure as opr, po.provolatile as vo, po.proleakproof as lo
 FROM pg_proc pp, pg_proc po, pg_operator o, pg_amproc ap, pg_amop ao
@@ -824,7 +826,10 @@ WHERE pp.oid = ap.amproc AND po.oid = o.oprcode AND o.oid = ao.amopopr AND
     ao.amoprighttype = ap.amprocrighttype AND
     ap.amprocnum = 1 AND
     (pp.provolatile != po.provolatile OR
-     pp.proleakproof != po.proleakproof)
+     (pp.proleakproof != po.proleakproof AND
+      ao.amoppurpose = 's') OR
+     (pp.proleakproof < po.proleakproof AND
+      ao.amoppurpose = 'o'))
 ORDER BY 1;
 
 
-- 
2.45.2

#49Kirill Reshke
reshkekirill@gmail.com
In reply to: Anton A. Melnikov (#48)
Re: [PATCH] kNN for btree

On Wed, 31 Jul 2024 at 09:46, Anton A. Melnikov
<a.melnikov@postgrespro.ru> wrote:

Hi!

Rebased existing patch on current master to have an actual working version.
There is an inconsistency with commit 5bf748b86.

Reproduction:
CREATE TABLE test (a int4);
INSERT INTO test VALUES (2), (3);
CREATE INDEX test_idx ON test USING btree(a);
SET enable_seqscan = OFF;
SELECT * FROM test WHERE a IN (2, 3) ORDER BY a <-> 5;

Actual result:
postgres=# SELECT * FROM test WHERE a IN (2, 3) ORDER BY a <-> 5;
a
---
3
(1 row)

Correct expected result:
postgres=# SELECT * FROM test WHERE a IN (2, 3) ORDER BY a <-> 5;
a
---
3
2
(2 rows)

Reported an issue in the thread corresponding to commit 5bf748b86.

With the best regards,

--
Anton A. Melnikov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Hi!
Given little activity here, there is a little chance of being
committed in the current commitfest, so I moved to the next.

I will try to take a look at v19 soon.

--
Best regards,
Kirill Reshke