Teach SP-GiST to do index-only scans.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 19 Dec 2011 19:58:41 +0000 (14:58 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 19 Dec 2011 19:58:41 +0000 (14:58 -0500)
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.

doc/src/sgml/spgist.sgml
src/backend/access/spgist/spgdoinsert.c
src/backend/access/spgist/spgkdtreeproc.c
src/backend/access/spgist/spgquadtreeproc.c
src/backend/access/spgist/spgscan.c
src/backend/access/spgist/spgtextproc.c
src/backend/access/spgist/spgutils.c
src/include/access/spgist.h
src/include/access/spgist_private.h
src/test/regress/expected/create_index.out

index 70e0e9ff503dedefd9c28c0ade7f09a77fea825f..dcc3cc2d7331247fe3b3cb998188ed08083ff98a 100644 (file)
@@ -145,6 +145,7 @@ typedef struct spgConfigOut
 {
     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 &gt; 1 page */
 } spgConfigOut;
 </programlisting>
@@ -159,6 +160,8 @@ typedef struct spgConfigOut
       <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
@@ -441,6 +444,7 @@ typedef struct spgInnerConsistentIn
 
     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? */
@@ -467,6 +471,9 @@ typedef struct spgInnerConsistentOut
        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
@@ -525,12 +532,14 @@ typedef struct spgLeafConsistentIn
 
     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>
@@ -543,6 +552,9 @@ typedef struct spgLeafConsistentOut
        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>
@@ -550,6 +562,9 @@ typedef struct spgLeafConsistentOut
       <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.
index 4bb8dfa150907243370fafad92f76b20b10c90ef..207c32776c73ca2f78746c0861af3e37d3351842 100644 (file)
@@ -15,6 +15,7 @@
 
 #include "postgres.h"
 
+#include "access/genam.h"
 #include "access/spgist_private.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
@@ -678,6 +679,7 @@ doPickSplit(Relation index, SpGistState *state,
        bool            insertedNew = false;
        spgPickSplitIn in;
        spgPickSplitOut out;
+       FmgrInfo   *procinfo;
        bool            includeNew;
        int                     i,
                                max,
@@ -816,7 +818,8 @@ doPickSplit(Relation index, SpGistState *state,
         */
        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));
@@ -1944,6 +1947,7 @@ spgdoinsert(Relation index, SpGistState *state,
                        SpGistInnerTuple innerTuple;
                        spgChooseIn in;
                        spgChooseOut out;
+                       FmgrInfo   *procinfo;
 
                        /*
                         * spgAddNode and spgSplitTuple cases will loop back to here to
@@ -1968,7 +1972,8 @@ spgdoinsert(Relation index, SpGistState *state,
 
                        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));
index e11d1a35e3af58fadc67cc7ae803ead98d3b3934..d5c1b4f454c375f57237c87838a0c98aef44eb2f 100644 (file)
@@ -30,6 +30,7 @@ spg_kd_config(PG_FUNCTION_ARGS)
 
        cfg->prefixType = FLOAT8OID;
        cfg->labelType = VOIDOID;       /* we don't need node labels */
+       cfg->canReturnData = true;
        cfg->longValuesOK = false;
        PG_RETURN_VOID();
 }
index 0be6e55ad305d82d61ff03e362b1eb983c4bbcde..e40d8b0e76577eaf78793fc73188cc78e757315a 100644 (file)
@@ -30,6 +30,7 @@ spg_quad_config(PG_FUNCTION_ARGS)
 
        cfg->prefixType = POINTOID;
        cfg->labelType = VOIDOID;       /* we don't need node labels */
+       cfg->canReturnData = true;
        cfg->longValuesOK = false;
        PG_RETURN_VOID();
 }
@@ -324,6 +325,9 @@ spg_quad_leaf_consistent(PG_FUNCTION_ARGS)
        /* all tests are exact */
        out->recheck = false;
 
+       /* leafDatum is what it is... */
+       out->leafValue = in->leafDatum;
+
        switch (in->strategy)
        {
                case RTLeftStrategyNumber:
index 748265ecba7dbae82ed6e933b52f222d93daae6b..e158ca1aae7e1aa909f04c229673b0d9430c8fb8 100644 (file)
@@ -55,7 +55,10 @@ freeScanStack(SpGistScanOpaque so)
        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)
 {
@@ -65,7 +68,16 @@ 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
@@ -87,6 +99,10 @@ spgbeginscan(PG_FUNCTION_ARGS)
                                                                                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);
@@ -138,28 +154,35 @@ spgrestrpos(PG_FUNCTION_ARGS)
 /*
  * 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];
@@ -174,12 +197,14 @@ spgLeafTest(SpGistScanOpaque so, Datum leafDatum,
                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;
@@ -198,7 +223,7 @@ spgLeafTest(SpGistScanOpaque so, Datum leafDatum,
  */
 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;
@@ -243,6 +268,7 @@ redirect:
                {
                        SpGistLeafTuple leafTuple;
                        OffsetNumber max = PageGetMaxOffsetNumber(page);
+                       Datum           leafValue = (Datum) 0;
                        bool            recheck = false;
 
                        if (blkno == SPGIST_HEAD_BLKNO)
@@ -260,13 +286,14 @@ redirect:
                                        }
 
                                        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;
                                        }
                                }
@@ -304,13 +331,14 @@ redirect:
                                        }
 
                                        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;
                                        }
 
@@ -374,6 +402,7 @@ redirect:
                        {
                                spgInnerConsistentIn in;
                                spgInnerConsistentOut out;
+                               FmgrInfo   *procinfo;
                                SpGistNodeTuple *nodes;
                                int                *andMap;
                                int                *levelAdds;
@@ -388,6 +417,7 @@ redirect:
                                /* 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);
@@ -405,6 +435,8 @@ redirect:
                                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];
@@ -421,7 +453,7 @@ redirect:
 
                                        memset(&out, 0, sizeof(out));
 
-                                       FunctionCall2Coll(&so->state.innerConsistentFn,
+                                       FunctionCall2Coll(procinfo,
                                                                          skey->sk_collation,
                                                                          PointerGetDatum(&in),
                                                                          PointerGetDatum(&out));
@@ -490,7 +522,8 @@ redirect:
 
 /* 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++;
@@ -506,6 +539,8 @@ spggetbitmap(PG_FUNCTION_ARGS)
        /* 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;
@@ -517,11 +552,24 @@ spggetbitmap(PG_FUNCTION_ARGS)
 
 /* 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++;
 }
 
@@ -538,6 +586,8 @@ spggettuple(PG_FUNCTION_ARGS)
        /* 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 (;;)
        {
@@ -546,11 +596,21 @@ spggettuple(PG_FUNCTION_ARGS)
                        /* 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)
@@ -563,6 +623,11 @@ spggettuple(PG_FUNCTION_ARGS)
 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);
 }
index b60379784256a5c732a034498f14053b0bf437c5..ab37d482636f023318b85f5f3fc02dbbadf43de0 100644 (file)
@@ -51,6 +51,7 @@ spg_text_config(PG_FUNCTION_ARGS)
 
        cfg->prefixType = TEXTOID;
        cfg->labelType = CHAROID;
+       cfg->canReturnData = true;
        cfg->longValuesOK = true;       /* suffixing will shorten long values */
        PG_RETURN_VOID();
 }
@@ -521,7 +522,10 @@ spg_text_leaf_consistent(PG_FUNCTION_ARGS)
 
        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);
 
@@ -529,15 +533,20 @@ spg_text_leaf_consistent(PG_FUNCTION_ARGS)
        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 */
index c6bf07a9424e11ffedfab504128fc8f6e75875d0..aced35c84414f56c52184a45f69ad46ab6c010ce 100644 (file)
@@ -34,51 +34,88 @@ fillTypeDesc(SpGistTypeDesc *desc, Oid type)
        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);
@@ -86,6 +123,7 @@ initSpGistState(SpGistState *state, Relation index)
        /* Set XID to use in redirection tuples */
        state->myXid = GetTopTransactionIdIfAny();
 
+       /* Assume we're not in an index build (spgbuild will override) */
        state->isBuild = false;
 }
 
@@ -153,46 +191,6 @@ SpGistNewBuffer(Relation index)
        return buffer;
 }
 
-/*
- * 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
  *
@@ -215,7 +213,7 @@ SpGistUpdateMetaPage(Relation index)
                if (ConditionalLockBuffer(metabuffer))
                {
                        metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
-                       metadata->lastUsedPages = *cache;
+                       metadata->lastUsedPages = cache->lastUsedPages;
 
                        MarkBufferDirty(metabuffer);
                        UnlockReleaseBuffer(metabuffer);
@@ -229,8 +227,8 @@ SpGistUpdateMetaPage(Relation index)
 
 /* 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
@@ -282,8 +280,8 @@ allocNewBuffer(Relation index, int flags)
                        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);
                        }
index df6fec6cf455939421e929542bc9840bbb7f50d5..d2f0a72c36bc005fe7dab7d6fa8fead7e954d49b 100644 (file)
@@ -43,6 +43,7 @@ typedef struct spgConfigOut
 {
        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;
 
@@ -132,6 +133,7 @@ typedef struct spgInnerConsistentIn
 
        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? */
@@ -159,12 +161,14 @@ typedef struct spgLeafConsistentIn
 
        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;
 
index 5c57799f09c12b9c9afa380b466a7d19f42abe95..ec6d2d07fc7664e89e2ba75e548966e7b65dcede 100644 (file)
@@ -71,11 +71,11 @@ typedef struct SpGistLastUsedPage
        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
@@ -83,7 +83,7 @@ typedef struct SpGistCache
 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)
@@ -112,12 +112,6 @@ typedef struct SpGistState
        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 */
@@ -144,10 +138,13 @@ typedef struct SpGistScanOpaqueData
        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
@@ -158,6 +155,21 @@ typedef struct SpGistScanOpaqueData
 
 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
@@ -570,6 +582,7 @@ typedef struct spgxlogVacuumRedirect
 #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);
index 86cee2de94269be38afce4fa56794feb6f7d259a..36198b8edd56a417c176a1da45ee416e79ee9088 100644 (file)
@@ -680,10 +680,10 @@ SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0
 
 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)
 
@@ -695,11 +695,11 @@ SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
 
 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;
@@ -710,10 +710,10 @@ 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)
 
@@ -725,10 +725,10 @@ SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
 
 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)
 
@@ -740,10 +740,10 @@ SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
 
 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)
 
@@ -755,10 +755,10 @@ SELECT count(*) FROM quad_point_tbl WHERE p <^ '(5000, 4000)';
 
 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)
 
@@ -770,10 +770,10 @@ SELECT count(*) FROM quad_point_tbl WHERE p >^ '(5000, 4000)';
 
 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)
 
@@ -788,7 +788,7 @@ SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
                        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)
 
@@ -803,8 +803,8 @@ SELECT count(*) FROM kd_point_tbl WHERE box '(200,200,1000,1000)' @> p;
                        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;
@@ -815,10 +815,10 @@ 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)
 
@@ -830,10 +830,10 @@ SELECT count(*) FROM kd_point_tbl WHERE p << '(5000, 4000)';
 
 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)
 
@@ -845,10 +845,10 @@ SELECT count(*) FROM kd_point_tbl WHERE p >> '(5000, 4000)';
 
 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)
 
@@ -860,10 +860,10 @@ SELECT count(*) FROM kd_point_tbl WHERE p <^ '(5000, 4000)';
 
 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)
 
@@ -875,10 +875,10 @@ SELECT count(*) FROM kd_point_tbl WHERE p >^ '(5000, 4000)';
 
 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)
 
@@ -890,10 +890,10 @@ SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
 
 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)
 
@@ -905,10 +905,10 @@ SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef';
 
 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)
 
@@ -920,10 +920,10 @@ SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde';
 
 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)
 
@@ -938,7 +938,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t <    'Aztec
                               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)
 
@@ -953,7 +953,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t ~<~  'Aztec
                                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)
 
@@ -968,7 +968,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t <=   'Aztec
                               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)
 
@@ -983,7 +983,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t ~<=~ 'Aztec
                                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)
 
@@ -998,7 +998,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t =    'Aztec
                               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)
 
@@ -1013,7 +1013,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t =    'Worth
                               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)
 
@@ -1028,7 +1028,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t >=   'Worth
                               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)
 
@@ -1043,7 +1043,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t ~>=~ 'Worth
                                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)
 
@@ -1058,7 +1058,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t >    'Worth
                               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)
 
@@ -1073,7 +1073,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t ~>~  'Worth
                                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)