Hello, Hacker. * [PATCH] add a box index to sp-gist
We have extended sp-gist with an index that keeps track of boxes We have used ideas underlying sp-gist range implementation to represent 2D boxes as points in 4D space. We use quad tree analogue, but in 4-dimensional space. We call this tree q4d. Each node of this tree is a box (a point in 4D space) which splits space in 16 hyperrectangles. Rationale: r-tree assumes that boxes we're trying to index don't overlap much. When this assumption fails, r-tree performs badly, while our proposal to represent a rectangle as a point in 4D space solves this problem. NB: the index uses traversalValue introduced in a separate patch. * [PATCH] add traversalValue in sp-gist During implementation of box index for sp-gist we saw that we only keep rectangles, but to determine traversal direction we may need to know boundaries of a hyperrectangle. So we calculate them on the fly and store them in traversalValue, available when traversing child nodes, because otherwise we would't be able to calculate them from inside the inner_consistent function call. This patch was written by Teodor Sigaev. * [PATCH] change reconstructValue -> traversalValue in range_spgist.c During traversal, local context is insufficient to pick traversal direction. As of now, this is worked around with the help of reconstructValue. reconstructValue only stores data of the same type as a tree node, that is, a range. We have written a patch that calculates auxillary values and makes them accessible during traversal. We then use traversalValue in a new box index and change range_spgist.c to use it in place of reconstructValue. NB: apply this patch after traversalValue patch.
diff --git a/doc/src/sgml/spgist.sgml b/doc/src/sgml/spgist.sgml index 56827e5..d558b8e 100644 --- a/doc/src/sgml/spgist.sgml +++ b/doc/src/sgml/spgist.sgml @@ -542,6 +542,8 @@ typedef struct spgInnerConsistentIn int nkeys; /* length of array */ Datum reconstructedValue; /* value reconstructed at parent */ + void *traversalValue; /* opclass-specific traverse value */ + MemoryContext traversalMemoryContext; int level; /* current level (counting from zero) */ bool returnData; /* original data must be returned? */ @@ -559,6 +561,8 @@ typedef struct spgInnerConsistentOut int *nodeNumbers; /* their indexes in the node array */ int *levelAdds; /* increment level by this much for each */ Datum *reconstructedValues; /* associated reconstructed values */ + void **traversalValues; /* opclass-specific traverse values */ + } spgInnerConsistentOut; </programlisting> @@ -593,6 +597,9 @@ typedef struct spgInnerConsistentOut inner tuple, and <structfield>nodeLabels</> is an array of their label values, or NULL if the nodes do not have labels. + <structfield>traversalValue</> is a pointer to data that + <function>inner_consistent</> gets when called on child nodes from an + outer call of <function>inner_consistent</> on parent nodes. </para> <para> @@ -612,6 +619,15 @@ typedef struct spgInnerConsistentOut responsible for palloc'ing the <structfield>nodeNumbers</>, <structfield>levelAdds</> and <structfield>reconstructedValues</> arrays. + Sometimes accumulating some information is needed, while + descending from parent to child node was happened. In this case + + <structfield>traversalValues</> array keeps pointers to + specific data you need to accumulate for every child node. + Memory for <structfield>traversalValues</> should be allocated in + the default context, but make sure to switch to + <structfield>traversalMemoryContext</> before allocating memory + for pointers themselves. </para> </listitem> </varlistentry> @@ -638,6 +654,7 @@ typedef struct spgLeafConsistentIn ScanKey scankeys; /* array of operators and comparison values */ int nkeys; /* length of array */ + void *traversalValue; /* opclass-specific traverse value */ Datum reconstructedValue; /* value reconstructed at parent */ int level; /* current level (counting from zero) */ bool returnData; /* original data must be returned? */ diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c index 8a0d909..bec6daf 100644 --- a/src/backend/access/spgist/spgscan.c +++ b/src/backend/access/spgist/spgscan.c @@ -30,6 +30,7 @@ typedef void (*storeRes_func) (SpGistScanOpaque so, ItemPointer heapPtr, typedef struct ScanStackEntry { Datum reconstructedValue; /* value reconstructed from parent */ + void *traversalValue; /* opclass-specific traverse value */ int level; /* level of items on this page */ ItemPointerData ptr; /* block and offset to scan from */ } ScanStackEntry; @@ -42,6 +43,9 @@ freeScanStackEntry(SpGistScanOpaque so, ScanStackEntry *stackEntry) if (!so->state.attType.attbyval && DatumGetPointer(stackEntry->reconstructedValue) != NULL) pfree(DatumGetPointer(stackEntry->reconstructedValue)); + if (stackEntry->traversalValue) + pfree(stackEntry->traversalValue); + pfree(stackEntry); } @@ -263,6 +267,7 @@ static bool spgLeafTest(Relation index, SpGistScanOpaque so, SpGistLeafTuple leafTuple, bool isnull, int level, Datum reconstructedValue, + void *traversalValue, Datum *leafValue, bool *recheck) { bool result; @@ -289,6 +294,7 @@ spgLeafTest(Relation index, SpGistScanOpaque so, in.scankeys = so->keyData; in.nkeys = so->numberOfKeys; in.reconstructedValue = reconstructedValue; + in.traversalValue = traversalValue; in.level = level; in.returnData = so->want_itup; in.leafDatum = leafDatum; @@ -389,6 +395,7 @@ redirect: leafTuple, isnull, stackEntry->level, stackEntry->reconstructedValue, + stackEntry->traversalValue, &leafValue, &recheck)) { @@ -435,6 +442,7 @@ redirect: leafTuple, isnull, stackEntry->level, stackEntry->reconstructedValue, + stackEntry->traversalValue, &leafValue, &recheck)) { @@ -480,6 +488,8 @@ redirect: in.scankeys = so->keyData; in.nkeys = so->numberOfKeys; in.reconstructedValue = stackEntry->reconstructedValue; + in.traversalMemoryContext = oldCtx; + in.traversalValue = stackEntry->traversalValue; in.level = stackEntry->level; in.returnData = so->want_itup; in.allTheSame = innerTuple->allTheSame; @@ -547,6 +557,9 @@ redirect: else newEntry->reconstructedValue = (Datum) 0; + newEntry->traversalValue = (out.traversalValues) ? + out.traversalValues[i] : NULL; + so->scanStack = lcons(newEntry, so->scanStack); } } diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h index 0cb8fd9..b3bbdf4 100644 --- a/src/include/access/spgist.h +++ b/src/include/access/spgist.h @@ -133,6 +133,8 @@ typedef struct spgInnerConsistentIn int nkeys; /* length of array */ Datum reconstructedValue; /* value reconstructed at parent */ + void *traversalValue; /* opclass-specific traverse value */ + MemoryContext traversalMemoryContext; int level; /* current level (counting from zero) */ bool returnData; /* original data must be returned? */ @@ -150,6 +152,7 @@ typedef struct spgInnerConsistentOut int *nodeNumbers; /* their indexes in the node array */ int *levelAdds; /* increment level by this much for each */ Datum *reconstructedValues; /* associated reconstructed values */ + void **traversalValues; /* opclass-specific traverse values */ } spgInnerConsistentOut; /* @@ -160,6 +163,7 @@ typedef struct spgLeafConsistentIn ScanKey scankeys; /* array of operators and comparison values */ int nkeys; /* length of array */ + void *traversalValue; /* opclass-specific traverse value */ Datum reconstructedValue; /* value reconstructed at parent */ int level; /* current level (counting from zero) */ bool returnData; /* original data must be returned? */
diff --git a/doc/src/sgml/spgist.sgml b/doc/src/sgml/spgist.sgml index d558b8e..8628205 100644 --- a/doc/src/sgml/spgist.sgml +++ b/doc/src/sgml/spgist.sgml @@ -113,6 +113,19 @@ </entry> </row> <row> + <entry><literal>box_ops</></entry> + <entry>box</entry> + <entry> + <literal>&&</> + <literal><<</> + <literal>>></> + <literal><<|</> + <literal>|>></> + <literal><@</> + <literal>@></> + </entry> + </row> + <row> <entry><literal>text_ops</></entry> <entry><type>text</></entry> <entry> diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c index bec6daf..5ac5bef 100644 --- a/src/backend/access/spgist/spgscan.c +++ b/src/backend/access/spgist/spgscan.c @@ -23,7 +23,6 @@ #include "utils/memutils.h" #include "utils/rel.h" - typedef void (*storeRes_func) (SpGistScanOpaque so, ItemPointer heapPtr, Datum leafValue, bool isnull, bool recheck); diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 3ed0b44..2236737 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -18,7 +18,7 @@ endif # keep this list arranged alphabetically or it gets to be a mess OBJS = acl.o arrayfuncs.o array_expanded.o array_selfuncs.o \ array_typanalyze.o array_userfuncs.o arrayutils.o ascii.o \ - bool.o cash.o char.o date.o datetime.o datum.o dbsize.o domains.o \ + bool.o boxtype_spgist.o cash.o char.o date.o datetime.o datum.o dbsize.o domains.o \ encode.o enum.o expandeddatum.o \ float.o format_type.o formatting.o genfile.o \ geo_ops.o geo_selfuncs.o inet_cidr_ntop.o inet_net_pton.o int.o \ diff --git a/src/backend/utils/adt/boxtype_spgist.c b/src/backend/utils/adt/boxtype_spgist.c new file mode 100644 index 0000000..a633ce9 --- /dev/null +++ b/src/backend/utils/adt/boxtype_spgist.c @@ -0,0 +1,733 @@ +/*------------------------------------------------------------------------- + * + * boxtype_spgist.c + * implementation of quad-4d tree over boxes for SP-GiST. + * + * Quad-4d is a 4-dimensional analog of quadtree. Quad-4d tree splits + * 4-dimensional space into 16 quadrants. Each inner node of a quad-4d tree contains + * a box. We call these boxes centroids. Main purpose of the boxtype index is + * to tell, for a given box, which other boxes intersect it, + * contain or are contained by it, etc. + * + * For example consider the case of intersection. + * When recursion descends deeper and deeper down the tree, all quadrants in the + * current node will eventually be passed to the intersect4D function call. This function + * answers the question: can any box from this quadrant intersect with given box (query box)? + * If yes, then this quadrant will be walked. If no, then this quadrant will be rejected. + * + * A quadrant has bounds, but sp-gist keeps only 4-d point (box) in inner nodes. + * We use traversalValue to calculate quadrant bounds from parent's quadrant + * bounds. + * + * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/adt/boxtype_spgist.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/spgist.h" +#include "access/stratnum.h" +#include "catalog/pg_type.h" +#include "utils/builtins.h"; +#include "utils/datum.h" +#include "utils/geo_decls.h" + +#define NegInf -1 +#define PosInf 1 +#define NotInf 0 + +#define LT -1 +#define GT 1 +#define EQ 0 + + + +/* InfR type implements doubles and +- infinity */ +typedef struct +{ + int infFlag; + double val; +} InfR; + +static InfR negInf = {.infFlag = NegInf,.val = 0}; +static InfR posInf = {.infFlag = PosInf,.val = 0}; + +/* wrap double to InfR */ +static InfR +toInfR(double v, InfR * r) +{ + r->infFlag = NotInf; + r->val = v; +} + +/* compare InfR with double */ +static int +cmp_InfR_r(const InfR * infVal, const double val) +{ + if (infVal->infFlag == PosInf) + return GT; + + else if (infVal->infFlag == NegInf) + return LT; + + else + { + double val0 = infVal->val; + + if (val0 < val) + return LT; + if (val0 > val) + return GT; + } + + return EQ; +} + +static int +compareDoubles(const void *a, const void *b) +{ + double x = *(double *) a; + double y = *(double *) b; + + if (x < y) + return LT; + if (x > y) + return GT; + return EQ; +} + +/*------------------------------------------------------------------------- + * We have two families of types: + * IRange, IRangeBox and IRectBox are parameterized with InfR, + * while Range and Rectangle are parameterized with double + *------------------------------------------------------------------------- + */ +typedef struct +{ + InfR low; + InfR high; +} IRange; + +typedef struct +{ + IRange left; + IRange right; +} IRangeBox; + +typedef struct +{ + IRangeBox range_box_x; + IRangeBox range_box_y; +} IRectBox; + +typedef struct +{ + double low; + double high; +} Range; + +typedef struct +{ + Range range_x; + Range range_y; +} Rectangle; + + +/* Fill Rectangle using BOX */ +inline static void +boxPointerToRectangle(BOX *box, Rectangle * rectangle) +{ + rectangle->range_x.low = box->low.x; + rectangle->range_x.high = box->high.x; + + rectangle->range_y.low = box->low.y; + rectangle->range_y.high = box->high.y; +} + +/*----------------------------------------------------------------- + * quadrant is 8bits unsigned integer with bits: + * [0,0,0,0,a,b,c,d] where + * a is 1 if inBox->low.x > centroid->low.x + * b is 1 if inBox->high.x > centroid->high.x + * c is 1 if inBox->low.y > centroid->low.y + * d is 1 if inBox->high.y > centroid->high.y + *----------------------------------------------------------------- + */ +static uint8 +getQuadrant(const BOX *centroid, const BOX *inBox) +{ + uint8 quadrant = 0; + + if (inBox->low.x > centroid->low.x) + quadrant |= 0x8; + + if (inBox->high.x > centroid->high.x) + quadrant |= 0x4; + + if (inBox->low.y > centroid->low.y) + quadrant |= 0x2; + + if (inBox->high.y > centroid->high.y) + quadrant |= 0x1; + + return quadrant; +} + + +/* + * All centroids in q4d tree are bounded by IRectBox, but SP-Gist only keeps boxes. + * When we walk into depth, we must calculate IRectBox, + * using centroid and quadrant. The following function calculates IRangeBox. + */ +static void +evalIRangeBox(const IRangeBox * range_box, const Range * range, const int half1, const int half2, IRangeBox * new_range_box) +{ + if (half1 == 0) + { + toInfR(range->low, &(new_range_box->left.high)); + new_range_box->left.low = range_box->left.low; + } + else + { + toInfR(range->low, &(new_range_box->left.low)); + new_range_box->left.high = range_box->left.high; + } + + if (half2 == 0) + { + toInfR(range->high, &(new_range_box->right.high)); + new_range_box->right.low = range_box->right.low; + } + else + { + toInfR(range->high, &(new_range_box->right.low)); + new_range_box->right.high = range_box->right.high; + } +} + + + +/* + * All centroids in q4d tree are bounded by IRectBox, but SP-Gist only keeps boxes. + * When we walk into depth, we must calculate IRectBox, + * using centroid and quadrant. + */ +static void +evalIRectBox(const IRectBox * rect_box, const Rectangle * centroid, const uint8 quadrant, IRectBox * new_rect_box) +{ + const int half1 = quadrant & 0x8; + const int half2 = quadrant & 0x4; + const int half3 = quadrant & 0x2; + const int half4 = quadrant & 0x1; + + evalIRangeBox(&(rect_box->range_box_x), &(centroid->range_x), half1, half2, &(new_rect_box->range_box_x)); + evalIRangeBox(&(rect_box->range_box_y), &(centroid->range_y), half3, half4, &(new_rect_box->range_box_y)); +} + + +/* initialize IRangeBox covering all space */ +inline static void +initializeUnboundedBox(IRectBox * rect_box) +{ + rect_box->range_box_x.left.low = negInf; + rect_box->range_box_x.left.high = posInf; + + rect_box->range_box_x.right.low = negInf; + rect_box->range_box_x.right.high = posInf; + + rect_box->range_box_y.left.low = negInf; + rect_box->range_box_y.left.high = posInf; + + rect_box->range_box_y.right.low = negInf; + rect_box->range_box_y.right.high = posInf; +} + + +/* answer the question: Can this range and any range from range_box intersect? */ +static int +intersect2D(const Range * range, const IRangeBox * range_box) +{ + const InfR *x0 = &(range_box->left.low); + const InfR *y1 = &(range_box->right.high); + + const double a = range->low; + const double b = range->high; + + const int p1 = cmp_InfR_r(y1, a); + const int p2 = cmp_InfR_r(x0, b); + + return ((p1 != LT) && (p2 != GT)); +} + +/* answer the question: Can this rectangle and any rectangle from rect_box intersect? */ +static int +intersect4D(const Rectangle * rectangle, const IRectBox * rect_box) +{ + const int px = intersect2D(&(rectangle->range_x), &(rect_box->range_box_x)); + const int py = intersect2D(&(rectangle->range_y), &(rect_box->range_box_y)); + + return (px && py); +} + + +/* answer the question: Can any range from range_box contain this range? */ +static int +contain2D(const Range * range, const IRangeBox * range_box) +{ + const InfR *x0 = &(range_box->left.low); + const InfR *y1 = &(range_box->right.high); + + const double a = range->low; + const double b = range->high; + + const int p1 = cmp_InfR_r(y1, b); + const int p2 = cmp_InfR_r(x0, a); + + return ((p1 != LT) && (p2 != GT)); +} + + +/* answer the question: Can any rectangle from rect_box contain this rectangle? */ +static int +contain4D(const Rectangle * rectangle, const IRectBox * rect_box) +{ + const int px = contain2D(&(rectangle->range_x), &(rect_box->range_box_x)); + const int py = contain2D(&(rectangle->range_y), &(rect_box->range_box_y)); + + return (px && py); +} + + +/* answer the question: Can this range contain any range from range_box? */ +static int +contained2D(const Range * range, const IRangeBox * range_box) +{ + const InfR *x0 = &(range_box->left.low); + const InfR *x1 = &(range_box->left.high); + + const InfR *y0 = &(range_box->right.low); + const InfR *y1 = &(range_box->right.high); + + const double a = range->low; + const double b = range->high; + + const int p1 = cmp_InfR_r(x0, b); + const int p2 = cmp_InfR_r(x1, a); + const int p3 = cmp_InfR_r(y0, b); + const int p4 = cmp_InfR_r(y1, a); + + return ((p1 != GT) && (p2 != LT) && (p3 != GT) && (p4 != LT)); +} + +/* answer the question: Can this rectangle contain any rectangle from rect_box? */ +static int +contained4D(const Rectangle * rectangle, const IRectBox * rect_box) +{ + const int px = contained2D(&(rectangle->range_x), &(rect_box->range_box_x)); + const int py = contained2D(&(rectangle->range_y), &(rect_box->range_box_y)); + + return (px && py); +} + + +/* answer the question: Can any range from range_box to be lower than this range? */ +static int +isLower(const Range * range, const IRangeBox * range_box) +{ + const InfR *x0 = &(range_box->left.low); + const InfR *y0 = &(range_box->right.low); + + const double a = range->low; + + const int p1 = cmp_InfR_r(x0, a); + const int p2 = cmp_InfR_r(y0, a); + + return (p1 == LT && p2 == LT); +} + +/* answer the question: Can any range from range_box to be higher than this range? */ +static int +isHigher(const Range * range, const IRangeBox * range_box) +{ + const InfR *x1 = &(range_box->left.high); + const InfR *y1 = &(range_box->right.high); + + const double b = range->high; + + const int p1 = cmp_InfR_r(x1, b); + const int p2 = cmp_InfR_r(y1, b); + + return (p1 == GT && p2 == GT); +} + +static int +left4D(const Rectangle * rectangle, const IRectBox * rect_box) +{ + return isLower(&(rectangle->range_x), &(rect_box->range_box_x)); +} + +static int +right4D(const Rectangle * rectangle, const IRectBox * rect_box) +{ + return isHigher(&(rectangle->range_x), &(rect_box->range_box_x)); +} + +static int +below4D(const Rectangle * rectangle, const IRectBox * rect_box) +{ + return isLower(&(rectangle->range_y), &(rect_box->range_box_y)); +} + +static int +above4D(const Rectangle * rectangle, const IRectBox * rect_box) +{ + return isHigher(&(rectangle->range_y), &(rect_box->range_box_y)); +} + + +/* SP-GiST API functions */ +Datum spg_box_quad_config(PG_FUNCTION_ARGS); +Datum spg_box_quad_choose(PG_FUNCTION_ARGS); +Datum spg_box_quad_picksplit(PG_FUNCTION_ARGS); +Datum spg_box_quad_inner_consistent(PG_FUNCTION_ARGS); +Datum spg_box_quad_leaf_consistent(PG_FUNCTION_ARGS); + +/* + * SP-GiST 'config' interface function. + */ +Datum +spg_box_quad_config(PG_FUNCTION_ARGS) +{ + spgConfigOut *cfg = (spgConfigOut *) PG_GETARG_POINTER(1); + + cfg->prefixType = BOXOID; + cfg->labelType = VOIDOID; /* we don't need node labels */ + cfg->canReturnData = true; + cfg->longValuesOK = false; + PG_RETURN_VOID(); +} + + +Datum +spg_box_quad_choose(PG_FUNCTION_ARGS) +{ + const spgChooseIn *in = (spgChooseIn *) PG_GETARG_POINTER(0); + spgChooseOut *out = (spgChooseOut *) PG_GETARG_POINTER(1); + + const BOX *inBox = DatumGetBoxP(in->datum); + const BOX *centroid = DatumGetBoxP(in->prefixDatum); + + uint8 quadrant; + + if (in->allTheSame) + { + out->resultType = spgMatchNode; + /* nodeN will be set by core */ + out->result.matchNode.levelAdd = 0; + out->result.matchNode.restDatum = BoxPGetDatum(inBox); + PG_RETURN_VOID(); + } + + quadrant = getQuadrant(centroid, inBox); + + out->resultType = spgMatchNode; + out->result.matchNode.nodeN = quadrant; + out->result.matchNode.levelAdd = 1; + out->result.matchNode.restDatum = BoxPGetDatum(inBox); + PG_RETURN_VOID(); +} + + +Datum +spg_box_quad_picksplit(PG_FUNCTION_ARGS) +{ + const spgPickSplitIn *in = (spgPickSplitIn *) PG_GETARG_POINTER(0); + spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1); + + BOX *centroid; + int median, + i; + + + /* + * Begin. This block evaluates the median of coordinates of boxes + */ + + double *lowXs = palloc(sizeof(double) * in->nTuples); + double *highXs = palloc(sizeof(double) * in->nTuples); + double *lowYs = palloc(sizeof(double) * in->nTuples); + double *highYs = palloc(sizeof(double) * in->nTuples); + + for (i = 0; i < in->nTuples; i++) + { + const BOX *box = DatumGetBoxP(in->datums[i]); + + lowXs[i] = box->low.x; + highXs[i] = box->high.x; + lowYs[i] = box->low.y; + highYs[i] = box->high.y; + } + + qsort(lowXs, in->nTuples, sizeof(double), compareDoubles); + qsort(highXs, in->nTuples, sizeof(double), compareDoubles); + qsort(lowYs, in->nTuples, sizeof(double), compareDoubles); + qsort(highYs, in->nTuples, sizeof(double), compareDoubles); + + median = in->nTuples / 2; + + centroid = palloc(sizeof(BOX)); + + centroid->low.x = lowXs[median]; + centroid->high.x = highXs[median]; + centroid->low.y = lowYs[median]; + centroid->high.y = highYs[median]; + + /* + * This block evaluates the median of coordinates of boxes. End. + */ + + out->hasPrefix = true; + out->prefixDatum = BoxPGetDatum(centroid); + + out->nNodes = 16; + 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++) + { + const BOX *box = DatumGetBoxP(in->datums[i]); + const uint8 quadrant = getQuadrant(centroid, box); + + out->leafTupleDatums[i] = BoxPGetDatum(box); + out->mapTuplesToNodes[i] = quadrant; + } + + PG_RETURN_VOID(); +} + +Datum +spg_box_quad_inner_consistent(PG_FUNCTION_ARGS) +{ + spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0); + spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1); + int i; + + MemoryContext oldCtx; + IRectBox *rect_box; + + uint8 quadrant; + + Rectangle *rectangle_centroid = (Rectangle *) palloc(sizeof(Rectangle)); + Rectangle *p_query_rect = (Rectangle *) palloc(sizeof(Rectangle)); + + boxPointerToRectangle(DatumGetBoxP(in->prefixDatum), rectangle_centroid); + + if (in->traversalValue) + { + /* Here we get 4 dimension bound box (IRectBox) from traversalValue */ + rect_box = in->traversalValue; + } + else + { + /* + * Here we initialize rect_box, because we have just begun to walk + * through the tree + */ + + rect_box = (IRectBox *) palloc(sizeof(IRectBox)); + initializeUnboundedBox(rect_box); + } + + out->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes); + + if (in->allTheSame) + { + /* Report that all nodes should be visited */ + int nnode; + + out->nNodes = in->nNodes; + out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); + + /* + * We switch memory context, because we want allocate memory for new + * traversal values for IRectBox and transmit these pieces of memory + * to further calls of spg_box_quad_inner_consistent. + */ + oldCtx = MemoryContextSwitchTo(in->traversalMemoryContext); + + for (nnode = 0; nnode < in->nNodes; nnode++) + { + IRectBox *new_rect_box; + + new_rect_box = (IRectBox *) palloc(sizeof(IRectBox)); + memcpy(new_rect_box, rect_box, sizeof(IRectBox)); + + out->traversalValues[nnode] = new_rect_box; + out->nodeNumbers[nnode] = nnode; + } + /* Switch back */ + MemoryContextSwitchTo(oldCtx); + PG_RETURN_VOID(); + } + + out->nNodes = 0; + out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); + + /* + * We switch memory context, because we want to allocate memory for new + * traversal values (new_rect_box) and pass these pieces of memory to + * further call of spg_box_quad_inner_consistent. + */ + oldCtx = MemoryContextSwitchTo(in->traversalMemoryContext); + + for (quadrant = 0; quadrant < in->nNodes; quadrant++) + { + IRectBox *new_rect_box; + int flag; + + new_rect_box = (IRectBox *) palloc(sizeof(IRectBox)); + + /* Calculates 4-dim IRectBox */ + evalIRectBox(rect_box, rectangle_centroid, quadrant, new_rect_box); + + for (i = 0, flag = 1; i < in->nkeys && flag; i++) + { + const StrategyNumber strategy = in->scankeys[i].sk_strategy; + + switch (strategy) + { + case RTOverlapStrategyNumber: + boxPointerToRectangle(DatumGetBoxP(in->scankeys[i].sk_argument), p_query_rect); + flag = flag && intersect4D(p_query_rect, new_rect_box); + break; + + case RTContainsStrategyNumber: + boxPointerToRectangle(DatumGetBoxP(in->scankeys[i].sk_argument), p_query_rect); + flag = flag && contain4D(p_query_rect, new_rect_box); + break; + + case RTContainedByStrategyNumber: + boxPointerToRectangle(DatumGetBoxP(in->scankeys[i].sk_argument), p_query_rect); + flag = flag && contained4D(p_query_rect, new_rect_box); + break; + + case RTLeftStrategyNumber: + boxPointerToRectangle(DatumGetBoxP(in->scankeys[i].sk_argument), p_query_rect); + flag = flag && left4D(p_query_rect, new_rect_box); + break; + + case RTRightStrategyNumber: + boxPointerToRectangle(DatumGetBoxP(in->scankeys[i].sk_argument), p_query_rect); + flag = flag && right4D(p_query_rect, new_rect_box); + break; + + case RTAboveStrategyNumber: + boxPointerToRectangle(DatumGetBoxP(in->scankeys[i].sk_argument), p_query_rect); + flag = flag && above4D(p_query_rect, new_rect_box); + break; + + case RTBelowStrategyNumber: + boxPointerToRectangle(DatumGetBoxP(in->scankeys[i].sk_argument), p_query_rect); + flag = flag && below4D(p_query_rect, new_rect_box); + break; + + default: + elog(ERROR, "This operation doesn't support by SP-Gist"); + } + } + if (flag) + { + out->traversalValues[out->nNodes] = new_rect_box; + out->nodeNumbers[out->nNodes] = quadrant; + out->nNodes++; + } + } + + MemoryContextSwitchTo(oldCtx); + PG_RETURN_VOID(); +} + +Datum +spg_box_quad_leaf_consistent(PG_FUNCTION_ARGS) +{ + spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0); + spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1); + BOX *leafBox = DatumGetBoxP(in->leafDatum); + int flag, + i; + + /* all tests are exact */ + out->recheck = false; + + /* leafDatum is what it is... */ + out->leafValue = in->leafDatum; + + /* Perform the required comparison(s) */ + for (i = 0, flag = 1; i < in->nkeys && flag; i++) + { + const StrategyNumber strategy = in->scankeys[i].sk_strategy; + const Datum keyDatum = in->scankeys[i].sk_argument; + + switch (strategy) + { + case RTOverlapStrategyNumber: + flag = flag && DatumGetBool(DirectFunctionCall2(box_overlap, + PointerGetDatum(leafBox), + keyDatum)); + break; + + case RTContainsStrategyNumber: + flag = flag && DatumGetBool(DirectFunctionCall2(box_contain, + PointerGetDatum(leafBox), + keyDatum)); + break; + + case RTContainedByStrategyNumber: + flag = flag && DatumGetBool(DirectFunctionCall2(box_contained, + PointerGetDatum(leafBox), + keyDatum)); + break; + + case RTLeftStrategyNumber: + flag = flag && DatumGetBool(DirectFunctionCall2(box_left, + PointerGetDatum(leafBox), + keyDatum)); + break; + + case RTRightStrategyNumber: + flag = flag && DatumGetBool(DirectFunctionCall2(box_right, + PointerGetDatum(leafBox), + keyDatum)); + break; + + case RTAboveStrategyNumber: + flag = flag && DatumGetBool(DirectFunctionCall2(box_above, + PointerGetDatum(leafBox), + keyDatum)); + break; + + case RTBelowStrategyNumber: + flag = flag && DatumGetBool(DirectFunctionCall2(box_below, + PointerGetDatum(leafBox), + keyDatum)); + break; + + default: + elog(ERROR, "This type operation doesn't support by sp-gist"); + } + } + + PG_RETURN_BOOL(flag); +} diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h index da5fe9d..c5083f9 100644 --- a/src/include/catalog/pg_amop.h +++ b/src/include/catalog/pg_amop.h @@ -833,6 +833,17 @@ DATA(insert ( 3474 3831 2283 16 s 3889 4000 0 )); DATA(insert ( 3474 3831 3831 18 s 3882 4000 0 )); /* + * SP-GiST box_ops + */ +DATA(insert ( 5000 603 603 1 s 493 4000 0 )); +DATA(insert ( 5000 603 603 3 s 500 4000 0 )); +DATA(insert ( 5000 603 603 5 s 496 4000 0 )); +DATA(insert ( 5000 603 603 7 s 498 4000 0 )); +DATA(insert ( 5000 603 603 8 s 497 4000 0 )); +DATA(insert ( 5000 603 603 10 s 2570 4000 0 )); +DATA(insert ( 5000 603 603 11 s 2573 4000 0 )); + +/* * GiST inet_ops */ DATA(insert ( 3550 869 869 3 s 3552 783 0 )); diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h index b57d6e6..86e3351 100644 --- a/src/include/catalog/pg_amproc.h +++ b/src/include/catalog/pg_amproc.h @@ -438,6 +438,11 @@ 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 ( 5000 603 603 1 5012 )); +DATA(insert ( 5000 603 603 2 5013 )); +DATA(insert ( 5000 603 603 3 5014 )); +DATA(insert ( 5000 603 603 4 5015 )); +DATA(insert ( 5000 603 603 5 5016 )); /* BRIN opclasses */ /* minmax bytea */ diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h index e7b3148..5a97c65 100644 --- a/src/include/catalog/pg_opclass.h +++ b/src/include/catalog/pg_opclass.h @@ -228,6 +228,7 @@ 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 box_ops PGNSP PGUID 5000 603 t 0 )); DATA(insert ( 4000 quad_point_ops PGNSP PGUID 4015 600 t 0 )); DATA(insert ( 4000 kd_point_ops PGNSP PGUID 4016 600 f 0 )); DATA(insert ( 4000 text_ops PGNSP PGUID 4017 25 t 0 )); diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h index acbc100..08b0c91 100644 --- a/src/include/catalog/pg_opfamily.h +++ b/src/include/catalog/pg_opfamily.h @@ -182,5 +182,6 @@ DATA(insert OID = 4081 ( 3580 uuid_minmax_ops PGNSP PGUID )); DATA(insert OID = 4103 ( 3580 range_inclusion_ops PGNSP PGUID )); DATA(insert OID = 4082 ( 3580 pg_lsn_minmax_ops PGNSP PGUID )); DATA(insert OID = 4104 ( 3580 box_inclusion_ops PGNSP PGUID )); +DATA(insert OID = 5000 ( 4000 box_ops PGNSP PGUID )); #endif /* PG_OPFAMILY_H */ diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index f688454..0a3a6ae 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -5194,6 +5194,17 @@ 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 s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ spg_range_quad_leaf_consistent _null_ _null_ _null_ )); DESCR("SP-GiST support for quad tree over range"); +DATA(insert OID = 5012 ( spg_box_quad_config PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_ _null_ spg_box_quad_config _null_ _null_ _null_ )); +DESCR("SP-GiST support for quad tree over box"); +DATA(insert OID = 5013 ( spg_box_quad_choose PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_ _null_ spg_box_quad_choose _null_ _null_ _null_ )); +DESCR("SP-GiST support for quad tree over box"); +DATA(insert OID = 5014 ( spg_box_quad_picksplit PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_ _null_ spg_box_quad_picksplit _null_ _null_ _null_ )); +DESCR("SP-GiST support for quad tree over box"); +DATA(insert OID = 5015 ( spg_box_quad_inner_consistent PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 2278 "2281 2281" _null_ _null_ _null_ _null_ _null_ spg_box_quad_inner_consistent _null_ _null_ _null_ )); +DESCR("SP-GiST support for quad tree over box"); +DATA(insert OID = 5016 ( spg_box_quad_leaf_consistent PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ spg_box_quad_leaf_consistent _null_ _null_ _null_ )); +DESCR("SP-GiST support for quad tree over box"); + /* replication slots */ DATA(insert OID = 3779 ( pg_create_physical_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f f f v u 2 0 2249 "19 16" "{19,16,19,3220}" "{i,i,o,o}" "{slot_name,immediately_reserve,slot_name,xlog_position}" _null_ _null_ pg_create_physical_replication_slot _null_ _null_ _null_ )); DESCR("create a physical replication slot"); diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index b72e65d..b10a8c9 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -70,6 +70,9 @@ CREATE INDEX ggcircleind ON gcircle_tbl USING gist (f1); -- -- SP-GiST -- +CREATE TEMP TABLE q4d_boxes_tbl AS + SELECT home_base AS home_base FROM fast_emp4000; +CREATE INDEX q4d_index ON q4d_boxes_tbl USING spgist (home_base); CREATE TABLE quad_point_tbl AS SELECT point(unique1,unique2) AS p FROM tenk1; INSERT INTO quad_point_tbl @@ -113,6 +116,27 @@ SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; 278 (1 row) +SELECT * FROM q4d_boxes_tbl + WHERE home_base @ '(200,200),(2000,1000)'::box + ORDER BY (home_base[0])[0]; + home_base +----------------------- + (337,455),(240,359) + (1444,403),(1346,344) +(2 rows) + +SELECT count(*) FROM q4d_boxes_tbl WHERE home_base && '(1000,1000,0,0)'::box; + count +------- + 2 +(1 row) + +SELECT count(*) FROM q4d_boxes_tbl WHERE home_base IS NULL; + count +------- + 278 +(1 row) + SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon ORDER BY (poly_center(f1))[0]; f1 @@ -458,6 +482,57 @@ SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; (1 row) EXPLAIN (COSTS OFF) +SELECT * FROM q4d_boxes_tbl + WHERE home_base @ '(200,200),(2000,1000)'::box + ORDER BY (home_base[0])[0]; + QUERY PLAN +------------------------------------------------------------ + Sort + Sort Key: ((home_base[0])[0]) + -> Index Only Scan using q4d_index on q4d_boxes_tbl + Filter: (home_base @ '(2000,1000),(200,200)'::box) +(4 rows) + +SELECT * FROM q4d_boxes_tbl + WHERE home_base @ '(200,200),(2000,1000)'::box + ORDER BY (home_base[0])[0]; + home_base +----------------------- + (337,455),(240,359) + (1444,403),(1346,344) +(2 rows) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM q4d_boxes_tbl WHERE home_base && '(1000,1000,0,0)'::box; + QUERY PLAN +------------------------------------------------------------- + Aggregate + -> Index Only Scan using q4d_index on q4d_boxes_tbl + Index Cond: (home_base && '(1000,1000),(0,0)'::box) +(3 rows) + +SELECT count(*) FROM q4d_boxes_tbl WHERE home_base && '(1000,1000,0,0)'::box; + count +------- + 2 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM q4d_boxes_tbl WHERE home_base IS NULL; + QUERY PLAN +-------------------------------------------------------- + Aggregate + -> Index Only Scan using q4d_index on q4d_boxes_tbl + Index Cond: (home_base IS NULL) +(3 rows) + +SELECT count(*) FROM q4d_boxes_tbl WHERE home_base IS NULL; + count +------- + 278 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon ORDER BY (poly_center(f1))[0]; QUERY PLAN diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index df29fe5..1ec0d57 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1704,15 +1704,17 @@ ORDER BY 1, 2, 3; 4000 | 6 | ~= 4000 | 7 | @> 4000 | 8 | <@ + 4000 | 10 | <<| 4000 | 10 | <^ 4000 | 11 | < 4000 | 11 | >^ + 4000 | 11 | |>> 4000 | 12 | <= 4000 | 14 | >= 4000 | 15 | > 4000 | 16 | @> 4000 | 18 | = -(108 rows) +(110 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql index ff86953..655fcf0 100644 --- a/src/test/regress/sql/create_index.sql +++ b/src/test/regress/sql/create_index.sql @@ -100,6 +100,11 @@ CREATE INDEX ggcircleind ON gcircle_tbl USING gist (f1); -- SP-GiST -- +CREATE TEMP TABLE q4d_boxes_tbl AS + SELECT home_base AS home_base FROM fast_emp4000; + +CREATE INDEX q4d_index ON q4d_boxes_tbl USING spgist (home_base); + CREATE TABLE quad_point_tbl AS SELECT point(unique1,unique2) AS p FROM tenk1; @@ -142,6 +147,14 @@ SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box; SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; +SELECT * FROM q4d_boxes_tbl + WHERE home_base @ '(200,200),(2000,1000)'::box + ORDER BY (home_base[0])[0]; + +SELECT count(*) FROM q4d_boxes_tbl WHERE home_base && '(1000,1000,0,0)'::box; + +SELECT count(*) FROM q4d_boxes_tbl WHERE home_base IS NULL; + SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon ORDER BY (poly_center(f1))[0]; @@ -250,6 +263,22 @@ SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; EXPLAIN (COSTS OFF) +SELECT * FROM q4d_boxes_tbl + WHERE home_base @ '(200,200),(2000,1000)'::box + ORDER BY (home_base[0])[0]; +SELECT * FROM q4d_boxes_tbl + WHERE home_base @ '(200,200),(2000,1000)'::box + ORDER BY (home_base[0])[0]; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM q4d_boxes_tbl WHERE home_base && '(1000,1000,0,0)'::box; +SELECT count(*) FROM q4d_boxes_tbl WHERE home_base && '(1000,1000,0,0)'::box; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM q4d_boxes_tbl WHERE home_base IS NULL; +SELECT count(*) FROM q4d_boxes_tbl WHERE home_base IS NULL; + +EXPLAIN (COSTS OFF) SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon ORDER BY (poly_center(f1))[0]; SELECT * FROM polygon_tbl WHERE f1 ~ '((1,1),(2,2),(2,1))'::polygon
diff --git a/src/backend/utils/adt/rangetypes_spgist.c b/src/backend/utils/adt/rangetypes_spgist.c index 3b5529e..23b1b9d 100644 --- a/src/backend/utils/adt/rangetypes_spgist.c +++ b/src/backend/utils/adt/rangetypes_spgist.c @@ -310,14 +310,12 @@ spg_range_quad_inner_consistent(PG_FUNCTION_ARGS) spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1); int which; int i; + MemoryContext oldCtx; /* * For adjacent search we need also previous centroid (if any) to improve * the precision of the consistent check. In this case needPrevious flag - * is set and centroid is passed into reconstructedValues. This is not the - * intended purpose of reconstructedValues (because we already have the - * full value available at the leaf), but it's a convenient place to store - * state while traversing the tree. + * is set and centroid is passed into traversalValue. */ bool needPrevious = false; @@ -565,9 +563,9 @@ spg_range_quad_inner_consistent(PG_FUNCTION_ARGS) * for lower or upper bounds to be adjacent. Deserialize * previous centroid range if present for checking this. */ - if (in->reconstructedValue != (Datum) 0) + if (in->traversalValue != (Datum) 0) { - prevCentroid = DatumGetRangeType(in->reconstructedValue); + prevCentroid = DatumGetRangeType(in->traversalValue); range_deserialize(typcache, prevCentroid, &prevLower, &prevUpper, &prevEmpty); } @@ -746,19 +744,33 @@ spg_range_quad_inner_consistent(PG_FUNCTION_ARGS) /* 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->traversalValues = (void **) palloc(sizeof(void *) * in->nNodes); out->nNodes = 0; + + oldCtx = MemoryContextSwitchTo(in->traversalMemoryContext); + 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; + { + Datum previousCentroid; + + /* We know, that in->prefixDatum in this place is varlena, + * because it's range + */ + previousCentroid = datumCopy(in->prefixDatum, false, -1); + out->traversalValues[out->nNodes] = (void *)previousCentroid; + } + out->nodeNumbers[out->nNodes] = i - 1; + out->nNodes++; } } + MemoryContextSwitchTo(oldCtx); + PG_RETURN_VOID(); }
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers