Index-only scans for GIST

Started by Anastasia Lubennikovaover 11 years ago8 messages
#1Anastasia Lubennikova
lubennikovaav@gmail.com
1 attachment(s)

Hi, hackers!
I work on a GSoC project "Index-only scans for GIST"
https://wiki.postgresql.org/wiki/Support_for_Index-only_scans_for_GIST_GSoC_2014

Repository is
https://github.com/lubennikovaav/postgres/tree/indexonlygist2
Patch is in attachments.

It includes index-only scans for multicolumn GIST and new regression test.
Fetch() method is realized for box and point opclasses.

Documentation is not updated yet, but I'm going to do it till the end of
GSoC.

I've got one question about query with OR condition. It is the last query
in regression test "gist_indexonly". It doesn't fail but it doensn't use
index-only scans. Could someone explain to me how it works?
It seems to depend on build_paths_for_OR
<http://doxygen.postgresql.org/indxpath_8c.html#ae660d2e886355e53ed3b9ec693e4afd2&gt;
function.
But I couldn't understand how.

--
Best regards,
Lubennikova Anastasia

Attachments:

indexonlyscan_gist.patchapplication/octet-stream; name=indexonlyscan_gist.patchDownload
*** a/src/backend/access/gist/gist.c
--- b/src/backend/access/gist/gist.c
***************
*** 1379,1384 **** initGISTstate(Relation index)
--- 1379,1392 ----
  		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
***************
*** 1401,1406 **** initGISTstate(Relation index)
--- 1409,1430 ----
  	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)
  {
*** a/src/backend/access/gist/gistget.c
--- b/src/backend/access/gist/gistget.c
***************
*** 226,233 **** 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,
   * heap tuple TIDs are pushed into individual search queue items.
   *
   * 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
--- 226,235 ----
   * 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,
   * 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,245 **** gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
--- 242,251 ----
  	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;
***************
*** 288,297 **** gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
  
  		(void) rb_insert(so->queue, (RBNode *) tmpItem, &isNew);
  
  		MemoryContextSwitchTo(oldcxt);
  	}
  
! 	so->nPageData = so->curPageData = 0;
  
  	/*
  	 * check all tuples on page
--- 294,305 ----
  
  		(void) rb_insert(so->queue, (RBNode *) tmpItem, &isNew);
  
+ 		/* Create new GISTSearchHeapItem to insert into pageData*/
+ 
  		MemoryContextSwitchTo(oldcxt);
  	}
  
! 	so->curPageData = NULL;
  
  	/*
  	 * check all tuples on page
***************
*** 330,340 **** gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
  		else if (scan->numberOfOrderBys == 0 && GistPageIsLeaf(page))
  		{
  			/*
! 			 * Non-ordered scan, so report heap tuples in so->pageData[]
  			 */
! 			so->pageData[so->nPageData].heapPtr = it->t_tid;
! 			so->pageData[so->nPageData].recheck = recheck;
! 			so->nPageData++;
  		}
  		else
  		{
--- 338,363 ----
  		else if (scan->numberOfOrderBys == 0 && GistPageIsLeaf(page))
  		{
  			/*
! 			 * Non-oredered scan, so tuples report in so->pageData
! 			 */
! 			oldcxt = MemoryContextSwitchTo(so->queueCxt);
! 			/* 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 fill the slot with data fetched from index field
  			 */
! 			if (scan->xs_want_itup)
! 				tmpListItem->ftup = gistFetchTuple(giststate, r, it, isnull);
! 
! 			so->pageData = lappend(so->pageData, tmpListItem);
! 			/*
! 			 * If it's first call of lappend() we should set so->curPageData not NULL
! 			 */
! 			if(so->curPageData == NULL)
! 				so->curPageData = list_head(so->pageData);
! 			MemoryContextSwitchTo(oldcxt);
  		}
  		else
  		{
***************
*** 357,362 **** gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
--- 380,390 ----
  				item->blkno = InvalidBlockNumber;
  				item->data.heap.heapPtr = it->t_tid;
  				item->data.heap.recheck = recheck;
+ 				/*
+ 				 * If index-only scan is possible fill the slot with data fetched from index field
+ 				 */
+ 				if (scan->xs_want_itup)
+ 					item->data.heap.ftup = gistFetchTuple(giststate, r, it, isnull);
  			}
  			else
  			{
***************
*** 451,456 **** getNextNearest(IndexScanDesc scan)
--- 479,489 ----
  			/* 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
***************
*** 492,498 **** 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));
--- 525,532 ----
  
  		so->firstCall = false;
  		so->curTreeItem = NULL;
! 
! 		so->curPageData = NULL;
  
  		fakeItem.blkno = GIST_ROOT_BLKNO;
  		memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
***************
*** 509,521 **** gistgettuple(PG_FUNCTION_ARGS)
  		/* Fetch tuples index-page-at-a-time */
  		for (;;)
  		{
! 			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;
! 				so->curPageData++;
! 				PG_RETURN_BOOL(true);
  			}
  
  			/* find and process the next index page */
--- 543,567 ----
  		/* Fetch tuples index-page-at-a-time */
  		for (;;)
  		{
! 			if(so->curPageData!=NULL)
  			{
  				/* continuing to return tuples from a leaf page */
! 				GISTSearchHeapItem *tmp = (GISTSearchHeapItem *)lfirst(so->curPageData);
! 				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;
! 
! 				ListCell *tmpPageData = so->curPageData;
! 				/* Go to the next ListCell */
! 				so->curPageData = lnext(so->curPageData);
! 				/*
! 				 * Delete ListCell that we have already read.
! 				 * It's always head of so->pageData
! 				 */
! 				so->pageData =  list_delete_cell(so->pageData, tmpPageData, NULL);
! 				PG_RETURN_BOOL(TRUE);
  			}
  
  			/* find and process the next index page */
***************
*** 537,543 **** gistgettuple(PG_FUNCTION_ARGS)
  				gistScanPage(scan, item, so->curTreeItem->distances, NULL, NULL);
  
  				pfree(item);
! 			} while (so->nPageData == 0);
  		}
  	}
  }
--- 583,589 ----
  				gistScanPage(scan, item, so->curTreeItem->distances, NULL, NULL);
  
  				pfree(item);
! 			} while (list_length(so->pageData)==0);
  		}
  	}
  }
***************
*** 561,567 **** 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));
--- 607,613 ----
  
  	/* Begin the scan by processing the root page */
  	so->curTreeItem = NULL;
! 	so->curPageData = NULL;
  
  	fakeItem.blkno = GIST_ROOT_BLKNO;
  	memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
*** a/src/backend/access/gist/gistproc.c
--- b/src/backend/access/gist/gistproc.c
***************
*** 152,157 **** gist_box_decompress(PG_FUNCTION_ARGS)
--- 152,167 ----
  }
  
  /*
+  * GiST Fetch method for boxes
+  * do not do anything --- we just use 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,1209 **** gist_point_compress(PG_FUNCTION_ARGS)
--- 1214,1253 ----
  	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)))
*** a/src/backend/access/gist/gistscan.c
--- b/src/backend/access/gist/gistscan.c
***************
*** 133,138 **** gistbeginscan(PG_FUNCTION_ARGS)
--- 133,146 ----
  
  	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;
+ 	so->curPageData = NULL;
+ 	scan->xs_itupdesc = RelationGetDescr(r);
+ 
  	MemoryContextSwitchTo(oldCxt);
  
  	PG_RETURN_POINTER(scan);
*** a/src/backend/access/gist/gistutil.c
--- b/src/backend/access/gist/gistutil.c
***************
*** 618,623 **** gistFormTuple(GISTSTATE *giststate, Relation r,
--- 618,680 ----
  	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 in key field
+  */
+ IndexTuple
+ gistFetchTuple(GISTSTATE *giststate, Relation r, IndexTuple tuple, bool isnull[])
+ {
+ 	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;
+ 	}
+ 	res = index_form_tuple(giststate->tupdesc, fetchatt, isnull);
+ 
+ 	/*
+ 	 * The offset number on tuples on internal pages is unused. For historical
+ 	 * reasons, it is set 0xffff.
+ 	 */
+ 	ItemPointerSetOffsetNumber(&(res->t_tid), 0xffff);
+ 	return res;
+ }
+ 
  float
  gistpenalty(GISTSTATE *giststate, int attno,
  			GISTENTRY *orig, bool isNullOrig,
*** a/src/backend/access/index/indexam.c
--- b/src/backend/access/index/indexam.c
***************
*** 722,732 **** index_vacuum_cleanup(IndexVacuumInfo *info,
  }
  
  /* ----------------
!  *		index_can_return - does index support index-only scans?
   * ----------------
   */
  bool
! index_can_return(Relation indexRelation)
  {
  	FmgrInfo   *procedure;
  
--- 722,732 ----
  }
  
  /* ----------------
!  *		index_can_return - does index column with number 'attno' supports index-only scans?
   * ----------------
   */
  bool
! index_can_return(Relation indexRelation, int attno)
  {
  	FmgrInfo   *procedure;
  
***************
*** 738,745 **** index_can_return(Relation indexRelation)
  
  	GET_REL_PROCEDURE(amcanreturn);
  
! 	return DatumGetBool(FunctionCall1(procedure,
! 									  PointerGetDatum(indexRelation)));
  }
  
  /* ----------------
--- 738,746 ----
  
  	GET_REL_PROCEDURE(amcanreturn);
  
! 	return DatumGetBool(FunctionCall2(procedure,
! 					  PointerGetDatum(indexRelation),
! 					  Int32GetDatum(attno)));
  }
  
  /* ----------------
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
***************
*** 1746,1759 **** check_index_only(RelOptInfo *rel, IndexOptInfo *index)
  	bool		result;
  	Bitmapset  *attrs_used = NULL;
  	Bitmapset  *index_attrs = NULL;
  	ListCell   *lc;
  	int			i;
  
! 	/* Index-only scans must be enabled, and index must be capable of them */
  	if (!enable_indexonlyscan)
  		return false;
- 	if (!index->canreturn)
- 		return false;
  
  	/*
  	 * Check that all needed attributes of the relation are available from the
--- 1746,1758 ----
  	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 */
  	if (!enable_indexonlyscan)
  		return false;
  
  	/*
  	 * Check that all needed attributes of the relation are available from the
***************
*** 1798,1811 **** check_index_only(RelOptInfo *rel, IndexOptInfo *index)
  		index_attrs =
  			bms_add_member(index_attrs,
  						   attno - FirstLowInvalidHeapAttributeNumber);
  	}
  
! 	/* Do we have all the necessary attributes? */
! 	result = bms_is_subset(attrs_used, index_attrs);
! 
  	bms_free(attrs_used);
  	bms_free(index_attrs);
! 
  	return result;
  }
  
--- 1797,1813 ----
  		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? 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;
  }
  
*** a/src/backend/optimizer/util/plancat.c
--- b/src/backend/optimizer/util/plancat.c
***************
*** 207,212 **** get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
--- 207,213 ----
  			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,224 **** 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->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;
--- 215,225 ----
  				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->amcanorderbyop = indexRelation->rd_am->amcanorderbyop;
  			info->amoptionalkey = indexRelation->rd_am->amoptionalkey;
  			info->amsearcharray = indexRelation->rd_am->amsearcharray;
*** a/src/include/access/genam.h
--- b/src/include/access/genam.h
***************
*** 156,162 **** 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 RegProcedure index_getprocid(Relation irel, AttrNumber attnum,
  				uint16 procnum);
  extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum,
--- 156,162 ----
  				  void *callback_state);
  extern IndexBulkDeleteResult *index_vacuum_cleanup(IndexVacuumInfo *info,
  					 IndexBulkDeleteResult *stats);
! 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,
*** a/src/include/access/gist.h
--- b/src/include/access/gist.h
***************
*** 33,39 ****
  #define GIST_PICKSPLIT_PROC				6
  #define GIST_EQUAL_PROC					7
  #define GIST_DISTANCE_PROC				8
! #define GISTNProcs						8
  
  /*
   * strategy numbers for GiST opclasses that want to implement the old
--- 33,40 ----
  #define GIST_PICKSPLIT_PROC				6
  #define GIST_EQUAL_PROC					7
  #define GIST_DISTANCE_PROC				8
! #define GIST_FETCH_PROC					9
! #define GISTNProcs					9
  
  /*
   * strategy numbers for GiST opclasses that want to implement the old
*** a/src/include/access/gist_private.h
--- b/src/include/access/gist_private.h
***************
*** 86,91 **** typedef struct GISTSTATE
--- 86,92 ----
  	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];
***************
*** 108,114 **** 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
   * there, instead of building a separate GISTSearchItem for each one.
   */
  
--- 109,115 ----
   * 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
   * there, instead of building a separate GISTSearchItem for each one.
   */
  
***************
*** 117,122 **** typedef struct GISTSearchHeapItem
--- 118,124 ----
  {
  	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 */
***************
*** 167,175 **** 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 */
  } GISTScanOpaqueData;
  
  typedef GISTScanOpaqueData *GISTScanOpaque;
--- 169,177 ----
  	double	   *distances;		/* output area for gistindex_keytest */
  
  	/* In a non-ordered search, returnable heap items are stored here: */
! 	List *pageData;
! 	ListCell *curPageData; /* next item to return from pageData */
! 
  } GISTScanOpaqueData;
  
  typedef GISTScanOpaqueData *GISTScanOpaque;
***************
*** 423,428 **** typedef struct GiSTOptions
--- 425,431 ----
  /* 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);
***************
*** 522,527 **** extern bool gistKeyIsEQ(GISTSTATE *giststate, int attno, Datum a, Datum b);
--- 525,535 ----
  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,
*** a/src/include/catalog/pg_am.h
--- b/src/include/catalog/pg_am.h
***************
*** 123,129 **** 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 ));
  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 ));
--- 123,129 ----
  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 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 ));
*** a/src/include/catalog/pg_amproc.h
--- b/src/include/catalog/pg_amproc.h
***************
*** 188,193 **** DATA(insert (	1029   600 600 5 2581 ));
--- 188,194 ----
  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 3253 ));
  DATA(insert (	2593   603 603 1 2578 ));
  DATA(insert (	2593   603 603 2 2583 ));
  DATA(insert (	2593   603 603 3 2579 ));
***************
*** 195,200 **** DATA(insert (	2593   603 603 4 2580 ));
--- 196,202 ----
  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 3252 ));
  DATA(insert (	2594   604 604 1 2585 ));
  DATA(insert (	2594   604 604 2 2583 ));
  DATA(insert (	2594   604 604 3 2586 ));
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 558,564 **** 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_ ));
  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)");
--- 558,564 ----
  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 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)");
***************
*** 941,946 **** DATA(insert OID = 776 (  gistbulkdelete    PGNSP PGUID 12 1 0 0 0 f f f f t f v
--- 941,948 ----
  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 = 3251 (  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_ ));
***************
*** 3996,4001 **** DATA(insert OID = 2579 (  gist_box_compress		PGNSP PGUID 12 1 0 0 0 f f f f t f
--- 3998,4005 ----
  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 = 3252 (  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_ ));
***************
*** 4014,4019 **** DATA(insert OID = 2592 (  gist_circle_compress	PGNSP PGUID 12 1 0 0 0 f f f f t
--- 4018,4025 ----
  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 = 3253 (  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_ ));
***************
*** 4905,4911 **** 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_ ));
  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)");
--- 4911,4917 ----
  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 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)");
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
***************
*** 526,532 **** typedef struct IndexOptInfo
  	bool		unique;			/* true if a unique index */
  	bool		immediate;		/* is uniqueness enforced immediately? */
  	bool		hypothetical;	/* true if index doesn't really exist */
! 	bool		canreturn;		/* can index return IndexTuples? */
  	bool		amcanorderbyop; /* does AM support order by operator result? */
  	bool		amoptionalkey;	/* can query omit key for the first column? */
  	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
--- 526,532 ----
  	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 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? */
*** a/src/include/utils/geo_decls.h
--- b/src/include/utils/geo_decls.h
***************
*** 411,416 **** extern Datum gist_box_picksplit(PG_FUNCTION_ARGS);
--- 411,417 ----
  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,423 **** extern Datum gist_circle_consistent(PG_FUNCTION_ARGS);
--- 419,426 ----
  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);
*** a/src/test/regress/expected/create_index.out
--- b/src/test/regress/expected/create_index.out
***************
*** 378,384 **** SELECT * FROM fast_emp4000
  ----------------------------------------------------------------
   Sort
     Sort Key: ((home_base[0])[0])
!    ->  Index Scan using grect2ind on fast_emp4000
           Index Cond: (home_base @ '(2000,1000),(200,200)'::box)
  (4 rows)
  
--- 378,384 ----
  ----------------------------------------------------------------
   Sort
     Sort Key: ((home_base[0])[0])
!    ->  Index Only Scan using grect2ind on fast_emp4000
           Index Cond: (home_base @ '(2000,1000),(200,200)'::box)
  (4 rows)
  
***************
*** 396,402 **** SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
                           QUERY PLAN                          
  -------------------------------------------------------------
   Aggregate
!    ->  Index Scan using grect2ind on fast_emp4000
           Index Cond: (home_base && '(1000,1000),(0,0)'::box)
  (3 rows)
  
--- 396,402 ----
                           QUERY PLAN                          
  -------------------------------------------------------------
   Aggregate
!    ->  Index Only Scan using grect2ind on fast_emp4000
           Index Cond: (home_base && '(1000,1000),(0,0)'::box)
  (3 rows)
  
***************
*** 408,417 **** 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                    
! --------------------------------------------------
   Aggregate
!    ->  Index Scan using grect2ind on fast_emp4000
           Index Cond: (home_base IS NULL)
  (3 rows)
  
--- 408,417 ----
  
  EXPLAIN (COSTS OFF)
  SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL;
!                       QUERY PLAN                       
! -------------------------------------------------------
   Aggregate
!    ->  Index Only Scan using grect2ind on fast_emp4000
           Index Cond: (home_base IS NULL)
  (3 rows)
  
***************
*** 495,501 **** SELECT count(*) FROM point_tbl WHERE f1 <@ box '(0,0,100,100)';
                       QUERY PLAN                     
  ----------------------------------------------------
   Aggregate
!    ->  Index Scan using gpointind on point_tbl
           Index Cond: (f1 <@ '(100,100),(0,0)'::box)
  (3 rows)
  
--- 495,501 ----
                       QUERY PLAN                     
  ----------------------------------------------------
   Aggregate
!    ->  Index Only Scan using gpointind on point_tbl
           Index Cond: (f1 <@ '(100,100),(0,0)'::box)
  (3 rows)
  
***************
*** 510,517 **** 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)
  (3 rows)
  
  SELECT count(*) FROM point_tbl WHERE box '(0,0,100,100)' @> f1;
--- 510,517 ----
                       QUERY PLAN                     
  ----------------------------------------------------
   Aggregate
!    ->  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;
***************
*** 525,531 **** 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 Cond: (f1 <@ '((0,0),(0,100),(100,100),(50,50),(100,0),(0,0))'::polygon)
  (3 rows)
  
--- 525,531 ----
                                         QUERY PLAN                                       
  ----------------------------------------------------------------------------------------
   Aggregate
!    ->  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)
  
***************
*** 540,546 **** SELECT count(*) FROM point_tbl WHERE f1 <@ circle '<(50,50),50>';
                       QUERY PLAN                     
  ----------------------------------------------------
   Aggregate
!    ->  Index Scan using gpointind on point_tbl
           Index Cond: (f1 <@ '<(50,50),50>'::circle)
  (3 rows)
  
--- 540,546 ----
                       QUERY PLAN                     
  ----------------------------------------------------
   Aggregate
!    ->  Index Only Scan using gpointind on point_tbl
           Index Cond: (f1 <@ '<(50,50),50>'::circle)
  (3 rows)
  
***************
*** 552,561 **** 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                    
! -------------------------------------------------
   Aggregate
!    ->  Index Scan using gpointind on point_tbl p
           Index Cond: (f1 << '(0,0)'::point)
  (3 rows)
  
