diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index dfed546f5133b11d01ad6f794d6270960c7be3ae..d0b78f27827cd5b1dd883af248657f33b802d013 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -724,8 +724,8 @@ <entry><structfield>amcanreturn</structfield></entry> <entry><type>regproc</type></entry> <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> - <entry>Function to check whether index supports index-only scans, - or zero if none</entry> + <entry>Function to check whether an index column supports index-only + scans. Can be zero if index-only scans are never supported.</entry> </row> <row> diff --git a/doc/src/sgml/gist.sgml b/doc/src/sgml/gist.sgml index 31ce2790047e5487d5e24e08200eefaaa2a5c85c..e7d1ff9d83f4f9b469480dc6dfc22303a2d51c4a 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); <para> There are seven methods that an index operator class for - <acronym>GiST</acronym> must provide, and an eighth that is optional. + <acronym>GiST</acronym> must provide, and two that are optional. Correctness of the index is ensured by proper implementation of the <function>same</>, <function>consistent</> and <function>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 <command>CREATE OPERATOR CLASS</> command can be used. The optional eighth method is <function>distance</>, which is needed if the operator class wishes to support ordered scans (nearest-neighbor - searches). + searches). The optional ninth method <function>fetch</> is needed if the + operator class wishes to support index-only scans. </para> <variablelist> @@ -506,7 +507,7 @@ my_compress(PG_FUNCTION_ARGS) <para> The reverse of the <function>compress</function> 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. </para> <para> @@ -807,6 +808,72 @@ my_distance(PG_FUNCTION_ARGS) </listitem> </varlistentry> + <varlistentry> + <term><function>fetch</></term> + <listitem> + <para> + 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. + </para> + + <para> + The <acronym>SQL</> declaration of the function must look like this: + +<programlisting> +CREATE OR REPLACE FUNCTION my_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; +</programlisting> + + The argument is a pointer to a <structname>GISTENTRY</> struct. On + entry, its 'key' field contains a non-NULL leaf datum in its + compressed form. The return value is another <structname>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. + </para> + + <para> + The matching code in the C module could then follow this skeleton: + +<programlisting> +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); +} +</programlisting> + </para> + + <para> + If the compress method is lossy for leaf entries, the operator class + cannot support index-only scans, and must not define a 'fetch' + function. + </para> + + </listitem> + </varlistentry> </variablelist> <para> diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml index 157047a23ab15ab86204db66b9e38d0aa64fae2f..1c09bae3955f88e3ea3d3c8150157b29c5531e99 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -274,14 +274,15 @@ amvacuumcleanup (IndexVacuumInfo *info, <para> <programlisting> bool -amcanreturn (Relation indexRelation); +amcanreturn (Relation indexRelation, int attno); </programlisting> - Check whether the index can support <firstterm>index-only scans</> by - returning the indexed column values for an index entry in the form of an - <structname>IndexTuple</structname>. 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 - <structfield>amcanreturn</> field to zero in <structname>pg_am</>. + Check whether the index can support <firstterm>index-only scans</> on the + given column, by returning the indexed column values for an index entry in + the form of an <structname>IndexTuple</structname>. 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 <structfield>amcanreturn</> field in its <structname>pg_am</> row can + be set to zero. </para> <para> diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index db2a452a4abb10e85d45d7cbc144ce27a854ecec..96b7701633f2013d36884765b137b2a16be56bc6 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 717cb85f7739952a974815dbec3099307aa8f963..e4c00c2c9f5adf8b7e4e65024443b51766afccde 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 9fab6c87c0573f7207471c97ea0847cc81e32ccf..9d21e3fb947eea13ff7a23b4a3f535679a849d05 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 991858ff43f837fd1a6d53d16321c33cd0cedae8..3522d75a49674ff05f9ffefc6e192556da892efe 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 824c40eb203fc9cf4a378f6fb3d13ac15e4db6bc..1680251a18b647998e8aeab9c507b71b62220a8a 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 00c1d6937665f32c88f3ddb2bff3ac130afebd58..2b27e732f13e5c9e056d3b130b1b929b29143b32 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 3c79fb99b889f2e3d80d00051beb4c2ab04922aa..06c6944fc72c67326514b92efff5efacbc46070c 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 49ab3666b93b881c5895c319dd0fd1be3da80b88..fdd6baba6c674c8a8a909804af5d6cfc6209f8c6 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 313a5c1ab2bc7f3270126620a94a225c54a1ff65..8abed2ae0dada01bf56fd080e041f1b481ee612f 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 d1d624721dbcb23c54442bc72e29424581d33f57..d86590ac111e6064c06a85b4dbc7b9979b9192b2 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 01f0a70feee35a39f68f7522452628b6f43fd909..50261b8bdd5d4686a27e722c1f0317ccaec78ad4 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 955068791682fc66df9ab66394f8849482494bde..3693893e261dde172cb690d2262342a8ca312ca2 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 da6035f2c587c0a3ea3aa0346eb806e0a197c677..3d50f704021e8222f1779d7a1476fa965ce931a5 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 0531222a7eaa0a8504b01dff373cf210c5d13de7..79609f7774c82a6081e4a645fa8dbf83d613b0da 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 49d3d13efbb5eb034129e7c06f584d1eb62c4c86..612a9d242e327b08a95b87ddef24b5bd34d3f07f 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 3c218a3987346282d8e744fbc49c36149966cac7..77b77176a334c17e02fddc40cdb0cc6bb996eefe 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 334cf51976bce067c5b942dc125f916c08614482..401a686664de329781234282a11100ccb7da4f3b 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 8da6c6c987b2318d8dc724a2a219fb3ea6e70f44..2a91620db7463aca0751bc54f3334b43103f77b9 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 5603817c7731b1be9273e8d12471a9c628d5a925..abe64e597c74ccb0aa4c679bd85b34a5d047055c 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 7bceb7399934eec7a10b477a2f41aa531e714997..42f6891ffeec00b73c0a379b446218958d414619 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 8c345d8b9d61214ea8ebb24184d9200d44e62360..d6cbc21717f62a1f3be4dd8fa6e60522f7d4783e 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;