SP-GiST for ranges based on 2d-mapping and quad-tree

Started by Alexander Korotkovover 13 years ago43 messages
#1Alexander Korotkov
aekorotkov@gmail.com
1 attachment(s)

Hackers,

attached patch implements quad-tree on ranges. Some performance results in
comparison with current GiST indexing.
Index creation is slightly slower. Probably, it need some investigation.
Search queries on SP-GiST use much more pages. However this comparison can
be not really correct, because SP-GiST can pin same buffer several times
during one scan. In CPU search queries on SP-GiST seems to be slightly
faster. Dramatical difference in "column <@ const" query is thanks to
2d-mapping.

test=# create index period_idx on period_test using gist (period);
CREATE INDEX
Time: 49141,148 ms

test=# create index period_idx2 on period_test2 using spgist (period);
CREATE INDEX
Time: 59503,215 ms

test=# explain (analyze, buffers) select * from period_test where period &&
daterange('2011-12-10', '2011-12-11');
QUERY PLAN

----------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on period_test (cost=471.63..13716.45 rows=11866
width=32) (actual time=107.258..147.139 rows=365451 loops=1)
Recheck Cond: (period && '[2011-12-10,2011-12-11)'::daterange)
Buffers: shared read=7636
-> Bitmap Index Scan on period_idx (cost=0.00..468.67 rows=11866
width=0) (actual time=106.697..106.697 rows=365451 loops=1)
Index Cond: (period && '[2011-12-10,2011-12-11)'::daterange)
Buffers: shared read=3694
Total runtime: 160.670 ms
(7 rows)

test=# explain (analyze, buffers) select * from period_test2 where period
&& daterange('2011-12-10', '2011-12-11');
QUERY PLAN

---------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on period_test2 (cost=414.52..13659.34 rows=11866
width=32) (actual time=88.793..129.608 rows=365451 loops=1)
Recheck Cond: (period && '[2011-12-10,2011-12-11)'::daterange)
Buffers: shared hit=7623 read=7687
-> Bitmap Index Scan on period_idx2 (cost=0.00..411.55 rows=11866
width=0) (actual time=88.285..88.285 rows=365451 loops=1)
Index Cond: (period && '[2011-12-10,2011-12-11)'::daterange)
Buffers: shared hit=7623 read=3745
Total runtime: 142.971 ms
(7 rows)

test=# explain (analyze, buffers) select * from period_test where period <@
daterange('2011-12-10', '2011-12-11');
QUERY PLAN

---------------------------------------------------------------------------------------------------
-----------------------
Bitmap Heap Scan on period_test (cost=102.06..6140.66 rows=2373 width=32)
(actual time=85.437..85.437 rows=0 loops=1)
Recheck Cond: (period <@ '[2011-12-10,2011-12-11)'::daterange)
Buffers: shared read=3694
-> Bitmap Index Scan on period_idx (cost=0.00..101.47 rows=2373
width=0) (actual time=85.431..85.431 rows=0 loops=1)
Index Cond: (period <@ '[2011-12-10,2011-12-11)'::daterange)
Buffers: shared read=3694
Total runtime: 85.493 ms
(7 rows)

test=# explain (analyze, buffers) select * from period_test2 where period
<@ daterange('2011-12-10', '2011-12-11');
QUERY PLAN

--------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on period_test2 (cost=88.95..6127.54 rows=2373 width=32)
(actual time=18.666..18.666 rows=0 loops=1)
Recheck Cond: (period <@ '[2011-12-10,2011-12-11)'::daterange)
Buffers: shared hit=1404
-> Bitmap Index Scan on period_idx2 (cost=0.00..88.35 rows=2373
width=0) (actual time=18.660..18.660 rows=0 loops=1)
Index Cond: (period <@ '[2011-12-10,2011-12-11)'::daterange)
Buffers: shared hit=1404
Total runtime: 18.717 ms
(7 rows)

test=# explain (analyze, buffers) select * from period_test where period @>
daterange('2011-08-10', '2011-12-31');
QUERY PLAN

-------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on period_test (cost=102.06..6140.66 rows=2373 width=32)
(actual time=56.114..73.785 rows=118097 loops=1)
Recheck Cond: (period @> '[2011-08-10,2011-12-31)'::daterange)
Buffers: shared read=4125
-> Bitmap Index Scan on period_idx (cost=0.00..101.47 rows=2373
width=0) (actual time=55.740..55.740 rows=118097 loops=1)
Index Cond: (period @> '[2011-08-10,2011-12-31)'::daterange)
Buffers: shared read=1391
Total runtime: 78.469 ms
(7 rows)

test=# explain (analyze, buffers) select * from period_test2 where period
@> daterange('2011-08-10', '2011-12-31');
QUERY PLAN

-------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on period_test2 (cost=88.95..6127.54 rows=2373 width=32)
(actual time=31.653..49
.751 rows=118097 loops=1)
Recheck Cond: (period @> '[2011-08-10,2011-12-31)'::daterange)
Buffers: shared hit=3093 read=3768
-> Bitmap Index Scan on period_idx2 (cost=0.00..88.35 rows=2373
width=0) (actual time=31.282..31.282 rows=118097 loops=1)
Index Cond: (period @> '[2011-08-10,2011-12-31)'::daterange)
Buffers: shared hit=3093 read=1034
Total runtime: 54.288 ms
(7 rows)

------
With best regards,
Alexander Korotkov.

Attachments:

range_spgist_quad-0.2.patch.gzapplication/x-gzip; name=range_spgist_quad-0.2.patch.gzDownload
#2Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Alexander Korotkov (#1)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On 14.06.2012 01:56, Alexander Korotkov wrote:

Hackers,

attached patch implements quad-tree on ranges. Some performance results in
comparison with current GiST indexing.

@@ -788,7 +774,7 @@ range_super_union(TypeCacheEntry *typcache, RangeType * r1, R
angeType * r2)
* part of the relcache entry for the index, typically) this essentially
* eliminates lookup overhead during operations on a GiST range index.
*/
-static Datum
+Datum
TrickFunctionCall2(PGFunction proc, FmgrInfo *flinfo, Datum arg1, Datum arg2)
{
FunctionCallInfoData fcinfo;

I don't think we want to expose TrickFunctionCall2(). Not with that
name, anyway. Perhaps we should refactor the functions called this way,
range_adjacent, range_overlaps etc., to have internal counterparts that
can be called without FunctionCall(). Like:

***************
*** 692,697 ****
--- 692,708 ----
{
RangeType  *r1 = PG_GETARG_RANGE(0);
RangeType  *r2 = PG_GETARG_RANGE(1);
+
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+ 	PG_RETURN_BOOL(range_adjacent_internal(r1, r2, typcache);
+ }
+
+ bool
+ range_adjacent_internal(RangeType r1, RangeType r2, TypeCacheEntry *typcache)
+ {
+ 	RangeType  *r1 = PG_GETARG_RANGE(0);
+ 	RangeType  *r2 = PG_GETARG_RANGE(1);
TypeCacheEntry *typcache;
RangeBound	lower1,
lower2;

The gist and SP-gist consistent functions could call the internal
function directly.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#3Alexander Korotkov
aekorotkov@gmail.com
In reply to: Heikki Linnakangas (#2)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Thu, Jun 21, 2012 at 11:12 AM, Heikki Linnakangas <
heikki.linnakangas@enterprisedb.com> wrote:

On 14.06.2012 01:56, Alexander Korotkov wrote:

Hackers,

attached patch implements quad-tree on ranges. Some performance results in
comparison with current GiST indexing.

@@ -788,7 +774,7 @@ range_super_union(**TypeCacheEntry *typcache,

RangeType * r1, R
angeType * r2)
* part of the relcache entry for the index, typically) this essentially
* eliminates lookup overhead during operations on a GiST range index.
*/
-static Datum
+Datum
TrickFunctionCall2(PGFunction proc, FmgrInfo *flinfo, Datum arg1, Datum
arg2)
{
FunctionCallInfoData fcinfo;

I don't think we want to expose TrickFunctionCall2(). Not with that name,
anyway. Perhaps we should refactor the functions called this way,
range_adjacent, range_overlaps etc., to have internal counterparts that can
be called without FunctionCall(). Like:

***************

*** 692,697 ****
--- 692,708 ----
{
RangeType  *r1 = PG_GETARG_RANGE(0);
RangeType  *r2 = PG_GETARG_RANGE(1);
+
+       typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+       PG_RETURN_BOOL(range_adjacent_**internal(r1, r2, typcache);
+ }
+
+ bool
+ range_adjacent_internal(**RangeType r1, RangeType r2, TypeCacheEntry
*typcache)
+ {
+       RangeType  *r1 = PG_GETARG_RANGE(0);
+       RangeType  *r2 = PG_GETARG_RANGE(1);
TypeCacheEntry *typcache;
RangeBound      lower1,
lower2;

The gist and SP-gist consistent functions could call the internal function
directly.

I like idea of replacing TrickFunctionCall2 with internal function. Do you
think I should post a separate patch for existing GiST code?

------
With best regards,
Alexander Korotkov.

#4Alexander Korotkov
aekorotkov@gmail.com
In reply to: Alexander Korotkov (#1)
1 attachment(s)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Thu, Jun 14, 2012 at 2:56 AM, Alexander Korotkov <aekorotkov@gmail.com>wrote:

attached patch implements quad-tree on ranges. Some performance results in
comparison with current GiST indexing.
Index creation is slightly slower. Probably, it need some investigation.
Search queries on SP-GiST use much more pages. However this comparison can
be not really correct, because SP-GiST can pin same buffer several times
during one scan. In CPU search queries on SP-GiST seems to be slightly
faster. Dramatical difference in "column <@ const" query is thanks to
2d-mapping.

Patch with another SP-GiST implementation for ranges is attached. It uses
k-d tree instead of quad-tree. I would like to leave only one
implementation of SP-GiST for ranges. I'm going to do as comprehensive
testing as I can for it.

------
With best regards,
Alexander Korotkov.

Attachments:

range_spgist_kd-0.1.patchapplication/octet-stream; name=range_spgist_kd-0.1.patchDownload
*** a/src/backend/utils/adt/Makefile
--- b/src/backend/utils/adt/Makefile
***************
*** 30,36 **** OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
  	tsvector.o tsvector_op.o tsvector_parser.o \
! 	txid.o uuid.o windowfuncs.o xml.o
  
  like.o: like.c like_match.c
  
--- 30,36 ----
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
  	tsvector.o tsvector_op.o tsvector_parser.o \
! 	txid.o uuid.o windowfuncs.o xml.o rangetypes_spgistkd.o
  
  like.o: like.c like_match.c
  
*** a/src/backend/utils/adt/rangetypes_gist.c
--- b/src/backend/utils/adt/rangetypes_gist.c
***************
*** 20,39 ****
  #include "utils/datum.h"
  #include "utils/rangetypes.h"
  
- 
- /* Operator strategy numbers used in the GiST range opclass */
- /* Numbers are chosen to match up operator names with existing usages */
- #define RANGESTRAT_BEFORE				1
- #define RANGESTRAT_OVERLEFT				2
- #define RANGESTRAT_OVERLAPS				3
- #define RANGESTRAT_OVERRIGHT			4
- #define RANGESTRAT_AFTER				5
- #define RANGESTRAT_ADJACENT				6
- #define RANGESTRAT_CONTAINS				7
- #define RANGESTRAT_CONTAINED_BY			8
- #define RANGESTRAT_CONTAINS_ELEM		16
- #define RANGESTRAT_EQ					18
- 
  /*
   * Range class properties used to segregate different classes of ranges in
   * GiST.  Each unique combination of properties is a class.  CLS_EMPTY cannot
--- 20,25 ----
***************
*** 792,798 **** range_super_union(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
   * part of the relcache entry for the index, typically) this essentially
   * eliminates lookup overhead during operations on a GiST range index.
   */
! static Datum
  TrickFunctionCall2(PGFunction proc, FmgrInfo *flinfo, Datum arg1, Datum arg2)
  {
  	FunctionCallInfoData fcinfo;
--- 778,784 ----
   * part of the relcache entry for the index, typically) this essentially
   * eliminates lookup overhead during operations on a GiST range index.
   */
! Datum
  TrickFunctionCall2(PGFunction proc, FmgrInfo *flinfo, Datum arg1, Datum arg2)
  {
  	FunctionCallInfoData fcinfo;
*** /dev/null
--- b/src/backend/utils/adt/rangetypes_spgistkd.c
***************
*** 0 ****
--- 1,860 ----
+ /*-------------------------------------------------------------------------
+  *
+  * rangetypes_spgistkd.c
+  *	  implementation of k-d tree over ranges mapped to 2d-points for SP-GiST
+  *
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * IDENTIFICATION
+  *			src/backend/utils/adt/rangetypes_spgistkd.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #include "postgres.h"
+ 
+ #include "access/spgist.h"
+ #include "access/skey.h"
+ #include "catalog/pg_type.h"
+ #include "utils/builtins.h"
+ #include "utils/datum.h"
+ #include "utils/rangetypes.h"
+ 
+ static int2 getNodeNumber(TypeCacheEntry *typcache, RangeType *centroid, RangeType *tst, int level);
+ static int bound_cmp(const void *a, const void *b, void *arg);
+ static bool bounds_connected(TypeCacheEntry *typcache, RangeBound lower, RangeBound upper);
+ 
+ /*
+  * Config SP-GiST interface function.
+  */
+ Datum
+ spg_range_kd_config(PG_FUNCTION_ARGS)
+ {
+ 	/* spgConfigIn *cfgin = (spgConfigIn *) PG_GETARG_POINTER(0); */
+ 	spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1);
+ 
+ 	cfg->prefixType = ANYRANGEOID;
+ 	cfg->labelType = VOIDOID;	/* we don't need node labels */
+ 	cfg->canReturnData = true;
+ 	cfg->longValuesOK = false;
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Returns node number for k-d tree by given coordinate of split.
+  */
+ static int2
+ getNodeNumber(TypeCacheEntry *typcache, RangeType *coord, 
+ 													RangeType *tst, int level)
+ {
+ 	RangeBound centroidLower, centroidUpper, lower, upper;
+ 	bool centroidEmpty, empty;
+ 	
+ 	range_deserialize(typcache, tst, &lower, &upper, &empty);
+ 	
+ 	/* Empty ranges are going to node 3 */
+ 	if (empty)
+ 		return 3;
+ 	
+ 	range_deserialize(typcache, coord, &centroidLower, &centroidUpper, 
+ 																&centroidEmpty);
+ 	
+ 	if (level % 2 == 0)
+ 	{
+ 		/* Even level number, split by lower bound of range */
+ 		if (range_cmp_bounds(typcache, &lower, &centroidLower) < 0)
+ 			return 1;
+ 		else
+ 			return 2;
+ 	}
+ 	else
+ 	{
+ 		/* Odd level number, split by lower bound of range */
+ 		if (range_cmp_bounds(typcache, &upper, &centroidUpper) < 0)
+ 			return 1;
+ 		else
+ 			return 2;
+ 	}
+ 
+ 	elog(ERROR, "getQuadrant: impossible case");
+ 	return 0;
+ }
+ 
+ 
+ /*
+  * Choose SP-GiST function: choose path for addition of new range.
+  */
+ Datum
+ spg_range_kd_choose(PG_FUNCTION_ARGS)
+ {
+ 	spgChooseIn	   *in = (spgChooseIn *) PG_GETARG_POINTER(0);
+ 	spgChooseOut   *out = (spgChooseOut *) PG_GETARG_POINTER(1);
+ 	RangeType	   *inRange = DatumGetRangeType(in->datum), *centroid;
+ 	int2			nodeNumber;
+ 	TypeCacheEntry *typcache;
+ 
+ 	if (in->allTheSame)
+ 	{
+ 		out->resultType = spgMatchNode;
+ 		/* nodeN will be set by core */
+ 		out->result.matchNode.levelAdd = 0;
+ 		out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(inRange));
+ 
+ 	Assert(in->hasPrefix);
+ 	centroid = DatumGetRangeType(in->prefixDatum);
+ 	
+ 	/*
+ 	 * Empty prefix datum divides ranges by empty sign. All empty ranges are
+ 	 * taken into node 0, all non-empty ranges are taken into node 1.
+ 	 */
+ 	if (RangeIsEmpty(centroid))
+ 	{
+ 		out->resultType = spgMatchNode;
+ 		if (RangeIsEmpty(inRange))
+ 			out->result.matchNode.nodeN = 0;
+ 		else
+ 			out->result.matchNode.nodeN = 1;
+ 		out->result.matchNode.levelAdd = 0;
+ 		out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	nodeNumber = getNodeNumber(typcache, centroid, inRange, in->level);
+ 
+ 	/* Node for empty range is possibly not exist, create it if so */
+ 	if (nodeNumber == 3 && in->nNodes == 2)
+ 	{
+ 		out->resultType = spgAddNode;
+ 		out->result.addNode.nodeN = nodeNumber - 1;
+ 	}
+ 
+ 	Assert(nodeNumber <= in->nNodes);
+ 
+ 	/* Select node */
+ 	out->resultType = spgMatchNode;
+ 	out->result.matchNode.nodeN = nodeNumber - 1;
+ 	out->result.matchNode.levelAdd = 1;
+ 	out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Bound comparison for sorting.
+  */
+ static int
+ bound_cmp(const void *a, const void *b, void *arg)
+ {
+ 	RangeBound *ba = (RangeBound *) a;
+ 	RangeBound *bb = (RangeBound *) b;
+ 	TypeCacheEntry *typcache = (TypeCacheEntry *)arg;
+ 
+ 	return range_cmp_bounds(typcache, ba, bb);
+ }
+ 
+ /*
+  * Picksplit SP-GiST function: split ranges into nodes. Select "median"
+  * of bound corresponding to level number.
+  */
+ Datum
+ spg_range_kd_picksplit(PG_FUNCTION_ARGS)
+ {
+ 	spgPickSplitIn  *in = (spgPickSplitIn *) PG_GETARG_POINTER(0);
+ 	spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1);
+ 	int				 i, j, nonEmptyCount;
+ 	RangeType		*coord;
+ 	bool			 empty;
+ 	TypeCacheEntry  *typcache;
+ 
+ 	/* Use the median values of lower or upper bounds for split */
+ 	RangeBound *bounds, otherBound;
+ 
+ 	typcache = range_get_typcache(fcinfo, 
+ 							RangeTypeGetOid(DatumGetRangeType(in->datums[0])));
+ 
+ 	/* Allocate memory for bounds */
+ 	bounds = palloc(sizeof(RangeBound) * in->nTuples);
+ 	j = 0;
+ 	
+ 	/* Deserialize bounds of ranges, count non-empty ranges */
+ 	for (i = 0; i < in->nTuples; i++)
+ 	{
+ 		if (in->level % 2 == 0)
+ 			range_deserialize(typcache, DatumGetRangeType(in->datums[i]),
+ 									&bounds[j], &otherBound, &empty);
+ 		else
+ 			range_deserialize(typcache, DatumGetRangeType(in->datums[i]),
+ 									&otherBound, &bounds[j], &empty);
+ 			
+ 		if (!empty)
+ 			j++;
+ 	}
+ 	nonEmptyCount = j;
+ 	
+ 	/*
+ 	 * All the ranges are empty. We've nothing better than put all the ranges
+ 	 * into node 0. Non-empty range will be routed to node 1.
+ 	 */
+ 	if (nonEmptyCount == 0)
+ 	{
+ 		out->nNodes = 2;
+ 		out->hasPrefix = true;
+ 		/* Prefix is empty */
+ 		out->prefixDatum = RangeTypeGetDatum(
+ 								range_serialize(typcache, NULL, NULL, true));
+ 		out->nodeLabels = NULL;
+ 		
+ 		out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+ 		out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+ 		
+ 		/* Place all ranges into node 0 */
+ 		for (i = 0; i < in->nTuples; i++)
+ 		{
+ 			RangeType	   *range = DatumGetRangeType(in->datums[i]);
+ 
+ 			out->leafTupleDatums[i] = RangeTypeGetDatum(range);
+ 			out->mapTuplesToNodes[i] = 0;
+ 		}		
+ 		PG_RETURN_VOID();
+ 	}
+ 
+ 	/* Sort range bounds in order to find median */
+ 	qsort_arg(bounds, nonEmptyCount, sizeof(RangeBound), bound_cmp, typcache);
+ 	
+ 	
+ 	otherBound.inclusive = false;
+ 	otherBound.infinite = true;
+ 	otherBound.val = PointerGetDatum(NULL);
+ 	
+ 	/* 
+ 	 * Construct range representing coordinate of split, another bound of this
+ 	 * range is infinity for space saving.
+ 	 */
+ 	if (in->level % 2 == 0)
+ 	{
+ 		otherBound.lower = false;
+ 		coord = range_serialize(typcache, &bounds[nonEmptyCount / 2],
+ 			&otherBound, false);
+ 	}
+ 	else
+ 	{
+ 		otherBound.lower = true;
+ 		coord = range_serialize(typcache, &otherBound,
+ 			&bounds[nonEmptyCount / 2], false);
+ 	}
+ 	
+ 	
+ 	out->hasPrefix = true;
+ 	out->prefixDatum = RangeTypeGetDatum(coord);
+ 	
+ 	/* Create node for empty ranges only if it is actually needed */
+ 	out->nNodes = (nonEmptyCount == in->nTuples) ? 2 : 3;
+ 	out->nodeLabels = NULL;		/* we don't need node labels */
+ 
+ 	out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+ 	out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+ 
+ 	/* 
+ 	 * Add ranges to corresponding nodes according to their position relative
+ 	 * to split coordinate
+ 	 */
+ 	for (i = 0; i < in->nTuples; i++)
+ 	{
+ 		RangeType *range = DatumGetRangeType(in->datums[i]);
+ 		int2	   nodeNumber = getNodeNumber(typcache, coord, range, in->level);
+ 
+ 		out->leafTupleDatums[i] = RangeTypeGetDatum(range);
+ 		out->mapTuplesToNodes[i] = nodeNumber - 1;
+ 	}
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Check if two bounds are "connected", i.e. there are no values which satisfy
+  * both bounds and there are no values between the bounds.
+  */
+ static bool
+ bounds_connected(TypeCacheEntry *typcache, RangeBound lower, RangeBound upper)
+ {
+ 	int cmp = range_cmp_bound_values(typcache, &upper, &lower);
+ 	if (cmp < 0)
+ 	{
+ 		RangeType *r;
+ 		/* in a continuous subtype, there are assumed to be points between */
+ 		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
+ 			return false;
+ 		/* flip the inclusion flags */
+ 		upper.inclusive = !upper.inclusive;
+ 		lower.inclusive = !lower.inclusive;
+ 		/* change upper/lower labels to avoid Assert failures */
+ 		upper.lower = true;
+ 		lower.lower = false;
+ 		r = make_range(typcache, &upper, &lower, false);
+ 		PG_RETURN_BOOL(RangeIsEmpty(r));
+ 	}
+ 	else if (cmp == 0)
+ 	{
+ 		PG_RETURN_BOOL(upper.inclusive != lower.inclusive);
+ 	}
+ 	else
+ 	{
+ 		PG_RETURN_BOOL(false);
+ 	}
+ }
+ 
+ /*
+  * Inner consisted SP-GiST function: check which nodes are consistent with
+  * given set of queries.
+  */
+ Datum
+ spg_range_kd_inner_consistent(PG_FUNCTION_ARGS)
+ {
+ 	spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
+ 	spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
+ 	RangeBound		prevLower, prevUpper, coordLower, coordUpper;
+ 	RangeType	   *coord, *reconstructed = NULL;
+ 	bool			centroidEmpty;
+ 	TypeCacheEntry *typcache;
+ 	int				which, i;
+ 	bool			needPrevious = false;
+ 
+ 	Assert(in->hasPrefix);
+ 	coord = DatumGetRangeType(in->prefixDatum);
+ 
+ 	if (in->allTheSame)
+ 	{
+ 		/* Report that all nodes should be visited */
+ 		out->nNodes = in->nNodes;
+ 		out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
+ 		for (i = 0; i < in->nNodes; i++)
+ 			out->nodeNumbers[i] = i;
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	if (RangeIsEmpty(coord))
+ 	{
+ 		/*
+ 		 * Empty "coordinate". We can use only information about emptiness of
+ 		 * ranges in nodes.
+ 		 */
+ 		Assert(in->nNodes == 2);
+ 		
+ 		/* 
+ 		 * Nth bit of which variable means that (N - 1)th node should be
+ 		 * visited. Initially all bits are set. Bits of nodes which should be
+ 		 * skipped will be unset.
+ 		 */
+ 		which = (1 << 1) | (1 << 2);
+ 		for (i = 0; i < in->nkeys; i++)
+ 		{
+ 			StrategyNumber strategy;
+ 			bool empty;
+ 			
+ 			strategy = in->scankeys[i].sk_strategy;
+ 			
+ 			/*
+ 			 * The only strategy when second argument of operator is not
+ 			 * range is RANGESTRAT_CONTAINS_ELEM.
+ 			 */
+ 			if (strategy != RANGESTRAT_CONTAINS_ELEM)
+ 				empty = RangeIsEmpty(
+ 								DatumGetRangeType(in->scankeys[i].sk_argument));
+ 			
+ 			switch (strategy)
+ 			{
+ 				/* These strategies return false if any argument is empty */
+ 				case RANGESTRAT_BEFORE:
+ 				case RANGESTRAT_OVERLEFT:
+ 				case RANGESTRAT_OVERLAPS:
+ 				case RANGESTRAT_OVERRIGHT:
+ 				case RANGESTRAT_AFTER:
+ 				case RANGESTRAT_ADJACENT:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 						which &= (1 << 2);
+ 					break;
+ 				/* 
+ 				 * "Empty" range is contained in any range. Non-empty ranges
+ 				 * can be contained in only non-empty ranges.
+ 				 */
+ 				case RANGESTRAT_CONTAINS:
+ 					if (!empty)
+ 						which &= (1 << 2);
+ 					break;
+ 				case RANGESTRAT_CONTAINED_BY:
+ 					if (empty)
+ 						which &= (1 << 1);
+ 					break;
+ 				/* Empty range can't contain any element */
+ 				case RANGESTRAT_CONTAINS_ELEM:
+ 					which &= (1 << 2);
+ 					break;
+ 				case RANGESTRAT_EQ:
+ 					if (empty)
+ 						which &= (1 << 1);
+ 					else
+ 						which &= (1 << 2);
+ 					break;
+ 				default:
+ 					elog(ERROR, "unrecognized range strategy: %d", strategy);
+ 					break;
+ 			}
+ 			if (which == 0)
+ 				break;			/* no need to consider remaining conditions */
+ 		}
+ 	}
+ 	else
+ 	{
+ 
+ 		/* Coordinate is not empty, get information about it. */
+ 		typcache = range_get_typcache(fcinfo,
+ 								RangeTypeGetOid(DatumGetRangeType(coord)));
+ 		range_deserialize(typcache, coord, &coordLower, &coordUpper, 
+ 			&centroidEmpty);
+ 		
+ 		Assert(in->nNodes == 2 || in->nNodes == 3);
+ 		
+ 		/* 
+ 		 * Nth bit of which variable means that (N - 1)th node (Nth quadrant)
+ 		 * should be visited. Initially all bits are set. Bits of nodes which
+ 		 * should be skipped will be unset.
+ 		 */
+ 		which = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5);
+ 
+ 		for (i = 0; i < in->nkeys; i++)
+ 		{
+ 			StrategyNumber strategy;
+ 			RangeBound	lower, upper;
+ 			bool		empty;
+ 			RangeType  *range = NULL;
+ 
+ 			strategy = in->scankeys[i].sk_strategy;
+ 			
+ 			/*
+ 			 * Deserialize range if argument is range. The only strategy when
+ 			 * second argument of operator is not range is
+ 			 * RANGESTRAT_CONTAINS_ELEM.
+ 			 */
+ 			if (strategy != RANGESTRAT_CONTAINS_ELEM)
+ 			{
+ 				range = DatumGetRangeType(in->scankeys[i].sk_argument);
+ 				range_deserialize(typcache, range, &lower, &upper, &empty);
+ 			}
+ 			
+ 			switch (strategy)
+ 			{
+ 				bool		prevEmpty, prevPresent;
+ 				RangeType  *prevCentroid;
+ 				int			cmp1, cmp2, cmp3;
+ 				
+ 				/*
+ 				 * Range A is before range B if upper bound of A is lower than
+ 				 * lower bound of B. If upper bound "coordinate" is greater
+ 				 * or equal to lower bound of argument then no ranges before
+ 				 * argument can be contained in node 2.
+ 				 */
+ 				case RANGESTRAT_BEFORE:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &coordUpper,
+ 											&lower) >= 0 && in->level % 2 == 1)
+ 						which &= (1 << 1);
+ 					else
+ 						which &= (1 << 1) | (1 << 2);
+ 					break;
+ 				/*
+ 				 * Range A is overleft to range B if upper bound of A is lower
+ 				 * or equal to lower bound of B. If upper bound "coordinate" is
+ 				 * greater to upper bound of argument then no ranges overleft
+ 				 * argument can be contained in node 2.
+ 				 */
+ 				case RANGESTRAT_OVERLEFT:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &coordUpper,
+ 											&upper) > 0 && in->level % 2 == 1)
+ 						which = (1 << 1);
+ 					else
+ 						which &= (1 << 1) | (1 << 2);
+ 					break;
+ 				/*
+ 				 * Non-empty ranges overlaps if lower bound of each range is
+ 				 * lower or equal to upper bound of another ranges.
+ 				 */
+ 				case RANGESTRAT_OVERLAPS:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 					{
+ 						which &= (1 << 1) | (1 << 2);
+ 
+ 						if (in->level % 2 == 0)
+ 						{
+ 							/*
+ 							 * If lower bound "coordinate" is greater than upper
+ 							 * bound of argument then no overlapping ranges can
+ 							 * be in node 2.
+ 							 */
+ 							if (range_cmp_bounds(typcache, &coordLower,
+ 																	&upper) > 0)
+ 								which &= (1 << 1);
+ 						}
+ 						else
+ 						{
+ 							/*
+ 							 * If upper bound "coordinate" is lower or equal than
+ 							 * lower bound of argument then no overlapping
+ 							 * ranges can be in node 1.
+ 							 */
+ 							if (range_cmp_bounds(typcache, &coordUpper,
+ 																	&lower) <= 0)
+ 								which &= (1 << 2);
+ 						}
+ 					}
+ 					break;
+ 				/*
+ 				 * Range A is overright to range B if lower bound of A is upper
+ 				 * or equal to upper bound of B. If lower bound "coordinate" is
+ 				 * lower or equal to lower bound of argument then no ranges
+ 				 * overright argument can be contained in node 1.
+ 				 */
+ 				case RANGESTRAT_OVERRIGHT:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &coordLower,
+ 											&lower) <= 0 && in->level % 2 == 0)
+ 						which = (1 << 2);
+ 					else
+ 						which &= (1 << 1) | (1 << 2);
+ 					break;
+ 				/*
+ 				 * Range A is after range B if lower bound of A is greater than
+ 				 * upper bound of B. If lower bound "coordinate" is lower
+ 				 * or equal to upper bound of argument then no ranges after
+ 				 * argument can be contained in node 1.
+ 				 */
+ 				case RANGESTRAT_AFTER:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &coordLower, 
+ 											&upper) <= 0 && in->level % 2 == 0)
+ 						which &= (1 << 2);
+ 					else
+ 						which &= (1 << 1) | (1 << 2);
+ 					break;
+ 				/*
+ 				 * Ranges are adjacent if lower bound of one range is connected
+ 				 * to upper bound of another range.
+ 				 */
+ 				case RANGESTRAT_ADJACENT:
+ 					/* Deserialize previous "coordinate" if present. */
+ 					prevPresent = (in->reconstructedValue != (Datum) 0);
+ 					if (prevPresent)
+ 					{
+ 						prevCentroid = DatumGetRangeType(in->reconstructedValue);
+ 						range_deserialize(typcache, prevCentroid, &prevLower,
+ 							&prevUpper, &prevEmpty);
+ 					}
+ 					
+ 					if (in->level %2 == 0)
+ 					{
+ 						cmp2 = range_cmp_bounds(typcache, &upper, &coordLower);
+ 						if (prevPresent)
+ 						{
+ 							/* 
+ 							 * Do comparison with previous "coordinate" of
+ 							 * lower bound.
+ 							 */
+ 							cmp1 = range_cmp_bounds(typcache, &upper, &prevLower);
+ 							cmp3 = range_cmp_bounds(typcache, &coordLower,
+ 																		&prevLower);
+ 
+ 							/* 
+ 							* Check if lower bound of argument is not in
+ 							* a quadrant we visit in previous step.
+ 							*/
+ 							if ((cmp3 < 0 && cmp1 > 0) || (cmp3 > 0 && cmp1 < 0))
+ 								which = 0;
+ 						}
+ 
+ 						if (cmp2 >= 0)
+ 							which &= (1 >> 2);
+ 						else if (!bounds_connected(typcache, coordLower, upper))
+ 							which &= (1 >> 1);
+ 					}
+ 					else
+ 					{
+ 						cmp2 = range_cmp_bounds(typcache, &lower, &coordUpper);
+ 						if (prevPresent)
+ 						{
+ 							/* 
+ 							 * Do comparison with previous "coordinate" of
+ 							 * upper bound.
+ 							 */
+ 							cmp1 = range_cmp_bounds(typcache, &lower, &prevUpper);
+ 							cmp3 = range_cmp_bounds(typcache, &coordUpper, &prevUpper);
+ 							/* 
+ 							* Check if upper bound of argument is not in
+ 							* a quadrant we visit in previous step.
+ 							*/
+ 							if ((cmp3 < 0 && cmp1 > 0) || (cmp3 > 0 && cmp1 < 0))
+ 								which = 0;
+ 						}
+ 
+ 						if (cmp2 > 0)
+ 							which &= (1 >> 2);
+ 						else if (cmp2 < 0)
+ 							which &= (1 >> 1);
+ 					}
+ 
+ 					needPrevious = true;
+ 					break;
+ 				/*
+ 				 * Non-empty range A contains non-empty range B if lower bound
+ 				 * of A is lower or equal to lower bound of range B and upper
+ 				 * bound of range A is greater or equal to upper bound of range
+ 				 * A.
+ 				 */
+ 				case RANGESTRAT_CONTAINS:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 					{
+ 						which &= (1 << 1) | (1 << 2);
+ 						if (in->level % 2 == 0)
+ 						{
+ 							/* 
+ 							 * If lower bound "coordinate" is greater than lower
+ 							 * bound of argument then no ranges which contain
+ 							 * argument can be in node 2.
+ 							 */
+ 							if (range_cmp_bounds(typcache, &coordLower,
+ 																		&lower) > 0)
+ 								which &= (1 << 1);
+ 						}
+ 						else
+ 						{
+ 							/* 
+ 							 * If upper bound "coordinate" is lower or equal to
+ 							 * upper bound of argument then no ranges which
+ 							 * contain argument can be in node 1.
+ 							 */
+ 							if (range_cmp_bounds(typcache, &coordUpper,
+ 																	&upper) <= 0)
+ 								which &= (1 << 2);
+ 						}
+ 					}
+ 					break;
+ 				case RANGESTRAT_CONTAINED_BY:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 					{
+ 						which &= (1 << 1) | (1 << 2);
+ 						if (in->level % 2 == 0)
+ 						{
+ 							/* 
+ 							 * If lower bound "coordinate" is lower or equal to
+ 							 * lower bound of argument then no ranges which are
+ 							 * contained in argument can be in node 1.
+ 							 */
+ 							if (range_cmp_bounds(typcache, &coordLower,
+ 																&lower) <= 0)
+ 								which &= (1 << 2);
+ 						}
+ 						else
+ 						{
+ 							/* 
+ 							 * If upper bound "coordinate" is greater than upper
+ 							 * bound of argument then no ranges which are
+ 							 * contained in argument can be in node 2.
+ 							 */
+ 							if (range_cmp_bounds(typcache, &coordUpper,
+ 																	&upper) > 0)
+ 								which &= (1 << 1);
+ 						}
+ 					}
+ 					break;
+ 				case RANGESTRAT_CONTAINS_ELEM:
+ 					which &= (1 << 1) | (1 << 2);
+ 					
+ 					if (in->level % 2 == 0)
+ 					{
+ 						/* 
+ 						 * Construct bound to pass then to bound comparison
+ 						 * functions
+ 						 */
+ 						lower.inclusive = true;
+ 						lower.infinite = false;
+ 						lower.lower = true;
+ 						lower.val = in->scankeys[i].sk_argument;
+ 						
+ 						/*
+ 						 * If lower bound "coordinate" is greater than element
+ 						 * then ranges containing element can't be in node 2.
+ 						 */
+ 						if (range_cmp_bound_values(typcache, &coordLower,
+ 																	&lower) > 0)
+ 								which &= (1 << 1);
+ 					}
+ 					else
+ 					{
+ 						/* 
+ 						 * Construct bound to pass then to bound comparison
+ 						 * functions
+ 						 */
+ 						upper.inclusive = true;
+ 						upper.infinite = false;
+ 						upper.lower = false;
+ 						upper.val = in->scankeys[i].sk_argument;
+ 						
+ 						/*
+ 						 * If upper bound "coordinate" is lower or equal than
+ 						 * element then ranges containing element can't be in
+ 						 * node 1.
+ 						 */
+ 						if (range_cmp_bound_values(typcache, &coordUpper,
+ 																&upper) <= 0)
+ 								which &= (1 << 2);
+ 					}
+ 					
+ 					break;
+ 				/*
+ 				 * Equal range can be only in the same node where argument
+ 				 * would be placed to.
+ 				 */
+ 				case RANGESTRAT_EQ:
+ 					which &= (1 << getNodeNumber(typcache, coord, range, 
+ 																	in->level));
+ 					break;
+ 				default:
+ 					elog(ERROR, "unrecognized range strategy: %d", strategy);
+ 					break;
+ 			}
+ 
+ 			if (which == 0)
+ 				break;			/* no need to consider remaining conditions */
+ 		}
+ 	}
+ 
+ 	/* We must descend into the node(s) identified by which */
+ 	out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
+ 	out->levelAdds = (int *) palloc(sizeof(int) * in->nNodes);
+ 	
+ 	/* Need to save this coordinate in reconstructed value for next checks? */
+ 	if (needPrevious)
+ 	{
+ 		out->reconstructedValues = (Datum *) palloc(sizeof(Datum) * in->nNodes);
+ 		if (in->level == 0)
+ 		{
+ 			reconstructed = coord;
+ 		}
+ 		else
+ 		{
+ 			/* Replace corresponding bound of current reconstructed value*/
+ 			if (in->level % 2 == 0)
+ 				reconstructed = range_serialize(typcache, &coordLower, 
+ 															&prevUpper, false);
+ 			else
+ 				reconstructed = range_serialize(typcache, &coordUpper, 
+ 															&prevLower, false);
+ 		}		
+ 	}
+ 	out->nNodes = 0;
+ 	for (i = 1; i <= in->nNodes; i++)
+ 	{
+ 		if (which & (1 << i))
+ 		{
+ 			/* Save this "coordinate" if needed */
+ 			if (needPrevious)
+ 				out->reconstructedValues[out->nNodes] = 
+ 											RangeTypeGetDatum(reconstructed);
+ 			out->nodeNumbers[out->nNodes++] = i - 1;
+ 			out->levelAdds[out->nNodes] = RangeIsEmpty(coord) ? 0 : 1;
+ 		}
+ 	}
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Leaf consistent SP-GiST function: check leaf value against query using
+  * corresponding function.
+  */
+ Datum
+ spg_range_kd_leaf_consistent(PG_FUNCTION_ARGS)
+ {
+ 	spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0);
+ 	spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1);
+ 	bool		res;
+ 	int			i;
+ 
+ 	/* all tests are exact */
+ 	out->recheck = false;
+ 
+ 	/* leafDatum is what it is... */
+ 	out->leafValue = in->leafDatum;
+ 
+ 	/* Perform the required comparison(s) */
+ 	res = true;
+ 	for (i = 0; i < in->nkeys; i++)
+ 	{
+ 		PGFunction	proc;
+ 		
+ 		/* Find the function which is corresponding to the scan strategy */
+ 		switch (in->scankeys[i].sk_strategy)
+ 		{
+ 			case RANGESTRAT_BEFORE:
+ 				proc = range_before;
+ 				break;
+ 			case RANGESTRAT_OVERLEFT:
+ 				proc = range_overleft;
+ 				break;
+ 			case RANGESTRAT_OVERLAPS:
+ 				proc = range_overlaps;
+ 				break;
+ 			case RANGESTRAT_OVERRIGHT:
+ 				proc = range_overright;
+ 				break;
+ 			case RANGESTRAT_AFTER:
+ 				proc = range_after;
+ 				break;
+ 			case RANGESTRAT_ADJACENT:
+ 				proc = range_adjacent;
+ 				break;
+ 			case RANGESTRAT_CONTAINS:
+ 				proc = range_contains;
+ 				break;
+ 			case RANGESTRAT_CONTAINED_BY:
+ 				proc = range_contained_by;
+ 				break;
+ 			case RANGESTRAT_CONTAINS_ELEM:
+ 				proc = range_contains_elem;
+ 				break;
+ 			case RANGESTRAT_EQ:
+ 				proc = range_eq;
+ 				break;
+ 			default:
+ 				elog(ERROR, "unrecognized range strategy: %d", 
+ 												in->scankeys[i].sk_strategy);
+ 				proc = InvalidOid;
+ 				break;
+ 		}
+ 		res = DatumGetBool(TrickFunctionCall2(proc, fcinfo->flinfo,
+ 								in->leafDatum, in->scankeys[i].sk_argument));
+ 		
+ 		/* If leaf datum don't match to one query, we can don't check another */
+ 		if (!res)
+ 			break;
+ 	}
+ 
+ 	PG_RETURN_BOOL(res);
+ }
*** a/src/include/catalog/pg_amop.h
--- b/src/include/catalog/pg_amop.h
***************
*** 767,770 **** DATA(insert (	4017   25 25 12 s	665 4000 0 ));
--- 767,784 ----
  DATA(insert (	4017   25 25 14 s	667 4000 0 ));
  DATA(insert (	4017   25 25 15 s	666 4000 0 ));
  
+ /*
+  * SP-GiST range_ops
+  */
+ DATA(insert (	3475   3831 3831 1 s	3893 4000 0 ));
+ DATA(insert (	3475   3831 3831 2 s	3895 4000 0 ));
+ DATA(insert (	3475   3831 3831 3 s	3888 4000 0 ));
+ DATA(insert (	3475   3831 3831 4 s	3896 4000 0 ));
+ DATA(insert (	3475   3831 3831 5 s	3894 4000 0 ));
+ DATA(insert (	3475   3831 3831 6 s	3897 4000 0 ));
+ DATA(insert (	3475   3831 3831 7 s	3890 4000 0 ));
+ DATA(insert (	3475   3831 3831 8 s	3892 4000 0 ));
+ DATA(insert (	3475   3831 2283 16 s	3889 4000 0 ));
+ DATA(insert (	3475   3831 3831 18 s	3882 4000 0 ));
+ 
  #endif   /* PG_AMOP_H */
*** a/src/include/catalog/pg_amproc.h
--- b/src/include/catalog/pg_amproc.h
***************
*** 373,377 **** DATA(insert (	4017   25 25 2 4028 ));
--- 373,382 ----
  DATA(insert (	4017   25 25 3 4029 ));
  DATA(insert (	4017   25 25 4 4030 ));
  DATA(insert (	4017   25 25 5 4031 ));
+ DATA(insert (	3475   3831 3831 1 3476 ));
+ DATA(insert (	3475   3831 3831 2 3477 ));
+ DATA(insert (	3475   3831 3831 3 3478 ));
+ DATA(insert (	3475   3831 3831 4 3479 ));
+ DATA(insert (	3475   3831 3831 5 3480 ));
  
  #endif   /* PG_AMPROC_H */
*** a/src/include/catalog/pg_opclass.h
--- b/src/include/catalog/pg_opclass.h
***************
*** 223,228 **** DATA(insert (	783		tsquery_ops			PGNSP PGUID 3702  3615 t 20 ));
--- 223,229 ----
  DATA(insert (	403		range_ops			PGNSP PGUID 3901  3831 t 0 ));
  DATA(insert (	405		range_ops			PGNSP PGUID 3903  3831 t 0 ));
  DATA(insert (	783		range_ops			PGNSP PGUID 3919  3831 t 0 ));
+ DATA(insert (	4000	range_ops			PGNSP PGUID 3475  3831 t 0 ));
  DATA(insert (	4000	quad_point_ops		PGNSP PGUID 4015  600 t 0 ));
  DATA(insert (	4000	kd_point_ops		PGNSP PGUID 4016  600 f 0 ));
  DATA(insert (	4000	text_ops			PGNSP PGUID 4017  25 t 0 ));
*** a/src/include/catalog/pg_opfamily.h
--- b/src/include/catalog/pg_opfamily.h
***************
*** 142,147 **** DATA(insert OID = 3702 (	783		tsquery_ops		PGNSP PGUID ));
--- 142,148 ----
  DATA(insert OID = 3901 (	403		range_ops		PGNSP PGUID ));
  DATA(insert OID = 3903 (	405		range_ops		PGNSP PGUID ));
  DATA(insert OID = 3919 (	783		range_ops		PGNSP PGUID ));
+ DATA(insert OID = 3475 (	4000	range_ops		PGNSP PGUID ));
  DATA(insert OID = 4015 (	4000	quad_point_ops	PGNSP PGUID ));
  DATA(insert OID = 4016 (	4000	kd_point_ops	PGNSP PGUID ));
  DATA(insert OID = 4017 (	4000	text_ops		PGNSP PGUID ));
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4645,4650 **** DESCR("SP-GiST support for suffix tree over text");
--- 4645,4661 ----
  DATA(insert OID = 4031 (  spg_text_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_text_leaf_consistent _null_ _null_ _null_ ));
  DESCR("SP-GiST support for suffix tree over text");
  
+ DATA(insert OID = 3476 (  spg_range_kd_config	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_kd_config _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for k-d tree over range");
+ DATA(insert OID = 3477 (  spg_range_kd_choose	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_kd_choose _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for k-d tree over range");
+ DATA(insert OID = 3478 (  spg_range_kd_picksplit	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_kd_picksplit _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for k-d tree over range");
+ DATA(insert OID = 3479 (  spg_range_kd_inner_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_kd_inner_consistent _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for k-d tree over range");
+ DATA(insert OID = 3480 (  spg_range_kd_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_range_kd_leaf_consistent _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for k-d tree over range");
+ 
  
  /*
   * Symbolic values for provolatile column: these indicate whether the result
*** a/src/include/utils/rangetypes.h
--- b/src/include/utils/rangetypes.h
***************
*** 75,80 **** typedef struct
--- 75,93 ----
  #define PG_GETARG_RANGE_COPY(n)		DatumGetRangeTypeCopy(PG_GETARG_DATUM(n))
  #define PG_RETURN_RANGE(x)			return RangeTypeGetDatum(x)
  
+ /* Operator strategy numbers used in the GiST range opclass */
+ /* Numbers are chosen to match up operator names with existing usages */
+ #define RANGESTRAT_BEFORE				1
+ #define RANGESTRAT_OVERLEFT				2
+ #define RANGESTRAT_OVERLAPS				3
+ #define RANGESTRAT_OVERRIGHT			4
+ #define RANGESTRAT_AFTER				5
+ #define RANGESTRAT_ADJACENT				6
+ #define RANGESTRAT_CONTAINS				7
+ #define RANGESTRAT_CONTAINED_BY			8
+ #define RANGESTRAT_CONTAINS_ELEM		16
+ #define RANGESTRAT_EQ					18
+ 
  /*
   * prototypes for functions defined in rangetypes.c
   */
***************
*** 166,171 **** extern int range_cmp_bound_values(TypeCacheEntry *typcache, RangeBound *b1,
--- 179,186 ----
  extern RangeType *make_empty_range(TypeCacheEntry *typcache);
  
  /* GiST support (in rangetypes_gist.c) */
+ extern Datum TrickFunctionCall2(PGFunction proc, FmgrInfo *flinfo, Datum arg1, 
+ 																	Datum arg2);
  extern Datum range_gist_consistent(PG_FUNCTION_ARGS);
  extern Datum range_gist_compress(PG_FUNCTION_ARGS);
  extern Datum range_gist_decompress(PG_FUNCTION_ARGS);
***************
*** 174,177 **** extern Datum range_gist_penalty(PG_FUNCTION_ARGS);
--- 189,199 ----
  extern Datum range_gist_picksplit(PG_FUNCTION_ARGS);
  extern Datum range_gist_same(PG_FUNCTION_ARGS);
  
+ /* SP-GiST k-d tree support (in rangetypes_spgistkd.c */
+ Datum spg_range_kd_config(PG_FUNCTION_ARGS);
+ Datum spg_range_kd_choose(PG_FUNCTION_ARGS);
+ Datum spg_range_kd_picksplit(PG_FUNCTION_ARGS);
+ Datum spg_range_kd_inner_consistent(PG_FUNCTION_ARGS);
+ Datum spg_range_kd_leaf_consistent(PG_FUNCTION_ARGS);
+ 
  #endif   /* RANGETYPES_H */
#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alexander Korotkov (#3)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

Alexander Korotkov <aekorotkov@gmail.com> writes:

On Thu, Jun 21, 2012 at 11:12 AM, Heikki Linnakangas <
heikki.linnakangas@enterprisedb.com> wrote:

I don't think we want to expose TrickFunctionCall2(). Not with that name,
anyway. Perhaps we should refactor the functions called this way,
range_adjacent, range_overlaps etc., to have internal counterparts that can
be called without FunctionCall(). Like:

I like idea of replacing TrickFunctionCall2 with internal function. Do you
think I should post a separate patch for existing GiST code?

+1 ... that was a pretty grotty hack, so let's get rid of it if we can.
It's still going to require some documentation though I think.

regards, tom lane

#6Jeff Davis
pgsql@j-davis.com
In reply to: Alexander Korotkov (#1)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Thu, 2012-06-14 at 02:56 +0400, Alexander Korotkov wrote:

Hackers,

attached patch implements quad-tree on ranges. Some performance
results in comparison with current GiST indexing.
Index creation is slightly slower. Probably, it need some
investigation. Search queries on SP-GiST use much more pages. However
this comparison can be not really correct, because SP-GiST can pin
same buffer several times during one scan. In CPU search queries on
SP-GiST seems to be slightly faster. Dramatical difference in "column
<@ const" query is thanks to 2d-mapping.

Looking at this patch now. I see that it fails the opr_sanity test (on
master), can you look into that?

It looks very promising from a performance standpoint. I think the "col
<@ const" query will be a common query; and I also think that pattern
will be useful to restrict a large table down to something more
manageable.

In the bounds_connected function, it might make more sense to use the
word "adjacent" which I already used for ordinary ranges, rather than
using the new word "connected".

Also, I'm getting a little confused switching between thinking in terms
of "X and Y" and "lower and upper" (particularly since lower and upper
can be confused with > or <). I don't have a suggestion yet how to
clarify that, but it might be good to use the spatial terminology in
more places and avoid lower/upper except where needed.

Please excuse the slow review, I'm catching up on the SP-GiST API.

Regards,
Jeff Davis

#7Jeff Davis
pgsql@j-davis.com
In reply to: Alexander Korotkov (#1)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Thu, 2012-06-14 at 02:56 +0400, Alexander Korotkov wrote:

Hackers,

attached patch implements quad-tree on ranges. Some performance
results in comparison with current GiST indexing.
Index creation is slightly slower. Probably, it need some
investigation. Search queries on SP-GiST use much more pages. However
this comparison can be not really correct, because SP-GiST can pin
same buffer several times during one scan. In CPU search queries on
SP-GiST seems to be slightly faster. Dramatical difference in "column
<@ const" query is thanks to 2d-mapping.

More comments:

* Minor rebase is required (simple int2 -> int16).

* Perhaps I'm mistaken, but the following code in getQuadrant() looks
wrong to me, shouldn't the 1 and 2 be reversed?

if (range_cmp_bounds(typcache, &upper, &centroidUpper) >= 0)
return 1;
else
return 2;

* in the "choose" method, why does in->allTheSame unconditionally match?
Why not split? Similarly, why does inner_consistent always match when
the nodes are allTheSame?

* It's a little confusing having empty prefixes mean that empty range go
to node0, and non-empty ranges meaning that empty ranges go to node4
(quadrant 5). Why can't there just always be 5 nodes, and iff all the
ranges are empty, then the prefix is NULL?

And for that matter, let's let the quadrant equal the node number, and
have the empty ranges in node0. I don't see much point in always
subtracting 1 from the quadrant number.

Regards,
Jeff Davis

#8Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#7)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Mon, 2012-07-02 at 23:47 -0700, Jeff Davis wrote:

On Thu, 2012-06-14 at 02:56 +0400, Alexander Korotkov wrote:

Hackers,

attached patch implements quad-tree on ranges. Some performance
results in comparison with current GiST indexing.
Index creation is slightly slower. Probably, it need some
investigation. Search queries on SP-GiST use much more pages. However
this comparison can be not really correct, because SP-GiST can pin
same buffer several times during one scan. In CPU search queries on
SP-GiST seems to be slightly faster. Dramatical difference in "column
<@ const" query is thanks to 2d-mapping.

Also, it would be helpful to add a couple tests to rangetypes.sql.

Regards,
Jeff Davis

#9Jeff Davis
pgsql@j-davis.com
In reply to: Jeff Davis (#7)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Mon, 2012-07-02 at 23:47 -0700, Jeff Davis wrote:

* Perhaps I'm mistaken, but the following code in getQuadrant() looks
wrong to me, shouldn't the 1 and 2 be reversed?

if (range_cmp_bounds(typcache, &upper, &centroidUpper) >= 0)
return 1;
else
return 2;

Oops, looks like I was mistaken. The code looks fine to me now.

Regards,
Jeff Davis

#10Alexander Korotkov
aekorotkov@gmail.com
In reply to: Jeff Davis (#8)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Tue, Jul 3, 2012 at 10:51 AM, Jeff Davis <pgsql@j-davis.com> wrote:

On Mon, 2012-07-02 at 23:47 -0700, Jeff Davis wrote:

On Thu, 2012-06-14 at 02:56 +0400, Alexander Korotkov wrote:

Hackers,

attached patch implements quad-tree on ranges. Some performance
results in comparison with current GiST indexing.
Index creation is slightly slower. Probably, it need some
investigation. Search queries on SP-GiST use much more pages. However
this comparison can be not really correct, because SP-GiST can pin
same buffer several times during one scan. In CPU search queries on
SP-GiST seems to be slightly faster. Dramatical difference in "column
<@ const" query is thanks to 2d-mapping.

Also, it would be helpful to add a couple tests to rangetypes.sql.

Thank you for review! Now I'm working on detailed performance benchmarks
for different opclasses. I hope to finish it in this week. Then we would
see which opclasses we're really need and nail down issues you've pointed.

------
With best regards,
Alexander Korotkov.

#11Alexander Korotkov
aekorotkov@gmail.com
In reply to: Jeff Davis (#8)
1 attachment(s)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Tue, Jul 3, 2012 at 10:51 AM, Jeff Davis <pgsql@j-davis.com> wrote:

Also, it would be helpful to add a couple tests to rangetypes.sql.

New version of patch is attached.

1) Idea of having different node numbers is that nodes takes some space
(check spgFormInnerTuple). I've rethink this idea a little because adding
of node without label just didn't work :). Only root inner index tuple have
5 nodes, others have 4. Thereby all empty ranges are branched already at
root inner index tuple.

2) Empty prefix datum replaced with absence of prefix datum.

3) int2 replaced with int16.

4) I've added some tests which duplicates tests for GiST.

5) "connected" replaced with "adjacent"

6) allTheSame nodes is node created by SP-GiST core when it decide than
result of picksplit method is not good enough. It divides tuples
arbitrarily. So we have to visit all the nodes in this case during scan.
See:
http://www.postgresql.org/docs/devel/static/spgist-implementation.html#SPGIST-ALL-THE-SAME
.
Currently in-core SP-GiST opclasses behaves similarly.

I didn't decide how to rethink terms in comments yet :(

------
With best regards,
Alexander Korotkov.

Attachments:

range_spgist_quad-0.3.patchapplication/octet-stream; name=range_spgist_quad-0.3.patchDownload
*** a/src/backend/utils/adt/Makefile
--- b/src/backend/utils/adt/Makefile
***************
*** 30,36 **** OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
  	tsvector.o tsvector_op.o tsvector_parser.o \
! 	txid.o uuid.o windowfuncs.o xml.o
  
  like.o: like.c like_match.c
  
--- 30,36 ----
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
  	tsvector.o tsvector_op.o tsvector_parser.o \
! 	txid.o uuid.o windowfuncs.o xml.o rangetypes_spgist.o
  
  like.o: like.c like_match.c
  
*** a/src/backend/utils/adt/rangetypes_gist.c
--- b/src/backend/utils/adt/rangetypes_gist.c
***************
*** 20,39 ****
  #include "utils/datum.h"
  #include "utils/rangetypes.h"
  
- 
- /* Operator strategy numbers used in the GiST range opclass */
- /* Numbers are chosen to match up operator names with existing usages */
- #define RANGESTRAT_BEFORE				1
- #define RANGESTRAT_OVERLEFT				2
- #define RANGESTRAT_OVERLAPS				3
- #define RANGESTRAT_OVERRIGHT			4
- #define RANGESTRAT_AFTER				5
- #define RANGESTRAT_ADJACENT				6
- #define RANGESTRAT_CONTAINS				7
- #define RANGESTRAT_CONTAINED_BY			8
- #define RANGESTRAT_CONTAINS_ELEM		16
- #define RANGESTRAT_EQ					18
- 
  /*
   * Range class properties used to segregate different classes of ranges in
   * GiST.  Each unique combination of properties is a class.  CLS_EMPTY cannot
--- 20,25 ----
***************
*** 792,798 **** range_super_union(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
   * part of the relcache entry for the index, typically) this essentially
   * eliminates lookup overhead during operations on a GiST range index.
   */
! static Datum
  TrickFunctionCall2(PGFunction proc, FmgrInfo *flinfo, Datum arg1, Datum arg2)
  {
  	FunctionCallInfoData fcinfo;
--- 778,784 ----
   * part of the relcache entry for the index, typically) this essentially
   * eliminates lookup overhead during operations on a GiST range index.
   */
! Datum
  TrickFunctionCall2(PGFunction proc, FmgrInfo *flinfo, Datum arg1, Datum arg2)
  {
  	FunctionCallInfoData fcinfo;
*** /dev/null
--- b/src/backend/utils/adt/rangetypes_spgist.c
***************
*** 0 ****
--- 1,795 ----
+ /*-------------------------------------------------------------------------
+  *
+  * rangetypes_spgist.c
+  *	  implementation of quad tree over ranges mapped to 2d-points for SP-GiST
+  *
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * IDENTIFICATION
+  *			src/backend/utils/adt/rangetypes_spgist.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #include "postgres.h"
+ 
+ #include "access/spgist.h"
+ #include "access/skey.h"
+ #include "catalog/pg_type.h"
+ #include "utils/builtins.h"
+ #include "utils/datum.h"
+ #include "utils/rangetypes.h"
+ 
+ Datum spg_range_quad_config(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_choose(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_picksplit(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_inner_consistent(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_leaf_consistent(PG_FUNCTION_ARGS);
+ 
+ static int16 getQuadrant(TypeCacheEntry *typcache, RangeType *centroid, RangeType *tst);
+ static int bound_cmp(const void *a, const void *b, void *arg);
+ static bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound lower, RangeBound upper);
+ 
+ 
+ /*
+  * Config SP-GiST interface function.
+  */
+ Datum
+ spg_range_quad_config(PG_FUNCTION_ARGS)
+ {
+ 	/* spgConfigIn *cfgin = (spgConfigIn *) PG_GETARG_POINTER(0); */
+ 	spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1);
+ 
+ 	cfg->prefixType = ANYRANGEOID;
+ 	cfg->labelType = VOIDOID;	/* we don't need node labels */
+ 	cfg->canReturnData = true;
+ 	cfg->longValuesOK = false;
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Determine which quadrant a 2d-mapped range falls into, relative to the
+  * centroid. Lower bound of range assumed to be the horizontal axis. Upper
+  * bound of range assumed to be the vertical axis.
+  *
+  * Quadrants are identified like this:
+  *
+  *	 4	|  1
+  *	----+-----
+  *	 3	|  2
+  *
+  * Ranges on one of the axes are taken to lie in the quadrant with higher value
+  * along perpendicular axis. Range equal to centroid is taken to lie in the
+  * quadrant 1. Empty ranges are taken to lie in the quadrant 5.
+  */
+ static int16
+ getQuadrant(TypeCacheEntry *typcache, RangeType *centroid, RangeType *tst)
+ {
+ 	RangeBound centroidLower, centroidUpper, lower, upper;
+ 	bool centroidEmpty, empty;
+ 	
+ 	range_deserialize(typcache, centroid, &centroidLower, &centroidUpper, 
+ 																&centroidEmpty);
+ 	range_deserialize(typcache, tst, &lower, &upper, &empty);
+ 	
+ 	if (empty)
+ 		return 5;
+ 	
+ 	if (range_cmp_bounds(typcache, &lower, &centroidLower) >= 0)
+ 	{
+ 		if (range_cmp_bounds(typcache, &upper, &centroidUpper) >= 0)
+ 			return 1;
+ 		else
+ 			return 2;		
+ 	}
+ 	else
+ 	{
+ 		if (range_cmp_bounds(typcache, &upper, &centroidUpper) >= 0)
+ 			return 4;
+ 		else
+ 			return 3;		
+ 	}
+ 
+ 	elog(ERROR, "getQuadrant: impossible case");
+ 	return 0;
+ }
+ 
+ 
+ /*
+  * Choose SP-GiST function: choose path for addition of new range.
+  */
+ Datum
+ spg_range_quad_choose(PG_FUNCTION_ARGS)
+ {
+ 	spgChooseIn	   *in = (spgChooseIn *) PG_GETARG_POINTER(0);
+ 	spgChooseOut   *out = (spgChooseOut *) PG_GETARG_POINTER(1);
+ 	RangeType	   *inRange = DatumGetRangeType(in->datum), *centroid;
+ 	int16			quadrant;
+ 	TypeCacheEntry *typcache;
+ 
+ 	if (in->allTheSame)
+ 	{
+ 		out->resultType = spgMatchNode;
+ 		/* nodeN will be set by core */
+ 		out->result.matchNode.levelAdd = 0;
+ 		out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(inRange));
+ 
+ 	/*
+ 	 * Empty prefix datum divides ranges by empty sign. All empty ranges are
+ 	 * taken into node 0, all non-empty ranges are taken into node 1.
+ 	 */
+ 	if (!in->hasPrefix)
+ 	{
+ 		out->resultType = spgMatchNode;
+ 		if (RangeIsEmpty(inRange))
+ 			out->result.matchNode.nodeN = 0;
+ 		else
+ 			out->result.matchNode.nodeN = 1;
+ 		out->result.matchNode.levelAdd = 1;
+ 		out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	centroid = DatumGetRangeType(in->prefixDatum);
+ 	quadrant = getQuadrant(typcache, centroid, inRange);
+ 
+ 	Assert(quadrant <= in->nNodes);
+ 
+ 	/* Select node matching to quadrant number */
+ 	out->resultType = spgMatchNode;
+ 	out->result.matchNode.nodeN = quadrant - 1;
+ 	out->result.matchNode.levelAdd = 1;
+ 	out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Bound comparison for sorting.
+  */
+ static int
+ bound_cmp(const void *a, const void *b, void *arg)
+ {
+ 	RangeBound *ba = (RangeBound *) a;
+ 	RangeBound *bb = (RangeBound *) b;
+ 	TypeCacheEntry *typcache = (TypeCacheEntry *)arg;
+ 
+ 	return range_cmp_bounds(typcache, ba, bb);
+ }
+ 
+ /*
+  * Picksplit SP-GiST function: split ranges into nodes. Select "centroid"
+  * range and distribute ranges according to quadrants.
+  */
+ Datum
+ spg_range_quad_picksplit(PG_FUNCTION_ARGS)
+ {
+ 	spgPickSplitIn  *in = (spgPickSplitIn *) PG_GETARG_POINTER(0);
+ 	spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1);
+ 	int				 i, j, nonEmptyCount;
+ 	RangeType		*centroid;
+ 	bool			 empty;
+ 	TypeCacheEntry  *typcache;
+ 
+ 	/* Use the median values of lower and upper bounds as the centroid range */
+ 	RangeBound *lowerBounds, *upperBounds;
+ 
+ 	typcache = range_get_typcache(fcinfo, 
+ 							RangeTypeGetOid(DatumGetRangeType(in->datums[0])));
+ 
+ 	/* Allocate memory for bounds */
+ 	lowerBounds = palloc(sizeof(RangeBound) * in->nTuples);
+ 	upperBounds = palloc(sizeof(RangeBound) * in->nTuples);
+ 	j = 0;
+ 	
+ 	/* Deserialize bounds of ranges, count non-empty ranges */
+ 	for (i = 0; i < in->nTuples; i++)
+ 	{
+ 		range_deserialize(typcache, DatumGetRangeType(in->datums[i]),
+ 									&lowerBounds[j], &upperBounds[j], &empty);
+ 		if (!empty)
+ 			j++;
+ 	}
+ 	nonEmptyCount = j;
+ 	
+ 	/*
+ 	 * All the ranges are empty. We've nothing better than put all the ranges
+ 	 * into node 0. Non-empty range will be routed to node 1.
+ 	 */
+ 	if (nonEmptyCount == 0)
+ 	{
+ 		out->nNodes = 2;
+ 		out->hasPrefix = false;
+ 		/* Prefix is empty */
+ 		out->prefixDatum = PointerGetDatum(NULL);
+ 		out->nodeLabels = NULL;
+ 		
+ 		out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+ 		out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+ 		
+ 		/* Place all ranges into node 0 */
+ 		for (i = 0; i < in->nTuples; i++)
+ 		{
+ 			RangeType	   *range = DatumGetRangeType(in->datums[i]);
+ 
+ 			out->leafTupleDatums[i] = RangeTypeGetDatum(range);
+ 			out->mapTuplesToNodes[i] = 0;
+ 		}		
+ 		PG_RETURN_VOID();
+ 	}
+ 
+ 	/* Sort range bounds in order to find medians */
+ 	qsort_arg(lowerBounds, nonEmptyCount, sizeof(RangeBound), 
+ 														bound_cmp, typcache);
+ 	qsort_arg(upperBounds, nonEmptyCount, sizeof(RangeBound), 
+ 														bound_cmp, typcache);
+ 
+ 	/* Construct "centroid" range from medians of lower and upper bounds */
+ 	centroid = range_serialize(typcache, &lowerBounds[nonEmptyCount / 2],
+ 		&upperBounds[nonEmptyCount / 2], false);
+ 	
+ 	
+ 	out->hasPrefix = true;
+ 	out->prefixDatum = RangeTypeGetDatum(centroid);
+ 	
+ 	/* Create node for empty ranges only if it is a root node */
+ 	out->nNodes = (in->level == 0) ? 5 : 4;
+ 	out->nodeLabels = NULL;		/* we don't need node labels */
+ 
+ 	out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+ 	out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+ 
+ 	/* 
+ 	 * Add ranges to corresponding nodes according to quadrants relative to
+ 	 * "centroid" range.
+ 	 */
+ 	for (i = 0; i < in->nTuples; i++)
+ 	{
+ 		RangeType	   *range = DatumGetRangeType(in->datums[i]);
+ 		int16			quadrant = getQuadrant(typcache, centroid, range);
+ 
+ 		out->leafTupleDatums[i] = RangeTypeGetDatum(range);
+ 		out->mapTuplesToNodes[i] = quadrant - 1;
+ 	}
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Check if two bounds are "adjacent", i.e. there are no values which satisfy
+  * both bounds and there are no values between the bounds.
+  */
+ static bool
+ bounds_adjacent(TypeCacheEntry *typcache, RangeBound lower, RangeBound upper)
+ {
+ 	int cmp = range_cmp_bound_values(typcache, &upper, &lower);
+ 	if (cmp < 0)
+ 	{
+ 		RangeType *r;
+ 		/* in a continuous subtype, there are assumed to be points between */
+ 		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
+ 			return false;
+ 		/* flip the inclusion flags */
+ 		upper.inclusive = !upper.inclusive;
+ 		lower.inclusive = !lower.inclusive;
+ 		/* change upper/lower labels to avoid Assert failures */
+ 		upper.lower = true;
+ 		lower.lower = false;
+ 		r = make_range(typcache, &upper, &lower, false);
+ 		PG_RETURN_BOOL(RangeIsEmpty(r));
+ 	}
+ 	else if (cmp == 0)
+ 	{
+ 		PG_RETURN_BOOL(upper.inclusive != lower.inclusive);
+ 	}
+ 	else
+ 	{
+ 		PG_RETURN_BOOL(false);
+ 	}
+ }
+ 
+ /*
+  * Inner consisted SP-GiST function: check which nodes are consistent with
+  * given set of queries.
+  */
+ Datum
+ spg_range_quad_inner_consistent(PG_FUNCTION_ARGS)
+ {
+ 	spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
+ 	spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
+ 	int			which;
+ 	int			i;
+ 	bool		needPrevious = false;
+ 
+ 	if (in->allTheSame)
+ 	{
+ 		/* Report that all nodes should be visited */
+ 		out->nNodes = in->nNodes;
+ 		out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
+ 		for (i = 0; i < in->nNodes; i++)
+ 			out->nodeNumbers[i] = i;
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	if (!in->hasPrefix)
+ 	{
+ 		/*
+ 		 * Empty "centroid". We can use only information about emptiness of
+ 		 * ranges in nodes.
+ 		 */
+ 		Assert(in->nNodes == 2);
+ 		
+ 		/* 
+ 		 * Nth bit of which variable means that (N - 1)th node should be
+ 		 * visited. Initially all bits are set. Bits of nodes which should be
+ 		 * skipped will be unset.
+ 		 */
+ 		which = (1 << 1) | (1 << 2);
+ 		for (i = 0; i < in->nkeys; i++)
+ 		{
+ 			StrategyNumber strategy;
+ 			bool empty;
+ 			
+ 			strategy = in->scankeys[i].sk_strategy;
+ 			
+ 			/*
+ 			 * The only strategy when second argument of operator is not
+ 			 * range is RANGESTRAT_CONTAINS_ELEM.
+ 			 */
+ 			if (strategy != RANGESTRAT_CONTAINS_ELEM)
+ 				empty = RangeIsEmpty(
+ 								DatumGetRangeType(in->scankeys[i].sk_argument));
+ 			
+ 			switch (strategy)
+ 			{
+ 				/* These strategies return false if any argument is empty */
+ 				case RANGESTRAT_BEFORE:
+ 				case RANGESTRAT_OVERLEFT:
+ 				case RANGESTRAT_OVERLAPS:
+ 				case RANGESTRAT_OVERRIGHT:
+ 				case RANGESTRAT_AFTER:
+ 				case RANGESTRAT_ADJACENT:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 						which &= (1 << 2);
+ 					break;
+ 				/* 
+ 				 * "Empty" range is contained in any range. Non-empty ranges
+ 				 * can be contained in only non-empty ranges.
+ 				 */
+ 				case RANGESTRAT_CONTAINS:
+ 					if (!empty)
+ 						which &= (1 << 2);
+ 					break;
+ 				case RANGESTRAT_CONTAINED_BY:
+ 					if (empty)
+ 						which &= (1 << 1);
+ 					break;
+ 				/* Empty range can't contain any element */
+ 				case RANGESTRAT_CONTAINS_ELEM:
+ 					which &= (1 << 2);
+ 					break;
+ 				case RANGESTRAT_EQ:
+ 					if (empty)
+ 						which &= (1 << 1);
+ 					else
+ 						which &= (1 << 2);
+ 					break;
+ 				default:
+ 					elog(ERROR, "unrecognized range strategy: %d", strategy);
+ 					break;
+ 			}
+ 			if (which == 0)
+ 				break;			/* no need to consider remaining conditions */
+ 		}
+ 	}
+ 	else
+ 	{
+ 		RangeBound		centroidLower, centroidUpper;
+ 		bool			centroidEmpty;
+ 		TypeCacheEntry *typcache;
+ 		RangeType	   *centroid;
+ 
+ 		/* Prefix is not null, get information about it. */
+ 		centroid = DatumGetRangeType(in->prefixDatum);
+ 		typcache = range_get_typcache(fcinfo,
+ 								RangeTypeGetOid(DatumGetRangeType(centroid)));
+ 		range_deserialize(typcache, centroid, &centroidLower, &centroidUpper, 
+ 			&centroidEmpty);
+ 		
+ 		Assert(in->nNodes == 4 || in->nNodes == 5);
+ 		
+ 		/* 
+ 		 * Nth bit of which variable means that (N - 1)th node (Nth quadrant)
+ 		 * should be visited. Initially all bits are set. Bits of nodes which
+ 		 * should be skipped will be unset.
+ 		 */
+ 		which = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5);
+ 
+ 		for (i = 0; i < in->nkeys; i++)
+ 		{
+ 			StrategyNumber strategy;
+ 			RangeBound	lower, upper;
+ 			bool		empty;
+ 			RangeType  *range = NULL;
+ 
+ 			strategy = in->scankeys[i].sk_strategy;
+ 			
+ 			/*
+ 			 * Deserialize range if argument is range. The only strategy when
+ 			 * second argument of operator is not range is
+ 			 * RANGESTRAT_CONTAINS_ELEM.
+ 			 */
+ 			if (strategy != RANGESTRAT_CONTAINS_ELEM)
+ 			{
+ 				range = DatumGetRangeType(in->scankeys[i].sk_argument);
+ 				range_deserialize(typcache, range, &lower, &upper, &empty);
+ 			}
+ 			
+ 			switch (strategy)
+ 			{
+ 				RangeBound	prevLower, prevUpper;
+ 				bool		prevEmpty, prevPresent;
+ 				RangeType  *prevCentroid;
+ 				int			cmp1, cmp2, cmp3, which1, which2;
+ 				
+ 				/*
+ 				 * Range A is before range B if upper bound of A is lower than
+ 				 * lower bound of B. If upper bound of "centroid" is greater
+ 				 * or equal to lower bound of argument then no ranges before
+ 				 * argument can be contained in quadrants 2 and 4.
+ 				 */
+ 				case RANGESTRAT_BEFORE:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidUpper,
+ 																&lower) >= 0)
+ 						which &= (1 << 2) | (1 << 3);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Range A is overleft to range B if upper bound of A is lower
+ 				 * or equal to lower bound of B. If upper bound of "centroid" is
+ 				 * greater to upper bound of argument then no ranges overleft
+ 				 * argument can be contained in quadrants 1 and 4.
+ 				 */
+ 				case RANGESTRAT_OVERLEFT:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidUpper,
+ 																	&upper) > 0)
+ 						which = (1 << 2) | (1 << 3);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Non-empty ranges overlaps if lower bound of each range is
+ 				 * lower or equal to upper bound of another ranges.
+ 				 */
+ 				case RANGESTRAT_OVERLAPS:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 					{
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 						
+ 						/*
+ 						 * If lower bound of centroid is greater than upper
+ 						 * bound of argument then no overlapping ranges can be
+ 						 * in 1 and 2 quadrants.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidLower,
+ 																	&upper) > 0)
+ 							which &= (1 << 3) | (1 << 4);
+ 						
+ 						/*
+ 						 * If upper bound of centroid is lower or equal than
+ 						 * lower bound of argument then no overlapping ranges
+ 						 * can be in 2 and 3 quadrants.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidUpper,
+ 																   &lower) <= 0)
+ 							which &= (1 << 1) | (1 << 4);
+ 					}
+ 					break;
+ 				/*
+ 				 * Range A is overright to range B if lower bound of A is upper
+ 				 * or equal to upper bound of B. If lower bound of "centroid" is
+ 				 * lower or equal to lower bound of argument then no ranges
+ 				 * overright argument can be contained in quadrants 3 and 4.
+ 				 */
+ 				case RANGESTRAT_OVERRIGHT:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidLower, &lower) <= 0)
+ 						which = (1 << 1) | (1 << 2);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Range A is after range B if lower bound of A is greater than
+ 				 * upper bound of B. If lower bound of "centroid" is lower
+ 				 * or equal to upper bound of argument then no ranges after
+ 				 * argument can be contained in quadrants 3 and 4.
+ 				 */
+ 				case RANGESTRAT_AFTER:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidLower, &upper) <= 0)
+ 						which &= (1 << 1) | (1 << 2);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Ranges are adjacent if lower bound of one range is adjacent
+ 				 * to upper bound of another range.
+ 				 */
+ 				case RANGESTRAT_ADJACENT:
+ 					/*
+ 					 * which1 is bitmask for possibility to be adjacent with
+ 					 * lower bound of argument. which2 is bitmask for
+ 					 * possibility to be adjacent with upper bound of
+ 					 * argument. 
+ 					 */
+ 					which1 = which2 = (1 >> 1) | (1 >> 2) | (1 >> 3) | (1 >> 4);
+ 
+ 					/* Deserialize previous centroid range if present. */
+ 					prevPresent = (in->reconstructedValue != (Datum) 0);
+ 					if (prevPresent)
+ 					{
+ 						prevCentroid = DatumGetRangeType(in->reconstructedValue);
+ 						range_deserialize(typcache, prevCentroid, &prevLower,
+ 							&prevUpper, &prevEmpty);
+ 					}
+ 					
+ 					cmp2 = range_cmp_bounds(typcache, &upper, &centroidLower);
+ 					if (prevPresent)
+ 					{
+ 						/* Do comparison with previous centroid */
+ 						cmp1 = range_cmp_bounds(typcache, &upper, &prevLower);
+ 						cmp3 = range_cmp_bounds(typcache, &centroidLower,
+ 																	&prevLower);
+ 						
+ 						/* 
+ 						 * Check if lower bound of argument is not in
+ 						 * a quadrant we visit in previous step.
+ 						 */
+ 						if ((cmp3 < 0 && cmp1 > 0) || (cmp3 > 0 && cmp1 < 0))
+ 							which1 = 0;
+ 					}
+ 
+ 					if (cmp2 >= 0)
+ 						which1 &= (1 >> 1) | (1 >> 2);
+ 					else if (!bounds_adjacent(typcache, centroidLower, upper))
+ 						which1 &= (1 >> 3) | (1 >> 4);
+ 
+ 					cmp2 = range_cmp_bounds(typcache, &lower, &centroidUpper);
+ 					if (prevPresent)
+ 					{
+ 						/* Do comparison with previous centroid */
+ 						cmp1 = range_cmp_bounds(typcache, &lower, &prevUpper);
+ 						cmp3 = range_cmp_bounds(typcache, &centroidUpper, &prevUpper);
+ 						/* 
+ 						 * Check if upper bound of argument is not in
+ 						 * a quadrant we visit in previous step.
+ 						 */
+ 						if ((cmp3 < 0 && cmp1 > 0) || (cmp3 > 0 && cmp1 < 0))
+ 							which2 = 0;
+ 					}
+ 
+ 					if (cmp2 > 0)
+ 						which2 &= (1 >> 1) | (1 >> 4);
+ 					else if (cmp2 < 0)
+ 						which2 &= (1 >> 2) | (1 >> 3);
+ 
+ 					which &= which1 | which2;
+ 					
+ 					needPrevious = true;
+ 					break;
+ 				/*
+ 				 * Non-empty range A contains non-empty range B if lower bound
+ 				 * of A is lower or equal to lower bound of range B and upper
+ 				 * bound of range A is greater or equal to upper bound of range
+ 				 * A.
+ 				 */
+ 				case RANGESTRAT_CONTAINS:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 					{
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 						/* 
+ 						 * If lower bound of centroid is greater than lower
+ 						 * bound of argument then no ranges which contain
+ 						 * argument can be in quadrants 1 and 2.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidLower,
+ 																	&lower) > 0)
+ 							which &= (1 << 3) | (1 << 4);
+ 						/* 
+ 						 * If upper bound of centroid is lower or equal to upper
+ 						 * bound of argument then no ranges which contain
+ 						 * argument can be in quadrants 2 and 3.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidUpper,
+ 																   &upper) <= 0)
+ 							which &= (1 << 1) | (1 << 4);
+ 					}
+ 					break;
+ 				case RANGESTRAT_CONTAINED_BY:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 					{
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 						/* 
+ 						 * If lower bound of centroid is lower or equal to lower
+ 						 * bound of argument then no ranges which are contained
+ 						 * in argument can be in quadrants 3 and 4.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidLower,
+ 																   &lower) <= 0)
+ 							which &= (1 << 1) | (1 << 2);
+ 						/* 
+ 						 * If upper bound of centroid is greater than upper
+ 						 * bound of argument then no ranges which are contained
+ 						 * in argument can be in quadrants 1 and 4.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidUpper,
+ 																	&upper) > 0)
+ 							which &= (1 << 2) | (1 << 3);
+ 					}
+ 					break;
+ 				case RANGESTRAT_CONTAINS_ELEM:
+ 					/* 
+ 					 * Construct bound to pass then to bound comparison
+ 					 * functions
+ 					 */
+ 					lower.inclusive = true;
+ 					lower.infinite = false;
+ 					lower.lower = true;
+ 					lower.val = in->scankeys[i].sk_argument;
+ 					
+ 					upper.inclusive = true;
+ 					upper.infinite = false;
+ 					upper.lower = false;
+ 					upper.val = in->scankeys[i].sk_argument;
+ 					
+ 					which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					
+ 					/*
+ 					 * If lower bound of centroid is greater than lower bound of
+ 					 * argument then ranges containing element can't be in 1 and 2
+ 					 * quadrants.
+ 					 */
+ 					if (range_cmp_bound_values(typcache, &centroidLower,
+ 																	&lower) > 0)
+ 							which &= (1 << 3) | (1 << 4);
+ 					
+ 					/*
+ 					 * If upper bound of centroid is lower or equal than upper
+ 					 * bound of argument then ranges containing element can't be
+ 					 * in 2 and 3 quadrants.
+ 					 */
+ 					if (range_cmp_bound_values(typcache, &centroidUpper,
+ 																   &upper) <= 0)
+ 							which &= (1 << 1) | (1 << 4);				
+ 					
+ 					break;
+ 				/*
+ 				 * Equal range can be only in the same quadrant where argument
+ 				 * would be placed to.
+ 				 */
+ 				case RANGESTRAT_EQ:
+ 					which &= (1 << getQuadrant(typcache, centroid, range));
+ 					break;
+ 				default:
+ 					elog(ERROR, "unrecognized range strategy: %d", strategy);
+ 					break;
+ 			}
+ 
+ 			if (which == 0)
+ 				break;			/* no need to consider remaining conditions */
+ 		}
+ 	}
+ 
+ 	/* We must descend into the quadrant(s) identified by which */
+ 	out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
+ 	if (needPrevious)
+ 		out->reconstructedValues = (Datum *) palloc(sizeof(Datum) * in->nNodes);
+ 	out->nNodes = 0;
+ 	for (i = 1; i <= in->nNodes; i++)
+ 	{
+ 		if (which & (1 << i))
+ 		{
+ 			/* Save previous prefix if needed */
+ 			if (needPrevious)
+ 				out->reconstructedValues[out->nNodes] = in->prefixDatum;
+ 			out->nodeNumbers[out->nNodes++] = i - 1;
+ 		}
+ 	}
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Leaf consistent SP-GiST function: check leaf value against query using
+  * corresponding function.
+  */
+ Datum
+ spg_range_quad_leaf_consistent(PG_FUNCTION_ARGS)
+ {
+ 	spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0);
+ 	spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1);
+ 	bool		res;
+ 	int			i;
+ 
+ 	/* all tests are exact */
+ 	out->recheck = false;
+ 
+ 	/* leafDatum is what it is... */
+ 	out->leafValue = in->leafDatum;
+ 
+ 	/* Perform the required comparison(s) */
+ 	res = true;
+ 	for (i = 0; i < in->nkeys; i++)
+ 	{
+ 		PGFunction	proc;
+ 		
+ 		/* Find the function which is corresponding to the scan strategy */
+ 		switch (in->scankeys[i].sk_strategy)
+ 		{
+ 			case RANGESTRAT_BEFORE:
+ 				proc = range_before;
+ 				break;
+ 			case RANGESTRAT_OVERLEFT:
+ 				proc = range_overleft;
+ 				break;
+ 			case RANGESTRAT_OVERLAPS:
+ 				proc = range_overlaps;
+ 				break;
+ 			case RANGESTRAT_OVERRIGHT:
+ 				proc = range_overright;
+ 				break;
+ 			case RANGESTRAT_AFTER:
+ 				proc = range_after;
+ 				break;
+ 			case RANGESTRAT_ADJACENT:
+ 				proc = range_adjacent;
+ 				break;
+ 			case RANGESTRAT_CONTAINS:
+ 				proc = range_contains;
+ 				break;
+ 			case RANGESTRAT_CONTAINED_BY:
+ 				proc = range_contained_by;
+ 				break;
+ 			case RANGESTRAT_CONTAINS_ELEM:
+ 				proc = range_contains_elem;
+ 				break;
+ 			case RANGESTRAT_EQ:
+ 				proc = range_eq;
+ 				break;
+ 			default:
+ 				elog(ERROR, "unrecognized range strategy: %d", 
+ 												in->scankeys[i].sk_strategy);
+ 				proc = InvalidOid;
+ 				break;
+ 		}
+ 		res = DatumGetBool(TrickFunctionCall2(proc, fcinfo->flinfo,
+ 								in->leafDatum, in->scankeys[i].sk_argument));
+ 		
+ 		/* If leaf datum don't match to one query, we can don't check another */
+ 		if (!res)
+ 			break;
+ 	}
+ 
+ 	PG_RETURN_BOOL(res);
+ }
*** a/src/include/catalog/pg_amop.h
--- b/src/include/catalog/pg_amop.h
***************
*** 767,770 **** DATA(insert (	4017   25 25 12 s	665 4000 0 ));
--- 767,784 ----
  DATA(insert (	4017   25 25 14 s	667 4000 0 ));
  DATA(insert (	4017   25 25 15 s	666 4000 0 ));
  
+ /*
+  * SP-GiST range_ops
+  */
+ DATA(insert (	3474   3831 3831 1 s	3893 4000 0 ));
+ DATA(insert (	3474   3831 3831 2 s	3895 4000 0 ));
+ DATA(insert (	3474   3831 3831 3 s	3888 4000 0 ));
+ DATA(insert (	3474   3831 3831 4 s	3896 4000 0 ));
+ DATA(insert (	3474   3831 3831 5 s	3894 4000 0 ));
+ DATA(insert (	3474   3831 3831 6 s	3897 4000 0 ));
+ DATA(insert (	3474   3831 3831 7 s	3890 4000 0 ));
+ DATA(insert (	3474   3831 3831 8 s	3892 4000 0 ));
+ DATA(insert (	3474   3831 2283 16 s	3889 4000 0 ));
+ DATA(insert (	3474   3831 3831 18 s	3882 4000 0 ));
+ 
  #endif   /* PG_AMOP_H */
*** a/src/include/catalog/pg_amproc.h
--- b/src/include/catalog/pg_amproc.h
***************
*** 373,377 **** DATA(insert (	4017   25 25 2 4028 ));
--- 373,382 ----
  DATA(insert (	4017   25 25 3 4029 ));
  DATA(insert (	4017   25 25 4 4030 ));
  DATA(insert (	4017   25 25 5 4031 ));
+ DATA(insert (	3474   3831 3831 1 3469 ));
+ DATA(insert (	3474   3831 3831 2 3470 ));
+ DATA(insert (	3474   3831 3831 3 3471 ));
+ DATA(insert (	3474   3831 3831 4 3472 ));
+ DATA(insert (	3474   3831 3831 5 3473 ));
  
  #endif   /* PG_AMPROC_H */
*** a/src/include/catalog/pg_opclass.h
--- b/src/include/catalog/pg_opclass.h
***************
*** 223,228 **** DATA(insert (	783		tsquery_ops			PGNSP PGUID 3702  3615 t 20 ));
--- 223,229 ----
  DATA(insert (	403		range_ops			PGNSP PGUID 3901  3831 t 0 ));
  DATA(insert (	405		range_ops			PGNSP PGUID 3903  3831 t 0 ));
  DATA(insert (	783		range_ops			PGNSP PGUID 3919  3831 t 0 ));
+ DATA(insert (	4000	range_ops			PGNSP PGUID 3474  3831 t 0 ));
  DATA(insert (	4000	quad_point_ops		PGNSP PGUID 4015  600 t 0 ));
  DATA(insert (	4000	kd_point_ops		PGNSP PGUID 4016  600 f 0 ));
  DATA(insert (	4000	text_ops			PGNSP PGUID 4017  25 t 0 ));
*** a/src/include/catalog/pg_opfamily.h
--- b/src/include/catalog/pg_opfamily.h
***************
*** 142,147 **** DATA(insert OID = 3702 (	783		tsquery_ops		PGNSP PGUID ));
--- 142,148 ----
  DATA(insert OID = 3901 (	403		range_ops		PGNSP PGUID ));
  DATA(insert OID = 3903 (	405		range_ops		PGNSP PGUID ));
  DATA(insert OID = 3919 (	783		range_ops		PGNSP PGUID ));
+ DATA(insert OID = 3474 (	4000	range_ops		PGNSP PGUID ));
  DATA(insert OID = 4015 (	4000	quad_point_ops	PGNSP PGUID ));
  DATA(insert OID = 4016 (	4000	kd_point_ops	PGNSP PGUID ));
  DATA(insert OID = 4017 (	4000	text_ops		PGNSP PGUID ));
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4645,4650 **** DESCR("SP-GiST support for suffix tree over text");
--- 4645,4661 ----
  DATA(insert OID = 4031 (  spg_text_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_text_leaf_consistent _null_ _null_ _null_ ));
  DESCR("SP-GiST support for suffix tree over text");
  
+ DATA(insert OID = 3469 (  spg_range_quad_config	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_config _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3470 (  spg_range_quad_choose	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_choose _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3471 (  spg_range_quad_picksplit	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_picksplit _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3472 (  spg_range_quad_inner_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_inner_consistent _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3473 (  spg_range_quad_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_leaf_consistent _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ 
  
  /*
   * Symbolic values for provolatile column: these indicate whether the result
*** a/src/include/utils/rangetypes.h
--- b/src/include/utils/rangetypes.h
***************
*** 75,80 **** typedef struct
--- 75,93 ----
  #define PG_GETARG_RANGE_COPY(n)		DatumGetRangeTypeCopy(PG_GETARG_DATUM(n))
  #define PG_RETURN_RANGE(x)			return RangeTypeGetDatum(x)
  
+ /* Operator strategy numbers used in the GiST range opclass */
+ /* Numbers are chosen to match up operator names with existing usages */
+ #define RANGESTRAT_BEFORE				1
+ #define RANGESTRAT_OVERLEFT				2
+ #define RANGESTRAT_OVERLAPS				3
+ #define RANGESTRAT_OVERRIGHT			4
+ #define RANGESTRAT_AFTER				5
+ #define RANGESTRAT_ADJACENT				6
+ #define RANGESTRAT_CONTAINS				7
+ #define RANGESTRAT_CONTAINED_BY			8
+ #define RANGESTRAT_CONTAINS_ELEM		16
+ #define RANGESTRAT_EQ					18
+ 
  /*
   * prototypes for functions defined in rangetypes.c
   */
***************
*** 166,171 **** extern int range_cmp_bound_values(TypeCacheEntry *typcache, RangeBound *b1,
--- 179,185 ----
  extern RangeType *make_empty_range(TypeCacheEntry *typcache);
  
  /* GiST support (in rangetypes_gist.c) */
+ extern Datum TrickFunctionCall2(PGFunction proc, FmgrInfo *flinfo, Datum arg1, Datum arg2);
  extern Datum range_gist_consistent(PG_FUNCTION_ARGS);
  extern Datum range_gist_compress(PG_FUNCTION_ARGS);
  extern Datum range_gist_decompress(PG_FUNCTION_ARGS);
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
***************
*** 1068,1079 **** ORDER BY 1, 2, 3;
--- 1068,1084 ----
         2742 |            4 | =
         4000 |            1 | <<
         4000 |            1 | ~<~
+        4000 |            2 | &<
         4000 |            2 | ~<=~
+        4000 |            3 | &&
         4000 |            3 | =
+        4000 |            4 | &>
         4000 |            4 | ~>=~
         4000 |            5 | >>
         4000 |            5 | ~>~
+        4000 |            6 | -|-
         4000 |            6 | ~=
+        4000 |            7 | @>
         4000 |            8 | <@
         4000 |           10 | <^
         4000 |           11 | <
***************
*** 1081,1087 **** ORDER BY 1, 2, 3;
         4000 |           12 | <=
         4000 |           14 | >=
         4000 |           15 | >
! (55 rows)
  
  -- Check that all opclass search operators have selectivity estimators.
  -- This is not absolutely required, but it seems a reasonable thing
--- 1086,1094 ----
         4000 |           12 | <=
         4000 |           14 | >=
         4000 |           15 | >
!        4000 |           16 | @>
!        4000 |           18 | =
! (62 rows)
  
  -- Check that all opclass search operators have selectivity estimators.
  -- This is not absolutely required, but it seems a reasonable thing
*** a/src/test/regress/expected/rangetypes.out
--- b/src/test/regress/expected/rangetypes.out
***************
*** 821,826 **** select count(*) from test_range_gist where ir -|- int4range(100,500);
--- 821,1045 ----
       5
  (1 row)
  
+ -- test SP-GiST index that's been built incrementally
+ create table test_range_spgist(ir int4range);
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(g, g+10000) from generate_series(1,1000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(NULL,g*10,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g*10,NULL,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ -- first, verify non-indexed results
+ SET enable_seqscan    = t;
+ SET enable_indexscan  = f;
+ SET enable_bitmapscan = f;
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+  count 
+ -------
+   6200
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> 10;
+  count 
+ -------
+    130
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+  count 
+ -------
+    111
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+  count 
+ -------
+    158
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+  count 
+ -------
+   1062
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+  count 
+ -------
+    189
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+  count 
+ -------
+   3554
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+  count 
+ -------
+   1029
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+  count 
+ -------
+   4794
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+  count 
+ -------
+      5
+ (1 row)
+ 
+ -- now check same queries using index
+ SET enable_seqscan    = f;
+ SET enable_indexscan  = t;
+ SET enable_bitmapscan = f;
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+  count 
+ -------
+      0
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> 10;
+  count 
+ -------
+    130
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+  count 
+ -------
+    111
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+  count 
+ -------
+    158
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+  count 
+ -------
+     62
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+  count 
+ -------
+    189
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+  count 
+ -------
+   3554
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+  count 
+ -------
+   1029
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+  count 
+ -------
+   4794
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+  count 
+ -------
+      0
+ (1 row)
+ 
+ -- now check same queries using a bulk-loaded index
+ drop index test_range_spgist_idx;
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+  count 
+ -------
+      0
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> 10;
+  count 
+ -------
+    130
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+  count 
+ -------
+    111
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+  count 
+ -------
+    158
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+  count 
+ -------
+     62
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+  count 
+ -------
+    189
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+  count 
+ -------
+   3554
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+  count 
+ -------
+   1029
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+  count 
+ -------
+   4794
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+  count 
+ -------
+      0
+ (1 row)
+ 
  RESET enable_seqscan;
  RESET enable_indexscan;
  RESET enable_bitmapscan;
*** a/src/test/regress/expected/sanity_check.out
--- b/src/test/regress/expected/sanity_check.out
***************
*** 156,161 **** SELECT relname, relhasindex
--- 156,162 ----
   tenk2                   | t
   test_range_excl         | t
   test_range_gist         | t
+  test_range_spgist       | t
   test_tsvector           | f
   text_tbl                | f
   time_tbl                | f
***************
*** 164,170 **** SELECT relname, relhasindex
   timetz_tbl              | f
   tinterval_tbl           | f
   varchar_tbl             | f
! (153 rows)
  
  --
  -- another sanity check: every system catalog that has OIDs should have
--- 165,171 ----
   timetz_tbl              | f
   tinterval_tbl           | f
   varchar_tbl             | f
! (154 rows)
  
  --
  -- another sanity check: every system catalog that has OIDs should have
*** a/src/test/regress/output/misc.source
--- b/src/test/regress/output/misc.source
***************
*** 675,680 **** SELECT user_relns() AS user_relns
--- 675,681 ----
   tenk2
   test_range_excl
   test_range_gist
+  test_range_spgist
   test_tsvector
   text_tbl
   time_tbl
***************
*** 685,691 **** SELECT user_relns() AS user_relns
   toyemp
   varchar_tbl
   xacttest
! (107 rows)
  
  SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
   name 
--- 686,692 ----
   toyemp
   varchar_tbl
   xacttest
! (108 rows)
  
  SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
   name 
*** a/src/test/regress/sql/rangetypes.sql
--- b/src/test/regress/sql/rangetypes.sql
***************
*** 220,225 **** select count(*) from test_range_gist where ir &< int4range(100,500);
--- 220,287 ----
  select count(*) from test_range_gist where ir &> int4range(100,500);
  select count(*) from test_range_gist where ir -|- int4range(100,500);
  
+ -- test SP-GiST index that's been built incrementally
+ create table test_range_spgist(ir int4range);
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ 
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(g, g+10000) from generate_series(1,1000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(NULL,g*10,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g*10,NULL,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ 
+ -- first, verify non-indexed results
+ SET enable_seqscan    = t;
+ SET enable_indexscan  = f;
+ SET enable_bitmapscan = f;
+ 
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+ select count(*) from test_range_spgist where ir @> 10;
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ 
+ -- now check same queries using index
+ SET enable_seqscan    = f;
+ SET enable_indexscan  = t;
+ SET enable_bitmapscan = f;
+ 
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+ select count(*) from test_range_spgist where ir @> 10;
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ 
+ -- now check same queries using a bulk-loaded index
+ drop index test_range_spgist_idx;
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ 
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+ select count(*) from test_range_spgist where ir @> 10;
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ 
  RESET enable_seqscan;
  RESET enable_indexscan;
  RESET enable_bitmapscan;
#12Alexander Korotkov
aekorotkov@gmail.com
In reply to: Alexander Korotkov (#11)
1 attachment(s)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Thu, Jul 12, 2012 at 3:03 AM, Alexander Korotkov <aekorotkov@gmail.com>wrote:

On Tue, Jul 3, 2012 at 10:51 AM, Jeff Davis <pgsql@j-davis.com> wrote:

Also, it would be helpful to add a couple tests to rangetypes.sql.

New version of patch is attached.

Oops, forgot to include one comment fix into patch.

------
With best regards,
Alexander Korotkov.

Attachments:

range_spgist_quad-0.4.patchapplication/octet-stream; name=range_spgist_quad-0.4.patchDownload
*** a/src/backend/utils/adt/Makefile
--- b/src/backend/utils/adt/Makefile
***************
*** 30,36 **** OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
  	tsvector.o tsvector_op.o tsvector_parser.o \
! 	txid.o uuid.o windowfuncs.o xml.o
  
  like.o: like.c like_match.c
  
--- 30,36 ----
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
  	tsvector.o tsvector_op.o tsvector_parser.o \
! 	txid.o uuid.o windowfuncs.o xml.o rangetypes_spgist.o
  
  like.o: like.c like_match.c
  
*** a/src/backend/utils/adt/rangetypes_gist.c
--- b/src/backend/utils/adt/rangetypes_gist.c
***************
*** 20,39 ****
  #include "utils/datum.h"
  #include "utils/rangetypes.h"
  
- 
- /* Operator strategy numbers used in the GiST range opclass */
- /* Numbers are chosen to match up operator names with existing usages */
- #define RANGESTRAT_BEFORE				1
- #define RANGESTRAT_OVERLEFT				2
- #define RANGESTRAT_OVERLAPS				3
- #define RANGESTRAT_OVERRIGHT			4
- #define RANGESTRAT_AFTER				5
- #define RANGESTRAT_ADJACENT				6
- #define RANGESTRAT_CONTAINS				7
- #define RANGESTRAT_CONTAINED_BY			8
- #define RANGESTRAT_CONTAINS_ELEM		16
- #define RANGESTRAT_EQ					18
- 
  /*
   * Range class properties used to segregate different classes of ranges in
   * GiST.  Each unique combination of properties is a class.  CLS_EMPTY cannot
--- 20,25 ----
***************
*** 792,798 **** range_super_union(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
   * part of the relcache entry for the index, typically) this essentially
   * eliminates lookup overhead during operations on a GiST range index.
   */
! static Datum
  TrickFunctionCall2(PGFunction proc, FmgrInfo *flinfo, Datum arg1, Datum arg2)
  {
  	FunctionCallInfoData fcinfo;
--- 778,784 ----
   * part of the relcache entry for the index, typically) this essentially
   * eliminates lookup overhead during operations on a GiST range index.
   */
! Datum
  TrickFunctionCall2(PGFunction proc, FmgrInfo *flinfo, Datum arg1, Datum arg2)
  {
  	FunctionCallInfoData fcinfo;
*** /dev/null
--- b/src/backend/utils/adt/rangetypes_spgist.c
***************
*** 0 ****
--- 1,795 ----
+ /*-------------------------------------------------------------------------
+  *
+  * rangetypes_spgist.c
+  *	  implementation of quad tree over ranges mapped to 2d-points for SP-GiST
+  *
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * IDENTIFICATION
+  *			src/backend/utils/adt/rangetypes_spgist.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #include "postgres.h"
+ 
+ #include "access/spgist.h"
+ #include "access/skey.h"
+ #include "catalog/pg_type.h"
+ #include "utils/builtins.h"
+ #include "utils/datum.h"
+ #include "utils/rangetypes.h"
+ 
+ Datum spg_range_quad_config(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_choose(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_picksplit(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_inner_consistent(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_leaf_consistent(PG_FUNCTION_ARGS);
+ 
+ static int16 getQuadrant(TypeCacheEntry *typcache, RangeType *centroid, RangeType *tst);
+ static int bound_cmp(const void *a, const void *b, void *arg);
+ static bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound lower, RangeBound upper);
+ 
+ 
+ /*
+  * Config SP-GiST interface function.
+  */
+ Datum
+ spg_range_quad_config(PG_FUNCTION_ARGS)
+ {
+ 	/* spgConfigIn *cfgin = (spgConfigIn *) PG_GETARG_POINTER(0); */
+ 	spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1);
+ 
+ 	cfg->prefixType = ANYRANGEOID;
+ 	cfg->labelType = VOIDOID;	/* we don't need node labels */
+ 	cfg->canReturnData = true;
+ 	cfg->longValuesOK = false;
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Determine which quadrant a 2d-mapped range falls into, relative to the
+  * centroid. Lower bound of range assumed to be the horizontal axis. Upper
+  * bound of range assumed to be the vertical axis.
+  *
+  * Quadrants are identified like this:
+  *
+  *	 4	|  1
+  *	----+-----
+  *	 3	|  2
+  *
+  * Ranges on one of the axes are taken to lie in the quadrant with higher value
+  * along perpendicular axis. Range equal to centroid is taken to lie in the
+  * quadrant 1. Empty ranges are taken to lie in the quadrant 5.
+  */
+ static int16
+ getQuadrant(TypeCacheEntry *typcache, RangeType *centroid, RangeType *tst)
+ {
+ 	RangeBound centroidLower, centroidUpper, lower, upper;
+ 	bool centroidEmpty, empty;
+ 	
+ 	range_deserialize(typcache, centroid, &centroidLower, &centroidUpper, 
+ 																&centroidEmpty);
+ 	range_deserialize(typcache, tst, &lower, &upper, &empty);
+ 	
+ 	if (empty)
+ 		return 5;
+ 	
+ 	if (range_cmp_bounds(typcache, &lower, &centroidLower) >= 0)
+ 	{
+ 		if (range_cmp_bounds(typcache, &upper, &centroidUpper) >= 0)
+ 			return 1;
+ 		else
+ 			return 2;		
+ 	}
+ 	else
+ 	{
+ 		if (range_cmp_bounds(typcache, &upper, &centroidUpper) >= 0)
+ 			return 4;
+ 		else
+ 			return 3;		
+ 	}
+ 
+ 	elog(ERROR, "getQuadrant: impossible case");
+ 	return 0;
+ }
+ 
+ 
+ /*
+  * Choose SP-GiST function: choose path for addition of new range.
+  */
+ Datum
+ spg_range_quad_choose(PG_FUNCTION_ARGS)
+ {
+ 	spgChooseIn	   *in = (spgChooseIn *) PG_GETARG_POINTER(0);
+ 	spgChooseOut   *out = (spgChooseOut *) PG_GETARG_POINTER(1);
+ 	RangeType	   *inRange = DatumGetRangeType(in->datum), *centroid;
+ 	int16			quadrant;
+ 	TypeCacheEntry *typcache;
+ 
+ 	if (in->allTheSame)
+ 	{
+ 		out->resultType = spgMatchNode;
+ 		/* nodeN will be set by core */
+ 		out->result.matchNode.levelAdd = 0;
+ 		out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(inRange));
+ 
+ 	/*
+ 	 * Absence of prefix datum divides ranges by empty sign. All empty ranges
+ 	 * are taken into node 0, all non-empty ranges are taken into node 1.
+ 	 */
+ 	if (!in->hasPrefix)
+ 	{
+ 		out->resultType = spgMatchNode;
+ 		if (RangeIsEmpty(inRange))
+ 			out->result.matchNode.nodeN = 0;
+ 		else
+ 			out->result.matchNode.nodeN = 1;
+ 		out->result.matchNode.levelAdd = 1;
+ 		out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	centroid = DatumGetRangeType(in->prefixDatum);
+ 	quadrant = getQuadrant(typcache, centroid, inRange);
+ 
+ 	Assert(quadrant <= in->nNodes);
+ 
+ 	/* Select node matching to quadrant number */
+ 	out->resultType = spgMatchNode;
+ 	out->result.matchNode.nodeN = quadrant - 1;
+ 	out->result.matchNode.levelAdd = 1;
+ 	out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Bound comparison for sorting.
+  */
+ static int
+ bound_cmp(const void *a, const void *b, void *arg)
+ {
+ 	RangeBound *ba = (RangeBound *) a;
+ 	RangeBound *bb = (RangeBound *) b;
+ 	TypeCacheEntry *typcache = (TypeCacheEntry *)arg;
+ 
+ 	return range_cmp_bounds(typcache, ba, bb);
+ }
+ 
+ /*
+  * Picksplit SP-GiST function: split ranges into nodes. Select "centroid"
+  * range and distribute ranges according to quadrants.
+  */
+ Datum
+ spg_range_quad_picksplit(PG_FUNCTION_ARGS)
+ {
+ 	spgPickSplitIn  *in = (spgPickSplitIn *) PG_GETARG_POINTER(0);
+ 	spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1);
+ 	int				 i, j, nonEmptyCount;
+ 	RangeType		*centroid;
+ 	bool			 empty;
+ 	TypeCacheEntry  *typcache;
+ 
+ 	/* Use the median values of lower and upper bounds as the centroid range */
+ 	RangeBound *lowerBounds, *upperBounds;
+ 
+ 	typcache = range_get_typcache(fcinfo, 
+ 							RangeTypeGetOid(DatumGetRangeType(in->datums[0])));
+ 
+ 	/* Allocate memory for bounds */
+ 	lowerBounds = palloc(sizeof(RangeBound) * in->nTuples);
+ 	upperBounds = palloc(sizeof(RangeBound) * in->nTuples);
+ 	j = 0;
+ 	
+ 	/* Deserialize bounds of ranges, count non-empty ranges */
+ 	for (i = 0; i < in->nTuples; i++)
+ 	{
+ 		range_deserialize(typcache, DatumGetRangeType(in->datums[i]),
+ 									&lowerBounds[j], &upperBounds[j], &empty);
+ 		if (!empty)
+ 			j++;
+ 	}
+ 	nonEmptyCount = j;
+ 	
+ 	/*
+ 	 * All the ranges are empty. We've nothing better than put all the ranges
+ 	 * into node 0. Non-empty range will be routed to node 1.
+ 	 */
+ 	if (nonEmptyCount == 0)
+ 	{
+ 		out->nNodes = 2;
+ 		out->hasPrefix = false;
+ 		/* Prefix is empty */
+ 		out->prefixDatum = PointerGetDatum(NULL);
+ 		out->nodeLabels = NULL;
+ 		
+ 		out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+ 		out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+ 		
+ 		/* Place all ranges into node 0 */
+ 		for (i = 0; i < in->nTuples; i++)
+ 		{
+ 			RangeType	   *range = DatumGetRangeType(in->datums[i]);
+ 
+ 			out->leafTupleDatums[i] = RangeTypeGetDatum(range);
+ 			out->mapTuplesToNodes[i] = 0;
+ 		}		
+ 		PG_RETURN_VOID();
+ 	}
+ 
+ 	/* Sort range bounds in order to find medians */
+ 	qsort_arg(lowerBounds, nonEmptyCount, sizeof(RangeBound), 
+ 														bound_cmp, typcache);
+ 	qsort_arg(upperBounds, nonEmptyCount, sizeof(RangeBound), 
+ 														bound_cmp, typcache);
+ 
+ 	/* Construct "centroid" range from medians of lower and upper bounds */
+ 	centroid = range_serialize(typcache, &lowerBounds[nonEmptyCount / 2],
+ 		&upperBounds[nonEmptyCount / 2], false);
+ 	
+ 	
+ 	out->hasPrefix = true;
+ 	out->prefixDatum = RangeTypeGetDatum(centroid);
+ 	
+ 	/* Create node for empty ranges only if it is a root node */
+ 	out->nNodes = (in->level == 0) ? 5 : 4;
+ 	out->nodeLabels = NULL;		/* we don't need node labels */
+ 
+ 	out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+ 	out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+ 
+ 	/* 
+ 	 * Add ranges to corresponding nodes according to quadrants relative to
+ 	 * "centroid" range.
+ 	 */
+ 	for (i = 0; i < in->nTuples; i++)
+ 	{
+ 		RangeType	   *range = DatumGetRangeType(in->datums[i]);
+ 		int16			quadrant = getQuadrant(typcache, centroid, range);
+ 
+ 		out->leafTupleDatums[i] = RangeTypeGetDatum(range);
+ 		out->mapTuplesToNodes[i] = quadrant - 1;
+ 	}
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Check if two bounds are "adjacent", i.e. there are no values which satisfy
+  * both bounds and there are no values between the bounds.
+  */
+ static bool
+ bounds_adjacent(TypeCacheEntry *typcache, RangeBound lower, RangeBound upper)
+ {
+ 	int cmp = range_cmp_bound_values(typcache, &upper, &lower);
+ 	if (cmp < 0)
+ 	{
+ 		RangeType *r;
+ 		/* in a continuous subtype, there are assumed to be points between */
+ 		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
+ 			return false;
+ 		/* flip the inclusion flags */
+ 		upper.inclusive = !upper.inclusive;
+ 		lower.inclusive = !lower.inclusive;
+ 		/* change upper/lower labels to avoid Assert failures */
+ 		upper.lower = true;
+ 		lower.lower = false;
+ 		r = make_range(typcache, &upper, &lower, false);
+ 		PG_RETURN_BOOL(RangeIsEmpty(r));
+ 	}
+ 	else if (cmp == 0)
+ 	{
+ 		PG_RETURN_BOOL(upper.inclusive != lower.inclusive);
+ 	}
+ 	else
+ 	{
+ 		PG_RETURN_BOOL(false);
+ 	}
+ }
+ 
+ /*
+  * Inner consisted SP-GiST function: check which nodes are consistent with
+  * given set of queries.
+  */
+ Datum
+ spg_range_quad_inner_consistent(PG_FUNCTION_ARGS)
+ {
+ 	spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
+ 	spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
+ 	int			which;
+ 	int			i;
+ 	bool		needPrevious = false;
+ 
+ 	if (in->allTheSame)
+ 	{
+ 		/* Report that all nodes should be visited */
+ 		out->nNodes = in->nNodes;
+ 		out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
+ 		for (i = 0; i < in->nNodes; i++)
+ 			out->nodeNumbers[i] = i;
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	if (!in->hasPrefix)
+ 	{
+ 		/*
+ 		 * Empty "centroid". We can use only information about emptiness of
+ 		 * ranges in nodes.
+ 		 */
+ 		Assert(in->nNodes == 2);
+ 		
+ 		/* 
+ 		 * Nth bit of which variable means that (N - 1)th node should be
+ 		 * visited. Initially all bits are set. Bits of nodes which should be
+ 		 * skipped will be unset.
+ 		 */
+ 		which = (1 << 1) | (1 << 2);
+ 		for (i = 0; i < in->nkeys; i++)
+ 		{
+ 			StrategyNumber strategy;
+ 			bool empty;
+ 			
+ 			strategy = in->scankeys[i].sk_strategy;
+ 			
+ 			/*
+ 			 * The only strategy when second argument of operator is not
+ 			 * range is RANGESTRAT_CONTAINS_ELEM.
+ 			 */
+ 			if (strategy != RANGESTRAT_CONTAINS_ELEM)
+ 				empty = RangeIsEmpty(
+ 								DatumGetRangeType(in->scankeys[i].sk_argument));
+ 			
+ 			switch (strategy)
+ 			{
+ 				/* These strategies return false if any argument is empty */
+ 				case RANGESTRAT_BEFORE:
+ 				case RANGESTRAT_OVERLEFT:
+ 				case RANGESTRAT_OVERLAPS:
+ 				case RANGESTRAT_OVERRIGHT:
+ 				case RANGESTRAT_AFTER:
+ 				case RANGESTRAT_ADJACENT:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 						which &= (1 << 2);
+ 					break;
+ 				/* 
+ 				 * "Empty" range is contained in any range. Non-empty ranges
+ 				 * can be contained in only non-empty ranges.
+ 				 */
+ 				case RANGESTRAT_CONTAINS:
+ 					if (!empty)
+ 						which &= (1 << 2);
+ 					break;
+ 				case RANGESTRAT_CONTAINED_BY:
+ 					if (empty)
+ 						which &= (1 << 1);
+ 					break;
+ 				/* Empty range can't contain any element */
+ 				case RANGESTRAT_CONTAINS_ELEM:
+ 					which &= (1 << 2);
+ 					break;
+ 				case RANGESTRAT_EQ:
+ 					if (empty)
+ 						which &= (1 << 1);
+ 					else
+ 						which &= (1 << 2);
+ 					break;
+ 				default:
+ 					elog(ERROR, "unrecognized range strategy: %d", strategy);
+ 					break;
+ 			}
+ 			if (which == 0)
+ 				break;			/* no need to consider remaining conditions */
+ 		}
+ 	}
+ 	else
+ 	{
+ 		RangeBound		centroidLower, centroidUpper;
+ 		bool			centroidEmpty;
+ 		TypeCacheEntry *typcache;
+ 		RangeType	   *centroid;
+ 
+ 		/* Prefix is not null, get information about it. */
+ 		centroid = DatumGetRangeType(in->prefixDatum);
+ 		typcache = range_get_typcache(fcinfo,
+ 								RangeTypeGetOid(DatumGetRangeType(centroid)));
+ 		range_deserialize(typcache, centroid, &centroidLower, &centroidUpper, 
+ 			&centroidEmpty);
+ 		
+ 		Assert(in->nNodes == 4 || in->nNodes == 5);
+ 		
+ 		/* 
+ 		 * Nth bit of which variable means that (N - 1)th node (Nth quadrant)
+ 		 * should be visited. Initially all bits are set. Bits of nodes which
+ 		 * should be skipped will be unset.
+ 		 */
+ 		which = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5);
+ 
+ 		for (i = 0; i < in->nkeys; i++)
+ 		{
+ 			StrategyNumber strategy;
+ 			RangeBound	lower, upper;
+ 			bool		empty;
+ 			RangeType  *range = NULL;
+ 
+ 			strategy = in->scankeys[i].sk_strategy;
+ 			
+ 			/*
+ 			 * Deserialize range if argument is range. The only strategy when
+ 			 * second argument of operator is not range is
+ 			 * RANGESTRAT_CONTAINS_ELEM.
+ 			 */
+ 			if (strategy != RANGESTRAT_CONTAINS_ELEM)
+ 			{
+ 				range = DatumGetRangeType(in->scankeys[i].sk_argument);
+ 				range_deserialize(typcache, range, &lower, &upper, &empty);
+ 			}
+ 			
+ 			switch (strategy)
+ 			{
+ 				RangeBound	prevLower, prevUpper;
+ 				bool		prevEmpty, prevPresent;
+ 				RangeType  *prevCentroid;
+ 				int			cmp1, cmp2, cmp3, which1, which2;
+ 				
+ 				/*
+ 				 * Range A is before range B if upper bound of A is lower than
+ 				 * lower bound of B. If upper bound of "centroid" is greater
+ 				 * or equal to lower bound of argument then no ranges before
+ 				 * argument can be contained in quadrants 2 and 4.
+ 				 */
+ 				case RANGESTRAT_BEFORE:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidUpper,
+ 																&lower) >= 0)
+ 						which &= (1 << 2) | (1 << 3);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Range A is overleft to range B if upper bound of A is lower
+ 				 * or equal to lower bound of B. If upper bound of "centroid" is
+ 				 * greater to upper bound of argument then no ranges overleft
+ 				 * argument can be contained in quadrants 1 and 4.
+ 				 */
+ 				case RANGESTRAT_OVERLEFT:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidUpper,
+ 																	&upper) > 0)
+ 						which = (1 << 2) | (1 << 3);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Non-empty ranges overlaps if lower bound of each range is
+ 				 * lower or equal to upper bound of another ranges.
+ 				 */
+ 				case RANGESTRAT_OVERLAPS:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 					{
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 						
+ 						/*
+ 						 * If lower bound of centroid is greater than upper
+ 						 * bound of argument then no overlapping ranges can be
+ 						 * in 1 and 2 quadrants.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidLower,
+ 																	&upper) > 0)
+ 							which &= (1 << 3) | (1 << 4);
+ 						
+ 						/*
+ 						 * If upper bound of centroid is lower or equal than
+ 						 * lower bound of argument then no overlapping ranges
+ 						 * can be in 2 and 3 quadrants.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidUpper,
+ 																   &lower) <= 0)
+ 							which &= (1 << 1) | (1 << 4);
+ 					}
+ 					break;
+ 				/*
+ 				 * Range A is overright to range B if lower bound of A is upper
+ 				 * or equal to upper bound of B. If lower bound of "centroid" is
+ 				 * lower or equal to lower bound of argument then no ranges
+ 				 * overright argument can be contained in quadrants 3 and 4.
+ 				 */
+ 				case RANGESTRAT_OVERRIGHT:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidLower, &lower) <= 0)
+ 						which = (1 << 1) | (1 << 2);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Range A is after range B if lower bound of A is greater than
+ 				 * upper bound of B. If lower bound of "centroid" is lower
+ 				 * or equal to upper bound of argument then no ranges after
+ 				 * argument can be contained in quadrants 3 and 4.
+ 				 */
+ 				case RANGESTRAT_AFTER:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidLower, &upper) <= 0)
+ 						which &= (1 << 1) | (1 << 2);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Ranges are adjacent if lower bound of one range is adjacent
+ 				 * to upper bound of another range.
+ 				 */
+ 				case RANGESTRAT_ADJACENT:
+ 					/*
+ 					 * which1 is bitmask for possibility to be adjacent with
+ 					 * lower bound of argument. which2 is bitmask for
+ 					 * possibility to be adjacent with upper bound of
+ 					 * argument. 
+ 					 */
+ 					which1 = which2 = (1 >> 1) | (1 >> 2) | (1 >> 3) | (1 >> 4);
+ 
+ 					/* Deserialize previous centroid range if present. */
+ 					prevPresent = (in->reconstructedValue != (Datum) 0);
+ 					if (prevPresent)
+ 					{
+ 						prevCentroid = DatumGetRangeType(in->reconstructedValue);
+ 						range_deserialize(typcache, prevCentroid, &prevLower,
+ 							&prevUpper, &prevEmpty);
+ 					}
+ 					
+ 					cmp2 = range_cmp_bounds(typcache, &upper, &centroidLower);
+ 					if (prevPresent)
+ 					{
+ 						/* Do comparison with previous centroid */
+ 						cmp1 = range_cmp_bounds(typcache, &upper, &prevLower);
+ 						cmp3 = range_cmp_bounds(typcache, &centroidLower,
+ 																	&prevLower);
+ 						
+ 						/* 
+ 						 * Check if lower bound of argument is not in
+ 						 * a quadrant we visit in previous step.
+ 						 */
+ 						if ((cmp3 < 0 && cmp1 > 0) || (cmp3 > 0 && cmp1 < 0))
+ 							which1 = 0;
+ 					}
+ 
+ 					if (cmp2 >= 0)
+ 						which1 &= (1 >> 1) | (1 >> 2);
+ 					else if (!bounds_adjacent(typcache, centroidLower, upper))
+ 						which1 &= (1 >> 3) | (1 >> 4);
+ 
+ 					cmp2 = range_cmp_bounds(typcache, &lower, &centroidUpper);
+ 					if (prevPresent)
+ 					{
+ 						/* Do comparison with previous centroid */
+ 						cmp1 = range_cmp_bounds(typcache, &lower, &prevUpper);
+ 						cmp3 = range_cmp_bounds(typcache, &centroidUpper, &prevUpper);
+ 						/* 
+ 						 * Check if upper bound of argument is not in
+ 						 * a quadrant we visit in previous step.
+ 						 */
+ 						if ((cmp3 < 0 && cmp1 > 0) || (cmp3 > 0 && cmp1 < 0))
+ 							which2 = 0;
+ 					}
+ 
+ 					if (cmp2 > 0)
+ 						which2 &= (1 >> 1) | (1 >> 4);
+ 					else if (cmp2 < 0)
+ 						which2 &= (1 >> 2) | (1 >> 3);
+ 
+ 					which &= which1 | which2;
+ 					
+ 					needPrevious = true;
+ 					break;
+ 				/*
+ 				 * Non-empty range A contains non-empty range B if lower bound
+ 				 * of A is lower or equal to lower bound of range B and upper
+ 				 * bound of range A is greater or equal to upper bound of range
+ 				 * A.
+ 				 */
+ 				case RANGESTRAT_CONTAINS:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 					{
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 						/* 
+ 						 * If lower bound of centroid is greater than lower
+ 						 * bound of argument then no ranges which contain
+ 						 * argument can be in quadrants 1 and 2.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidLower,
+ 																	&lower) > 0)
+ 							which &= (1 << 3) | (1 << 4);
+ 						/* 
+ 						 * If upper bound of centroid is lower or equal to upper
+ 						 * bound of argument then no ranges which contain
+ 						 * argument can be in quadrants 2 and 3.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidUpper,
+ 																   &upper) <= 0)
+ 							which &= (1 << 1) | (1 << 4);
+ 					}
+ 					break;
+ 				case RANGESTRAT_CONTAINED_BY:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 					{
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 						/* 
+ 						 * If lower bound of centroid is lower or equal to lower
+ 						 * bound of argument then no ranges which are contained
+ 						 * in argument can be in quadrants 3 and 4.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidLower,
+ 																   &lower) <= 0)
+ 							which &= (1 << 1) | (1 << 2);
+ 						/* 
+ 						 * If upper bound of centroid is greater than upper
+ 						 * bound of argument then no ranges which are contained
+ 						 * in argument can be in quadrants 1 and 4.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidUpper,
+ 																	&upper) > 0)
+ 							which &= (1 << 2) | (1 << 3);
+ 					}
+ 					break;
+ 				case RANGESTRAT_CONTAINS_ELEM:
+ 					/* 
+ 					 * Construct bound to pass then to bound comparison
+ 					 * functions
+ 					 */
+ 					lower.inclusive = true;
+ 					lower.infinite = false;
+ 					lower.lower = true;
+ 					lower.val = in->scankeys[i].sk_argument;
+ 					
+ 					upper.inclusive = true;
+ 					upper.infinite = false;
+ 					upper.lower = false;
+ 					upper.val = in->scankeys[i].sk_argument;
+ 					
+ 					which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					
+ 					/*
+ 					 * If lower bound of centroid is greater than lower bound of
+ 					 * argument then ranges containing element can't be in 1 and 2
+ 					 * quadrants.
+ 					 */
+ 					if (range_cmp_bound_values(typcache, &centroidLower,
+ 																	&lower) > 0)
+ 							which &= (1 << 3) | (1 << 4);
+ 					
+ 					/*
+ 					 * If upper bound of centroid is lower or equal than upper
+ 					 * bound of argument then ranges containing element can't be
+ 					 * in 2 and 3 quadrants.
+ 					 */
+ 					if (range_cmp_bound_values(typcache, &centroidUpper,
+ 																   &upper) <= 0)
+ 							which &= (1 << 1) | (1 << 4);				
+ 					
+ 					break;
+ 				/*
+ 				 * Equal range can be only in the same quadrant where argument
+ 				 * would be placed to.
+ 				 */
+ 				case RANGESTRAT_EQ:
+ 					which &= (1 << getQuadrant(typcache, centroid, range));
+ 					break;
+ 				default:
+ 					elog(ERROR, "unrecognized range strategy: %d", strategy);
+ 					break;
+ 			}
+ 
+ 			if (which == 0)
+ 				break;			/* no need to consider remaining conditions */
+ 		}
+ 	}
+ 
+ 	/* We must descend into the quadrant(s) identified by which */
+ 	out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
+ 	if (needPrevious)
+ 		out->reconstructedValues = (Datum *) palloc(sizeof(Datum) * in->nNodes);
+ 	out->nNodes = 0;
+ 	for (i = 1; i <= in->nNodes; i++)
+ 	{
+ 		if (which & (1 << i))
+ 		{
+ 			/* Save previous prefix if needed */
+ 			if (needPrevious)
+ 				out->reconstructedValues[out->nNodes] = in->prefixDatum;
+ 			out->nodeNumbers[out->nNodes++] = i - 1;
+ 		}
+ 	}
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Leaf consistent SP-GiST function: check leaf value against query using
+  * corresponding function.
+  */
+ Datum
+ spg_range_quad_leaf_consistent(PG_FUNCTION_ARGS)
+ {
+ 	spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0);
+ 	spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1);
+ 	bool		res;
+ 	int			i;
+ 
+ 	/* all tests are exact */
+ 	out->recheck = false;
+ 
+ 	/* leafDatum is what it is... */
+ 	out->leafValue = in->leafDatum;
+ 
+ 	/* Perform the required comparison(s) */
+ 	res = true;
+ 	for (i = 0; i < in->nkeys; i++)
+ 	{
+ 		PGFunction	proc;
+ 		
+ 		/* Find the function which is corresponding to the scan strategy */
+ 		switch (in->scankeys[i].sk_strategy)
+ 		{
+ 			case RANGESTRAT_BEFORE:
+ 				proc = range_before;
+ 				break;
+ 			case RANGESTRAT_OVERLEFT:
+ 				proc = range_overleft;
+ 				break;
+ 			case RANGESTRAT_OVERLAPS:
+ 				proc = range_overlaps;
+ 				break;
+ 			case RANGESTRAT_OVERRIGHT:
+ 				proc = range_overright;
+ 				break;
+ 			case RANGESTRAT_AFTER:
+ 				proc = range_after;
+ 				break;
+ 			case RANGESTRAT_ADJACENT:
+ 				proc = range_adjacent;
+ 				break;
+ 			case RANGESTRAT_CONTAINS:
+ 				proc = range_contains;
+ 				break;
+ 			case RANGESTRAT_CONTAINED_BY:
+ 				proc = range_contained_by;
+ 				break;
+ 			case RANGESTRAT_CONTAINS_ELEM:
+ 				proc = range_contains_elem;
+ 				break;
+ 			case RANGESTRAT_EQ:
+ 				proc = range_eq;
+ 				break;
+ 			default:
+ 				elog(ERROR, "unrecognized range strategy: %d", 
+ 												in->scankeys[i].sk_strategy);
+ 				proc = InvalidOid;
+ 				break;
+ 		}
+ 		res = DatumGetBool(TrickFunctionCall2(proc, fcinfo->flinfo,
+ 								in->leafDatum, in->scankeys[i].sk_argument));
+ 		
+ 		/* If leaf datum don't match to one query, we can don't check another */
+ 		if (!res)
+ 			break;
+ 	}
+ 
+ 	PG_RETURN_BOOL(res);
+ }
*** a/src/include/catalog/pg_amop.h
--- b/src/include/catalog/pg_amop.h
***************
*** 767,770 **** DATA(insert (	4017   25 25 12 s	665 4000 0 ));
--- 767,784 ----
  DATA(insert (	4017   25 25 14 s	667 4000 0 ));
  DATA(insert (	4017   25 25 15 s	666 4000 0 ));
  
+ /*
+  * SP-GiST range_ops
+  */
+ DATA(insert (	3474   3831 3831 1 s	3893 4000 0 ));
+ DATA(insert (	3474   3831 3831 2 s	3895 4000 0 ));
+ DATA(insert (	3474   3831 3831 3 s	3888 4000 0 ));
+ DATA(insert (	3474   3831 3831 4 s	3896 4000 0 ));
+ DATA(insert (	3474   3831 3831 5 s	3894 4000 0 ));
+ DATA(insert (	3474   3831 3831 6 s	3897 4000 0 ));
+ DATA(insert (	3474   3831 3831 7 s	3890 4000 0 ));
+ DATA(insert (	3474   3831 3831 8 s	3892 4000 0 ));
+ DATA(insert (	3474   3831 2283 16 s	3889 4000 0 ));
+ DATA(insert (	3474   3831 3831 18 s	3882 4000 0 ));
+ 
  #endif   /* PG_AMOP_H */
*** a/src/include/catalog/pg_amproc.h
--- b/src/include/catalog/pg_amproc.h
***************
*** 373,377 **** DATA(insert (	4017   25 25 2 4028 ));
--- 373,382 ----
  DATA(insert (	4017   25 25 3 4029 ));
  DATA(insert (	4017   25 25 4 4030 ));
  DATA(insert (	4017   25 25 5 4031 ));
+ DATA(insert (	3474   3831 3831 1 3469 ));
+ DATA(insert (	3474   3831 3831 2 3470 ));
+ DATA(insert (	3474   3831 3831 3 3471 ));
+ DATA(insert (	3474   3831 3831 4 3472 ));
+ DATA(insert (	3474   3831 3831 5 3473 ));
  
  #endif   /* PG_AMPROC_H */
*** a/src/include/catalog/pg_opclass.h
--- b/src/include/catalog/pg_opclass.h
***************
*** 223,228 **** DATA(insert (	783		tsquery_ops			PGNSP PGUID 3702  3615 t 20 ));
--- 223,229 ----
  DATA(insert (	403		range_ops			PGNSP PGUID 3901  3831 t 0 ));
  DATA(insert (	405		range_ops			PGNSP PGUID 3903  3831 t 0 ));
  DATA(insert (	783		range_ops			PGNSP PGUID 3919  3831 t 0 ));
+ DATA(insert (	4000	range_ops			PGNSP PGUID 3474  3831 t 0 ));
  DATA(insert (	4000	quad_point_ops		PGNSP PGUID 4015  600 t 0 ));
  DATA(insert (	4000	kd_point_ops		PGNSP PGUID 4016  600 f 0 ));
  DATA(insert (	4000	text_ops			PGNSP PGUID 4017  25 t 0 ));
*** a/src/include/catalog/pg_opfamily.h
--- b/src/include/catalog/pg_opfamily.h
***************
*** 142,147 **** DATA(insert OID = 3702 (	783		tsquery_ops		PGNSP PGUID ));
--- 142,148 ----
  DATA(insert OID = 3901 (	403		range_ops		PGNSP PGUID ));
  DATA(insert OID = 3903 (	405		range_ops		PGNSP PGUID ));
  DATA(insert OID = 3919 (	783		range_ops		PGNSP PGUID ));
+ DATA(insert OID = 3474 (	4000	range_ops		PGNSP PGUID ));
  DATA(insert OID = 4015 (	4000	quad_point_ops	PGNSP PGUID ));
  DATA(insert OID = 4016 (	4000	kd_point_ops	PGNSP PGUID ));
  DATA(insert OID = 4017 (	4000	text_ops		PGNSP PGUID ));
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4645,4650 **** DESCR("SP-GiST support for suffix tree over text");
--- 4645,4661 ----
  DATA(insert OID = 4031 (  spg_text_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_text_leaf_consistent _null_ _null_ _null_ ));
  DESCR("SP-GiST support for suffix tree over text");
  
+ DATA(insert OID = 3469 (  spg_range_quad_config	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_config _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3470 (  spg_range_quad_choose	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_choose _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3471 (  spg_range_quad_picksplit	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_picksplit _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3472 (  spg_range_quad_inner_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_inner_consistent _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3473 (  spg_range_quad_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_leaf_consistent _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ 
  
  /*
   * Symbolic values for provolatile column: these indicate whether the result
*** a/src/include/utils/rangetypes.h
--- b/src/include/utils/rangetypes.h
***************
*** 75,80 **** typedef struct
--- 75,93 ----
  #define PG_GETARG_RANGE_COPY(n)		DatumGetRangeTypeCopy(PG_GETARG_DATUM(n))
  #define PG_RETURN_RANGE(x)			return RangeTypeGetDatum(x)
  
+ /* Operator strategy numbers used in the GiST range opclass */
+ /* Numbers are chosen to match up operator names with existing usages */
+ #define RANGESTRAT_BEFORE				1
+ #define RANGESTRAT_OVERLEFT				2
+ #define RANGESTRAT_OVERLAPS				3
+ #define RANGESTRAT_OVERRIGHT			4
+ #define RANGESTRAT_AFTER				5
+ #define RANGESTRAT_ADJACENT				6
+ #define RANGESTRAT_CONTAINS				7
+ #define RANGESTRAT_CONTAINED_BY			8
+ #define RANGESTRAT_CONTAINS_ELEM		16
+ #define RANGESTRAT_EQ					18
+ 
  /*
   * prototypes for functions defined in rangetypes.c
   */
***************
*** 166,171 **** extern int range_cmp_bound_values(TypeCacheEntry *typcache, RangeBound *b1,
--- 179,185 ----
  extern RangeType *make_empty_range(TypeCacheEntry *typcache);
  
  /* GiST support (in rangetypes_gist.c) */
+ extern Datum TrickFunctionCall2(PGFunction proc, FmgrInfo *flinfo, Datum arg1, Datum arg2);
  extern Datum range_gist_consistent(PG_FUNCTION_ARGS);
  extern Datum range_gist_compress(PG_FUNCTION_ARGS);
  extern Datum range_gist_decompress(PG_FUNCTION_ARGS);
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
***************
*** 1068,1079 **** ORDER BY 1, 2, 3;
--- 1068,1084 ----
         2742 |            4 | =
         4000 |            1 | <<
         4000 |            1 | ~<~
+        4000 |            2 | &<
         4000 |            2 | ~<=~
+        4000 |            3 | &&
         4000 |            3 | =
+        4000 |            4 | &>
         4000 |            4 | ~>=~
         4000 |            5 | >>
         4000 |            5 | ~>~
+        4000 |            6 | -|-
         4000 |            6 | ~=
+        4000 |            7 | @>
         4000 |            8 | <@
         4000 |           10 | <^
         4000 |           11 | <
***************
*** 1081,1087 **** ORDER BY 1, 2, 3;
         4000 |           12 | <=
         4000 |           14 | >=
         4000 |           15 | >
! (55 rows)
  
  -- Check that all opclass search operators have selectivity estimators.
  -- This is not absolutely required, but it seems a reasonable thing
--- 1086,1094 ----
         4000 |           12 | <=
         4000 |           14 | >=
         4000 |           15 | >
!        4000 |           16 | @>
!        4000 |           18 | =
! (62 rows)
  
  -- Check that all opclass search operators have selectivity estimators.
  -- This is not absolutely required, but it seems a reasonable thing
*** a/src/test/regress/expected/rangetypes.out
--- b/src/test/regress/expected/rangetypes.out
***************
*** 821,826 **** select count(*) from test_range_gist where ir -|- int4range(100,500);
--- 821,1045 ----
       5
  (1 row)
  
+ -- test SP-GiST index that's been built incrementally
+ create table test_range_spgist(ir int4range);
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(g, g+10000) from generate_series(1,1000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(NULL,g*10,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g*10,NULL,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ -- first, verify non-indexed results
+ SET enable_seqscan    = t;
+ SET enable_indexscan  = f;
+ SET enable_bitmapscan = f;
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+  count 
+ -------
+   6200
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> 10;
+  count 
+ -------
+    130
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+  count 
+ -------
+    111
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+  count 
+ -------
+    158
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+  count 
+ -------
+   1062
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+  count 
+ -------
+    189
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+  count 
+ -------
+   3554
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+  count 
+ -------
+   1029
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+  count 
+ -------
+   4794
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+  count 
+ -------
+      5
+ (1 row)
+ 
+ -- now check same queries using index
+ SET enable_seqscan    = f;
+ SET enable_indexscan  = t;
+ SET enable_bitmapscan = f;
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+  count 
+ -------
+      0
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> 10;
+  count 
+ -------
+    130
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+  count 
+ -------
+    111
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+  count 
+ -------
+    158
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+  count 
+ -------
+     62
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+  count 
+ -------
+    189
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+  count 
+ -------
+   3554
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+  count 
+ -------
+   1029
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+  count 
+ -------
+   4794
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+  count 
+ -------
+      0
+ (1 row)
+ 
+ -- now check same queries using a bulk-loaded index
+ drop index test_range_spgist_idx;
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+  count 
+ -------
+      0
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> 10;
+  count 
+ -------
+    130
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+  count 
+ -------
+    111
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+  count 
+ -------
+    158
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+  count 
+ -------
+     62
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+  count 
+ -------
+    189
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+  count 
+ -------
+   3554
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+  count 
+ -------
+   1029
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+  count 
+ -------
+   4794
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+  count 
+ -------
+      0
+ (1 row)
+ 
  RESET enable_seqscan;
  RESET enable_indexscan;
  RESET enable_bitmapscan;
*** a/src/test/regress/expected/sanity_check.out
--- b/src/test/regress/expected/sanity_check.out
***************
*** 156,161 **** SELECT relname, relhasindex
--- 156,162 ----
   tenk2                   | t
   test_range_excl         | t
   test_range_gist         | t
+  test_range_spgist       | t
   test_tsvector           | f
   text_tbl                | f
   time_tbl                | f
***************
*** 164,170 **** SELECT relname, relhasindex
   timetz_tbl              | f
   tinterval_tbl           | f
   varchar_tbl             | f
! (153 rows)
  
  --
  -- another sanity check: every system catalog that has OIDs should have
--- 165,171 ----
   timetz_tbl              | f
   tinterval_tbl           | f
   varchar_tbl             | f
! (154 rows)
  
  --
  -- another sanity check: every system catalog that has OIDs should have
*** a/src/test/regress/output/misc.source
--- b/src/test/regress/output/misc.source
***************
*** 675,680 **** SELECT user_relns() AS user_relns
--- 675,681 ----
   tenk2
   test_range_excl
   test_range_gist
+  test_range_spgist
   test_tsvector
   text_tbl
   time_tbl
***************
*** 685,691 **** SELECT user_relns() AS user_relns
   toyemp
   varchar_tbl
   xacttest
! (107 rows)
  
  SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
   name 
--- 686,692 ----
   toyemp
   varchar_tbl
   xacttest
! (108 rows)
  
  SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
   name 
*** a/src/test/regress/sql/rangetypes.sql
--- b/src/test/regress/sql/rangetypes.sql
***************
*** 220,225 **** select count(*) from test_range_gist where ir &< int4range(100,500);
--- 220,287 ----
  select count(*) from test_range_gist where ir &> int4range(100,500);
  select count(*) from test_range_gist where ir -|- int4range(100,500);
  
+ -- test SP-GiST index that's been built incrementally
+ create table test_range_spgist(ir int4range);
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ 
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(g, g+10000) from generate_series(1,1000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(NULL,g*10,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g*10,NULL,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ 
+ -- first, verify non-indexed results
+ SET enable_seqscan    = t;
+ SET enable_indexscan  = f;
+ SET enable_bitmapscan = f;
+ 
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+ select count(*) from test_range_spgist where ir @> 10;
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ 
+ -- now check same queries using index
+ SET enable_seqscan    = f;
+ SET enable_indexscan  = t;
+ SET enable_bitmapscan = f;
+ 
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+ select count(*) from test_range_spgist where ir @> 10;
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ 
+ -- now check same queries using a bulk-loaded index
+ drop index test_range_spgist_idx;
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ 
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+ select count(*) from test_range_spgist where ir @> 10;
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ 
  RESET enable_seqscan;
  RESET enable_indexscan;
  RESET enable_bitmapscan;
#13Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Alexander Korotkov (#12)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On 12.07.2012 02:11, Alexander Korotkov wrote:

On Thu, Jul 12, 2012 at 3:03 AM, Alexander Korotkov<aekorotkov@gmail.com>wrote:

On Tue, Jul 3, 2012 at 10:51 AM, Jeff Davis<pgsql@j-davis.com> wrote:

Also, it would be helpful to add a couple tests to rangetypes.sql.

New version of patch is attached.

Oops, forgot to include one comment fix into patch.

Thanks. Can you do something about TrickFunctionCall2, please?
(http://archives.postgresql.org/message-id/4FE2C968.2010503@enterprisedb.com)
A separate patch to refactor that in the existing gist opclass would
probably be best.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#14Alexander Korotkov
aekorotkov@gmail.com
In reply to: Heikki Linnakangas (#13)
2 attachment(s)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Thu, Jul 12, 2012 at 10:29 AM, Heikki Linnakangas <
heikki.linnakangas@enterprisedb.com> wrote:

On 12.07.2012 02:11, Alexander Korotkov wrote:

On Thu, Jul 12, 2012 at 3:03 AM, Alexander Korotkov<aekorotkov@gmail.com>
**wrote:

On Tue, Jul 3, 2012 at 10:51 AM, Jeff Davis<pgsql@j-davis.com> wrote:

Also, it would be helpful to add a couple tests to rangetypes.sql.

New version of patch is attached.

Oops, forgot to include one comment fix into patch.

Thanks. Can you do something about TrickFunctionCall2, please? (
http://archives.postgresql.**org/message-id/4FE2C968.**
2010503@enterprisedb.com<http://archives.postgresql.org/message-id/4FE2C968.2010503@enterprisedb.com&gt;)
A separate patch to refactor that in the existing gist opclass would
probably be best.

Done. There are separate patch for get rid of TrickFunctionCall2 and
version of SP-GiST for ranges based on that patch.

------
With best regards,
Alexander Korotkov.

Attachments:

notrickfunctioncall-0.1.patchapplication/octet-stream; name=notrickfunctioncall-0.1.patchDownload
*** a/src/backend/utils/adt/rangetypes.c
--- b/src/backend/utils/adt/rangetypes.c
***************
*** 63,72 **** static const char *range_parse_bound(const char *string, const char *ptr,
  static char *range_deparse(char flags, const char *lbound_str,
  			  const char *ubound_str);
  static char *range_bound_escape(const char *value);
- static bool range_contains_internal(TypeCacheEntry *typcache,
- 						RangeType *r1, RangeType *r2);
- static bool range_contains_elem_internal(TypeCacheEntry *typcache,
- 							 RangeType *r, Datum val);
  static Size datum_compute_size(Size sz, Datum datum, bool typbyval,
  				   char typalign, int16 typlen, char typstorage);
  static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
--- 63,68 ----
***************
*** 546,558 **** elem_contained_by_range(PG_FUNCTION_ARGS)
  
  /* range, range -> bool functions */
  
! /* equality */
! Datum
! range_eq(PG_FUNCTION_ARGS)
  {
- 	RangeType  *r1 = PG_GETARG_RANGE(0);
- 	RangeType  *r2 = PG_GETARG_RANGE(1);
- 	TypeCacheEntry *typcache;
  	RangeBound	lower1,
  				lower2;
  	RangeBound	upper1,
--- 542,551 ----
  
  /* range, range -> bool functions */
  
! /* equality (internal version) */
! bool
! range_eq_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
  {
  	RangeBound	lower1,
  				lower2;
  	RangeBound	upper1,
***************
*** 564,595 **** range_eq(PG_FUNCTION_ARGS)
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
  
- 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
- 
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
  	if (empty1 && empty2)
! 		PG_RETURN_BOOL(true);
  	if (empty1 != empty2)
! 		PG_RETURN_BOOL(false);
  
  	if (range_cmp_bounds(typcache, &lower1, &lower2) != 0)
! 		PG_RETURN_BOOL(false);
  
  	if (range_cmp_bounds(typcache, &upper1, &upper2) != 0)
! 		PG_RETURN_BOOL(false);
  
! 	PG_RETURN_BOOL(true);
  }
  
  /* inequality */
  Datum
  range_ne(PG_FUNCTION_ARGS)
  {
! 	bool		eq = DatumGetBool(range_eq(fcinfo));
  
! 	PG_RETURN_BOOL(!eq);
  }
  
  /* contains? */
--- 557,610 ----
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
  
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
  	if (empty1 && empty2)
! 		return true;
  	if (empty1 != empty2)
! 		return false;
  
  	if (range_cmp_bounds(typcache, &lower1, &lower2) != 0)
! 		return false;
  
  	if (range_cmp_bounds(typcache, &upper1, &upper2) != 0)
! 		return false;
! 
! 	return true;
! }
  
! /* equality */
! Datum
! range_eq(PG_FUNCTION_ARGS)
! {
! 	RangeType  *r1 = PG_GETARG_RANGE(0);
! 	RangeType  *r2 = PG_GETARG_RANGE(1);
! 	TypeCacheEntry *typcache;
! 
! 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
! 
! 	PG_RETURN_BOOL(range_eq_internal(typcache, r1, r2));
! }
! 
! /* inequality (internal version) */
! bool
! range_ne_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
! {
! 	return (!range_eq_internal(typcache, r1, r2));
  }
  
  /* inequality */
  Datum
  range_ne(PG_FUNCTION_ARGS)
  {
! 	RangeType  *r1 = PG_GETARG_RANGE(0);
! 	RangeType  *r2 = PG_GETARG_RANGE(1);
! 	TypeCacheEntry *typcache;
! 
! 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
  
! 	PG_RETURN_BOOL(range_ne_internal(typcache, r1, r2));
  }
  
  /* contains? */
***************
*** 600,609 **** range_contains(PG_FUNCTION_ARGS)
  	RangeType  *r2 = PG_GETARG_RANGE(1);
  	TypeCacheEntry *typcache;
  
- 	/* Different types should be prevented by ANYRANGE matching rules */
- 	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
- 		elog(ERROR, "range types do not match");
- 
  	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
  
  	PG_RETURN_BOOL(range_contains_internal(typcache, r1, r2));
--- 615,620 ----
***************
*** 617,638 **** range_contained_by(PG_FUNCTION_ARGS)
  	RangeType  *r2 = PG_GETARG_RANGE(1);
  	TypeCacheEntry *typcache;
  
- 	/* Different types should be prevented by ANYRANGE matching rules */
- 	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
- 		elog(ERROR, "range types do not match");
- 
  	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
  
! 	PG_RETURN_BOOL(range_contains_internal(typcache, r2, r1));
  }
  
! /* strictly left of? */
! Datum
! range_before(PG_FUNCTION_ARGS)
  {
- 	RangeType  *r1 = PG_GETARG_RANGE(0);
- 	RangeType  *r2 = PG_GETARG_RANGE(1);
- 	TypeCacheEntry *typcache;
  	RangeBound	lower1,
  				lower2;
  	RangeBound	upper1,
--- 628,642 ----
  	RangeType  *r2 = PG_GETARG_RANGE(1);
  	TypeCacheEntry *typcache;
  
  	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
  
! 	PG_RETURN_BOOL(range_contained_by_internal(typcache, r1, r2));
  }
  
! /* strictly left of? (internal version) */
! bool
! range_before_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
  {
  	RangeBound	lower1,
  				lower2;
  	RangeBound	upper1,
***************
*** 644,668 **** range_before(PG_FUNCTION_ARGS)
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
  
- 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
- 
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
  	/* An empty range is neither before nor after any other range */
  	if (empty1 || empty2)
! 		PG_RETURN_BOOL(false);
  
! 	PG_RETURN_BOOL(range_cmp_bounds(typcache, &upper1, &lower2) < 0);
  }
  
! /* strictly right of? */
  Datum
! range_after(PG_FUNCTION_ARGS)
  {
  	RangeType  *r1 = PG_GETARG_RANGE(0);
  	RangeType  *r2 = PG_GETARG_RANGE(1);
  	TypeCacheEntry *typcache;
  	RangeBound	lower1,
  				lower2;
  	RangeBound	upper1,
--- 648,680 ----
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
  
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
  	/* An empty range is neither before nor after any other range */
  	if (empty1 || empty2)
! 		return false;
  
! 	return (range_cmp_bounds(typcache, &upper1, &lower2) < 0);
  }
  
! /* strictly left of? */
  Datum
! range_before(PG_FUNCTION_ARGS)
  {
  	RangeType  *r1 = PG_GETARG_RANGE(0);
  	RangeType  *r2 = PG_GETARG_RANGE(1);
  	TypeCacheEntry *typcache;
+ 
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+ 
+ 	PG_RETURN_BOOL(range_before_internal(typcache, r1, r2));
+ }
+ 
+ /* strictly right of? (internal version) */
+ bool
+ range_after_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+ {
  	RangeBound	lower1,
  				lower2;
  	RangeBound	upper1,
***************
*** 674,698 **** range_after(PG_FUNCTION_ARGS)
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
  
- 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
- 
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
  	/* An empty range is neither before nor after any other range */
  	if (empty1 || empty2)
! 		PG_RETURN_BOOL(false);
  
! 	PG_RETURN_BOOL(range_cmp_bounds(typcache, &lower1, &upper2) > 0);
  }
  
! /* adjacent to (but not overlapping)? */
  Datum
! range_adjacent(PG_FUNCTION_ARGS)
  {
  	RangeType  *r1 = PG_GETARG_RANGE(0);
  	RangeType  *r2 = PG_GETARG_RANGE(1);
  	TypeCacheEntry *typcache;
  	RangeBound	lower1,
  				lower2;
  	RangeBound	upper1,
--- 686,718 ----
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
  
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
  	/* An empty range is neither before nor after any other range */
  	if (empty1 || empty2)
! 		return false;
  
! 	return (range_cmp_bounds(typcache, &lower1, &upper2) > 0);
  }
  
! /* strictly right of? */
  Datum
! range_after(PG_FUNCTION_ARGS)
  {
  	RangeType  *r1 = PG_GETARG_RANGE(0);
  	RangeType  *r2 = PG_GETARG_RANGE(1);
  	TypeCacheEntry *typcache;
+ 
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+ 
+ 	PG_RETURN_BOOL(range_after_internal(typcache, r1, r2));
+ }
+ 
+ /* adjacent to (but not overlapping)? (internal version) */
+ bool
+ range_adjacent_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+ {
  	RangeBound	lower1,
  				lower2;
  	RangeBound	upper1,
***************
*** 706,719 **** range_adjacent(PG_FUNCTION_ARGS)
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
  
- 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
- 
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
  	/* An empty range is not adjacent to any other range */
  	if (empty1 || empty2)
! 		PG_RETURN_BOOL(false);
  
  	/*
  	 * Given two ranges A..B and C..D, where B < C, the ranges are adjacent if
--- 726,737 ----
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
  
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
  	/* An empty range is not adjacent to any other range */
  	if (empty1 || empty2)
! 		return false;
  
  	/*
  	 * Given two ranges A..B and C..D, where B < C, the ranges are adjacent if
***************
*** 736,742 **** range_adjacent(PG_FUNCTION_ARGS)
  	{
  		/* in a continuous subtype, there are assumed to be points between */
  		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
! 			PG_RETURN_BOOL(false);
  		/* flip the inclusion flags */
  		upper1.inclusive = !upper1.inclusive;
  		lower2.inclusive = !lower2.inclusive;
--- 754,760 ----
  	{
  		/* in a continuous subtype, there are assumed to be points between */
  		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
! 			return (false);
  		/* flip the inclusion flags */
  		upper1.inclusive = !upper1.inclusive;
  		lower2.inclusive = !lower2.inclusive;
***************
*** 744,754 **** range_adjacent(PG_FUNCTION_ARGS)
  		upper1.lower = true;
  		lower2.lower = false;
  		r3 = make_range(typcache, &upper1, &lower2, false);
! 		PG_RETURN_BOOL(RangeIsEmpty(r3));
  	}
  	if (cmp == 0)
  	{
! 		PG_RETURN_BOOL(upper1.inclusive != lower2.inclusive);
  	}
  
  	cmp = range_cmp_bound_values(typcache, &upper2, &lower1);
--- 762,772 ----
  		upper1.lower = true;
  		lower2.lower = false;
  		r3 = make_range(typcache, &upper1, &lower2, false);
! 		return RangeIsEmpty(r3);
  	}
  	if (cmp == 0)
  	{
! 		return (upper1.inclusive != lower2.inclusive);
  	}
  
  	cmp = range_cmp_bound_values(typcache, &upper2, &lower1);
***************
*** 756,762 **** range_adjacent(PG_FUNCTION_ARGS)
  	{
  		/* in a continuous subtype, there are assumed to be points between */
  		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
! 			PG_RETURN_BOOL(false);
  		/* flip the inclusion flags */
  		upper2.inclusive = !upper2.inclusive;
  		lower1.inclusive = !lower1.inclusive;
--- 774,780 ----
  	{
  		/* in a continuous subtype, there are assumed to be points between */
  		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
! 			return (false);
  		/* flip the inclusion flags */
  		upper2.inclusive = !upper2.inclusive;
  		lower1.inclusive = !lower1.inclusive;
***************
*** 764,786 **** range_adjacent(PG_FUNCTION_ARGS)
  		upper2.lower = true;
  		lower1.lower = false;
  		r3 = make_range(typcache, &upper2, &lower1, false);
! 		PG_RETURN_BOOL(RangeIsEmpty(r3));
  	}
  	if (cmp == 0)
  	{
! 		PG_RETURN_BOOL(upper2.inclusive != lower1.inclusive);
  	}
  
! 	PG_RETURN_BOOL(false);
  }
  
! /* overlaps? */
  Datum
! range_overlaps(PG_FUNCTION_ARGS)
  {
  	RangeType  *r1 = PG_GETARG_RANGE(0);
  	RangeType  *r2 = PG_GETARG_RANGE(1);
  	TypeCacheEntry *typcache;
  	RangeBound	lower1,
  				lower2;
  	RangeBound	upper1,
--- 782,814 ----
  		upper2.lower = true;
  		lower1.lower = false;
  		r3 = make_range(typcache, &upper2, &lower1, false);
! 		return RangeIsEmpty(r3);
  	}
  	if (cmp == 0)
  	{
! 		return (upper2.inclusive != lower1.inclusive);
  	}
  
! 	return false;
  }
  
! /* adjacent to (but not overlapping)? */
  Datum
! range_adjacent(PG_FUNCTION_ARGS)
  {
  	RangeType  *r1 = PG_GETARG_RANGE(0);
  	RangeType  *r2 = PG_GETARG_RANGE(1);
  	TypeCacheEntry *typcache;
+ 
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+ 
+ 	PG_RETURN_BOOL(range_adjacent_internal(typcache, r1, r2));
+ }
+ 
+ /* overlaps? (internal version) */
+ bool
+ range_overlaps_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+ {
  	RangeBound	lower1,
  				lower2;
  	RangeBound	upper1,
***************
*** 792,824 **** range_overlaps(PG_FUNCTION_ARGS)
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
  
- 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
- 
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
  	/* An empty range does not overlap any other range */
  	if (empty1 || empty2)
! 		PG_RETURN_BOOL(false);
  
  	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0 &&
  		range_cmp_bounds(typcache, &lower1, &upper2) <= 0)
! 		PG_RETURN_BOOL(true);
  
  	if (range_cmp_bounds(typcache, &lower2, &lower1) >= 0 &&
  		range_cmp_bounds(typcache, &lower2, &upper1) <= 0)
! 		PG_RETURN_BOOL(true);
  
! 	PG_RETURN_BOOL(false);
  }
  
! /* does not extend to right of? */
  Datum
! range_overleft(PG_FUNCTION_ARGS)
  {
  	RangeType  *r1 = PG_GETARG_RANGE(0);
  	RangeType  *r2 = PG_GETARG_RANGE(1);
  	TypeCacheEntry *typcache;
  	RangeBound	lower1,
  				lower2;
  	RangeBound	upper1,
--- 820,860 ----
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
  
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
  	/* An empty range does not overlap any other range */
  	if (empty1 || empty2)
! 		return false;
  
  	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0 &&
  		range_cmp_bounds(typcache, &lower1, &upper2) <= 0)
! 		return true;
  
  	if (range_cmp_bounds(typcache, &lower2, &lower1) >= 0 &&
  		range_cmp_bounds(typcache, &lower2, &upper1) <= 0)
! 		return true;
  
! 	return false;
  }
  
! /* overlaps? */
  Datum
! range_overlaps(PG_FUNCTION_ARGS)
  {
  	RangeType  *r1 = PG_GETARG_RANGE(0);
  	RangeType  *r2 = PG_GETARG_RANGE(1);
  	TypeCacheEntry *typcache;
+ 
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+ 
+ 	PG_RETURN_BOOL(range_overlaps_internal(typcache, r1, r2));
+ }
+ 
+ /* does not extend to right of? (internal version) */
+ bool
+ range_overleft_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+ {
  	RangeBound	lower1,
  				lower2;
  	RangeBound	upper1,
***************
*** 830,857 **** range_overleft(PG_FUNCTION_ARGS)
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
  
- 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
- 
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
  	/* An empty range is neither before nor after any other range */
  	if (empty1 || empty2)
! 		PG_RETURN_BOOL(false);
  
  	if (range_cmp_bounds(typcache, &upper1, &upper2) <= 0)
! 		PG_RETURN_BOOL(true);
  
! 	PG_RETURN_BOOL(false);
  }
  
! /* does not extend to left of? */
  Datum
! range_overright(PG_FUNCTION_ARGS)
  {
  	RangeType  *r1 = PG_GETARG_RANGE(0);
  	RangeType  *r2 = PG_GETARG_RANGE(1);
  	TypeCacheEntry *typcache;
  	RangeBound	lower1,
  				lower2;
  	RangeBound	upper1,
--- 866,901 ----
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
  
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
  	/* An empty range is neither before nor after any other range */
  	if (empty1 || empty2)
! 		return false;
  
  	if (range_cmp_bounds(typcache, &upper1, &upper2) <= 0)
! 		return true;
  
! 	return false;
  }
  
! /* does not extend to right of? */
  Datum
! range_overleft(PG_FUNCTION_ARGS)
  {
  	RangeType  *r1 = PG_GETARG_RANGE(0);
  	RangeType  *r2 = PG_GETARG_RANGE(1);
  	TypeCacheEntry *typcache;
+ 
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+ 
+ 	PG_RETURN_BOOL(range_overleft_internal(typcache, r1, r2));
+ }
+ 
+ /* does not extend to left of? (internal version) */
+ bool
+ range_overright_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+ {
  	RangeBound	lower1,
  				lower2;
  	RangeBound	upper1,
***************
*** 863,870 **** range_overright(PG_FUNCTION_ARGS)
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
  
- 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
- 
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
--- 907,912 ----
***************
*** 878,883 **** range_overright(PG_FUNCTION_ARGS)
--- 920,938 ----
  	PG_RETURN_BOOL(false);
  }
  
+ /* does not extend to left of? */
+ Datum
+ range_overright(PG_FUNCTION_ARGS)
+ {
+ 	RangeType  *r1 = PG_GETARG_RANGE(0);
+ 	RangeType  *r2 = PG_GETARG_RANGE(1);
+ 	TypeCacheEntry *typcache;
+ 
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+ 
+ 	PG_RETURN_BOOL(range_overright_internal(typcache, r1, r2));
+ }
+ 
  
  /* range, range -> range functions */
  
***************
*** 2152,2158 **** range_bound_escape(const char *value)
   * Caller has already checked that they are the same range type, and looked up
   * the necessary typcache entry.
   */
! static bool
  range_contains_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
  {
  	RangeBound	lower1;
--- 2207,2213 ----
   * Caller has already checked that they are the same range type, and looked up
   * the necessary typcache entry.
   */
! bool
  range_contains_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
  {
  	RangeBound	lower1;
***************
*** 2162,2167 **** range_contains_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
--- 2217,2226 ----
  	RangeBound	upper2;
  	bool		empty2;
  
+ 	/* Different types should be prevented by ANYRANGE matching rules */
+ 	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
+ 		elog(ERROR, "range types do not match");
+ 	
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
***************
*** 2180,2189 **** range_contains_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
  	return true;
  }
  
  /*
   * Test whether range r contains a specific element value.
   */
! static bool
  range_contains_elem_internal(TypeCacheEntry *typcache, RangeType *r, Datum val)
  {
  	RangeBound	lower;
--- 2239,2254 ----
  	return true;
  }
  
+ bool
+ range_contained_by_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+ {
+ 	return range_contains_internal(typcache, r2, r1);
+ }
+ 
  /*
   * Test whether range r contains a specific element value.
   */
! bool
  range_contains_elem_internal(TypeCacheEntry *typcache, RangeType *r, Datum val)
  {
  	RangeBound	lower;
*** a/src/backend/utils/adt/rangetypes_gist.c
--- b/src/backend/utils/adt/rangetypes_gist.c
***************
*** 148,157 **** typedef struct
  
  static RangeType *range_super_union(TypeCacheEntry *typcache, RangeType *r1,
  				  RangeType *r2);
! static bool range_gist_consistent_int(FmgrInfo *flinfo,
  						  StrategyNumber strategy, RangeType *key,
  						  Datum query);
! static bool range_gist_consistent_leaf(FmgrInfo *flinfo,
  						   StrategyNumber strategy, RangeType *key,
  						   Datum query);
  static void range_gist_fallback_split(TypeCacheEntry *typcache,
--- 148,157 ----
  
  static RangeType *range_super_union(TypeCacheEntry *typcache, RangeType *r1,
  				  RangeType *r2);
! static bool range_gist_consistent_int(TypeCacheEntry *typcache,
  						  StrategyNumber strategy, RangeType *key,
  						  Datum query);
! static bool range_gist_consistent_leaf(TypeCacheEntry *typcache,
  						   StrategyNumber strategy, RangeType *key,
  						   Datum query);
  static void range_gist_fallback_split(TypeCacheEntry *typcache,
***************
*** 191,205 **** range_gist_consistent(PG_FUNCTION_ARGS)
  	/* Oid subtype = PG_GETARG_OID(3); */
  	bool	   *recheck = (bool *) PG_GETARG_POINTER(4);
  	RangeType  *key = DatumGetRangeType(entry->key);
  
  	/* All operators served by this function are exact */
  	*recheck = false;
  
  	if (GIST_LEAF(entry))
! 		PG_RETURN_BOOL(range_gist_consistent_leaf(fcinfo->flinfo, strategy,
  												  key, query));
  	else
! 		PG_RETURN_BOOL(range_gist_consistent_int(fcinfo->flinfo, strategy,
  												 key, query));
  }
  
--- 191,208 ----
  	/* Oid subtype = PG_GETARG_OID(3); */
  	bool	   *recheck = (bool *) PG_GETARG_POINTER(4);
  	RangeType  *key = DatumGetRangeType(entry->key);
+ 	TypeCacheEntry *typcache;
  
  	/* All operators served by this function are exact */
  	*recheck = false;
  
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(key));
+ 	
  	if (GIST_LEAF(entry))
! 		PG_RETURN_BOOL(range_gist_consistent_leaf(typcache, strategy,
  												  key, query));
  	else
! 		PG_RETURN_BOOL(range_gist_consistent_int(typcache, strategy,
  												 key, query));
  }
  
***************
*** 781,870 **** range_super_union(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
  }
  
  /*
-  * trick function call: call the given function with given FmgrInfo
-  *
-  * To allow the various functions called here to cache lookups of range
-  * datatype information, we use a trick: we pass them the FmgrInfo struct
-  * for the GiST consistent function.  This relies on the knowledge that
-  * none of them consult FmgrInfo for anything but fn_extra, and that they
-  * all use fn_extra the same way, i.e. as a pointer to the typcache entry
-  * for the range data type.  Since the FmgrInfo is long-lived (it's actually
-  * part of the relcache entry for the index, typically) this essentially
-  * eliminates lookup overhead during operations on a GiST range index.
-  */
- static Datum
- TrickFunctionCall2(PGFunction proc, FmgrInfo *flinfo, Datum arg1, Datum arg2)
- {
- 	FunctionCallInfoData fcinfo;
- 	Datum		result;
- 
- 	InitFunctionCallInfoData(fcinfo, flinfo, 2, InvalidOid, NULL, NULL);
- 
- 	fcinfo.arg[0] = arg1;
- 	fcinfo.arg[1] = arg2;
- 	fcinfo.argnull[0] = false;
- 	fcinfo.argnull[1] = false;
- 
- 	result = (*proc) (&fcinfo);
- 
- 	if (fcinfo.isnull)
- 		elog(ERROR, "function %p returned NULL", proc);
- 
- 	return result;
- }
- 
- /*
   * GiST consistent test on an index internal page
   */
  static bool
! range_gist_consistent_int(FmgrInfo *flinfo, StrategyNumber strategy,
  						  RangeType *key, Datum query)
  {
- 	PGFunction	proc;
- 	bool		negate = false;
- 	bool		retval;
- 
  	switch (strategy)
  	{
  		case RANGESTRAT_BEFORE:
  			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeType(query)))
  				return false;
! 			proc = range_overright;
! 			negate = true;
! 			break;
  		case RANGESTRAT_OVERLEFT:
  			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeType(query)))
  				return false;
! 			proc = range_after;
! 			negate = true;
! 			break;
  		case RANGESTRAT_OVERLAPS:
! 			proc = range_overlaps;
! 			break;
  		case RANGESTRAT_OVERRIGHT:
  			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeType(query)))
  				return false;
! 			proc = range_before;
! 			negate = true;
! 			break;
  		case RANGESTRAT_AFTER:
  			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeType(query)))
  				return false;
! 			proc = range_overleft;
! 			negate = true;
! 			break;
  		case RANGESTRAT_ADJACENT:
  			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeType(query)))
  				return false;
! 			if (DatumGetBool(TrickFunctionCall2(range_adjacent, flinfo,
! 												RangeTypeGetDatum(key),
! 												query)))
  				return true;
! 			proc = range_overlaps;
! 			break;
  		case RANGESTRAT_CONTAINS:
! 			proc = range_contains;
! 			break;
  		case RANGESTRAT_CONTAINED_BY:
  
  			/*
--- 784,831 ----
  }
  
  /*
   * GiST consistent test on an index internal page
   */
  static bool
! range_gist_consistent_int(TypeCacheEntry *typcache, StrategyNumber strategy,
  						  RangeType *key, Datum query)
  {
  	switch (strategy)
  	{
  		case RANGESTRAT_BEFORE:
  			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeType(query)))
  				return false;
! 			return (!range_overright_internal(typcache, key,
! 													DatumGetRangeType(query)));
  		case RANGESTRAT_OVERLEFT:
  			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeType(query)))
  				return false;
! 			return (!range_after_internal(typcache, key,
! 													DatumGetRangeType(query)));
  		case RANGESTRAT_OVERLAPS:
! 			return range_overlaps_internal(typcache, key,
! 													DatumGetRangeType(query));
  		case RANGESTRAT_OVERRIGHT:
  			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeType(query)))
  				return false;
! 			return (!range_before_internal(typcache, key,
! 													DatumGetRangeType(query)));
  		case RANGESTRAT_AFTER:
  			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeType(query)))
  				return false;
! 			return (!range_overleft_internal(typcache, key,
! 													DatumGetRangeType(query)));
  		case RANGESTRAT_ADJACENT:
  			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeType(query)))
  				return false;
! 			if (range_adjacent_internal(typcache, key,
! 													DatumGetRangeType(query)))
  				return true;
! 			return range_overlaps_internal(typcache, key,
! 													DatumGetRangeType(query));
  		case RANGESTRAT_CONTAINS:
! 			return range_contains_internal(typcache, key,
! 													DatumGetRangeType(query));
  		case RANGESTRAT_CONTAINED_BY:
  
  			/*
***************
*** 874,884 **** range_gist_consistent_int(FmgrInfo *flinfo, StrategyNumber strategy,
  			 */
  			if (RangeIsOrContainsEmpty(key))
  				return true;
! 			proc = range_overlaps;
! 			break;
  		case RANGESTRAT_CONTAINS_ELEM:
! 			proc = range_contains_elem;
! 			break;
  		case RANGESTRAT_EQ:
  
  			/*
--- 835,844 ----
  			 */
  			if (RangeIsOrContainsEmpty(key))
  				return true;
! 			return range_overlaps_internal(typcache, key,
! 													DatumGetRangeType(query));
  		case RANGESTRAT_CONTAINS_ELEM:
! 			return range_contains_elem_internal(typcache, key, query);
  		case RANGESTRAT_EQ:
  
  			/*
***************
*** 887,959 **** range_gist_consistent_int(FmgrInfo *flinfo, StrategyNumber strategy,
  			 */
  			if (RangeIsEmpty(DatumGetRangeType(query)))
  				return RangeIsOrContainsEmpty(key);
! 			proc = range_contains;
! 			break;
  		default:
  			elog(ERROR, "unrecognized range strategy: %d", strategy);
- 			proc = NULL;		/* keep compiler quiet */
  			break;
  	}
  
! 	retval = DatumGetBool(TrickFunctionCall2(proc, flinfo,
! 											 RangeTypeGetDatum(key),
! 											 query));
! 	if (negate)
! 		retval = !retval;
! 
! 	return retval;
  }
  
  /*
   * GiST consistent test on an index leaf page
   */
  static bool
! range_gist_consistent_leaf(FmgrInfo *flinfo, StrategyNumber strategy,
  						   RangeType *key, Datum query)
  {
- 	PGFunction	proc;
- 
  	switch (strategy)
  	{
  		case RANGESTRAT_BEFORE:
! 			proc = range_before;
! 			break;
  		case RANGESTRAT_OVERLEFT:
! 			proc = range_overleft;
! 			break;
  		case RANGESTRAT_OVERLAPS:
! 			proc = range_overlaps;
! 			break;
  		case RANGESTRAT_OVERRIGHT:
! 			proc = range_overright;
! 			break;
  		case RANGESTRAT_AFTER:
! 			proc = range_after;
! 			break;
  		case RANGESTRAT_ADJACENT:
! 			proc = range_adjacent;
! 			break;
  		case RANGESTRAT_CONTAINS:
! 			proc = range_contains;
! 			break;
  		case RANGESTRAT_CONTAINED_BY:
! 			proc = range_contained_by;
! 			break;
  		case RANGESTRAT_CONTAINS_ELEM:
! 			proc = range_contains_elem;
! 			break;
  		case RANGESTRAT_EQ:
! 			proc = range_eq;
! 			break;
  		default:
  			elog(ERROR, "unrecognized range strategy: %d", strategy);
- 			proc = NULL;		/* keep compiler quiet */
  			break;
  	}
  
! 	return DatumGetBool(TrickFunctionCall2(proc, flinfo,
! 										   RangeTypeGetDatum(key),
! 										   query));
  }
  
  /*
--- 847,907 ----
  			 */
  			if (RangeIsEmpty(DatumGetRangeType(query)))
  				return RangeIsOrContainsEmpty(key);
! 			return range_contains_internal(typcache, key,
! 													DatumGetRangeType(query));
  		default:
  			elog(ERROR, "unrecognized range strategy: %d", strategy);
  			break;
  	}
  
! 	/* Keep compiler quite */
! 	return false;
  }
  
  /*
   * GiST consistent test on an index leaf page
   */
  static bool
! range_gist_consistent_leaf(TypeCacheEntry *typcache, StrategyNumber strategy,
  						   RangeType *key, Datum query)
  {
  	switch (strategy)
  	{
  		case RANGESTRAT_BEFORE:
! 			return range_before_internal(typcache, key,
! 													DatumGetRangeType(query));
  		case RANGESTRAT_OVERLEFT:
! 			return range_overleft_internal(typcache, key,
! 													DatumGetRangeType(query));
  		case RANGESTRAT_OVERLAPS:
! 			return range_overlaps_internal(typcache, key,
! 													DatumGetRangeType(query));
  		case RANGESTRAT_OVERRIGHT:
! 			return range_overright_internal(typcache, key,
! 													DatumGetRangeType(query));
  		case RANGESTRAT_AFTER:
! 			return range_after_internal(typcache, key,
! 													DatumGetRangeType(query));
  		case RANGESTRAT_ADJACENT:
! 			return range_adjacent_internal(typcache, key,
! 													DatumGetRangeType(query));
  		case RANGESTRAT_CONTAINS:
! 			return range_contains_internal(typcache, key,
! 													DatumGetRangeType(query));
  		case RANGESTRAT_CONTAINED_BY:
! 			return range_contained_by_internal(typcache, key,
! 													DatumGetRangeType(query));
  		case RANGESTRAT_CONTAINS_ELEM:
! 			return range_contains_elem_internal(typcache, key, query);
  		case RANGESTRAT_EQ:
! 			return range_eq_internal(typcache, key, DatumGetRangeType(query));
  		default:
  			elog(ERROR, "unrecognized range strategy: %d", strategy);
  			break;
  	}
  
! 	/* Keep compiler quite */
! 	return false;
  }
  
  /*
*** a/src/include/utils/rangetypes.h
--- b/src/include/utils/rangetypes.h
***************
*** 104,109 **** extern Datum range_upper_inf(PG_FUNCTION_ARGS);
--- 104,111 ----
  extern Datum range_contains_elem(PG_FUNCTION_ARGS);
  extern Datum elem_contained_by_range(PG_FUNCTION_ARGS);
  
+ extern bool range_contains_elem_internal(TypeCacheEntry *typcache, RangeType *r, Datum val);
+ 
  /* range, range -> bool */
  extern Datum range_eq(PG_FUNCTION_ARGS);
  extern Datum range_ne(PG_FUNCTION_ARGS);
***************
*** 116,121 **** extern Datum range_overlaps(PG_FUNCTION_ARGS);
--- 118,135 ----
  extern Datum range_overleft(PG_FUNCTION_ARGS);
  extern Datum range_overright(PG_FUNCTION_ARGS);
  
+ /* internal versions */
+ extern bool range_eq_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2);
+ extern bool range_ne_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2);
+ extern bool range_contains_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2);
+ extern bool range_contained_by_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2);
+ extern bool range_before_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2);
+ extern bool range_after_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2);
+ extern bool range_adjacent_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2);
+ extern bool range_overlaps_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2);
+ extern bool range_overleft_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2);
+ extern bool range_overright_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2);
+ 
  /* range, range -> range */
  extern Datum range_minus(PG_FUNCTION_ARGS);
  extern Datum range_union(PG_FUNCTION_ARGS);
range_spgist_quad-0.5.patchapplication/octet-stream; name=range_spgist_quad-0.5.patchDownload
*** a/src/backend/utils/adt/Makefile
--- b/src/backend/utils/adt/Makefile
***************
*** 30,36 **** OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
  	tsvector.o tsvector_op.o tsvector_parser.o \
! 	txid.o uuid.o windowfuncs.o xml.o
  
  like.o: like.c like_match.c
  
--- 30,36 ----
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
  	tsvector.o tsvector_op.o tsvector_parser.o \
! 	txid.o uuid.o windowfuncs.o xml.o rangetypes_spgist.o
  
  like.o: like.c like_match.c
  
*** a/src/backend/utils/adt/rangetypes_gist.c
--- b/src/backend/utils/adt/rangetypes_gist.c
***************
*** 20,39 ****
  #include "utils/datum.h"
  #include "utils/rangetypes.h"
  
- 
- /* Operator strategy numbers used in the GiST range opclass */
- /* Numbers are chosen to match up operator names with existing usages */
- #define RANGESTRAT_BEFORE				1
- #define RANGESTRAT_OVERLEFT				2
- #define RANGESTRAT_OVERLAPS				3
- #define RANGESTRAT_OVERRIGHT			4
- #define RANGESTRAT_AFTER				5
- #define RANGESTRAT_ADJACENT				6
- #define RANGESTRAT_CONTAINS				7
- #define RANGESTRAT_CONTAINED_BY			8
- #define RANGESTRAT_CONTAINS_ELEM		16
- #define RANGESTRAT_EQ					18
- 
  /*
   * Range class properties used to segregate different classes of ranges in
   * GiST.  Each unique combination of properties is a class.  CLS_EMPTY cannot
--- 20,25 ----
*** /dev/null
--- b/src/backend/utils/adt/rangetypes_spgist.c
***************
*** 0 ****
--- 1,815 ----
+ /*-------------------------------------------------------------------------
+  *
+  * rangetypes_spgist.c
+  *	  implementation of quad tree over ranges mapped to 2d-points for SP-GiST
+  *
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * IDENTIFICATION
+  *			src/backend/utils/adt/rangetypes_spgist.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #include "postgres.h"
+ 
+ #include "access/spgist.h"
+ #include "access/skey.h"
+ #include "catalog/pg_type.h"
+ #include "utils/builtins.h"
+ #include "utils/datum.h"
+ #include "utils/rangetypes.h"
+ 
+ Datum spg_range_quad_config(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_choose(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_picksplit(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_inner_consistent(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_leaf_consistent(PG_FUNCTION_ARGS);
+ 
+ static int16 getQuadrant(TypeCacheEntry *typcache, RangeType *centroid, RangeType *tst);
+ static int bound_cmp(const void *a, const void *b, void *arg);
+ static bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound lower, RangeBound upper);
+ 
+ 
+ /*
+  * Config SP-GiST interface function.
+  */
+ Datum
+ spg_range_quad_config(PG_FUNCTION_ARGS)
+ {
+ 	/* spgConfigIn *cfgin = (spgConfigIn *) PG_GETARG_POINTER(0); */
+ 	spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1);
+ 
+ 	cfg->prefixType = ANYRANGEOID;
+ 	cfg->labelType = VOIDOID;	/* we don't need node labels */
+ 	cfg->canReturnData = true;
+ 	cfg->longValuesOK = false;
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Determine which quadrant a 2d-mapped range falls into, relative to the
+  * centroid. Lower bound of range assumed to be the horizontal axis. Upper
+  * bound of range assumed to be the vertical axis.
+  *
+  * Quadrants are identified like this:
+  *
+  *	 4	|  1
+  *	----+-----
+  *	 3	|  2
+  *
+  * Ranges on one of the axes are taken to lie in the quadrant with higher value
+  * along perpendicular axis. Range equal to centroid is taken to lie in the
+  * quadrant 1. Empty ranges are taken to lie in the quadrant 5.
+  */
+ static int16
+ getQuadrant(TypeCacheEntry *typcache, RangeType *centroid, RangeType *tst)
+ {
+ 	RangeBound centroidLower, centroidUpper, lower, upper;
+ 	bool centroidEmpty, empty;
+ 	
+ 	range_deserialize(typcache, centroid, &centroidLower, &centroidUpper, 
+ 																&centroidEmpty);
+ 	range_deserialize(typcache, tst, &lower, &upper, &empty);
+ 	
+ 	if (empty)
+ 		return 5;
+ 	
+ 	if (range_cmp_bounds(typcache, &lower, &centroidLower) >= 0)
+ 	{
+ 		if (range_cmp_bounds(typcache, &upper, &centroidUpper) >= 0)
+ 			return 1;
+ 		else
+ 			return 2;		
+ 	}
+ 	else
+ 	{
+ 		if (range_cmp_bounds(typcache, &upper, &centroidUpper) >= 0)
+ 			return 4;
+ 		else
+ 			return 3;		
+ 	}
+ 
+ 	elog(ERROR, "getQuadrant: impossible case");
+ 	return 0;
+ }
+ 
+ 
+ /*
+  * Choose SP-GiST function: choose path for addition of new range.
+  */
+ Datum
+ spg_range_quad_choose(PG_FUNCTION_ARGS)
+ {
+ 	spgChooseIn	   *in = (spgChooseIn *) PG_GETARG_POINTER(0);
+ 	spgChooseOut   *out = (spgChooseOut *) PG_GETARG_POINTER(1);
+ 	RangeType	   *inRange = DatumGetRangeType(in->datum), *centroid;
+ 	int16			quadrant;
+ 	TypeCacheEntry *typcache;
+ 
+ 	if (in->allTheSame)
+ 	{
+ 		out->resultType = spgMatchNode;
+ 		/* nodeN will be set by core */
+ 		out->result.matchNode.levelAdd = 0;
+ 		out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(inRange));
+ 
+ 	/*
+ 	 * Absence of prefix datum divides ranges by empty sign. All empty ranges
+ 	 * are taken into node 0, all non-empty ranges are taken into node 1.
+ 	 */
+ 	if (!in->hasPrefix)
+ 	{
+ 		out->resultType = spgMatchNode;
+ 		if (RangeIsEmpty(inRange))
+ 			out->result.matchNode.nodeN = 0;
+ 		else
+ 			out->result.matchNode.nodeN = 1;
+ 		out->result.matchNode.levelAdd = 1;
+ 		out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	centroid = DatumGetRangeType(in->prefixDatum);
+ 	quadrant = getQuadrant(typcache, centroid, inRange);
+ 
+ 	Assert(quadrant <= in->nNodes);
+ 
+ 	/* Select node matching to quadrant number */
+ 	out->resultType = spgMatchNode;
+ 	out->result.matchNode.nodeN = quadrant - 1;
+ 	out->result.matchNode.levelAdd = 1;
+ 	out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Bound comparison for sorting.
+  */
+ static int
+ bound_cmp(const void *a, const void *b, void *arg)
+ {
+ 	RangeBound *ba = (RangeBound *) a;
+ 	RangeBound *bb = (RangeBound *) b;
+ 	TypeCacheEntry *typcache = (TypeCacheEntry *)arg;
+ 
+ 	return range_cmp_bounds(typcache, ba, bb);
+ }
+ 
+ /*
+  * Picksplit SP-GiST function: split ranges into nodes. Select "centroid"
+  * range and distribute ranges according to quadrants.
+  */
+ Datum
+ spg_range_quad_picksplit(PG_FUNCTION_ARGS)
+ {
+ 	spgPickSplitIn  *in = (spgPickSplitIn *) PG_GETARG_POINTER(0);
+ 	spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1);
+ 	int				 i, j, nonEmptyCount;
+ 	RangeType		*centroid;
+ 	bool			 empty;
+ 	TypeCacheEntry  *typcache;
+ 
+ 	/* Use the median values of lower and upper bounds as the centroid range */
+ 	RangeBound *lowerBounds, *upperBounds;
+ 
+ 	typcache = range_get_typcache(fcinfo, 
+ 							RangeTypeGetOid(DatumGetRangeType(in->datums[0])));
+ 
+ 	/* Allocate memory for bounds */
+ 	lowerBounds = palloc(sizeof(RangeBound) * in->nTuples);
+ 	upperBounds = palloc(sizeof(RangeBound) * in->nTuples);
+ 	j = 0;
+ 	
+ 	/* Deserialize bounds of ranges, count non-empty ranges */
+ 	for (i = 0; i < in->nTuples; i++)
+ 	{
+ 		range_deserialize(typcache, DatumGetRangeType(in->datums[i]),
+ 									&lowerBounds[j], &upperBounds[j], &empty);
+ 		if (!empty)
+ 			j++;
+ 	}
+ 	nonEmptyCount = j;
+ 	
+ 	/*
+ 	 * All the ranges are empty. We've nothing better than put all the ranges
+ 	 * into node 0. Non-empty range will be routed to node 1.
+ 	 */
+ 	if (nonEmptyCount == 0)
+ 	{
+ 		out->nNodes = 2;
+ 		out->hasPrefix = false;
+ 		/* Prefix is empty */
+ 		out->prefixDatum = PointerGetDatum(NULL);
+ 		out->nodeLabels = NULL;
+ 		
+ 		out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+ 		out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+ 		
+ 		/* Place all ranges into node 0 */
+ 		for (i = 0; i < in->nTuples; i++)
+ 		{
+ 			RangeType	   *range = DatumGetRangeType(in->datums[i]);
+ 
+ 			out->leafTupleDatums[i] = RangeTypeGetDatum(range);
+ 			out->mapTuplesToNodes[i] = 0;
+ 		}		
+ 		PG_RETURN_VOID();
+ 	}
+ 
+ 	/* Sort range bounds in order to find medians */
+ 	qsort_arg(lowerBounds, nonEmptyCount, sizeof(RangeBound), 
+ 														bound_cmp, typcache);
+ 	qsort_arg(upperBounds, nonEmptyCount, sizeof(RangeBound), 
+ 														bound_cmp, typcache);
+ 
+ 	/* Construct "centroid" range from medians of lower and upper bounds */
+ 	centroid = range_serialize(typcache, &lowerBounds[nonEmptyCount / 2],
+ 		&upperBounds[nonEmptyCount / 2], false);
+ 	
+ 	
+ 	out->hasPrefix = true;
+ 	out->prefixDatum = RangeTypeGetDatum(centroid);
+ 	
+ 	/* Create node for empty ranges only if it is a root node */
+ 	out->nNodes = (in->level == 0) ? 5 : 4;
+ 	out->nodeLabels = NULL;		/* we don't need node labels */
+ 
+ 	out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+ 	out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+ 
+ 	/* 
+ 	 * Add ranges to corresponding nodes according to quadrants relative to
+ 	 * "centroid" range.
+ 	 */
+ 	for (i = 0; i < in->nTuples; i++)
+ 	{
+ 		RangeType	   *range = DatumGetRangeType(in->datums[i]);
+ 		int16			quadrant = getQuadrant(typcache, centroid, range);
+ 
+ 		out->leafTupleDatums[i] = RangeTypeGetDatum(range);
+ 		out->mapTuplesToNodes[i] = quadrant - 1;
+ 	}
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Check if two bounds are "adjacent", i.e. there are no values which satisfy
+  * both bounds and there are no values between the bounds.
+  */
+ static bool
+ bounds_adjacent(TypeCacheEntry *typcache, RangeBound lower, RangeBound upper)
+ {
+ 	int cmp = range_cmp_bound_values(typcache, &upper, &lower);
+ 	if (cmp < 0)
+ 	{
+ 		RangeType *r;
+ 		/* in a continuous subtype, there are assumed to be points between */
+ 		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
+ 			return false;
+ 		/* flip the inclusion flags */
+ 		upper.inclusive = !upper.inclusive;
+ 		lower.inclusive = !lower.inclusive;
+ 		/* change upper/lower labels to avoid Assert failures */
+ 		upper.lower = true;
+ 		lower.lower = false;
+ 		r = make_range(typcache, &upper, &lower, false);
+ 		PG_RETURN_BOOL(RangeIsEmpty(r));
+ 	}
+ 	else if (cmp == 0)
+ 	{
+ 		PG_RETURN_BOOL(upper.inclusive != lower.inclusive);
+ 	}
+ 	else
+ 	{
+ 		PG_RETURN_BOOL(false);
+ 	}
+ }
+ 
+ /*
+  * Inner consisted SP-GiST function: check which nodes are consistent with
+  * given set of queries.
+  */
+ Datum
+ spg_range_quad_inner_consistent(PG_FUNCTION_ARGS)
+ {
+ 	spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
+ 	spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
+ 	int			which;
+ 	int			i;
+ 	bool		needPrevious = false;
+ 
+ 	if (in->allTheSame)
+ 	{
+ 		/* Report that all nodes should be visited */
+ 		out->nNodes = in->nNodes;
+ 		out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
+ 		for (i = 0; i < in->nNodes; i++)
+ 			out->nodeNumbers[i] = i;
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	if (!in->hasPrefix)
+ 	{
+ 		/*
+ 		 * Empty "centroid". We can use only information about emptiness of
+ 		 * ranges in nodes.
+ 		 */
+ 		Assert(in->nNodes == 2);
+ 		
+ 		/* 
+ 		 * Nth bit of which variable means that (N - 1)th node should be
+ 		 * visited. Initially all bits are set. Bits of nodes which should be
+ 		 * skipped will be unset.
+ 		 */
+ 		which = (1 << 1) | (1 << 2);
+ 		for (i = 0; i < in->nkeys; i++)
+ 		{
+ 			StrategyNumber strategy;
+ 			bool empty;
+ 			
+ 			strategy = in->scankeys[i].sk_strategy;
+ 			
+ 			/*
+ 			 * The only strategy when second argument of operator is not
+ 			 * range is RANGESTRAT_CONTAINS_ELEM.
+ 			 */
+ 			if (strategy != RANGESTRAT_CONTAINS_ELEM)
+ 				empty = RangeIsEmpty(
+ 								DatumGetRangeType(in->scankeys[i].sk_argument));
+ 			
+ 			switch (strategy)
+ 			{
+ 				/* These strategies return false if any argument is empty */
+ 				case RANGESTRAT_BEFORE:
+ 				case RANGESTRAT_OVERLEFT:
+ 				case RANGESTRAT_OVERLAPS:
+ 				case RANGESTRAT_OVERRIGHT:
+ 				case RANGESTRAT_AFTER:
+ 				case RANGESTRAT_ADJACENT:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 						which &= (1 << 2);
+ 					break;
+ 				/* 
+ 				 * "Empty" range is contained in any range. Non-empty ranges
+ 				 * can be contained in only non-empty ranges.
+ 				 */
+ 				case RANGESTRAT_CONTAINS:
+ 					if (!empty)
+ 						which &= (1 << 2);
+ 					break;
+ 				case RANGESTRAT_CONTAINED_BY:
+ 					if (empty)
+ 						which &= (1 << 1);
+ 					break;
+ 				/* Empty range can't contain any element */
+ 				case RANGESTRAT_CONTAINS_ELEM:
+ 					which &= (1 << 2);
+ 					break;
+ 				case RANGESTRAT_EQ:
+ 					if (empty)
+ 						which &= (1 << 1);
+ 					else
+ 						which &= (1 << 2);
+ 					break;
+ 				default:
+ 					elog(ERROR, "unrecognized range strategy: %d", strategy);
+ 					break;
+ 			}
+ 			if (which == 0)
+ 				break;			/* no need to consider remaining conditions */
+ 		}
+ 	}
+ 	else
+ 	{
+ 		RangeBound		centroidLower, centroidUpper;
+ 		bool			centroidEmpty;
+ 		TypeCacheEntry *typcache;
+ 		RangeType	   *centroid;
+ 
+ 		/* Prefix is not null, get information about it. */
+ 		centroid = DatumGetRangeType(in->prefixDatum);
+ 		typcache = range_get_typcache(fcinfo,
+ 								RangeTypeGetOid(DatumGetRangeType(centroid)));
+ 		range_deserialize(typcache, centroid, &centroidLower, &centroidUpper, 
+ 			&centroidEmpty);
+ 		
+ 		Assert(in->nNodes == 4 || in->nNodes == 5);
+ 		
+ 		/* 
+ 		 * Nth bit of which variable means that (N - 1)th node (Nth quadrant)
+ 		 * should be visited. Initially all bits are set. Bits of nodes which
+ 		 * should be skipped will be unset.
+ 		 */
+ 		which = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5);
+ 
+ 		for (i = 0; i < in->nkeys; i++)
+ 		{
+ 			StrategyNumber strategy;
+ 			RangeBound	lower, upper;
+ 			bool		empty;
+ 			RangeType  *range = NULL;
+ 
+ 			strategy = in->scankeys[i].sk_strategy;
+ 			
+ 			/*
+ 			 * Deserialize range if argument is range. The only strategy when
+ 			 * second argument of operator is not range is
+ 			 * RANGESTRAT_CONTAINS_ELEM.
+ 			 */
+ 			if (strategy != RANGESTRAT_CONTAINS_ELEM)
+ 			{
+ 				range = DatumGetRangeType(in->scankeys[i].sk_argument);
+ 				range_deserialize(typcache, range, &lower, &upper, &empty);
+ 			}
+ 			
+ 			switch (strategy)
+ 			{
+ 				RangeBound	prevLower, prevUpper;
+ 				bool		prevEmpty, prevPresent;
+ 				RangeType  *prevCentroid;
+ 				int			cmp1, cmp2, cmp3, which1, which2;
+ 				
+ 				/*
+ 				 * Range A is before range B if upper bound of A is lower than
+ 				 * lower bound of B. If upper bound of "centroid" is greater
+ 				 * or equal to lower bound of argument then no ranges before
+ 				 * argument can be contained in quadrants 2 and 4.
+ 				 */
+ 				case RANGESTRAT_BEFORE:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidUpper,
+ 																&lower) >= 0)
+ 						which &= (1 << 2) | (1 << 3);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Range A is overleft to range B if upper bound of A is lower
+ 				 * or equal to lower bound of B. If upper bound of "centroid" is
+ 				 * greater to upper bound of argument then no ranges overleft
+ 				 * argument can be contained in quadrants 1 and 4.
+ 				 */
+ 				case RANGESTRAT_OVERLEFT:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidUpper,
+ 																	&upper) > 0)
+ 						which = (1 << 2) | (1 << 3);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Non-empty ranges overlaps if lower bound of each range is
+ 				 * lower or equal to upper bound of another ranges.
+ 				 */
+ 				case RANGESTRAT_OVERLAPS:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 					{
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 						
+ 						/*
+ 						 * If lower bound of centroid is greater than upper
+ 						 * bound of argument then no overlapping ranges can be
+ 						 * in 1 and 2 quadrants.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidLower,
+ 																	&upper) > 0)
+ 							which &= (1 << 3) | (1 << 4);
+ 						
+ 						/*
+ 						 * If upper bound of centroid is lower or equal than
+ 						 * lower bound of argument then no overlapping ranges
+ 						 * can be in 2 and 3 quadrants.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidUpper,
+ 																   &lower) <= 0)
+ 							which &= (1 << 1) | (1 << 4);
+ 					}
+ 					break;
+ 				/*
+ 				 * Range A is overright to range B if lower bound of A is upper
+ 				 * or equal to upper bound of B. If lower bound of "centroid" is
+ 				 * lower or equal to lower bound of argument then no ranges
+ 				 * overright argument can be contained in quadrants 3 and 4.
+ 				 */
+ 				case RANGESTRAT_OVERRIGHT:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidLower, &lower) <= 0)
+ 						which = (1 << 1) | (1 << 2);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Range A is after range B if lower bound of A is greater than
+ 				 * upper bound of B. If lower bound of "centroid" is lower
+ 				 * or equal to upper bound of argument then no ranges after
+ 				 * argument can be contained in quadrants 3 and 4.
+ 				 */
+ 				case RANGESTRAT_AFTER:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidLower, &upper) <= 0)
+ 						which &= (1 << 1) | (1 << 2);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Ranges are adjacent if lower bound of one range is adjacent
+ 				 * to upper bound of another range.
+ 				 */
+ 				case RANGESTRAT_ADJACENT:
+ 					/*
+ 					 * which1 is bitmask for possibility to be adjacent with
+ 					 * lower bound of argument. which2 is bitmask for
+ 					 * possibility to be adjacent with upper bound of
+ 					 * argument. 
+ 					 */
+ 					which1 = which2 = (1 >> 1) | (1 >> 2) | (1 >> 3) | (1 >> 4);
+ 
+ 					/* Deserialize previous centroid range if present. */
+ 					prevPresent = (in->reconstructedValue != (Datum) 0);
+ 					if (prevPresent)
+ 					{
+ 						prevCentroid = DatumGetRangeType(in->reconstructedValue);
+ 						range_deserialize(typcache, prevCentroid, &prevLower,
+ 							&prevUpper, &prevEmpty);
+ 					}
+ 					
+ 					cmp2 = range_cmp_bounds(typcache, &upper, &centroidLower);
+ 					if (prevPresent)
+ 					{
+ 						/* Do comparison with previous centroid */
+ 						cmp1 = range_cmp_bounds(typcache, &upper, &prevLower);
+ 						cmp3 = range_cmp_bounds(typcache, &centroidLower,
+ 																	&prevLower);
+ 						
+ 						/* 
+ 						 * Check if lower bound of argument is not in
+ 						 * a quadrant we visit in previous step.
+ 						 */
+ 						if ((cmp3 < 0 && cmp1 > 0) || (cmp3 > 0 && cmp1 < 0))
+ 							which1 = 0;
+ 					}
+ 
+ 					if (cmp2 >= 0)
+ 						which1 &= (1 >> 1) | (1 >> 2);
+ 					else if (!bounds_adjacent(typcache, centroidLower, upper))
+ 						which1 &= (1 >> 3) | (1 >> 4);
+ 
+ 					cmp2 = range_cmp_bounds(typcache, &lower, &centroidUpper);
+ 					if (prevPresent)
+ 					{
+ 						/* Do comparison with previous centroid */
+ 						cmp1 = range_cmp_bounds(typcache, &lower, &prevUpper);
+ 						cmp3 = range_cmp_bounds(typcache, &centroidUpper, &prevUpper);
+ 						/* 
+ 						 * Check if upper bound of argument is not in
+ 						 * a quadrant we visit in previous step.
+ 						 */
+ 						if ((cmp3 < 0 && cmp1 > 0) || (cmp3 > 0 && cmp1 < 0))
+ 							which2 = 0;
+ 					}
+ 
+ 					if (cmp2 > 0)
+ 						which2 &= (1 >> 1) | (1 >> 4);
+ 					else if (cmp2 < 0)
+ 						which2 &= (1 >> 2) | (1 >> 3);
+ 
+ 					which &= which1 | which2;
+ 					
+ 					needPrevious = true;
+ 					break;
+ 				/*
+ 				 * Non-empty range A contains non-empty range B if lower bound
+ 				 * of A is lower or equal to lower bound of range B and upper
+ 				 * bound of range A is greater or equal to upper bound of range
+ 				 * A.
+ 				 */
+ 				case RANGESTRAT_CONTAINS:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 					{
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 						/* 
+ 						 * If lower bound of centroid is greater than lower
+ 						 * bound of argument then no ranges which contain
+ 						 * argument can be in quadrants 1 and 2.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidLower,
+ 																	&lower) > 0)
+ 							which &= (1 << 3) | (1 << 4);
+ 						/* 
+ 						 * If upper bound of centroid is lower or equal to upper
+ 						 * bound of argument then no ranges which contain
+ 						 * argument can be in quadrants 2 and 3.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidUpper,
+ 																   &upper) <= 0)
+ 							which &= (1 << 1) | (1 << 4);
+ 					}
+ 					break;
+ 				case RANGESTRAT_CONTAINED_BY:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 					{
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 						/* 
+ 						 * If lower bound of centroid is lower or equal to lower
+ 						 * bound of argument then no ranges which are contained
+ 						 * in argument can be in quadrants 3 and 4.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidLower,
+ 																   &lower) <= 0)
+ 							which &= (1 << 1) | (1 << 2);
+ 						/* 
+ 						 * If upper bound of centroid is greater than upper
+ 						 * bound of argument then no ranges which are contained
+ 						 * in argument can be in quadrants 1 and 4.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidUpper,
+ 																	&upper) > 0)
+ 							which &= (1 << 2) | (1 << 3);
+ 					}
+ 					break;
+ 				case RANGESTRAT_CONTAINS_ELEM:
+ 					/* 
+ 					 * Construct bound to pass then to bound comparison
+ 					 * functions
+ 					 */
+ 					lower.inclusive = true;
+ 					lower.infinite = false;
+ 					lower.lower = true;
+ 					lower.val = in->scankeys[i].sk_argument;
+ 					
+ 					upper.inclusive = true;
+ 					upper.infinite = false;
+ 					upper.lower = false;
+ 					upper.val = in->scankeys[i].sk_argument;
+ 					
+ 					which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					
+ 					/*
+ 					 * If lower bound of centroid is greater than lower bound of
+ 					 * argument then ranges containing element can't be in 1 and 2
+ 					 * quadrants.
+ 					 */
+ 					if (range_cmp_bound_values(typcache, &centroidLower,
+ 																	&lower) > 0)
+ 							which &= (1 << 3) | (1 << 4);
+ 					
+ 					/*
+ 					 * If upper bound of centroid is lower or equal than upper
+ 					 * bound of argument then ranges containing element can't be
+ 					 * in 2 and 3 quadrants.
+ 					 */
+ 					if (range_cmp_bound_values(typcache, &centroidUpper,
+ 																   &upper) <= 0)
+ 							which &= (1 << 1) | (1 << 4);				
+ 					
+ 					break;
+ 				/*
+ 				 * Equal range can be only in the same quadrant where argument
+ 				 * would be placed to.
+ 				 */
+ 				case RANGESTRAT_EQ:
+ 					which &= (1 << getQuadrant(typcache, centroid, range));
+ 					break;
+ 				default:
+ 					elog(ERROR, "unrecognized range strategy: %d", strategy);
+ 					break;
+ 			}
+ 
+ 			if (which == 0)
+ 				break;			/* no need to consider remaining conditions */
+ 		}
+ 	}
+ 
+ 	/* We must descend into the quadrant(s) identified by which */
+ 	out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
+ 	if (needPrevious)
+ 		out->reconstructedValues = (Datum *) palloc(sizeof(Datum) * in->nNodes);
+ 	out->nNodes = 0;
+ 	for (i = 1; i <= in->nNodes; i++)
+ 	{
+ 		if (which & (1 << i))
+ 		{
+ 			/* Save previous prefix if needed */
+ 			if (needPrevious)
+ 				out->reconstructedValues[out->nNodes] = in->prefixDatum;
+ 			out->nodeNumbers[out->nNodes++] = i - 1;
+ 		}
+ 	}
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Leaf consistent SP-GiST function: check leaf value against query using
+  * corresponding function.
+  */
+ Datum
+ spg_range_quad_leaf_consistent(PG_FUNCTION_ARGS)
+ {
+ 	spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0);
+ 	spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1);
+ 	bool		res;
+ 	int			i;
+ 	TypeCacheEntry *typcache;
+ 
+ 	/* all tests are exact */
+ 	out->recheck = false;
+ 
+ 	/* leafDatum is what it is... */
+ 	out->leafValue = in->leafDatum;
+ 
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(
+ 											DatumGetRangeType(in->leafDatum)));
+ 	
+ 	/* Perform the required comparison(s) */
+ 	res = true;
+ 	for (i = 0; i < in->nkeys; i++)
+ 	{
+ 		/* Find the function which is corresponding to the scan strategy */
+ 		switch (in->scankeys[i].sk_strategy)
+ 		{
+ 			case RANGESTRAT_BEFORE:
+ 				res = range_before_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_OVERLEFT:
+ 				res = range_overleft_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_OVERLAPS:
+ 				res = range_overlaps_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_OVERRIGHT:
+ 				res = range_overright_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_AFTER:
+ 				res = range_after_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_ADJACENT:
+ 				res = range_adjacent_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_CONTAINS:
+ 				res = range_contains_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_CONTAINED_BY:
+ 				res = range_contained_by_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_CONTAINS_ELEM:
+ 				res = range_contains_elem_internal(typcache, 
+ 											DatumGetRangeType(in->leafDatum), 
+ 												in->scankeys[i].sk_argument);
+ 				break;
+ 			case RANGESTRAT_EQ:
+ 				res = range_eq_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 								DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			default:
+ 				elog(ERROR, "unrecognized range strategy: %d", 
+ 												in->scankeys[i].sk_strategy);
+ 				res = false;
+ 				break;
+ 		}
+ 		
+ 		/* If leaf datum don't match to one query, we can don't check another */
+ 		if (!res)
+ 			break;
+ 	}
+ 
+ 	PG_RETURN_BOOL(res);
+ }
*** a/src/include/catalog/pg_amop.h
--- b/src/include/catalog/pg_amop.h
***************
*** 767,770 **** DATA(insert (	4017   25 25 12 s	665 4000 0 ));
--- 767,784 ----
  DATA(insert (	4017   25 25 14 s	667 4000 0 ));
  DATA(insert (	4017   25 25 15 s	666 4000 0 ));
  
+ /*
+  * SP-GiST range_ops
+  */
+ DATA(insert (	3474   3831 3831 1 s	3893 4000 0 ));
+ DATA(insert (	3474   3831 3831 2 s	3895 4000 0 ));
+ DATA(insert (	3474   3831 3831 3 s	3888 4000 0 ));
+ DATA(insert (	3474   3831 3831 4 s	3896 4000 0 ));
+ DATA(insert (	3474   3831 3831 5 s	3894 4000 0 ));
+ DATA(insert (	3474   3831 3831 6 s	3897 4000 0 ));
+ DATA(insert (	3474   3831 3831 7 s	3890 4000 0 ));
+ DATA(insert (	3474   3831 3831 8 s	3892 4000 0 ));
+ DATA(insert (	3474   3831 2283 16 s	3889 4000 0 ));
+ DATA(insert (	3474   3831 3831 18 s	3882 4000 0 ));
+ 
  #endif   /* PG_AMOP_H */
*** a/src/include/catalog/pg_amproc.h
--- b/src/include/catalog/pg_amproc.h
***************
*** 373,377 **** DATA(insert (	4017   25 25 2 4028 ));
--- 373,382 ----
  DATA(insert (	4017   25 25 3 4029 ));
  DATA(insert (	4017   25 25 4 4030 ));
  DATA(insert (	4017   25 25 5 4031 ));
+ DATA(insert (	3474   3831 3831 1 3469 ));
+ DATA(insert (	3474   3831 3831 2 3470 ));
+ DATA(insert (	3474   3831 3831 3 3471 ));
+ DATA(insert (	3474   3831 3831 4 3472 ));
+ DATA(insert (	3474   3831 3831 5 3473 ));
  
  #endif   /* PG_AMPROC_H */
*** a/src/include/catalog/pg_opclass.h
--- b/src/include/catalog/pg_opclass.h
***************
*** 223,228 **** DATA(insert (	783		tsquery_ops			PGNSP PGUID 3702  3615 t 20 ));
--- 223,229 ----
  DATA(insert (	403		range_ops			PGNSP PGUID 3901  3831 t 0 ));
  DATA(insert (	405		range_ops			PGNSP PGUID 3903  3831 t 0 ));
  DATA(insert (	783		range_ops			PGNSP PGUID 3919  3831 t 0 ));
+ DATA(insert (	4000	range_ops			PGNSP PGUID 3474  3831 t 0 ));
  DATA(insert (	4000	quad_point_ops		PGNSP PGUID 4015  600 t 0 ));
  DATA(insert (	4000	kd_point_ops		PGNSP PGUID 4016  600 f 0 ));
  DATA(insert (	4000	text_ops			PGNSP PGUID 4017  25 t 0 ));
*** a/src/include/catalog/pg_opfamily.h
--- b/src/include/catalog/pg_opfamily.h
***************
*** 142,147 **** DATA(insert OID = 3702 (	783		tsquery_ops		PGNSP PGUID ));
--- 142,148 ----
  DATA(insert OID = 3901 (	403		range_ops		PGNSP PGUID ));
  DATA(insert OID = 3903 (	405		range_ops		PGNSP PGUID ));
  DATA(insert OID = 3919 (	783		range_ops		PGNSP PGUID ));
+ DATA(insert OID = 3474 (	4000	range_ops		PGNSP PGUID ));
  DATA(insert OID = 4015 (	4000	quad_point_ops	PGNSP PGUID ));
  DATA(insert OID = 4016 (	4000	kd_point_ops	PGNSP PGUID ));
  DATA(insert OID = 4017 (	4000	text_ops		PGNSP PGUID ));
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4649,4654 **** DESCR("SP-GiST support for suffix tree over text");
--- 4649,4665 ----
  DATA(insert OID = 4031 (  spg_text_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_text_leaf_consistent _null_ _null_ _null_ ));
  DESCR("SP-GiST support for suffix tree over text");
  
+ DATA(insert OID = 3469 (  spg_range_quad_config	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_config _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3470 (  spg_range_quad_choose	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_choose _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3471 (  spg_range_quad_picksplit	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_picksplit _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3472 (  spg_range_quad_inner_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_inner_consistent _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3473 (  spg_range_quad_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_leaf_consistent _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ 
  
  /*
   * Symbolic values for provolatile column: these indicate whether the result
*** a/src/include/utils/rangetypes.h
--- b/src/include/utils/rangetypes.h
***************
*** 75,80 **** typedef struct
--- 75,93 ----
  #define PG_GETARG_RANGE_COPY(n)		DatumGetRangeTypeCopy(PG_GETARG_DATUM(n))
  #define PG_RETURN_RANGE(x)			return RangeTypeGetDatum(x)
  
+ /* Operator strategy numbers used in the GiST range opclass */
+ /* Numbers are chosen to match up operator names with existing usages */
+ #define RANGESTRAT_BEFORE				1
+ #define RANGESTRAT_OVERLEFT				2
+ #define RANGESTRAT_OVERLAPS				3
+ #define RANGESTRAT_OVERRIGHT			4
+ #define RANGESTRAT_AFTER				5
+ #define RANGESTRAT_ADJACENT				6
+ #define RANGESTRAT_CONTAINS				7
+ #define RANGESTRAT_CONTAINED_BY			8
+ #define RANGESTRAT_CONTAINS_ELEM		16
+ #define RANGESTRAT_EQ					18
+ 
  /*
   * prototypes for functions defined in rangetypes.c
   */
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
***************
*** 1068,1079 **** ORDER BY 1, 2, 3;
--- 1068,1084 ----
         2742 |            4 | =
         4000 |            1 | <<
         4000 |            1 | ~<~
+        4000 |            2 | &<
         4000 |            2 | ~<=~
+        4000 |            3 | &&
         4000 |            3 | =
+        4000 |            4 | &>
         4000 |            4 | ~>=~
         4000 |            5 | >>
         4000 |            5 | ~>~
+        4000 |            6 | -|-
         4000 |            6 | ~=
+        4000 |            7 | @>
         4000 |            8 | <@
         4000 |           10 | <^
         4000 |           11 | <
***************
*** 1081,1087 **** ORDER BY 1, 2, 3;
         4000 |           12 | <=
         4000 |           14 | >=
         4000 |           15 | >
! (55 rows)
  
  -- Check that all opclass search operators have selectivity estimators.
  -- This is not absolutely required, but it seems a reasonable thing
--- 1086,1094 ----
         4000 |           12 | <=
         4000 |           14 | >=
         4000 |           15 | >
!        4000 |           16 | @>
!        4000 |           18 | =
! (62 rows)
  
  -- Check that all opclass search operators have selectivity estimators.
  -- This is not absolutely required, but it seems a reasonable thing
*** a/src/test/regress/expected/rangetypes.out
--- b/src/test/regress/expected/rangetypes.out
***************
*** 821,826 **** select count(*) from test_range_gist where ir -|- int4range(100,500);
--- 821,1045 ----
       5
  (1 row)
  
+ -- test SP-GiST index that's been built incrementally
+ create table test_range_spgist(ir int4range);
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(g, g+10000) from generate_series(1,1000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(NULL,g*10,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g*10,NULL,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ -- first, verify non-indexed results
+ SET enable_seqscan    = t;
+ SET enable_indexscan  = f;
+ SET enable_bitmapscan = f;
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+  count 
+ -------
+   6200
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> 10;
+  count 
+ -------
+    130
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+  count 
+ -------
+    111
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+  count 
+ -------
+    158
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+  count 
+ -------
+   1062
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+  count 
+ -------
+    189
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+  count 
+ -------
+   3554
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+  count 
+ -------
+   1029
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+  count 
+ -------
+   4794
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+  count 
+ -------
+      5
+ (1 row)
+ 
+ -- now check same queries using index
+ SET enable_seqscan    = f;
+ SET enable_indexscan  = t;
+ SET enable_bitmapscan = f;
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+  count 
+ -------
+      0
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> 10;
+  count 
+ -------
+    130
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+  count 
+ -------
+    111
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+  count 
+ -------
+    158
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+  count 
+ -------
+     62
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+  count 
+ -------
+    189
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+  count 
+ -------
+   3554
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+  count 
+ -------
+   1029
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+  count 
+ -------
+   4794
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+  count 
+ -------
+      0
+ (1 row)
+ 
+ -- now check same queries using a bulk-loaded index
+ drop index test_range_spgist_idx;
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+  count 
+ -------
+      0
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> 10;
+  count 
+ -------
+    130
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+  count 
+ -------
+    111
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+  count 
+ -------
+    158
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+  count 
+ -------
+     62
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+  count 
+ -------
+    189
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+  count 
+ -------
+   3554
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+  count 
+ -------
+   1029
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+  count 
+ -------
+   4794
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+  count 
+ -------
+      0
+ (1 row)
+ 
  RESET enable_seqscan;
  RESET enable_indexscan;
  RESET enable_bitmapscan;
*** a/src/test/regress/expected/sanity_check.out
--- b/src/test/regress/expected/sanity_check.out
***************
*** 156,161 **** SELECT relname, relhasindex
--- 156,162 ----
   tenk2                   | t
   test_range_excl         | t
   test_range_gist         | t
+  test_range_spgist       | t
   test_tsvector           | f
   text_tbl                | f
   time_tbl                | f
***************
*** 164,170 **** SELECT relname, relhasindex
   timetz_tbl              | f
   tinterval_tbl           | f
   varchar_tbl             | f
! (153 rows)
  
  --
  -- another sanity check: every system catalog that has OIDs should have
--- 165,171 ----
   timetz_tbl              | f
   tinterval_tbl           | f
   varchar_tbl             | f
! (154 rows)
  
  --
  -- another sanity check: every system catalog that has OIDs should have
*** a/src/test/regress/output/misc.source
--- b/src/test/regress/output/misc.source
***************
*** 675,680 **** SELECT user_relns() AS user_relns
--- 675,681 ----
   tenk2
   test_range_excl
   test_range_gist
+  test_range_spgist
   test_tsvector
   text_tbl
   time_tbl
***************
*** 685,691 **** SELECT user_relns() AS user_relns
   toyemp
   varchar_tbl
   xacttest
! (107 rows)
  
  SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
   name 
--- 686,692 ----
   toyemp
   varchar_tbl
   xacttest
! (108 rows)
  
  SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
   name 
*** a/src/test/regress/sql/rangetypes.sql
--- b/src/test/regress/sql/rangetypes.sql
***************
*** 220,225 **** select count(*) from test_range_gist where ir &< int4range(100,500);
--- 220,287 ----
  select count(*) from test_range_gist where ir &> int4range(100,500);
  select count(*) from test_range_gist where ir -|- int4range(100,500);
  
+ -- test SP-GiST index that's been built incrementally
+ create table test_range_spgist(ir int4range);
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ 
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(g, g+10000) from generate_series(1,1000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(NULL,g*10,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g*10,NULL,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ 
+ -- first, verify non-indexed results
+ SET enable_seqscan    = t;
+ SET enable_indexscan  = f;
+ SET enable_bitmapscan = f;
+ 
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+ select count(*) from test_range_spgist where ir @> 10;
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ 
+ -- now check same queries using index
+ SET enable_seqscan    = f;
+ SET enable_indexscan  = t;
+ SET enable_bitmapscan = f;
+ 
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+ select count(*) from test_range_spgist where ir @> 10;
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ 
+ -- now check same queries using a bulk-loaded index
+ drop index test_range_spgist_idx;
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ 
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+ select count(*) from test_range_spgist where ir @> 10;
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ 
  RESET enable_seqscan;
  RESET enable_indexscan;
  RESET enable_bitmapscan;
#15Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Alexander Korotkov (#14)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On 13.07.2012 02:00, Alexander Korotkov wrote:

On Thu, Jul 12, 2012 at 10:29 AM, Heikki Linnakangas<
heikki.linnakangas@enterprisedb.com> wrote:

Thanks. Can you do something about TrickFunctionCall2, please? (
http://archives.postgresql.**org/message-id/4FE2C968.**
2010503@enterprisedb.com<http://archives.postgresql.org/message-id/4FE2C968.2010503@enterprisedb.com&gt;)
A separate patch to refactor that in the existing gist opclass would
probably be best.

Done. There are separate patch for get rid of TrickFunctionCall2 and
version of SP-GiST for ranges based on that patch.

Committed the get-rid-of-TrickFunctionCall2 patch. I also changed one
call in range_gist_same(), which was not using TrickFunctionCall2 but
was nevertheless doing the same thing in spirit.

I'll try to take a look at the other patch in the next few days.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#16Alexander Korotkov
aekorotkov@gmail.com
In reply to: Heikki Linnakangas (#15)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Thu, Jul 19, 2012 at 12:22 AM, Heikki Linnakangas <
heikki.linnakangas@enterprisedb.com> wrote:

On 13.07.2012 02:00, Alexander Korotkov wrote:

On Thu, Jul 12, 2012 at 10:29 AM, Heikki Linnakangas<
heikki.linnakangas@**enterprisedb.com<heikki.linnakangas@enterprisedb.com>>
wrote:

Thanks. Can you do something about TrickFunctionCall2, please? (

http://archives.postgresql.****org/message-id/4FE2C968.**
2010503@enterprisedb.com<http:**//archives.postgresql.org/**
message-id/4FE2C968.2010503@**enterprisedb.com<http://archives.postgresql.org/message-id/4FE2C968.2010503@enterprisedb.com&gt;

)

A separate patch to refactor that in the existing gist opclass would
probably be best.

Done. There are separate patch for get rid of TrickFunctionCall2 and
version of SP-GiST for ranges based on that patch.

Committed the get-rid-of-TrickFunctionCall2 patch. I also changed one call
in range_gist_same(), which was not using TrickFunctionCall2 but was
nevertheless doing the same thing in spirit.

Good, thanks!

------
With best regards,
Alexander Korotkov.

#17Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Alexander Korotkov (#14)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On 13.07.2012 02:00, Alexander Korotkov wrote:

Done. There are separate patch for get rid of TrickFunctionCall2 and
version of SP-GiST for ranges based on that patch.

Looking at the SP-GiST patch now..

It would be nice to have an introduction, perhaps as a file comment at
the top of rangetypes_spgist.c, explaining how the quad tree works. I
have a general idea of what a quad tree is, but it's not immediately
obvious how it maps to SP-GiST. What is stored on a leaf node and an
internal node? What is the 'prefix' (seems to be the centroid)? How are
ranges mapped to 2D points? (the function comment of getQuadrant() is a
good start for that last one)

In spg_range_quad_inner_consistent(), if in->hasPrefix == true, ISTM
that in all cases where 'empty' is true, 'which' is set to 0, meaning
that there can be no matches in any of the quadrants. In most of the
case-branches, you explicitly check for 'empty', but even in the ones
where you don't, I think you end up setting which=0 if empty==true. I'm
not 100% sure about the RANGESTRAT_ADJACENT case, though. Am I missing
something?

It would be nice to avoid the code duplication between the new
bounds_adjacent() function, and the range_adjacent_internal(). Perhaps
move bounds_adjacent() to rangetypes.c and use it in
range_adjacent_internal() too?

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#18Alexander Korotkov
aekorotkov@gmail.com
In reply to: Heikki Linnakangas (#17)
1 attachment(s)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Fri, Jul 20, 2012 at 3:48 PM, Heikki Linnakangas <
heikki.linnakangas@enterprisedb.com> wrote:

On 13.07.2012 02:00, Alexander Korotkov wrote:

Done. There are separate patch for get rid of TrickFunctionCall2 and
version of SP-GiST for ranges based on that patch.

Looking at the SP-GiST patch now..

It would be nice to have an introduction, perhaps as a file comment at the
top of rangetypes_spgist.c, explaining how the quad tree works. I have a
general idea of what a quad tree is, but it's not immediately obvious how
it maps to SP-GiST. What is stored on a leaf node and an internal node?
What is the 'prefix' (seems to be the centroid)? How are ranges mapped to
2D points? (the function comment of getQuadrant() is a good start for that
last one)

I've added some comments at the top of rangetypes_spgist.c.

In spg_range_quad_inner_**consistent(), if in->hasPrefix == true, ISTM that

in all cases where 'empty' is true, 'which' is set to 0, meaning that there
can be no matches in any of the quadrants. In most of the case-branches,
you explicitly check for 'empty', but even in the ones where you don't, I
think you end up setting which=0 if empty==true. I'm not 100% sure about
the RANGESTRAT_ADJACENT case, though. Am I missing something?

Ops., it was a bug: RANGESTRAT_ADJACENT shoud set which=0 if empty==true,
while RANGESTRAT_CONTAINS and RANGESTRAT_CONTAINED_BY not. Corrected.

It would be nice to avoid the code duplication between the new

bounds_adjacent() function, and the range_adjacent_internal(). Perhaps move
bounds_adjacent() to rangetypes.c and use it in range_adjacent_internal()
too?

Done.

------
With best regards,
Alexander Korotkov.

Attachments:

range_spgist_quad-0.6.patchapplication/octet-stream; name=range_spgist_quad-0.6.patchDownload
*** a/src/backend/utils/adt/Makefile
--- b/src/backend/utils/adt/Makefile
***************
*** 30,36 **** OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
  	tsvector.o tsvector_op.o tsvector_parser.o \
! 	txid.o uuid.o windowfuncs.o xml.o
  
  like.o: like.c like_match.c
  
--- 30,36 ----
  	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
  	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
  	tsvector.o tsvector_op.o tsvector_parser.o \
! 	txid.o uuid.o windowfuncs.o xml.o rangetypes_spgist.o
  
  like.o: like.c like_match.c
  
*** a/src/backend/utils/adt/rangetypes.c
--- b/src/backend/utils/adt/rangetypes.c
***************
*** 709,714 **** range_after(PG_FUNCTION_ARGS)
--- 709,758 ----
  	PG_RETURN_BOOL(range_after_internal(typcache, r1, r2));
  }
  
+ /*
+  * Check if two bounds A and B are "adjacent", i.e. each subtype value satisfy
+  * to strictly one of those bounds: there are no values which satisfy both
+  * bounds and there are no values between the bounds. For discrete ranges, we
+  * have to rely on the canonicalization function to normalize A..B to empty if
+  * it contains no elements of the subtype.  (If there is no canonicalization
+  * function, it's impossible for such a range to normalize to empty, so we
+  * needn't bother to try.)
+  * 
+  * If A == B, the ranges are adjacent only if these bounds have different
+  * inclusive flags (i.e., exactly one of the ranges includes the common
+  * boundary point).
+  *
+  * And if A > B then the ranges cannot be adjacent in this order.
+  */
+ bool
+ bounds_adjacent(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper)
+ {
+ 	int cmp = range_cmp_bound_values(typcache, upper, lower);
+ 	if (cmp < 0)
+ 	{
+ 		RangeType *r;
+ 		/* in a continuous subtype, there are assumed to be points between */
+ 		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
+ 			return false;
+ 		/* flip the inclusion flags */
+ 		upper->inclusive = !upper->inclusive;
+ 		lower->inclusive = !lower->inclusive;
+ 		/* change upper/lower labels to avoid Assert failures */
+ 		upper->lower = true;
+ 		lower->lower = false;
+ 		r = make_range(typcache, upper, lower, false);
+ 		PG_RETURN_BOOL(RangeIsEmpty(r));
+ 	}
+ 	else if (cmp == 0)
+ 	{
+ 		PG_RETURN_BOOL(upper->inclusive != lower->inclusive);
+ 	}
+ 	else
+ 	{
+ 		PG_RETURN_BOOL(false);
+ 	}
+ }
+ 
  /* adjacent to (but not overlapping)? (internal version) */
  bool
  range_adjacent_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
***************
*** 719,726 **** range_adjacent_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
  				upper2;
  	bool		empty1,
  				empty2;
- 	RangeType  *r3;
- 	int			cmp;
  
  	/* Different types should be prevented by ANYRANGE matching rules */
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
--- 763,768 ----
***************
*** 734,795 **** range_adjacent_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
  		return false;
  
  	/*
! 	 * Given two ranges A..B and C..D, where B < C, the ranges are adjacent if
! 	 * and only if the range B..C is empty, where inclusivity of these two
! 	 * bounds is inverted compared to the original bounds.	For discrete
! 	 * ranges, we have to rely on the canonicalization function to normalize
! 	 * B..C to empty if it contains no elements of the subtype.  (If there is
! 	 * no canonicalization function, it's impossible for such a range to
! 	 * normalize to empty, so we needn't bother to try.)
! 	 *
! 	 * If B == C, the ranges are adjacent only if these bounds have different
! 	 * inclusive flags (i.e., exactly one of the ranges includes the common
! 	 * boundary point).
! 	 *
! 	 * And if B > C then the ranges cannot be adjacent in this order, but we
! 	 * must consider the other order (i.e., check D <= A).
  	 */
! 	cmp = range_cmp_bound_values(typcache, &upper1, &lower2);
! 	if (cmp < 0)
! 	{
! 		/* in a continuous subtype, there are assumed to be points between */
! 		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
! 			return (false);
! 		/* flip the inclusion flags */
! 		upper1.inclusive = !upper1.inclusive;
! 		lower2.inclusive = !lower2.inclusive;
! 		/* change upper/lower labels to avoid Assert failures */
! 		upper1.lower = true;
! 		lower2.lower = false;
! 		r3 = make_range(typcache, &upper1, &lower2, false);
! 		return RangeIsEmpty(r3);
! 	}
! 	if (cmp == 0)
! 	{
! 		return (upper1.inclusive != lower2.inclusive);
! 	}
! 
! 	cmp = range_cmp_bound_values(typcache, &upper2, &lower1);
! 	if (cmp < 0)
! 	{
! 		/* in a continuous subtype, there are assumed to be points between */
! 		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
! 			return (false);
! 		/* flip the inclusion flags */
! 		upper2.inclusive = !upper2.inclusive;
! 		lower1.inclusive = !lower1.inclusive;
! 		/* change upper/lower labels to avoid Assert failures */
! 		upper2.lower = true;
! 		lower1.lower = false;
! 		r3 = make_range(typcache, &upper2, &lower1, false);
! 		return RangeIsEmpty(r3);
! 	}
! 	if (cmp == 0)
! 	{
! 		return (upper2.inclusive != lower1.inclusive);
! 	}
! 
! 	return false;
  }
  
  /* adjacent to (but not overlapping)? */
--- 776,787 ----
  		return false;
  
  	/*
! 	 * Given two ranges A..B and C..D, the ranges are adjacent if and only if
! 	 * the pair of B and C bounds is adjacent or pair of D and A bounds is
! 	 * adjacent.
  	 */
! 	return (bounds_adjacent(typcache, &lower2, &upper1) ||
! 			bounds_adjacent(typcache, &lower1, &upper2));
  }
  
  /* adjacent to (but not overlapping)? */
***************
*** 2220,2226 **** range_contains_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
  	/* Different types should be prevented by ANYRANGE matching rules */
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
! 
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
--- 2212,2218 ----
  	/* Different types should be prevented by ANYRANGE matching rules */
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
  		elog(ERROR, "range types do not match");
! 	
  	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
  	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
  
*** a/src/backend/utils/adt/rangetypes_gist.c
--- b/src/backend/utils/adt/rangetypes_gist.c
***************
*** 20,39 ****
  #include "utils/datum.h"
  #include "utils/rangetypes.h"
  
- 
- /* Operator strategy numbers used in the GiST range opclass */
- /* Numbers are chosen to match up operator names with existing usages */
- #define RANGESTRAT_BEFORE				1
- #define RANGESTRAT_OVERLEFT				2
- #define RANGESTRAT_OVERLAPS				3
- #define RANGESTRAT_OVERRIGHT			4
- #define RANGESTRAT_AFTER				5
- #define RANGESTRAT_ADJACENT				6
- #define RANGESTRAT_CONTAINS				7
- #define RANGESTRAT_CONTAINED_BY			8
- #define RANGESTRAT_CONTAINS_ELEM		16
- #define RANGESTRAT_EQ					18
- 
  /*
   * Range class properties used to segregate different classes of ranges in
   * GiST.  Each unique combination of properties is a class.  CLS_EMPTY cannot
--- 20,25 ----
***************
*** 197,203 **** range_gist_consistent(PG_FUNCTION_ARGS)
  	*recheck = false;
  
  	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(key));
! 
  	if (GIST_LEAF(entry))
  		PG_RETURN_BOOL(range_gist_consistent_leaf(typcache, strategy,
  												  key, query));
--- 183,189 ----
  	*recheck = false;
  
  	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(key));
! 	
  	if (GIST_LEAF(entry))
  		PG_RETURN_BOOL(range_gist_consistent_leaf(typcache, strategy,
  												  key, query));
*** /dev/null
--- b/src/backend/utils/adt/rangetypes_spgist.c
***************
*** 0 ****
--- 1,808 ----
+ /*-------------------------------------------------------------------------
+  *
+  * rangetypes_spgist.c
+  *	  implementation of quad tree over ranges mapped to 2d-points for SP-GiST.
+  *
+  * Ranges are mapped into 2d-points as following. Lower bound of range assumed
+  * to be the horizontal axis. Upper bound of range assumed to be the vertical
+  * axis. This implementation of quad-tree uses only comparison function for
+  * range element datatype, therefore it works for any rangetype.
+  * 
+  * Quad tree is a data structure like a binary tree, but it is adopted to
+  * 2d data. Each node of quad-tree contain a point (centroid) which divides
+  * 2d-space into 4 quadrants, which are associated with children nodes. SP-GiST
+  * implementation of quad-tree have some speciality. SP-GiST accumulates leaf
+  * index tuples in pages until it overflows. Then it calls picksplit function
+  * of operator class for them. Picksplit function of this Quad-tree
+  * implementation uses medians along both axes as the centroid. 
+  * 
+  * Another speciality of this quad-tree implementation is handling of empty
+  * ranges. Idea is to have only one path in tree for empty ranges. If all the
+  * ranges at the moment of first picksplit is empty then we put all empty ranges
+  * into one node and create another node for further non-empty nodes. All
+  * further empty nodes will be put into same path as initial ones. If not all
+  * ranges in first picksplit are empty then we create special node number 5 for
+  * empty nodes. All further empty nodes will be put into node number 5 from
+  * root.
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * IDENTIFICATION
+  *			src/backend/utils/adt/rangetypes_spgist.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #include "postgres.h"
+ 
+ #include "access/spgist.h"
+ #include "access/skey.h"
+ #include "catalog/pg_type.h"
+ #include "utils/builtins.h"
+ #include "utils/datum.h"
+ #include "utils/rangetypes.h"
+ 
+ Datum spg_range_quad_config(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_choose(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_picksplit(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_inner_consistent(PG_FUNCTION_ARGS);
+ Datum spg_range_quad_leaf_consistent(PG_FUNCTION_ARGS);
+ 
+ static int16 getQuadrant(TypeCacheEntry *typcache, RangeType *centroid, RangeType *tst);
+ static int bound_cmp(const void *a, const void *b, void *arg);
+ 
+ /*
+  * Config SP-GiST interface function.
+  */
+ Datum
+ spg_range_quad_config(PG_FUNCTION_ARGS)
+ {
+ 	/* spgConfigIn *cfgin = (spgConfigIn *) PG_GETARG_POINTER(0); */
+ 	spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1);
+ 
+ 	cfg->prefixType = ANYRANGEOID;
+ 	cfg->labelType = VOIDOID;	/* we don't need node labels */
+ 	cfg->canReturnData = true;
+ 	cfg->longValuesOK = false;
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Determine which quadrant a 2d-mapped range falls into, relative to the
+  * centroid. 
+  *
+  * Quadrants are identified like this:
+  *
+  *	 4	|  1
+  *	----+-----
+  *	 3	|  2
+  *
+  * Ranges on one of the axes are taken to lie in the quadrant with higher value
+  * along perpendicular axis. Range equal to centroid is taken to lie in the
+  * quadrant 1. Empty ranges are taken to lie in the quadrant 5.
+  */
+ static int16
+ getQuadrant(TypeCacheEntry *typcache, RangeType *centroid, RangeType *tst)
+ {
+ 	RangeBound centroidLower, centroidUpper, lower, upper;
+ 	bool centroidEmpty, empty;
+ 	
+ 	range_deserialize(typcache, centroid, &centroidLower, &centroidUpper, 
+ 																&centroidEmpty);
+ 	range_deserialize(typcache, tst, &lower, &upper, &empty);
+ 	
+ 	if (empty)
+ 		return 5;
+ 	
+ 	if (range_cmp_bounds(typcache, &lower, &centroidLower) >= 0)
+ 	{
+ 		if (range_cmp_bounds(typcache, &upper, &centroidUpper) >= 0)
+ 			return 1;
+ 		else
+ 			return 2;		
+ 	}
+ 	else
+ 	{
+ 		if (range_cmp_bounds(typcache, &upper, &centroidUpper) >= 0)
+ 			return 4;
+ 		else
+ 			return 3;		
+ 	}
+ 
+ 	elog(ERROR, "getQuadrant: impossible case");
+ 	return 0;
+ }
+ 
+ 
+ /*
+  * Choose SP-GiST function: choose path for addition of new range.
+  */
+ Datum
+ spg_range_quad_choose(PG_FUNCTION_ARGS)
+ {
+ 	spgChooseIn	   *in = (spgChooseIn *) PG_GETARG_POINTER(0);
+ 	spgChooseOut   *out = (spgChooseOut *) PG_GETARG_POINTER(1);
+ 	RangeType	   *inRange = DatumGetRangeType(in->datum), *centroid;
+ 	int16			quadrant;
+ 	TypeCacheEntry *typcache;
+ 
+ 	if (in->allTheSame)
+ 	{
+ 		out->resultType = spgMatchNode;
+ 		/* nodeN will be set by core */
+ 		out->result.matchNode.levelAdd = 0;
+ 		out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(inRange));
+ 
+ 	/*
+ 	 * Absence of prefix datum divides ranges by empty sign. All empty ranges
+ 	 * are taken into node 0, all non-empty ranges are taken into node 1.
+ 	 */
+ 	if (!in->hasPrefix)
+ 	{
+ 		out->resultType = spgMatchNode;
+ 		if (RangeIsEmpty(inRange))
+ 			out->result.matchNode.nodeN = 0;
+ 		else
+ 			out->result.matchNode.nodeN = 1;
+ 		out->result.matchNode.levelAdd = 1;
+ 		out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	centroid = DatumGetRangeType(in->prefixDatum);
+ 	quadrant = getQuadrant(typcache, centroid, inRange);
+ 
+ 	Assert(quadrant <= in->nNodes);
+ 
+ 	/* Select node matching to quadrant number */
+ 	out->resultType = spgMatchNode;
+ 	out->result.matchNode.nodeN = quadrant - 1;
+ 	out->result.matchNode.levelAdd = 1;
+ 	out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Bound comparison for sorting.
+  */
+ static int
+ bound_cmp(const void *a, const void *b, void *arg)
+ {
+ 	RangeBound *ba = (RangeBound *) a;
+ 	RangeBound *bb = (RangeBound *) b;
+ 	TypeCacheEntry *typcache = (TypeCacheEntry *)arg;
+ 
+ 	return range_cmp_bounds(typcache, ba, bb);
+ }
+ 
+ /*
+  * Picksplit SP-GiST function: split ranges into nodes. Select "centroid"
+  * range and distribute ranges according to quadrants.
+  */
+ Datum
+ spg_range_quad_picksplit(PG_FUNCTION_ARGS)
+ {
+ 	spgPickSplitIn  *in = (spgPickSplitIn *) PG_GETARG_POINTER(0);
+ 	spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1);
+ 	int				 i, j, nonEmptyCount;
+ 	RangeType		*centroid;
+ 	bool			 empty;
+ 	TypeCacheEntry  *typcache;
+ 
+ 	/* Use the median values of lower and upper bounds as the centroid range */
+ 	RangeBound *lowerBounds, *upperBounds;
+ 
+ 	typcache = range_get_typcache(fcinfo, 
+ 							RangeTypeGetOid(DatumGetRangeType(in->datums[0])));
+ 
+ 	/* Allocate memory for bounds */
+ 	lowerBounds = palloc(sizeof(RangeBound) * in->nTuples);
+ 	upperBounds = palloc(sizeof(RangeBound) * in->nTuples);
+ 	j = 0;
+ 	
+ 	/* Deserialize bounds of ranges, count non-empty ranges */
+ 	for (i = 0; i < in->nTuples; i++)
+ 	{
+ 		range_deserialize(typcache, DatumGetRangeType(in->datums[i]),
+ 									&lowerBounds[j], &upperBounds[j], &empty);
+ 		if (!empty)
+ 			j++;
+ 	}
+ 	nonEmptyCount = j;
+ 	
+ 	/*
+ 	 * All the ranges are empty. We've nothing better than put all the ranges
+ 	 * into node 0. Non-empty range will be routed to node 1.
+ 	 */
+ 	if (nonEmptyCount == 0)
+ 	{
+ 		out->nNodes = 2;
+ 		out->hasPrefix = false;
+ 		/* Prefix is empty */
+ 		out->prefixDatum = PointerGetDatum(NULL);
+ 		out->nodeLabels = NULL;
+ 		
+ 		out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+ 		out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+ 		
+ 		/* Place all ranges into node 0 */
+ 		for (i = 0; i < in->nTuples; i++)
+ 		{
+ 			RangeType	   *range = DatumGetRangeType(in->datums[i]);
+ 
+ 			out->leafTupleDatums[i] = RangeTypeGetDatum(range);
+ 			out->mapTuplesToNodes[i] = 0;
+ 		}		
+ 		PG_RETURN_VOID();
+ 	}
+ 
+ 	/* Sort range bounds in order to find medians */
+ 	qsort_arg(lowerBounds, nonEmptyCount, sizeof(RangeBound), 
+ 														bound_cmp, typcache);
+ 	qsort_arg(upperBounds, nonEmptyCount, sizeof(RangeBound), 
+ 														bound_cmp, typcache);
+ 
+ 	/* Construct "centroid" range from medians of lower and upper bounds */
+ 	centroid = range_serialize(typcache, &lowerBounds[nonEmptyCount / 2],
+ 		&upperBounds[nonEmptyCount / 2], false);
+ 	
+ 	
+ 	out->hasPrefix = true;
+ 	out->prefixDatum = RangeTypeGetDatum(centroid);
+ 	
+ 	/* Create node for empty ranges only if it is a root node */
+ 	out->nNodes = (in->level == 0) ? 5 : 4;
+ 	out->nodeLabels = NULL;		/* we don't need node labels */
+ 
+ 	out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+ 	out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+ 
+ 	/* 
+ 	 * Add ranges to corresponding nodes according to quadrants relative to
+ 	 * "centroid" range.
+ 	 */
+ 	for (i = 0; i < in->nTuples; i++)
+ 	{
+ 		RangeType	   *range = DatumGetRangeType(in->datums[i]);
+ 		int16			quadrant = getQuadrant(typcache, centroid, range);
+ 
+ 		out->leafTupleDatums[i] = RangeTypeGetDatum(range);
+ 		out->mapTuplesToNodes[i] = quadrant - 1;
+ 	}
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Inner consisted SP-GiST function: check which nodes are consistent with
+  * given set of queries.
+  */
+ Datum
+ spg_range_quad_inner_consistent(PG_FUNCTION_ARGS)
+ {
+ 	spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
+ 	spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
+ 	int			which;
+ 	int			i;
+ 	bool		needPrevious = false;
+ 
+ 	if (in->allTheSame)
+ 	{
+ 		/* Report that all nodes should be visited */
+ 		out->nNodes = in->nNodes;
+ 		out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
+ 		for (i = 0; i < in->nNodes; i++)
+ 			out->nodeNumbers[i] = i;
+ 		PG_RETURN_VOID();
+ 	}
+ 	
+ 	if (!in->hasPrefix)
+ 	{
+ 		/*
+ 		 * Empty "centroid". We can use only information about emptiness of
+ 		 * ranges in nodes.
+ 		 */
+ 		Assert(in->nNodes == 2);
+ 		
+ 		/* 
+ 		 * Nth bit of which variable means that (N - 1)th node should be
+ 		 * visited. Initially all bits are set. Bits of nodes which should be
+ 		 * skipped will be unset.
+ 		 */
+ 		which = (1 << 1) | (1 << 2);
+ 		for (i = 0; i < in->nkeys; i++)
+ 		{
+ 			StrategyNumber strategy;
+ 			bool empty;
+ 			
+ 			strategy = in->scankeys[i].sk_strategy;
+ 			
+ 			/*
+ 			 * The only strategy when second argument of operator is not
+ 			 * range is RANGESTRAT_CONTAINS_ELEM.
+ 			 */
+ 			if (strategy != RANGESTRAT_CONTAINS_ELEM)
+ 				empty = RangeIsEmpty(
+ 								DatumGetRangeType(in->scankeys[i].sk_argument));
+ 			
+ 			switch (strategy)
+ 			{
+ 				/* These strategies return false if any argument is empty */
+ 				case RANGESTRAT_BEFORE:
+ 				case RANGESTRAT_OVERLEFT:
+ 				case RANGESTRAT_OVERLAPS:
+ 				case RANGESTRAT_OVERRIGHT:
+ 				case RANGESTRAT_AFTER:
+ 				case RANGESTRAT_ADJACENT:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 						which &= (1 << 2);
+ 					break;
+ 				/* 
+ 				 * "Empty" range is contained in any range. Non-empty ranges
+ 				 * can be contained in only non-empty ranges.
+ 				 */
+ 				case RANGESTRAT_CONTAINS:
+ 					if (!empty)
+ 						which &= (1 << 2);
+ 					break;
+ 				case RANGESTRAT_CONTAINED_BY:
+ 					if (empty)
+ 						which &= (1 << 1);
+ 					break;
+ 				/* Empty range can't contain any element */
+ 				case RANGESTRAT_CONTAINS_ELEM:
+ 					which &= (1 << 2);
+ 					break;
+ 				case RANGESTRAT_EQ:
+ 					if (empty)
+ 						which &= (1 << 1);
+ 					else
+ 						which &= (1 << 2);
+ 					break;
+ 				default:
+ 					elog(ERROR, "unrecognized range strategy: %d", strategy);
+ 					break;
+ 			}
+ 			if (which == 0)
+ 				break;			/* no need to consider remaining conditions */
+ 		}
+ 	}
+ 	else
+ 	{
+ 		RangeBound		centroidLower, centroidUpper;
+ 		bool			centroidEmpty;
+ 		TypeCacheEntry *typcache;
+ 		RangeType	   *centroid;
+ 
+ 		/* Prefix is not null, get information about it. */
+ 		centroid = DatumGetRangeType(in->prefixDatum);
+ 		typcache = range_get_typcache(fcinfo,
+ 								RangeTypeGetOid(DatumGetRangeType(centroid)));
+ 		range_deserialize(typcache, centroid, &centroidLower, &centroidUpper, 
+ 			&centroidEmpty);
+ 		
+ 		Assert(in->nNodes == 4 || in->nNodes == 5);
+ 		
+ 		/* 
+ 		 * Nth bit of which variable means that (N - 1)th node (Nth quadrant)
+ 		 * should be visited. Initially all bits are set. Bits of nodes which
+ 		 * should be skipped will be unset.
+ 		 */
+ 		which = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5);
+ 
+ 		for (i = 0; i < in->nkeys; i++)
+ 		{
+ 			StrategyNumber strategy;
+ 			RangeBound	lower, upper;
+ 			bool		empty;
+ 			RangeType  *range = NULL;
+ 
+ 			strategy = in->scankeys[i].sk_strategy;
+ 			
+ 			/*
+ 			 * Deserialize range if argument is range. The only strategy when
+ 			 * second argument of operator is not range is
+ 			 * RANGESTRAT_CONTAINS_ELEM.
+ 			 */
+ 			if (strategy != RANGESTRAT_CONTAINS_ELEM)
+ 			{
+ 				range = DatumGetRangeType(in->scankeys[i].sk_argument);
+ 				range_deserialize(typcache, range, &lower, &upper, &empty);
+ 			}
+ 			
+ 			switch (strategy)
+ 			{
+ 				RangeBound	prevLower, prevUpper;
+ 				bool		prevEmpty, prevPresent;
+ 				RangeType  *prevCentroid;
+ 				int			cmp1, cmp2, cmp3, which1, which2;
+ 				
+ 				/*
+ 				 * Range A is before range B if upper bound of A is lower than
+ 				 * lower bound of B. If upper bound of "centroid" is greater
+ 				 * or equal to lower bound of argument then no ranges before
+ 				 * argument can be contained in quadrants 2 and 4.
+ 				 */
+ 				case RANGESTRAT_BEFORE:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidUpper,
+ 																&lower) >= 0)
+ 						which &= (1 << 2) | (1 << 3);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Range A is overleft to range B if upper bound of A is lower
+ 				 * or equal to lower bound of B. If upper bound of "centroid" is
+ 				 * greater to upper bound of argument then no ranges overleft
+ 				 * argument can be contained in quadrants 1 and 4.
+ 				 */
+ 				case RANGESTRAT_OVERLEFT:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidUpper,
+ 																	&upper) > 0)
+ 						which = (1 << 2) | (1 << 3);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Non-empty ranges overlaps if lower bound of each range is
+ 				 * lower or equal to upper bound of another ranges.
+ 				 */
+ 				case RANGESTRAT_OVERLAPS:
+ 					if (empty)
+ 						which = 0;
+ 					else
+ 					{
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 						
+ 						/*
+ 						 * If lower bound of centroid is greater than upper
+ 						 * bound of argument then no overlapping ranges can be
+ 						 * in 1 and 2 quadrants.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidLower,
+ 																	&upper) > 0)
+ 							which &= (1 << 3) | (1 << 4);
+ 						
+ 						/*
+ 						 * If upper bound of centroid is lower or equal than
+ 						 * lower bound of argument then no overlapping ranges
+ 						 * can be in 2 and 3 quadrants.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidUpper,
+ 																   &lower) <= 0)
+ 							which &= (1 << 1) | (1 << 4);
+ 					}
+ 					break;
+ 				/*
+ 				 * Range A is overright to range B if lower bound of A is upper
+ 				 * or equal to upper bound of B. If lower bound of "centroid" is
+ 				 * lower or equal to lower bound of argument then no ranges
+ 				 * overright argument can be contained in quadrants 3 and 4.
+ 				 */
+ 				case RANGESTRAT_OVERRIGHT:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidLower, &lower) <= 0)
+ 						which = (1 << 1) | (1 << 2);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Range A is after range B if lower bound of A is greater than
+ 				 * upper bound of B. If lower bound of "centroid" is lower
+ 				 * or equal to upper bound of argument then no ranges after
+ 				 * argument can be contained in quadrants 3 and 4.
+ 				 */
+ 				case RANGESTRAT_AFTER:
+ 					if (empty)
+ 						which = 0;
+ 					else if (range_cmp_bounds(typcache, &centroidLower, &upper) <= 0)
+ 						which &= (1 << 1) | (1 << 2);
+ 					else
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					break;
+ 				/*
+ 				 * Ranges are adjacent if lower bound of one range is adjacent
+ 				 * to upper bound of another range.
+ 				 */
+ 				case RANGESTRAT_ADJACENT:
+ 					if (empty)
+ 					{
+ 						which = 0;
+ 						break;
+ 					}
+ 					
+ 					lower.lower = false;
+ 					lower.inclusive = !lower.inclusive;
+ 					upper.lower = true;
+ 					upper.inclusive = !upper.inclusive;
+ 					
+ 					/*
+ 					 * which1 is bitmask for possibility to be adjacent with
+ 					 * lower bound of argument. which2 is bitmask for
+ 					 * possibility to be adjacent with upper bound of
+ 					 * argument. 
+ 					 */
+ 					which1 = which2 = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 
+ 					/* Deserialize previous centroid range if present. */
+ 					prevPresent = (in->reconstructedValue != (Datum) 0);
+ 					if (prevPresent)
+ 					{
+ 						prevCentroid = DatumGetRangeType(in->reconstructedValue);
+ 						range_deserialize(typcache, prevCentroid, &prevLower,
+ 							&prevUpper, &prevEmpty);
+ 					}
+ 					
+ 					cmp2 = range_cmp_bounds(typcache, &upper, &centroidLower);
+ 					if (prevPresent)
+ 					{
+ 						/* Do comparison with previous centroid */
+ 						cmp1 = range_cmp_bounds(typcache, &upper, &prevLower);
+ 						cmp3 = range_cmp_bounds(typcache, &centroidLower,
+ 																	&prevLower);
+ 						
+ 						/* 
+ 						 * Check if lower bound of argument is not in
+ 						 * a quadrant we visit in previous step.
+ 						 */
+ 						if ((cmp3 < 0 && cmp1 > 0) || (cmp3 > 0 && cmp1 < 0))
+ 							which1 = 0;
+ 					}
+ 
+ 					if (cmp2 >= 0)
+ 						which1 &= (1 << 1) | (1 << 2);
+ 					else if (!bounds_adjacent(typcache, &centroidLower, &upper))
+ 						which1 &= (1 << 3) | (1 << 4);
+ 
+ 					cmp2 = range_cmp_bounds(typcache, &lower, &centroidUpper);
+ 					if (prevPresent)
+ 					{
+ 						/* Do comparison with previous centroid */
+ 						cmp1 = range_cmp_bounds(typcache, &lower, &prevUpper);
+ 						cmp3 = range_cmp_bounds(typcache, &centroidUpper, &prevUpper);
+ 						/* 
+ 						 * Check if upper bound of argument is not in
+ 						 * a quadrant we visit in previous step.
+ 						 */
+ 						if ((cmp3 < 0 && cmp1 > 0) || (cmp3 > 0 && cmp1 < 0))
+ 							which2 = 0;
+ 					}
+ 
+ 					if (cmp2 > 0)
+ 						which2 &= (1 << 1) | (1 << 4);
+ 					else if (cmp2 < 0)
+ 						which2 &= (1 << 2) | (1 << 3);
+ 
+ 					which &= which1 | which2;
+ 					
+ 					needPrevious = true;
+ 					break;
+ 				/*
+ 				 * Non-empty range A contains non-empty range B if lower bound
+ 				 * of A is lower or equal to lower bound of range B and upper
+ 				 * bound of range A is greater or equal to upper bound of range
+ 				 * A.
+ 				 */
+ 				case RANGESTRAT_CONTAINS:
+ 					if (!empty)
+ 					{
+ 						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 						/* 
+ 						 * If lower bound of centroid is greater than lower
+ 						 * bound of argument then no ranges which contain
+ 						 * argument can be in quadrants 1 and 2.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidLower,
+ 																	&lower) > 0)
+ 							which &= (1 << 3) | (1 << 4);
+ 						/* 
+ 						 * If upper bound of centroid is lower or equal to upper
+ 						 * bound of argument then no ranges which contain
+ 						 * argument can be in quadrants 2 and 3.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidUpper,
+ 																   &upper) <= 0)
+ 							which &= (1 << 1) | (1 << 4);
+ 					}
+ 					break;
+ 				case RANGESTRAT_CONTAINED_BY:
+ 					if (empty)
+ 						which = (1 << 5);
+ 					else
+ 					{
+ 						/* 
+ 						 * If lower bound of centroid is lower or equal to lower
+ 						 * bound of argument then no ranges which are contained
+ 						 * in argument can be in quadrants 3 and 4.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidLower,
+ 																   &lower) <= 0)
+ 							which &= (1 << 1) | (1 << 2) | (1 << 5);
+ 						/* 
+ 						 * If upper bound of centroid is greater than upper
+ 						 * bound of argument then no ranges which are contained
+ 						 * in argument can be in quadrants 1 and 4.
+ 						 */
+ 						if (range_cmp_bounds(typcache, &centroidUpper,
+ 																	&upper) > 0)
+ 							which &= (1 << 2) | (1 << 3) | (1 << 5);
+ 					}
+ 					break;
+ 				case RANGESTRAT_CONTAINS_ELEM:
+ 					/* 
+ 					 * Construct bound to pass then to bound comparison
+ 					 * functions
+ 					 */
+ 					lower.inclusive = true;
+ 					lower.infinite = false;
+ 					lower.lower = true;
+ 					lower.val = in->scankeys[i].sk_argument;
+ 					
+ 					upper.inclusive = true;
+ 					upper.infinite = false;
+ 					upper.lower = false;
+ 					upper.val = in->scankeys[i].sk_argument;
+ 					
+ 					which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 					
+ 					/*
+ 					 * If lower bound of centroid is greater than lower bound of
+ 					 * argument then ranges containing element can't be in 1 and 2
+ 					 * quadrants.
+ 					 */
+ 					if (range_cmp_bound_values(typcache, &centroidLower,
+ 																	&lower) > 0)
+ 							which &= (1 << 3) | (1 << 4);
+ 					
+ 					/*
+ 					 * If upper bound of centroid is lower or equal than upper
+ 					 * bound of argument then ranges containing element can't be
+ 					 * in 2 and 3 quadrants.
+ 					 */
+ 					if (range_cmp_bound_values(typcache, &centroidUpper,
+ 																   &upper) <= 0)
+ 							which &= (1 << 1) | (1 << 4);				
+ 					
+ 					break;
+ 				/*
+ 				 * Equal range can be only in the same quadrant where argument
+ 				 * would be placed to.
+ 				 */
+ 				case RANGESTRAT_EQ:
+ 					which &= (1 << getQuadrant(typcache, centroid, range));
+ 					break;
+ 				default:
+ 					elog(ERROR, "unrecognized range strategy: %d", strategy);
+ 					break;
+ 			}
+ 
+ 			if (which == 0)
+ 				break;			/* no need to consider remaining conditions */
+ 		}
+ 	}
+ 
+ 	/* We must descend into the quadrant(s) identified by which */
+ 	out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
+ 	if (needPrevious)
+ 		out->reconstructedValues = (Datum *) palloc(sizeof(Datum) * in->nNodes);
+ 	out->nNodes = 0;
+ 	for (i = 1; i <= in->nNodes; i++)
+ 	{
+ 		if (which & (1 << i))
+ 		{
+ 			/* Save previous prefix if needed */
+ 			if (needPrevious)
+ 				out->reconstructedValues[out->nNodes] = in->prefixDatum;
+ 			out->nodeNumbers[out->nNodes++] = i - 1;
+ 		}
+ 	}
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Leaf consistent SP-GiST function: check leaf value against query using
+  * corresponding function.
+  */
+ Datum
+ spg_range_quad_leaf_consistent(PG_FUNCTION_ARGS)
+ {
+ 	spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0);
+ 	spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1);
+ 	bool		res;
+ 	int			i;
+ 	TypeCacheEntry *typcache;
+ 
+ 	/* all tests are exact */
+ 	out->recheck = false;
+ 
+ 	/* leafDatum is what it is... */
+ 	out->leafValue = in->leafDatum;
+ 
+ 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(
+ 											DatumGetRangeType(in->leafDatum)));
+ 	
+ 	/* Perform the required comparison(s) */
+ 	res = true;
+ 	for (i = 0; i < in->nkeys; i++)
+ 	{
+ 		/* Find the function which is corresponding to the scan strategy */
+ 		switch (in->scankeys[i].sk_strategy)
+ 		{
+ 			case RANGESTRAT_BEFORE:
+ 				res = range_before_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_OVERLEFT:
+ 				res = range_overleft_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_OVERLAPS:
+ 				res = range_overlaps_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_OVERRIGHT:
+ 				res = range_overright_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_AFTER:
+ 				res = range_after_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_ADJACENT:
+ 				res = range_adjacent_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_CONTAINS:
+ 				res = range_contains_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_CONTAINED_BY:
+ 				res = range_contained_by_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 							DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			case RANGESTRAT_CONTAINS_ELEM:
+ 				res = range_contains_elem_internal(typcache, 
+ 											DatumGetRangeType(in->leafDatum), 
+ 												in->scankeys[i].sk_argument);
+ 				break;
+ 			case RANGESTRAT_EQ:
+ 				res = range_eq_internal(typcache,
+ 											DatumGetRangeType(in->leafDatum), 
+ 								DatumGetRangeType(in->scankeys[i].sk_argument));
+ 				break;
+ 			default:
+ 				elog(ERROR, "unrecognized range strategy: %d", 
+ 												in->scankeys[i].sk_strategy);
+ 				res = false;
+ 				break;
+ 		}
+ 		
+ 		/* If leaf datum don't match to one query, we can don't check another */
+ 		if (!res)
+ 			break;
+ 	}
+ 
+ 	PG_RETURN_BOOL(res);
+ }
*** a/src/include/catalog/pg_amop.h
--- b/src/include/catalog/pg_amop.h
***************
*** 767,770 **** DATA(insert (	4017   25 25 12 s	665 4000 0 ));
--- 767,784 ----
  DATA(insert (	4017   25 25 14 s	667 4000 0 ));
  DATA(insert (	4017   25 25 15 s	666 4000 0 ));
  
+ /*
+  * SP-GiST range_ops
+  */
+ DATA(insert (	3474   3831 3831 1 s	3893 4000 0 ));
+ DATA(insert (	3474   3831 3831 2 s	3895 4000 0 ));
+ DATA(insert (	3474   3831 3831 3 s	3888 4000 0 ));
+ DATA(insert (	3474   3831 3831 4 s	3896 4000 0 ));
+ DATA(insert (	3474   3831 3831 5 s	3894 4000 0 ));
+ DATA(insert (	3474   3831 3831 6 s	3897 4000 0 ));
+ DATA(insert (	3474   3831 3831 7 s	3890 4000 0 ));
+ DATA(insert (	3474   3831 3831 8 s	3892 4000 0 ));
+ DATA(insert (	3474   3831 2283 16 s	3889 4000 0 ));
+ DATA(insert (	3474   3831 3831 18 s	3882 4000 0 ));
+ 
  #endif   /* PG_AMOP_H */
*** a/src/include/catalog/pg_amproc.h
--- b/src/include/catalog/pg_amproc.h
***************
*** 373,377 **** DATA(insert (	4017   25 25 2 4028 ));
--- 373,382 ----
  DATA(insert (	4017   25 25 3 4029 ));
  DATA(insert (	4017   25 25 4 4030 ));
  DATA(insert (	4017   25 25 5 4031 ));
+ DATA(insert (	3474   3831 3831 1 3469 ));
+ DATA(insert (	3474   3831 3831 2 3470 ));
+ DATA(insert (	3474   3831 3831 3 3471 ));
+ DATA(insert (	3474   3831 3831 4 3472 ));
+ DATA(insert (	3474   3831 3831 5 3473 ));
  
  #endif   /* PG_AMPROC_H */
*** a/src/include/catalog/pg_opclass.h
--- b/src/include/catalog/pg_opclass.h
***************
*** 223,228 **** DATA(insert (	783		tsquery_ops			PGNSP PGUID 3702  3615 t 20 ));
--- 223,229 ----
  DATA(insert (	403		range_ops			PGNSP PGUID 3901  3831 t 0 ));
  DATA(insert (	405		range_ops			PGNSP PGUID 3903  3831 t 0 ));
  DATA(insert (	783		range_ops			PGNSP PGUID 3919  3831 t 0 ));
+ DATA(insert (	4000	range_ops			PGNSP PGUID 3474  3831 t 0 ));
  DATA(insert (	4000	quad_point_ops		PGNSP PGUID 4015  600 t 0 ));
  DATA(insert (	4000	kd_point_ops		PGNSP PGUID 4016  600 f 0 ));
  DATA(insert (	4000	text_ops			PGNSP PGUID 4017  25 t 0 ));
*** a/src/include/catalog/pg_opfamily.h
--- b/src/include/catalog/pg_opfamily.h
***************
*** 142,147 **** DATA(insert OID = 3702 (	783		tsquery_ops		PGNSP PGUID ));
--- 142,148 ----
  DATA(insert OID = 3901 (	403		range_ops		PGNSP PGUID ));
  DATA(insert OID = 3903 (	405		range_ops		PGNSP PGUID ));
  DATA(insert OID = 3919 (	783		range_ops		PGNSP PGUID ));
+ DATA(insert OID = 3474 (	4000	range_ops		PGNSP PGUID ));
  DATA(insert OID = 4015 (	4000	quad_point_ops	PGNSP PGUID ));
  DATA(insert OID = 4016 (	4000	kd_point_ops	PGNSP PGUID ));
  DATA(insert OID = 4017 (	4000	text_ops		PGNSP PGUID ));
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4653,4658 **** DESCR("SP-GiST support for suffix tree over text");
--- 4653,4669 ----
  DATA(insert OID = 4031 (  spg_text_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_text_leaf_consistent _null_ _null_ _null_ ));
  DESCR("SP-GiST support for suffix tree over text");
  
+ DATA(insert OID = 3469 (  spg_range_quad_config	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_config _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3470 (  spg_range_quad_choose	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_choose _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3471 (  spg_range_quad_picksplit	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_picksplit _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3472 (  spg_range_quad_inner_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_inner_consistent _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ DATA(insert OID = 3473 (  spg_range_quad_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_leaf_consistent _null_ _null_ _null_ ));
+ DESCR("SP-GiST support for quad tree over range");
+ 
  
  /*
   * Symbolic values for provolatile column: these indicate whether the result
*** a/src/include/utils/rangetypes.h
--- b/src/include/utils/rangetypes.h
***************
*** 75,80 **** typedef struct
--- 75,93 ----
  #define PG_GETARG_RANGE_COPY(n)		DatumGetRangeTypeCopy(PG_GETARG_DATUM(n))
  #define PG_RETURN_RANGE(x)			return RangeTypeGetDatum(x)
  
+ /* Operator strategy numbers used in the GiST range opclass */
+ /* Numbers are chosen to match up operator names with existing usages */
+ #define RANGESTRAT_BEFORE				1
+ #define RANGESTRAT_OVERLEFT				2
+ #define RANGESTRAT_OVERLAPS				3
+ #define RANGESTRAT_OVERRIGHT			4
+ #define RANGESTRAT_AFTER				5
+ #define RANGESTRAT_ADJACENT				6
+ #define RANGESTRAT_CONTAINS				7
+ #define RANGESTRAT_CONTAINED_BY			8
+ #define RANGESTRAT_CONTAINS_ELEM		16
+ #define RANGESTRAT_EQ					18
+ 
  /*
   * prototypes for functions defined in rangetypes.c
   */
***************
*** 188,193 **** extern int range_cmp_bounds(TypeCacheEntry *typcache, RangeBound *b1,
--- 201,208 ----
  extern int range_cmp_bound_values(TypeCacheEntry *typcache, RangeBound *b1,
  					   RangeBound *b2);
  extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+ extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound *lower,
+ 						RangeBound *upper);
  
  /* GiST support (in rangetypes_gist.c) */
  extern Datum range_gist_consistent(PG_FUNCTION_ARGS);
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
***************
*** 1068,1079 **** ORDER BY 1, 2, 3;
--- 1068,1084 ----
         2742 |            4 | =
         4000 |            1 | <<
         4000 |            1 | ~<~
+        4000 |            2 | &<
         4000 |            2 | ~<=~
+        4000 |            3 | &&
         4000 |            3 | =
+        4000 |            4 | &>
         4000 |            4 | ~>=~
         4000 |            5 | >>
         4000 |            5 | ~>~
+        4000 |            6 | -|-
         4000 |            6 | ~=
+        4000 |            7 | @>
         4000 |            8 | <@
         4000 |           10 | <^
         4000 |           11 | <
***************
*** 1081,1087 **** ORDER BY 1, 2, 3;
         4000 |           12 | <=
         4000 |           14 | >=
         4000 |           15 | >
! (55 rows)
  
  -- Check that all opclass search operators have selectivity estimators.
  -- This is not absolutely required, but it seems a reasonable thing
--- 1086,1094 ----
         4000 |           12 | <=
         4000 |           14 | >=
         4000 |           15 | >
!        4000 |           16 | @>
!        4000 |           18 | =
! (62 rows)
  
  -- Check that all opclass search operators have selectivity estimators.
  -- This is not absolutely required, but it seems a reasonable thing
*** a/src/test/regress/expected/rangetypes.out
--- b/src/test/regress/expected/rangetypes.out
***************
*** 821,826 **** select count(*) from test_range_gist where ir -|- int4range(100,500);
--- 821,1045 ----
       5
  (1 row)
  
+ -- test SP-GiST index that's been built incrementally
+ create table test_range_spgist(ir int4range);
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(g, g+10000) from generate_series(1,1000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(NULL,g*10,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g*10,NULL,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ -- first, verify non-indexed results
+ SET enable_seqscan    = t;
+ SET enable_indexscan  = f;
+ SET enable_bitmapscan = f;
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+  count 
+ -------
+   6200
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> 10;
+  count 
+ -------
+    130
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+  count 
+ -------
+    111
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+  count 
+ -------
+    158
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+  count 
+ -------
+   1062
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+  count 
+ -------
+    189
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+  count 
+ -------
+   3554
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+  count 
+ -------
+   1029
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+  count 
+ -------
+   4794
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+  count 
+ -------
+      5
+ (1 row)
+ 
+ -- now check same queries using index
+ SET enable_seqscan    = f;
+ SET enable_indexscan  = t;
+ SET enable_bitmapscan = f;
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+  count 
+ -------
+   6200
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> 10;
+  count 
+ -------
+    130
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+  count 
+ -------
+    111
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+  count 
+ -------
+    158
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+  count 
+ -------
+   1062
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+  count 
+ -------
+    189
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+  count 
+ -------
+   3554
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+  count 
+ -------
+   1029
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+  count 
+ -------
+   4794
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+  count 
+ -------
+      5
+ (1 row)
+ 
+ -- now check same queries using a bulk-loaded index
+ drop index test_range_spgist_idx;
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+  count 
+ -------
+   6200
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+  count 
+ -------
+      2
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> 10;
+  count 
+ -------
+    130
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+  count 
+ -------
+    111
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+  count 
+ -------
+    158
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+  count 
+ -------
+   1062
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+  count 
+ -------
+    189
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+  count 
+ -------
+   3554
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+  count 
+ -------
+   1029
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+  count 
+ -------
+   4794
+ (1 row)
+ 
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+  count 
+ -------
+      5
+ (1 row)
+ 
  RESET enable_seqscan;
  RESET enable_indexscan;
  RESET enable_bitmapscan;
*** a/src/test/regress/expected/sanity_check.out
--- b/src/test/regress/expected/sanity_check.out
***************
*** 157,162 **** SELECT relname, relhasindex
--- 157,163 ----
   tenk2                   | t
   test_range_excl         | t
   test_range_gist         | t
+  test_range_spgist       | t
   test_tsvector           | f
   text_tbl                | f
   time_tbl                | f
***************
*** 165,171 **** SELECT relname, relhasindex
   timetz_tbl              | f
   tinterval_tbl           | f
   varchar_tbl             | f
! (154 rows)
  
  --
  -- another sanity check: every system catalog that has OIDs should have
--- 166,172 ----
   timetz_tbl              | f
   tinterval_tbl           | f
   varchar_tbl             | f
! (155 rows)
  
  --
  -- another sanity check: every system catalog that has OIDs should have
*** a/src/test/regress/output/misc.source
--- b/src/test/regress/output/misc.source
***************
*** 675,680 **** SELECT user_relns() AS user_relns
--- 675,681 ----
   tenk2
   test_range_excl
   test_range_gist
+  test_range_spgist
   test_tsvector
   text_tbl
   time_tbl
***************
*** 685,691 **** SELECT user_relns() AS user_relns
   toyemp
   varchar_tbl
   xacttest
! (107 rows)
  
  SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
   name 
--- 686,692 ----
   toyemp
   varchar_tbl
   xacttest
! (108 rows)
  
  SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
   name 
*** a/src/test/regress/sql/rangetypes.sql
--- b/src/test/regress/sql/rangetypes.sql
***************
*** 220,225 **** select count(*) from test_range_gist where ir &< int4range(100,500);
--- 220,287 ----
  select count(*) from test_range_gist where ir &> int4range(100,500);
  select count(*) from test_range_gist where ir -|- int4range(100,500);
  
+ -- test SP-GiST index that's been built incrementally
+ create table test_range_spgist(ir int4range);
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ 
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(g, g+10000) from generate_series(1,1000) g;
+ insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+ insert into test_range_spgist select int4range(NULL,g*10,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g*10,NULL,'(]') from generate_series(1,100) g;
+ insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+ 
+ -- first, verify non-indexed results
+ SET enable_seqscan    = t;
+ SET enable_indexscan  = f;
+ SET enable_bitmapscan = f;
+ 
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+ select count(*) from test_range_spgist where ir @> 10;
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ 
+ -- now check same queries using index
+ SET enable_seqscan    = f;
+ SET enable_indexscan  = t;
+ SET enable_bitmapscan = f;
+ 
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+ select count(*) from test_range_spgist where ir @> 10;
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ 
+ -- now check same queries using a bulk-loaded index
+ drop index test_range_spgist_idx;
+ create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+ 
+ select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ select count(*) from test_range_spgist where ir = int4range(10,20);
+ select count(*) from test_range_spgist where ir @> 10;
+ select count(*) from test_range_spgist where ir @> int4range(10,20);
+ select count(*) from test_range_spgist where ir && int4range(10,20);
+ select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ select count(*) from test_range_spgist where ir << int4range(100,500);
+ select count(*) from test_range_spgist where ir >> int4range(100,500);
+ select count(*) from test_range_spgist where ir &< int4range(100,500);
+ select count(*) from test_range_spgist where ir &> int4range(100,500);
+ select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ 
  RESET enable_seqscan;
  RESET enable_indexscan;
  RESET enable_bitmapscan;
#19Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Alexander Korotkov (#18)
1 attachment(s)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On 23.07.2012 10:37, Alexander Korotkov wrote:

On Fri, Jul 20, 2012 at 3:48 PM, Heikki Linnakangas<
heikki.linnakangas@enterprisedb.com> wrote:

It would be nice to have an introduction, perhaps as a file comment at the
top of rangetypes_spgist.c, explaining how the quad tree works. I have a
general idea of what a quad tree is, but it's not immediately obvious how
it maps to SP-GiST. What is stored on a leaf node and an internal node?
What is the 'prefix' (seems to be the centroid)? How are ranges mapped to
2D points? (the function comment of getQuadrant() is a good start for that
last one)

I've added some comments at the top of rangetypes_spgist.c.

Thanks, much better.

I think the handling of empty ranges needs some further explanation. If
I understand the code correctly, the root node can contain a centroid
like usual, and empty ranges are placed in the magic 5th quadrant.
Alternatively, the root node has no centroid, and contains only two
subnodes: all empty ranges are placed under one subnode, and non-empty
ranges under the other.

It seems it would be simpler if we always stored empty nodes the latter
way, with a no-centroid root node, and nodes with a centroid would
always only have 4 subnodes. When the first empty range is added to an
index that already contains non-empty values, the choose-function could
return spgSplitTuple to create a new root node that divides the space
into empty and non-empty values. Then again, I don't think it would
actually simplify the code much. Handling the 5th quadrant doesn't
require much code in spg_range_quad_inner_consistent() as it is. So
maybe it's better to leave it the way it is.

Or perhaps we should stipulate that a root node with no centroid can
only contain empty ranges. When you add the first non-empty range, have
choose function return spgSplitTuple, and create a new root node with a
centroid, and the empty ranges in the 5th quadrant.

In spg_range_quad_inner_**consistent(), if in->hasPrefix == true, ISTM that
in all cases where 'empty' is true, 'which' is set to 0, meaning that there
can be no matches in any of the quadrants. In most of the case-branches,
you explicitly check for 'empty', but even in the ones where you don't, I
think you end up setting which=0 if empty==true. I'm not 100% sure about
the RANGESTRAT_ADJACENT case, though. Am I missing something?

Ops., it was a bug: RANGESTRAT_ADJACENT shoud set which=0 if empty==true,
while RANGESTRAT_CONTAINS and RANGESTRAT_CONTAINED_BY not. Corrected.

Ok. I did some copy-editing, rewording some comments, and fixing
whitespace. Patch attached, hope I didn't break anything.

I think the most difficult part of the patch is the
spg_range_quad_inner_consistent() function. It's hard to understand how
the various strategies are implemented. I started to expand the comments
in for each strategy, explaining how each strategy maps to a bounding
box in the 2D space, but I'm not sure how much that actually helps.
Perhaps it would help to also restructure the code so that you first
have a switch-case statement that maps the scan key to bounding box,
without worrying about the centroid yet, and then check which quadrants
you need to descend to to find points within the box. The
adjacent-strategy is more complicated than a simple bounding-box search,
though.

I'm having trouble understanding how exactly the RANGESTRAT_ADJACENT
works. The geometric interpretation of 'adjacent' is that the scan key
defines two lines, one horizontal and one vertical. Any points that lie
on either of the lines match the query. Looking at the implementation,
it's not clear how the code implements the search for along those two lines.

Also, I wonder if we really need to reconstruct the "previous" value in
a RANGESTRAT_ADJACENT search. ISTM we only need to remember which of the
two lines we are chasing. For example, if you descend to quadrant 2
because there might be a point there that lies on the horizontal line,
but we already know that there can't be any points there lie on the
vertical line, you only need to remember that, not the whole centroid
from the previous level. Does the SP-GiST API require the
"reconstructed" values stored by inner_consistent to be of the correct
datatype, or can it store any Datums in the array?

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

Attachments:

range_spgist_quad-0.6-heikki.patchtext/x-diff; name=range_spgist_quad-0.6-heikki.patchDownload
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index c5b0a75..a692086 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -30,7 +30,7 @@ OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
 	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
 	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
 	tsvector.o tsvector_op.o tsvector_parser.o \
-	txid.o uuid.o windowfuncs.o xml.o
+	txid.o uuid.o windowfuncs.o xml.o rangetypes_spgist.o
 
 like.o: like.c like_match.c
 
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index c2f0b25..bab9861 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -709,6 +709,49 @@ range_after(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(range_after_internal(typcache, r1, r2));
 }
 
+/*
+ * Check if two bounds are "adjacent". Bounds are adjacent when each subtype
+ * value belongs to strictly one of the bounds: there are no values which
+ * satisfy both bounds, and there are no values between the bounds.
+ *
+ * For discrete ranges, we rely on the canonicalization function to normalize
+ * A..B to empty if it contains no elements of the subtype.  (If there is no
+ * canonicalization function, it's impossible for such a range to normalize to
+ * empty, so we needn't bother to try.)
+ *
+ * If A == B, the ranges are adjacent only if these bounds have different
+ * inclusive flags (i.e., exactly one of the ranges includes the common
+ * boundary point).
+ *
+ * If A > B, the ranges cannot be adjacent in this order.
+ */
+bool
+bounds_adjacent(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper)
+{
+	int cmp;
+
+	cmp = range_cmp_bound_values(typcache, upper, lower);
+	if (cmp < 0)
+	{
+		RangeType *r;
+		/* in a continuous subtype, there are assumed to be points between */
+		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
+			return false;
+		/* flip the inclusion flags */
+		upper->inclusive = !upper->inclusive;
+		lower->inclusive = !lower->inclusive;
+		/* change upper/lower labels to avoid Assert failures */
+		upper->lower = true;
+		lower->lower = false;
+		r = make_range(typcache, upper, lower, false);
+		PG_RETURN_BOOL(RangeIsEmpty(r));
+	}
+	else if (cmp == 0)
+		PG_RETURN_BOOL(upper->inclusive != lower->inclusive);
+	else
+		PG_RETURN_BOOL(false);
+}
+
 /* adjacent to (but not overlapping)? (internal version) */
 bool
 range_adjacent_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
@@ -719,8 +762,6 @@ range_adjacent_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
 				upper2;
 	bool		empty1,
 				empty2;
-	RangeType  *r3;
-	int			cmp;
 
 	/* Different types should be prevented by ANYRANGE matching rules */
 	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
@@ -734,62 +775,11 @@ range_adjacent_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
 		return false;
 
 	/*
-	 * Given two ranges A..B and C..D, where B < C, the ranges are adjacent if
-	 * and only if the range B..C is empty, where inclusivity of these two
-	 * bounds is inverted compared to the original bounds.	For discrete
-	 * ranges, we have to rely on the canonicalization function to normalize
-	 * B..C to empty if it contains no elements of the subtype.  (If there is
-	 * no canonicalization function, it's impossible for such a range to
-	 * normalize to empty, so we needn't bother to try.)
-	 *
-	 * If B == C, the ranges are adjacent only if these bounds have different
-	 * inclusive flags (i.e., exactly one of the ranges includes the common
-	 * boundary point).
-	 *
-	 * And if B > C then the ranges cannot be adjacent in this order, but we
-	 * must consider the other order (i.e., check D <= A).
+	 * Given two ranges A..B and C..D, the ranges are adjacent if and only if
+	 * the bounds B and C are adjacent, or D and A are adjacent.
 	 */
-	cmp = range_cmp_bound_values(typcache, &upper1, &lower2);
-	if (cmp < 0)
-	{
-		/* in a continuous subtype, there are assumed to be points between */
-		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
-			return (false);
-		/* flip the inclusion flags */
-		upper1.inclusive = !upper1.inclusive;
-		lower2.inclusive = !lower2.inclusive;
-		/* change upper/lower labels to avoid Assert failures */
-		upper1.lower = true;
-		lower2.lower = false;
-		r3 = make_range(typcache, &upper1, &lower2, false);
-		return RangeIsEmpty(r3);
-	}
-	if (cmp == 0)
-	{
-		return (upper1.inclusive != lower2.inclusive);
-	}
-
-	cmp = range_cmp_bound_values(typcache, &upper2, &lower1);
-	if (cmp < 0)
-	{
-		/* in a continuous subtype, there are assumed to be points between */
-		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
-			return (false);
-		/* flip the inclusion flags */
-		upper2.inclusive = !upper2.inclusive;
-		lower1.inclusive = !lower1.inclusive;
-		/* change upper/lower labels to avoid Assert failures */
-		upper2.lower = true;
-		lower1.lower = false;
-		r3 = make_range(typcache, &upper2, &lower1, false);
-		return RangeIsEmpty(r3);
-	}
-	if (cmp == 0)
-	{
-		return (upper2.inclusive != lower1.inclusive);
-	}
-
-	return false;
+	return (bounds_adjacent(typcache, &lower2, &upper1) ||
+			bounds_adjacent(typcache, &lower1, &upper2));
 }
 
 /* adjacent to (but not overlapping)? */
diff --git a/src/backend/utils/adt/rangetypes_gist.c b/src/backend/utils/adt/rangetypes_gist.c
index 21f0eba..190b506 100644
--- a/src/backend/utils/adt/rangetypes_gist.c
+++ b/src/backend/utils/adt/rangetypes_gist.c
@@ -20,20 +20,6 @@
 #include "utils/datum.h"
 #include "utils/rangetypes.h"
 
-
-/* Operator strategy numbers used in the GiST range opclass */
-/* Numbers are chosen to match up operator names with existing usages */
-#define RANGESTRAT_BEFORE				1
-#define RANGESTRAT_OVERLEFT				2
-#define RANGESTRAT_OVERLAPS				3
-#define RANGESTRAT_OVERRIGHT			4
-#define RANGESTRAT_AFTER				5
-#define RANGESTRAT_ADJACENT				6
-#define RANGESTRAT_CONTAINS				7
-#define RANGESTRAT_CONTAINED_BY			8
-#define RANGESTRAT_CONTAINS_ELEM		16
-#define RANGESTRAT_EQ					18
-
 /*
  * Range class properties used to segregate different classes of ranges in
  * GiST.  Each unique combination of properties is a class.  CLS_EMPTY cannot
diff --git a/src/backend/utils/adt/rangetypes_spgist.c b/src/backend/utils/adt/rangetypes_spgist.c
new file mode 100644
index 0000000..a30c231
--- /dev/null
+++ b/src/backend/utils/adt/rangetypes_spgist.c
@@ -0,0 +1,827 @@
+/*-------------------------------------------------------------------------
+ *
+ * rangetypes_spgist.c
+ *	  implementation of quad tree over ranges mapped to 2d-points for SP-GiST.
+ *
+ * Ranges are mapped to 2d-points as following: Lower bound of range is mapped
+ * to the horizontal axis. Upper bound is mapped to the vertical axis. This
+ * implementation of quad-tree uses only comparison function for range element
+ * datatype, therefore it works for any range type.
+ *
+ * Quad tree is a data structure like a binary tree, but it is adopted to
+ * 2d data. Each node of quad-tree contains a point (centroid) which divides
+ * the 2d-space into 4 quadrants, which are associated with children nodes.
+ *
+ * SP-GiST implementation of quad-tree have some speciality. SP-GiST
+ * accumulates leaf index tuples in pages until it overflows. Then it calls
+ * picksplit function of operator class for them. Picksplit function of this
+ * Quad-tree implementation uses medians along both axes as the centroid.
+ *
+ * Another speciality of this quad-tree implementation is handling of empty
+ * ranges. Idea is to have only one path in tree for empty ranges. If all the
+ * ranges at the moment of first picksplit are empty, we put all empty ranges
+ * into one node and create another node for further non-empty nodes. All
+ * further empty nodes will be put into same path as initial ones. If not all
+ * ranges in first picksplit are empty then we create special node number 5 for
+ * empty nodes. All further empty nodes will be put into node number 5 from
+ * root.
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *			src/backend/utils/adt/rangetypes_spgist.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/spgist.h"
+#include "access/skey.h"
+#include "catalog/pg_type.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/rangetypes.h"
+
+Datum spg_range_quad_config(PG_FUNCTION_ARGS);
+Datum spg_range_quad_choose(PG_FUNCTION_ARGS);
+Datum spg_range_quad_picksplit(PG_FUNCTION_ARGS);
+Datum spg_range_quad_inner_consistent(PG_FUNCTION_ARGS);
+Datum spg_range_quad_leaf_consistent(PG_FUNCTION_ARGS);
+
+static int16 getQuadrant(TypeCacheEntry *typcache, RangeType *centroid,
+			RangeType *tst);
+static int bound_cmp(const void *a, const void *b, void *arg);
+
+/*
+ * SP-GiST 'config' interface function.
+ */
+Datum
+spg_range_quad_config(PG_FUNCTION_ARGS)
+{
+	/* spgConfigIn *cfgin = (spgConfigIn *) PG_GETARG_POINTER(0); */
+	spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1);
+
+	cfg->prefixType = ANYRANGEOID;
+	cfg->labelType = VOIDOID;	/* we don't need node labels */
+	cfg->canReturnData = true;
+	cfg->longValuesOK = false;
+	PG_RETURN_VOID();
+}
+
+/*----------
+ * Determine which quadrant a 2d-mapped range falls into, relative to the
+ * centroid.
+ *
+ * Lower bound of range is mapped to the horizontal axis and upper bound to
+ * the vertical axis. Quadrants are numbered like this:
+ *
+ *	 4	|  1
+ *	----+-----
+ *	 3	|  2
+ *
+ * Ranges on one of the axes are taken to lie in the quadrant with higher value
+ * along perpendicular axis. Range equal to centroid is taken to lie in
+ * quadrant 1. Empty ranges are taken to lie in quadrant 5.
+ *----------
+ */
+static int16
+getQuadrant(TypeCacheEntry *typcache, RangeType *centroid, RangeType *tst)
+{
+	RangeBound	centroidLower,
+				centroidUpper;
+	bool		centroidEmpty;
+	RangeBound	lower,
+				upper;
+	bool		empty;
+
+	range_deserialize(typcache, centroid, &centroidLower, &centroidUpper,
+					  &centroidEmpty);
+	range_deserialize(typcache, tst, &lower, &upper, &empty);
+
+	if (empty)
+		return 5;
+
+	if (range_cmp_bounds(typcache, &lower, &centroidLower) >= 0)
+	{
+		if (range_cmp_bounds(typcache, &upper, &centroidUpper) >= 0)
+			return 1;
+		else
+			return 2;
+	}
+	else
+	{
+		if (range_cmp_bounds(typcache, &upper, &centroidUpper) >= 0)
+			return 4;
+		else
+			return 3;
+	}
+}
+
+/*
+ * Choose SP-GiST function: choose path for addition of new range.
+ */
+Datum
+spg_range_quad_choose(PG_FUNCTION_ARGS)
+{
+	spgChooseIn	   *in = (spgChooseIn *) PG_GETARG_POINTER(0);
+	spgChooseOut   *out = (spgChooseOut *) PG_GETARG_POINTER(1);
+	RangeType	   *inRange = DatumGetRangeType(in->datum), *centroid;
+	int16			quadrant;
+	TypeCacheEntry *typcache;
+
+	if (in->allTheSame)
+	{
+		out->resultType = spgMatchNode;
+		/* nodeN will be set by core */
+		out->result.matchNode.levelAdd = 0;
+		out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+		PG_RETURN_VOID();
+	}
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(inRange));
+
+	/*
+	 * Absence of prefix datum divides ranges by empty sign. All empty ranges
+	 * are taken into node 0, all non-empty ranges are taken into node 1.
+	 */
+	if (!in->hasPrefix)
+	{
+		out->resultType = spgMatchNode;
+		if (RangeIsEmpty(inRange))
+			out->result.matchNode.nodeN = 0;
+		else
+			out->result.matchNode.nodeN = 1;
+		out->result.matchNode.levelAdd = 1;
+		out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+		PG_RETURN_VOID();
+	}
+
+	centroid = DatumGetRangeType(in->prefixDatum);
+	quadrant = getQuadrant(typcache, centroid, inRange);
+
+	Assert(quadrant <= in->nNodes);
+
+	/* Select node matching to quadrant number */
+	out->resultType = spgMatchNode;
+	out->result.matchNode.nodeN = quadrant - 1;
+	out->result.matchNode.levelAdd = 1;
+	out->result.matchNode.restDatum = RangeTypeGetDatum(inRange);
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * Bound comparison for sorting.
+ */
+static int
+bound_cmp(const void *a, const void *b, void *arg)
+{
+	RangeBound *ba = (RangeBound *) a;
+	RangeBound *bb = (RangeBound *) b;
+	TypeCacheEntry *typcache = (TypeCacheEntry *)arg;
+
+	return range_cmp_bounds(typcache, ba, bb);
+}
+
+/*
+ * Picksplit SP-GiST function: split ranges into nodes. Select "centroid"
+ * range and distribute ranges according to quadrants.
+ */
+Datum
+spg_range_quad_picksplit(PG_FUNCTION_ARGS)
+{
+	spgPickSplitIn *in = (spgPickSplitIn *) PG_GETARG_POINTER(0);
+	spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1);
+	int			i;
+	int			j;
+	int			nonEmptyCount;
+	RangeType  *centroid;
+	bool		empty;
+	TypeCacheEntry *typcache;
+
+	/* Use the median values of lower and upper bounds as the centroid range */
+	RangeBound	   *lowerBounds,
+				   *upperBounds;
+
+	typcache = range_get_typcache(fcinfo,
+							RangeTypeGetOid(DatumGetRangeType(in->datums[0])));
+
+	/* Allocate memory for bounds */
+	lowerBounds = palloc(sizeof(RangeBound) * in->nTuples);
+	upperBounds = palloc(sizeof(RangeBound) * in->nTuples);
+	j = 0;
+
+	/* Deserialize bounds of ranges, count non-empty ranges */
+	for (i = 0; i < in->nTuples; i++)
+	{
+		range_deserialize(typcache, DatumGetRangeType(in->datums[i]),
+									&lowerBounds[j], &upperBounds[j], &empty);
+		if (!empty)
+			j++;
+	}
+	nonEmptyCount = j;
+
+	/*
+	 * All the ranges are empty. The best we can do is put all ranges
+	 * into node 0. Non-empty range will be routed to node 1.
+	 */
+	if (nonEmptyCount == 0)
+	{
+		out->nNodes = 2;
+		out->hasPrefix = false;
+		/* Prefix is empty */
+		out->prefixDatum = PointerGetDatum(NULL);
+		out->nodeLabels = NULL;
+
+		out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+		out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+
+		/* Place all ranges into node 0 */
+		for (i = 0; i < in->nTuples; i++)
+		{
+			RangeType	   *range = DatumGetRangeType(in->datums[i]);
+
+			out->leafTupleDatums[i] = RangeTypeGetDatum(range);
+			out->mapTuplesToNodes[i] = 0;
+		}
+		PG_RETURN_VOID();
+	}
+
+	/* Sort range bounds in order to find medians */
+	qsort_arg(lowerBounds, nonEmptyCount, sizeof(RangeBound),
+			  bound_cmp, typcache);
+	qsort_arg(upperBounds, nonEmptyCount, sizeof(RangeBound),
+			  bound_cmp, typcache);
+
+	/* Construct "centroid" range from medians of lower and upper bounds */
+	centroid = range_serialize(typcache, &lowerBounds[nonEmptyCount / 2],
+							   &upperBounds[nonEmptyCount / 2], false);
+
+	out->hasPrefix = true;
+	out->prefixDatum = RangeTypeGetDatum(centroid);
+
+	/* Create node for empty ranges only if it is a root node */
+	out->nNodes = (in->level == 0) ? 5 : 4;
+	out->nodeLabels = NULL;		/* we don't need node labels */
+
+	out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples);
+	out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples);
+
+	/*
+	 * Assign ranges to corresponding nodes according to quadrants relative to
+	 * "centroid" range.
+	 */
+	for (i = 0; i < in->nTuples; i++)
+	{
+		RangeType	   *range = DatumGetRangeType(in->datums[i]);
+		int16			quadrant = getQuadrant(typcache, centroid, range);
+
+		out->leafTupleDatums[i] = RangeTypeGetDatum(range);
+		out->mapTuplesToNodes[i] = quadrant - 1;
+	}
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * SP-GiST consistent function for inner nodes: check which nodes are
+ * consistent with given set of queries.
+ */
+Datum
+spg_range_quad_inner_consistent(PG_FUNCTION_ARGS)
+{
+	spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0);
+	spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
+	int			which;
+	int			i;
+	bool		needPrevious = false;
+
+	if (in->allTheSame)
+	{
+		/* Report that all nodes should be visited */
+		out->nNodes = in->nNodes;
+		out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
+		for (i = 0; i < in->nNodes; i++)
+			out->nodeNumbers[i] = i;
+		PG_RETURN_VOID();
+	}
+
+	if (!in->hasPrefix)
+	{
+		/*
+		 * Empty "centroid". We can use only information about emptiness of
+		 * ranges in nodes.
+		 */
+		Assert(in->nNodes == 2);
+
+		/*
+		 * Nth bit of which variable means that (N - 1)th node should be
+		 * visited. Initially all bits are set. Bits of nodes which should be
+		 * skipped will be unset.
+		 */
+		which = (1 << 1) | (1 << 2);
+		for (i = 0; i < in->nkeys; i++)
+		{
+			StrategyNumber strategy;
+			bool empty;
+
+			strategy = in->scankeys[i].sk_strategy;
+
+			/*
+			 * The only strategy when second argument of operator is not
+			 * range is RANGESTRAT_CONTAINS_ELEM.
+			 */
+			if (strategy != RANGESTRAT_CONTAINS_ELEM)
+				empty = RangeIsEmpty(
+								DatumGetRangeType(in->scankeys[i].sk_argument));
+
+			switch (strategy)
+			{
+				/* These strategies return false if any argument is empty */
+				case RANGESTRAT_BEFORE:
+				case RANGESTRAT_OVERLEFT:
+				case RANGESTRAT_OVERLAPS:
+				case RANGESTRAT_OVERRIGHT:
+				case RANGESTRAT_AFTER:
+				case RANGESTRAT_ADJACENT:
+					if (empty)
+						which = 0;
+					else
+						which &= (1 << 2);
+					break;
+				/*
+				 * "Empty" range is contained in any range. Non-empty ranges
+				 * can be contained only in non-empty ranges.
+				 */
+				case RANGESTRAT_CONTAINS:
+					if (!empty)
+						which &= (1 << 2);
+					break;
+				case RANGESTRAT_CONTAINED_BY:
+					if (empty)
+						which &= (1 << 1);
+					break;
+				/* Empty range can't contain any element */
+				case RANGESTRAT_CONTAINS_ELEM:
+					which &= (1 << 2);
+					break;
+				case RANGESTRAT_EQ:
+					if (empty)
+						which &= (1 << 1);
+					else
+						which &= (1 << 2);
+					break;
+				default:
+					elog(ERROR, "unrecognized range strategy: %d", strategy);
+					break;
+			}
+			if (which == 0)
+				break;			/* no need to consider remaining conditions */
+		}
+	}
+	else
+	{
+		RangeBound		centroidLower, centroidUpper;
+		bool			centroidEmpty;
+		TypeCacheEntry *typcache;
+		RangeType	   *centroid;
+
+		/* This node has a centroid. Fetch it. */
+		centroid = DatumGetRangeType(in->prefixDatum);
+		typcache = range_get_typcache(fcinfo,
+								RangeTypeGetOid(DatumGetRangeType(centroid)));
+		range_deserialize(typcache, centroid, &centroidLower, &centroidUpper,
+						  &centroidEmpty);
+
+		Assert(in->nNodes == 4 || in->nNodes == 5);
+
+		/*
+		 * Nth bit of which variable means that (N - 1)th node (Nth quadrant)
+		 * should be visited. Initially all bits are set. Bits of nodes which
+		 * can be skipped will be unset.
+		 */
+		which = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5);
+
+		for (i = 0; i < in->nkeys; i++)
+		{
+			StrategyNumber strategy;
+			RangeBound	lower, upper;
+			bool		empty;
+			RangeType  *range = NULL;
+
+			strategy = in->scankeys[i].sk_strategy;
+
+			/*
+			 * RANGESTRAT_CONTAINS_ELEM is just like RANGESTRAT_CONTAINS,
+			 * but the argument is a single element. Expand the single element
+			 * to a range containing only the element, and treat it like
+			 * RANGESTRAT_CONTAINS.
+			 */
+			if (strategy == RANGESTRAT_CONTAINS_ELEM)
+			{
+				lower.inclusive = true;
+				lower.infinite = false;
+				lower.lower = true;
+				lower.val = in->scankeys[i].sk_argument;
+
+				upper.inclusive = true;
+				upper.infinite = false;
+				upper.lower = false;
+				upper.val = in->scankeys[i].sk_argument;
+
+				empty = false;
+
+				strategy = RANGESTRAT_CONTAINS;
+			}
+			else
+			{
+				range = DatumGetRangeType(in->scankeys[i].sk_argument);
+				range_deserialize(typcache, range, &lower, &upper, &empty);
+			}
+
+			switch (strategy)
+			{
+				int			which1, which2;
+
+				/*
+				 * Range A is before range B if upper bound of A is lower than
+				 * lower bound of B. The geometrical interpretation is that
+				 * the lower bound of the scan key (B) specifies a horizontal
+				 * line, and points below the line match the query. If the
+				 * centroid lies above (or on) the line, then all points above
+				 * or equal to the centroid, in quadrants 1 and 4, are also
+				 * above the line and cannot match.
+				 */
+				case RANGESTRAT_BEFORE:
+					if (empty)
+						which = 0; /* nothing can be before 'empty' */
+					else if (range_cmp_bounds(typcache, &centroidUpper,
+											  &lower) >= 0)
+						which &= (1 << 2) | (1 << 3);
+					else
+						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+					break;
+
+				/*
+				 * Range A is overleft to range B if upper bound of A is less
+				 * or equal to upper bound of B. The geometrical interpretation
+				 * is that the upper bound of the scan key (B) specifies a
+				 * horizontal line, and points below or on the line match the
+				 * query.
+				 */
+				case RANGESTRAT_OVERLEFT:
+					if (empty)
+						which = 0;
+					/*
+					 * If the centroid is above the scan key point, then any
+					 * points above or equal to the centroid (in quadrants 1
+					 * and 4) must also be above the scan key point and cannot
+					 * match.
+					 */
+					 else if (range_cmp_bounds(typcache, &centroidUpper,
+											  &upper) > 0)
+						which = (1 << 2) | (1 << 3);
+					else
+						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+					break;
+
+				/*
+				 * Non-empty ranges overlap, if lower bound of each range is
+				 * lower or equal to upper bound of the other range. The
+				 * geometrical interpretation is that the scan key specifies
+				 * a point, so that the scan key's upper bound is the X
+				 * coordinate, and lower bound the Y coordinate. Note that this
+				 * is reverse to usual mapping. Everything to the left and
+				 * above (or equal) the scan key point match.
+				 */
+				case RANGESTRAT_OVERLAPS:
+					if (empty)
+						which = 0;
+					else
+					{
+						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+
+						/*
+						 * If centroid is to the right of the scan key point,
+						 * then nothing right of (or equal to) the centroid can
+						 * match.
+						 */
+						if (range_cmp_bounds(typcache, &centroidLower,
+											 &upper) > 0)
+							which &= (1 << 3) | (1 << 4);
+
+						/*
+						 * If centroid is below the scan key point, then
+						 * nothing below the centroid can match.
+						 */
+						if (range_cmp_bounds(typcache, &centroidUpper,
+											 &lower) <= 0)
+							which &= (1 << 1) | (1 << 4);
+					}
+					break;
+
+				/*
+				 * Range A is overright to range B if lower bound of A is
+				 * greater or equal to lower bound of B. The geometrical
+				 * interpretation is that the lower bound of the scan key (B)
+				 * forms a vertical line, and points at or to the right of the
+				 * line match.
+				 */
+				case RANGESTRAT_OVERRIGHT:
+					if (empty)
+						which = 0;
+					/*
+					 * If the centroid is to the left or equal to the
+					 * scan key line, then any points to the left of the
+					 * centroid (in quadrants 3 and 4) are to the left of
+					 * the scan key point, and cannot match.
+					 */
+					else if (range_cmp_bounds(typcache, &centroidLower,
+											  &lower) <= 0)
+						which = (1 << 1) | (1 << 2);
+					else
+						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+					break;
+
+				/*
+				 * Range A is after range B if lower bound of A is greater than
+				 * upper bound of B. The geometric interpretation is that upper
+				 * bound of the scan key (B) forms a vertical line, and any
+				 * points to the right of the line match.
+				 */
+				case RANGESTRAT_AFTER:
+					if (empty)
+						which = 0;
+					/*
+					 * If the centroid is to the left of or equal to the scan
+					 * key line, then anything to the left of the centroid
+					 * (in quadrants 3 and 4) must also be to the left of the
+					 * line and cannot match.
+					 */
+					else if (range_cmp_bounds(typcache, &centroidLower,
+											  &upper) <= 0)
+						which &= (1 << 1) | (1 << 2);
+					else
+						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+					break;
+
+				/*
+				 * Ranges are adjacent if lower bound of one range is adjacent
+				 * to upper bound of another range.
+				 */
+				case RANGESTRAT_ADJACENT:
+					if (empty)
+					{
+						which = 0;
+						break;
+					}
+
+					lower.lower = false;
+					lower.inclusive = !lower.inclusive;
+					upper.lower = true;
+					upper.inclusive = !upper.inclusive;
+
+					/*
+					 * which1 is bitmask for possibility to be adjacent with
+					 * lower bound of argument. which2 is bitmask for
+					 * possibility to be adjacent with upper bound of
+					 * argument.
+					 */
+					which1 = which2 = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+
+					/* Deserialize previous centroid range if present. */
+					if (in->reconstructedValue != (Datum) 0)
+					{
+						RangeType  *prevCentroid;
+						RangeBound	prevLower, prevUpper;
+						bool		prevEmpty;
+						int			cmp1, cmp3;
+
+						prevCentroid = DatumGetRangeType(in->reconstructedValue);
+						range_deserialize(typcache, prevCentroid, &prevLower,
+										  &prevUpper, &prevEmpty);
+
+						/* Do comparison with previous centroid */
+						cmp1 = range_cmp_bounds(typcache, &upper, &prevLower);
+						cmp3 = range_cmp_bounds(typcache, &centroidLower,
+												&prevLower);
+
+						/*
+						 * Check if lower bound of argument is not in
+						 * a quadrant we visit in previous step.
+						 */
+						if ((cmp3 < 0 && cmp1 > 0) || (cmp3 > 0 && cmp1 < 0))
+							which1 = 0;
+
+						/* Do comparison with previous centroid */
+						cmp1 = range_cmp_bounds(typcache, &lower, &prevUpper);
+						cmp3 = range_cmp_bounds(typcache, &centroidUpper, &prevUpper);
+						/*
+						 * Check if upper bound of argument is not in
+						 * a quadrant we visit in previous step.
+						 */
+						if ((cmp3 < 0 && cmp1 > 0) || (cmp3 > 0 && cmp1 < 0))
+							which2 = 0;
+					}
+
+					if (which1)
+					{
+						if (range_cmp_bounds(typcache, &upper, &centroidLower) >= 0)
+							which1 &= (1 << 1) | (1 << 2);
+						else if (!bounds_adjacent(typcache, &centroidLower, &upper))
+							which1 &= (1 << 3) | (1 << 4);
+					}
+
+					if (which2)
+					{
+						int cmp2 = range_cmp_bounds(typcache, &lower, &centroidUpper);
+						if (cmp2 > 0)
+							which2 &= (1 << 1) | (1 << 4);
+						else if (cmp2 < 0)
+							which2 &= (1 << 2) | (1 << 3);
+					}
+
+					which &= which1 | which2;
+
+					needPrevious = true;
+					break;
+
+				/*
+				 * Non-empty range A contains non-empty range B if lower bound
+				 * of A is lower or equal to lower bound of range B and upper
+				 * bound of range A is greater or equal to upper bound of range
+				 * A.
+				 */
+				case RANGESTRAT_CONTAINS:
+					if (!empty)
+					{
+						which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+						/*
+						 * If lower bound of centroid is greater than lower
+						 * bound of argument then no ranges which contain
+						 * argument can be in quadrants 1 and 2.
+						 */
+						if (range_cmp_bounds(typcache, &centroidLower,
+																	&lower) > 0)
+							which &= (1 << 3) | (1 << 4);
+						/*
+						 * If upper bound of centroid is lower or equal to upper
+						 * bound of argument then no ranges which contain
+						 * argument can be in quadrants 2 and 3.
+						 */
+						if (range_cmp_bounds(typcache, &centroidUpper,
+																   &upper) <= 0)
+							which &= (1 << 1) | (1 << 4);
+					}
+					break;
+
+				case RANGESTRAT_CONTAINED_BY:
+					if (empty)
+						which = (1 << 5);
+					else
+					{
+						/*
+						 * If lower bound of centroid is lower or equal to lower
+						 * bound of argument then no ranges which are contained
+						 * in argument can be in quadrants 3 and 4.
+						 */
+						if (range_cmp_bounds(typcache, &centroidLower,
+																   &lower) <= 0)
+							which &= (1 << 1) | (1 << 2) | (1 << 5);
+						/*
+						 * If upper bound of centroid is greater than upper
+						 * bound of argument then no ranges which are contained
+						 * in argument can be in quadrants 1 and 4.
+						 */
+						if (range_cmp_bounds(typcache, &centroidUpper,
+																	&upper) > 0)
+							which &= (1 << 2) | (1 << 3) | (1 << 5);
+					}
+					break;
+
+				/*
+				 * Equal range can be only in the same quadrant where argument
+				 * would be placed to.
+				 */
+				case RANGESTRAT_EQ:
+					which &= (1 << getQuadrant(typcache, centroid, range));
+					break;
+
+				default:
+					elog(ERROR, "unrecognized range strategy: %d", strategy);
+					break;
+			}
+
+			if (which == 0)
+				break;			/* no need to consider remaining conditions */
+		}
+	}
+
+	/* We must descend into the quadrant(s) identified by 'which' */
+	out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
+	if (needPrevious)
+		out->reconstructedValues = (Datum *) palloc(sizeof(Datum) * in->nNodes);
+	out->nNodes = 0;
+	for (i = 1; i <= in->nNodes; i++)
+	{
+		if (which & (1 << i))
+		{
+			/* Save previous prefix if needed */
+			if (needPrevious)
+				out->reconstructedValues[out->nNodes] = in->prefixDatum;
+			out->nodeNumbers[out->nNodes++] = i - 1;
+		}
+	}
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * SP-GiST consistent function for leaf nodes: check leaf value against query
+ * using corresponding function.
+ */
+Datum
+spg_range_quad_leaf_consistent(PG_FUNCTION_ARGS)
+{
+	spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0);
+	spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1);
+	RangeType  *leafRange = DatumGetRangeType(in->leafDatum);
+	TypeCacheEntry *typcache;
+	bool		res;
+	int			i;
+
+	/* all tests are exact */
+	out->recheck = false;
+
+	/* leafDatum is what it is... */
+	out->leafValue = in->leafDatum;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(leafRange));
+
+	/* Perform the required comparison(s) */
+	res = true;
+	for (i = 0; i < in->nkeys; i++)
+	{
+		Datum	keyDatum = in->scankeys[i].sk_argument;
+
+		/* Call the function corresponding to the scan strategy */
+		switch (in->scankeys[i].sk_strategy)
+		{
+			case RANGESTRAT_BEFORE:
+				res = range_before_internal(typcache, leafRange,
+											DatumGetRangeType(keyDatum));
+				break;
+			case RANGESTRAT_OVERLEFT:
+				res = range_overleft_internal(typcache, leafRange,
+											  DatumGetRangeType(keyDatum));
+				break;
+			case RANGESTRAT_OVERLAPS:
+				res = range_overlaps_internal(typcache, leafRange,
+											  DatumGetRangeType(keyDatum));
+				break;
+			case RANGESTRAT_OVERRIGHT:
+				res = range_overright_internal(typcache, leafRange,
+											   DatumGetRangeType(keyDatum));
+				break;
+			case RANGESTRAT_AFTER:
+				res = range_after_internal(typcache, leafRange,
+										   DatumGetRangeType(keyDatum));
+				break;
+			case RANGESTRAT_ADJACENT:
+				res = range_adjacent_internal(typcache, leafRange,
+											  DatumGetRangeType(keyDatum));
+				break;
+			case RANGESTRAT_CONTAINS:
+				res = range_contains_internal(typcache, leafRange,
+											  DatumGetRangeType(keyDatum));
+				break;
+			case RANGESTRAT_CONTAINED_BY:
+				res = range_contained_by_internal(typcache, leafRange,
+												  DatumGetRangeType(keyDatum));
+				break;
+			case RANGESTRAT_CONTAINS_ELEM:
+				res = range_contains_elem_internal(typcache, leafRange,
+												   keyDatum);
+				break;
+			case RANGESTRAT_EQ:
+				res = range_eq_internal(typcache, leafRange,
+										DatumGetRangeType(keyDatum));
+				break;
+			default:
+				elog(ERROR, "unrecognized range strategy: %d",
+					 in->scankeys[i].sk_strategy);
+				break;
+		}
+
+		/*
+		 * If leaf datum doesn't match to a query key, no need to check
+		 * subsequent keys.
+		 */
+		if (!res)
+			break;
+	}
+
+	PG_RETURN_BOOL(res);
+}
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index ee6ac8f..74db745 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -767,4 +767,18 @@ DATA(insert (	4017   25 25 12 s	665 4000 0 ));
 DATA(insert (	4017   25 25 14 s	667 4000 0 ));
 DATA(insert (	4017   25 25 15 s	666 4000 0 ));
 
+/*
+ * SP-GiST range_ops
+ */
+DATA(insert (	3474   3831 3831 1 s	3893 4000 0 ));
+DATA(insert (	3474   3831 3831 2 s	3895 4000 0 ));
+DATA(insert (	3474   3831 3831 3 s	3888 4000 0 ));
+DATA(insert (	3474   3831 3831 4 s	3896 4000 0 ));
+DATA(insert (	3474   3831 3831 5 s	3894 4000 0 ));
+DATA(insert (	3474   3831 3831 6 s	3897 4000 0 ));
+DATA(insert (	3474   3831 3831 7 s	3890 4000 0 ));
+DATA(insert (	3474   3831 3831 8 s	3892 4000 0 ));
+DATA(insert (	3474   3831 2283 16 s	3889 4000 0 ));
+DATA(insert (	3474   3831 3831 18 s	3882 4000 0 ));
+
 #endif   /* PG_AMOP_H */
diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h
index 99d4676..984f1ec 100644
--- a/src/include/catalog/pg_amproc.h
+++ b/src/include/catalog/pg_amproc.h
@@ -373,5 +373,10 @@ DATA(insert (	4017   25 25 2 4028 ));
 DATA(insert (	4017   25 25 3 4029 ));
 DATA(insert (	4017   25 25 4 4030 ));
 DATA(insert (	4017   25 25 5 4031 ));
+DATA(insert (	3474   3831 3831 1 3469 ));
+DATA(insert (	3474   3831 3831 2 3470 ));
+DATA(insert (	3474   3831 3831 3 3471 ));
+DATA(insert (	3474   3831 3831 4 3472 ));
+DATA(insert (	3474   3831 3831 5 3473 ));
 
 #endif   /* PG_AMPROC_H */
diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h
index 638f808..2ed98d5 100644
--- a/src/include/catalog/pg_opclass.h
+++ b/src/include/catalog/pg_opclass.h
@@ -223,6 +223,7 @@ DATA(insert (	783		tsquery_ops			PGNSP PGUID 3702  3615 t 20 ));
 DATA(insert (	403		range_ops			PGNSP PGUID 3901  3831 t 0 ));
 DATA(insert (	405		range_ops			PGNSP PGUID 3903  3831 t 0 ));
 DATA(insert (	783		range_ops			PGNSP PGUID 3919  3831 t 0 ));
+DATA(insert (	4000	range_ops			PGNSP PGUID 3474  3831 t 0 ));
 DATA(insert (	4000	quad_point_ops		PGNSP PGUID 4015  600 t 0 ));
 DATA(insert (	4000	kd_point_ops		PGNSP PGUID 4016  600 f 0 ));
 DATA(insert (	4000	text_ops			PGNSP PGUID 4017  25 t 0 ));
diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h
index 41ebccc..b1340ca 100644
--- a/src/include/catalog/pg_opfamily.h
+++ b/src/include/catalog/pg_opfamily.h
@@ -142,6 +142,7 @@ DATA(insert OID = 3702 (	783		tsquery_ops		PGNSP PGUID ));
 DATA(insert OID = 3901 (	403		range_ops		PGNSP PGUID ));
 DATA(insert OID = 3903 (	405		range_ops		PGNSP PGUID ));
 DATA(insert OID = 3919 (	783		range_ops		PGNSP PGUID ));
+DATA(insert OID = 3474 (	4000	range_ops		PGNSP PGUID ));
 DATA(insert OID = 4015 (	4000	quad_point_ops	PGNSP PGUID ));
 DATA(insert OID = 4016 (	4000	kd_point_ops	PGNSP PGUID ));
 DATA(insert OID = 4017 (	4000	text_ops		PGNSP PGUID ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 665918f..51449a4 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4653,6 +4653,17 @@ DESCR("SP-GiST support for suffix tree over text");
 DATA(insert OID = 4031 (  spg_text_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_text_leaf_consistent _null_ _null_ _null_ ));
 DESCR("SP-GiST support for suffix tree over text");
 
+DATA(insert OID = 3469 (  spg_range_quad_config	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_config _null_ _null_ _null_ ));
+DESCR("SP-GiST support for quad tree over range");
+DATA(insert OID = 3470 (  spg_range_quad_choose	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_choose _null_ _null_ _null_ ));
+DESCR("SP-GiST support for quad tree over range");
+DATA(insert OID = 3471 (  spg_range_quad_picksplit	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_picksplit _null_ _null_ _null_ ));
+DESCR("SP-GiST support for quad tree over range");
+DATA(insert OID = 3472 (  spg_range_quad_inner_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_inner_consistent _null_ _null_ _null_ ));
+DESCR("SP-GiST support for quad tree over range");
+DATA(insert OID = 3473 (  spg_range_quad_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_  spg_range_quad_leaf_consistent _null_ _null_ _null_ ));
+DESCR("SP-GiST support for quad tree over range");
+
 
 /*
  * Symbolic values for provolatile column: these indicate whether the result
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index a37401c..992054d 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -75,6 +75,19 @@ typedef struct
 #define PG_GETARG_RANGE_COPY(n)		DatumGetRangeTypeCopy(PG_GETARG_DATUM(n))
 #define PG_RETURN_RANGE(x)			return RangeTypeGetDatum(x)
 
+/* Operator strategy numbers used in the GiST range opclass */
+/* Numbers are chosen to match up operator names with existing usages */
+#define RANGESTRAT_BEFORE				1
+#define RANGESTRAT_OVERLEFT				2
+#define RANGESTRAT_OVERLAPS				3
+#define RANGESTRAT_OVERRIGHT			4
+#define RANGESTRAT_AFTER				5
+#define RANGESTRAT_ADJACENT				6
+#define RANGESTRAT_CONTAINS				7
+#define RANGESTRAT_CONTAINED_BY			8
+#define RANGESTRAT_CONTAINS_ELEM		16
+#define RANGESTRAT_EQ					18
+
 /*
  * prototypes for functions defined in rangetypes.c
  */
@@ -188,6 +201,8 @@ extern int range_cmp_bounds(TypeCacheEntry *typcache, RangeBound *b1,
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, RangeBound *b1,
 					   RangeBound *b2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound *lower,
+						RangeBound *upper);
 
 /* GiST support (in rangetypes_gist.c) */
 extern Datum range_gist_consistent(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 110ea41..256b719 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1068,12 +1068,17 @@ ORDER BY 1, 2, 3;
        2742 |            4 | =
        4000 |            1 | <<
        4000 |            1 | ~<~
+       4000 |            2 | &<
        4000 |            2 | ~<=~
+       4000 |            3 | &&
        4000 |            3 | =
+       4000 |            4 | &>
        4000 |            4 | ~>=~
        4000 |            5 | >>
        4000 |            5 | ~>~
+       4000 |            6 | -|-
        4000 |            6 | ~=
+       4000 |            7 | @>
        4000 |            8 | <@
        4000 |           10 | <^
        4000 |           11 | <
@@ -1081,7 +1086,9 @@ ORDER BY 1, 2, 3;
        4000 |           12 | <=
        4000 |           14 | >=
        4000 |           15 | >
-(55 rows)
+       4000 |           16 | @>
+       4000 |           18 | =
+(62 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/rangetypes.out b/src/test/regress/expected/rangetypes.out
index e37fab3..1023c4e 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -821,6 +821,225 @@ select count(*) from test_range_gist where ir -|- int4range(100,500);
      5
 (1 row)
 
+-- test SP-GiST index that's been built incrementally
+create table test_range_spgist(ir int4range);
+create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+insert into test_range_spgist select int4range(g, g+10000) from generate_series(1,1000) g;
+insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+insert into test_range_spgist select int4range(NULL,g*10,'(]') from generate_series(1,100) g;
+insert into test_range_spgist select int4range(g*10,NULL,'(]') from generate_series(1,100) g;
+insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+-- first, verify non-indexed results
+SET enable_seqscan    = t;
+SET enable_indexscan  = f;
+SET enable_bitmapscan = f;
+select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ count 
+-------
+  6200
+(1 row)
+
+select count(*) from test_range_spgist where ir = int4range(10,20);
+ count 
+-------
+     2
+(1 row)
+
+select count(*) from test_range_spgist where ir @> 10;
+ count 
+-------
+   130
+(1 row)
+
+select count(*) from test_range_spgist where ir @> int4range(10,20);
+ count 
+-------
+   111
+(1 row)
+
+select count(*) from test_range_spgist where ir && int4range(10,20);
+ count 
+-------
+   158
+(1 row)
+
+select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ count 
+-------
+  1062
+(1 row)
+
+select count(*) from test_range_spgist where ir << int4range(100,500);
+ count 
+-------
+   189
+(1 row)
+
+select count(*) from test_range_spgist where ir >> int4range(100,500);
+ count 
+-------
+  3554
+(1 row)
+
+select count(*) from test_range_spgist where ir &< int4range(100,500);
+ count 
+-------
+  1029
+(1 row)
+
+select count(*) from test_range_spgist where ir &> int4range(100,500);
+ count 
+-------
+  4794
+(1 row)
+
+select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ count 
+-------
+     5
+(1 row)
+
+-- now check same queries using index
+SET enable_seqscan    = f;
+SET enable_indexscan  = t;
+SET enable_bitmapscan = f;
+select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ count 
+-------
+  6200
+(1 row)
+
+select count(*) from test_range_spgist where ir = int4range(10,20);
+ count 
+-------
+     2
+(1 row)
+
+select count(*) from test_range_spgist where ir @> 10;
+ count 
+-------
+   130
+(1 row)
+
+select count(*) from test_range_spgist where ir @> int4range(10,20);
+ count 
+-------
+   111
+(1 row)
+
+select count(*) from test_range_spgist where ir && int4range(10,20);
+ count 
+-------
+   158
+(1 row)
+
+select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ count 
+-------
+  1062
+(1 row)
+
+select count(*) from test_range_spgist where ir << int4range(100,500);
+ count 
+-------
+   189
+(1 row)
+
+select count(*) from test_range_spgist where ir >> int4range(100,500);
+ count 
+-------
+  3554
+(1 row)
+
+select count(*) from test_range_spgist where ir &< int4range(100,500);
+ count 
+-------
+  1029
+(1 row)
+
+select count(*) from test_range_spgist where ir &> int4range(100,500);
+ count 
+-------
+  4794
+(1 row)
+
+select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ count 
+-------
+     5
+(1 row)
+
+-- now check same queries using a bulk-loaded index
+drop index test_range_spgist_idx;
+create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+ count 
+-------
+  6200
+(1 row)
+
+select count(*) from test_range_spgist where ir = int4range(10,20);
+ count 
+-------
+     2
+(1 row)
+
+select count(*) from test_range_spgist where ir @> 10;
+ count 
+-------
+   130
+(1 row)
+
+select count(*) from test_range_spgist where ir @> int4range(10,20);
+ count 
+-------
+   111
+(1 row)
+
+select count(*) from test_range_spgist where ir && int4range(10,20);
+ count 
+-------
+   158
+(1 row)
+
+select count(*) from test_range_spgist where ir <@ int4range(10,50);
+ count 
+-------
+  1062
+(1 row)
+
+select count(*) from test_range_spgist where ir << int4range(100,500);
+ count 
+-------
+   189
+(1 row)
+
+select count(*) from test_range_spgist where ir >> int4range(100,500);
+ count 
+-------
+  3554
+(1 row)
+
+select count(*) from test_range_spgist where ir &< int4range(100,500);
+ count 
+-------
+  1029
+(1 row)
+
+select count(*) from test_range_spgist where ir &> int4range(100,500);
+ count 
+-------
+  4794
+(1 row)
+
+select count(*) from test_range_spgist where ir -|- int4range(100,500);
+ count 
+-------
+     5
+(1 row)
+
 RESET enable_seqscan;
 RESET enable_indexscan;
 RESET enable_bitmapscan;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d4b3361..3f04442 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -157,6 +157,7 @@ SELECT relname, relhasindex
  tenk2                   | t
  test_range_excl         | t
  test_range_gist         | t
+ test_range_spgist       | t
  test_tsvector           | f
  text_tbl                | f
  time_tbl                | f
@@ -165,7 +166,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(154 rows)
+(155 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/output/misc.source b/src/test/regress/output/misc.source
index 979ed33..4353d0b 100644
--- a/src/test/regress/output/misc.source
+++ b/src/test/regress/output/misc.source
@@ -675,6 +675,7 @@ SELECT user_relns() AS user_relns
  tenk2
  test_range_excl
  test_range_gist
+ test_range_spgist
  test_tsvector
  text_tbl
  time_tbl
@@ -685,7 +686,7 @@ SELECT user_relns() AS user_relns
  toyemp
  varchar_tbl
  xacttest
-(107 rows)
+(108 rows)
 
 SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer')));
  name 
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 08d6976..035fcec 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -220,6 +220,68 @@ select count(*) from test_range_gist where ir &< int4range(100,500);
 select count(*) from test_range_gist where ir &> int4range(100,500);
 select count(*) from test_range_gist where ir -|- int4range(100,500);
 
+-- test SP-GiST index that's been built incrementally
+create table test_range_spgist(ir int4range);
+create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+
+insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+insert into test_range_spgist select int4range(g, g+10000) from generate_series(1,1000) g;
+insert into test_range_spgist select 'empty'::int4range from generate_series(1,500) g;
+insert into test_range_spgist select int4range(NULL,g*10,'(]') from generate_series(1,100) g;
+insert into test_range_spgist select int4range(g*10,NULL,'(]') from generate_series(1,100) g;
+insert into test_range_spgist select int4range(g, g+10) from generate_series(1,2000) g;
+
+-- first, verify non-indexed results
+SET enable_seqscan    = t;
+SET enable_indexscan  = f;
+SET enable_bitmapscan = f;
+
+select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+select count(*) from test_range_spgist where ir = int4range(10,20);
+select count(*) from test_range_spgist where ir @> 10;
+select count(*) from test_range_spgist where ir @> int4range(10,20);
+select count(*) from test_range_spgist where ir && int4range(10,20);
+select count(*) from test_range_spgist where ir <@ int4range(10,50);
+select count(*) from test_range_spgist where ir << int4range(100,500);
+select count(*) from test_range_spgist where ir >> int4range(100,500);
+select count(*) from test_range_spgist where ir &< int4range(100,500);
+select count(*) from test_range_spgist where ir &> int4range(100,500);
+select count(*) from test_range_spgist where ir -|- int4range(100,500);
+
+-- now check same queries using index
+SET enable_seqscan    = f;
+SET enable_indexscan  = t;
+SET enable_bitmapscan = f;
+
+select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+select count(*) from test_range_spgist where ir = int4range(10,20);
+select count(*) from test_range_spgist where ir @> 10;
+select count(*) from test_range_spgist where ir @> int4range(10,20);
+select count(*) from test_range_spgist where ir && int4range(10,20);
+select count(*) from test_range_spgist where ir <@ int4range(10,50);
+select count(*) from test_range_spgist where ir << int4range(100,500);
+select count(*) from test_range_spgist where ir >> int4range(100,500);
+select count(*) from test_range_spgist where ir &< int4range(100,500);
+select count(*) from test_range_spgist where ir &> int4range(100,500);
+select count(*) from test_range_spgist where ir -|- int4range(100,500);
+
+-- now check same queries using a bulk-loaded index
+drop index test_range_spgist_idx;
+create index test_range_spgist_idx on test_range_spgist using spgist (ir);
+
+select count(*) from test_range_spgist where ir @> 'empty'::int4range;
+select count(*) from test_range_spgist where ir = int4range(10,20);
+select count(*) from test_range_spgist where ir @> 10;
+select count(*) from test_range_spgist where ir @> int4range(10,20);
+select count(*) from test_range_spgist where ir && int4range(10,20);
+select count(*) from test_range_spgist where ir <@ int4range(10,50);
+select count(*) from test_range_spgist where ir << int4range(100,500);
+select count(*) from test_range_spgist where ir >> int4range(100,500);
+select count(*) from test_range_spgist where ir &< int4range(100,500);
+select count(*) from test_range_spgist where ir &> int4range(100,500);
+select count(*) from test_range_spgist where ir -|- int4range(100,500);
+
 RESET enable_seqscan;
 RESET enable_indexscan;
 RESET enable_bitmapscan;
#20Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#19)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:

Also, I wonder if we really need to reconstruct the "previous" value in
a RANGESTRAT_ADJACENT search. ISTM we only need to remember which of the
two lines we are chasing. For example, if you descend to quadrant 2
because there might be a point there that lies on the horizontal line,
but we already know that there can't be any points there lie on the
vertical line, you only need to remember that, not the whole centroid
from the previous level. Does the SP-GiST API require the
"reconstructed" values stored by inner_consistent to be of the correct
datatype, or can it store any Datums in the array?

They have to match the attribute type, at least as to storage details
(typbyval/typlen), because the core uses datumCopy to copy them around.

We could possibly extend the API to allow a different type to be used
for this, but then it wouldn't be "reconstructed data" in any sense of
the word; so I think it'd be abuse of the concept --- which would come
back to bite us if we ever try to support index-only scans with SPGiST.
ISTM what this points up is that the opclass might want some private
state kept around during a tree descent. If we want to support that,
we should support it as a separate concept from reconstructed data.

regards, tom lane

#21Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Tom Lane (#20)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On 29.07.2012 00:50, Tom Lane wrote:

Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:

Also, I wonder if we really need to reconstruct the "previous" value in
a RANGESTRAT_ADJACENT search. ISTM we only need to remember which of the
two lines we are chasing. For example, if you descend to quadrant 2
because there might be a point there that lies on the horizontal line,
but we already know that there can't be any points there lie on the
vertical line, you only need to remember that, not the whole centroid
from the previous level. Does the SP-GiST API require the
"reconstructed" values stored by inner_consistent to be of the correct
datatype, or can it store any Datums in the array?

They have to match the attribute type, at least as to storage details
(typbyval/typlen), because the core uses datumCopy to copy them around.

We could possibly extend the API to allow a different type to be used
for this, but then it wouldn't be "reconstructed data" in any sense of
the word; so I think it'd be abuse of the concept --- which would come
back to bite us if we ever try to support index-only scans with SPGiST.

I can see that for leaf nodes, but does that also hold for inner nodes?

ISTM what this points up is that the opclass might want some private
state kept around during a tree descent. If we want to support that,
we should support it as a separate concept from reconstructed data.

Yeah, that seems better. The representation of an inner node is
datatype-specific, there should be no need to expose "reconstructed"
inner node values outside a datatype's SP-GiST implementation.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#22Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#21)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:

On 29.07.2012 00:50, Tom Lane wrote:

We could possibly extend the API to allow a different type to be used
for this, but then it wouldn't be "reconstructed data" in any sense of
the word; so I think it'd be abuse of the concept --- which would come
back to bite us if we ever try to support index-only scans with SPGiST.

I can see that for leaf nodes, but does that also hold for inner nodes?

I didn't explain myself terribly well, probably. Consider an opclass
that wants some private state like this and *also* needs to reconstruct
column data.

In principle I suppose we could do away with the reconstructed-data
support altogether, and consider that if you need that then it is just a
portion of the unspecified private state the opclass is holding. But
it's probably a bit late to remove bits of the opclass API.

regards, tom lane

#23Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Alexander Korotkov (#18)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

Just to check where we stand on this: Are you going to send a finalized
version of this patch, based on the one I sent earlier, or should I pick
up that version and try to get it into committable state?

On 23.07.2012 10:37, Alexander Korotkov wrote:

On Fri, Jul 20, 2012 at 3:48 PM, Heikki Linnakangas<
heikki.linnakangas@enterprisedb.com> wrote:

On 13.07.2012 02:00, Alexander Korotkov wrote:

Done. There are separate patch for get rid of TrickFunctionCall2 and
version of SP-GiST for ranges based on that patch.

Looking at the SP-GiST patch now..

It would be nice to have an introduction, perhaps as a file comment at the
top of rangetypes_spgist.c, explaining how the quad tree works. I have a
general idea of what a quad tree is, but it's not immediately obvious how
it maps to SP-GiST. What is stored on a leaf node and an internal node?
What is the 'prefix' (seems to be the centroid)? How are ranges mapped to
2D points? (the function comment of getQuadrant() is a good start for that
last one)

I've added some comments at the top of rangetypes_spgist.c.

In spg_range_quad_inner_**consistent(), if in->hasPrefix == true, ISTM that

in all cases where 'empty' is true, 'which' is set to 0, meaning that there
can be no matches in any of the quadrants. In most of the case-branches,
you explicitly check for 'empty', but even in the ones where you don't, I
think you end up setting which=0 if empty==true. I'm not 100% sure about
the RANGESTRAT_ADJACENT case, though. Am I missing something?

Ops., it was a bug: RANGESTRAT_ADJACENT shoud set which=0 if empty==true,
while RANGESTRAT_CONTAINS and RANGESTRAT_CONTAINED_BY not. Corrected.

It would be nice to avoid the code duplication between the new

bounds_adjacent() function, and the range_adjacent_internal(). Perhaps move
bounds_adjacent() to rangetypes.c and use it in range_adjacent_internal()
too?

Done.

------
With best regards,
Alexander Korotkov.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#24Alexander Korotkov
aekorotkov@gmail.com
In reply to: Alexander Korotkov (#1)
1 attachment(s)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

In this revision of patch I tried to handle conditions more generally using
variables minLower, maxLower, minUpper, maxUpper, inclusive and
strictEmpty. However some strategies still contain additional logic.
What is our conclusion about saving previous choice for RANGESTRAT_ADJACENT
strategy?

------
With best regards,
Alexander Korotkov.

Attachments:

range_spgist_quad-0.7.patch.gzapplication/x-gzip; name=range_spgist_quad-0.7.patch.gzDownload
#25Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Alexander Korotkov (#24)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On 09.08.2012 18:42, Alexander Korotkov wrote:

In this revision of patch I tried to handle conditions more generally using
variables minLower, maxLower, minUpper, maxUpper, inclusive and
strictEmpty. However some strategies still contain additional logic.

Thanks, that clarified the code tremendously. The comments I added about
the geometrical interpretations of the operations earlier seem
unnecessary now, so removed those.

What is our conclusion about saving previous choice for RANGESTRAT_ADJACENT
strategy?

I think we're going to do what you did in the patch. A more generic
mechanism for holding private state across consistent calls would be
nice, but it's not that ugly the way you wrote it.

I committed the patch now, but left out the support for adjacent for
now. Not because there was necessarily anything wrong with that, but
because I have limited time for reviewing, and the rest of the patch
looks ready for commit now. I reworded the comments quite a lot, you
might want to proofread those to double-check that they're still
correct. I'll take a look at the adjacent-support next, as a separate patch.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#26Alexander Korotkov
aekorotkov@gmail.com
In reply to: Heikki Linnakangas (#25)
1 attachment(s)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Thu, Aug 16, 2012 at 3:46 PM, Heikki Linnakangas <
heikki.linnakangas@enterprisedb.com> wrote:

I committed the patch now, but left out the support for adjacent for now.
Not because there was necessarily anything wrong with that, but because I
have limited time for reviewing, and the rest of the patch looks ready for
commit now. I reworded the comments quite a lot, you might want to
proofread those to double-check that they're still correct. I'll take a
look at the adjacent-support next, as a separate patch.

Thanks! There is a separate patch for adjacent. I've reworked adjacent
check in order to make it more clear.

------
With best regards,
Alexander Korotkov.

Attachments:

range_spgist_adjacent-0.1.patchapplication/octet-stream; name=range_spgist_adjacent-0.1.patchDownload
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
new file mode 100644
index fe9e0c4..cf3627b
*** a/src/backend/utils/adt/rangetypes.c
--- b/src/backend/utils/adt/rangetypes.c
*************** range_after(PG_FUNCTION_ARGS)
*** 709,714 ****
--- 709,761 ----
  	PG_RETURN_BOOL(range_after_internal(typcache, r1, r2));
  }
  
+ /*
+  * Check if two bounds A and B are "adjacent", i.e. each subtype value satisfy
+  * to strictly one of those bounds: there are no values which satisfy both
+  * bounds and there are no values between the bounds. For discrete ranges, we
+  * have to rely on the canonicalization function to normalize A..B to empty if
+  * it contains no elements of the subtype.  (If there is no canonicalization
+  * function, it's impossible for such a range to normalize to empty, so we
+  * needn't bother to try.)
+  *
+  * If A == B, the ranges are adjacent only if these bounds have different
+  * inclusive flags (i.e., exactly one of the ranges includes the common
+  * boundary point).
+  *
+  * And if A > B then the ranges cannot be adjacent in this order.
+  */
+ bool
+ bounds_adjacent(TypeCacheEntry *typcache, RangeBound *lower, RangeBound *upper)
+ {
+ 	int cmp = range_cmp_bound_values(typcache, upper, lower);
+ 	if (cmp < 0)
+ 	{
+ 		RangeType *r;
+ 		/* in a continuous subtype, there are assumed to be points between */
+ 		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
+ 			return false;
+ 		/* flip the inclusion flags */
+ 		upper->inclusive = !upper->inclusive;
+ 		lower->inclusive = !lower->inclusive;
+ 		/* change upper/lower labels to avoid Assert failures */
+ 		upper->lower = true;
+ 		lower->lower = false;
+ 		r = make_range(typcache, upper, lower, false);
+ 		/* turn labels back */
+ 		upper->lower = false;
+ 		lower->lower = true;
+ 		PG_RETURN_BOOL(RangeIsEmpty(r));
+ 	}
+ 	else if (cmp == 0)
+ 	{
+ 		PG_RETURN_BOOL(upper->inclusive != lower->inclusive);
+ 	}
+ 	else
+ 	{
+ 		PG_RETURN_BOOL(false);
+ 	}
+ }
+ 
  /* adjacent to (but not overlapping)? (internal version) */
  bool
  range_adjacent_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
*************** range_adjacent_internal(TypeCacheEntry *
*** 719,726 ****
  				upper2;
  	bool		empty1,
  				empty2;
- 	RangeType  *r3;
- 	int			cmp;
  
  	/* Different types should be prevented by ANYRANGE matching rules */
  	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
--- 766,771 ----
*************** range_adjacent_internal(TypeCacheEntry *
*** 734,795 ****
  		return false;
  
  	/*
! 	 * Given two ranges A..B and C..D, where B < C, the ranges are adjacent if
! 	 * and only if the range B..C is empty, where inclusivity of these two
! 	 * bounds is inverted compared to the original bounds.	For discrete
! 	 * ranges, we have to rely on the canonicalization function to normalize
! 	 * B..C to empty if it contains no elements of the subtype.  (If there is
! 	 * no canonicalization function, it's impossible for such a range to
! 	 * normalize to empty, so we needn't bother to try.)
! 	 *
! 	 * If B == C, the ranges are adjacent only if these bounds have different
! 	 * inclusive flags (i.e., exactly one of the ranges includes the common
! 	 * boundary point).
! 	 *
! 	 * And if B > C then the ranges cannot be adjacent in this order, but we
! 	 * must consider the other order (i.e., check D <= A).
  	 */
! 	cmp = range_cmp_bound_values(typcache, &upper1, &lower2);
! 	if (cmp < 0)
! 	{
! 		/* in a continuous subtype, there are assumed to be points between */
! 		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
! 			return (false);
! 		/* flip the inclusion flags */
! 		upper1.inclusive = !upper1.inclusive;
! 		lower2.inclusive = !lower2.inclusive;
! 		/* change upper/lower labels to avoid Assert failures */
! 		upper1.lower = true;
! 		lower2.lower = false;
! 		r3 = make_range(typcache, &upper1, &lower2, false);
! 		return RangeIsEmpty(r3);
! 	}
! 	if (cmp == 0)
! 	{
! 		return (upper1.inclusive != lower2.inclusive);
! 	}
! 
! 	cmp = range_cmp_bound_values(typcache, &upper2, &lower1);
! 	if (cmp < 0)
! 	{
! 		/* in a continuous subtype, there are assumed to be points between */
! 		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
! 			return (false);
! 		/* flip the inclusion flags */
! 		upper2.inclusive = !upper2.inclusive;
! 		lower1.inclusive = !lower1.inclusive;
! 		/* change upper/lower labels to avoid Assert failures */
! 		upper2.lower = true;
! 		lower1.lower = false;
! 		r3 = make_range(typcache, &upper2, &lower1, false);
! 		return RangeIsEmpty(r3);
! 	}
! 	if (cmp == 0)
! 	{
! 		return (upper2.inclusive != lower1.inclusive);
! 	}
! 
! 	return false;
  }
  
  /* adjacent to (but not overlapping)? */
--- 779,790 ----
  		return false;
  
  	/*
! 	 * Given two ranges A..B and C..D, the ranges are adjacent if and only if
! 	 * the pair of B and C bounds is adjacent or pair of D and A bounds is
! 	 * adjacent.
  	 */
! 	return (bounds_adjacent(typcache, &lower2, &upper1) ||
! 			bounds_adjacent(typcache, &lower1, &upper2));
  }
  
  /* adjacent to (but not overlapping)? */
diff --git a/src/backend/utils/adt/rangetypes_spgist.c b/src/backend/utils/adt/rangetypes_spgist.c
new file mode 100644
index b69fbfc..9e56ae1
*** a/src/backend/utils/adt/rangetypes_spgist.c
--- b/src/backend/utils/adt/rangetypes_spgist.c
*************** spg_range_quad_inner_consistent(PG_FUNCT
*** 304,309 ****
--- 304,310 ----
  	spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1);
  	int			which;
  	int			i;
+ 	bool		needPrevious = false;
  
  	if (in->allTheSame)
  	{
*************** spg_range_quad_inner_consistent(PG_FUNCT
*** 351,356 ****
--- 352,358 ----
  				case RANGESTRAT_OVERLAPS:
  				case RANGESTRAT_OVERRIGHT:
  				case RANGESTRAT_AFTER:
+ 				case RANGESTRAT_ADJACENT:
  					/* These strategies return false if any argument is empty */
  					if (empty)
  						which = 0;
*************** spg_range_quad_inner_consistent(PG_FUNCT
*** 479,484 ****
--- 481,488 ----
  			 */
  			switch (strategy)
  			{
+ 				int cmp, which1, which2;
+ 
  				case RANGESTRAT_BEFORE:
  					/*
  					 * Range A is before range B if upper bound of A is lower
*************** spg_range_quad_inner_consistent(PG_FUNCT
*** 522,527 ****
--- 526,670 ----
  					inclusive = false;
  					break;
  
+ 				case RANGESTRAT_ADJACENT:
+ 					/*
+ 					 * Skip to strictEmpty check.
+ 					 */
+ 					if (empty)
+ 						break;
+ 
+ 					/*
+ 					 * Inverse bounds of arguments for comparison with
+ 					 * opposite bounds.
+ 					 */
+ 					lower.lower = false;
+ 					lower.inclusive = !lower.inclusive;
+ 					upper.lower = true;
+ 					upper.inclusive = !upper.inclusive;
+ 
+ 					/*
+ 					 * which1 is bitmask for possibility to be adjacent with
+ 					 * lower bound of argument. which2 is bitmask for
+ 					 * possibility to be adjacent with upper bound of
+ 					 * argument.
+ 					 */
+ 					which1 = which2 = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+ 
+ 					/*
+ 					 * Previously selected quadrant could exclude possibility
+ 					 * for lower or upper bounds to be adjacent. Deserialize
+ 					 * previous centroid range if present for checking this.
+ 					 */
+ 					if (in->reconstructedValue != (Datum) 0)
+ 					{
+ 						RangeType  *prevCentroid;
+ 						RangeBound	prevLower,
+ 									prevUpper;
+ 						bool		prevEmpty;
+ 						int			cmp1, cmp2;
+ 
+ 						prevCentroid = DatumGetRangeType(in->reconstructedValue);
+ 						range_deserialize(typcache, prevCentroid, &prevLower,
+ 							&prevUpper, &prevEmpty);
+ 
+ 						/*
+ 						 * Check if lower bound of argument is not in
+ 						 * a quadrant we visit in previous step.
+ 						 */
+ 						cmp1 = range_cmp_bounds(typcache, &upper, &prevLower);
+ 						cmp2 = range_cmp_bounds(typcache, &centroidLower,
+ 																	&prevLower);
+ 						if ((cmp2 < 0 && cmp1 > 0) || (cmp2 > 0 && cmp1 < 0))
+ 							which1 = 0;
+ 
+ 						/*
+ 						 * Check if upper bound of argument is not in
+ 						 * a quadrant we visit in previous step.
+ 						 */
+ 						cmp1 = range_cmp_bounds(typcache, &lower, &prevUpper);
+ 						cmp2 = range_cmp_bounds(typcache, &centroidUpper,
+ 																	&prevUpper);
+ 						if ((cmp2 < 0 && cmp1 > 0) || (cmp2 > 0 && cmp1 < 0))
+ 							which2 = 0;
+ 					}
+ 
+ 					if (which1)
+ 					{
+ 						cmp = range_cmp_bounds(typcache, &centroidLower,
+ 																		&upper);
+ 						if (cmp <= 0)
+ 						{
+ 							/*
+ 							 * If the centroid's lower bound is less or equal
+ 							 * to argument's upper bound then any bound from
+ 							 * 3rd or 4th quadrant is less than centroid's
+ 							 * lower and also less than argument's upper bound.
+ 							 * Thus, even in corner case, centroid's lower
+ 							 * bound is adjacent to argument's upper bound and
+ 							 * any bound from 3rd or 4th quadrant is even
+ 							 * less and can't match.
+ 							 */
+ 							which1 &= (1 << 1) | (1 << 2);
+ 						}
+ 						else if (!bounds_adjacent(typcache, &centroidLower,
+ 																		&upper))
+ 						{
+ 							/*
+ 							 * If the centroid's lower bound is greater than
+ 							 * argument's upper bound then any bound from
+ 							 * 1st or 2nd quadrant is greater or equal to
+ 							 * centroid's lower and greater than argument's
+ 							 * upper bound. But, in corner case, centroid's
+ 							 * lower bound is adjacent to argument's upper bound
+ 							 * and bound from 1st or 2nd quadrant is equal to
+ 							 * it. Thus, we can exclude 1st and 2nd quardrants
+ 							 * only if centroid's lower bound and argument's
+ 							 * upper bound aren't adjacent.
+ 							 */
+ 							which1 &= (1 << 3) | (1 << 4);
+ 						}
+ 					}
+ 
+ 					if (which2)
+ 					{
+ 						cmp = range_cmp_bounds(typcache, &centroidUpper,
+ 																		&lower);
+ 						if (cmp <= 0)
+ 						{
+ 							/*
+ 							 * If the centroid's upper bound is less or equal
+ 							 * than argument's lower bound then any bound from
+ 							 * 2nd or 3rd quadrant is less than centroid's
+ 							 * upper bound. Thus, even in corner case,
+ 							 * centroid's upper bound is equal to argument's
+ 							 * lower bound, and any bound from 2nd or 3rd
+ 							 * quadrant is even less and can't match.
+ 							 */
+ 							which2 &= (1 << 1) | (1 << 4);
+ 						}
+ 						else if (bounds_adjacent(typcache, &lower,
+ 																&centroidUpper))
+ 						{
+ 							/*
+ 							 * If the centroid's upper bound is greater than
+ 							 * argument's lower bound then any bound from
+ 							 * 1st or 4th quadrant is greater or equal to
+ 							 * centroid's upper bound. Thus, in corner case,
+ 							 * centroid's upper bound is adjacent to argument's
+ 							 * lower bound, and bound from 1st or 4th quadrant
+ 							 * is equal to it. Thus, we can exclude 1st and 4th
+ 							 * quardrants only if centroid's upper bound and
+ 							 * argument's lower bound aren't adjacent.							 *
+ 							 */
+ 							which2 &= (1 << 2) | (1 << 3);
+ 						}
+ 					}
+ 
+ 					which &= which1 | which2;
+ 
+ 					needPrevious = true;
+ 					break;
+ 
  				case RANGESTRAT_CONTAINS:
  					/*
  					 * Non-empty range A contains non-empty range B if lower
*************** spg_range_quad_inner_consistent(PG_FUNCT
*** 652,662 ****
--- 795,812 ----
  
  	/* We must descend into the quadrant(s) identified by 'which' */
  	out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes);
+ 	if (needPrevious)
+ 		out->reconstructedValues = (Datum *) palloc(sizeof(Datum) * in->nNodes);
  	out->nNodes = 0;
  	for (i = 1; i <= in->nNodes; i++)
  	{
  		if (which & (1 << i))
+ 		{
+ 			/* Save previous prefix if needed */
+ 			if (needPrevious)
+ 				out->reconstructedValues[out->nNodes] = in->prefixDatum;
  			out->nodeNumbers[out->nNodes++] = i - 1;
+ 		}
  	}
  
  	PG_RETURN_VOID();
*************** spg_range_quad_leaf_consistent(PG_FUNCTI
*** 713,718 ****
--- 863,872 ----
  				res = range_after_internal(typcache, leafRange,
  										   DatumGetRangeType(keyDatum));
  				break;
+ 			case RANGESTRAT_ADJACENT:
+ 				res = range_adjacent_internal(typcache, leafRange,
+ 										   DatumGetRangeType(keyDatum));
+ 				break;
  			case RANGESTRAT_CONTAINS:
  				res = range_contains_internal(typcache, leafRange,
  											  DatumGetRangeType(keyDatum));
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
new file mode 100644
index aeee87e..74db745
*** a/src/include/catalog/pg_amop.h
--- b/src/include/catalog/pg_amop.h
*************** DATA(insert (	3474   3831 3831 2 s	3895
*** 775,780 ****
--- 775,781 ----
  DATA(insert (	3474   3831 3831 3 s	3888 4000 0 ));
  DATA(insert (	3474   3831 3831 4 s	3896 4000 0 ));
  DATA(insert (	3474   3831 3831 5 s	3894 4000 0 ));
+ DATA(insert (	3474   3831 3831 6 s	3897 4000 0 ));
  DATA(insert (	3474   3831 3831 7 s	3890 4000 0 ));
  DATA(insert (	3474   3831 3831 8 s	3892 4000 0 ));
  DATA(insert (	3474   3831 2283 16 s	3889 4000 0 ));
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
new file mode 100644
index fb0b23b..b6f900b
*** a/src/include/utils/rangetypes.h
--- b/src/include/utils/rangetypes.h
*************** extern int range_cmp_bounds(TypeCacheEnt
*** 201,206 ****
--- 201,208 ----
  extern int range_cmp_bound_values(TypeCacheEntry *typcache, RangeBound *b1,
  					   RangeBound *b2);
  extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+ extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound *lower,
+ 						RangeBound *upper);
  
  /* GiST support (in rangetypes_gist.c) */
  extern Datum range_gist_consistent(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
new file mode 100644
index 6faaf42..256b719
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
*************** ORDER BY 1, 2, 3;
*** 1076,1081 ****
--- 1076,1082 ----
         4000 |            4 | ~>=~
         4000 |            5 | >>
         4000 |            5 | ~>~
+        4000 |            6 | -|-
         4000 |            6 | ~=
         4000 |            7 | @>
         4000 |            8 | <@
*************** ORDER BY 1, 2, 3;
*** 1087,1093 ****
         4000 |           15 | >
         4000 |           16 | @>
         4000 |           18 | =
! (61 rows)
  
  -- Check that all opclass search operators have selectivity estimators.
  -- This is not absolutely required, but it seems a reasonable thing
--- 1088,1094 ----
         4000 |           15 | >
         4000 |           16 | @>
         4000 |           18 | =
! (62 rows)
  
  -- Check that all opclass search operators have selectivity estimators.
  -- This is not absolutely required, but it seems a reasonable thing
#27Jeff Davis
pgsql@j-davis.com
In reply to: Alexander Korotkov (#26)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Sat, 2012-08-18 at 18:10 +0400, Alexander Korotkov wrote:

On Thu, Aug 16, 2012 at 3:46 PM, Heikki Linnakangas
<heikki.linnakangas@enterprisedb.com> wrote:
I committed the patch now, but left out the support for
adjacent for now. Not because there was necessarily anything
wrong with that, but because I have limited time for
reviewing, and the rest of the patch looks ready for commit
now. I reworded the comments quite a lot, you might want to
proofread those to double-check that they're still correct.
I'll take a look at the adjacent-support next, as a separate
patch.

Thanks! There is a separate patch for adjacent. I've reworked adjacent
check in order to make it more clear.

I am taking a look at this patch now. A few quick comments:

* It looks like bounds_adjacent modifies it's by-reference arguments,
which is a little worrying to me. The lower/upper labels are flipped
back, but the inclusivities are not. Maybe just pass by value instead?

* Bounds_adjacent is sensitive to the argument order. Can't it just take
bound1 and bound2?

* I tried some larger tests and they seemed to work. I haven't reviewed
the spgist code changes in detail though.

Regards,
Jeff Davis

#28Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#20)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Sat, 2012-07-28 at 17:50 -0400, Tom Lane wrote:

which would come
back to bite us if we ever try to support index-only scans with SPGiST.

I'm confused:

http://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=92203624934095163f8b57b5b3d7bbd2645da2c8

And the patch that was just committed for Range Types SP-GiST is already
using index-only scans.

Regards,
Jeff Davis

#29Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeff Davis (#28)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

Jeff Davis <pgsql@j-davis.com> writes:

On Sat, 2012-07-28 at 17:50 -0400, Tom Lane wrote:

which would come
back to bite us if we ever try to support index-only scans with SPGiST.

I'm confused:
http://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=92203624934095163f8b57b5b3d7bbd2645da2c8

Sorry, I was being imprecise there. What I meant was that an opclass
that abused the reconstructed-value storage for something else might
have problems supporting index-only scans.

If we think opclasses might need private storage for index searches, we
should add that as a new part of the API, not tell them to misuse this
part.

regards, tom lane

#30Alexander Korotkov
aekorotkov@gmail.com
In reply to: Jeff Davis (#27)
1 attachment(s)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Mon, Aug 20, 2012 at 12:25 AM, Jeff Davis <pgsql@j-davis.com> wrote:

I am taking a look at this patch now. A few quick comments:

* It looks like bounds_adjacent modifies it's by-reference arguments,
which is a little worrying to me. The lower/upper labels are flipped
back, but the inclusivities are not. Maybe just pass by value instead?

* Bounds_adjacent is sensitive to the argument order. Can't it just take
bound1 and bound2?

Fixed. Patch is attached.

------
With best regards,
Alexander Korotkov.

Attachments:

range_spgist_adjacent-0.2.patch.gzapplication/x-gzip; name=range_spgist_adjacent-0.2.patch.gzDownload
#31Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Jeff Davis (#27)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

Jeff Davis escribió:

On Sat, 2012-08-18 at 18:10 +0400, Alexander Korotkov wrote:

Thanks! There is a separate patch for adjacent. I've reworked adjacent
check in order to make it more clear.

I am taking a look at this patch now. A few quick comments:

* I tried some larger tests and they seemed to work. I haven't reviewed
the spgist code changes in detail though.

Jeff, Heikki,

Any input on the subsequent version of this patch?

--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#32Jeff Davis
pgsql@j-davis.com
In reply to: Alexander Korotkov (#30)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Tue, 2012-09-04 at 17:45 +0400, Alexander Korotkov wrote:

On Mon, Aug 20, 2012 at 12:25 AM, Jeff Davis <pgsql@j-davis.com>
wrote:
I am taking a look at this patch now. A few quick comments:

* It looks like bounds_adjacent modifies it's by-reference
arguments,
which is a little worrying to me. The lower/upper labels are
flipped
back, but the inclusivities are not. Maybe just pass by value
instead?

* Bounds_adjacent is sensitive to the argument order. Can't it
just take
bound1 and bound2?

Fixed. Patch is attached.

It looks like this is basically the same diff as v0.1. Did something get
mixed up?

Regards,
Jeff Davis

#33Alexander Korotkov
aekorotkov@gmail.com
In reply to: Jeff Davis (#32)
1 attachment(s)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Sun, Oct 21, 2012 at 11:22 AM, Jeff Davis <pgsql@j-davis.com> wrote:

On Tue, 2012-09-04 at 17:45 +0400, Alexander Korotkov wrote:

On Mon, Aug 20, 2012 at 12:25 AM, Jeff Davis <pgsql@j-davis.com>
wrote:
I am taking a look at this patch now. A few quick comments:

* It looks like bounds_adjacent modifies it's by-reference
arguments,
which is a little worrying to me. The lower/upper labels are
flipped
back, but the inclusivities are not. Maybe just pass by value
instead?

* Bounds_adjacent is sensitive to the argument order. Can't it
just take
bound1 and bound2?

Fixed. Patch is attached.

It looks like this is basically the same diff as v0.1. Did something get
mixed up?

Right version of patch is attached.

------
With best regards,
Alexander Korotkov.

Attachments:

range_spgist_adjacent-0.3.patch.gzapplication/x-gzip; name=range_spgist_adjacent-0.3.patch.gzDownload
#34Jeff Davis
pgsql@j-davis.com
In reply to: Alexander Korotkov (#33)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Fri, 2012-11-02 at 12:47 +0400, Alexander Korotkov wrote:

Right version of patch is attached.

* In bounds_adjacent, there's no reason to flip the labels back.
* Comment should indicate more explicitly that bounds_adjacent is
sensitive to the argument order.
* In bounds_adjacent, it appears that "bound1" corresponds to "B" in the
comment above, and "bound2" corresponds to "A" in the comment above. I
would have guessed from reading the comment that bound1 corresponded to
A. We should just use consistent names between the comment and the code
(e.g. boundA and boundB).
* I could be getting confused, but I think that line 645 of
rangetypes_spgist.c should be inverted (!bounds_adjacent(...)).
* I think needPrevious should have an explanatory comment somewhere. It
looks like you are using it to store some state as you descend the tree,
but it doesn't look like it's used to reconstruct the value (because we
already have the value anyway). Since it's being used for a purpose
other than what's intended, that should be explained.

Regards,
Jeff Davis

#35Andres Freund
andres@2ndquadrant.com
In reply to: Jeff Davis (#34)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

Hi Alexander,

On 2012-11-04 11:41:48 -0800, Jeff Davis wrote:

On Fri, 2012-11-02 at 12:47 +0400, Alexander Korotkov wrote:

Right version of patch is attached.

* In bounds_adjacent, there's no reason to flip the labels back.
* Comment should indicate more explicitly that bounds_adjacent is
sensitive to the argument order.
* In bounds_adjacent, it appears that "bound1" corresponds to "B" in the
comment above, and "bound2" corresponds to "A" in the comment above. I
would have guessed from reading the comment that bound1 corresponded to
A. We should just use consistent names between the comment and the code
(e.g. boundA and boundB).
* I could be getting confused, but I think that line 645 of
rangetypes_spgist.c should be inverted (!bounds_adjacent(...)).
* I think needPrevious should have an explanatory comment somewhere. It
looks like you are using it to store some state as you descend the tree,
but it doesn't look like it's used to reconstruct the value (because we
already have the value anyway). Since it's being used for a purpose
other than what's intended, that should be explained.

Do you have time to address these comments or should the patch marked
as returned with Feedback? Afaics there hasn't been progress since
CF2...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

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

#36Alexander Korotkov
aekorotkov@gmail.com
In reply to: Jeff Davis (#34)
1 attachment(s)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

Hi!

On Sun, Nov 4, 2012 at 11:41 PM, Jeff Davis <pgsql@j-davis.com> wrote:

On Fri, 2012-11-02 at 12:47 +0400, Alexander Korotkov wrote:

Right version of patch is attached.

* In bounds_adjacent, there's no reason to flip the labels back.

Fixed.

* Comment should indicate more explicitly that bounds_adjacent is
sensitive to the argument order.

Fixed.

* In bounds_adjacent, it appears that "bound1" corresponds to "B" in the
comment above, and "bound2" corresponds to "A" in the comment above. I
would have guessed from reading the comment that bound1 corresponded to
A. We should just use consistent names between the comment and the code
(e.g. boundA and boundB).

Fixed.

* I could be getting confused, but I think that line 645 of
rangetypes_spgist.c should be inverted (!bounds_adjacent(...)).

Good catch! Actually, code was in direct contradiction with comment. Fixed.

* I think needPrevious should have an explanatory comment somewhere. It
looks like you are using it to store some state as you descend the tree,
but it doesn't look like it's used to reconstruct the value (because we
already have the value anyway). Since it's being used for a purpose
other than what's intended, that should be explained.

Yes, it's a some kind of hack now. Comment is added.

------
With best regards,
Alexander Korotkov.

Attachments:

range_spgist_adjacent-0.4.patch.gzapplication/x-gzip; name=range_spgist_adjacent-0.4.patch.gzDownload
#37Jeff Davis
pgsql@j-davis.com
In reply to: Alexander Korotkov (#36)
1 attachment(s)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Fri, 2012-12-14 at 01:31 +0400, Alexander Korotkov wrote:

Hi!

Hi!

I have attached a patch with some significant edits.

* In your patch, there was still an inconsistency between the comment
for bounds_adjacent and the code. I refactored it to ensure it always
takes the upper bound as boundA, and the lower bound as boundB, so that
it can invert the inclusivities to create A..B to match the comments.

* In the consistent method, you were inverting upper to be a lower bound
and lower to be an upper bound. I don't understand why (perhaps I did
the first time I read the patch), so I removed it.

* It looks like the comments for which1/2 were inconsistent with the
code. I tried to fix that.

* I significantly refactored the comments and code for the consistent
method. I had trouble understanding the original comments, particularly
around the edge cases.

Please take a look and see if it still matches your algorithm properly.
This patch is not intended to be a final version (I didn't analyze my
code carefully), but just to show you what I mean and how I interpret
what the code is trying to do. You don't need to use my refactorings,
but if not, the comments in your version need more improvement so I can
understand.

I found it easier to reason in terms of horizontal and vertical lines,
and which quadrants they crossed, and then work out the edge cases. I'm
not sure if that reasoning was correct, but it seemed to make more sense
to me.

Regards,
Jeff Davis

Attachments:

range_spgist_adjacent-0.4A.patch.gzapplication/x-gzip; name=range_spgist_adjacent-0.4A.patch.gzDownload
#38Alexander Korotkov
aekorotkov@gmail.com
In reply to: Jeff Davis (#37)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

Hi!

On Mon, Dec 17, 2012 at 2:42 PM, Jeff Davis <pgsql@j-davis.com> wrote:

I have attached a patch with some significant edits.

* In your patch, there was still an inconsistency between the comment
for bounds_adjacent and the code. I refactored it to ensure it always
takes the upper bound as boundA, and the lower bound as boundB, so that
it can invert the inclusivities to create A..B to match the comments.

* In the consistent method, you were inverting upper to be a lower bound
and lower to be an upper bound. I don't understand why (perhaps I did
the first time I read the patch), so I removed it.

* It looks like the comments for which1/2 were inconsistent with the
code. I tried to fix that.

* I significantly refactored the comments and code for the consistent
method. I had trouble understanding the original comments, particularly
around the edge cases.

Please take a look and see if it still matches your algorithm properly.
This patch is not intended to be a final version (I didn't analyze my
code carefully), but just to show you what I mean and how I interpret
what the code is trying to do. You don't need to use my refactorings,
but if not, the comments in your version need more improvement so I can
understand.

I found it easier to reason in terms of horizontal and vertical lines,
and which quadrants they crossed, and then work out the edge cases. I'm
not sure if that reasoning was correct, but it seemed to make more sense
to me.

Your comments and refactoring looks good for me.

------
With best regards,
Alexander Korotkov.

#39Craig Ringer
craig@2ndquadrant.com
In reply to: Alexander Korotkov (#38)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On 02/09/2013 05:36 PM, Alexander Korotkov wrote:

Your comments and refactoring looks good for me.

This patch is currently flagged as waiting for author. Have you had a
chance to test and examine Jeff's changes in more detail? Would you
consider giving us a summary of the status of this work - do you think
you can have it production-ready within a week or two, with all
necessary tests and documentation.

Note that Jeff wrote:

Please take a look and see if it still matches your algorithm properly.
This patch is not intended to be a final version (I didn't analyze my
code carefully), but just to show you what I mean and how I interpret
what the code is trying to do.

... so it's clear that you need to read the patch in detail and make any
desired edits, re-test, and go from there.

I see a couple of changes to the regression tests. Do you need any
documentation changes for this too? Any user-visible effects other than
performance?

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#40Alexander Korotkov
aekorotkov@gmail.com
In reply to: Craig Ringer (#39)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Sun, Mar 3, 2013 at 6:37 PM, Craig Ringer <craig@2ndquadrant.com> wrote:

On 02/09/2013 05:36 PM, Alexander Korotkov wrote:

Your comments and refactoring looks good for me.

This patch is currently flagged as waiting for author. Have you had a
chance to test and examine Jeff's changes in more detail? Would you
consider giving us a summary of the status of this work - do you think you
can have it production-ready within a week or two, with all necessary tests
and documentation.

Note that Jeff wrote:

Please take a look and see if it still matches your algorithm properly.
This patch is not intended to be a final version (I didn't analyze my
code carefully), but just to show you what I mean and how I interpret
what the code is trying to do.

... so it's clear that you need to read the patch in detail and make any
desired edits, re-test, and go from there.

I see a couple of changes to the regression tests. Do you need any
documentation changes for this too? Any user-visible effects other than
performance?

This patch only adds one more operator to already committed new opclass.
Tests already cover this case. Without patch corresponding test leads to
sequential scan instead of index scan. However, I can't see any
documentation changes about already committed opclass. I think we need a
documentation update as an separate patch.
Jeff changes only does comments changes and renaming, I don't need to
examine them more.

------
With best regards,
Alexander Korotkov.

#41Craig Ringer
craig@2ndquadrant.com
In reply to: Alexander Korotkov (#40)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On 03/04/2013 01:42 AM, Alexander Korotkov wrote:

This patch only adds one more operator to already committed new
opclass. Tests already cover this case. Without patch corresponding
test leads to sequential scan instead of index scan. However, I can't
see any documentation changes about already committed opclass. I think
we need a documentation update as an separate patch.
Jeff changes only does comments changes and renaming, I don't need to
examine them more.

OK. Would it be fair to summarize that as "I think it's ready to commit
as-is" ? Do you have any outstanding concerns or uncertainties?

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

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

#42Alexander Korotkov
aekorotkov@gmail.com
In reply to: Craig Ringer (#41)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On Mon, Mar 4, 2013 at 6:53 AM, Craig Ringer <craig@2ndquadrant.com> wrote:

On 03/04/2013 01:42 AM, Alexander Korotkov wrote:

This patch only adds one more operator to already committed new
opclass. Tests already cover this case. Without patch corresponding
test leads to sequential scan instead of index scan. However, I can't
see any documentation changes about already committed opclass. I think
we need a documentation update as an separate patch.
Jeff changes only does comments changes and renaming, I don't need to
examine them more.

OK. Would it be fair to summarize that as "I think it's ready to commit
as-is" ? Do you have any outstanding concerns or uncertainties?

Yes. I think it's ready to commit as-is.

------
With best regards,
Alexander Korotkov.

#43Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Alexander Korotkov (#40)
Re: SP-GiST for ranges based on 2d-mapping and quad-tree

On 03.03.2013 19:42, Alexander Korotkov wrote:

This patch only adds one more operator to already committed new opclass.
Tests already cover this case. Without patch corresponding test leads to
sequential scan instead of index scan.

Thanks, committed with some trivial cleanup.

However, I can't see any
documentation changes about already committed opclass. I think we need a
documentation update as an separate patch.

Agreed. I think a small mention in the Range Types - Indexing section
(http://www.postgresql.org/docs/devel/static/rangetypes.html#RANGETYPES-GIST)
will do.

- Heikki

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