--- 552,561 ----
  
  EXPLAIN (COSTS OFF)
  SELECT count(*) FROM point_tbl p WHERE p.f1 << '(0.0, 0.0)';
!                       QUERY PLAN                      
! ------------------------------------------------------
   Aggregate
!    ->  Index Only Scan using gpointind on point_tbl p
           Index Cond: (f1 << '(0,0)'::point)
  (3 rows)
  
***************
*** 567,576 **** 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                    
! -------------------------------------------------
   Aggregate
!    ->  Index Scan using gpointind on point_tbl p
           Index Cond: (f1 >> '(0,0)'::point)
  (3 rows)
  
--- 567,576 ----
  
  EXPLAIN (COSTS OFF)
  SELECT count(*) FROM point_tbl p WHERE p.f1 >> '(0.0, 0.0)';
!                       QUERY PLAN                      
! ------------------------------------------------------
   Aggregate
!    ->  Index Only Scan using gpointind on point_tbl p
           Index Cond: (f1 >> '(0,0)'::point)
  (3 rows)
  
***************
*** 582,591 **** 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                    
! -------------------------------------------------
   Aggregate
!    ->  Index Scan using gpointind on point_tbl p
           Index Cond: (f1 <^ '(0,0)'::point)
  (3 rows)
  
--- 582,591 ----
  
  EXPLAIN (COSTS OFF)
  SELECT count(*) FROM point_tbl p WHERE p.f1 <^ '(0.0, 0.0)';
!                       QUERY PLAN                      
! ------------------------------------------------------
   Aggregate
!    ->  Index Only Scan using gpointind on point_tbl p
           Index Cond: (f1 <^ '(0,0)'::point)
  (3 rows)
  
***************
*** 597,606 **** 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                    
! -------------------------------------------------
   Aggregate
!    ->  Index Scan using gpointind on point_tbl p
           Index Cond: (f1 >^ '(0,0)'::point)
  (3 rows)
  
--- 597,606 ----
  
  EXPLAIN (COSTS OFF)
  SELECT count(*) FROM point_tbl p WHERE p.f1 >^ '(0.0, 0.0)';
!                       QUERY PLAN                      
! ------------------------------------------------------
   Aggregate
!    ->  Index Only Scan using gpointind on point_tbl p
           Index Cond: (f1 >^ '(0,0)'::point)
  (3 rows)
  
***************
*** 612,621 **** 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                    
! -------------------------------------------------
   Aggregate
!    ->  Index Scan using gpointind on point_tbl p
           Index Cond: (f1 ~= '(-5,-12)'::point)
  (3 rows)
  
--- 612,621 ----
  
  EXPLAIN (COSTS OFF)
  SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)';
!                       QUERY PLAN                      
! ------------------------------------------------------
   Aggregate
!    ->  Index Only Scan using gpointind on point_tbl p
           Index Cond: (f1 ~= '(-5,-12)'::point)
  (3 rows)
  
***************
*** 627,635 **** 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
     Order By: (f1 <-> '(0,1)'::point)
  (2 rows)
  
--- 627,635 ----
  
  EXPLAIN (COSTS OFF)
  SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
!                   QUERY PLAN                  
! ----------------------------------------------
!  Index Only Scan using gpointind on point_tbl
     Order By: (f1 <-> '(0,1)'::point)
  (2 rows)
  
***************
*** 647,655 **** 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
     Index Cond: (f1 IS NULL)
  (2 rows)
  
--- 647,655 ----
  
  EXPLAIN (COSTS OFF)
  SELECT * FROM point_tbl WHERE f1 IS NULL;
!                   QUERY PLAN                  
! ----------------------------------------------
!  Index Only Scan using gpointind on point_tbl
     Index Cond: (f1 IS NULL)
  (2 rows)
  
***************
*** 661,669 **** 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
     Index Cond: (f1 IS NOT NULL)
     Order By: (f1 <-> '(0,1)'::point)
  (3 rows)
--- 661,669 ----
  
  EXPLAIN (COSTS OFF)
  SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
!                   QUERY PLAN                  
! ----------------------------------------------
!  Index Only Scan using gpointind on point_tbl
     Index Cond: (f1 IS NOT NULL)
     Order By: (f1 <-> '(0,1)'::point)
  (3 rows)
***************
*** 683,689 **** 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 Cond: (f1 <@ '(10,10),(-10,-10)'::box)
     Order By: (f1 <-> '(0,1)'::point)
  (3 rows)
--- 683,689 ----
  SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
                     QUERY PLAN                   
  ------------------------------------------------
!  Index Only Scan using gpointind on point_tbl
     Index Cond: (f1 <@ '(10,10),(-10,-10)'::box)
     Order By: (f1 <-> '(0,1)'::point)
  (3 rows)
*** /dev/null
--- b/src/test/regress/expected/gist_indexonly.out
***************
*** 0 ****
--- 1,61 ----
+ --
+ -- 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 TO false;
+ SET enable_bitmapscan TO false;
+ SET enable_indexscan TO false;
+ SET enable_indexonlyscan TO true;
+ -- 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)) limit 10;
+                           QUERY PLAN                          
+ --------------------------------------------------------------
+  Limit
+    ->  Index Only Scan using gist_tbl_point_index on gist_tbl
+          Index Cond: (p <@ '(100,100),(0,0)'::box)
+ (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);
+ EXPLAIN (COSTS OFF)
+ select b from gist_tbl where b <@ box(point(5,5),  point(6,6)) limit 10;
+                          QUERY PLAN                         
+ ------------------------------------------------------------
+  Limit
+    ->  Index Only Scan using gist_tbl_box_index on gist_tbl
+          Index Cond: (b <@ '(6,6),(5,5)'::box)
+ (3 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);
+ EXPLAIN (COSTS OFF)
+ select * from gist_tbl where ( (b <@ box(point(5,5),  point(6,6))) and (p <@ box(point(0,0),  point(100,100)))) limit 10;
+                                     QUERY PLAN                                     
+ -----------------------------------------------------------------------------------
+  Limit
+    ->  Index Only Scan using gist_tbl_multi_index on gist_tbl
+          Index Cond: ((b <@ '(6,6),(5,5)'::box) AND (p <@ '(100,100),(0,0)'::box))
+ (3 rows)
+ 
+ EXPLAIN (COSTS OFF)
+ select * from gist_tbl where ( (b <@ box(point(5,5),  point(6,6))) or (p <@ box(point(0,0),  point(100,100)))) limit 10;
+                                   QUERY PLAN                                  
+ ------------------------------------------------------------------------------
+  Limit
+    ->  Seq Scan on gist_tbl
+          Filter: ((b <@ '(6,6),(5,5)'::box) OR (p <@ '(100,100),(0,0)'::box))
+ (3 rows)
+ 
+ DROP INDEX gist_tbl_multi_index;
+ RESET enable_seqscan;
+ RESET enable_bitmapscan;
+ RESET enable_indexscan;
+ RESET enable_indexonlyscan;
+ DROP TABLE gist_tbl;
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
***************
*** 78,84 **** ignore: random
  # ----------
  # Another group of parallel tests
  # ----------
! test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete
  
  # ----------
  # Another group of parallel tests
--- 78,84 ----
  # ----------
  # Another group of parallel tests
  # ----------
! test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index gist_indexonly update namespace prepared_xacts delete
  
  # ----------
  # Another group of parallel tests
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
***************
*** 91,96 **** test: portals
--- 91,97 ----
  test: arrays
  test: btree_index
  test: hash_index
+ test: gist_indexonly
  test: update
  test: delete
  test: namespace
*** /dev/null
--- b/src/test/regress/sql/gist_indexonly.sql
***************
*** 0 ****
--- 1,51 ----
+ --
+ -- 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 TO false;
+ SET enable_bitmapscan TO false;
+ SET enable_indexscan TO false;
+ SET enable_indexonlyscan TO true;
+ 
+ -- 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)) limit 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);
+ EXPLAIN (COSTS OFF)
+ select b from gist_tbl where b <@ box(point(5,5),  point(6,6)) limit 10;
+ 
+ DROP INDEX gist_tbl_box_index;
+ 
+ -- Check multicolumn indexonlyscan for gist
+ 
+ CREATE INDEX gist_tbl_multi_index ON gist_tbl USING gist (b, p);
+ 
+ EXPLAIN (COSTS OFF)
+ select * from gist_tbl where ( (b <@ box(point(5,5),  point(6,6))) and (p <@ box(point(0,0),  point(100,100)))) limit 10;
+ 
+ EXPLAIN (COSTS OFF)
+ select * from gist_tbl where ( (b <@ box(point(5,5),  point(6,6))) or (p <@ box(point(0,0),  point(100,100)))) limit 10;
+ 
+ DROP INDEX gist_tbl_multi_index;
+ 
+ RESET enable_seqscan;
+ RESET enable_bitmapscan;
+ RESET enable_indexscan;
+ RESET enable_indexonlyscan;
+ 
+ DROP TABLE gist_tbl;
#2Fabrízio de Royes Mello
fabriziomello@gmail.com
In reply to: Anastasia Lubennikova (#1)
Re: Index-only scans for GIST

On Fri, Aug 1, 2014 at 4:58 AM, Anastasia Lubennikova <
lubennikovaav@gmail.com> wrote:

Hi, hackers!
I work on a GSoC project "Index-only scans for GIST"

https://wiki.postgresql.org/wiki/Support_for_Index-only_scans_for_GIST_GSoC_2014

Repository is
https://github.com/lubennikovaav/postgres/tree/indexonlygist2
Patch is in attachments.

It includes index-only scans for multicolumn GIST and new regression test.
Fetch() method is realized for box and point opclasses.

Documentation is not updated yet, but I'm going to do it till the end of

GSoC.

I've got one question about query with OR condition. It is the last query

in regression test "gist_indexonly". It doesn't fail but it doensn't use
index-only scans. Could someone explain to me how it works?

It seems to depend on build_paths_for_OR function. But I couldn't

understand how.

Very nice... please add your patch to the next commit fest [1]https://commitfest.postgresql.org/action/commitfest_view?id=23.

Regards,

[1]: https://commitfest.postgresql.org/action/commitfest_view?id=23

--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL

Show quoted text

Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello

#3Anastasia Lubennikova
lubennikovaav@gmail.com
In reply to: Fabrízio de Royes Mello (#2)
Re: Index-only scans for GIST

Thank you for comment
Patch is already added in Performance topic.

2014-08-01 20:25 GMT+04:00 Fabrízio de Royes Mello <fabriziomello@gmail.com>
:

On Fri, Aug 1, 2014 at 4:58 AM, Anastasia Lubennikova <
lubennikovaav@gmail.com> wrote:

Hi, hackers!
I work on a GSoC project "Index-only scans for GIST"

https://wiki.postgresql.org/wiki/Support_for_Index-only_scans_for_GIST_GSoC_2014

Repository is
https://github.com/lubennikovaav/postgres/tree/indexonlygist2
Patch is in attachments.

It includes index-only scans for multicolumn GIST and new regression

test.

Fetch() method is realized for box and point opclasses.

Documentation is not updated yet, but I'm going to do it till the end of

GSoC.

I've got one question about query with OR condition. It is the last

query in regression test "gist_indexonly". It doesn't fail but it doensn't
use index-only scans. Could someone explain to me how it works?

It seems to depend on build_paths_for_OR function. But I couldn't

understand how.

Very nice... please add your patch to the next commit fest [1].

Regards,

[1] https://commitfest.postgresql.org/action/commitfest_view?id=23

--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL

Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello

--
Best regards,
Lubennikova Anastasia

#4Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Anastasia Lubennikova (#1)
Re: Index-only scans for GIST

On 08/01/2014 10:58 AM, Anastasia Lubennikova wrote:

Hi, hackers!
I work on a GSoC project "Index-only scans for GIST"
https://wiki.postgresql.org/wiki/Support_for_Index-only_scans_for_GIST_GSoC_2014

Repository is
https://github.com/lubennikovaav/postgres/tree/indexonlygist2
Patch is in attachments.

Thanks!

Some comments:

* I got this compiler warning:

gistget.c:556:5: warning: ISO C90 forbids mixed declarations and code
[-Wdeclaration-after-statement]
ListCell *tmpPageData = so->curPageData;
^

* I'm getting two regression failures with this (opr_sanity and join).

* After merging with master, build fails because of duplicate OIDs.

* The regression test queries that use LIMIT are not guaranteed to
always return the same rows, hence they're not very good regression test
cases. I'd suggest using more restricting WHERE clauses, so that each
query only returns a handful of rows.

* What's the reason for turning GISTScanOpaqueData.pageData from an
array to a List?

* I think it's leaking memory, in GIST scan context. I tested this with
a variant of the regression tests:

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,
10000000) as i;
CREATE INDEX gist_tbl_point_index ON gist_tbl USING gist (p);

set enable_seqscan=off;
set enable_bitmapscan=off;

explain analyze select p from gist_tbl where p <@ box(point(0,0),
point(9999999,9999999)) and length(p::text) < 10;

while the final query runs, 'top' shows constantly increasing memory usage.

It includes index-only scans for multicolumn GIST and new regression test.
Fetch() method is realized for box and point opclasses.

Can we have Fetch functions for all the datatypes in btree_gist contrib
module, please? Do other contrib modules contain GiST opclasses that
could have Fetch functions?

Documentation is not updated yet, but I'm going to do it till the end of
GSoC.

I've got one question about query with OR condition. It is the last query
in regression test "gist_indexonly". It doesn't fail but it doensn't use
index-only scans. Could someone explain to me how it works?
It seems to depend on build_paths_for_OR
<http://doxygen.postgresql.org/indxpath_8c.html#ae660d2e886355e53ed3b9ec693e4afd2&gt;
function.
But I couldn't understand how.

The query is:

select * from gist_tbl
where b <@ box(point(5,5), point(6,6))
or p <@ box(point(0,0), point(100,100)) limit 10;

It cannot use an index(-only) scan for this, because a single index scan
can only return rows based on one key. In this case, you need to do two
scans, and then return the rows returned by either scan, removing
duplicates. A bitmap scan is possible, because it can remove the
duplicates, but the planner can't produce a plain index scan plan that
would do the same.

A common trick when that happens in a real-world application is to
re-write the query using UNION:

select * from gist_tbl
where b <@ box(point(5,5), point(6,6))
UNION
select * from gist_tbl
where p <@ box(point(0,0), point(100,100))
limit 10;

Although that doesn't seem to actually work:

ERROR: could not identify an equality operator for type box
LINE 1: select * from gist_tbl
^

but that's not your patch's fault, the same happens with unpatched master.

IOW, you don't need to worry about that case.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Anastasia Lubennikova
lubennikovaav@gmail.com
In reply to: Heikki Linnakangas (#4)
Re: Index-only scans for GIST

2014-08-07 0:30 GMT+04:00 Heikki Linnakangas <hlinnakangas@vmware.com>:

* I'm getting two regression failures with this (opr_sanity and join).

opr_sanity failure is corrected.
But there is remain question with join.
I check the latest version of my github repo and there's no fail in join
regression test
All 145 tests passed.
To tell the truth, I don't understand which changes could led to this
failure.
Could you show me regression diffs?
I want to understand what's wrong with the patch.

* The regression test queries that use LIMIT are not guaranteed to always

return the same rows, hence they're not very good regression test cases.
I'd suggest using more restricting WHERE clauses, so that each query only
returns a handful of rows.

Thank you for comment, I rewrote wrong queries. But could you explain why
LIMIT queries may return different results? Is it happens because of
different query optimization?

* I think it's leaking memory, in GIST scan context. I tested this with a

variant of the regression tests:

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,
10000000) as i;
CREATE INDEX gist_tbl_point_index ON gist_tbl USING gist (p);

set enable_seqscan=off;
set enable_bitmapscan=off;

explain analyze select p from gist_tbl where p <@ box(point(0,0),
point(9999999,9999999)) and length(p::text) < 10;

while the final query runs, 'top' shows constantly increasing memory usage.

I don't think it's memory leak. After some increasing, memory using remain
the same. It works similar without using indexonlyscan.

--
Best regards,
Lubennikova Anastasia

#6Anastasia Lubennikova
lubennikovaav@gmail.com
In reply to: Anastasia Lubennikova (#5)
2 attachment(s)
Re: Index-only scans for GIST

Updated patch
* Compiler, merge and regression fails checked
* Regression tests was impoved
* GiST and amcanreturn docs updated
--
Best regards,
Lubennikova Anastasia

Attachments:

indexonlyscan_gist2.patchapplication/octet-stream; name=indexonlyscan_gist2.patchDownload
*** a/src/backend/access/gist/gist.c
--- b/src/backend/access/gist/gist.c
***************
*** 1379,1384 **** initGISTstate(Relation index)
--- 1379,1392 ----
  		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
***************
*** 1401,1406 **** initGISTstate(Relation index)
--- 1409,1430 ----
  	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)
  {
*** a/src/backend/access/gist/gistget.c
--- b/src/backend/access/gist/gistget.c
***************
*** 226,233 **** 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,
   * heap tuple TIDs are pushed into individual search queue items.
   *
   * 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
--- 226,235 ----
   * 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,
   * 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,245 **** gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
--- 242,251 ----
  	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;
***************
*** 288,297 **** gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
  
  		(void) rb_insert(so->queue, (RBNode *) tmpItem, &isNew);
  
  		MemoryContextSwitchTo(oldcxt);
  	}
  
! 	so->nPageData = so->curPageData = 0;
  
  	/*
  	 * check all tuples on page
--- 294,305 ----
  
  		(void) rb_insert(so->queue, (RBNode *) tmpItem, &isNew);
  
+ 		/* Create new GISTSearchHeapItem to insert into pageData*/
+ 
  		MemoryContextSwitchTo(oldcxt);
  	}
  
! 	so->curPageData = NULL;
  
  	/*
  	 * check all tuples on page
***************
*** 330,340 **** gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
  		else if (scan->numberOfOrderBys == 0 && GistPageIsLeaf(page))
  		{
  			/*
! 			 * Non-ordered scan, so report heap tuples in so->pageData[]
  			 */
! 			so->pageData[so->nPageData].heapPtr = it->t_tid;
! 			so->pageData[so->nPageData].recheck = recheck;
! 			so->nPageData++;
  		}
  		else
  		{
--- 338,363 ----
  		else if (scan->numberOfOrderBys == 0 && GistPageIsLeaf(page))
  		{
  			/*
! 			 * Non-oredered scan, so tuples report in so->pageData
! 			 */
! 			oldcxt = MemoryContextSwitchTo(so->queueCxt);
! 			/* 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 fill the slot with data fetched from index field
  			 */
! 			if (scan->xs_want_itup)
! 				tmpListItem->ftup = gistFetchTuple(giststate, r, it, isnull);
! 
! 			so->pageData = lappend(so->pageData, tmpListItem);
! 			/*
! 			 * If it's first call of lappend() we should set so->curPageData not NULL
! 			 */
! 			if(so->curPageData == NULL)
! 				so->curPageData = list_head(so->pageData);
! 			MemoryContextSwitchTo(oldcxt);
  		}
  		else
  		{
***************
*** 357,362 **** gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
--- 380,390 ----
  				item->blkno = InvalidBlockNumber;
  				item->data.heap.heapPtr = it->t_tid;
  				item->data.heap.recheck = recheck;
+ 				/*
+ 				 * If index-only scan is possible fill the slot with data fetched from index field
+ 				 */
+ 				if (scan->xs_want_itup)
+ 					item->data.heap.ftup = gistFetchTuple(giststate, r, it, isnull);
  			}
  			else
  			{
***************
*** 451,456 **** getNextNearest(IndexScanDesc scan)
--- 479,489 ----
  			/* 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,481 **** gistgettuple(PG_FUNCTION_ARGS)
--- 509,515 ----
  	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
  	ScanDirection dir = (ScanDirection) PG_GETARG_INT32(1);
  	GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
+ 	ListCell *tmpPageData;
  
  	if (dir != ForwardScanDirection)
  		elog(ERROR, "GiST only supports forward scan direction");
***************
*** 492,498 **** 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));
--- 526,533 ----
  
  		so->firstCall = false;
  		so->curTreeItem = NULL;
! 
! 		so->curPageData = NULL;
  
  		fakeItem.blkno = GIST_ROOT_BLKNO;
  		memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
***************
*** 509,521 **** gistgettuple(PG_FUNCTION_ARGS)
  		/* Fetch tuples index-page-at-a-time */
  		for (;;)
  		{
! 			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;
! 				so->curPageData++;
! 				PG_RETURN_BOOL(true);
  			}
  
  			/* find and process the next index page */
--- 544,568 ----
  		/* Fetch tuples index-page-at-a-time */
  		for (;;)
  		{
! 			if(so->curPageData!=NULL)
  			{
  				/* continuing to return tuples from a leaf page */
! 				GISTSearchHeapItem *tmp = (GISTSearchHeapItem *)lfirst(so->curPageData);
! 				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;
! 
! 				tmpPageData = so->curPageData;
! 				/* Go to the next ListCell */
! 				so->curPageData = lnext(so->curPageData);
! 				/*
! 				 * Delete ListCell that we have already read.
! 				 * It's always head of so->pageData
! 				 */
! 				so->pageData =  list_delete_cell(so->pageData, tmpPageData, NULL);
! 				PG_RETURN_BOOL(TRUE);
  			}
  
  			/* find and process the next index page */
***************
*** 537,543 **** gistgettuple(PG_FUNCTION_ARGS)
  				gistScanPage(scan, item, so->curTreeItem->distances, NULL, NULL);
  
  				pfree(item);
! 			} while (so->nPageData == 0);
  		}
  	}
  }
--- 584,590 ----
  				gistScanPage(scan, item, so->curTreeItem->distances, NULL, NULL);
  
  				pfree(item);
! 			} while (list_length(so->pageData)==0);
  		}
  	}
  }
***************
*** 561,567 **** 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));
--- 608,614 ----
  
  	/* Begin the scan by processing the root page */
  	so->curTreeItem = NULL;
! 	so->curPageData = NULL;
  
  	fakeItem.blkno = GIST_ROOT_BLKNO;
  	memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
*** a/src/backend/access/gist/gistproc.c
--- b/src/backend/access/gist/gistproc.c
***************
*** 152,157 **** gist_box_decompress(PG_FUNCTION_ARGS)
--- 152,167 ----
  }
  
  /*
+  * GiST Fetch method for boxes
+  * do not do anything --- we just use 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,1209 **** gist_point_compress(PG_FUNCTION_ARGS)
--- 1214,1253 ----
  	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)))
*** a/src/backend/access/gist/gistscan.c
--- b/src/backend/access/gist/gistscan.c
***************
*** 133,138 **** gistbeginscan(PG_FUNCTION_ARGS)
--- 133,146 ----
  
  	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;
+ 	so->curPageData = NULL;
+ 	scan->xs_itupdesc = RelationGetDescr(r);
+ 
  	MemoryContextSwitchTo(oldCxt);
  
  	PG_RETURN_POINTER(scan);
*** a/src/backend/access/gist/gistutil.c
--- b/src/backend/access/gist/gistutil.c
***************
*** 618,623 **** gistFormTuple(GISTSTATE *giststate, Relation r,
--- 618,680 ----
  	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 in key field
+  */
+ IndexTuple
+ gistFetchTuple(GISTSTATE *giststate, Relation r, IndexTuple tuple, bool isnull[])
+ {
+ 	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;
+ 	}
+ 	res = index_form_tuple(giststate->tupdesc, fetchatt, isnull);
+ 
+ 	/*
+ 	 * The offset number on tuples on internal pages is unused. For historical
+ 	 * reasons, it is set 0xffff.
+ 	 */
+ 	ItemPointerSetOffsetNumber(&(res->t_tid), 0xffff);
+ 	return res;
+ }
+ 
  float
  gistpenalty(GISTSTATE *giststate, int attno,
  			GISTENTRY *orig, bool isNullOrig,
*** a/src/backend/access/index/indexam.c
--- b/src/backend/access/index/indexam.c
***************
*** 722,732 **** index_vacuum_cleanup(IndexVacuumInfo *info,
  }
  
  /* ----------------
!  *		index_can_return - does index support index-only scans?
   * ----------------
   */
  bool
! index_can_return(Relation indexRelation)
  {
  	FmgrInfo   *procedure;
  
--- 722,732 ----
  }
  
  /* ----------------
!  *		index_can_return - does index column with number 'attno' supports index-only scans?
   * ----------------
   */
  bool
! index_can_return(Relation indexRelation, int attno)
  {
  	FmgrInfo   *procedure;
  
***************
*** 738,745 **** index_can_return(Relation indexRelation)
  
  	GET_REL_PROCEDURE(amcanreturn);
  
! 	return DatumGetBool(FunctionCall1(procedure,
! 									  PointerGetDatum(indexRelation)));
  }
  
  /* ----------------
--- 738,746 ----
  
  	GET_REL_PROCEDURE(amcanreturn);
  
! 	return DatumGetBool(FunctionCall2(procedure,
! 					  PointerGetDatum(indexRelation),
! 					  Int32GetDatum(attno)));
  }
  
  /* ----------------
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
***************
*** 1746,1759 **** check_index_only(RelOptInfo *rel, IndexOptInfo *index)
  	bool		result;
  	Bitmapset  *attrs_used = NULL;
  	Bitmapset  *index_attrs = NULL;
  	ListCell   *lc;
  	int			i;
  
! 	/* Index-only scans must be enabled, and index must be capable of them */
  	if (!enable_indexonlyscan)
  		return false;
- 	if (!index->canreturn)
- 		return false;
  
  	/*
  	 * Check that all needed attributes of the relation are available from the
--- 1746,1758 ----
  	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 */
  	if (!enable_indexonlyscan)
  		return false;
  
  	/*
  	 * Check that all needed attributes of the relation are available from the
***************
*** 1798,1811 **** check_index_only(RelOptInfo *rel, IndexOptInfo *index)
  		index_attrs =
  			bms_add_member(index_attrs,
  						   attno - FirstLowInvalidHeapAttributeNumber);
  	}
  
! 	/* Do we have all the necessary attributes? */
! 	result = bms_is_subset(attrs_used, index_attrs);
! 
  	bms_free(attrs_used);
  	bms_free(index_attrs);
! 
  	return result;
  }
  
--- 1797,1813 ----
  		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? 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;
  }
  
*** a/src/backend/optimizer/util/plancat.c
--- b/src/backend/optimizer/util/plancat.c
***************
*** 207,212 **** get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
--- 207,213 ----
  			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,224 **** 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->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;
--- 215,225 ----
  				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->amcanorderbyop = indexRelation->rd_am->amcanorderbyop;
  			info->amoptionalkey = indexRelation->rd_am->amoptionalkey;
  			info->amsearcharray = indexRelation->rd_am->amsearcharray;
*** a/src/include/access/genam.h
--- b/src/include/access/genam.h
***************
*** 156,162 **** 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 RegProcedure index_getprocid(Relation irel, AttrNumber attnum,
  				uint16 procnum);
  extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum,
--- 156,162 ----
  				  void *callback_state);
  extern IndexBulkDeleteResult *index_vacuum_cleanup(IndexVacuumInfo *info,
  					 IndexBulkDeleteResult *stats);
! 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,
*** a/src/include/access/gist.h
--- b/src/include/access/gist.h
***************
*** 33,39 ****
  #define GIST_PICKSPLIT_PROC				6
  #define GIST_EQUAL_PROC					7
  #define GIST_DISTANCE_PROC				8
! #define GISTNProcs						8
  
  /*
   * strategy numbers for GiST opclasses that want to implement the old
--- 33,40 ----
  #define GIST_PICKSPLIT_PROC				6
  #define GIST_EQUAL_PROC					7
  #define GIST_DISTANCE_PROC				8
! #define GIST_FETCH_PROC					9
! #define GISTNProcs					9
  
  /*
   * strategy numbers for GiST opclasses that want to implement the old
*** a/src/include/access/gist_private.h
--- b/src/include/access/gist_private.h
***************
*** 86,91 **** typedef struct GISTSTATE
--- 86,92 ----
  	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];
***************
*** 108,114 **** 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
   * there, instead of building a separate GISTSearchItem for each one.
   */
  
--- 109,115 ----
   * 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
   * there, instead of building a separate GISTSearchItem for each one.
   */
  
***************
*** 117,122 **** typedef struct GISTSearchHeapItem
--- 118,124 ----
  {
  	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 */
***************
*** 167,175 **** 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 */
  } GISTScanOpaqueData;
  
  typedef GISTScanOpaqueData *GISTScanOpaque;
--- 169,177 ----
  	double	   *distances;		/* output area for gistindex_keytest */
  
  	/* In a non-ordered search, returnable heap items are stored here: */
! 	List *pageData;
! 	ListCell *curPageData; /* next item to return from pageData */
! 
  } GISTScanOpaqueData;
  
  typedef GISTScanOpaqueData *GISTScanOpaque;
***************
*** 423,428 **** typedef struct GiSTOptions
--- 425,431 ----
  /* 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);
***************
*** 522,527 **** extern bool gistKeyIsEQ(GISTSTATE *giststate, int attno, Datum a, Datum b);
--- 525,535 ----
  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,
*** a/src/include/catalog/pg_am.h
--- b/src/include/catalog/pg_am.h
***************
*** 123,129 **** 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 ));
  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 ));
--- 123,129 ----
  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 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 ));
*** a/src/include/catalog/pg_amproc.h
--- b/src/include/catalog/pg_amproc.h
***************
*** 188,193 **** DATA(insert (	1029   600 600 5 2581 ));
--- 188,194 ----
  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 3257 ));
  DATA(insert (	2593   603 603 1 2578 ));
  DATA(insert (	2593   603 603 2 2583 ));
  DATA(insert (	2593   603 603 3 2579 ));
***************
*** 195,200 **** DATA(insert (	2593   603 603 4 2580 ));
--- 196,202 ----
  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 3256 ));
  DATA(insert (	2594   604 604 1 2585 ));
  DATA(insert (	2594   604 604 2 2583 ));
  DATA(insert (	2594   604 604 3 2586 ));
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 558,564 **** 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_ ));
  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)");
--- 558,564 ----
  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 2 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)");
***************
*** 941,946 **** DATA(insert OID = 776 (  gistbulkdelete    PGNSP PGUID 12 1 0 0 0 f f f f t f v
--- 941,948 ----
  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 = 3258 (  gistcanreturn	   PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 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_ ));
***************
*** 3996,4001 **** DATA(insert OID = 2579 (  gist_box_compress		PGNSP PGUID 12 1 0 0 0 f f f f t f
--- 3998,4005 ----
  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 = 3256 (  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_ ));
***************
*** 4014,4019 **** DATA(insert OID = 2592 (  gist_circle_compress	PGNSP PGUID 12 1 0 0 0 f f f f t
--- 4018,4025 ----
  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 = 3257 (  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_ ));
***************
*** 4905,4911 **** 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_ ));
  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)");
--- 4911,4917 ----
  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 2 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)");
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
***************
*** 526,532 **** typedef struct IndexOptInfo
  	bool		unique;			/* true if a unique index */
  	bool		immediate;		/* is uniqueness enforced immediately? */
  	bool		hypothetical;	/* true if index doesn't really exist */
! 	bool		canreturn;		/* can index return IndexTuples? */
  	bool		amcanorderbyop; /* does AM support order by operator result? */
  	bool		amoptionalkey;	/* can query omit key for the first column? */
  	bool		amsearcharray;	/* can AM handle ScalarArrayOpExpr quals? */
--- 526,532 ----
  	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 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? */
*** a/src/include/utils/geo_decls.h
--- b/src/include/utils/geo_decls.h
***************
*** 411,416 **** extern Datum gist_box_picksplit(PG_FUNCTION_ARGS);
--- 411,417 ----
  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,423 **** extern Datum gist_circle_consistent(PG_FUNCTION_ARGS);
--- 419,426 ----
  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);
*** a/src/test/regress/expected/create_index.out
--- b/src/test/regress/expected/create_index.out
***************
*** 378,384 **** SELECT * FROM fast_emp4000
  ----------------------------------------------------------------
   Sort
     Sort Key: ((home_base[0])[0])
!    ->  Index Scan using grect2ind on fast_emp4000
           Index Cond: (home_base @ '(2000,1000),(200,200)'::box)
  (4 rows)
  
--- 378,384 ----
  ----------------------------------------------------------------
   Sort
     Sort Key: ((home_base[0])[0])
!    ->  Index Only Scan using grect2ind on fast_emp4000
           Index Cond: (home_base @ '(2000,1000),(200,200)'::box)
  (4 rows)
  
***************
*** 396,402 **** SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box;
                           QUERY PLAN                          
  -------------------------------------------------------------
   Aggregate
!    ->  Index Scan using grect2ind on fast_emp4000
           Index Cond: (home_base && '(1000,1000),(0,0)'::box)
  (3 rows)
  
--- 396,402 ----
                           QUERY PLAN                          
  -------------------------------------------------------------
   Aggregate
!    ->  Index Only Scan using grect2ind on fast_emp4000
           Index Cond: (home_base && '(1000,1000),(0,0)'::box)
  (3 rows)
  
***************
*** 408,417 **** 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                    
! --------------------------------------------------
   Aggregate
!    ->  Index Scan using grect2ind on fast_emp4000
           Index Cond: (home_base IS NULL)
  (3 rows)
  
--- 408,417 ----
  
  EXPLAIN (COSTS OFF)
  SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL;
!                       QUERY PLAN                       
! -------------------------------------------------------
   Aggregate
!    ->  Index Only Scan using grect2ind on fast_emp4000
           Index Cond: (home_base IS NULL)
  (3 rows)
  
***************
*** 495,501 **** SELECT count(*) FROM point_tbl WHERE f1 <@ box '(0,0,100,100)';
                       QUERY PLAN                     
  ----------------------------------------------------
   Aggregate
!    ->  Index Scan using gpointind on point_tbl
           Index Cond: (f1 <@ '(100,100),(0,0)'::box)
  (3 rows)
  
--- 495,501 ----
                       QUERY PLAN                     
  ----------------------------------------------------
   Aggregate
!    ->  Index Only Scan using gpointind on point_tbl
           Index Cond: (f1 <@ '(100,100),(0,0)'::box)
  (3 rows)
  
***************
*** 510,517 **** 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)
  (3 rows)
  
  SELECT count(*) FROM point_tbl WHERE box '(0,0,100,100)' @> f1;
--- 510,517 ----
                       QUERY PLAN                     
  ----------------------------------------------------
   Aggregate
!    ->  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;
***************
*** 525,531 **** 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 Cond: (f1 <@ '((0,0),(0,100),(100,100),(50,50),(100,0),(0,0))'::polygon)
  (3 rows)
  
--- 525,531 ----
                                         QUERY PLAN                                       
  ----------------------------------------------------------------------------------------
   Aggregate
!    ->  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)
  
***************
*** 540,546 **** SELECT count(*) FROM point_tbl WHERE f1 <@ circle '<(50,50),50>';
                       QUERY PLAN                     
  ----------------------------------------------------
   Aggregate
!    ->  Index Scan using gpointind on point_tbl
           Index Cond: (f1 <@ '<(50,50),50>'::circle)
  (3 rows)
  
--- 540,546 ----
                       QUERY PLAN                     
  ----------------------------------------------------
   Aggregate
!    ->  Index Only Scan using gpointind on point_tbl
           Index Cond: (f1 <@ '<(50,50),50>'::circle)
  (3 rows)
  
***************
*** 552,561 **** 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                    
! -------------------------------------------------
   Aggregate
!    ->  Index Scan using gpointind on point_tbl p
           Index Cond: (f1 << '(0,0)'::point)
  (3 rows)
  
--- 552,561 ----
  
  EXPLAIN (COSTS OFF)
  SELECT count(*) FROM point_tbl p WHERE p.f1 << '(0.0, 0.0)';
!                       QUERY PLAN                      
! ------------------------------------------------------
   Aggregate
!    ->  Index Only Scan using gpointind on point_tbl p
           Index Cond: (f1 << '(0,0)'::point)
  (3 rows)
  
***************
*** 567,576 **** 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                    
! -------------------------------------------------
   Aggregate
!    ->  Index Scan using gpointind on point_tbl p
           Index Cond: (f1 >> '(0,0)'::point)
  (3 rows)
  
--- 567,576 ----
  
  EXPLAIN (COSTS OFF)
  SELECT count(*) FROM point_tbl p WHERE p.f1 >> '(0.0, 0.0)';
!                       QUERY PLAN                      
! ------------------------------------------------------
   Aggregate
!    ->  Index Only Scan using gpointind on point_tbl p
           Index Cond: (f1 >> '(0,0)'::point)
  (3 rows)
  
***************
*** 582,591 **** 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                    
! -------------------------------------------------
   Aggregate
!    ->  Index Scan using gpointind on point_tbl p
           Index Cond: (f1 <^ '(0,0)'::point)
  (3 rows)
  
--- 582,591 ----
  
  EXPLAIN (COSTS OFF)
  SELECT count(*) FROM point_tbl p WHERE p.f1 <^ '(0.0, 0.0)';
!                       QUERY PLAN                      
! ------------------------------------------------------
   Aggregate
!    ->  Index Only Scan using gpointind on point_tbl p
           Index Cond: (f1 <^ '(0,0)'::point)
  (3 rows)
  
***************
*** 597,606 **** 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                    
! -------------------------------------------------
   Aggregate
!    ->  Index Scan using gpointind on point_tbl p
           Index Cond: (f1 >^ '(0,0)'::point)
  (3 rows)
  
--- 597,606 ----
  
  EXPLAIN (COSTS OFF)
  SELECT count(*) FROM point_tbl p WHERE p.f1 >^ '(0.0, 0.0)';
!                       QUERY PLAN                      
! ------------------------------------------------------
   Aggregate
!    ->  Index Only Scan using gpointind on point_tbl p
           Index Cond: (f1 >^ '(0,0)'::point)
  (3 rows)
  
***************
*** 612,621 **** 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                    
! -------------------------------------------------
   Aggregate
!    ->  Index Scan using gpointind on point_tbl p
           Index Cond: (f1 ~= '(-5,-12)'::point)
  (3 rows)
  
--- 612,621 ----
  
  EXPLAIN (COSTS OFF)
  SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)';
!                       QUERY PLAN                      
! ------------------------------------------------------
   Aggregate
!    ->  Index Only Scan using gpointind on point_tbl p
           Index Cond: (f1 ~= '(-5,-12)'::point)
  (3 rows)
  
***************
*** 627,635 **** 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
     Order By: (f1 <-> '(0,1)'::point)
  (2 rows)
  
--- 627,635 ----
  
  EXPLAIN (COSTS OFF)
  SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
!                   QUERY PLAN                  
! ----------------------------------------------
!  Index Only Scan using gpointind on point_tbl
     Order By: (f1 <-> '(0,1)'::point)
  (2 rows)
  
***************
*** 647,655 **** 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
     Index Cond: (f1 IS NULL)
  (2 rows)
  
--- 647,655 ----
  
  EXPLAIN (COSTS OFF)
  SELECT * FROM point_tbl WHERE f1 IS NULL;
!                   QUERY PLAN                  
! ----------------------------------------------
!  Index Only Scan using gpointind on point_tbl
     Index Cond: (f1 IS NULL)
  (2 rows)
  
***************
*** 661,669 **** 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
     Index Cond: (f1 IS NOT NULL)
     Order By: (f1 <-> '(0,1)'::point)
  (3 rows)
--- 661,669 ----
  
  EXPLAIN (COSTS OFF)
  SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
!                   QUERY PLAN                  
! ----------------------------------------------
!  Index Only Scan using gpointind on point_tbl
     Index Cond: (f1 IS NOT NULL)
     Order By: (f1 <-> '(0,1)'::point)
  (3 rows)
***************
*** 683,689 **** 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 Cond: (f1 <@ '(10,10),(-10,-10)'::box)
     Order By: (f1 <-> '(0,1)'::point)
  (3 rows)
--- 683,689 ----
  SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
                     QUERY PLAN                   
  ------------------------------------------------
!  Index Only Scan using gpointind on point_tbl
     Index Cond: (f1 <@ '(10,10),(-10,-10)'::box)
     Order By: (f1 <-> '(0,1)'::point)
  (3 rows)
*** /dev/null
--- b/src/test/regress/expected/gist_indexonly.out
***************
*** 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;
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
***************
*** 78,84 **** ignore: random
  # ----------
  # Another group of parallel tests
  # ----------
! test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete
  
  # ----------
  # Another group of parallel tests
--- 78,84 ----
  # ----------
  # Another group of parallel tests
  # ----------
! test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index gist_indexonly update namespace prepared_xacts delete
  
  # ----------
  # Another group of parallel tests
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
***************
*** 91,96 **** test: portals
--- 91,97 ----
  test: arrays
  test: btree_index
  test: hash_index
+ test: gist_indexonly
  test: update
  test: delete
  test: namespace
*** /dev/null
--- b/src/test/regress/sql/gist_indexonly.sql
***************
*** 0 ****
--- 1,44 ----
+ --
+ -- 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-&gt;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 only
#7Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Anastasia Lubennikova (#5)
2 attachment(s)
Re: Index-only scans for GIST

On 08/17/2014 07:15 PM, Anastasia Lubennikova wrote:

2014-08-07 0:30 GMT+04:00 Heikki Linnakangas <hlinnakangas@vmware.com>:

* I'm getting two regression failures with this (opr_sanity and join).

opr_sanity failure is corrected.
But there is remain question with join.
I check the latest version of my github repo and there's no fail in join
regression test
All 145 tests passed.
To tell the truth, I don't understand which changes could led to this
failure.
Could you show me regression diffs?

Sure, here you go. It seems like a change in a plan. At a quick glance
it seems harmless: the new plan is identical except that the left and
right side of a join have been reversed. But I don't understand either
why this patch would change that, so it needs to be investigated.

* The regression test queries that use LIMIT are not guaranteed to always

return the same rows, hence they're not very good regression test cases.
I'd suggest using more restricting WHERE clauses, so that each query only
returns a handful of rows.

Thank you for comment, I rewrote wrong queries. But could you explain why
LIMIT queries may return different results? Is it happens because of
different query optimization?

Imagine that you have a table with two rows, A and B. If you run a query
like "SELECT * FROM table LIMIT 1", the system can legally return either
row A or B, because there's no ORDER BY.

Now, admittedly you have a similar problem even without the LIMIT - the
system can legally return the rows in either order - but it's less of an
issue because at least you can quickly see from the diff that the result
set is in fact the same, the rows are just in different order. You could
fix that by adding an ORDER BY to all test queries, but we haven't done
that in the regression suite because then we would not have any test
coverage for cases where you don't have an ORDER BY. As a compromise,
test cases are usually written without an ORDER BY, but if e.g. the
buildfarm starts failing because of differences in the result set order
across platforms, then we add an ORDER BY to make it stable.

* I think it's leaking memory, in GIST scan context. I tested this with a

variant of the regression tests:

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,
10000000) as i;
CREATE INDEX gist_tbl_point_index ON gist_tbl USING gist (p);

set enable_seqscan=off;
set enable_bitmapscan=off;

explain analyze select p from gist_tbl where p <@ box(point(0,0),
point(9999999,9999999)) and length(p::text) < 10;

while the final query runs, 'top' shows constantly increasing memory usage.

I don't think it's memory leak. After some increasing, memory using remain
the same. It works similar without using indexonlyscan.

No, it's definitely a leak caused by the patch. Test with the attached
patch, which prints out to the server log the amount of memory used by
the GiST scan memory context every 10000 rows. It clearly shows
increasing memory usage all the way to the end of the query.

It's cleaned up at the end of the query, but that's not good enough
because for a large query you might accumulate gigabytes of leaked
memory until the query has finished. If you (manually) apply the same
patch to git master, you'll see that the memory usage stays consistent
and small.

- Heikki

Attachments:

regression.diffstext/plain; charset=UTF-8; name=regression.diffsDownload
*** /home/heikki/git-sandbox/postgresql/src/test/regress/expected/join.out	2014-08-18 09:38:55.146171394 +0300
--- /home/heikki/git-sandbox/postgresql/src/test/regress/results/join.out	2014-08-18 10:28:30.326491898 +0300
***************
*** 2579,2590 ****
  -----------------------------------------------------------
   Sort
     Sort Key: t1.q1, t1.q2
!    ->  Hash Left Join
!          Hash Cond: (t1.q2 = t2.q1)
           Filter: (1 = (SubPlan 1))
!          ->  Seq Scan on int8_tbl t1
           ->  Hash
!                ->  Seq Scan on int8_tbl t2
           SubPlan 1
             ->  Limit
                   ->  Result
--- 2579,2590 ----
  -----------------------------------------------------------
   Sort
     Sort Key: t1.q1, t1.q2
!    ->  Hash Right Join
!          Hash Cond: (t2.q1 = t1.q2)
           Filter: (1 = (SubPlan 1))
!          ->  Seq Scan on int8_tbl t2
           ->  Hash
!                ->  Seq Scan on int8_tbl t1
           SubPlan 1
             ->  Limit
                   ->  Result
***************
*** 3589,3603 ****
    lateral (values(x.q1,y.q1,y.q2)) v(xq1,yq1,yq2);
          q1        |        q2         |        q1        |        q2         |       xq1        |       yq1        |        yq2        
  ------------------+-------------------+------------------+-------------------+------------------+------------------+-------------------
-               123 |               456 |                  |                   |              123 |                  |                  
-               123 |  4567890123456789 | 4567890123456789 | -4567890123456789 |              123 | 4567890123456789 | -4567890123456789
-               123 |  4567890123456789 | 4567890123456789 |  4567890123456789 |              123 | 4567890123456789 |  4567890123456789
-               123 |  4567890123456789 | 4567890123456789 |               123 |              123 | 4567890123456789 |               123
-  4567890123456789 |               123 |              123 |  4567890123456789 | 4567890123456789 |              123 |  4567890123456789
   4567890123456789 |               123 |              123 |               456 | 4567890123456789 |              123 |               456
!  4567890123456789 |  4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789
!  4567890123456789 |  4567890123456789 | 4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 |  4567890123456789
   4567890123456789 |  4567890123456789 | 4567890123456789 |               123 | 4567890123456789 | 4567890123456789 |               123
   4567890123456789 | -4567890123456789 |                  |                   | 4567890123456789 |                  |                  
  (10 rows)
  
--- 3589,3603 ----
    lateral (values(x.q1,y.q1,y.q2)) v(xq1,yq1,yq2);
          q1        |        q2         |        q1        |        q2         |       xq1        |       yq1        |        yq2        
  ------------------+-------------------+------------------+-------------------+------------------+------------------+-------------------
   4567890123456789 |               123 |              123 |               456 | 4567890123456789 |              123 |               456
!  4567890123456789 |               123 |              123 |  4567890123456789 | 4567890123456789 |              123 |  4567890123456789
   4567890123456789 |  4567890123456789 | 4567890123456789 |               123 | 4567890123456789 | 4567890123456789 |               123
+               123 |  4567890123456789 | 4567890123456789 |               123 |              123 | 4567890123456789 |               123
+  4567890123456789 |  4567890123456789 | 4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 |  4567890123456789
+               123 |  4567890123456789 | 4567890123456789 |  4567890123456789 |              123 | 4567890123456789 |  4567890123456789
+  4567890123456789 |  4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789
+               123 |  4567890123456789 | 4567890123456789 | -4567890123456789 |              123 | 4567890123456789 | -4567890123456789
+               123 |               456 |                  |                   |              123 |                  |                  
   4567890123456789 | -4567890123456789 |                  |                   | 4567890123456789 |                  |                  
  (10 rows)
  
***************
*** 3606,3620 ****
    lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2);
          q1        |        q2         |        q1        |        q2         |       xq1        |       yq1        |        yq2        
  ------------------+-------------------+------------------+-------------------+------------------+------------------+-------------------
-               123 |               456 |                  |                   |              123 |                  |                  
-               123 |  4567890123456789 | 4567890123456789 | -4567890123456789 |              123 | 4567890123456789 | -4567890123456789
-               123 |  4567890123456789 | 4567890123456789 |  4567890123456789 |              123 | 4567890123456789 |  4567890123456789
-               123 |  4567890123456789 | 4567890123456789 |               123 |              123 | 4567890123456789 |               123
-  4567890123456789 |               123 |              123 |  4567890123456789 | 4567890123456789 |              123 |  4567890123456789
   4567890123456789 |               123 |              123 |               456 | 4567890123456789 |              123 |               456
!  4567890123456789 |  4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789
!  4567890123456789 |  4567890123456789 | 4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 |  4567890123456789
   4567890123456789 |  4567890123456789 | 4567890123456789 |               123 | 4567890123456789 | 4567890123456789 |               123
   4567890123456789 | -4567890123456789 |                  |                   | 4567890123456789 |                  |                  
  (10 rows)
  
--- 3606,3620 ----
    lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2);
          q1        |        q2         |        q1        |        q2         |       xq1        |       yq1        |        yq2        
  ------------------+-------------------+------------------+-------------------+------------------+------------------+-------------------
   4567890123456789 |               123 |              123 |               456 | 4567890123456789 |              123 |               456
!  4567890123456789 |               123 |              123 |  4567890123456789 | 4567890123456789 |              123 |  4567890123456789
   4567890123456789 |  4567890123456789 | 4567890123456789 |               123 | 4567890123456789 | 4567890123456789 |               123
+               123 |  4567890123456789 | 4567890123456789 |               123 |              123 | 4567890123456789 |               123
+  4567890123456789 |  4567890123456789 | 4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 |  4567890123456789
+               123 |  4567890123456789 | 4567890123456789 |  4567890123456789 |              123 | 4567890123456789 |  4567890123456789
+  4567890123456789 |  4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789
+               123 |  4567890123456789 | 4567890123456789 | -4567890123456789 |              123 | 4567890123456789 | -4567890123456789
+               123 |               456 |                  |                   |              123 |                  |                  
   4567890123456789 | -4567890123456789 |                  |                   | 4567890123456789 |                  |                  
  (10 rows)
  
