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.
This commit is contained in:
Heikki Linnakangas 2015-03-26 19:12:00 +02:00
parent 8fa393a6d7
commit d04c8ed904
24 changed files with 575 additions and 73 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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);
}

View File

@ -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)))

View File

@ -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);

View File

@ -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,

View File

@ -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)));
}
/* ----------------

View File

@ -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 */

View File

@ -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;
}

View File

@ -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;

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201503191
#define CATALOG_VERSION_NO 201503261
#endif

View File

@ -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 ));

View File

@ -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 ));

View File

@ -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)");

View File

@ -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? */

View File

@ -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);

View File

@ -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)

View File

@ -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;

View File

@ -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;