*** 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, ¢roidLower, ¢roidUpper, + ¢roidEmpty); + range_deserialize(typcache, tst, &lower, &upper, &empty); + + if (empty) + return 5; + + if (range_cmp_bounds(typcache, &lower, ¢roidLower) >= 0) + { + if (range_cmp_bounds(typcache, &upper, ¢roidUpper) >= 0) + return 1; + else + return 2; + } + else + { + if (range_cmp_bounds(typcache, &upper, ¢roidUpper) >= 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, ¢roidLower, ¢roidUpper, + ¢roidEmpty); + + 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, ¢roidUpper, + &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, ¢roidUpper, + &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, ¢roidLower, + &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, ¢roidUpper, + &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, ¢roidLower, &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, ¢roidLower, &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, ¢roidLower); + if (prevPresent) + { + /* Do comparison with previous centroid */ + cmp1 = range_cmp_bounds(typcache, &upper, &prevLower); + cmp3 = range_cmp_bounds(typcache, ¢roidLower, + &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, ¢roidUpper); + if (prevPresent) + { + /* Do comparison with previous centroid */ + cmp1 = range_cmp_bounds(typcache, &lower, &prevUpper); + cmp3 = range_cmp_bounds(typcache, ¢roidUpper, &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, ¢roidLower, + &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, ¢roidUpper, + &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, ¢roidLower, + &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, ¢roidUpper, + &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, ¢roidLower, + &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, ¢roidUpper, + &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;