Index-only scans for GiST.
Finally there is a new version of patch (in attachments).
It provides multicolumn index-only scan for GiST indexes.
- Memory leak is fixed.
- little code cleanup
- example of performance test in attachmens
- function OIDs have debugging values (1111*) just to avoid merge conflicts
while testing patch
Wiki page of the project is
https://wiki.postgresql.org/wiki/Support_for_Index-only_scans_for_GIST_GSoC_2014
Waiting for feedback.
--
Best regards,
Lubennikova Anastasia
Attachments:
indexonlyscan_gist_2.0.patchapplication/octet-stream; name=indexonlyscan_gist_2.0.patchDownload
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 644b882..7964b53 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -1395,6 +1395,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
@@ -1417,6 +1425,22 @@ initGISTstate(Relation index)
return giststate;
}
+/*
+ * Gistcanreturn is supposed to be true if ANY FetchFn method is defined.
+ * If FetchFn exists it would be used in index-only scan
+ * Thus the responsibility rests with the opclass developer.
+ */
+
+Datum
+gistcanreturn(PG_FUNCTION_ARGS) {
+ Relation index = (Relation) PG_GETARG_POINTER(0);
+ int i = PG_GETARG_INT32(1);
+ if (OidIsValid(index_getprocid(index, i+1, GIST_FETCH_PROC)))
+ PG_RETURN_BOOL(true);
+ else
+ PG_RETURN_BOOL(false);
+}
+
void
freeGISTstate(GISTSTATE *giststate)
{
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 7a8692b..2953178 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -226,8 +226,10 @@ gistindex_keytest(IndexScanDesc scan,
* If tbm/ntids aren't NULL, we are doing an amgetbitmap scan, and heap
* 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,
+ * tuple TIDs are returned into so->pageData. For an ordered indexscan,
* heap tuple TIDs are pushed into individual search queue items.
+ * If index-only scan is possible, heap tuples themselves are returned
+ * into so->pageData or into search queue.
*
* 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
@@ -240,6 +242,10 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
Buffer buffer;
Page page;
+ GISTSTATE *giststate = so->giststate;
+ Relation r = scan->indexRelation;
+ bool isnull[INDEX_MAX_KEYS];
+ GISTSearchHeapItem *tmpListItem;
GISTPageOpaque opaque;
OffsetNumber maxoff;
OffsetNumber i;
@@ -291,8 +297,6 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
MemoryContextSwitchTo(oldcxt);
}
- so->nPageData = so->curPageData = 0;
-
/*
* check all tuples on page
*/
@@ -330,11 +334,20 @@ 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
+ */
+
+ /* form tmpListItem and fill it with data to add into so->pageData */
+ tmpListItem = palloc(sizeof(GISTSearchHeapItem));
+ tmpListItem->heapPtr = it->t_tid;
+ tmpListItem->recheck = recheck;
+ /*
+ * If index-only scan is possible add the data fetched from index field
*/
- so->pageData[so->nPageData].heapPtr = it->t_tid;
- so->pageData[so->nPageData].recheck = recheck;
- so->nPageData++;
+ if (scan->xs_want_itup)
+ tmpListItem->ftup = gistFetchTuple(giststate, r, it, isnull);
+
+ so->pageData = lappend(so->pageData, tmpListItem);
}
else
{
@@ -357,6 +370,13 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
item->blkno = InvalidBlockNumber;
item->data.heap.heapPtr = it->t_tid;
item->data.heap.recheck = recheck;
+
+ /*
+ * If index-only scan is possible add the data fetched from index field
+ */
+ if (scan->xs_want_itup) {
+ item->data.heap.ftup = gistFetchTuple(giststate, r, it, isnull);
+ }
}
else
{
@@ -451,6 +471,11 @@ 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;
+ /*
+ * If index-only scan is possible fill the slot with data fetched from index field
+ */
+ if(scan->xs_want_itup)
+ scan->xs_itup = item->data.heap.ftup;
res = true;
}
else
@@ -476,6 +501,7 @@ gistgettuple(PG_FUNCTION_ARGS)
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
ScanDirection dir = (ScanDirection) PG_GETARG_INT32(1);
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
+ bool first_tuple = true;
if (dir != ForwardScanDirection)
elog(ERROR, "GiST only supports forward scan direction");
@@ -492,7 +518,6 @@ gistgettuple(PG_FUNCTION_ARGS)
so->firstCall = false;
so->curTreeItem = NULL;
- so->curPageData = so->nPageData = 0;
fakeItem.blkno = GIST_ROOT_BLKNO;
memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
@@ -509,13 +534,27 @@ gistgettuple(PG_FUNCTION_ARGS)
/* Fetch tuples index-page-at-a-time */
for (;;)
{
- if (so->curPageData < so->nPageData)
+ if(list_length(so->pageData)>0)
{
/* 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;
- so->curPageData++;
- PG_RETURN_BOOL(true);
+ GISTSearchHeapItem *tmp = (GISTSearchHeapItem *)lfirst(list_head(so->pageData));
+ scan->xs_ctup.t_self = tmp->heapPtr;
+ scan->xs_recheck = tmp->recheck;
+ /* If index-only scan is possible, return fetched data*/
+ if(scan->xs_want_itup) {
+ scan->xs_itup = tmp->ftup;
+ if(!first_tuple)
+ pfree(tmp->ftup);
+ first_tuple=false;
+ }
+ pfree(tmp);
+
+ /*
+ * Delete ListCell that we have already read.
+ * It's always head of so->pageData
+ */
+ so->pageData = list_delete_first(so->pageData);
+ PG_RETURN_BOOL(TRUE);
}
/* find and process the next index page */
@@ -537,7 +576,7 @@ gistgettuple(PG_FUNCTION_ARGS)
gistScanPage(scan, item, so->curTreeItem->distances, NULL, NULL);
pfree(item);
- } while (so->nPageData == 0);
+ } while (list_length(so->pageData)==0);
}
}
}
@@ -561,7 +600,6 @@ gistgetbitmap(PG_FUNCTION_ARGS)
/* Begin the scan by processing the root page */
so->curTreeItem = NULL;
- so->curPageData = so->nPageData = 0;
fakeItem.blkno = GIST_ROOT_BLKNO;
memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
diff --git a/src/backend/access/gist/gistproc.c b/src/backend/access/gist/gistproc.c
index db0bec6..00d890b 100644
--- a/src/backend/access/gist/gistproc.c
+++ b/src/backend/access/gist/gistproc.c
@@ -152,6 +152,16 @@ gist_box_decompress(PG_FUNCTION_ARGS)
}
/*
+ * 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)
*
* As in the R-tree paper, we use change in area as our penalty metric
@@ -1204,6 +1214,41 @@ gist_point_compress(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(entry);
}
+/*
+ * GiST Fetch method for point
+ * get point coordinates from it's bounding box coordinates
+ * and form new gistentry
+ */
+Datum
+gist_point_fetch(PG_FUNCTION_ARGS)
+{
+ GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+ GISTENTRY *retval;
+ retval = palloc(sizeof(GISTENTRY));
+ if (DatumGetBoxP(entry->key) != NULL)
+ {
+ BOX *in = DatumGetBoxP(entry->key);
+ Point *r;
+
+ 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);
+
+ }
+ else
+ {
+ gistentryinit(*retval, (Datum) 0,
+ 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 8360b16..150a833 100644
--- a/src/backend/access/gist/gistscan.c
+++ b/src/backend/access/gist/gistscan.c
@@ -133,6 +133,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.
+ */
+ so->pageData = NULL;
+ scan->xs_itupdesc = RelationGetDescr(r);
+
MemoryContextSwitchTo(oldCxt);
PG_RETURN_POINTER(scan);
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index f32e35a..8df33be 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -618,6 +618,65 @@ gistFormTuple(GISTSTATE *giststate, Relation r,
return res;
}
+/*
+ * initialize a GiST entry with fetched value in key field
+ */
+void
+gistfentryinit(GISTSTATE *giststate, int nkey,
+ GISTENTRY *e, Datum k, Relation r,
+ Page pg, OffsetNumber o, bool l, bool isNull)
+{
+
+ if (!isNull)
+ {
+
+ GISTENTRY *fep;
+
+ gistentryinit(*e, k, r, pg, o, l);
+
+
+ fep = (GISTENTRY *)
+ DatumGetPointer(FunctionCall1Coll(&giststate->fetchFn[nkey],
+ giststate->supportCollation[nkey],
+ PointerGetDatum(e)));
+ /* fecthFn returns the given pointer */
+ if (fep != e)
+ gistentryinit(*e, fep->key, fep->rel, fep->page, fep->offset,
+ fep->leafkey);
+ }
+ else
+ gistentryinit(*e, (Datum) 0, r, pg, o, l);
+}
+
+/*
+ * Fetch all keys in tuple.
+ * returns new IndexTuple that contains GISTENTRY with fetched data
+ */
+IndexTuple
+gistFetchTuple(GISTSTATE *giststate, Relation r, IndexTuple tuple, bool isnull[])
+{
+ MemoryContext oldcxt = MemoryContextSwitchTo(giststate->tempCxt);
+ GISTENTRY fentry[INDEX_MAX_KEYS];
+ Datum fetchatt[INDEX_MAX_KEYS];
+ int i;
+ IndexTuple res;
+
+
+ for (i = 0; i < r->rd_att->natts; i++)
+ {
+ Datum datum = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]);
+ gistfentryinit(giststate, i, &fentry[i],
+ datum, r, NULL, (OffsetNumber) 0,
+ FALSE, FALSE);
+
+ fetchatt[i] = fentry[i].key;
+
+ }
+ 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 53cf96f..0706269 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -722,11 +722,11 @@ index_vacuum_cleanup(IndexVacuumInfo *info,
}
/* ----------------
- * index_can_return - does index support index-only scans?
+ * index_can_return - does index column with number 'attno' supports index-only scans?
* ----------------
*/
bool
-index_can_return(Relation indexRelation)
+index_can_return(Relation indexRelation, int attno)
{
FmgrInfo *procedure;
@@ -738,8 +738,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/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 1ee3b93..0b72c99 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1782,14 +1782,13 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
bool result;
Bitmapset *attrs_used = NULL;
Bitmapset *index_attrs = NULL;
+ Bitmapset *index_only_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
@@ -1834,14 +1833,17 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
index_attrs =
bms_add_member(index_attrs,
attno - FirstLowInvalidHeapAttributeNumber);
+ if (index->canreturn[i])
+ index_only_attrs = bms_add_member(index_only_attrs,
+ attno - FirstLowInvalidHeapAttributeNumber);
}
- /* Do we have all the necessary attributes? */
- result = bms_is_subset(attrs_used, index_attrs);
-
+ /* Do we have all the necessary attributes? And do all of them support index-only scan? */
+ result = ((bms_is_subset(attrs_used, index_attrs))&&
+ (bms_is_subset(attrs_used, index_only_attrs)));
bms_free(attrs_used);
bms_free(index_attrs);
-
+ bms_free(index_only_attrs);
return result;
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index b2becfa..fdd82a9 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);
}
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 d99158f..01199a1 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 39394df..f014946 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 21daf3b..62b3de8 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];
@@ -109,7 +110,7 @@ typedef struct GISTSTATE
* In a non-ordered search (no order-by operators), the RBTree degenerates
* to a single item, which we use as a queue of unvisited index pages only.
* In this case matched heap items from the current index leaf page are
- * remembered in GISTScanOpaqueData.pageData[] and returned directly from
+ * remembered in GISTScanOpaqueData.pageData and returned directly from
* there, instead of building a separate GISTSearchItem for each one.
*/
@@ -118,6 +119,7 @@ typedef struct GISTSearchHeapItem
{
ItemPointerData heapPtr;
bool recheck; /* T if quals must be rechecked */
+ IndexTuple ftup; /* Tuple contains datum fetched from key for index-only scans */
} GISTSearchHeapItem;
/* Unvisited item, either index page or heap tuple */
@@ -168,9 +170,8 @@ typedef struct GISTScanOpaqueData
double *distances; /* output area for gistindex_keytest */
/* In a non-ordered search, returnable heap items are stored here: */
- GISTSearchHeapItem pageData[BLCKSZ / sizeof(IndexTupleData)];
- OffsetNumber nPageData; /* number of valid items in array */
- OffsetNumber curPageData; /* next item to return */
+ List *pageData;
+
} GISTScanOpaqueData;
typedef GISTScanOpaqueData *GISTScanOpaque;
@@ -424,6 +425,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);
@@ -524,6 +526,10 @@ 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 void gistfentryinit(GISTSTATE *giststate, int nkey,
+ GISTENTRY *e, Datum k, Relation r,
+ Page pg, OffsetNumber o, bool l, bool isNull);
+extern IndexTuple gistFetchTuple(GISTSTATE *giststate, Relation r, IndexTuple tuple, bool *isnull);
extern void gistMakeUnionKey(GISTSTATE *giststate, int attno,
GISTENTRY *entry1, bool isnull1,
GISTENTRY *entry2, bool isnull2,
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index 67b57cd..d92d632 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 e09f557..a729031 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 11113 ));
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 11112 ));
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 497e652..a5a2aae 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 1 0 16 "2281 2281" _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)");
@@ -974,6 +974,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 = 11111 ( gistcanreturn PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "2281 2281" _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_ ));
@@ -4039,6 +4041,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 = 11112 ( 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_ ));
@@ -4057,6 +4061,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 = 11113 ( 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_ ));
@@ -4958,7 +4964,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 1 0 16 "2281 2281" _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 05cfbcd..2b52e66 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -533,7 +533,7 @@ 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 *canreturn; /* can index columns 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 60b5d01..44b0203 100644
--- a/src/include/utils/geo_decls.h
+++ b/src/include/utils/geo_decls.h
@@ -411,6 +411,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);
@@ -418,6 +419,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 26d883c..a656816 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_indexonly.out b/src/test/regress/expected/gist_indexonly.out
new file mode 100644
index 0000000..0a51cdd
--- /dev/null
+++ b/src/test/regress/expected/gist_indexonly.out
@@ -0,0 +1,50 @@
+--
+-- Test Index-only scan plan on GiST indexes
+--
+CREATE TABLE gist_tbl (b box, p point);
+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) FROM generate_series(0,10000) as i;
+vacuum analyze;
+SET enable_seqscan=off;
+SET enable_bitmapscan=off;
+-- Check singlecolumn index-only scan for point opclass
+CREATE INDEX gist_tbl_point_index ON gist_tbl USING gist (p);
+EXPLAIN (COSTS OFF)
+select p from gist_tbl where p <@ box(point(0,0), point(100,100)) and length(p::text) < 10;
+ QUERY PLAN
+--------------------------------------------------------
+ Index Only Scan using gist_tbl_point_index on gist_tbl
+ Index Cond: (p <@ '(100,100),(0,0)'::box)
+ Filter: (length((p)::text) < 10)
+(3 rows)
+
+DROP INDEX gist_tbl_point_index;
+-- Check singlecolumn index-only scan for box opclass
+CREATE INDEX gist_tbl_box_index ON gist_tbl USING gist (b);
+vacuum analyze;
+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)
+
+DROP INDEX gist_tbl_box_index;
+-- Check multicolumn indexonlyscan for gist
+CREATE INDEX gist_tbl_multi_index ON gist_tbl USING gist (b, p);
+vacuum analyze;
+EXPLAIN (COSTS OFF)
+select b, p from gist_tbl where ( (b <@ box(point(5,5), point(6,6))) and (p <@ box(point(5,5), point(5.5,5.5))));
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Index Only Scan using gist_tbl_multi_index on gist_tbl
+ Index Cond: ((b <@ '(6,6),(5,5)'::box) AND (p <@ '(5.5,5.5),(5,5)'::box))
+(2 rows)
+
+DROP INDEX gist_tbl_multi_index;
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+RESET enable_indexscan;
+RESET enable_indexonlyscan;
+DROP TABLE gist_tbl;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d4f02e5..e22cf13 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -98,7 +98,7 @@ test: event_trigger
# ----------
# Another group of parallel tests
# ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast equivclass
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast equivclass gist_indexonly
# ----------
# Another group of parallel tests
# NB: temp.sql does a reconnect which transiently uses 2 connections,
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 611b0a8..9d9ca9b 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -91,6 +91,7 @@ test: portals
test: arrays
test: btree_index
test: hash_index
+test: gist_indexonly
test: update
test: delete
test: namespace
diff --git a/src/test/regress/sql/gist_indexonly.sql b/src/test/regress/sql/gist_indexonly.sql
new file mode 100644
index 0000000..49da865
--- /dev/null
+++ b/src/test/regress/sql/gist_indexonly.sql
@@ -0,0 +1,43 @@
+--
+-- Test Index-only scan plan on GiST indexes
+--
+
+CREATE TABLE gist_tbl (b box, p point);
+
+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) FROM generate_series(0,10000) as i;
+
+vacuum analyze;
+
+SET enable_seqscan=off;
+SET enable_bitmapscan=off;
+
+-- Check singlecolumn index-only scan for point opclass
+
+CREATE INDEX gist_tbl_point_index ON gist_tbl USING gist (p);
+EXPLAIN (COSTS OFF)
+select p from gist_tbl where p <@ box(point(0,0), point(100,100)) and length(p::text) < 10;
+DROP INDEX gist_tbl_point_index;
+
+-- Check singlecolumn index-only scan for box opclass
+
+CREATE INDEX gist_tbl_box_index ON gist_tbl USING gist (b);
+vacuum analyze;
+EXPLAIN (COSTS OFF)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6));
+DROP INDEX gist_tbl_box_index;
+
+-- Check multicolumn indexonlyscan for gist
+
+CREATE INDEX gist_tbl_multi_index ON gist_tbl USING gist (b, p);
+vacuum analyze;
+EXPLAIN (COSTS OFF)
+select b, p from gist_tbl where ( (b <@ box(point(5,5), point(6,6))) and (p <@ box(point(5,5), point(5.5,5.5))));
+DROP INDEX gist_tbl_multi_index;
+
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+RESET enable_indexscan;
+RESET enable_indexonlyscan;
+
+DROP TABLE gist_tbl;indexonlyscan_gist_docs.patchapplication/octet-stream; name=indexonlyscan_gist_docs.patchDownload
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 719,725 ****
<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>
</row>
--- 719,725 ----
<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 column supports index-only scans,
or zero if none</entry>
</row>
*** a/doc/src/sgml/gist.sgml
--- b/doc/src/sgml/gist.sgml
***************
*** 266,272 **** 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.
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
--- 266,272 ----
<para>
There are seven methods that an index operator class for
! <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,288 **** 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).
</para>
<variablelist>
--- 282,289 ----
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). The optional ninth method <function>fetch</> is required to provide
! an opportunity for operator class to support index-only scan.
</para>
<variablelist>
***************
*** 813,818 **** my_distance(PG_FUNCTION_ARGS)
--- 814,897 ----
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><function>fetch</></term>
+ <listitem>
+ <para>
+ The method of retrieving data from the index page without loss. Converts the
+ index representation of the data item into a suitable format.
+ </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>
+
+ And 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);
+ GISTENTRY *retval;
+ /*
+ * return data from index into output slot in appropriate datatype
+ */
+ retval = palloc(sizeof(GISTENTRY));
+ if (DatumGetP(entry->key) != NULL)
+ {
+ input_data_type *in = DatumGetP(entry->key);
+ fetched_data_type *fetched_data = palloc(sizeof(fetched_data_type));
+
+ /*
+ * computations that are necessary to get fetched_data formatted as it storaged in table
+ */
+
+ /*
+ * fill *fetched_data from entry->key ...
+ */
+ gistentryinit(*retval, PointerGetDatum(fetched_data),
+ entry->rel, entry->page,
+ entry->offset, FALSE);
+
+ }
+ else
+ {
+ gistentryinit(*retval, (Datum) 0,
+ entry->rel, entry->page,
+ entry->offset, FALSE);
+ }
+
+ PG_RETURN_POINTER(retval);
+ }
+ </programlisting>
+ </para>
+
+ <para>
+ Attention:
+ Gistcanreturn is supposed to be true for opclass if ANY <function>fetch</>
+ method is defined. If <function>fetch</> function exists it would be used in
+ index-only scan. Thus the responsibility rests with the opclass developer.
+ </para>
+
+ <para>
+ If compress method is not lossy, opclass could in principle support index-only scans.
+ One must be careful with calculations inside <function>fetch</> function.
+ If conversions lose some precision due to rounding, query result will be
+ wrong. Such behaviour is not allowed for any scan plan.
+ </para>
+
+ </listitem>
+ </varlistentry>
</variablelist>
<para>
*** a/doc/src/sgml/indexam.sgml
--- b/doc/src/sgml/indexam.sgml
***************
*** 274,282 **** amvacuumcleanup (IndexVacuumInfo *info,
<para>
<programlisting>
bool
! amcanreturn (Relation indexRelation);
</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
IndexTuple. Return TRUE if so, else FALSE. If the index AM can never
support index-only scans (an example is hash, which stores only
--- 274,282 ----
<para>
<programlisting>
bool
! amcanreturn (Relation indexRelation, int attno);
</programlisting>
! Check whether the index column can support <firstterm>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 onlyOn 11 February 2015 at 22:50, Anastasia Lubennikova <lubennikovaav@gmail.com
wrote:
Finally there is a new version of patch (in attachments).
It provides multicolumn index-only scan for GiST indexes.- Memory leak is fixed.
- little code cleanup
- example of performance test in attachmens
- function OIDs have debugging values (1111*) just to avoid merge
conflicts while testing patchWiki page of the project is
https://wiki.postgresql.org/wiki/Support_for_Index-only_scans_for_GIST_GSoC_2014
Waiting for feedback.
Hi Anastasia. Thanks for the updated patch. I've just tried applying it
to head and it doesn't appear to apply cleanly.
$ patch -p1 < ~/Downloads/indexonlyscan_gist_2.0.patch
(Stripping trailing CRs from patch; use --binary to disable.)
patching file src/backend/access/gist/gist.c
Hunk #1 succeeded at 1404 (offset 9 lines).
Hunk #2 succeeded at 1434 (offset 9 lines).
(Stripping trailing CRs from patch; use --binary to disable.)
patching file src/backend/access/gist/gistget.c
Hunk #1 succeeded at 227 (offset 1 line).
Hunk #2 succeeded at 243 (offset 1 line).
Hunk #3 succeeded at 293 (offset -4 lines).
Hunk #4 succeeded at 330 (offset -4 lines).
Hunk #5 succeeded at 365 (offset -5 lines).
Hunk #6 succeeded at 444 (offset -27 lines).
Hunk #7 succeeded at 474 (offset -27 lines).
Hunk #8 FAILED at 518.
Hunk #9 succeeded at 507 (offset -28 lines).
Hunk #10 succeeded at 549 with fuzz 1 (offset -28 lines).
Hunk #11 FAILED at 601.
2 out of 11 hunks FAILED -- saving rejects to file
src/backend/access/gist/gistget.c.rej
...
--
Thom
Thanks for answer.
Now it seems to be applied correctly.
2015-02-12 3:12 GMT+04:00 Thom Brown <thom@linux.com>:
On 11 February 2015 at 22:50, Anastasia Lubennikova <
lubennikovaav@gmail.com> wrote:Finally there is a new version of patch (in attachments).
It provides multicolumn index-only scan for GiST indexes.- Memory leak is fixed.
- little code cleanup
- example of performance test in attachmens
- function OIDs have debugging values (1111*) just to avoid merge
conflicts while testing patchWiki page of the project is
https://wiki.postgresql.org/wiki/Support_for_Index-only_scans_for_GIST_GSoC_2014
Waiting for feedback.
Hi Anastasia. Thanks for the updated patch. I've just tried applying it
to head and it doesn't appear to apply cleanly.$ patch -p1 < ~/Downloads/indexonlyscan_gist_2.0.patch
(Stripping trailing CRs from patch; use --binary to disable.)
patching file src/backend/access/gist/gist.c
Hunk #1 succeeded at 1404 (offset 9 lines).
Hunk #2 succeeded at 1434 (offset 9 lines).
(Stripping trailing CRs from patch; use --binary to disable.)
patching file src/backend/access/gist/gistget.c
Hunk #1 succeeded at 227 (offset 1 line).
Hunk #2 succeeded at 243 (offset 1 line).
Hunk #3 succeeded at 293 (offset -4 lines).
Hunk #4 succeeded at 330 (offset -4 lines).
Hunk #5 succeeded at 365 (offset -5 lines).
Hunk #6 succeeded at 444 (offset -27 lines).
Hunk #7 succeeded at 474 (offset -27 lines).
Hunk #8 FAILED at 518.
Hunk #9 succeeded at 507 (offset -28 lines).
Hunk #10 succeeded at 549 with fuzz 1 (offset -28 lines).
Hunk #11 FAILED at 601.
2 out of 11 hunks FAILED -- saving rejects to file
src/backend/access/gist/gistget.c.rej
...--
Thom
--
Best regards,
Lubennikova Anastasia
Attachments:
indexonlyscan_gist_2.1.patchtext/x-diff; charset=US-ASCII; name=indexonlyscan_gist_2.1.patchDownload
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index db2a452..53750da 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
@@ -1426,6 +1434,22 @@ initGISTstate(Relation index)
return giststate;
}
+/*
+ * Gistcanreturn is supposed to be true if ANY FetchFn method is defined.
+ * If FetchFn exists it would be used in index-only scan
+ * Thus the responsibility rests with the opclass developer.
+ */
+
+Datum
+gistcanreturn(PG_FUNCTION_ARGS) {
+ Relation index = (Relation) PG_GETARG_POINTER(0);
+ int i = PG_GETARG_INT32(1);
+ if (OidIsValid(index_getprocid(index, i+1, GIST_FETCH_PROC)))
+ PG_RETURN_BOOL(true);
+ else
+ PG_RETURN_BOOL(false);
+}
+
void
freeGISTstate(GISTSTATE *giststate)
{
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 717cb85..0925e56 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -227,8 +227,10 @@ gistindex_keytest(IndexScanDesc scan,
* If tbm/ntids aren't NULL, we are doing an amgetbitmap scan, and heap
* 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,
+ * tuple TIDs are returned into so->pageData. For an ordered indexscan,
* heap tuple TIDs are pushed into individual search queue items.
+ * If index-only scan is possible, heap tuples themselves are returned
+ * into so->pageData or into search queue.
*
* 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
@@ -241,6 +243,10 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
Buffer buffer;
Page page;
+ GISTSTATE *giststate = so->giststate;
+ Relation r = scan->indexRelation;
+ bool isnull[INDEX_MAX_KEYS];
+ GISTSearchHeapItem *tmpListItem;
GISTPageOpaque opaque;
OffsetNumber maxoff;
OffsetNumber i;
@@ -287,8 +293,6 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
MemoryContextSwitchTo(oldcxt);
}
- so->nPageData = so->curPageData = 0;
-
/*
* check all tuples on page
*/
@@ -326,11 +330,20 @@ 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
+ */
+
+ /* form tmpListItem and fill it with data to add into so->pageData */
+ tmpListItem = palloc(sizeof(GISTSearchHeapItem));
+ tmpListItem->heapPtr = it->t_tid;
+ tmpListItem->recheck = recheck;
+ /*
+ * If index-only scan is possible add the data fetched from index field
*/
- so->pageData[so->nPageData].heapPtr = it->t_tid;
- so->pageData[so->nPageData].recheck = recheck;
- so->nPageData++;
+ if (scan->xs_want_itup)
+ tmpListItem->ftup = gistFetchTuple(giststate, r, it, isnull);
+
+ so->pageData = lappend(so->pageData, tmpListItem);
}
else
{
@@ -352,6 +365,13 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
item->blkno = InvalidBlockNumber;
item->data.heap.heapPtr = it->t_tid;
item->data.heap.recheck = recheck;
+
+ /*
+ * If index-only scan is possible add the data fetched from index field
+ */
+ if (scan->xs_want_itup) {
+ item->data.heap.ftup = gistFetchTuple(giststate, r, it, isnull);
+ }
}
else
{
@@ -424,6 +444,11 @@ 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;
+ /*
+ * If index-only scan is possible fill the slot with data fetched from index field
+ */
+ if(scan->xs_want_itup)
+ scan->xs_itup = item->data.heap.ftup;
res = true;
}
else
@@ -449,6 +474,7 @@ gistgettuple(PG_FUNCTION_ARGS)
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
ScanDirection dir = (ScanDirection) PG_GETARG_INT32(1);
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
+ bool first_tuple = true;
if (dir != ForwardScanDirection)
elog(ERROR, "GiST only supports forward scan direction");
@@ -464,7 +490,6 @@ gistgettuple(PG_FUNCTION_ARGS)
pgstat_count_index_scan(scan->indexRelation);
so->firstCall = false;
- so->curPageData = so->nPageData = 0;
fakeItem.blkno = GIST_ROOT_BLKNO;
memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
@@ -481,13 +506,27 @@ gistgettuple(PG_FUNCTION_ARGS)
/* Fetch tuples index-page-at-a-time */
for (;;)
{
- if (so->curPageData < so->nPageData)
+ if(list_length(so->pageData)>0)
{
/* 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;
- so->curPageData++;
- PG_RETURN_BOOL(true);
+ GISTSearchHeapItem *tmp = (GISTSearchHeapItem *)lfirst(list_head(so->pageData));
+ scan->xs_ctup.t_self = tmp->heapPtr;
+ scan->xs_recheck = tmp->recheck;
+ /* If index-only scan is possible, return fetched data*/
+ if(scan->xs_want_itup) {
+ scan->xs_itup = tmp->ftup;
+ if(!first_tuple)
+ pfree(tmp->ftup);
+ first_tuple=false;
+ }
+ pfree(tmp);
+
+ /*
+ * Delete ListCell that we have already read.
+ * It's always head of so->pageData
+ */
+ so->pageData = list_delete_first(so->pageData);
+ PG_RETURN_BOOL(TRUE);
}
/* find and process the next index page */
@@ -509,7 +548,7 @@ gistgettuple(PG_FUNCTION_ARGS)
gistScanPage(scan, item, item->distances, NULL, NULL);
pfree(item);
- } while (so->nPageData == 0);
+ } while (list_length(so->pageData)==0);
}
}
}
@@ -532,7 +571,6 @@ gistgetbitmap(PG_FUNCTION_ARGS)
pgstat_count_index_scan(scan->indexRelation);
/* Begin the scan by processing the root page */
- so->curPageData = so->nPageData = 0;
fakeItem.blkno = GIST_ROOT_BLKNO;
memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
diff --git a/src/backend/access/gist/gistproc.c b/src/backend/access/gist/gistproc.c
index 9fab6c8..e1355dd 100644
--- a/src/backend/access/gist/gistproc.c
+++ b/src/backend/access/gist/gistproc.c
@@ -152,6 +152,16 @@ gist_box_decompress(PG_FUNCTION_ARGS)
}
/*
+ * 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)
*
* As in the R-tree paper, we use change in area as our penalty metric
@@ -1186,6 +1196,41 @@ gist_point_compress(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(entry);
}
+/*
+ * GiST Fetch method for point
+ * get point coordinates from it's bounding box coordinates
+ * and form new gistentry
+ */
+Datum
+gist_point_fetch(PG_FUNCTION_ARGS)
+{
+ GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+ GISTENTRY *retval;
+ retval = palloc(sizeof(GISTENTRY));
+ if (DatumGetBoxP(entry->key) != NULL)
+ {
+ BOX *in = DatumGetBoxP(entry->key);
+ Point *r;
+
+ 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);
+
+ }
+ else
+ {
+ gistentryinit(*retval, (Datum) 0,
+ 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 cc8d818..98a1b54 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.
+ */
+ so->pageData = NULL;
+ scan->xs_itupdesc = RelationGetDescr(r);
+
MemoryContextSwitchTo(oldCxt);
PG_RETURN_POINTER(scan);
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 38af0e0..b03966a 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -618,6 +618,65 @@ gistFormTuple(GISTSTATE *giststate, Relation r,
return res;
}
+/*
+ * initialize a GiST entry with fetched value in key field
+ */
+void
+gistfentryinit(GISTSTATE *giststate, int nkey,
+ GISTENTRY *e, Datum k, Relation r,
+ Page pg, OffsetNumber o, bool l, bool isNull)
+{
+
+ if (!isNull)
+ {
+
+ GISTENTRY *fep;
+
+ gistentryinit(*e, k, r, pg, o, l);
+
+
+ fep = (GISTENTRY *)
+ DatumGetPointer(FunctionCall1Coll(&giststate->fetchFn[nkey],
+ giststate->supportCollation[nkey],
+ PointerGetDatum(e)));
+ /* fecthFn returns the given pointer */
+ if (fep != e)
+ gistentryinit(*e, fep->key, fep->rel, fep->page, fep->offset,
+ fep->leafkey);
+ }
+ else
+ gistentryinit(*e, (Datum) 0, r, pg, o, l);
+}
+
+/*
+ * Fetch all keys in tuple.
+ * returns new IndexTuple that contains GISTENTRY with fetched data
+ */
+IndexTuple
+gistFetchTuple(GISTSTATE *giststate, Relation r, IndexTuple tuple, bool isnull[])
+{
+ MemoryContext oldcxt = MemoryContextSwitchTo(giststate->tempCxt);
+ GISTENTRY fentry[INDEX_MAX_KEYS];
+ Datum fetchatt[INDEX_MAX_KEYS];
+ int i;
+ IndexTuple res;
+
+
+ for (i = 0; i < r->rd_att->natts; i++)
+ {
+ Datum datum = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]);
+ gistfentryinit(giststate, i, &fentry[i],
+ datum, r, NULL, (OffsetNumber) 0,
+ FALSE, FALSE);
+
+ fetchatt[i] = fentry[i].key;
+
+ }
+ 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 00c1d69..62b10ce 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -722,11 +722,11 @@ index_vacuum_cleanup(IndexVacuumInfo *info,
}
/* ----------------
- * index_can_return - does index support index-only scans?
+ * index_can_return - does index column with number 'attno' supports index-only scans?
* ----------------
*/
bool
-index_can_return(Relation indexRelation)
+index_can_return(Relation indexRelation, int attno)
{
FmgrInfo *procedure;
@@ -738,8 +738,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/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index b86a3cd..8e08f2e 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1782,14 +1782,13 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
bool result;
Bitmapset *attrs_used = NULL;
Bitmapset *index_attrs = NULL;
+ Bitmapset *index_only_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
@@ -1834,14 +1833,17 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
index_attrs =
bms_add_member(index_attrs,
attno - FirstLowInvalidHeapAttributeNumber);
+ if (index->canreturn[i])
+ index_only_attrs = bms_add_member(index_only_attrs,
+ attno - FirstLowInvalidHeapAttributeNumber);
}
- /* Do we have all the necessary attributes? */
- result = bms_is_subset(attrs_used, index_attrs);
-
+ /* Do we have all the necessary attributes? And do all of them support index-only scan? */
+ result = ((bms_is_subset(attrs_used, index_attrs))&&
+ (bms_is_subset(attrs_used, index_only_attrs)));
bms_free(attrs_used);
bms_free(index_attrs);
-
+ bms_free(index_only_attrs);
return result;
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index fb7db6d..192f418 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);
}
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 d1d6247..d86590a 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 01f0a70..50261b8 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 382826e..14f83d4 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];
@@ -109,7 +110,7 @@ typedef struct GISTSTATE
* In a non-ordered search (no order-by operators), the RBTree degenerates
* to a single item, which we use as a queue of unvisited index pages only.
* In this case matched heap items from the current index leaf page are
- * remembered in GISTScanOpaqueData.pageData[] and returned directly from
+ * remembered in GISTScanOpaqueData.pageData and returned directly from
* there, instead of building a separate GISTSearchItem for each one.
*/
@@ -118,6 +119,7 @@ typedef struct GISTSearchHeapItem
{
ItemPointerData heapPtr;
bool recheck; /* T if quals must be rechecked */
+ IndexTuple ftup; /* Tuple contains datum fetched from key for index-only scans */
} GISTSearchHeapItem;
/* Unvisited item, either index page or heap tuple */
@@ -153,9 +155,8 @@ typedef struct GISTScanOpaqueData
double *distances; /* output area for gistindex_keytest */
/* In a non-ordered search, returnable heap items are stored here: */
- GISTSearchHeapItem pageData[BLCKSZ / sizeof(IndexTupleData)];
- OffsetNumber nPageData; /* number of valid items in array */
- OffsetNumber curPageData; /* next item to return */
+ List *pageData;
+
} GISTScanOpaqueData;
typedef GISTScanOpaqueData *GISTScanOpaque;
@@ -408,6 +409,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);
@@ -508,6 +510,10 @@ 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 void gistfentryinit(GISTSTATE *giststate, int nkey,
+ GISTENTRY *e, Datum k, Relation r,
+ Page pg, OffsetNumber o, bool l, bool isNull);
+extern IndexTuple gistFetchTuple(GISTSTATE *giststate, Relation r, IndexTuple tuple, bool *isnull);
extern void gistMakeUnionKey(GISTSTATE *giststate, int attno,
GISTENTRY *entry1, bool isnull1,
GISTENTRY *entry2, bool isnull2,
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index 0531222..79609f7 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 49d3d13..c71cb8f 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 11113 ));
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 11112 ));
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 9edfdb8..dc8ddea 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 1 0 16 "2281 2281" _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)");
@@ -981,6 +981,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 = 11111 ( gistcanreturn PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "2281 2281" _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_ ));
@@ -4062,6 +4064,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 = 11112 ( 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_ ));
@@ -4080,6 +4084,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 = 11113 ( 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_ ));
@@ -5012,7 +5018,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 1 0 16 "2281 2281" _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 6845a40..e134e38 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -533,7 +533,7 @@ 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 *canreturn; /* can index columns 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 0b6d3c3..786096e 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 5603817..abe64e5 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_indexonly.out b/src/test/regress/expected/gist_indexonly.out
new file mode 100644
index 0000000..0a51cdd
--- /dev/null
+++ b/src/test/regress/expected/gist_indexonly.out
@@ -0,0 +1,50 @@
+--
+-- Test Index-only scan plan on GiST indexes
+--
+CREATE TABLE gist_tbl (b box, p point);
+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) FROM generate_series(0,10000) as i;
+vacuum analyze;
+SET enable_seqscan=off;
+SET enable_bitmapscan=off;
+-- Check singlecolumn index-only scan for point opclass
+CREATE INDEX gist_tbl_point_index ON gist_tbl USING gist (p);
+EXPLAIN (COSTS OFF)
+select p from gist_tbl where p <@ box(point(0,0), point(100,100)) and length(p::text) < 10;
+ QUERY PLAN
+--------------------------------------------------------
+ Index Only Scan using gist_tbl_point_index on gist_tbl
+ Index Cond: (p <@ '(100,100),(0,0)'::box)
+ Filter: (length((p)::text) < 10)
+(3 rows)
+
+DROP INDEX gist_tbl_point_index;
+-- Check singlecolumn index-only scan for box opclass
+CREATE INDEX gist_tbl_box_index ON gist_tbl USING gist (b);
+vacuum analyze;
+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)
+
+DROP INDEX gist_tbl_box_index;
+-- Check multicolumn indexonlyscan for gist
+CREATE INDEX gist_tbl_multi_index ON gist_tbl USING gist (b, p);
+vacuum analyze;
+EXPLAIN (COSTS OFF)
+select b, p from gist_tbl where ( (b <@ box(point(5,5), point(6,6))) and (p <@ box(point(5,5), point(5.5,5.5))));
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Index Only Scan using gist_tbl_multi_index on gist_tbl
+ Index Cond: ((b <@ '(6,6),(5,5)'::box) AND (p <@ '(5.5,5.5),(5,5)'::box))
+(2 rows)
+
+DROP INDEX gist_tbl_multi_index;
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+RESET enable_indexscan;
+RESET enable_indexonlyscan;
+DROP TABLE gist_tbl;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e0ae2f2..758c326 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,7 +96,7 @@ test: rules
# ----------
# Another group of parallel tests
# ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast equivclass
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast equivclass gist_indexonly
# ----------
# Another group of parallel tests
# NB: temp.sql does a reconnect which transiently uses 2 connections,
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 7f762bd..47803a3 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -91,6 +91,7 @@ test: portals
test: arrays
test: btree_index
test: hash_index
+test: gist_indexonly
test: update
test: delete
test: namespace
diff --git a/src/test/regress/sql/gist_indexonly.sql b/src/test/regress/sql/gist_indexonly.sql
new file mode 100644
index 0000000..49da865
--- /dev/null
+++ b/src/test/regress/sql/gist_indexonly.sql
@@ -0,0 +1,43 @@
+--
+-- Test Index-only scan plan on GiST indexes
+--
+
+CREATE TABLE gist_tbl (b box, p point);
+
+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) FROM generate_series(0,10000) as i;
+
+vacuum analyze;
+
+SET enable_seqscan=off;
+SET enable_bitmapscan=off;
+
+-- Check singlecolumn index-only scan for point opclass
+
+CREATE INDEX gist_tbl_point_index ON gist_tbl USING gist (p);
+EXPLAIN (COSTS OFF)
+select p from gist_tbl where p <@ box(point(0,0), point(100,100)) and length(p::text) < 10;
+DROP INDEX gist_tbl_point_index;
+
+-- Check singlecolumn index-only scan for box opclass
+
+CREATE INDEX gist_tbl_box_index ON gist_tbl USING gist (b);
+vacuum analyze;
+EXPLAIN (COSTS OFF)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6));
+DROP INDEX gist_tbl_box_index;
+
+-- Check multicolumn indexonlyscan for gist
+
+CREATE INDEX gist_tbl_multi_index ON gist_tbl USING gist (b, p);
+vacuum analyze;
+EXPLAIN (COSTS OFF)
+select b, p from gist_tbl where ( (b <@ box(point(5,5), point(6,6))) and (p <@ box(point(5,5), point(5.5,5.5))));
+DROP INDEX gist_tbl_multi_index;
+
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+RESET enable_indexscan;
+RESET enable_indexonlyscan;
+
+DROP TABLE gist_tbl;
On 12 February 2015 at 10:40, Anastasia Lubennikova <lubennikovaav@gmail.com
wrote:
Thanks for answer.
Now it seems to be applied correctly.
(please avoid top-posting)
Thanks for the updated patch. I can confirm that it now cleanly applies
and compiles fine. I've run the tested in the SQL file you provided, and
it's behaving as expected.
Thom
On 02/12/2015 12:40 PM, Anastasia Lubennikova wrote:
Thanks for answer.
Now it seems to be applied correctly.
Thanks, it would be great to get this completed.
This still leaks memory with the same test query as earlier. The leak
seems to be into a different memory context now; it used to be to the
"GiST scan context", but now it's to the ExecutorState context. See
attached patch which makes the leak evident.
Looking at my previous comments from August:
* What's the reason for turning GISTScanOpaqueData.pageData from an
array to a List?
* Documentation is missing.
- Heikki
Attachments:
show-gistgettuple-leak-2.patchapplication/x-patch; name=show-gistgettuple-leak-2.patchDownload
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 0925e56..3768c9c 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -526,6 +526,12 @@ gistgettuple(PG_FUNCTION_ARGS)
* It's always head of so->pageData
*/
so->pageData = list_delete_first(so->pageData);
+ {
+ static int lastreport = 0;
+ if ((lastreport++) % 10000 == 0)
+ MemoryContextStats(CurrentMemoryContext);
+ }
+
PG_RETURN_BOOL(TRUE);
}
On 12 February 2015 at 16:40, Heikki Linnakangas <hlinnakangas@vmware.com>
wrote:
On 02/12/2015 12:40 PM, Anastasia Lubennikova wrote:
Thanks for answer.
Now it seems to be applied correctly.* Documentation is missing.
Anastasia provided a documentation patch in the first email.
Thom
On Thu, Feb 12, 2015 at 3:07 PM, Thom Brown <thom@linux.com> wrote:
On 12 February 2015 at 16:40, Heikki Linnakangas <hlinnakangas@vmware.com>
wrote:
On 02/12/2015 12:40 PM, Anastasia Lubennikova wrote:
Thanks for answer.
Now it seems to be applied correctly.* Documentation is missing.
Anastasia provided a documentation patch in the first email.
Yeah, but it's a good practice send all the patches together. Will make the
life of the reviewers better ;-)
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
I add MemoryContext listCxt to avoid memory leak. listCxt is created once
in gistrescan (only for index-only scan plan ) and reseted when scan of the
leaf page is finished.
I do not sure if the problem was completely solved, so I wait for feedback.
* What's the reason for turning GISTScanOpaqueData.pageData from an array
to a List?
This array is field of structure GISTScanOpaqueData. Memory for that
structure allocated in function gistbeginscan(). The array is static so
it's declared only one time in structure:
GISTSearchHeapItem pageData [BLCKSZ/sizeof(IndexTupleData)]
But how could we know size of array if we don't know what data would be
returned? I mean type and amount.
I asked Alexander about that and he offered me to use List instead of Array.
Attachments:
indexonlyscan_gist_2.2.patchapplication/octet-stream; name=indexonlyscan_gist_2.2.patchDownload
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index db2a452..53750da 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
@@ -1426,6 +1434,22 @@ initGISTstate(Relation index)
return giststate;
}
+/*
+ * Gistcanreturn is supposed to be true if ANY FetchFn method is defined.
+ * If FetchFn exists it would be used in index-only scan
+ * Thus the responsibility rests with the opclass developer.
+ */
+
+Datum
+gistcanreturn(PG_FUNCTION_ARGS) {
+ Relation index = (Relation) PG_GETARG_POINTER(0);
+ int i = PG_GETARG_INT32(1);
+ if (OidIsValid(index_getprocid(index, i+1, GIST_FETCH_PROC)))
+ PG_RETURN_BOOL(true);
+ else
+ PG_RETURN_BOOL(false);
+}
+
void
freeGISTstate(GISTSTATE *giststate)
{
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 717cb85..860997f 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -227,8 +227,10 @@ gistindex_keytest(IndexScanDesc scan,
* If tbm/ntids aren't NULL, we are doing an amgetbitmap scan, and heap
* 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,
+ * tuple TIDs are returned into so->pageData. For an ordered indexscan,
* heap tuple TIDs are pushed into individual search queue items.
+ * If index-only scan is possible, heap tuples themselves are returned
+ * into so->pageData or into search queue.
*
* 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
@@ -241,6 +243,10 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
Buffer buffer;
Page page;
+ GISTSTATE *giststate = so->giststate;
+ Relation r = scan->indexRelation;
+ bool isnull[INDEX_MAX_KEYS];
+ GISTSearchHeapItem *tmpListItem;
GISTPageOpaque opaque;
OffsetNumber maxoff;
OffsetNumber i;
@@ -287,8 +293,6 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
MemoryContextSwitchTo(oldcxt);
}
- so->nPageData = so->curPageData = 0;
-
/*
* check all tuples on page
*/
@@ -326,11 +330,29 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
else if (scan->numberOfOrderBys == 0 && GistPageIsLeaf(page))
{
/*
- * Non-ordered scan, so report heap tuples in so->pageData[]
+ * To avoid the overhead, when we don't need second memory context,
+ * switch to listCxt only if index-only scan is executing
+ */
+ if (scan->xs_want_itup)
+ oldcxt = MemoryContextSwitchTo(so->listCxt);
+ /*
+ * Non-ordered scan, so report tuples in so->pageData
+ */
+
+ /* form tmpListItem and fill it with data to add into so->pageData */
+ tmpListItem = palloc(sizeof(GISTSearchHeapItem));
+ tmpListItem->heapPtr = it->t_tid;
+ tmpListItem->recheck = recheck;
+ /*
+ * If index-only scan is possible add the data fetched from index field
*/
- so->pageData[so->nPageData].heapPtr = it->t_tid;
- so->pageData[so->nPageData].recheck = recheck;
- so->nPageData++;
+ if (scan->xs_want_itup)
+ tmpListItem->ftup = gistFetchTuple(giststate, r, it);
+
+ so->pageData = lappend(so->pageData, tmpListItem);
+
+ if (scan->xs_want_itup)
+ MemoryContextSwitchTo(oldcxt);
}
else
{
@@ -352,6 +374,12 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
item->blkno = InvalidBlockNumber;
item->data.heap.heapPtr = it->t_tid;
item->data.heap.recheck = recheck;
+
+ /*
+ * If index-only scan is possible add the data fetched from index field
+ */
+ if (scan->xs_want_itup)
+ item->data.heap.ftup = gistFetchTuple(giststate, r, it);
}
else
{
@@ -424,6 +452,11 @@ 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;
+ /*
+ * If index-only scan is possible fill the slot with data fetched from index field
+ */
+ if(scan->xs_want_itup)
+ scan->xs_itup = item->data.heap.ftup;
res = true;
}
else
@@ -449,6 +482,7 @@ gistgettuple(PG_FUNCTION_ARGS)
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
ScanDirection dir = (ScanDirection) PG_GETARG_INT32(1);
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
+ bool first_tuple = true;
if (dir != ForwardScanDirection)
elog(ERROR, "GiST only supports forward scan direction");
@@ -464,11 +498,11 @@ gistgettuple(PG_FUNCTION_ARGS)
pgstat_count_index_scan(scan->indexRelation);
so->firstCall = false;
- so->curPageData = so->nPageData = 0;
fakeItem.blkno = GIST_ROOT_BLKNO;
memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
gistScanPage(scan, &fakeItem, NULL, NULL, NULL);
+
}
if (scan->numberOfOrderBys > 0)
@@ -481,15 +515,33 @@ gistgettuple(PG_FUNCTION_ARGS)
/* Fetch tuples index-page-at-a-time */
for (;;)
{
- if (so->curPageData < so->nPageData)
+ if(list_length(so->pageData)>0)
{
+
/* 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;
- so->curPageData++;
- PG_RETURN_BOOL(true);
- }
+ GISTSearchHeapItem *tmp = (GISTSearchHeapItem *)lfirst(list_head(so->pageData));
+ scan->xs_ctup.t_self = tmp->heapPtr;
+ scan->xs_recheck = tmp->recheck;
+ /* If index-only scan is possible, return fetched data*/
+ if(scan->xs_want_itup) {
+ scan->xs_itup = tmp->ftup;
+ if(!first_tuple)
+ pfree(tmp->ftup);
+ first_tuple=false;
+ }
+ pfree(tmp);
+ /*
+ * Delete ListCell that we have already read.
+ * It's always head of so->pageData
+ */
+ so->pageData = list_delete_first(so->pageData);
+
+ if((scan->xs_want_itup)&&(list_length(so->pageData)==0))
+ MemoryContextReset(so->listCxt);
+
+ PG_RETURN_BOOL(TRUE);
+ }
/* find and process the next index page */
do
{
@@ -509,7 +561,7 @@ gistgettuple(PG_FUNCTION_ARGS)
gistScanPage(scan, item, item->distances, NULL, NULL);
pfree(item);
- } while (so->nPageData == 0);
+ } while (list_length(so->pageData)==0);
}
}
}
@@ -532,7 +584,6 @@ gistgetbitmap(PG_FUNCTION_ARGS)
pgstat_count_index_scan(scan->indexRelation);
/* Begin the scan by processing the root page */
- so->curPageData = so->nPageData = 0;
fakeItem.blkno = GIST_ROOT_BLKNO;
memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
diff --git a/src/backend/access/gist/gistproc.c b/src/backend/access/gist/gistproc.c
index 9fab6c8..e1355dd 100644
--- a/src/backend/access/gist/gistproc.c
+++ b/src/backend/access/gist/gistproc.c
@@ -152,6 +152,16 @@ gist_box_decompress(PG_FUNCTION_ARGS)
}
/*
+ * 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)
*
* As in the R-tree paper, we use change in area as our penalty metric
@@ -1186,6 +1196,41 @@ gist_point_compress(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(entry);
}
+/*
+ * GiST Fetch method for point
+ * get point coordinates from it's bounding box coordinates
+ * and form new gistentry
+ */
+Datum
+gist_point_fetch(PG_FUNCTION_ARGS)
+{
+ GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+ GISTENTRY *retval;
+ retval = palloc(sizeof(GISTENTRY));
+ if (DatumGetBoxP(entry->key) != NULL)
+ {
+ BOX *in = DatumGetBoxP(entry->key);
+ Point *r;
+
+ 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);
+
+ }
+ else
+ {
+ gistentryinit(*retval, (Datum) 0,
+ 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 991858f..a63133a 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.
+ */
+ so->pageData = NULL;
+ scan->xs_itupdesc = RelationGetDescr(r);
+
MemoryContextSwitchTo(oldCxt);
PG_RETURN_POINTER(scan);
@@ -106,6 +113,12 @@ gistrescan(PG_FUNCTION_ARGS)
int i;
MemoryContext oldCxt;
+ if(scan->xs_want_itup)
+ so->listCxt = AllocSetContextCreate(so->giststate->scanCxt,
+ "GiST list context",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
/* rescan an existing indexscan --- reset state */
/*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 38af0e0..0776f9c 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -618,6 +618,66 @@ gistFormTuple(GISTSTATE *giststate, Relation r,
return res;
}
+/*
+ * initialize a GiST entry with fetched value in key field
+ */
+void
+gistfentryinit(GISTSTATE *giststate, int nkey,
+ GISTENTRY *e, Datum k, Relation r,
+ Page pg, OffsetNumber o, bool l, bool isNull)
+{
+
+ if (!isNull)
+ {
+
+ GISTENTRY *fep;
+
+ gistentryinit(*e, k, r, pg, o, l);
+
+
+ fep = (GISTENTRY *)
+ DatumGetPointer(FunctionCall1Coll(&giststate->fetchFn[nkey],
+ giststate->supportCollation[nkey],
+ PointerGetDatum(e)));
+ /* fecthFn returns the given pointer */
+ if (fep != e)
+ gistentryinit(*e, fep->key, fep->rel, fep->page, fep->offset,
+ fep->leafkey);
+ }
+ else
+ gistentryinit(*e, (Datum) 0, r, pg, o, l);
+}
+
+/*
+ * 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);
+ GISTENTRY fentry[INDEX_MAX_KEYS];
+ Datum fetchatt[INDEX_MAX_KEYS];
+ bool isnull[INDEX_MAX_KEYS];
+ int i;
+ IndexTuple res;
+
+
+ for (i = 0; i < r->rd_att->natts; i++)
+ {
+ Datum datum = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]);
+ gistfentryinit(giststate, i, &fentry[i],
+ datum, r, NULL, (OffsetNumber) 0,
+ FALSE, FALSE);
+
+ fetchatt[i] = fentry[i].key;
+
+ }
+ 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 00c1d69..62b10ce 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -722,11 +722,11 @@ index_vacuum_cleanup(IndexVacuumInfo *info,
}
/* ----------------
- * index_can_return - does index support index-only scans?
+ * index_can_return - does index column with number 'attno' supports index-only scans?
* ----------------
*/
bool
-index_can_return(Relation indexRelation)
+index_can_return(Relation indexRelation, int attno)
{
FmgrInfo *procedure;
@@ -738,8 +738,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/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index b86a3cd..8e08f2e 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1782,14 +1782,13 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
bool result;
Bitmapset *attrs_used = NULL;
Bitmapset *index_attrs = NULL;
+ Bitmapset *index_only_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
@@ -1834,14 +1833,17 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
index_attrs =
bms_add_member(index_attrs,
attno - FirstLowInvalidHeapAttributeNumber);
+ if (index->canreturn[i])
+ index_only_attrs = bms_add_member(index_only_attrs,
+ attno - FirstLowInvalidHeapAttributeNumber);
}
- /* Do we have all the necessary attributes? */
- result = bms_is_subset(attrs_used, index_attrs);
-
+ /* Do we have all the necessary attributes? And do all of them support index-only scan? */
+ result = ((bms_is_subset(attrs_used, index_attrs))&&
+ (bms_is_subset(attrs_used, index_only_attrs)));
bms_free(attrs_used);
bms_free(index_attrs);
-
+ bms_free(index_only_attrs);
return result;
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 313a5c1..a4d380e 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);
}
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 d1d6247..d86590a 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 01f0a70..50261b8 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 ce83042..0613b9d 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];
@@ -109,7 +110,7 @@ typedef struct GISTSTATE
* In a non-ordered search (no order-by operators), the RBTree degenerates
* to a single item, which we use as a queue of unvisited index pages only.
* In this case matched heap items from the current index leaf page are
- * remembered in GISTScanOpaqueData.pageData[] and returned directly from
+ * remembered in GISTScanOpaqueData.pageData and returned directly from
* there, instead of building a separate GISTSearchItem for each one.
*/
@@ -118,6 +119,7 @@ typedef struct GISTSearchHeapItem
{
ItemPointerData heapPtr;
bool recheck; /* T if quals must be rechecked */
+ IndexTuple ftup; /* Tuple contains datum fetched from key. Used for index-only scans */
} GISTSearchHeapItem;
/* Unvisited item, either index page or heap tuple */
@@ -146,6 +148,7 @@ typedef struct GISTScanOpaqueData
{
GISTSTATE *giststate; /* index information, see above */
pairingheap *queue; /* queue of unvisited items */
+ MemoryContext listCxt; /* context holding the list of returned tuples. for index-only scans */
MemoryContext queueCxt; /* context holding the queue */
bool qual_ok; /* false if qual can never be satisfied */
bool firstCall; /* true until first gistgettuple call */
@@ -154,9 +157,8 @@ typedef struct GISTScanOpaqueData
double *distances; /* output area for gistindex_keytest */
/* In a non-ordered search, returnable heap items are stored here: */
- GISTSearchHeapItem pageData[BLCKSZ / sizeof(IndexTupleData)];
- OffsetNumber nPageData; /* number of valid items in array */
- OffsetNumber curPageData; /* next item to return */
+ List *pageData;
+
} GISTScanOpaqueData;
typedef GISTScanOpaqueData *GISTScanOpaque;
@@ -409,6 +411,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);
@@ -509,6 +512,10 @@ 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 void gistfentryinit(GISTSTATE *giststate, int nkey,
+ GISTENTRY *e, Datum k, Relation r,
+ Page pg, OffsetNumber o, bool l, 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/pg_am.h b/src/include/catalog/pg_am.h
index 0531222..79609f7 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 49d3d13..c71cb8f 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 11113 ));
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 11112 ));
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 4268b99..6d6c8f4 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 1 0 16 "2281 2281" _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)");
@@ -981,6 +981,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 = 11111 ( gistcanreturn PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "2281 2281" _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_ ));
@@ -4064,6 +4066,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 = 11112 ( 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_ ));
@@ -4082,6 +4086,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 = 11113 ( 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_ ));
@@ -5014,7 +5020,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 1 0 16 "2281 2281" _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 6845a40..e134e38 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -533,7 +533,7 @@ 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 *canreturn; /* can index columns 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 8da6c6c..2a91620 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 5603817..abe64e5 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/parallel_schedule b/src/test/regress/parallel_schedule
index e0ae2f2..758c326 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,7 +96,7 @@ test: rules
# ----------
# Another group of parallel tests
# ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast equivclass
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast equivclass gist_indexonly
# ----------
# Another group of parallel tests
# NB: temp.sql does a reconnect which transiently uses 2 connections,
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 7f762bd..47803a3 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -91,6 +91,7 @@ test: portals
test: arrays
test: btree_index
test: hash_index
+test: gist_indexonly
test: update
test: delete
test: namespace
indexonlyscan_gist_docs.patchapplication/octet-stream; name=indexonlyscan_gist_docs.patchDownload
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 719,725 ****
<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>
</row>
--- 719,725 ----
<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 column supports index-only scans,
or zero if none</entry>
</row>
*** a/doc/src/sgml/gist.sgml
--- b/doc/src/sgml/gist.sgml
***************
*** 266,272 **** 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.
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
--- 266,272 ----
<para>
There are seven methods that an index operator class for
! <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,288 **** 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).
</para>
<variablelist>
--- 282,289 ----
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). The optional ninth method <function>fetch</> is required to provide
! an opportunity for operator class to support index-only scan.
</para>
<variablelist>
***************
*** 813,818 **** my_distance(PG_FUNCTION_ARGS)
--- 814,897 ----
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><function>fetch</></term>
+ <listitem>
+ <para>
+ The method of retrieving data from the index page without loss. Converts the
+ index representation of the data item into a suitable format.
+ </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>
+
+ And 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);
+ GISTENTRY *retval;
+ /*
+ * return data from index into output slot in appropriate datatype
+ */
+ retval = palloc(sizeof(GISTENTRY));
+ if (DatumGetP(entry->key) != NULL)
+ {
+ input_data_type *in = DatumGetP(entry->key);
+ fetched_data_type *fetched_data = palloc(sizeof(fetched_data_type));
+
+ /*
+ * computations that are necessary to get fetched_data formatted as it storaged in table
+ */
+
+ /*
+ * fill *fetched_data from entry->key ...
+ */
+ gistentryinit(*retval, PointerGetDatum(fetched_data),
+ entry->rel, entry->page,
+ entry->offset, FALSE);
+
+ }
+ else
+ {
+ gistentryinit(*retval, (Datum) 0,
+ entry->rel, entry->page,
+ entry->offset, FALSE);
+ }
+
+ PG_RETURN_POINTER(retval);
+ }
+ </programlisting>
+ </para>
+
+ <para>
+ Attention:
+ Gistcanreturn is supposed to be true for opclass if ANY <function>fetch</>
+ method is defined. If <function>fetch</> function exists it would be used in
+ index-only scan. Thus the responsibility rests with the opclass developer.
+ </para>
+
+ <para>
+ If compress method is not lossy, opclass could in principle support index-only scans.
+ One must be careful with calculations inside <function>fetch</> function.
+ If conversions lose some precision due to rounding, query result will be
+ wrong. Such behaviour is not allowed for any scan plan.
+ </para>
+
+ </listitem>
+ </varlistentry>
</variablelist>
<para>
*** a/doc/src/sgml/indexam.sgml
--- b/doc/src/sgml/indexam.sgml
***************
*** 274,282 **** amvacuumcleanup (IndexVacuumInfo *info,
<para>
<programlisting>
bool
! amcanreturn (Relation indexRelation);
</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
IndexTuple. Return TRUE if so, else FALSE. If the index AM can never
support index-only scans (an example is hash, which stores only
--- 274,282 ----
<para>
<programlisting>
bool
! amcanreturn (Relation indexRelation, int attno);
</programlisting>
! Check whether the index column can support <firstterm>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 onlyOn 02/27/2015 04:19 PM, Anastasia Lubennikova wrote:
I add MemoryContext listCxt to avoid memory leak. listCxt is created once
in gistrescan (only for index-only scan plan ) and reseted when scan of the
leaf page is finished.I do not sure if the problem was completely solved, so I wait for feedback.
Yeah, I think that solves it.
* What's the reason for turning GISTScanOpaqueData.pageData from an array
to a List?This array is field of structure GISTScanOpaqueData. Memory for that
structure allocated in function gistbeginscan(). The array is static so
it's declared only one time in structure:
GISTSearchHeapItem pageData [BLCKSZ/sizeof(IndexTupleData)]But how could we know size of array if we don't know what data would be
returned? I mean type and amount.
You're only adding a pointer to the IndexTuple to GISTSearchHeapItem.
The GISTSearchHeapItem struct itself is still of constant size.
I spent a little time cleaning this up. I reverted that pageData change
so that it's an array again, put back the gist_indexonly.sql and
expected output files that were missing from your latest version,
removed a couple of unused local variables that gcc complained about. I
refactored gistFetchTuple a bit, because it was doing IMHO quite bogus
things with NULLs. It was passing NULLs to the opclass' fetch function,
but it didn't pass the isNull flag correctly. I changed it so that the
fetch function is not called at all for NULLs.
I think this is pretty close to being committable. I'll make a round of
editorializing over the docs, and the code comments as well.
The opr_sanity regression test is failing, there's apparently something
wrong with the pg_proc entries of the *canreturn functions. I haven't
looked into that yet; could you fix that?
- Heikki
Attachments:
indexonlyscan_gist_2.3-heikki.patchapplication/x-patch; name=indexonlyscan_gist_2.3-heikki.patchDownload
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index db2a452..53750da 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
@@ -1426,6 +1434,22 @@ initGISTstate(Relation index)
return giststate;
}
+/*
+ * Gistcanreturn is supposed to be true if ANY FetchFn method is defined.
+ * If FetchFn exists it would be used in index-only scan
+ * Thus the responsibility rests with the opclass developer.
+ */
+
+Datum
+gistcanreturn(PG_FUNCTION_ARGS) {
+ Relation index = (Relation) PG_GETARG_POINTER(0);
+ int i = PG_GETARG_INT32(1);
+ if (OidIsValid(index_getprocid(index, i+1, GIST_FETCH_PROC)))
+ PG_RETURN_BOOL(true);
+ else
+ PG_RETURN_BOOL(false);
+}
+
void
freeGISTstate(GISTSTATE *giststate)
{
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 717cb85..80532c8 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -229,6 +229,8 @@ gistindex_keytest(IndexScanDesc scan,
* 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.
+ * If index-only scan is possible, heap tuples themselves are returned
+ * into so->pageData or into search queue.
*
* 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
@@ -241,6 +243,8 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
Buffer buffer;
Page page;
+ GISTSTATE *giststate = so->giststate;
+ Relation r = scan->indexRelation;
GISTPageOpaque opaque;
OffsetNumber maxoff;
OffsetNumber i;
@@ -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,20 @@ 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;
+
+ /*
+ * If index-only scan is possible add the data fetched from index.
+ */
+ 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 +368,12 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
item->blkno = InvalidBlockNumber;
item->data.heap.heapPtr = it->t_tid;
item->data.heap.recheck = recheck;
+
+ /*
+ * If index-only scan is possible add the data fetched from index field
+ */
+ if (scan->xs_want_itup)
+ item->data.heap.ftup = gistFetchTuple(giststate, r, it);
}
else
{
@@ -424,6 +446,11 @@ 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;
+ /*
+ * If index-only scan is possible fill the slot with data fetched from index field
+ */
+ if(scan->xs_want_itup)
+ scan->xs_itup = item->data.heap.ftup;
res = true;
}
else
@@ -465,6 +492,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 +512,16 @@ 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;
+ /* If index-only scan is possible, return fetched data */
+ if (scan->xs_want_itup)
+ scan->xs_itup = so->pageData[so->curPageData].ftup;
+
so->curPageData++;
+
PG_RETURN_BOOL(true);
}
@@ -533,6 +568,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));
diff --git a/src/backend/access/gist/gistproc.c b/src/backend/access/gist/gistproc.c
index 9fab6c8..e1355dd 100644
--- a/src/backend/access/gist/gistproc.c
+++ b/src/backend/access/gist/gistproc.c
@@ -152,6 +152,16 @@ gist_box_decompress(PG_FUNCTION_ARGS)
}
/*
+ * 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)
*
* As in the R-tree paper, we use change in area as our penalty metric
@@ -1186,6 +1196,41 @@ gist_point_compress(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(entry);
}
+/*
+ * GiST Fetch method for point
+ * get point coordinates from it's bounding box coordinates
+ * and form new gistentry
+ */
+Datum
+gist_point_fetch(PG_FUNCTION_ARGS)
+{
+ GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+ GISTENTRY *retval;
+ retval = palloc(sizeof(GISTENTRY));
+ if (DatumGetBoxP(entry->key) != NULL)
+ {
+ BOX *in = DatumGetBoxP(entry->key);
+ Point *r;
+
+ 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);
+
+ }
+ else
+ {
+ gistentryinit(*retval, (Datum) 0,
+ 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 991858f..cb461fd 100644
--- a/src/backend/access/gist/gistscan.c
+++ b/src/backend/access/gist/gistscan.c
@@ -88,6 +88,12 @@ 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);
@@ -106,6 +112,12 @@ gistrescan(PG_FUNCTION_ARGS)
int i;
MemoryContext oldCxt;
+ 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);
/* rescan an existing indexscan --- reset state */
/*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 38af0e0..1e755ed 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -618,6 +618,50 @@ 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 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 = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]);
+ if (!isnull[i])
+ fetchatt[i] = gistFetchAtt(giststate, i, datum, r);
+ else
+ 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 00c1d69..62b10ce 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -722,11 +722,11 @@ index_vacuum_cleanup(IndexVacuumInfo *info,
}
/* ----------------
- * index_can_return - does index support index-only scans?
+ * index_can_return - does index column with number 'attno' supports index-only scans?
* ----------------
*/
bool
-index_can_return(Relation indexRelation)
+index_can_return(Relation indexRelation, int attno)
{
FmgrInfo *procedure;
@@ -738,8 +738,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/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index b86a3cd..8e08f2e 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1782,14 +1782,13 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
bool result;
Bitmapset *attrs_used = NULL;
Bitmapset *index_attrs = NULL;
+ Bitmapset *index_only_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
@@ -1834,14 +1833,17 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
index_attrs =
bms_add_member(index_attrs,
attno - FirstLowInvalidHeapAttributeNumber);
+ if (index->canreturn[i])
+ index_only_attrs = bms_add_member(index_only_attrs,
+ attno - FirstLowInvalidHeapAttributeNumber);
}
- /* Do we have all the necessary attributes? */
- result = bms_is_subset(attrs_used, index_attrs);
-
+ /* Do we have all the necessary attributes? And do all of them support index-only scan? */
+ result = ((bms_is_subset(attrs_used, index_attrs))&&
+ (bms_is_subset(attrs_used, index_only_attrs)));
bms_free(attrs_used);
bms_free(index_attrs);
-
+ bms_free(index_only_attrs);
return result;
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 313a5c1..a4d380e 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);
}
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 d1d6247..d86590a 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 01f0a70..50261b8 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 ce83042..b8e0ba7 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,7 @@ typedef struct GISTSearchHeapItem
{
ItemPointerData heapPtr;
bool recheck; /* T if quals must be rechecked */
+ IndexTuple ftup; /* Tuple contains datum fetched from key. Used for index-only scans */
} GISTSearchHeapItem;
/* Unvisited item, either index page or heap tuple */
@@ -157,6 +159,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 +413,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);
@@ -508,7 +513,7 @@ 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/pg_am.h b/src/include/catalog/pg_am.h
index 0531222..79609f7 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 49d3d13..c71cb8f 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 11113 ));
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 11112 ));
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 4268b99..6d6c8f4 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 1 0 16 "2281 2281" _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)");
@@ -981,6 +981,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 = 11111 ( gistcanreturn PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "2281 2281" _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_ ));
@@ -4064,6 +4066,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 = 11112 ( 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_ ));
@@ -4082,6 +4086,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 = 11113 ( 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_ ));
@@ -5014,7 +5020,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 1 0 16 "2281 2281" _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 6845a40..e134e38 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -533,7 +533,7 @@ 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 *canreturn; /* can index columns 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 8da6c6c..2a91620 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 5603817..abe64e5 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_indexonly.out b/src/test/regress/expected/gist_indexonly.out
new file mode 100644
index 0000000..0a51cdd
--- /dev/null
+++ b/src/test/regress/expected/gist_indexonly.out
@@ -0,0 +1,50 @@
+--
+-- Test Index-only scan plan on GiST indexes
+--
+CREATE TABLE gist_tbl (b box, p point);
+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) FROM generate_series(0,10000) as i;
+vacuum analyze;
+SET enable_seqscan=off;
+SET enable_bitmapscan=off;
+-- Check singlecolumn index-only scan for point opclass
+CREATE INDEX gist_tbl_point_index ON gist_tbl USING gist (p);
+EXPLAIN (COSTS OFF)
+select p from gist_tbl where p <@ box(point(0,0), point(100,100)) and length(p::text) < 10;
+ QUERY PLAN
+--------------------------------------------------------
+ Index Only Scan using gist_tbl_point_index on gist_tbl
+ Index Cond: (p <@ '(100,100),(0,0)'::box)
+ Filter: (length((p)::text) < 10)
+(3 rows)
+
+DROP INDEX gist_tbl_point_index;
+-- Check singlecolumn index-only scan for box opclass
+CREATE INDEX gist_tbl_box_index ON gist_tbl USING gist (b);
+vacuum analyze;
+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)
+
+DROP INDEX gist_tbl_box_index;
+-- Check multicolumn indexonlyscan for gist
+CREATE INDEX gist_tbl_multi_index ON gist_tbl USING gist (b, p);
+vacuum analyze;
+EXPLAIN (COSTS OFF)
+select b, p from gist_tbl where ( (b <@ box(point(5,5), point(6,6))) and (p <@ box(point(5,5), point(5.5,5.5))));
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Index Only Scan using gist_tbl_multi_index on gist_tbl
+ Index Cond: ((b <@ '(6,6),(5,5)'::box) AND (p <@ '(5.5,5.5),(5,5)'::box))
+(2 rows)
+
+DROP INDEX gist_tbl_multi_index;
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+RESET enable_indexscan;
+RESET enable_indexonlyscan;
+DROP TABLE gist_tbl;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e0ae2f2..758c326 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -96,7 +96,7 @@ test: rules
# ----------
# Another group of parallel tests
# ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast equivclass
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast equivclass gist_indexonly
# ----------
# Another group of parallel tests
# NB: temp.sql does a reconnect which transiently uses 2 connections,
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 7f762bd..47803a3 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -91,6 +91,7 @@ test: portals
test: arrays
test: btree_index
test: hash_index
+test: gist_indexonly
test: update
test: delete
test: namespace
diff --git a/src/test/regress/sql/gist_indexonly.sql b/src/test/regress/sql/gist_indexonly.sql
new file mode 100644
index 0000000..49da865
--- /dev/null
+++ b/src/test/regress/sql/gist_indexonly.sql
@@ -0,0 +1,43 @@
+--
+-- Test Index-only scan plan on GiST indexes
+--
+
+CREATE TABLE gist_tbl (b box, p point);
+
+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) FROM generate_series(0,10000) as i;
+
+vacuum analyze;
+
+SET enable_seqscan=off;
+SET enable_bitmapscan=off;
+
+-- Check singlecolumn index-only scan for point opclass
+
+CREATE INDEX gist_tbl_point_index ON gist_tbl USING gist (p);
+EXPLAIN (COSTS OFF)
+select p from gist_tbl where p <@ box(point(0,0), point(100,100)) and length(p::text) < 10;
+DROP INDEX gist_tbl_point_index;
+
+-- Check singlecolumn index-only scan for box opclass
+
+CREATE INDEX gist_tbl_box_index ON gist_tbl USING gist (b);
+vacuum analyze;
+EXPLAIN (COSTS OFF)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6));
+DROP INDEX gist_tbl_box_index;
+
+-- Check multicolumn indexonlyscan for gist
+
+CREATE INDEX gist_tbl_multi_index ON gist_tbl USING gist (b, p);
+vacuum analyze;
+EXPLAIN (COSTS OFF)
+select b, p from gist_tbl where ( (b <@ box(point(5,5), point(6,6))) and (p <@ box(point(5,5), point(5.5,5.5))));
+DROP INDEX gist_tbl_multi_index;
+
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+RESET enable_indexscan;
+RESET enable_indexonlyscan;
+
+DROP TABLE gist_tbl;
On 03/02/2015 12:43 AM, Heikki Linnakangas wrote:
On 02/27/2015 04:19 PM, Anastasia Lubennikova wrote:
I add MemoryContext listCxt to avoid memory leak. listCxt is created once
in gistrescan (only for index-only scan plan ) and reseted when scan of the
leaf page is finished.I do not sure if the problem was completely solved, so I wait for feedback.
Yeah, I think that solves it.
On further testing, there was actually still a leak with kNN-searches.
Fixed.
I spent a little time cleaning this up. I reverted that pageData change
so that it's an array again, put back the gist_indexonly.sql and
expected output files that were missing from your latest version,
removed a couple of unused local variables that gcc complained about. I
refactored gistFetchTuple a bit, because it was doing IMHO quite bogus
things with NULLs. It was passing NULLs to the opclass' fetch function,
but it didn't pass the isNull flag correctly. I changed it so that the
fetch function is not called at all for NULLs.I think this is pretty close to being committable. I'll make a round of
editorializing over the docs, and the code comments as well.The opr_sanity regression test is failing, there's apparently something
wrong with the pg_proc entries of the *canreturn functions. I haven't
looked into that yet; could you fix that?
I have pushed this, after fixing the opr_sanity failure, some bug fixes,
and documentation and comment cleanup.
Thanks for the patch!
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers