Commits

Tom Lane  committed 9220362

Teach SP-GiST to do index-only scans.

Operator classes can specify whether or not they support this; this
preserves the flexibility to use lossy representations within an index.

In passing, move constant data about a given index into the rd_amcache
cache area, instead of doing fresh lookups each time we start an index
operation. This is mainly to try to make sure that spgcanreturn() has
insignificant cost; I still don't have any proof that it matters for
actual index accesses. Also, get rid of useless copying of FmgrInfo
pointers; we can perfectly well use the relcache's versions in-place.

  • Participants
  • Parent commits 3695a55

Comments (0)

Files changed (10)

File doc/src/sgml/spgist.sgml

 {
     Oid         prefixType;     /* Data type of inner-tuple prefixes */
     Oid         labelType;      /* Data type of inner-tuple node labels */
+    bool        canReturnData;  /* Opclass can reconstruct original data */
     bool        longValuesOK;   /* Opclass can cope with values > 1 page */
 } spgConfigOut;
 </programlisting>
       <structfield>prefixType</> can be set to <literal>VOIDOID</>.
       Likewise, for operator classes that do not use node labels,
       <structfield>labelType</> can be set to <literal>VOIDOID</>.
+      <structfield>canReturnData</> should be set true if the operator class
+      is capable of reconstructing the originally-supplied index value.
       <structfield>longValuesOK</> should be set true only when the
       <structfield>attType</> is of variable length and the operator
       class is capable of segmenting long values by repeated suffixing
 
     Datum       reconstructedValue;     /* value reconstructed at parent */
     int         level;          /* current level (counting from zero) */
+    bool        returnData;     /* original data must be returned? */
 
     /* Data from current inner tuple */
     bool        allTheSame;     /* tuple is marked all-the-same? */
        parent level.
        <structfield>level</> is the current inner tuple's level, starting at
        zero for the root level.
+       <structfield>returnData</> is <literal>true</> if reconstructed data is
+       required for this query; this will only be so if the
+       <function>config</> function asserted <structfield>canReturnData</>.
        <structfield>allTheSame</> is true if the current inner tuple is
        marked <quote>all-the-same</>; in this case all the nodes have the
        same label (if any) and so either all or none of them match the query
 
     Datum       reconstructedValue;     /* value reconstructed at parent */
     int         level;          /* current level (counting from zero) */
+    bool        returnData;     /* original data must be returned? */
 
     Datum       leafDatum;      /* datum in leaf tuple */
 } spgLeafConsistentIn;
 
 typedef struct spgLeafConsistentOut
 {
+    Datum       leafValue;      /* reconstructed original data, if any */
     bool        recheck;        /* set true if operator must be rechecked */
 } spgLeafConsistentOut;
 </programlisting>
        parent level.
        <structfield>level</> is the current leaf tuple's level, starting at
        zero for the root level.
+       <structfield>returnData</> is <literal>true</> if reconstructed data is
+       required for this query; this will only be so if the
+       <function>config</> function asserted <structfield>canReturnData</>.
        <structfield>leafDatum</> is the key value stored in the current
        leaf tuple.
       </para>
       <para>
        The function must return <literal>true</> if the leaf tuple matches the
        query, or <literal>false</> if not.  In the <literal>true</> case,
+       if <structfield>returnData</> is <literal>true</> then
+       <structfield>leafValue</> must be set to the value originally supplied
+       to be indexed for this leaf tuple.  Also,
        <structfield>recheck</> may be set to <literal>true</> if the match
        is uncertain and so the operator must be re-applied to the actual heap
        tuple to verify the match.

File src/backend/access/spgist/spgdoinsert.c

 
 #include "postgres.h"
 
+#include "access/genam.h"
 #include "access/spgist_private.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
 	bool		insertedNew = false;
 	spgPickSplitIn in;
 	spgPickSplitOut out;
+	FmgrInfo   *procinfo;
 	bool		includeNew;
 	int			i,
 				max,
 	 */
 	memset(&out, 0, sizeof(out));
 
-	FunctionCall2Coll(&state->picksplitFn,
+	procinfo = index_getprocinfo(index, 1, SPGIST_PICKSPLIT_PROC);
+	FunctionCall2Coll(procinfo,
 					  index->rd_indcollation[0],
 					  PointerGetDatum(&in),
 					  PointerGetDatum(&out));
 			SpGistInnerTuple innerTuple;
 			spgChooseIn in;
 			spgChooseOut out;
+			FmgrInfo   *procinfo;
 
 			/*
 			 * spgAddNode and spgSplitTuple cases will loop back to here to
 
 			memset(&out, 0, sizeof(out));
 
-			FunctionCall2Coll(&state->chooseFn,
+			procinfo = index_getprocinfo(index, 1, SPGIST_CHOOSE_PROC);
+			FunctionCall2Coll(procinfo,
 							  index->rd_indcollation[0],
 							  PointerGetDatum(&in),
 							  PointerGetDatum(&out));

File src/backend/access/spgist/spgkdtreeproc.c

 
 	cfg->prefixType = FLOAT8OID;
 	cfg->labelType = VOIDOID;	/* we don't need node labels */
+	cfg->canReturnData = true;
 	cfg->longValuesOK = false;
 	PG_RETURN_VOID();
 }

