]> granicus.if.org Git - postgresql/commitdiff
Add SP-GiST support for range types.
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Thu, 16 Aug 2012 09:55:37 +0000 (12:55 +0300)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Thu, 16 Aug 2012 11:30:45 +0000 (14:30 +0300)
The implementation is a quad-tree, largely copied from the quad-tree
implementation for points. The lower and upper bound of ranges are the 2d
coordinates, with some extra code to handle empty ranges.

I left out the support for adjacent operator, -|-, from the original patch.
Not because there was necessarily anything wrong with it, but it was more
complicated than the other operators, and I only have limited time for
reviewing. That will follow as a separate patch.

Alexander Korotkov, reviewed by Jeff Davis and me.

15 files changed:
src/backend/utils/adt/Makefile
src/backend/utils/adt/rangetypes_gist.c
src/backend/utils/adt/rangetypes_spgist.c [new file with mode: 0644]
src/include/catalog/catversion.h
src/include/catalog/pg_amop.h
src/include/catalog/pg_amproc.h
src/include/catalog/pg_opclass.h
src/include/catalog/pg_opfamily.h
src/include/catalog/pg_proc.h
src/include/utils/rangetypes.h
src/test/regress/expected/opr_sanity.out
src/test/regress/expected/rangetypes.out
src/test/regress/expected/sanity_check.out
src/test/regress/output/misc.source
src/test/regress/sql/rangetypes.sql

index c5b0a75e931a1271d0e057ce087208b14dbed4c4..a692086bdfd1fead182ce269ad8380e40472d366 100644 (file)
@@ -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
 
index 21f0ebabc782c28633f1dae4699ee29df5011260..c278e4e7b31b764a6b289e49214cf595c7fb639b 100644 (file)
 #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 (file)
index 0000000..117e975
--- /dev/null
@@ -0,0 +1,748 @@
+/*-------------------------------------------------------------------------
+ *
+ * rangetypes_spgist.c
+ *       implementation of quad tree over ranges mapped to 2d-points for SP-GiST.
+ *
+ * Quad tree is a data structure similar to a binary tree, but is adapted to
+ * 2d data. Each inner node of a quad tree contains a point (centroid) which
+ * divides the 2d-space into 4 quadrants. Each quadrant is associated with a
+ * child node.
+ *
+ * Ranges are mapped to 2d-points so that the lower bound is one dimension,
+ * and the upper bound is another. By convention, we visualize the lower bound
+ * to be the horizontal axis, and upper bound the vertical axis.
+ *
+ * One quirk with this mapping is the handling of empty ranges. An empty range
+ * doesn't have lower and upper bounds, so it cannot be mapped to 2d space in
+ * a straightforward way. To cope with that, the root node can have a 5th
+ * quadrant, which is reserved for empty ranges. Furthermore, there can be
+ * inner nodes in the tree with no centroid. They contain only two child nodes,
+ * one for empty ranges and another for non-empty ones. Such a node can appear
+ * as the root node, or in the tree under the 5th child of the root node (in
+ * which case it will only contain empty nodes).
+ *
+ * The SP-GiST picksplit function uses medians along both axes as the centroid.
+ * This implementation only uses the comparison function of the range element
+ * datatype, therefore it works for any range type.
+ *
+ * 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"
+
+/* SP-GiST API functions */
+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.
+ *
+ * Quadrants are numbered like this:
+ *
+ *      4      |  1
+ *     ----+----
+ *      3      |  2
+ *
+ * Where the lower bound of range is the horizontal axis and upper bound the
+ * vertical axis.
+ *
+ * Ranges on one of the axes are taken to lie in the quadrant with higher value
+ * along perpendicular axis. That is, a value on the horizontal axis is taken
+ * to belong to quadrant 1 or 4, and a value on the vertical axis is taken to
+ * belong to quadrant 1 or 2. A range equal to centroid is taken to lie in
+ * quadrant 1.
+ *
+ * Empty ranges are taken to lie in the special 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));
+
+       /*
+        * A node with no centroid divides ranges purely on whether they're empty
+        * or not. All empty ranges go to child node 0, all non-empty ranges go
+        * to 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 to construct an inner
+        * node with no centroid, and put all ranges into node 0. If non-empty
+        * ranges are added later, they 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;
+
+       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)
+       {
+               /*
+                * No centroid on this inner node. Such a node has two child nodes,
+                * the first for empty ranges, and the second for non-empty ones.
+                */
+               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 = in->scankeys[i].sk_strategy;
+                       bool            empty;
+
+                       /*
+                        * 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));
+                       else
+                               empty = false;
+
+                       switch (strategy)
+                       {
+                               case RANGESTRAT_BEFORE:
+                               case RANGESTRAT_OVERLEFT:
+                               case RANGESTRAT_OVERLAPS:
+                               case RANGESTRAT_OVERRIGHT:
+                               case RANGESTRAT_AFTER:
+                                       /* These strategies return false if any argument is empty */
+                                       if (empty)
+                                               which = 0;
+                                       else
+                                               which &= (1 << 2);
+                                       break;
+
+                               case RANGESTRAT_CONTAINS:
+                                       /*
+                                        * All ranges contain an empty range. Only non-empty ranges
+                                        * can contain a non-empty range.
+                                        */
+                                       if (!empty)
+                                               which &= (1 << 2);
+                                       break;
+
+                               case RANGESTRAT_CONTAINED_BY:
+                                       /*
+                                        * Only an empty range is contained by an empty range. Both
+                                        * empty and non-empty ranges can be contained by a
+                                        * non-empty range.
+                                        */
+                                       if (empty)
+                                               which &= (1 << 1);
+                                       break;
+
+                               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;
+
+                       /* Restrictions on range bounds according to scan strategy */
+                       RangeBound *minLower = NULL,
+                                          *maxLower = NULL,
+                                          *minUpper = NULL,
+                                          *maxUpper = NULL;
+                       /* Are the restrictions on range bounds inclusive? */
+                       bool            inclusive = true;
+                       bool            strictEmpty = true;
+
+                       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);
+                       }
+
+                       /*
+                        * Most strategies are handled by forming a bounding box from the
+                        * search key, defined by a minLower, maxLower, minUpper, maxUpper.
+                        * Some modify 'which' directly, to specify exactly which quadrants
+                        * need to be visited.
+                        *
+                        * For most strategies, nothing matches an empty search key, and
+                        * an empty range never matches a non-empty key. If a strategy
+                        * does not behave like that wrt. empty ranges, set strictEmpty to
+                        * false.
+                        */
+                       switch (strategy)
+                       {
+                               case RANGESTRAT_BEFORE:
+                                       /*
+                                        * Range A is before range B if upper bound of A is lower
+                                        * than lower bound of B.
+                                        */
+                                       maxUpper = &lower;
+                                       inclusive = false;
+                                       break;
+
+                               case RANGESTRAT_OVERLEFT:
+                                       /*
+                                        * Range A is overleft to range B if upper bound of A is
+                                        * less or equal to upper bound of B.
+                                        */
+                                       maxUpper = &upper;
+                                       break;
+
+                               case RANGESTRAT_OVERLAPS:
+                                       /*
+                                        * Non-empty ranges overlap, if lower bound of each range
+                                        * is lower or equal to upper bound of the other range.
+                                        */
+                                       maxLower = &upper;
+                                       minUpper = &lower;
+                                       break;
+
+                               case RANGESTRAT_OVERRIGHT:
+                                       /*
+                                        * Range A is overright to range B if lower bound of A is
+                                        * greater or equal to lower bound of B.
+                                        */
+                                       minLower = &lower;
+                                       break;
+
+                               case RANGESTRAT_AFTER:
+                                       /*
+                                        * Range A is after range B if lower bound of A is greater
+                                        * than upper bound of B.
+                                        */
+                                       minLower = &upper;
+                                       inclusive = false;
+                                       break;
+
+                               case RANGESTRAT_CONTAINS:
+                                       /*
+                                        * 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.
+                                        *
+                                        * All non-empty ranges contain an empty range.
+                                        */
+                                       strictEmpty = false;
+                                       if (!empty)
+                                       {
+                                               which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+                                               maxLower = &lower;
+                                               minUpper = &upper;
+                                       }
+                                       break;
+
+                               case RANGESTRAT_CONTAINED_BY:
+                                       /* The opposite of contains. */
+                                       strictEmpty = false;
+                                       if (empty)
+                                       {
+                                               /* An empty range is only contained by an empty range */
+                                               which &= (1 << 5);
+                                       }
+                                       else
+                                       {
+                                               minLower = &lower;
+                                               maxUpper = &upper;
+                                       }
+                                       break;
+
+                               case RANGESTRAT_EQ:
+                                       /*
+                                        * Equal range can be only in the same quadrant where
+                                        * argument would be placed to.
+                                        */
+                                       strictEmpty = false;
+                                       which &= (1 << getQuadrant(typcache, centroid, range));
+                                       break;
+
+                               default:
+                                       elog(ERROR, "unrecognized range strategy: %d", strategy);
+                                       break;
+                       }
+
+                       if (strictEmpty)
+                       {
+                               if (empty)
+                               {
+                                       /* Scan key is empty, no branches are satisfying */
+                                       which = 0;
+                                       break;
+                               }
+                               else
+                               {
+                                       /* Shouldn't visit tree branch with empty ranges */
+                                       which &= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+                               }
+                       }
+
+                       /*
+                        * Using the bounding box, see which quadrants we have to descend
+                        * into.
+                        */
+                       if (minLower)
+                       {
+                               /*
+                                * If the centroid's lower bound is less than or equal to
+                                * the minimum lower bound, anything in the 3rd and 4th
+                                * quadrants will have an even smaller lower bound, and thus
+                                * can't match.
+                                */
+                               if (range_cmp_bounds(typcache, &centroidLower, minLower) <= 0)
+                                       which &= (1 << 1) | (1 << 2) | (1 << 5);
+                       }
+                       if (maxLower)
+                       {
+                               /*
+                                * If the centroid's lower bound is greater than the maximum
+                                * lower bound, anything in the 1st and 2nd quadrants will
+                                * also have a greater than or equal lower bound, and thus
+                                * can't match. If the centroid's lower bound is equal to
+                                * the maximum lower bound, we can still exclude the 1st and
+                                * 2nd quadrants if we're looking for a value strictly greater
+                                * than the maximum.
+                                */
+                               int                     cmp;
+
+                               cmp = range_cmp_bounds(typcache, &centroidLower, maxLower);
+                               if (cmp > 0 || (!inclusive && cmp == 0))
+                                       which &= (1 << 3) | (1 << 4) | (1 << 5);
+                       }
+                       if (minUpper)
+                       {
+                               /*
+                                * If the centroid's upper bound is less than or equal to
+                                * the minimum upper bound, anything in the 2nd and 3rd
+                                * quadrants will have an even smaller upper bound, and thus
+                                * can't match.
+                                */
+                               if (range_cmp_bounds(typcache, &centroidUpper, minUpper) <= 0)
+                                       which &= (1 << 1) | (1 << 4) | (1 << 5);
+                       }
+                       if (maxUpper)
+                       {
+                               /*
+                                * If the centroid's upper bound is greater than the maximum
+                                * upper bound, anything in the 1st and 4th quadrants will
+                                * also have a greater than or equal upper bound, and thus
+                                * can't match. If the centroid's upper bound is equal to
+                                * the maximum upper bound, we can still exclude the 1st and
+                                * 4th quadrants if we're looking for a value strictly greater
+                                * than the maximum.
+                                */
+                               int                     cmp;
+
+                               cmp = range_cmp_bounds(typcache, &centroidUpper, maxUpper);
+                               if (cmp > 0 || (!inclusive && cmp == 0))
+                                       which &= (1 << 2) | (1 << 3) | (1 << 5);
+                       }
+
+                       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);
+       out->nNodes = 0;
+       for (i = 1; i <= in->nNodes; i++)
+       {
+               if (which & (1 << i))
+                       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_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);
+}
index 82b6c0cfc71e4ee5922feb8199bc2732effe7430..da84f625203822211900c45a7b5b675b8bf170e7 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201208071
+#define CATALOG_VERSION_NO     201208161
 
 #endif
index ee6ac8fb69809a46770c81e2f3ce14e6293e1655..aeee87e7a1dfcc3709a5928bf439caf93c0e1e8b 100644 (file)
@@ -767,4 +767,17 @@ 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 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 */
index 99d4676a5de11af6a0a372b34c6ba3b59a08d6e7..984f1ecdb8c7490e6719979de2a51496ea8149c1 100644 (file)
@@ -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 */
index 638f8088c7be4e1f6fa30b514da2b416279c8f68..2ed98d5fb7363702cc5be4da0e8c648decfe6c42 100644 (file)
@@ -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 ));
index 41ebccccbc1dc67271f8d56dce83317eeaedb17f..b1340cad8aa262f66f75a320c309ce228583ad5f 100644 (file)
@@ -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 ));
index 665918f2eb75f2eca8fa27666545943638b6bada..51449a4f1c5a2a2d95aae6059be5e4cae4c18f60 100644 (file)
@@ -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
index a37401c8ec91aa9b966150a472775c6c44b90e4d..fb0b23b4be08f97dbb704886488cc87191fecbec 100644 (file)
@@ -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 and SP-GiST range opclasses */
+/* 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
  */
index 110ea4111f9337289440f40097994ee47abee7aa..6faaf4272f8fe2f64b85dd98fa20d260c79d4cbf 100644 (file)
@@ -1068,12 +1068,16 @@ 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 |            7 | @>
        4000 |            8 | <@
        4000 |           10 | <^
        4000 |           11 | <
@@ -1081,7 +1085,9 @@ ORDER BY 1, 2, 3;
        4000 |           12 | <=
        4000 |           14 | >=
        4000 |           15 | >
-(55 rows)
+       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
index e37fab3972c9255257e6ad1a0ffe0071cdef5bee..1023c4ef3e8f8086b60b77b09b051bce5ddca5ab 100644 (file)
@@ -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;
index d4b3361171af1b7d3c66f4363f5c3be3602606c9..3f04442a006ab45364ce51ed2412bcd238f54c53 100644 (file)
@@ -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
index 979ed337b29db0a30f3e28dafdd75559fedf4484..4353d0b1e3433f96e472a6e44f6e8323dffbf137 100644 (file)
@@ -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 
index 08d6976e045621955d86c24ad9a132fcb489af5c..035fceca19a1834264f6628324cc02b49b1c4d10 100644 (file)
@@ -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;