diff --git a/doc/src/sgml/spgist.sgml b/doc/src/sgml/spgist.sgml index dcc3cc2d73..0202dbcdd5 100644 --- a/doc/src/sgml/spgist.sgml +++ b/doc/src/sgml/spgist.sgml @@ -439,8 +439,8 @@ CREATE FUNCTION my_inner_consistent(internal, internal) RETURNS void ... typedef struct spgInnerConsistentIn { - StrategyNumber strategy; /* operator strategy number */ - Datum query; /* operator's RHS value */ + ScanKey scankeys; /* array of operators and comparison values */ + int nkeys; /* length of array */ Datum reconstructedValue; /* value reconstructed at parent */ int level; /* current level (counting from zero) */ @@ -463,8 +463,17 @@ typedef struct spgInnerConsistentOut } spgInnerConsistentOut; - strategy and - query describe the index search condition. + The array scankeys, of length nkeys, + describes the index search condition(s). These conditions are + combined with AND — only index entries that satisfy all of + them are interesting. (Note that nkeys = 0 implies + that all index entries satisfy the query.) Usually the consistent + function only cares about the sk_strategy and + sk_argument fields of each array entry, which + respectively give the indexable operator and comparison value. + In particular it is not necessary to check sk_flags to + see if the comparison value is NULL, because the SP-GiST core code + will filter out such conditions. reconstructedValue is the value reconstructed for the parent tuple; it is (Datum) 0 at the root level or if the inner_consistent function did not provide a value at the @@ -527,8 +536,8 @@ CREATE FUNCTION my_leaf_consistent(internal, internal) RETURNS bool ... typedef struct spgLeafConsistentIn { - StrategyNumber strategy; /* operator strategy number */ - Datum query; /* operator's RHS value */ + ScanKey scankeys; /* array of operators and comparison values */ + int nkeys; /* length of array */ Datum reconstructedValue; /* value reconstructed at parent */ int level; /* current level (counting from zero) */ @@ -544,8 +553,17 @@ typedef struct spgLeafConsistentOut } spgLeafConsistentOut; - strategy and - query define the index search condition. + The array scankeys, of length nkeys, + describes the index search condition(s). These conditions are + combined with AND — only index entries that satisfy all of + them satisfy the query. (Note that nkeys = 0 implies + that all index entries satisfy the query.) Usually the consistent + function only cares about the sk_strategy and + sk_argument fields of each array entry, which + respectively give the indexable operator and comparison value. + In particular it is not necessary to check sk_flags to + see if the comparison value is NULL, because the SP-GiST core code + will filter out such conditions. reconstructedValue is the value reconstructed for the parent tuple; it is (Datum) 0 at the root level or if the inner_consistent function did not provide a value at the @@ -566,8 +584,8 @@ typedef struct spgLeafConsistentOut leafValue must be set to the value originally supplied to be indexed for this leaf tuple. Also, recheck may be set to true if the match - is uncertain and so the operator must be re-applied to the actual heap - tuple to verify the match. + is uncertain and so the operator(s) must be re-applied to the actual + heap tuple to verify the match. diff --git a/src/backend/access/spgist/spgkdtreeproc.c b/src/backend/access/spgist/spgkdtreeproc.c index eca972a6f0..adfe287581 100644 --- a/src/backend/access/spgist/spgkdtreeproc.c +++ b/src/backend/access/spgist/spgkdtreeproc.c @@ -159,11 +159,10 @@ spg_kd_inner_consistent(PG_FUNCTION_ARGS) { spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0); spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1); - Point *query; - BOX *boxQuery; double coord; + int which; + int i; - query = DatumGetPointP(in->query); Assert(in->hasPrefix); coord = DatumGetFloat8(in->prefixDatum); @@ -171,123 +170,96 @@ spg_kd_inner_consistent(PG_FUNCTION_ARGS) elog(ERROR, "allTheSame should not occur for k-d trees"); Assert(in->nNodes == 2); + + /* "which" is a bitmask of children that satisfy all constraints */ + which = (1 << 1) | (1 << 2); + + for (i = 0; i < in->nkeys; i++) + { + Point *query = DatumGetPointP(in->scankeys[i].sk_argument); + BOX *boxQuery; + + switch (in->scankeys[i].sk_strategy) + { + case RTLeftStrategyNumber: + if ((in->level % 2) != 0 && FPlt(query->x, coord)) + which &= (1 << 1); + break; + case RTRightStrategyNumber: + if ((in->level % 2) != 0 && FPgt(query->x, coord)) + which &= (1 << 2); + break; + case RTSameStrategyNumber: + if ((in->level % 2) != 0) + { + if (FPlt(query->x, coord)) + which &= (1 << 1); + else if (FPgt(query->x, coord)) + which &= (1 << 2); + } + else + { + if (FPlt(query->y, coord)) + which &= (1 << 1); + else if (FPgt(query->y, coord)) + which &= (1 << 2); + } + break; + case RTBelowStrategyNumber: + if ((in->level % 2) == 0 && FPlt(query->y, coord)) + which &= (1 << 1); + break; + case RTAboveStrategyNumber: + if ((in->level % 2) == 0 && FPgt(query->y, coord)) + which &= (1 << 2); + break; + case RTContainedByStrategyNumber: + + /* + * For this operator, the query is a box not a point. We + * cheat to the extent of assuming that DatumGetPointP won't + * do anything that would be bad for a pointer-to-box. + */ + boxQuery = DatumGetBoxP(in->scankeys[i].sk_argument); + + if ((in->level % 2) != 0) + { + if (FPlt(boxQuery->high.x, coord)) + which &= (1 << 1); + else if (FPgt(boxQuery->low.x, coord)) + which &= (1 << 2); + } + else + { + if (FPlt(boxQuery->high.y, coord)) + which &= (1 << 1); + else if (FPgt(boxQuery->low.y, coord)) + which &= (1 << 2); + } + break; + default: + elog(ERROR, "unrecognized strategy number: %d", + in->scankeys[i].sk_strategy); + break; + } + + if (which == 0) + break; /* no need to consider remaining conditions */ + } + + /* We must descend into the children identified by which */ out->nodeNumbers = (int *) palloc(sizeof(int) * 2); + out->nNodes = 0; + for (i = 1; i <= 2; i++) + { + if (which & (1 << i)) + out->nodeNumbers[out->nNodes++] = i - 1; + } + + /* Set up level increments, too */ out->levelAdds = (int *) palloc(sizeof(int) * 2); out->levelAdds[0] = 1; out->levelAdds[1] = 1; - out->nNodes = 0; - - switch (in->strategy) - { - case RTLeftStrategyNumber: - out->nNodes = 1; - out->nodeNumbers[0] = 0; - - if ((in->level % 2) == 0 || FPge(query->x, coord)) - { - out->nodeNumbers[1] = 1; - out->nNodes++; - } - break; - case RTRightStrategyNumber: - out->nNodes = 1; - out->nodeNumbers[0] = 1; - - if ((in->level % 2) == 0 || FPle(query->x, coord)) - { - out->nodeNumbers[1] = 0; - out->nNodes++; - } - break; - case RTSameStrategyNumber: - if (in->level % 2) - { - if (FPle(query->x, coord)) - { - out->nodeNumbers[out->nNodes] = 0; - out->nNodes++; - } - if (FPge(query->x, coord)) - { - out->nodeNumbers[out->nNodes] = 1; - out->nNodes++; - } - } - else - { - if (FPle(query->y, coord)) - { - out->nodeNumbers[out->nNodes] = 0; - out->nNodes++; - } - if (FPge(query->y, coord)) - { - out->nodeNumbers[out->nNodes] = 1; - out->nNodes++; - } - } - break; - case RTBelowStrategyNumber: - out->nNodes = 1; - out->nodeNumbers[0] = 0; - - if ((in->level % 2) == 1 || FPge(query->y, coord)) - { - out->nodeNumbers[1] = 1; - out->nNodes++; - } - break; - case RTAboveStrategyNumber: - out->nNodes = 1; - out->nodeNumbers[0] = 1; - - if ((in->level % 2) == 1 || FPle(query->y, coord)) - { - out->nodeNumbers[1] = 0; - out->nNodes++; - } - break; - case RTContainedByStrategyNumber: - - /* - * For this operator, the query is a box not a point. We cheat to - * the extent of assuming that DatumGetPointP won't do anything - * that would be bad for a pointer-to-box. - */ - boxQuery = DatumGetBoxP(in->query); - - out->nNodes = 1; - if (in->level % 2) - { - if (FPlt(boxQuery->high.x, coord)) - out->nodeNumbers[0] = 0; - else if (FPgt(boxQuery->low.x, coord)) - out->nodeNumbers[0] = 1; - else - { - out->nodeNumbers[0] = 0; - out->nodeNumbers[1] = 1; - out->nNodes = 2; - } - } - else - { - if (FPlt(boxQuery->high.y, coord)) - out->nodeNumbers[0] = 0; - else if (FPgt(boxQuery->low.y, coord)) - out->nodeNumbers[0] = 1; - else - { - out->nodeNumbers[0] = 0; - out->nodeNumbers[1] = 1; - out->nNodes = 2; - } - } - break; - default: - elog(ERROR, "unrecognized strategy number: %d", in->strategy); - break; - } PG_RETURN_VOID(); } diff --git a/src/backend/access/spgist/spgquadtreeproc.c b/src/backend/access/spgist/spgquadtreeproc.c index 231749e992..10fafe5864 100644 --- a/src/backend/access/spgist/spgquadtreeproc.c +++ b/src/backend/access/spgist/spgquadtreeproc.c @@ -190,45 +190,21 @@ spg_quad_picksplit(PG_FUNCTION_ARGS) } -/* Subroutine to fill out->nodeNumbers[] for spg_quad_inner_consistent */ -static void -setNodes(spgInnerConsistentOut *out, bool isAll, int first, int second) -{ - if (isAll) - { - out->nNodes = 4; - out->nodeNumbers[0] = 0; - out->nodeNumbers[1] = 1; - out->nodeNumbers[2] = 2; - out->nodeNumbers[3] = 3; - } - else - { - out->nNodes = 2; - out->nodeNumbers[0] = first - 1; - out->nodeNumbers[1] = second - 1; - } -} - - Datum spg_quad_inner_consistent(PG_FUNCTION_ARGS) { spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0); spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1); - Point *query, - *centroid; - BOX *boxQuery; + Point *centroid; + int which; + int i; - query = DatumGetPointP(in->query); Assert(in->hasPrefix); centroid = DatumGetPointP(in->prefixDatum); if (in->allTheSame) { /* Report that all nodes should be visited */ - int i; - out->nNodes = in->nNodes; out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); for (i = 0; i < in->nNodes; i++) @@ -237,76 +213,86 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS) } Assert(in->nNodes == 4); - out->nodeNumbers = (int *) palloc(sizeof(int) * 4); - switch (in->strategy) + /* "which" is a bitmask of quadrants that satisfy all constraints */ + which = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4); + + for (i = 0; i < in->nkeys; i++) { - case RTLeftStrategyNumber: - setNodes(out, SPTEST(point_left, centroid, query), 3, 4); - break; - case RTRightStrategyNumber: - setNodes(out, SPTEST(point_right, centroid, query), 1, 2); - break; - case RTSameStrategyNumber: - out->nNodes = 1; - out->nodeNumbers[0] = getQuadrant(centroid, query) - 1; - break; - case RTBelowStrategyNumber: - setNodes(out, SPTEST(point_below, centroid, query), 2, 3); - break; - case RTAboveStrategyNumber: - setNodes(out, SPTEST(point_above, centroid, query), 1, 4); - break; - case RTContainedByStrategyNumber: + Point *query = DatumGetPointP(in->scankeys[i].sk_argument); + BOX *boxQuery; - /* - * For this operator, the query is a box not a point. We cheat to - * the extent of assuming that DatumGetPointP won't do anything - * that would be bad for a pointer-to-box. - */ - boxQuery = DatumGetBoxP(in->query); + switch (in->scankeys[i].sk_strategy) + { + case RTLeftStrategyNumber: + if (SPTEST(point_right, centroid, query)) + which &= (1 << 3) | (1 << 4); + break; + case RTRightStrategyNumber: + if (SPTEST(point_left, centroid, query)) + which &= (1 << 1) | (1 << 2); + break; + case RTSameStrategyNumber: + which &= (1 << getQuadrant(centroid, query)); + break; + case RTBelowStrategyNumber: + if (SPTEST(point_above, centroid, query)) + which &= (1 << 2) | (1 << 3); + break; + case RTAboveStrategyNumber: + if (SPTEST(point_below, centroid, query)) + which &= (1 << 1) | (1 << 4); + break; + case RTContainedByStrategyNumber: - if (DatumGetBool(DirectFunctionCall2(box_contain_pt, - PointerGetDatum(boxQuery), - PointerGetDatum(centroid)))) - { - /* centroid is in box, so descend to all quadrants */ - setNodes(out, true, 0, 0); - } - else - { - /* identify quadrant(s) containing all corners of box */ - Point p; - int i, - r = 0; + /* + * For this operator, the query is a box not a point. We + * cheat to the extent of assuming that DatumGetPointP won't + * do anything that would be bad for a pointer-to-box. + */ + boxQuery = DatumGetBoxP(in->scankeys[i].sk_argument); - p = boxQuery->low; - r |= 1 << (getQuadrant(centroid, &p) - 1); - - p.y = boxQuery->high.y; - r |= 1 << (getQuadrant(centroid, &p) - 1); - - p = boxQuery->high; - r |= 1 << (getQuadrant(centroid, &p) - 1); - - p.x = boxQuery->low.x; - r |= 1 << (getQuadrant(centroid, &p) - 1); - - /* we must descend into those quadrant(s) */ - out->nNodes = 0; - for (i = 0; i < 4; i++) + if (DatumGetBool(DirectFunctionCall2(box_contain_pt, + PointerGetDatum(boxQuery), + PointerGetDatum(centroid)))) { - if (r & (1 << i)) - { - out->nodeNumbers[out->nNodes] = i; - out->nNodes++; - } + /* centroid is in box, so all quadrants are OK */ } - } - break; - default: - elog(ERROR, "unrecognized strategy number: %d", in->strategy); - break; + else + { + /* identify quadrant(s) containing all corners of box */ + Point p; + int r = 0; + + p = boxQuery->low; + r |= 1 << getQuadrant(centroid, &p); + p.y = boxQuery->high.y; + r |= 1 << getQuadrant(centroid, &p); + p = boxQuery->high; + r |= 1 << getQuadrant(centroid, &p); + p.x = boxQuery->low.x; + r |= 1 << getQuadrant(centroid, &p); + + which &= r; + } + break; + default: + elog(ERROR, "unrecognized strategy number: %d", + in->scankeys[i].sk_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) * 4); + out->nNodes = 0; + for (i = 1; i <= 4; i++) + { + if (which & (1 << i)) + out->nodeNumbers[out->nNodes++] = i - 1; } PG_RETURN_VOID(); @@ -318,9 +304,9 @@ spg_quad_leaf_consistent(PG_FUNCTION_ARGS) { spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0); spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1); - Point *query = DatumGetPointP(in->query); Point *datum = DatumGetPointP(in->leafDatum); bool res; + int i; /* all tests are exact */ out->recheck = false; @@ -328,35 +314,45 @@ spg_quad_leaf_consistent(PG_FUNCTION_ARGS) /* leafDatum is what it is... */ out->leafValue = in->leafDatum; - switch (in->strategy) + /* Perform the required comparison(s) */ + res = true; + for (i = 0; i < in->nkeys; i++) { - case RTLeftStrategyNumber: - res = SPTEST(point_left, datum, query); - break; - case RTRightStrategyNumber: - res = SPTEST(point_right, datum, query); - break; - case RTSameStrategyNumber: - res = SPTEST(point_eq, datum, query); - break; - case RTBelowStrategyNumber: - res = SPTEST(point_below, datum, query); - break; - case RTAboveStrategyNumber: - res = SPTEST(point_above, datum, query); - break; - case RTContainedByStrategyNumber: + Point *query = DatumGetPointP(in->scankeys[i].sk_argument); - /* - * For this operator, the query is a box not a point. We cheat to - * the extent of assuming that DatumGetPointP won't do anything - * that would be bad for a pointer-to-box. - */ - res = SPTEST(box_contain_pt, query, datum); - break; - default: - elog(ERROR, "unrecognized strategy number: %d", in->strategy); - res = false; + switch (in->scankeys[i].sk_strategy) + { + case RTLeftStrategyNumber: + res = SPTEST(point_left, datum, query); + break; + case RTRightStrategyNumber: + res = SPTEST(point_right, datum, query); + break; + case RTSameStrategyNumber: + res = SPTEST(point_eq, datum, query); + break; + case RTBelowStrategyNumber: + res = SPTEST(point_below, datum, query); + break; + case RTAboveStrategyNumber: + res = SPTEST(point_above, datum, query); + break; + case RTContainedByStrategyNumber: + + /* + * For this operator, the query is a box not a point. We + * cheat to the extent of assuming that DatumGetPointP won't + * do anything that would be bad for a pointer-to-box. + */ + res = SPTEST(box_contain_pt, query, datum); + break; + default: + elog(ERROR, "unrecognized strategy number: %d", + in->scankeys[i].sk_strategy); + break; + } + + if (!res) break; } diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c index 22cfcc8792..99b0852611 100644 --- a/src/backend/access/spgist/spgscan.c +++ b/src/backend/access/spgist/spgscan.c @@ -56,18 +56,25 @@ freeScanStack(SpGistScanOpaque so) } /* - * Initialize scanStack with a single entry for the root page, resetting + * Initialize scanStack to search the root page, resetting * any previously active scan */ static void resetSpGistScanOpaque(SpGistScanOpaque so) { - ScanStackEntry *startEntry = palloc0(sizeof(ScanStackEntry)); - - ItemPointerSet(&startEntry->ptr, SPGIST_HEAD_BLKNO, FirstOffsetNumber); + ScanStackEntry *startEntry; freeScanStack(so); - so->scanStack = list_make1(startEntry); + + Assert(!so->searchNulls); /* XXX fixme */ + + if (so->searchNonNulls) + { + /* Stack a work item to scan the non-null index entries */ + startEntry = (ScanStackEntry *) palloc0(sizeof(ScanStackEntry)); + ItemPointerSet(&startEntry->ptr, SPGIST_HEAD_BLKNO, FirstOffsetNumber); + so->scanStack = list_make1(startEntry); + } if (so->want_itup) { @@ -80,6 +87,82 @@ resetSpGistScanOpaque(SpGistScanOpaque so) so->iPtr = so->nPtrs = 0; } +/* + * Prepare scan keys in SpGistScanOpaque from caller-given scan keys + * + * Sets searchNulls, searchNonNulls, numberOfKeys, keyData fields of *so. + * + * The point here is to eliminate null-related considerations from what the + * opclass consistent functions need to deal with. We assume all SPGiST- + * indexable operators are strict, so any null RHS value makes the scan + * condition unsatisfiable. We also pull out any IS NULL/IS NOT NULL + * conditions; their effect is reflected into searchNulls/searchNonNulls. + */ +static void +spgPrepareScanKeys(IndexScanDesc scan) +{ + SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque; + bool qual_ok; + bool haveIsNull; + bool haveNotNull; + int nkeys; + int i; + + if (scan->numberOfKeys <= 0) + { + /* If no quals, whole-index scan is required */ + so->searchNulls = true; + so->searchNonNulls = true; + so->numberOfKeys = 0; + return; + } + + /* Examine the given quals */ + qual_ok = true; + haveIsNull = haveNotNull = false; + nkeys = 0; + for (i = 0; i < scan->numberOfKeys; i++) + { + ScanKey skey = &scan->keyData[i]; + + if (skey->sk_flags & SK_SEARCHNULL) + haveIsNull = true; + else if (skey->sk_flags & SK_SEARCHNOTNULL) + haveNotNull = true; + else if (skey->sk_flags & SK_ISNULL) + { + /* ordinary qual with null argument - unsatisfiable */ + qual_ok = false; + break; + } + else + { + /* ordinary qual, propagate into so->keyData */ + so->keyData[nkeys++] = *skey; + /* this effectively creates a not-null requirement */ + haveNotNull = true; + } + } + + /* IS NULL in combination with something else is unsatisfiable */ + if (haveIsNull && haveNotNull) + qual_ok = false; + + /* Emit results */ + if (qual_ok) + { + so->searchNulls = haveIsNull; + so->searchNonNulls = haveNotNull; + so->numberOfKeys = nkeys; + } + else + { + so->searchNulls = false; + so->searchNonNulls = false; + so->numberOfKeys = 0; + } +} + Datum spgbeginscan(PG_FUNCTION_ARGS) { @@ -92,13 +175,16 @@ spgbeginscan(PG_FUNCTION_ARGS) scan = RelationGetIndexScan(rel, keysz, 0); so = (SpGistScanOpaque) palloc0(sizeof(SpGistScanOpaqueData)); + if (keysz > 0) + so->keyData = (ScanKey) palloc(sizeof(ScanKeyData) * keysz); + else + so->keyData = NULL; initSpGistState(&so->state, scan->indexRelation); so->tempCxt = AllocSetContextCreate(CurrentMemoryContext, "SP-GiST search temporary context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); - resetSpGistScanOpaque(so); /* Set up indexTupDesc and xs_itupdesc in case it's an index-only scan */ so->indexTupDesc = scan->xs_itupdesc = RelationGetDescr(rel); @@ -115,12 +201,17 @@ spgrescan(PG_FUNCTION_ARGS) SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque; ScanKey scankey = (ScanKey) PG_GETARG_POINTER(1); + /* copy scankeys into local storage */ if (scankey && scan->numberOfKeys > 0) { memmove(scan->keyData, scankey, scan->numberOfKeys * sizeof(ScanKeyData)); } + /* preprocess scankeys, set up the representation in *so */ + spgPrepareScanKeys(scan); + + /* set up starting stack entries */ resetSpGistScanOpaque(so); PG_RETURN_VOID(); @@ -162,53 +253,34 @@ spgLeafTest(Relation index, SpGistScanOpaque so, Datum leafDatum, int level, Datum reconstructedValue, Datum *leafValue, bool *recheck) { - bool result = true; + bool result; spgLeafConsistentIn in; spgLeafConsistentOut out; FmgrInfo *procinfo; MemoryContext oldCtx; - int i; - *leafValue = (Datum) 0; - *recheck = false; + /* use temp context for calling leaf_consistent */ + oldCtx = MemoryContextSwitchTo(so->tempCxt); - /* set up values that are the same for all quals */ + in.scankeys = so->keyData; + in.nkeys = so->numberOfKeys; in.reconstructedValue = reconstructedValue; in.level = level; in.returnData = so->want_itup; in.leafDatum = leafDatum; - /* Apply each leaf consistency check, working in the temp context */ - oldCtx = MemoryContextSwitchTo(so->tempCxt); + out.leafValue = (Datum) 0; + out.recheck = false; procinfo = index_getprocinfo(index, 1, SPGIST_LEAF_CONSISTENT_PROC); + result = DatumGetBool(FunctionCall2Coll(procinfo, + index->rd_indcollation[0], + PointerGetDatum(&in), + PointerGetDatum(&out))); - for (i = 0; i < so->numberOfKeys; i++) - { - ScanKey skey = &so->keyData[i]; + *leafValue = out.leafValue; + *recheck = out.recheck; - /* Assume SPGiST-indexable operators are strict */ - if (skey->sk_flags & SK_ISNULL) - { - result = false; - break; - } - - in.strategy = skey->sk_strategy; - in.query = skey->sk_argument; - - out.leafValue = (Datum) 0; - out.recheck = false; - - result = DatumGetBool(FunctionCall2Coll(procinfo, - skey->sk_collation, - PointerGetDatum(&in), - PointerGetDatum(&out))); - *leafValue = out.leafValue; - *recheck |= out.recheck; - if (!result) - break; - } MemoryContextSwitchTo(oldCtx); return result; @@ -349,8 +421,13 @@ redirect: else /* page is inner */ { SpGistInnerTuple innerTuple; + spgInnerConsistentIn in; + spgInnerConsistentOut out; + FmgrInfo *procinfo; + SpGistNodeTuple *nodes; SpGistNodeTuple node; int i; + MemoryContext oldCtx; innerTuple = (SpGistInnerTuple) PageGetItem(page, PageGetItemId(page, offset)); @@ -368,144 +445,68 @@ redirect: innerTuple->tupstate); } - if (so->numberOfKeys == 0) + /* use temp context for calling inner_consistent */ + oldCtx = MemoryContextSwitchTo(so->tempCxt); + + in.scankeys = so->keyData; + in.nkeys = so->numberOfKeys; + in.reconstructedValue = stackEntry->reconstructedValue; + in.level = stackEntry->level; + in.returnData = so->want_itup; + in.allTheSame = innerTuple->allTheSame; + in.hasPrefix = (innerTuple->prefixSize > 0); + in.prefixDatum = SGITDATUM(innerTuple, &so->state); + in.nNodes = innerTuple->nNodes; + in.nodeLabels = spgExtractNodeLabels(&so->state, innerTuple); + + /* collect node pointers */ + nodes = (SpGistNodeTuple *) palloc(sizeof(SpGistNodeTuple) * in.nNodes); + SGITITERATE(innerTuple, i, node) { - /* - * This case cannot happen at the moment, because we don't - * set pg_am.amoptionalkey for SP-GiST. In order for full - * index scans to produce correct answers, we'd need to - * index nulls, which we don't. - */ - Assert(false); - -#ifdef NOT_USED - /* - * A full index scan could be done approximately like this, - * but note that reconstruction of indexed values would be - * impossible unless the API for inner_consistent is changed. - */ - SGITITERATE(innerTuple, i, node) - { - if (ItemPointerIsValid(&node->t_tid)) - { - ScanStackEntry *newEntry = palloc(sizeof(ScanStackEntry)); - - newEntry->ptr = node->t_tid; - newEntry->level = -1; - newEntry->reconstructedValue = (Datum) 0; - so->scanStack = lcons(newEntry, so->scanStack); - } - } -#endif + nodes[i] = node; } - else + + memset(&out, 0, sizeof(out)); + + procinfo = index_getprocinfo(index, 1, SPGIST_INNER_CONSISTENT_PROC); + FunctionCall2Coll(procinfo, + index->rd_indcollation[0], + PointerGetDatum(&in), + PointerGetDatum(&out)); + + MemoryContextSwitchTo(oldCtx); + + /* If allTheSame, they should all or none of 'em match */ + if (innerTuple->allTheSame) + if (out.nNodes != 0 && out.nNodes != in.nNodes) + elog(ERROR, "inconsistent inner_consistent results for allTheSame inner tuple"); + + for (i = 0; i < out.nNodes; i++) { - spgInnerConsistentIn in; - spgInnerConsistentOut out; - FmgrInfo *procinfo; - SpGistNodeTuple *nodes; - int *andMap; - int *levelAdds; - Datum *reconstructedValues; - int j, - nMatches = 0; - MemoryContext oldCtx; + int nodeN = out.nodeNumbers[i]; - /* use temp context for calling inner_consistent */ - oldCtx = MemoryContextSwitchTo(so->tempCxt); - - /* set up values that are the same for all scankeys */ - in.reconstructedValue = stackEntry->reconstructedValue; - in.level = stackEntry->level; - in.returnData = so->want_itup; - in.allTheSame = innerTuple->allTheSame; - in.hasPrefix = (innerTuple->prefixSize > 0); - in.prefixDatum = SGITDATUM(innerTuple, &so->state); - in.nNodes = innerTuple->nNodes; - in.nodeLabels = spgExtractNodeLabels(&so->state, innerTuple); - - /* collect node pointers */ - nodes = (SpGistNodeTuple *) palloc(sizeof(SpGistNodeTuple) * in.nNodes); - SGITITERATE(innerTuple, i, node) + Assert(nodeN >= 0 && nodeN < in.nNodes); + if (ItemPointerIsValid(&nodes[nodeN]->t_tid)) { - nodes[i] = node; - } + ScanStackEntry *newEntry; - andMap = (int *) palloc0(sizeof(int) * in.nNodes); - levelAdds = (int *) palloc0(sizeof(int) * in.nNodes); - reconstructedValues = (Datum *) palloc0(sizeof(Datum) * in.nNodes); + /* Create new work item for this node */ + newEntry = palloc(sizeof(ScanStackEntry)); + newEntry->ptr = nodes[nodeN]->t_tid; + if (out.levelAdds) + newEntry->level = stackEntry->level + out.levelAdds[i]; + else + newEntry->level = stackEntry->level; + /* Must copy value out of temp context */ + if (out.reconstructedValues) + newEntry->reconstructedValue = + datumCopy(out.reconstructedValues[i], + so->state.attType.attbyval, + so->state.attType.attlen); + else + newEntry->reconstructedValue = (Datum) 0; - procinfo = index_getprocinfo(index, 1, SPGIST_INNER_CONSISTENT_PROC); - - for (j = 0; j < so->numberOfKeys; j++) - { - ScanKey skey = &so->keyData[j]; - - /* Assume SPGiST-indexable operators are strict */ - if (skey->sk_flags & SK_ISNULL) - { - nMatches = 0; - break; - } - - in.strategy = skey->sk_strategy; - in.query = skey->sk_argument; - - memset(&out, 0, sizeof(out)); - - FunctionCall2Coll(procinfo, - skey->sk_collation, - PointerGetDatum(&in), - PointerGetDatum(&out)); - - /* If allTheSame, they should all or none of 'em match */ - if (innerTuple->allTheSame) - if (out.nNodes != 0 && out.nNodes != in.nNodes) - elog(ERROR, "inconsistent inner_consistent results for allTheSame inner tuple"); - - nMatches = 0; - for (i = 0; i < out.nNodes; i++) - { - int nodeN = out.nodeNumbers[i]; - - andMap[nodeN]++; - if (andMap[nodeN] == j + 1) - nMatches++; - if (out.levelAdds) - levelAdds[nodeN] = out.levelAdds[i]; - if (out.reconstructedValues) - reconstructedValues[nodeN] = out.reconstructedValues[i]; - } - - /* quit as soon as all nodes have failed some qual */ - if (nMatches == 0) - break; - } - - MemoryContextSwitchTo(oldCtx); - - if (nMatches > 0) - { - for (i = 0; i < in.nNodes; i++) - { - if (andMap[i] == so->numberOfKeys && - ItemPointerIsValid(&nodes[i]->t_tid)) - { - ScanStackEntry *newEntry; - - /* Create new work item for this node */ - newEntry = palloc(sizeof(ScanStackEntry)); - newEntry->ptr = nodes[i]->t_tid; - newEntry->level = stackEntry->level + levelAdds[i]; - /* Must copy value out of temp context */ - newEntry->reconstructedValue = - datumCopy(reconstructedValues[i], - so->state.attType.attbyval, - so->state.attType.attlen); - - so->scanStack = lcons(newEntry, so->scanStack); - } - } + so->scanStack = lcons(newEntry, so->scanStack); } } } @@ -536,10 +537,7 @@ spggetbitmap(PG_FUNCTION_ARGS) TIDBitmap *tbm = (TIDBitmap *) PG_GETARG_POINTER(1); SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque; - /* Copy scankey to *so so we don't need to pass it around separately */ - so->numberOfKeys = scan->numberOfKeys; - so->keyData = scan->keyData; - /* Ditto for the want_itup flag */ + /* Copy want_itup to *so so we don't need to pass it around separately */ so->want_itup = false; so->tbm = tbm; @@ -583,10 +581,7 @@ spggettuple(PG_FUNCTION_ARGS) if (dir != ForwardScanDirection) elog(ERROR, "SP-GiST only supports forward scan direction"); - /* Copy scankey to *so so we don't need to pass it around separately */ - so->numberOfKeys = scan->numberOfKeys; - so->keyData = scan->keyData; - /* Ditto for the want_itup flag */ + /* Copy want_itup to *so so we don't need to pass it around separately */ so->want_itup = scan->xs_want_itup; for (;;) diff --git a/src/backend/access/spgist/spgtextproc.c b/src/backend/access/spgist/spgtextproc.c index b194fc1b13..656015ea7e 100644 --- a/src/backend/access/spgist/spgtextproc.c +++ b/src/backend/access/spgist/spgtextproc.c @@ -362,25 +362,12 @@ spg_text_inner_consistent(PG_FUNCTION_ARGS) { spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0); spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1); - StrategyNumber strategy = in->strategy; - text *inText; - int inSize; - int i; + bool collate_is_c = lc_collate_is_c(PG_GET_COLLATION()); text *reconstrText = NULL; int maxReconstrLen = 0; text *prefixText = NULL; int prefixSize = 0; - - /* - * If it's a collation-aware operator, but the collation is C, we can - * treat it as non-collation-aware. - */ - if (strategy > 10 && - lc_collate_is_c(PG_GET_COLLATION())) - strategy -= 10; - - inText = DatumGetTextPP(in->query); - inSize = VARSIZE_ANY_EXHDR(inText); + int i; /* * Reconstruct values represented at this tuple, including parent data, @@ -431,8 +418,8 @@ spg_text_inner_consistent(PG_FUNCTION_ARGS) { uint8 nodeChar = DatumGetUInt8(in->nodeLabels[i]); int thisLen; - int r; - bool res = false; + bool res = true; + int j; /* If nodeChar is zero, don't include it in data */ if (nodeChar == '\0') @@ -443,38 +430,57 @@ spg_text_inner_consistent(PG_FUNCTION_ARGS) thisLen = maxReconstrLen; } - r = memcmp(VARDATA(reconstrText), VARDATA_ANY(inText), - Min(inSize, thisLen)); - - switch (strategy) + for (j = 0; j < in->nkeys; j++) { - case BTLessStrategyNumber: - case BTLessEqualStrategyNumber: - if (r <= 0) - res = true; - break; - case BTEqualStrategyNumber: - if (r == 0 && inSize >= thisLen) - res = true; - break; - case BTGreaterEqualStrategyNumber: - case BTGreaterStrategyNumber: - if (r >= 0) - res = true; - break; - case BTLessStrategyNumber + 10: - case BTLessEqualStrategyNumber + 10: - case BTGreaterEqualStrategyNumber + 10: - case BTGreaterStrategyNumber + 10: - /* - * with non-C collation we need to traverse whole tree :-( - */ - res = true; - break; - default: - elog(ERROR, "unrecognized strategy number: %d", - in->strategy); - break; + StrategyNumber strategy = in->scankeys[j].sk_strategy; + text *inText; + int inSize; + int r; + + /* + * If it's a collation-aware operator, but the collation is C, we + * can treat it as non-collation-aware. With non-C collation we + * need to traverse whole tree :-( so there's no point in making + * any check here. + */ + if (strategy > 10) + { + if (collate_is_c) + strategy -= 10; + else + continue; + } + + inText = DatumGetTextPP(in->scankeys[j].sk_argument); + inSize = VARSIZE_ANY_EXHDR(inText); + + r = memcmp(VARDATA(reconstrText), VARDATA_ANY(inText), + Min(inSize, thisLen)); + + switch (strategy) + { + case BTLessStrategyNumber: + case BTLessEqualStrategyNumber: + if (r > 0) + res = false; + break; + case BTEqualStrategyNumber: + if (r != 0 || inSize < thisLen) + res = false; + break; + case BTGreaterEqualStrategyNumber: + case BTGreaterStrategyNumber: + if (r < 0) + res = false; + break; + default: + elog(ERROR, "unrecognized strategy number: %d", + in->scankeys[j].sk_strategy); + break; + } + + if (!res) + break; /* no need to consider remaining conditions */ } if (res) @@ -496,16 +502,13 @@ spg_text_leaf_consistent(PG_FUNCTION_ARGS) { spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0); spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1); - StrategyNumber strategy = in->strategy; - text *query = DatumGetTextPP(in->query); int level = in->level; text *leafValue, *reconstrValue = NULL; char *fullValue; int fullLen; - int queryLen; - int r; bool res; + int j; /* all tests are exact */ out->recheck = false; @@ -518,18 +521,8 @@ spg_text_leaf_consistent(PG_FUNCTION_ARGS) Assert(level == 0 ? reconstrValue == NULL : VARSIZE_ANY_EXHDR(reconstrValue) == level); + /* Reconstruct the full string represented by this leaf tuple */ fullLen = level + VARSIZE_ANY_EXHDR(leafValue); - - queryLen = VARSIZE_ANY_EXHDR(query); - - /* - * For an equality check, we needn't reconstruct fullValue if not same - * length; it can't match - */ - if (strategy == BTEqualStrategyNumber && queryLen != fullLen) - PG_RETURN_BOOL(false); - - /* Else, reconstruct the full string represented by this leaf tuple */ if (VARSIZE_ANY_EXHDR(leafValue) == 0 && level > 0) { fullValue = VARDATA(reconstrValue); @@ -549,54 +542,67 @@ spg_text_leaf_consistent(PG_FUNCTION_ARGS) out->leafValue = PointerGetDatum(fullText); } - /* Run the appropriate type of comparison */ - if (strategy > 10) + /* Perform the required comparison(s) */ + res = true; + for (j = 0; j < in->nkeys; j++) { - /* Collation-aware comparison */ - strategy -= 10; + StrategyNumber strategy = in->scankeys[j].sk_strategy; + text *query = DatumGetTextPP(in->scankeys[j].sk_argument); + int queryLen = VARSIZE_ANY_EXHDR(query); + int r; - /* If asserts are enabled, verify encoding of reconstructed string */ - Assert(pg_verifymbstr(fullValue, fullLen, false)); + if (strategy > 10) + { + /* Collation-aware comparison */ + strategy -= 10; - r = varstr_cmp(fullValue, Min(queryLen, fullLen), - VARDATA_ANY(query), Min(queryLen, fullLen), - PG_GET_COLLATION()); - } - else - { - /* Non-collation-aware comparison */ - r = memcmp(fullValue, VARDATA_ANY(query), Min(queryLen, fullLen)); - } + /* If asserts enabled, verify encoding of reconstructed string */ + Assert(pg_verifymbstr(fullValue, fullLen, false)); - if (r == 0) - { - if (queryLen > fullLen) - r = -1; - else if (queryLen < fullLen) - r = 1; - } + r = varstr_cmp(fullValue, Min(queryLen, fullLen), + VARDATA_ANY(query), Min(queryLen, fullLen), + PG_GET_COLLATION()); + } + else + { + /* Non-collation-aware comparison */ + r = memcmp(fullValue, VARDATA_ANY(query), Min(queryLen, fullLen)); + } - switch (strategy) - { - case BTLessStrategyNumber: - res = (r < 0); - break; - case BTLessEqualStrategyNumber: - res = (r <= 0); - break; - case BTEqualStrategyNumber: - res = (r == 0); - break; - case BTGreaterEqualStrategyNumber: - res = (r >= 0); - break; - case BTGreaterStrategyNumber: - res = (r > 0); - break; - default: - elog(ERROR, "unrecognized strategy number: %d", in->strategy); - res = false; - break; + if (r == 0) + { + if (queryLen > fullLen) + r = -1; + else if (queryLen < fullLen) + r = 1; + } + + switch (strategy) + { + case BTLessStrategyNumber: + res = (r < 0); + break; + case BTLessEqualStrategyNumber: + res = (r <= 0); + break; + case BTEqualStrategyNumber: + res = (r == 0); + break; + case BTGreaterEqualStrategyNumber: + res = (r >= 0); + break; + case BTGreaterStrategyNumber: + res = (r > 0); + break; + default: + elog(ERROR, "unrecognized strategy number: %d", + in->scankeys[j].sk_strategy); + res = false; + break; + } + + if (!res) + break; /* no need to consider remaining conditions */ } PG_RETURN_BOOL(res); diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h index cd6de2c98d..8d0205e691 100644 --- a/src/include/access/spgist.h +++ b/src/include/access/spgist.h @@ -128,8 +128,8 @@ typedef struct spgPickSplitOut */ typedef struct spgInnerConsistentIn { - StrategyNumber strategy; /* operator strategy number */ - Datum query; /* operator's RHS value */ + ScanKey scankeys; /* array of operators and comparison values */ + int nkeys; /* length of array */ Datum reconstructedValue; /* value reconstructed at parent */ int level; /* current level (counting from zero) */ @@ -156,8 +156,8 @@ typedef struct spgInnerConsistentOut */ typedef struct spgLeafConsistentIn { - StrategyNumber strategy; /* operator strategy number */ - Datum query; /* operator's RHS value */ + ScanKey scankeys; /* array of operators and comparison values */ + int nkeys; /* length of array */ Datum reconstructedValue; /* value reconstructed at parent */ int level; /* current level (counting from zero) */ diff --git a/src/include/access/spgist_private.h b/src/include/access/spgist_private.h index fa23acf6cd..76ea5a1578 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -126,7 +126,11 @@ typedef struct SpGistScanOpaqueData SpGistState state; /* see above */ MemoryContext tempCxt; /* short-lived memory context */ - /* Index quals for scan (copied from IndexScanDesc for convenience) */ + /* Control flags showing whether to search nulls and/or non-nulls */ + bool searchNulls; /* scan matches (all) null entries */ + bool searchNonNulls; /* scan matches (some) non-null entries */ + + /* Index quals to be passed to opclass (null-related quals removed) */ int numberOfKeys; /* number of index qualifier conditions */ ScanKey keyData; /* array of index qualifier descriptors */