File src/backend/access/spgist/spgquadtreeproc.c

 
 	cfg->prefixType = POINTOID;
 	cfg->labelType = VOIDOID;	/* we don't need node labels */
+	cfg->canReturnData = true;
 	cfg->longValuesOK = false;
 	PG_RETURN_VOID();
 }
 	/* all tests are exact */
 	out->recheck = false;
 
+	/* leafDatum is what it is... */
+	out->leafValue = in->leafDatum;
+
 	switch (in->strategy)
 	{
 		case RTLeftStrategyNumber:

File src/backend/access/spgist/spgscan.c

 	so->scanStack = NIL;
 }
 
-/* Initialize scanStack with a single entry for the root page */
+/*
+ * Initialize scanStack with a single entry for the root page, resetting
+ * any previously active scan
+ */
 static void
 resetSpGistScanOpaque(SpGistScanOpaque so)
 {
 
 	freeScanStack(so);
 	so->scanStack = list_make1(startEntry);
-	so->nPtrs = so->iPtr = 0;
+
+	if (so->want_itup)
+	{
+		/* Must pfree IndexTuples to avoid memory leak */
+		int		i;
+
+		for (i = 0; i < so->nPtrs; i++)
+			pfree(so->indexTups[i]);
+	}
+	so->iPtr = so->nPtrs = 0;
 }
 
 Datum
 										ALLOCSET_DEFAULT_INITSIZE,
 										ALLOCSET_DEFAULT_MAXSIZE);
 	resetSpGistScanOpaque(so);
+
+	/* Set up indexTupDesc and xs_itupdesc in case it's an index-only scan */
+	so->indexTupDesc = scan->xs_itupdesc = RelationGetDescr(rel);
+
 	scan->opaque = so;
 
 	PG_RETURN_POINTER(scan);
 /*
  * Test whether a leaf datum satisfies all the scan keys
  *
+ * *leafValue is set to the reconstructed datum, if provided
  * *recheck is set true if any of the operators are lossy
  */
 static bool
-spgLeafTest(SpGistScanOpaque so, Datum leafDatum,
+spgLeafTest(Relation index, SpGistScanOpaque so, Datum leafDatum,
 			int level, Datum reconstructedValue,
-			bool *recheck)
+			Datum *leafValue, bool *recheck)
 {
 	bool		result = true;
 	spgLeafConsistentIn in;
 	spgLeafConsistentOut out;
+	FmgrInfo   *procinfo;
 	MemoryContext oldCtx;
 	int			i;
 
+	*leafValue = (Datum) 0;
 	*recheck = false;
 
 	/* set up values that are the same for all quals */
 	in.reconstructedValue = reconstructedValue;
 	in.level = level;
+	in.returnData = so->want_itup;
 	in.leafDatum = leafDatum;
 
-	/* Apply each leaf consistent function, working in the temp context */
+	/* Apply each leaf consistency check, working in the temp context */
 	oldCtx = MemoryContextSwitchTo(so->tempCxt);
+
+	procinfo = index_getprocinfo(index, 1, SPGIST_LEAF_CONSISTENT_PROC);
+
 	for (i = 0; i < so->numberOfKeys; i++)
 	{
 		ScanKey		skey = &so->keyData[i];
 		in.strategy = skey->sk_strategy;
 		in.query = skey->sk_argument;
 
+		out.leafValue = (Datum) 0;
 		out.recheck = false;
 
-		result = DatumGetBool(FunctionCall2Coll(&so->state.leafConsistentFn,
+		result = DatumGetBool(FunctionCall2Coll(procinfo,
 												skey->sk_collation,
 												PointerGetDatum(&in),
 												PointerGetDatum(&out)));
+		*leafValue = out.leafValue;
 		*recheck |= out.recheck;
 		if (!result)
 			break;
  */
 static void
 spgWalk(Relation index, SpGistScanOpaque so, bool scanWholeIndex,
-		void (*storeRes) (SpGistScanOpaque, ItemPointer, bool))
+		void (*storeRes) (SpGistScanOpaque, ItemPointer, Datum, bool))
 {
 	Buffer		buffer = InvalidBuffer;
 	bool		reportedSome = false;
 		{
 			SpGistLeafTuple leafTuple;
 			OffsetNumber max = PageGetMaxOffsetNumber(page);
+			Datum		leafValue = (Datum) 0;
 			bool		recheck = false;
 
 			if (blkno == SPGIST_HEAD_BLKNO)
 					}
 
 					Assert(ItemPointerIsValid(&leafTuple->heapPtr));
-					if (spgLeafTest(so,
+					if (spgLeafTest(index, so,
 									SGLTDATUM(leafTuple, &so->state),
 									stackEntry->level,
 									stackEntry->reconstructedValue,
+									&leafValue,
 									&recheck))
 					{
-						storeRes(so, &leafTuple->heapPtr, recheck);
+						storeRes(so, &leafTuple->heapPtr, leafValue, recheck);
 						reportedSome = true;
 					}
 				}
 					}
 
 					Assert(ItemPointerIsValid(&leafTuple->heapPtr));
-					if (spgLeafTest(so,
+					if (spgLeafTest(index, so,
 									SGLTDATUM(leafTuple, &so->state),
 									stackEntry->level,
 									stackEntry->reconstructedValue,
+									&leafValue,
 									&recheck))
 					{
-						storeRes(so, &leafTuple->heapPtr, recheck);
+						storeRes(so, &leafTuple->heapPtr, leafValue, recheck);
 						reportedSome = true;
 					}
 
 			{
 				spgInnerConsistentIn in;
 				spgInnerConsistentOut out;
+				FmgrInfo   *procinfo;
 				SpGistNodeTuple *nodes;
 				int		   *andMap;
 				int		   *levelAdds;
 				/* set up values that are the same for all scankeys */
 				in.reconstructedValue = stackEntry->reconstructedValue;
 				in.level = stackEntry->level;
+				in.returnData = so->want_itup;
 				in.allTheSame = innerTuple->allTheSame;
 				in.hasPrefix = (innerTuple->prefixSize > 0);
 				in.prefixDatum = SGITDATUM(innerTuple, &so->state);
 				levelAdds = (int *) palloc0(sizeof(int) * in.nNodes);
 				reconstructedValues = (Datum *) palloc0(sizeof(Datum) * in.nNodes);
 
+				procinfo = index_getprocinfo(index, 1, SPGIST_INNER_CONSISTENT_PROC);
+
 				for (j = 0; j < so->numberOfKeys; j++)
 				{
 					ScanKey		skey = &so->keyData[j];
 
 					memset(&out, 0, sizeof(out));
 
-					FunctionCall2Coll(&so->state.innerConsistentFn,
+					FunctionCall2Coll(procinfo,
 									  skey->sk_collation,
 									  PointerGetDatum(&in),
 									  PointerGetDatum(&out));
 
 /* storeRes subroutine for getbitmap case */
 static void
-storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr, bool recheck)
+storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr,
+			Datum leafValue, bool recheck)
 {
 	tbm_add_tuples(so->tbm, heapPtr, 1, recheck);
 	so->ntids++;
 	/* Copy scankey to *so so we don't need to pass it around separately */
 	so->numberOfKeys = scan->numberOfKeys;
 	so->keyData = scan->keyData;
+	/* Ditto for the want_itup flag */
+	so->want_itup = false;
 
 	so->tbm = tbm;
 	so->ntids = 0;
 
 /* storeRes subroutine for gettuple case */
 static void
-storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr, bool recheck)
+storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
+			  Datum leafValue, bool recheck)
 {
 	Assert(so->nPtrs < MaxIndexTuplesPerPage);
 	so->heapPtrs[so->nPtrs] = *heapPtr;
 	so->recheck[so->nPtrs] = recheck;
+	if (so->want_itup)
+	{
+		/*
+		 * Reconstruct desired IndexTuple.  We have to copy the datum out of
+		 * the temp context anyway, so we may as well create the tuple here.
+		 */
+		bool	isnull = false;
+
+		so->indexTups[so->nPtrs] = index_form_tuple(so->indexTupDesc,
+													&leafValue,
+													&isnull);
+	}
 	so->nPtrs++;
 }
 
 	/* Copy scankey to *so so we don't need to pass it around separately */
 	so->numberOfKeys = scan->numberOfKeys;
 	so->keyData = scan->keyData;
+	/* Ditto for the want_itup flag */
+	so->want_itup = scan->xs_want_itup;
 
 	for (;;)
 	{
 			/* continuing to return tuples from a leaf page */
 			scan->xs_ctup.t_self = so->heapPtrs[so->iPtr];
 			scan->xs_recheck = so->recheck[so->iPtr];
+			scan->xs_itup = so->indexTups[so->iPtr];
 			so->iPtr++;
 			PG_RETURN_BOOL(true);
 		}
 
+		if (so->want_itup)
+		{
+			/* Must pfree IndexTuples to avoid memory leak */
+			int		i;
+
+			for (i = 0; i < so->nPtrs; i++)
+				pfree(so->indexTups[i]);
+		}
 		so->iPtr = so->nPtrs = 0;
+
 		spgWalk(scan->indexRelation, so, false, storeGettuple);
 
 		if (so->nPtrs == 0)
 Datum
 spgcanreturn(PG_FUNCTION_ARGS)
 {
-	/* Not implemented yet */
-	PG_RETURN_BOOL(false);
+	Relation	index = (Relation) PG_GETARG_POINTER(0);
+	SpGistCache *cache;
+
+	/* We can do it if the opclass config function says so */
+	cache = spgGetCache(index);
+
+	PG_RETURN_BOOL(cache->config.canReturnData);
 }

File src/backend/access/spgist/spgtextproc.c

 
 	cfg->prefixType = TEXTOID;
 	cfg->labelType = CHAROID;
+	cfg->canReturnData = true;
 	cfg->longValuesOK = true;	/* suffixing will shorten long values */
 	PG_RETURN_VOID();
 }
 
 	queryLen = VARSIZE_ANY_EXHDR(query);
 
-	/* For equality, we needn't reconstruct fullValue if not same length */
+	/*
+	 * For an equality check, we needn't reconstruct fullValue if not same
+	 * length; it can't match
+	 */
 	if (strategy == BTEqualStrategyNumber && queryLen != fullLen)
 		PG_RETURN_BOOL(false);
 
 	if (VARSIZE_ANY_EXHDR(leafValue) == 0 && level > 0)
 	{
 		fullValue = VARDATA(reconstrValue);
+		out->leafValue = PointerGetDatum(reconstrValue);
 	}
 	else
 	{
-		fullValue = palloc(fullLen);
+		text   *fullText = palloc(VARHDRSZ + fullLen);
+
+		SET_VARSIZE(fullText, VARHDRSZ + fullLen);
+		fullValue = VARDATA(fullText);
 		if (level)
 			memcpy(fullValue, VARDATA(reconstrValue), level);
 		if (VARSIZE_ANY_EXHDR(leafValue) > 0)
 			memcpy(fullValue + level, VARDATA_ANY(leafValue),
 				   VARSIZE_ANY_EXHDR(leafValue));
+		out->leafValue = PointerGetDatum(fullText);
 	}
 
 	/* Run the appropriate type of comparison */

File src/backend/access/spgist/spgutils.c

 	get_typlenbyval(type, &desc->attlen, &desc->attbyval);
 }
 
+/*
+ * Fetch local cache of AM-specific info about the index, initializing it
+ * if necessary
+ */
+SpGistCache *
+spgGetCache(Relation index)
+{
+	SpGistCache *cache;
+
+	if (index->rd_amcache == NULL)
+	{
+		Oid			atttype;
+		spgConfigIn in;
+		FmgrInfo   *procinfo;
+		Buffer		metabuffer;
+		SpGistMetaPageData *metadata;
+
+		cache = MemoryContextAllocZero(index->rd_indexcxt,
+									   sizeof(SpGistCache));
+
+		/* SPGiST doesn't support multi-column indexes */
+		Assert(index->rd_att->natts == 1);
+
+		/*
+		 * Get the actual data type of the indexed column from the index
+		 * tupdesc.  We pass this to the opclass config function so that
+		 * polymorphic opclasses are possible.
+		 */
+		atttype = index->rd_att->attrs[0]->atttypid;
+
+		/* Call the config function to get config info for the opclass */
+		in.attType = atttype;
+
+		procinfo = index_getprocinfo(index, 1, SPGIST_CONFIG_PROC);
+		FunctionCall2Coll(procinfo,
+						  index->rd_indcollation[0],
+						  PointerGetDatum(&in),
+						  PointerGetDatum(&cache->config));
+
+		/* Get the information we need about each relevant datatype */
+		fillTypeDesc(&cache->attType, atttype);
+		fillTypeDesc(&cache->attPrefixType, cache->config.prefixType);
+		fillTypeDesc(&cache->attLabelType, cache->config.labelType);
+
+		/* Last, get the lastUsedPages data from the metapage */
+		metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
+		LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
+
+		metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
+
+		if (metadata->magicNumber != SPGIST_MAGIC_NUMBER)
+			elog(ERROR, "index \"%s\" is not an SP-GiST index",
+				 RelationGetRelationName(index));
+
+		cache->lastUsedPages = metadata->lastUsedPages;
+
+		UnlockReleaseBuffer(metabuffer);
+
+		index->rd_amcache = (void *) cache;
+	}
+	else
+	{
+		/* assume it's up to date */
+		cache = (SpGistCache *) index->rd_amcache;
+	}
+
+	return cache;
+}
+
 /* Initialize SpGistState for working with the given index */
 void
 initSpGistState(SpGistState *state, Relation index)
 {
-	Oid			atttype;
-	spgConfigIn in;
+	SpGistCache *cache;
 
-	/* SPGiST doesn't support multi-column indexes */
-	Assert(index->rd_att->natts == 1);
+	/* Get cached static information about index */
+	cache = spgGetCache(index);
 
-	/*
-	 * Get the actual data type of the indexed column from the index tupdesc.
-	 * We pass this to the opclass config function so that polymorphic
-	 * opclasses are possible.
-	 */
-	atttype = index->rd_att->attrs[0]->atttypid;
-
-	/* Get the config info for the opclass */
-	in.attType = atttype;
-
-	memset(&state->config, 0, sizeof(state->config));
-
-	FunctionCall2Coll(index_getprocinfo(index, 1, SPGIST_CONFIG_PROC),
-					  index->rd_indcollation[0],
-					  PointerGetDatum(&in),
-					  PointerGetDatum(&state->config));
-
-	/* Get the information we need about each relevant datatype */
-	fillTypeDesc(&state->attType, atttype);
-	fillTypeDesc(&state->attPrefixType, state->config.prefixType);
-	fillTypeDesc(&state->attLabelType, state->config.labelType);
-
-	/* Get lookup info for opclass support procs */
-	fmgr_info_copy(&(state->chooseFn),
-				   index_getprocinfo(index, 1, SPGIST_CHOOSE_PROC),
-				   CurrentMemoryContext);
-	fmgr_info_copy(&(state->picksplitFn),
-				   index_getprocinfo(index, 1, SPGIST_PICKSPLIT_PROC),
-				   CurrentMemoryContext);
-	fmgr_info_copy(&(state->innerConsistentFn),
-				   index_getprocinfo(index, 1, SPGIST_INNER_CONSISTENT_PROC),
-				   CurrentMemoryContext);
-	fmgr_info_copy(&(state->leafConsistentFn),
-				   index_getprocinfo(index, 1, SPGIST_LEAF_CONSISTENT_PROC),
-				   CurrentMemoryContext);
+	state->config = cache->config;
+	state->attType = cache->attType;
+	state->attPrefixType = cache->attPrefixType;
+	state->attLabelType = cache->attLabelType;
 
 	/* Make workspace for constructing dead tuples */
 	state->deadTupleStorage = palloc0(SGDTSIZE);
 	/* Set XID to use in redirection tuples */
 	state->myXid = GetTopTransactionIdIfAny();
 
+	/* Assume we're not in an index build (spgbuild will override) */
 	state->isBuild = false;
 }
 
 }
 
 /*
- * Fetch local cache of lastUsedPages info, initializing it from the metapage
- * if necessary
- */
-static SpGistCache *
-spgGetCache(Relation index)
-{
-	SpGistCache *cache;
-
-	if (index->rd_amcache == NULL)
-	{
-		Buffer		metabuffer;
-		SpGistMetaPageData *metadata;
-
-		cache = MemoryContextAlloc(index->rd_indexcxt,
-								   sizeof(SpGistCache));
-
-		metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
-		LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
-
-		metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
-
-		if (metadata->magicNumber != SPGIST_MAGIC_NUMBER)
-			elog(ERROR, "index \"%s\" is not an SP-GiST index",
-				 RelationGetRelationName(index));
-
-		*cache = metadata->lastUsedPages;
-
-		UnlockReleaseBuffer(metabuffer);
-
-		index->rd_amcache = cache;
-	}
-	else
-	{
-		cache = (SpGistCache *) index->rd_amcache;
-	}
-
-	return cache;
-}
-
-/*
  * Update index metapage's lastUsedPages info from local cache, if possible
  *
  * Updating meta page isn't critical for index working, so
 		if (ConditionalLockBuffer(metabuffer))
 		{
 			metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
-			metadata->lastUsedPages = *cache;
+			metadata->lastUsedPages = cache->lastUsedPages;
 
 			MarkBufferDirty(metabuffer);
 			UnlockReleaseBuffer(metabuffer);
 
 /* Macro to select proper element of lastUsedPages cache depending on flags */
 #define GET_LUP(c, f)	(((f) & GBUF_LEAF) ? \
-						 &(c)->leafPage : \
-						 &(c)->innerPage[(f) & GBUF_PARITY_MASK])
+						 &(c)->lastUsedPages.leafPage : \
+						 &(c)->lastUsedPages.innerPage[(f) & GBUF_PARITY_MASK])
 
 /*
  * Allocate and initialize a new buffer of the type and parity specified by
 			else
 			{
 				/* Page has wrong parity, record it in cache and try again */
-				cache->innerPage[blkParity].blkno = blkno;
-				cache->innerPage[blkParity].freeSpace =
+				cache->lastUsedPages.innerPage[blkParity].blkno = blkno;
+				cache->lastUsedPages.innerPage[blkParity].freeSpace =
 					PageGetExactFreeSpace(BufferGetPage(buffer));
 				UnlockReleaseBuffer(buffer);
 			}

File src/include/access/spgist.h

 {
 	Oid			prefixType;		/* Data type of inner-tuple prefixes */
 	Oid			labelType;		/* Data type of inner-tuple node labels */
+	bool		canReturnData;	/* Opclass can reconstruct original data */
 	bool		longValuesOK;	/* Opclass can cope with values > 1 page */
 } spgConfigOut;
 
 
 	Datum		reconstructedValue;		/* value reconstructed at parent */
 	int			level;			/* current level (counting from zero) */
+	bool		returnData;		/* original data must be returned? */
 
 	/* Data from current inner tuple */
 	bool		allTheSame;		/* tuple is marked all-the-same? */
 
 	Datum		reconstructedValue;		/* value reconstructed at parent */
 	int			level;			/* current level (counting from zero) */
+	bool		returnData;		/* original data must be returned? */
 
 	Datum		leafDatum;		/* datum in leaf tuple */
 } spgLeafConsistentIn;
 
 typedef struct spgLeafConsistentOut
 {
+	Datum		leafValue;		/* reconstructed original data, if any */
 	bool		recheck;		/* set true if operator must be rechecked */
 } spgLeafConsistentOut;
 

File src/include/access/spgist_private.h

 	int			freeSpace;		/* its free space (could be obsolete!) */
 } SpGistLastUsedPage;
 
