From d04c8ed9044eccebce043143a930617e3998c005 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Thu, 26 Mar 2015 19:12:00 +0200 Subject: [PATCH] Add support for index-only scans in GiST. This adds a new GiST opclass method, 'fetch', which is used to reconstruct the original Datum from the value stored in the index. Also, the 'canreturn' index AM interface function gains a new 'attno' argument. That makes it possible to use index-only scans on a multi-column index where some of the opclasses support index-only scans but some do not. This patch adds support in the box and point opclasses. Other opclasses can added later as follow-on patches (btree_gist would be particularly interesting). Anastasia Lubennikova, with additional fixes and modifications by me. --- doc/src/sgml/catalogs.sgml | 4 +- doc/src/sgml/gist.sgml | 73 ++++++++++- doc/src/sgml/indexam.sgml | 15 ++- src/backend/access/gist/gist.c | 8 ++ src/backend/access/gist/gistget.c | 66 +++++++++- src/backend/access/gist/gistproc.c | 37 ++++++ src/backend/access/gist/gistscan.c | 18 +++ src/backend/access/gist/gistutil.c | 64 ++++++++- src/backend/access/index/indexam.c | 12 +- src/backend/access/spgist/spgscan.c | 1 + src/backend/optimizer/path/indxpath.c | 22 ++-- src/backend/optimizer/util/plancat.c | 3 +- src/include/access/genam.h | 2 +- src/include/access/gist.h | 3 +- src/include/access/gist_private.h | 9 +- src/include/catalog/catversion.h | 2 +- src/include/catalog/pg_am.h | 2 +- src/include/catalog/pg_amproc.h | 2 + src/include/catalog/pg_proc.h | 10 +- src/include/nodes/relation.h | 3 +- src/include/utils/geo_decls.h | 3 + src/test/regress/expected/create_index.out | 70 +++++----- src/test/regress/expected/gist.out | 146 +++++++++++++++++++++ src/test/regress/sql/gist.sql | 73 +++++++++++ 24 files changed, 575 insertions(+), 73 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index dfed546f51..d0b78f2782 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -724,8 +724,8 @@ amcanreturn regproc pg_proc.oid - Function to check whether index supports index-only scans, - or zero if none + Function to check whether an index column supports index-only + scans. Can be zero if index-only scans are never supported. diff --git a/doc/src/sgml/gist.sgml b/doc/src/sgml/gist.sgml index 31ce279004..e7d1ff9d83 100644 --- a/doc/src/sgml/gist.sgml +++ b/doc/src/sgml/gist.sgml @@ -266,7 +266,7 @@ CREATE INDEX ON my_table USING gist (my_inet_column inet_ops); There are seven methods that an index operator class for - GiST must provide, and an eighth that is optional. + GiST must provide, and two that are optional. Correctness of the index is ensured by proper implementation of the same, consistent and union methods, while efficiency (size and speed) of the @@ -282,7 +282,8 @@ CREATE INDEX ON my_table USING gist (my_inet_column inet_ops); of the CREATE OPERATOR CLASS command can be used. The optional eighth method is distance, which is needed if the operator class wishes to support ordered scans (nearest-neighbor - searches). + searches). The optional ninth method fetch is needed if the + operator class wishes to support index-only scans. @@ -506,7 +507,7 @@ my_compress(PG_FUNCTION_ARGS) The reverse of the compress method. Converts the index representation of the data item into a format that can be - manipulated by the database. + manipulated by the other GiST methods in the operator class. @@ -807,6 +808,72 @@ my_distance(PG_FUNCTION_ARGS) + + fetch + + + Converts the compressed index representation of the data item into the + original data type, for index-only scans. The returned data must be an + exact, non-lossy copy of the originally indexed value. + + + + The SQL declaration of the function must look like this: + + +CREATE OR REPLACE FUNCTION my_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + + + The argument is a pointer to a GISTENTRY struct. On + entry, its 'key' field contains a non-NULL leaf datum in its + compressed form. The return value is another GISTENTRY + struct, whose 'key' field contains the same datum in the original, + uncompressed form. If the opclass' compress function does nothing for + leaf entries, the fetch method can return the argument as is. + + + + The matching code in the C module could then follow this skeleton: + + +Datum my_fetch(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(my_fetch); + +Datum +my_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + input_data_type *in = DatumGetP(entry->key); + fetched_data_type *fetched_data; + GISTENTRY *retval; + + retval = palloc(sizeof(GISTENTRY)); + fetched_data = palloc(sizeof(fetched_data_type)); + + /* + * Convert 'fetched_data' into the a Datum of the original datatype. + */ + + /* fill *retval from fetch_data. */ + gistentryinit(*retval, PointerGetDatum(converted_datum), + entry->rel, entry->page, entry->offset, FALSE); + + PG_RETURN_POINTER(retval); +} + + + + + If the compress method is lossy for leaf entries, the operator class + cannot support index-only scans, and must not define a 'fetch' + function. + + + + diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml index 157047a23a..1c09bae395 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -274,14 +274,15 @@ amvacuumcleanup (IndexVacuumInfo *info, bool -amcanreturn (Relation indexRelation); +amcanreturn (Relation indexRelation, int attno); - Check whether the index can support index-only scans by - returning the indexed column values for an index entry in the form of an - IndexTuple. Return TRUE if so, else FALSE. If the index AM can never - support index-only scans (an example is hash, which stores only - the hash values not the original data), it is sufficient to set its - amcanreturn field to zero in pg_am. + Check whether the index can support index-only scans on the + given column, by returning the indexed column values for an index entry in + the form of an IndexTuple. The attribute number + is 1-based, i.e. the first columns attno is 1. Returns TRUE if supported, + else FALSE. If the access method does not support index-only scans at all, + the amcanreturn field in its pg_am row can + be set to zero. diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index db2a452a4a..96b7701633 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -1404,6 +1404,14 @@ initGISTstate(Relation index) else giststate->distanceFn[i].fn_oid = InvalidOid; + /* opclasses are not required to provide a Fetch method */ + if (OidIsValid(index_getprocid(index, i + 1, GIST_FETCH_PROC))) + fmgr_info_copy(&(giststate->fetchFn[i]), + index_getprocinfo(index, i + 1, GIST_FETCH_PROC), + scanCxt); + else + giststate->fetchFn[i].fn_oid = InvalidOid; + /* * If the index column has a specified collation, we should honor that * while doing comparisons. However, we may have a collatable storage diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c index 717cb85f77..e4c00c2c9f 100644 --- a/src/backend/access/gist/gistget.c +++ b/src/backend/access/gist/gistget.c @@ -228,7 +228,9 @@ gistindex_keytest(IndexScanDesc scan, * tuples should be reported directly into the bitmap. If they are NULL, * we're doing a plain or ordered indexscan. For a plain indexscan, heap * tuple TIDs are returned into so->pageData[]. For an ordered indexscan, - * heap tuple TIDs are pushed into individual search queue items. + * heap tuple TIDs are pushed into individual search queue items. In an + * index-only scan, reconstructed index tuples are returned along with the + * TIDs. * * If we detect that the index page has split since we saw its downlink * in the parent, we push its new right sibling onto the queue so the @@ -239,6 +241,8 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances, TIDBitmap *tbm, int64 *ntids) { GISTScanOpaque so = (GISTScanOpaque) scan->opaque; + GISTSTATE *giststate = so->giststate; + Relation r = scan->indexRelation; Buffer buffer; Page page; GISTPageOpaque opaque; @@ -288,6 +292,8 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances, } so->nPageData = so->curPageData = 0; + if (so->pageDataCxt) + MemoryContextReset(so->pageDataCxt); /* * check all tuples on page @@ -326,10 +332,21 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances, else if (scan->numberOfOrderBys == 0 && GistPageIsLeaf(page)) { /* - * Non-ordered scan, so report heap tuples in so->pageData[] + * Non-ordered scan, so report tuples in so->pageData[] */ so->pageData[so->nPageData].heapPtr = it->t_tid; so->pageData[so->nPageData].recheck = recheck; + + /* + * In an index-only scan, also fetch the data from the tuple. + */ + if (scan->xs_want_itup) + { + oldcxt = MemoryContextSwitchTo(so->pageDataCxt); + so->pageData[so->nPageData].ftup = + gistFetchTuple(giststate, r, it); + MemoryContextSwitchTo(oldcxt); + } so->nPageData++; } else @@ -352,6 +369,12 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances, item->blkno = InvalidBlockNumber; item->data.heap.heapPtr = it->t_tid; item->data.heap.recheck = recheck; + + /* + * In an index-only scan, also fetch the data from the tuple. + */ + if (scan->xs_want_itup) + item->data.heap.ftup = gistFetchTuple(giststate, r, it); } else { @@ -412,6 +435,13 @@ getNextNearest(IndexScanDesc scan) GISTScanOpaque so = (GISTScanOpaque) scan->opaque; bool res = false; + if (scan->xs_itup) + { + /* free previously returned tuple */ + pfree(scan->xs_itup); + scan->xs_itup = NULL; + } + do { GISTSearchItem *item = getNextGISTSearchItem(so); @@ -424,6 +454,10 @@ getNextNearest(IndexScanDesc scan) /* found a heap item at currently minimal distance */ scan->xs_ctup.t_self = item->data.heap.heapPtr; scan->xs_recheck = item->data.heap.recheck; + + /* in an index-only scan, also return the reconstructed tuple. */ + if (scan->xs_want_itup) + scan->xs_itup = item->data.heap.ftup; res = true; } else @@ -465,6 +499,8 @@ gistgettuple(PG_FUNCTION_ARGS) so->firstCall = false; so->curPageData = so->nPageData = 0; + if (so->pageDataCxt) + MemoryContextReset(so->pageDataCxt); fakeItem.blkno = GIST_ROOT_BLKNO; memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN)); @@ -483,10 +519,17 @@ gistgettuple(PG_FUNCTION_ARGS) { if (so->curPageData < so->nPageData) { + /* continuing to return tuples from a leaf page */ scan->xs_ctup.t_self = so->pageData[so->curPageData].heapPtr; scan->xs_recheck = so->pageData[so->curPageData].recheck; + + /* in an index-only scan, also return the reconstructed tuple */ + if (scan->xs_want_itup) + scan->xs_itup = so->pageData[so->curPageData].ftup; + so->curPageData++; + PG_RETURN_BOOL(true); } @@ -533,6 +576,8 @@ gistgetbitmap(PG_FUNCTION_ARGS) /* Begin the scan by processing the root page */ so->curPageData = so->nPageData = 0; + if (so->pageDataCxt) + MemoryContextReset(so->pageDataCxt); fakeItem.blkno = GIST_ROOT_BLKNO; memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN)); @@ -558,3 +603,20 @@ gistgetbitmap(PG_FUNCTION_ARGS) PG_RETURN_INT64(ntids); } + +/* + * Can we do index-only scans on the given index column? + * + * Opclasses that implement a fetch function support index-only scans. + */ +Datum +gistcanreturn(PG_FUNCTION_ARGS) +{ + Relation index = (Relation) PG_GETARG_POINTER(0); + int attno = PG_GETARG_INT32(1); + + if (OidIsValid(index_getprocid(index, attno, GIST_FETCH_PROC))) + PG_RETURN_BOOL(true); + else + PG_RETURN_BOOL(false); +} diff --git a/src/backend/access/gist/gistproc.c b/src/backend/access/gist/gistproc.c index 9fab6c87c0..9d21e3fb94 100644 --- a/src/backend/access/gist/gistproc.c +++ b/src/backend/access/gist/gistproc.c @@ -151,6 +151,16 @@ gist_box_decompress(PG_FUNCTION_ARGS) PG_RETURN_POINTER(PG_GETARG_POINTER(0)); } +/* + * GiST Fetch method for boxes + * do not do anything --- we just return the stored box as is. + */ +Datum +gist_box_fetch(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(PG_GETARG_POINTER(0)); +} + /* * The GiST Penalty method for boxes (also used for points) * @@ -1186,6 +1196,33 @@ gist_point_compress(PG_FUNCTION_ARGS) PG_RETURN_POINTER(entry); } +/* + * GiST Fetch method for point + * + * Get point coordinates from its bounding box coordinates and form new + * gistentry. + */ +Datum +gist_point_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + BOX *in = DatumGetBoxP(entry->key); + Point *r; + GISTENTRY *retval; + + retval = palloc(sizeof(GISTENTRY)); + + r = (Point *) palloc(sizeof(Point)); + r->x = in->high.x; + r->y = in->high.y; + gistentryinit(*retval, PointerGetDatum(r), + entry->rel, entry->page, + entry->offset, FALSE); + + PG_RETURN_POINTER(retval); +} + + #define point_point_distance(p1,p2) \ DatumGetFloat8(DirectFunctionCall2(point_distance, \ PointPGetDatum(p1), PointPGetDatum(p2))) diff --git a/src/backend/access/gist/gistscan.c b/src/backend/access/gist/gistscan.c index 991858ff43..3522d75a49 100644 --- a/src/backend/access/gist/gistscan.c +++ b/src/backend/access/gist/gistscan.c @@ -88,6 +88,13 @@ gistbeginscan(PG_FUNCTION_ARGS) scan->opaque = so; + /* + * All fields required for index-only scans are null until gistrescan. + * However, we set up scan->xs_itupdesc whether we'll need it or not, + * since that's cheap. + */ + scan->xs_itupdesc = RelationGetDescr(r); + MemoryContextSwitchTo(oldCxt); PG_RETURN_POINTER(scan); @@ -141,6 +148,17 @@ gistrescan(PG_FUNCTION_ARGS) first_time = false; } + /* + * If we're doing an index-only scan, also create a memory context to hold + * the returned tuples. + */ + if (scan->xs_want_itup && so->pageDataCxt == NULL) + so->pageDataCxt = AllocSetContextCreate(so->giststate->scanCxt, + "GiST page data context", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + /* create new, empty RBTree for search queue */ oldCxt = MemoryContextSwitchTo(so->queueCxt); so->queue = pairingheap_allocate(pairingheap_GISTSearchItem_cmp, scan); diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 824c40eb20..1680251a18 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -294,8 +294,9 @@ gistDeCompressAtt(GISTSTATE *giststate, Relation r, IndexTuple tuple, Page p, for (i = 0; i < r->rd_att->natts; i++) { - Datum datum = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]); + Datum datum; + datum = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]); gistdentryinit(giststate, i, &attdata[i], datum, r, p, o, FALSE, isnull[i]); @@ -598,6 +599,67 @@ gistFormTuple(GISTSTATE *giststate, Relation r, return res; } +/* + * initialize a GiST entry with fetched value in key field + */ +static Datum +gistFetchAtt(GISTSTATE *giststate, int nkey, Datum k, Relation r) +{ + GISTENTRY fentry; + GISTENTRY *fep; + + gistentryinit(fentry, k, r, NULL, (OffsetNumber) 0, false); + + fep = (GISTENTRY *) + DatumGetPointer(FunctionCall1Coll(&giststate->fetchFn[nkey], + giststate->supportCollation[nkey], + PointerGetDatum(&fentry))); + + /* fetchFn set 'key', return it to the caller */ + return fep->key; +} + +/* + * Fetch all keys in tuple. + * returns new IndexTuple that contains GISTENTRY with fetched data + */ +IndexTuple +gistFetchTuple(GISTSTATE *giststate, Relation r, IndexTuple tuple) +{ + MemoryContext oldcxt = MemoryContextSwitchTo(giststate->tempCxt); + Datum fetchatt[INDEX_MAX_KEYS]; + bool isnull[INDEX_MAX_KEYS]; + int i; + + for (i = 0; i < r->rd_att->natts; i++) + { + Datum datum; + + datum = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]); + + if (giststate->fetchFn[i].fn_oid != InvalidOid) + { + if (!isnull[i]) + fetchatt[i] = gistFetchAtt(giststate, i, datum, r); + else + fetchatt[i] = (Datum) 0; + } + else + { + /* + * Index-only scans not supported for this column. Since the + * planner chose an index-only scan anyway, it is not interested + * in this column, and we can replace it with a NULL. + */ + isnull[i] = true; + fetchatt[i] = (Datum) 0; + } + } + MemoryContextSwitchTo(oldcxt); + + return index_form_tuple(giststate->tupdesc, fetchatt, isnull); +} + float gistpenalty(GISTSTATE *giststate, int attno, GISTENTRY *orig, bool isNullOrig, diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index 00c1d69376..2b27e732f1 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -722,11 +722,14 @@ index_vacuum_cleanup(IndexVacuumInfo *info, } /* ---------------- - * index_can_return - does index support index-only scans? + * index_can_return + * + * Does the index access method support index-only scans for the given + * column? * ---------------- */ bool -index_can_return(Relation indexRelation) +index_can_return(Relation indexRelation, int attno) { FmgrInfo *procedure; @@ -738,8 +741,9 @@ index_can_return(Relation indexRelation) GET_REL_PROCEDURE(amcanreturn); - return DatumGetBool(FunctionCall1(procedure, - PointerGetDatum(indexRelation))); + return DatumGetBool(FunctionCall2(procedure, + PointerGetDatum(indexRelation), + Int32GetDatum(attno))); } /* ---------------- diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c index 3c79fb99b8..06c6944fc7 100644 --- a/src/backend/access/spgist/spgscan.c +++ b/src/backend/access/spgist/spgscan.c @@ -658,6 +658,7 @@ Datum spgcanreturn(PG_FUNCTION_ARGS) { Relation index = (Relation) PG_GETARG_POINTER(0); + /* int i = PG_GETARG_INT32(1); */ SpGistCache *cache; /* We can do it if the opclass config function says so */ diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 49ab3666b9..fdd6baba6c 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -1786,15 +1786,13 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index) { bool result; Bitmapset *attrs_used = NULL; - Bitmapset *index_attrs = NULL; + Bitmapset *index_canreturn_attrs = NULL; ListCell *lc; int i; - /* Index-only scans must be enabled, and index must be capable of them */ + /* Index-only scans must be enabled */ if (!enable_indexonlyscan) return false; - if (!index->canreturn) - return false; /* * Check that all needed attributes of the relation are available from the @@ -1824,7 +1822,10 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index) pull_varattnos((Node *) rinfo->clause, rel->relid, &attrs_used); } - /* Construct a bitmapset of columns stored in the index. */ + /* + * Construct a bitmapset of columns that the index can return back in an + * index-only scan. + */ for (i = 0; i < index->ncolumns; i++) { int attno = index->indexkeys[i]; @@ -1836,16 +1837,17 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index) if (attno == 0) continue; - index_attrs = - bms_add_member(index_attrs, - attno - FirstLowInvalidHeapAttributeNumber); + if (index->canreturn[i]) + index_canreturn_attrs = + bms_add_member(index_canreturn_attrs, + attno - FirstLowInvalidHeapAttributeNumber); } /* Do we have all the necessary attributes? */ - result = bms_is_subset(attrs_used, index_attrs); + result = bms_is_subset(attrs_used, index_canreturn_attrs); bms_free(attrs_used); - bms_free(index_attrs); + bms_free(index_canreturn_attrs); return result; } diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 313a5c1ab2..8abed2ae0d 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -207,6 +207,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, info->indexcollations = (Oid *) palloc(sizeof(Oid) * ncolumns); info->opfamily = (Oid *) palloc(sizeof(Oid) * ncolumns); info->opcintype = (Oid *) palloc(sizeof(Oid) * ncolumns); + info->canreturn = (bool *) palloc(sizeof(bool) * ncolumns); for (i = 0; i < ncolumns; i++) { @@ -214,11 +215,11 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, info->indexcollations[i] = indexRelation->rd_indcollation[i]; info->opfamily[i] = indexRelation->rd_opfamily[i]; info->opcintype[i] = indexRelation->rd_opcintype[i]; + info->canreturn[i] = index_can_return(indexRelation, i + 1); } info->relam = indexRelation->rd_rel->relam; info->amcostestimate = indexRelation->rd_am->amcostestimate; - info->canreturn = index_can_return(indexRelation); info->amcanorderbyop = indexRelation->rd_am->amcanorderbyop; info->amoptionalkey = indexRelation->rd_am->amoptionalkey; info->amsearcharray = indexRelation->rd_am->amsearcharray; diff --git a/src/include/access/genam.h b/src/include/access/genam.h index d1d624721d..d86590ac11 100644 --- a/src/include/access/genam.h +++ b/src/include/access/genam.h @@ -156,7 +156,7 @@ extern IndexBulkDeleteResult *index_bulk_delete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *index_vacuum_cleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bool index_can_return(Relation indexRelation); +extern bool index_can_return(Relation indexRelation, int attno); extern RegProcedure index_getprocid(Relation irel, AttrNumber attnum, uint16 procnum); extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum, diff --git a/src/include/access/gist.h b/src/include/access/gist.h index 01f0a70fee..50261b8bdd 100644 --- a/src/include/access/gist.h +++ b/src/include/access/gist.h @@ -33,7 +33,8 @@ #define GIST_PICKSPLIT_PROC 6 #define GIST_EQUAL_PROC 7 #define GIST_DISTANCE_PROC 8 -#define GISTNProcs 8 +#define GIST_FETCH_PROC 9 +#define GISTNProcs 9 /* * strategy numbers for GiST opclasses that want to implement the old diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h index 9550687916..3693893e26 100644 --- a/src/include/access/gist_private.h +++ b/src/include/access/gist_private.h @@ -87,6 +87,7 @@ typedef struct GISTSTATE FmgrInfo picksplitFn[INDEX_MAX_KEYS]; FmgrInfo equalFn[INDEX_MAX_KEYS]; FmgrInfo distanceFn[INDEX_MAX_KEYS]; + FmgrInfo fetchFn[INDEX_MAX_KEYS]; /* Collations to pass to the support functions */ Oid supportCollation[INDEX_MAX_KEYS]; @@ -118,6 +119,8 @@ typedef struct GISTSearchHeapItem { ItemPointerData heapPtr; bool recheck; /* T if quals must be rechecked */ + IndexTuple ftup; /* data fetched back from the index, used in + * index-only scans */ } GISTSearchHeapItem; /* Unvisited item, either index page or heap tuple */ @@ -157,6 +160,8 @@ typedef struct GISTScanOpaqueData GISTSearchHeapItem pageData[BLCKSZ / sizeof(IndexTupleData)]; OffsetNumber nPageData; /* number of valid items in array */ OffsetNumber curPageData; /* next item to return */ + MemoryContext pageDataCxt; /* context holding the fetched tuples, for + index-only scans */ } GISTScanOpaqueData; typedef GISTScanOpaqueData *GISTScanOpaque; @@ -409,6 +414,7 @@ typedef struct GiSTOptions /* gist.c */ extern Datum gistbuildempty(PG_FUNCTION_ARGS); extern Datum gistinsert(PG_FUNCTION_ARGS); +extern Datum gistcanreturn(PG_FUNCTION_ARGS); extern MemoryContext createTempGistContext(void); extern GISTSTATE *initGISTstate(Relation index); extern void freeGISTstate(GISTSTATE *giststate); @@ -504,7 +510,8 @@ extern void gistMakeUnionItVec(GISTSTATE *giststate, IndexTuple *itvec, int len, extern bool gistKeyIsEQ(GISTSTATE *giststate, int attno, Datum a, Datum b); extern void gistDeCompressAtt(GISTSTATE *giststate, Relation r, IndexTuple tuple, Page p, OffsetNumber o, GISTENTRY *attdata, bool *isnull); - +extern IndexTuple gistFetchTuple(GISTSTATE *giststate, Relation r, + IndexTuple tuple); extern void gistMakeUnionKey(GISTSTATE *giststate, int attno, GISTENTRY *entry1, bool isnull1, GISTENTRY *entry2, bool isnull2, diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index da6035f2c5..3d50f70402 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201503191 +#define CATALOG_VERSION_NO 201503261 #endif diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h index 0531222a7e..79609f7774 100644 --- a/src/include/catalog/pg_am.h +++ b/src/include/catalog/pg_am.h @@ -123,7 +123,7 @@ DESCR("b-tree index access method"); DATA(insert OID = 405 ( hash 1 1 f f t f f f f f f f f 23 hashinsert hashbeginscan hashgettuple hashgetbitmap hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbuildempty hashbulkdelete hashvacuumcleanup - hashcostestimate hashoptions )); DESCR("hash index access method"); #define HASH_AM_OID 405 -DATA(insert OID = 783 ( gist 0 8 f t f f t t f t t t f 0 gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbuildempty gistbulkdelete gistvacuumcleanup - gistcostestimate gistoptions )); +DATA(insert OID = 783 ( gist 0 9 f t f f t t f t t t f 0 gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbuildempty gistbulkdelete gistvacuumcleanup gistcanreturn gistcostestimate gistoptions )); DESCR("GiST index access method"); #define GIST_AM_OID 783 DATA(insert OID = 2742 ( gin 0 6 f f f f t t f f t f f 0 gininsert ginbeginscan - gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbuildempty ginbulkdelete ginvacuumcleanup - gincostestimate ginoptions )); diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h index 49d3d13efb..612a9d242e 100644 --- a/src/include/catalog/pg_amproc.h +++ b/src/include/catalog/pg_amproc.h @@ -191,6 +191,7 @@ DATA(insert ( 1029 600 600 5 2581 )); DATA(insert ( 1029 600 600 6 2582 )); DATA(insert ( 1029 600 600 7 2584 )); DATA(insert ( 1029 600 600 8 3064 )); +DATA(insert ( 1029 600 600 9 3282 )); DATA(insert ( 2593 603 603 1 2578 )); DATA(insert ( 2593 603 603 2 2583 )); DATA(insert ( 2593 603 603 3 2579 )); @@ -198,6 +199,7 @@ DATA(insert ( 2593 603 603 4 2580 )); DATA(insert ( 2593 603 603 5 2581 )); DATA(insert ( 2593 603 603 6 2582 )); DATA(insert ( 2593 603 603 7 2584 )); +DATA(insert ( 2593 603 603 9 3281 )); DATA(insert ( 2594 604 604 1 2585 )); DATA(insert ( 2594 604 604 2 2583 )); DATA(insert ( 2594 604 604 3 2586 )); diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 3c218a3987..77b77176a3 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -558,7 +558,7 @@ DATA(insert OID = 332 ( btbulkdelete PGNSP PGUID 12 1 0 0 0 f f f f t f v 4 DESCR("btree(internal)"); DATA(insert OID = 972 ( btvacuumcleanup PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ btvacuumcleanup _null_ _null_ _null_ )); DESCR("btree(internal)"); -DATA(insert OID = 276 ( btcanreturn PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "2281" _null_ _null_ _null_ _null_ btcanreturn _null_ _null_ _null_ )); +DATA(insert OID = 276 ( btcanreturn PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "2281 23" _null_ _null_ _null_ _null_ btcanreturn _null_ _null_ _null_ )); DESCR("btree(internal)"); DATA(insert OID = 1268 ( btcostestimate PGNSP PGUID 12 1 0 0 0 f f f f t f v 7 0 2278 "2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ btcostestimate _null_ _null_ _null_ )); DESCR("btree(internal)"); @@ -987,6 +987,8 @@ DATA(insert OID = 776 ( gistbulkdelete PGNSP PGUID 12 1 0 0 0 f f f f t f v DESCR("gist(internal)"); DATA(insert OID = 2561 ( gistvacuumcleanup PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ gistvacuumcleanup _null_ _null_ _null_ )); DESCR("gist(internal)"); +DATA(insert OID = 3280 ( gistcanreturn PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "2281 23" _null_ _null_ _null_ _null_ gistcanreturn _null_ _null_ _null_ )); +DESCR("gist(internal)"); DATA(insert OID = 772 ( gistcostestimate PGNSP PGUID 12 1 0 0 0 f f f f t f v 7 0 2278 "2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gistcostestimate _null_ _null_ _null_ )); DESCR("gist(internal)"); DATA(insert OID = 2787 ( gistoptions PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 17 "1009 16" _null_ _null_ _null_ _null_ gistoptions _null_ _null_ _null_ )); @@ -4089,6 +4091,8 @@ DATA(insert OID = 2579 ( gist_box_compress PGNSP PGUID 12 1 0 0 0 f f f f t f DESCR("GiST support"); DATA(insert OID = 2580 ( gist_box_decompress PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ gist_box_decompress _null_ _null_ _null_ )); DESCR("GiST support"); +DATA(insert OID = 3281 ( gist_box_fetch PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ gist_box_fetch _null_ _null_ _null_ )); +DESCR("GiST support"); DATA(insert OID = 2581 ( gist_box_penalty PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ gist_box_penalty _null_ _null_ _null_ )); DESCR("GiST support"); DATA(insert OID = 2582 ( gist_box_picksplit PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ gist_box_picksplit _null_ _null_ _null_ )); @@ -4107,6 +4111,8 @@ DATA(insert OID = 2592 ( gist_circle_compress PGNSP PGUID 12 1 0 0 0 f f f f t DESCR("GiST support"); DATA(insert OID = 1030 ( gist_point_compress PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ gist_point_compress _null_ _null_ _null_ )); DESCR("GiST support"); +DATA(insert OID = 3282 ( gist_point_fetch PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ gist_point_fetch _null_ _null_ _null_ )); +DESCR("GiST support"); DATA(insert OID = 2179 ( gist_point_consistent PGNSP PGUID 12 1 0 0 0 f f f f t f i 5 0 16 "2281 600 23 26 2281" _null_ _null_ _null_ _null_ gist_point_consistent _null_ _null_ _null_ )); DESCR("GiST support"); DATA(insert OID = 3064 ( gist_point_distance PGNSP PGUID 12 1 0 0 0 f f f f t f i 4 0 701 "2281 600 23 26" _null_ _null_ _null_ _null_ gist_point_distance _null_ _null_ _null_ )); @@ -5039,7 +5045,7 @@ DATA(insert OID = 4011 ( spgbulkdelete PGNSP PGUID 12 1 0 0 0 f f f f t f v DESCR("spgist(internal)"); DATA(insert OID = 4012 ( spgvacuumcleanup PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ spgvacuumcleanup _null_ _null_ _null_ )); DESCR("spgist(internal)"); -DATA(insert OID = 4032 ( spgcanreturn PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "2281" _null_ _null_ _null_ _null_ spgcanreturn _null_ _null_ _null_ )); +DATA(insert OID = 4032 ( spgcanreturn PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "2281 23" _null_ _null_ _null_ _null_ spgcanreturn _null_ _null_ _null_ )); DESCR("spgist(internal)"); DATA(insert OID = 4013 ( spgcostestimate PGNSP PGUID 12 1 0 0 0 f f f f t f v 7 0 2278 "2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ spgcostestimate _null_ _null_ _null_ )); DESCR("spgist(internal)"); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 334cf51976..401a686664 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -520,6 +520,8 @@ typedef struct IndexOptInfo Oid *sortopfamily; /* OIDs of btree opfamilies, if orderable */ bool *reverse_sort; /* is sort order descending? */ bool *nulls_first; /* do NULLs come first in the sort order? */ + bool *canreturn; /* which index cols can be returned in an + index-only scan? */ Oid relam; /* OID of the access method (in pg_am) */ RegProcedure amcostestimate; /* OID of the access method's cost fcn */ @@ -533,7 +535,6 @@ typedef struct IndexOptInfo bool unique; /* true if a unique index */ bool immediate; /* is uniqueness enforced immediately? */ bool hypothetical; /* true if index doesn't really exist */ - bool canreturn; /* can index return IndexTuples? */ bool amcanorderbyop; /* does AM support order by operator result? */ bool amoptionalkey; /* can query omit key for the first column? */ bool amsearcharray; /* can AM handle ScalarArrayOpExpr quals? */ diff --git a/src/include/utils/geo_decls.h b/src/include/utils/geo_decls.h index 8da6c6c987..2a91620db7 100644 --- a/src/include/utils/geo_decls.h +++ b/src/include/utils/geo_decls.h @@ -410,6 +410,7 @@ extern Datum gist_box_picksplit(PG_FUNCTION_ARGS); extern Datum gist_box_consistent(PG_FUNCTION_ARGS); extern Datum gist_box_penalty(PG_FUNCTION_ARGS); extern Datum gist_box_same(PG_FUNCTION_ARGS); +extern Datum gist_box_fetch(PG_FUNCTION_ARGS); extern Datum gist_poly_compress(PG_FUNCTION_ARGS); extern Datum gist_poly_consistent(PG_FUNCTION_ARGS); extern Datum gist_circle_compress(PG_FUNCTION_ARGS); @@ -417,6 +418,8 @@ extern Datum gist_circle_consistent(PG_FUNCTION_ARGS); extern Datum gist_point_compress(PG_FUNCTION_ARGS); extern Datum gist_point_consistent(PG_FUNCTION_ARGS); extern Datum gist_point_distance(PG_FUNCTION_ARGS); +extern Datum gist_point_fetch(PG_FUNCTION_ARGS); + /* geo_selfuncs.c */ extern Datum areasel(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index 5603817c77..abe64e597c 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -384,7 +384,7 @@ SELECT * FROM fast_emp4000 ---------------------------------------------------------------- Sort Sort Key: ((home_base[0])[0]) - -> Index Scan using grect2ind on fast_emp4000 + -> Index Only Scan using grect2ind on fast_emp4000 Index Cond: (home_base @ '(2000,1000),(200,200)'::box) (4 rows) @@ -402,7 +402,7 @@ SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box; QUERY PLAN ------------------------------------------------------------- Aggregate - -> Index Scan using grect2ind on fast_emp4000 + -> Index Only Scan using grect2ind on fast_emp4000 Index Cond: (home_base && '(1000,1000),(0,0)'::box) (3 rows) @@ -414,10 +414,10 @@ SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box; EXPLAIN (COSTS OFF) SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; - QUERY PLAN --------------------------------------------------- + QUERY PLAN +------------------------------------------------------- Aggregate - -> Index Scan using grect2ind on fast_emp4000 + -> Index Only Scan using grect2ind on fast_emp4000 Index Cond: (home_base IS NULL) (3 rows) @@ -501,7 +501,7 @@ SELECT count(*) FROM point_tbl WHERE f1 <@ box '(0,0,100,100)'; QUERY PLAN ---------------------------------------------------- Aggregate - -> Index Scan using gpointind on point_tbl + -> Index Only Scan using gpointind on point_tbl Index Cond: (f1 <@ '(100,100),(0,0)'::box) (3 rows) @@ -516,8 +516,8 @@ SELECT count(*) FROM point_tbl WHERE box '(0,0,100,100)' @> f1; QUERY PLAN ---------------------------------------------------- Aggregate - -> Index Scan using gpointind on point_tbl - Index Cond: ('(100,100),(0,0)'::box @> f1) + -> Index Only Scan using gpointind on point_tbl + Index Cond: (f1 <@ '(100,100),(0,0)'::box) (3 rows) SELECT count(*) FROM point_tbl WHERE box '(0,0,100,100)' @> f1; @@ -531,7 +531,7 @@ SELECT count(*) FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50, QUERY PLAN ---------------------------------------------------------------------------------------- Aggregate - -> Index Scan using gpointind on point_tbl + -> Index Only Scan using gpointind on point_tbl Index Cond: (f1 <@ '((0,0),(0,100),(100,100),(50,50),(100,0),(0,0))'::polygon) (3 rows) @@ -546,7 +546,7 @@ SELECT count(*) FROM point_tbl WHERE f1 <@ circle '<(50,50),50>'; QUERY PLAN ---------------------------------------------------- Aggregate - -> Index Scan using gpointind on point_tbl + -> Index Only Scan using gpointind on point_tbl Index Cond: (f1 <@ '<(50,50),50>'::circle) (3 rows) @@ -558,10 +558,10 @@ SELECT count(*) FROM point_tbl WHERE f1 <@ circle '<(50,50),50>'; EXPLAIN (COSTS OFF) SELECT count(*) FROM point_tbl p WHERE p.f1 << '(0.0, 0.0)'; - QUERY PLAN -------------------------------------------------- + QUERY PLAN +------------------------------------------------------ Aggregate - -> Index Scan using gpointind on point_tbl p + -> Index Only Scan using gpointind on point_tbl p Index Cond: (f1 << '(0,0)'::point) (3 rows) @@ -573,10 +573,10 @@ SELECT count(*) FROM point_tbl p WHERE p.f1 << '(0.0, 0.0)'; EXPLAIN (COSTS OFF) SELECT count(*) FROM point_tbl p WHERE p.f1 >> '(0.0, 0.0)'; - QUERY PLAN -------------------------------------------------- + QUERY PLAN +------------------------------------------------------ Aggregate - -> Index Scan using gpointind on point_tbl p + -> Index Only Scan using gpointind on point_tbl p Index Cond: (f1 >> '(0,0)'::point) (3 rows) @@ -588,10 +588,10 @@ SELECT count(*) FROM point_tbl p WHERE p.f1 >> '(0.0, 0.0)'; EXPLAIN (COSTS OFF) SELECT count(*) FROM point_tbl p WHERE p.f1 <^ '(0.0, 0.0)'; - QUERY PLAN -------------------------------------------------- + QUERY PLAN +------------------------------------------------------ Aggregate - -> Index Scan using gpointind on point_tbl p + -> Index Only Scan using gpointind on point_tbl p Index Cond: (f1 <^ '(0,0)'::point) (3 rows) @@ -603,10 +603,10 @@ SELECT count(*) FROM point_tbl p WHERE p.f1 <^ '(0.0, 0.0)'; EXPLAIN (COSTS OFF) SELECT count(*) FROM point_tbl p WHERE p.f1 >^ '(0.0, 0.0)'; - QUERY PLAN -------------------------------------------------- + QUERY PLAN +------------------------------------------------------ Aggregate - -> Index Scan using gpointind on point_tbl p + -> Index Only Scan using gpointind on point_tbl p Index Cond: (f1 >^ '(0,0)'::point) (3 rows) @@ -618,10 +618,10 @@ SELECT count(*) FROM point_tbl p WHERE p.f1 >^ '(0.0, 0.0)'; EXPLAIN (COSTS OFF) SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)'; - QUERY PLAN -------------------------------------------------- + QUERY PLAN +------------------------------------------------------ Aggregate - -> Index Scan using gpointind on point_tbl p + -> Index Only Scan using gpointind on point_tbl p Index Cond: (f1 ~= '(-5,-12)'::point) (3 rows) @@ -633,9 +633,9 @@ SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)'; EXPLAIN (COSTS OFF) SELECT * FROM point_tbl ORDER BY f1 <-> '0,1'; - QUERY PLAN ------------------------------------------ - Index Scan using gpointind on point_tbl + QUERY PLAN +---------------------------------------------- + Index Only Scan using gpointind on point_tbl Order By: (f1 <-> '(0,1)'::point) (2 rows) @@ -653,9 +653,9 @@ SELECT * FROM point_tbl ORDER BY f1 <-> '0,1'; EXPLAIN (COSTS OFF) SELECT * FROM point_tbl WHERE f1 IS NULL; - QUERY PLAN ------------------------------------------ - Index Scan using gpointind on point_tbl + QUERY PLAN +---------------------------------------------- + Index Only Scan using gpointind on point_tbl Index Cond: (f1 IS NULL) (2 rows) @@ -667,9 +667,9 @@ SELECT * FROM point_tbl WHERE f1 IS NULL; EXPLAIN (COSTS OFF) SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1'; - QUERY PLAN ------------------------------------------ - Index Scan using gpointind on point_tbl + QUERY PLAN +---------------------------------------------- + Index Only Scan using gpointind on point_tbl Index Cond: (f1 IS NOT NULL) Order By: (f1 <-> '(0,1)'::point) (3 rows) @@ -689,7 +689,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1'; QUERY PLAN ------------------------------------------------ - Index Scan using gpointind on point_tbl + Index Only Scan using gpointind on point_tbl Index Cond: (f1 <@ '(10,10),(-10,-10)'::box) Order By: (f1 <-> '(0,1)'::point) (3 rows) diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out index 7bceb73999..42f6891ffe 100644 --- a/src/test/regress/expected/gist.out +++ b/src/test/regress/expected/gist.out @@ -17,3 +17,149 @@ delete from gist_point_tbl where id % 2 = 1; -- would exercise it) delete from gist_point_tbl where id < 10000; vacuum gist_point_tbl; +-- +-- Test Index-only plans on GiST indexes +-- +create table gist_tbl (b box, p point, c circle); +insert into gist_tbl +select box(point(0.05*i, 0.05*i), point(0.05*i, 0.05*i)), + point(0.05*i, 0.05*i), + circle(point(0.05*i, 0.05*i), 1.0) +from generate_series(0,10000) as i; +vacuum analyze; +set enable_seqscan=off; +set enable_bitmapscan=off; +set enable_indexonlyscan=on; +-- Test index-only scan with point opclass +create index gist_tbl_point_index on gist_tbl using gist (p); +-- check that the planner chooses an index-only scan +explain (costs off) +select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)); + QUERY PLAN +-------------------------------------------------------- + Index Only Scan using gist_tbl_point_index on gist_tbl + Index Cond: (p <@ '(0.5,0.5),(0,0)'::box) +(2 rows) + +-- execute the same +select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)); + p +------------- + (0,0) + (0.05,0.05) + (0.1,0.1) + (0.15,0.15) + (0.2,0.2) + (0.25,0.25) + (0.3,0.3) + (0.35,0.35) + (0.4,0.4) + (0.45,0.45) + (0.5,0.5) +(11 rows) + +-- Also test an index-only knn-search +explain (costs off) +select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)) +order by p <-> point(0.2, 0.2); + QUERY PLAN +-------------------------------------------------------- + Index Only Scan using gist_tbl_point_index on gist_tbl + Index Cond: (p <@ '(0.5,0.5),(0,0)'::box) + Order By: (p <-> '(0.2,0.2)'::point) +(3 rows) + +select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)) +order by p <-> point(0.2, 0.2); + p +------------- + (0.2,0.2) + (0.25,0.25) + (0.15,0.15) + (0.3,0.3) + (0.1,0.1) + (0.35,0.35) + (0.05,0.05) + (0,0) + (0.4,0.4) + (0.45,0.45) + (0.5,0.5) +(11 rows) + +drop index gist_tbl_point_index; +-- Test index-only scan with box opclass +create index gist_tbl_box_index on gist_tbl using gist (b); +-- check that the planner chooses an index-only scan +explain (costs off) +select b from gist_tbl where b <@ box(point(5,5), point(6,6)); + QUERY PLAN +------------------------------------------------------ + Index Only Scan using gist_tbl_box_index on gist_tbl + Index Cond: (b <@ '(6,6),(5,5)'::box) +(2 rows) + +-- execute the same +select b from gist_tbl where b <@ box(point(5,5), point(6,6)); + b +------------------------- + (5,5),(5,5) + (5.05,5.05),(5.05,5.05) + (5.1,5.1),(5.1,5.1) + (5.15,5.15),(5.15,5.15) + (5.2,5.2),(5.2,5.2) + (5.25,5.25),(5.25,5.25) + (5.3,5.3),(5.3,5.3) + (5.35,5.35),(5.35,5.35) + (5.4,5.4),(5.4,5.4) + (5.45,5.45),(5.45,5.45) + (5.5,5.5),(5.5,5.5) + (5.55,5.55),(5.55,5.55) + (5.6,5.6),(5.6,5.6) + (5.65,5.65),(5.65,5.65) + (5.7,5.7),(5.7,5.7) + (5.75,5.75),(5.75,5.75) + (5.8,5.8),(5.8,5.8) + (5.85,5.85),(5.85,5.85) + (5.9,5.9),(5.9,5.9) + (5.95,5.95),(5.95,5.95) + (6,6),(6,6) +(21 rows) + +drop index gist_tbl_box_index; +-- Test that an index-only scan is not chosen, when the query involves the +-- circle column (the circle opclass does not support index-only scans). +create index gist_tbl_multi_index on gist_tbl using gist (p, c); +explain (costs off) +select p, c from gist_tbl +where p <@ box(point(5,5), point(6, 6)); + QUERY PLAN +--------------------------------------------------- + Index Scan using gist_tbl_multi_index on gist_tbl + Index Cond: (p <@ '(6,6),(5,5)'::box) +(2 rows) + +-- execute the same +select b, p from gist_tbl +where b <@ box(point(4.5, 4.5), point(5.5, 5.5)) +and p <@ box(point(5,5), point(6, 6)); + b | p +-------------------------+------------- + (5,5),(5,5) | (5,5) + (5.05,5.05),(5.05,5.05) | (5.05,5.05) + (5.1,5.1),(5.1,5.1) | (5.1,5.1) + (5.15,5.15),(5.15,5.15) | (5.15,5.15) + (5.2,5.2),(5.2,5.2) | (5.2,5.2) + (5.25,5.25),(5.25,5.25) | (5.25,5.25) + (5.3,5.3),(5.3,5.3) | (5.3,5.3) + (5.35,5.35),(5.35,5.35) | (5.35,5.35) + (5.4,5.4),(5.4,5.4) | (5.4,5.4) + (5.45,5.45),(5.45,5.45) | (5.45,5.45) + (5.5,5.5),(5.5,5.5) | (5.5,5.5) +(11 rows) + +drop index gist_tbl_multi_index; +-- Clean up +reset enable_seqscan; +reset enable_bitmapscan; +reset enable_indexonlyscan; +drop table gist_tbl; diff --git a/src/test/regress/sql/gist.sql b/src/test/regress/sql/gist.sql index 8c345d8b9d..d6cbc21717 100644 --- a/src/test/regress/sql/gist.sql +++ b/src/test/regress/sql/gist.sql @@ -23,3 +23,76 @@ delete from gist_point_tbl where id % 2 = 1; delete from gist_point_tbl where id < 10000; vacuum gist_point_tbl; + + +-- +-- Test Index-only plans on GiST indexes +-- + +create table gist_tbl (b box, p point, c circle); + +insert into gist_tbl +select box(point(0.05*i, 0.05*i), point(0.05*i, 0.05*i)), + point(0.05*i, 0.05*i), + circle(point(0.05*i, 0.05*i), 1.0) +from generate_series(0,10000) as i; + +vacuum analyze; + +set enable_seqscan=off; +set enable_bitmapscan=off; +set enable_indexonlyscan=on; + +-- Test index-only scan with point opclass +create index gist_tbl_point_index on gist_tbl using gist (p); + +-- check that the planner chooses an index-only scan +explain (costs off) +select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)); + +-- execute the same +select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)); + +-- Also test an index-only knn-search +explain (costs off) +select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)) +order by p <-> point(0.2, 0.2); + +select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)) +order by p <-> point(0.2, 0.2); + +drop index gist_tbl_point_index; + +-- Test index-only scan with box opclass +create index gist_tbl_box_index on gist_tbl using gist (b); + +-- check that the planner chooses an index-only scan +explain (costs off) +select b from gist_tbl where b <@ box(point(5,5), point(6,6)); + +-- execute the same +select b from gist_tbl where b <@ box(point(5,5), point(6,6)); + +drop index gist_tbl_box_index; + +-- Test that an index-only scan is not chosen, when the query involves the +-- circle column (the circle opclass does not support index-only scans). +create index gist_tbl_multi_index on gist_tbl using gist (p, c); + +explain (costs off) +select p, c from gist_tbl +where p <@ box(point(5,5), point(6, 6)); + +-- execute the same +select b, p from gist_tbl +where b <@ box(point(4.5, 4.5), point(5.5, 5.5)) +and p <@ box(point(5,5), point(6, 6)); + +drop index gist_tbl_multi_index; + +-- Clean up +reset enable_seqscan; +reset enable_bitmapscan; +reset enable_indexonlyscan; + +drop table gist_tbl;