***************
*** 3623,3637 ****
    lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2);
          q1        |        q2         
  ------------------+-------------------
-               123 |               456
-               123 |  4567890123456789
-               123 |  4567890123456789
-               123 |  4567890123456789
   4567890123456789 |               123
   4567890123456789 |               123
   4567890123456789 |  4567890123456789
   4567890123456789 |  4567890123456789
   4567890123456789 |  4567890123456789
   4567890123456789 | -4567890123456789
  (10 rows)
  
--- 3623,3637 ----
    lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2);
          q1        |        q2         
  ------------------+-------------------
   4567890123456789 |               123
   4567890123456789 |               123
   4567890123456789 |  4567890123456789
+               123 |  4567890123456789
   4567890123456789 |  4567890123456789
+               123 |  4567890123456789
   4567890123456789 |  4567890123456789
+               123 |  4567890123456789
+               123 |               456
   4567890123456789 | -4567890123456789
  (10 rows)
  
***************
*** 3990,4004 ****
           Hash Cond: (d.q1 = c.q2)
           ->  Nested Loop
                 Output: a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, 42::bigint)), (COALESCE((COALESCE(b.q2, 42::bigint)), d.q2))
!                ->  Hash Left Join
                       Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, 42::bigint))
!                      Hash Cond: (a.q2 = b.q1)
!                      ->  Seq Scan on public.int8_tbl a
!                            Output: a.q1, a.q2
                       ->  Hash
!                            Output: b.q1, (COALESCE(b.q2, 42::bigint))
!                            ->  Seq Scan on public.int8_tbl b
!                                  Output: b.q1, COALESCE(b.q2, 42::bigint)
                 ->  Seq Scan on public.int8_tbl d
                       Output: d.q1, COALESCE((COALESCE(b.q2, 42::bigint)), d.q2)
           ->  Hash
--- 3990,4004 ----
           Hash Cond: (d.q1 = c.q2)
           ->  Nested Loop
                 Output: a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, 42::bigint)), (COALESCE((COALESCE(b.q2, 42::bigint)), d.q2))
!                ->  Hash Right Join
                       Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, 42::bigint))
!                      Hash Cond: (b.q1 = a.q2)
!                      ->  Seq Scan on public.int8_tbl b
!                            Output: b.q1, COALESCE(b.q2, 42::bigint)
                       ->  Hash
!                            Output: a.q1, a.q2
!                            ->  Seq Scan on public.int8_tbl a
!                                  Output: a.q1, a.q2
                 ->  Seq Scan on public.int8_tbl d
                       Output: d.q1, COALESCE((COALESCE(b.q2, 42::bigint)), d.q2)
           ->  Hash
***************
*** 4021,4062 ****
      lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2
    ) on c.q2 = ss2.q1,
    lateral (select * from int4_tbl i where ss2.y > f1) ss3;
!                                                QUERY PLAN                                                
! ---------------------------------------------------------------------------------------------------------
!  Nested Loop
     Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, i.f1
!    Join Filter: ((COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)) > i.f1)
!    ->  Hash Right Join
!          Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, (COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2))
!          Hash Cond: (d.q1 = c.q2)
!          ->  Nested Loop
!                Output: a.q1, a.q2, b.q1, d.q1, (COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2))
!                ->  Hash Right Join
!                      Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, (b2.f1)::bigint))
!                      Hash Cond: (b.q1 = a.q2)
!                      ->  Nested Loop
!                            Output: b.q1, COALESCE(b.q2, (b2.f1)::bigint)
!                            Join Filter: (b.q1 < b2.f1)
!                            ->  Seq Scan on public.int8_tbl b
!                                  Output: b.q1, b.q2
!                            ->  Materialize
                                   Output: b2.f1
!                                  ->  Seq Scan on public.int4_tbl b2
!                                        Output: b2.f1
!                      ->  Hash
                             Output: a.q1, a.q2
!                            ->  Seq Scan on public.int8_tbl a
!                                  Output: a.q1, a.q2
!                ->  Seq Scan on public.int8_tbl d
!                      Output: d.q1, COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)
!          ->  Hash
!                Output: c.q1, c.q2
                 ->  Seq Scan on public.int8_tbl c
                       Output: c.q1, c.q2
!    ->  Materialize
!          Output: i.f1
!          ->  Seq Scan on public.int4_tbl i
!                Output: i.f1
  (34 rows)
  
  -- check processing of postponed quals (bug #9041)
--- 4021,4062 ----
      lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2
    ) on c.q2 = ss2.q1,
    lateral (select * from int4_tbl i where ss2.y > f1) ss3;
!                                          QUERY PLAN                                          
! ---------------------------------------------------------------------------------------------
!  Hash Right Join
     Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, i.f1
!    Hash Cond: (d.q1 = c.q2)
!    Filter: ((COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)) > i.f1)
!    ->  Nested Loop
!          Output: a.q1, a.q2, b.q1, d.q1, (COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2))
!          ->  Hash Right Join
!                Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, (b2.f1)::bigint))
!                Hash Cond: (b.q1 = a.q2)
!                ->  Nested Loop
!                      Output: b.q1, COALESCE(b.q2, (b2.f1)::bigint)
!                      Join Filter: (b.q1 < b2.f1)
!                      ->  Seq Scan on public.int8_tbl b
!                            Output: b.q1, b.q2
!                      ->  Materialize
!                            Output: b2.f1
!                            ->  Seq Scan on public.int4_tbl b2
                                   Output: b2.f1
!                ->  Hash
!                      Output: a.q1, a.q2
!                      ->  Seq Scan on public.int8_tbl a
                             Output: a.q1, a.q2
!          ->  Seq Scan on public.int8_tbl d
!                Output: d.q1, COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)
!    ->  Hash
!          Output: c.q1, c.q2, i.f1
!          ->  Nested Loop
!                Output: c.q1, c.q2, i.f1
                 ->  Seq Scan on public.int8_tbl c
                       Output: c.q1, c.q2
!                ->  Materialize
!                      Output: i.f1
!                      ->  Seq Scan on public.int4_tbl i
!                            Output: i.f1
  (34 rows)
  
  -- check processing of postponed quals (bug #9041)

======================================================================

show-gistgettuple-leak-1.patchtext/x-diff; name=show-gistgettuple-leak-1.patchDownload
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index b2a20ca..c3a6cfc 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -562,6 +562,12 @@ gistgettuple(PG_FUNCTION_ARGS)
 				 * It's always head of so->pageData
 				 */
 				so->pageData =  list_delete_cell(so->pageData, tmpPageData, NULL);
+
+				{
+					static int lastreport = 0;
+					if ((lastreport++) % 10000 == 0)
+						MemoryContextStats(so->giststate->scanCxt);
+				}
 				PG_RETURN_BOOL(TRUE);
 			}
 
#8Thom Brown
thom@linux.com
In reply to: Heikki Linnakangas (#7)
Re: Index-only scans for GIST

On 18 August 2014 09:05, Heikki Linnakangas <hlinnakangas@vmware.com> wrote:

On 08/17/2014 07:15 PM, Anastasia Lubennikova wrote:

2014-08-07 0:30 GMT+04:00 Heikki Linnakangas <hlinnakangas@vmware.com>:

* I'm getting two regression failures with this (opr_sanity and join).

opr_sanity failure is corrected.
But there is remain question with join.
I check the latest version of my github repo and there's no fail in join
regression test
All 145 tests passed.
To tell the truth, I don't understand which changes could led to this
failure.
Could you show me regression diffs?

Sure, here you go. It seems like a change in a plan. At a quick glance it
seems harmless: the new plan is identical except that the left and right
side of a join have been reversed. But I don't understand either why this
patch would change that, so it needs to be investigated.

* The regression test queries that use LIMIT are not guaranteed to always

return the same rows, hence they're not very good regression test cases.
I'd suggest using more restricting WHERE clauses, so that each query only
returns a handful of rows.

Thank you for comment, I rewrote wrong queries. But could you explain why
LIMIT queries may return different results? Is it happens because of
different query optimization?

Imagine that you have a table with two rows, A and B. If you run a query
like "SELECT * FROM table LIMIT 1", the system can legally return either
row A or B, because there's no ORDER BY.

Now, admittedly you have a similar problem even without the LIMIT - the
system can legally return the rows in either order - but it's less of an
issue because at least you can quickly see from the diff that the result
set is in fact the same, the rows are just in different order. You could
fix that by adding an ORDER BY to all test queries, but we haven't done
that in the regression suite because then we would not have any test
coverage for cases where you don't have an ORDER BY. As a compromise, test
cases are usually written without an ORDER BY, but if e.g. the buildfarm
starts failing because of differences in the result set order across
platforms, then we add an ORDER BY to make it stable.

* I think it's leaking memory, in GIST scan context. I tested this with a

variant of the regression tests:

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,
10000000) as i;
CREATE INDEX gist_tbl_point_index ON gist_tbl USING gist (p);

set enable_seqscan=off;
set enable_bitmapscan=off;

explain analyze select p from gist_tbl where p <@ box(point(0,0),
point(9999999,9999999)) and length(p::text) < 10;

while the final query runs, 'top' shows constantly increasing memory
usage.

I don't think it's memory leak. After some increasing, memory using remain
the same. It works similar without using indexonlyscan.

No, it's definitely a leak caused by the patch. Test with the attached
patch, which prints out to the server log the amount of memory used by the
GiST scan memory context every 10000 rows. It clearly shows increasing
memory usage all the way to the end of the query.

It's cleaned up at the end of the query, but that's not good enough
because for a large query you might accumulate gigabytes of leaked memory
until the query has finished. If you (manually) apply the same patch to git
master, you'll see that the memory usage stays consistent and small.

Hi Anastasia,

Do you have time to address the issues brought up in Heikki's review? It
would be good if we could your work into PostgreSQL 9.5.

Thanks

Thom