-typedef struct SpGistCache
+typedef struct SpGistLUPCache
 {
 	SpGistLastUsedPage innerPage[3];	/* one per triple-parity group */
 	SpGistLastUsedPage leafPage;
-} SpGistCache;
+} SpGistLUPCache;
 
 /*
  * metapage
 typedef struct SpGistMetaPageData
 {
 	uint32		magicNumber;	/* for identity cross-check */
-	SpGistCache lastUsedPages;	/* shared storage of last-used info */
+	SpGistLUPCache lastUsedPages;	/* shared storage of last-used info */
 } SpGistMetaPageData;
 
 #define SPGIST_MAGIC_NUMBER (0xBA0BABED)
 	SpGistTypeDesc attPrefixType;	/* type of inner-tuple prefix values */
 	SpGistTypeDesc attLabelType;	/* type of node label values */
 
-	/* lookup data for the opclass support functions, except config */
-	FmgrInfo	chooseFn;
-	FmgrInfo	picksplitFn;
-	FmgrInfo	innerConsistentFn;
-	FmgrInfo	leafConsistentFn;
-
 	char	   *deadTupleStorage;	/* workspace for spgFormDeadTuple */
 
 	TransactionId myXid;		/* XID to use when creating a redirect tuple */
 	int64		ntids;			/* number of TIDs passed to bitmap */
 
 	/* These fields are only used in amgettuple scans: */
+	bool		want_itup;		/* are we reconstructing tuples? */
+	TupleDesc	indexTupDesc;	/* if so, tuple descriptor for them */
 	int			nPtrs;			/* number of TIDs found on current page */
 	int			iPtr;			/* index for scanning through same */
 	ItemPointerData heapPtrs[MaxIndexTuplesPerPage]; /* TIDs from cur page */
 	bool		recheck[MaxIndexTuplesPerPage];		/* their recheck flags */
+	IndexTuple	indexTups[MaxIndexTuplesPerPage];	/* reconstructed tuples */
 
 	/*
 	 * Note: using MaxIndexTuplesPerPage above is a bit hokey since
 
 typedef SpGistScanOpaqueData *SpGistScanOpaque;
 
+/*
+ * This struct is what we actually keep in index->rd_amcache.  It includes
+ * static configuration information as well as the lastUsedPages cache.
+ */
+typedef struct SpGistCache
+{
+	spgConfigOut config;		/* filled in by opclass config method */
+
+	SpGistTypeDesc attType;			/* type of input data and leaf values */
+	SpGistTypeDesc attPrefixType;	/* type of inner-tuple prefix values */
+	SpGistTypeDesc attLabelType;	/* type of node label values */
+
+	SpGistLUPCache lastUsedPages;	/* local storage of last-used info */
+} SpGistCache;
+
 
 /*
  * SPGiST tuple types.  Note: inner, leaf, and dead tuple structs
 #define GBUF_INNER_PARITY(x)	((x) % 3)
 
 /* spgutils.c */
+extern SpGistCache *spgGetCache(Relation index);
 extern void initSpGistState(SpGistState *state, Relation index);
 extern Buffer SpGistNewBuffer(Relation index);
 extern void SpGistUpdateMetaPage(Relation index);

File src/test/regress/expected/create_index.out

 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
-                       QUERY PLAN                        
----------------------------------------------------------
+                        QUERY PLAN                         
+-----------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_quad_ind on quad_point_tbl
+   ->  Index Only Scan using sp_quad_ind on quad_point_tbl
          Index Cond: (p <@ '(1000,1000),(200,200)'::box)
 (3 rows)
 
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
-                       QUERY PLAN                        
----------------------------------------------------------
+                        QUERY PLAN                         
+-----------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_quad_ind on quad_point_tbl
-         Index Cond: ('(1000,1000),(200,200)'::box @> p)
+   ->  Index Only Scan using sp_quad_ind on quad_point_tbl
+         Index Cond: (p <@ '(1000,1000),(200,200)'::box)
 (3 rows)
 
 SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
-                      QUERY PLAN                      
-------------------------------------------------------
+                        QUERY PLAN                         
+-----------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_quad_ind on quad_point_tbl
+   ->  Index Only Scan using sp_quad_ind on quad_point_tbl
          Index Cond: (p << '(5000,4000)'::point)
 (3 rows)
 
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
-                      QUERY PLAN                      
-------------------------------------------------------
+                        QUERY PLAN                         
+-----------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_quad_ind on quad_point_tbl
+   ->  Index Only Scan using sp_quad_ind on quad_point_tbl
          Index Cond: (p >> '(5000,4000)'::point)
 (3 rows)
 
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM quad_point_tbl WHERE p <^ '(5000, 4000)';
-                      QUERY PLAN                      
-------------------------------------------------------
+                        QUERY PLAN                         
+-----------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_quad_ind on quad_point_tbl
+   ->  Index Only Scan using sp_quad_ind on quad_point_tbl
          Index Cond: (p <^ '(5000,4000)'::point)
 (3 rows)
 
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM quad_point_tbl WHERE p >^ '(5000, 4000)';
-                      QUERY PLAN                      
-------------------------------------------------------
+                        QUERY PLAN                         
+-----------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_quad_ind on quad_point_tbl
+   ->  Index Only Scan using sp_quad_ind on quad_point_tbl
          Index Cond: (p >^ '(5000,4000)'::point)
 (3 rows)
 
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
-                      QUERY PLAN                      
-------------------------------------------------------
+                        QUERY PLAN                         
+-----------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_quad_ind on quad_point_tbl
+   ->  Index Only Scan using sp_quad_ind on quad_point_tbl
          Index Cond: (p ~= '(4585,365)'::point)
 (3 rows)
 
                        QUERY PLAN                        
 ---------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_kd_ind on kd_point_tbl
+   ->  Index Only Scan using sp_kd_ind on kd_point_tbl
          Index Cond: (p <@ '(1000,1000),(200,200)'::box)
 (3 rows)
 
                        QUERY PLAN                        
 ---------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_kd_ind on kd_point_tbl
-         Index Cond: ('(1000,1000),(200,200)'::box @> p)
+   ->  Index Only Scan using sp_kd_ind on kd_point_tbl
+         Index Cond: (p <@ '(1000,1000),(200,200)'::box)
 (3 rows)
 
 SELECT count(*) FROM kd_point_tbl WHERE box '(200,200,1000,1000)' @> p;
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM kd_point_tbl WHERE p << '(5000, 4000)';
-                    QUERY PLAN                    
---------------------------------------------------
+                      QUERY PLAN                       
+-------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_kd_ind on kd_point_tbl
+   ->  Index Only Scan using sp_kd_ind on kd_point_tbl
          Index Cond: (p << '(5000,4000)'::point)
 (3 rows)
 
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM kd_point_tbl WHERE p >> '(5000, 4000)';
-                    QUERY PLAN                    
---------------------------------------------------
+                      QUERY PLAN                       
+-------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_kd_ind on kd_point_tbl
+   ->  Index Only Scan using sp_kd_ind on kd_point_tbl
          Index Cond: (p >> '(5000,4000)'::point)
 (3 rows)
 
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM kd_point_tbl WHERE p <^ '(5000, 4000)';
-                    QUERY PLAN                    
---------------------------------------------------
+                      QUERY PLAN                       
+-------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_kd_ind on kd_point_tbl
+   ->  Index Only Scan using sp_kd_ind on kd_point_tbl
          Index Cond: (p <^ '(5000,4000)'::point)
 (3 rows)
 
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM kd_point_tbl WHERE p >^ '(5000, 4000)';
-                    QUERY PLAN                    
---------------------------------------------------
+                      QUERY PLAN                       
+-------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_kd_ind on kd_point_tbl
+   ->  Index Only Scan using sp_kd_ind on kd_point_tbl
          Index Cond: (p >^ '(5000,4000)'::point)
 (3 rows)
 
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
-                    QUERY PLAN                    
---------------------------------------------------
+                      QUERY PLAN                       
+-------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_kd_ind on kd_point_tbl
+   ->  Index Only Scan using sp_kd_ind on kd_point_tbl
          Index Cond: (p ~= '(4585,365)'::point)
 (3 rows)
 
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef';
-                      QUERY PLAN                       
--------------------------------------------------------
+                         QUERY PLAN                         
+------------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_suff_ind on suffix_text_tbl
+   ->  Index Only Scan using sp_suff_ind on suffix_text_tbl
          Index Cond: (t = '0123456789abcdef'::text)
 (3 rows)
 
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde';
-                      QUERY PLAN                       
--------------------------------------------------------
+                         QUERY PLAN                         
+------------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_suff_ind on suffix_text_tbl
+   ->  Index Only Scan using sp_suff_ind on suffix_text_tbl
          Index Cond: (t = '0123456789abcde'::text)
 (3 rows)
 
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdefF';
-                      QUERY PLAN                       
--------------------------------------------------------
+                         QUERY PLAN                         
+------------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_suff_ind on suffix_text_tbl
+   ->  Index Only Scan using sp_suff_ind on suffix_text_tbl
          Index Cond: (t = '0123456789abcdefF'::text)
 (3 rows)
 
                               QUERY PLAN                              
 ----------------------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_suff_ind on suffix_text_tbl
+   ->  Index Only Scan using sp_suff_ind on suffix_text_tbl
          Index Cond: (t < 'Aztec                         Ct  '::text)
 (3 rows)
 
                                QUERY PLAN                               
 ------------------------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_suff_ind on suffix_text_tbl
+   ->  Index Only Scan using sp_suff_ind on suffix_text_tbl
          Index Cond: (t ~<~ 'Aztec                         Ct  '::text)
 (3 rows)
 
                               QUERY PLAN                               
 -----------------------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_suff_ind on suffix_text_tbl
+   ->  Index Only Scan using sp_suff_ind on suffix_text_tbl
          Index Cond: (t <= 'Aztec                         Ct  '::text)
 (3 rows)
 
                                QUERY PLAN                                
 -------------------------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_suff_ind on suffix_text_tbl
+   ->  Index Only Scan using sp_suff_ind on suffix_text_tbl
          Index Cond: (t ~<=~ 'Aztec                         Ct  '::text)
 (3 rows)
 
                               QUERY PLAN                              
 ----------------------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_suff_ind on suffix_text_tbl
+   ->  Index Only Scan using sp_suff_ind on suffix_text_tbl
          Index Cond: (t = 'Aztec                         Ct  '::text)
 (3 rows)
 
                               QUERY PLAN                              
 ----------------------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_suff_ind on suffix_text_tbl
+   ->  Index Only Scan using sp_suff_ind on suffix_text_tbl
          Index Cond: (t = 'Worth                         St  '::text)
 (3 rows)
 
                               QUERY PLAN                               
 -----------------------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_suff_ind on suffix_text_tbl
+   ->  Index Only Scan using sp_suff_ind on suffix_text_tbl
          Index Cond: (t >= 'Worth                         St  '::text)
 (3 rows)
 
                                QUERY PLAN                                
 -------------------------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_suff_ind on suffix_text_tbl
+   ->  Index Only Scan using sp_suff_ind on suffix_text_tbl
          Index Cond: (t ~>=~ 'Worth                         St  '::text)
 (3 rows)
 
                               QUERY PLAN                              
 ----------------------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_suff_ind on suffix_text_tbl
+   ->  Index Only Scan using sp_suff_ind on suffix_text_tbl
          Index Cond: (t > 'Worth                         St  '::text)
 (3 rows)
 
                                QUERY PLAN                               
 ------------------------------------------------------------------------
  Aggregate
-   ->  Index Scan using sp_suff_ind on suffix_text_tbl
+   ->  Index Only Scan using sp_suff_ind on suffix_text_tbl
          Index Cond: (t ~>~ 'Worth                         St  '::text)
 (3 rows)