LCOV - code coverage report
Current view: top level - src/backend/access/gist - gistget.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 90.5 % 263 238
Test Date: 2026-03-04 16:14:47 Functions: 100.0 % 8 8
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * gistget.c
       4              :  *    fetch tuples from a GiST scan.
       5              :  *
       6              :  *
       7              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       8              :  * Portions Copyright (c) 1994, Regents of the University of California
       9              :  *
      10              :  * IDENTIFICATION
      11              :  *    src/backend/access/gist/gistget.c
      12              :  *
      13              :  *-------------------------------------------------------------------------
      14              :  */
      15              : #include "postgres.h"
      16              : 
      17              : #include "access/genam.h"
      18              : #include "access/gist_private.h"
      19              : #include "access/relscan.h"
      20              : #include "executor/instrument_node.h"
      21              : #include "lib/pairingheap.h"
      22              : #include "miscadmin.h"
      23              : #include "pgstat.h"
      24              : #include "storage/predicate.h"
      25              : #include "utils/float.h"
      26              : #include "utils/memutils.h"
      27              : #include "utils/rel.h"
      28              : 
      29              : /*
      30              :  * gistkillitems() -- set LP_DEAD state for items an indexscan caller has
      31              :  * told us were killed.
      32              :  *
      33              :  * We re-read page here, so it's important to check page LSN. If the page
      34              :  * has been modified since the last read (as determined by LSN), we cannot
      35              :  * flag any entries because it is possible that the old entry was vacuumed
      36              :  * away and the TID was re-used by a completely different heap tuple.
      37              :  */
      38              : static void
      39            1 : gistkillitems(IndexScanDesc scan)
      40              : {
      41            1 :     GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
      42              :     Buffer      buffer;
      43              :     Page        page;
      44              :     OffsetNumber offnum;
      45              :     ItemId      iid;
      46              :     int         i;
      47            1 :     bool        killedsomething = false;
      48              : 
      49              :     Assert(so->curBlkno != InvalidBlockNumber);
      50              :     Assert(XLogRecPtrIsValid(so->curPageLSN));
      51              :     Assert(so->killedItems != NULL);
      52              : 
      53            1 :     buffer = ReadBuffer(scan->indexRelation, so->curBlkno);
      54            1 :     if (!BufferIsValid(buffer))
      55            0 :         return;
      56              : 
      57            1 :     LockBuffer(buffer, GIST_SHARE);
      58            1 :     gistcheckpage(scan->indexRelation, buffer);
      59            1 :     page = BufferGetPage(buffer);
      60              : 
      61              :     /*
      62              :      * If page LSN differs it means that the page was modified since the last
      63              :      * read. killedItems could be not valid so LP_DEAD hints applying is not
      64              :      * safe.
      65              :      */
      66            1 :     if (BufferGetLSNAtomic(buffer) != so->curPageLSN)
      67              :     {
      68            0 :         UnlockReleaseBuffer(buffer);
      69            0 :         so->numKilled = 0;       /* reset counter */
      70            0 :         return;
      71              :     }
      72              : 
      73              :     Assert(GistPageIsLeaf(page));
      74              : 
      75              :     /*
      76              :      * Mark all killedItems as dead. We need no additional recheck, because,
      77              :      * if page was modified, curPageLSN must have changed.
      78              :      */
      79            2 :     for (i = 0; i < so->numKilled; i++)
      80              :     {
      81            1 :         offnum = so->killedItems[i];
      82            1 :         iid = PageGetItemId(page, offnum);
      83            1 :         ItemIdMarkDead(iid);
      84            1 :         killedsomething = true;
      85              :     }
      86              : 
      87            1 :     if (killedsomething)
      88              :     {
      89            1 :         GistMarkPageHasGarbage(page);
      90            1 :         MarkBufferDirtyHint(buffer, true);
      91              :     }
      92              : 
      93            1 :     UnlockReleaseBuffer(buffer);
      94              : 
      95              :     /*
      96              :      * Always reset the scan state, so we don't look for same items on other
      97              :      * pages.
      98              :      */
      99            1 :     so->numKilled = 0;
     100              : }
     101              : 
     102              : /*
     103              :  * gistindex_keytest() -- does this index tuple satisfy the scan key(s)?
     104              :  *
     105              :  * The index tuple might represent either a heap tuple or a lower index page,
     106              :  * depending on whether the containing page is a leaf page or not.
     107              :  *
     108              :  * On success return for a heap tuple, *recheck_p is set to indicate whether
     109              :  * the quals need to be rechecked.  We recheck if any of the consistent()
     110              :  * functions request it.  recheck is not interesting when examining a non-leaf
     111              :  * entry, since we must visit the lower index page if there's any doubt.
     112              :  * Similarly, *recheck_distances_p is set to indicate whether the distances
     113              :  * need to be rechecked, and it is also ignored for non-leaf entries.
     114              :  *
     115              :  * If we are doing an ordered scan, so->distances[] is filled with distance
     116              :  * data from the distance() functions before returning success.
     117              :  *
     118              :  * We must decompress the key in the IndexTuple before passing it to the
     119              :  * sk_funcs (which actually are the opclass Consistent or Distance methods).
     120              :  *
     121              :  * Note that this function is always invoked in a short-lived memory context,
     122              :  * so we don't need to worry about cleaning up allocated memory, either here
     123              :  * or in the implementation of any Consistent or Distance methods.
     124              :  */
     125              : static bool
     126      1064201 : gistindex_keytest(IndexScanDesc scan,
     127              :                   IndexTuple tuple,
     128              :                   Page page,
     129              :                   OffsetNumber offset,
     130              :                   bool *recheck_p,
     131              :                   bool *recheck_distances_p)
     132              : {
     133      1064201 :     GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
     134      1064201 :     GISTSTATE  *giststate = so->giststate;
     135      1064201 :     ScanKey     key = scan->keyData;
     136      1064201 :     int         keySize = scan->numberOfKeys;
     137              :     IndexOrderByDistance *distance_p;
     138      1064201 :     Relation    r = scan->indexRelation;
     139              : 
     140      1064201 :     *recheck_p = false;
     141      1064201 :     *recheck_distances_p = false;
     142              : 
     143              :     /*
     144              :      * If it's a leftover invalid tuple from pre-9.1, treat it as a match with
     145              :      * minimum possible distances.  This means we'll always follow it to the
     146              :      * referenced page.
     147              :      */
     148      1064201 :     if (GistTupleIsInvalid(tuple))
     149              :     {
     150              :         int         i;
     151              : 
     152            0 :         if (GistPageIsLeaf(page))   /* shouldn't happen */
     153            0 :             elog(ERROR, "invalid GiST tuple found on leaf page");
     154            0 :         for (i = 0; i < scan->numberOfOrderBys; i++)
     155              :         {
     156            0 :             so->distances[i].value = -get_float8_infinity();
     157            0 :             so->distances[i].isnull = false;
     158              :         }
     159            0 :         return true;
     160              :     }
     161              : 
     162              :     /* Check whether it matches according to the Consistent functions */
     163      1644482 :     while (keySize > 0)
     164              :     {
     165              :         Datum       datum;
     166              :         bool        isNull;
     167              : 
     168      1021372 :         datum = index_getattr(tuple,
     169      1021372 :                               key->sk_attno,
     170              :                               giststate->leafTupdesc,
     171              :                               &isNull);
     172              : 
     173      1021372 :         if (key->sk_flags & SK_ISNULL)
     174              :         {
     175              :             /*
     176              :              * On non-leaf page we can't conclude that child hasn't NULL
     177              :              * values because of assumption in GiST: union (VAL, NULL) is VAL.
     178              :              * But if on non-leaf page key IS NULL, then all children are
     179              :              * NULL.
     180              :              */
     181        23098 :             if (key->sk_flags & SK_SEARCHNULL)
     182              :             {
     183        23065 :                 if (GistPageIsLeaf(page) && !isNull)
     184       441091 :                     return false;
     185              :             }
     186              :             else
     187              :             {
     188              :                 Assert(key->sk_flags & SK_SEARCHNOTNULL);
     189           33 :                 if (isNull)
     190            3 :                     return false;
     191              :             }
     192              :         }
     193       998274 :         else if (isNull)
     194              :         {
     195         1637 :             return false;
     196              :         }
     197              :         else
     198              :         {
     199              :             Datum       test;
     200              :             bool        recheck;
     201              :             GISTENTRY   de;
     202              : 
     203       996637 :             gistdentryinit(giststate, key->sk_attno - 1, &de,
     204              :                            datum, r, page, offset,
     205              :                            false, isNull);
     206              : 
     207              :             /*
     208              :              * Call the Consistent function to evaluate the test.  The
     209              :              * arguments are the index datum (as a GISTENTRY*), the comparison
     210              :              * datum, the comparison operator's strategy number and subtype
     211              :              * from pg_amop, and the recheck flag.
     212              :              *
     213              :              * (Presently there's no need to pass the subtype since it'll
     214              :              * always be zero, but might as well pass it for possible future
     215              :              * use.)
     216              :              *
     217              :              * We initialize the recheck flag to true (the safest assumption)
     218              :              * in case the Consistent function forgets to set it.
     219              :              */
     220       996637 :             recheck = true;
     221              : 
     222      1993274 :             test = FunctionCall5Coll(&key->sk_func,
     223              :                                      key->sk_collation,
     224              :                                      PointerGetDatum(&de),
     225              :                                      key->sk_argument,
     226       996637 :                                      UInt16GetDatum(key->sk_strategy),
     227              :                                      ObjectIdGetDatum(key->sk_subtype),
     228              :                                      PointerGetDatum(&recheck));
     229              : 
     230       996637 :             if (!DatumGetBool(test))
     231       418244 :                 return false;
     232       578393 :             *recheck_p |= recheck;
     233              :         }
     234              : 
     235       580281 :         key++;
     236       580281 :         keySize--;
     237              :     }
     238              : 
     239              :     /* OK, it passes --- now let's compute the distances */
     240       623110 :     key = scan->orderByData;
     241       623110 :     distance_p = so->distances;
     242       623110 :     keySize = scan->numberOfOrderBys;
     243       638878 :     while (keySize > 0)
     244              :     {
     245              :         Datum       datum;
     246              :         bool        isNull;
     247              : 
     248        15768 :         datum = index_getattr(tuple,
     249        15768 :                               key->sk_attno,
     250              :                               giststate->leafTupdesc,
     251              :                               &isNull);
     252              : 
     253        15768 :         if ((key->sk_flags & SK_ISNULL) || isNull)
     254              :         {
     255              :             /* Assume distance computes as null */
     256           54 :             distance_p->value = 0.0;
     257           54 :             distance_p->isnull = true;
     258              :         }
     259              :         else
     260              :         {
     261              :             Datum       dist;
     262              :             bool        recheck;
     263              :             GISTENTRY   de;
     264              : 
     265        15714 :             gistdentryinit(giststate, key->sk_attno - 1, &de,
     266              :                            datum, r, page, offset,
     267              :                            false, isNull);
     268              : 
     269              :             /*
     270              :              * Call the Distance function to evaluate the distance.  The
     271              :              * arguments are the index datum (as a GISTENTRY*), the comparison
     272              :              * datum, the ordering operator's strategy number and subtype from
     273              :              * pg_amop, and the recheck flag.
     274              :              *
     275              :              * (Presently there's no need to pass the subtype since it'll
     276              :              * always be zero, but might as well pass it for possible future
     277              :              * use.)
     278              :              *
     279              :              * If the function sets the recheck flag, the returned distance is
     280              :              * a lower bound on the true distance and needs to be rechecked.
     281              :              * We initialize the flag to 'false'.  This flag was added in
     282              :              * version 9.5; distance functions written before that won't know
     283              :              * about the flag, but are expected to never be lossy.
     284              :              */
     285        15714 :             recheck = false;
     286        31428 :             dist = FunctionCall5Coll(&key->sk_func,
     287              :                                      key->sk_collation,
     288              :                                      PointerGetDatum(&de),
     289              :                                      key->sk_argument,
     290        15714 :                                      UInt16GetDatum(key->sk_strategy),
     291              :                                      ObjectIdGetDatum(key->sk_subtype),
     292              :                                      PointerGetDatum(&recheck));
     293        15714 :             *recheck_distances_p |= recheck;
     294        15714 :             distance_p->value = DatumGetFloat8(dist);
     295        15714 :             distance_p->isnull = false;
     296              :         }
     297              : 
     298        15768 :         key++;
     299        15768 :         distance_p++;
     300        15768 :         keySize--;
     301              :     }
     302              : 
     303       623110 :     return true;
     304              : }
     305              : 
     306              : /*
     307              :  * Scan all items on the GiST index page identified by *pageItem, and insert
     308              :  * them into the queue (or directly to output areas)
     309              :  *
     310              :  * scan: index scan we are executing
     311              :  * pageItem: search queue item identifying an index page to scan
     312              :  * myDistances: distances array associated with pageItem, or NULL at the root
     313              :  * tbm: if not NULL, gistgetbitmap's output bitmap
     314              :  * ntids: if not NULL, gistgetbitmap's output tuple counter
     315              :  *
     316              :  * If tbm/ntids aren't NULL, we are doing an amgetbitmap scan, and heap
     317              :  * tuples should be reported directly into the bitmap.  If they are NULL,
     318              :  * we're doing a plain or ordered indexscan.  For a plain indexscan, heap
     319              :  * tuple TIDs are returned into so->pageData[].  For an ordered indexscan,
     320              :  * heap tuple TIDs are pushed into individual search queue items.  In an
     321              :  * index-only scan, reconstructed index tuples are returned along with the
     322              :  * TIDs.
     323              :  *
     324              :  * If we detect that the index page has split since we saw its downlink
     325              :  * in the parent, we push its new right sibling onto the queue so the
     326              :  * sibling will be processed next.
     327              :  */
     328              : static void
     329        38813 : gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
     330              :              IndexOrderByDistance *myDistances, TIDBitmap *tbm, int64 *ntids)
     331              : {
     332        38813 :     GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
     333        38813 :     GISTSTATE  *giststate = so->giststate;
     334        38813 :     Relation    r = scan->indexRelation;
     335              :     Buffer      buffer;
     336              :     Page        page;
     337              :     GISTPageOpaque opaque;
     338              :     OffsetNumber maxoff;
     339              :     OffsetNumber i;
     340              :     MemoryContext oldcxt;
     341              : 
     342              :     Assert(!GISTSearchItemIsHeap(*pageItem));
     343              : 
     344        38813 :     buffer = ReadBuffer(scan->indexRelation, pageItem->blkno);
     345        38813 :     LockBuffer(buffer, GIST_SHARE);
     346        38813 :     PredicateLockPage(r, BufferGetBlockNumber(buffer), scan->xs_snapshot);
     347        38813 :     gistcheckpage(scan->indexRelation, buffer);
     348        38813 :     page = BufferGetPage(buffer);
     349        38813 :     opaque = GistPageGetOpaque(page);
     350              : 
     351              :     /*
     352              :      * Check if we need to follow the rightlink. We need to follow it if the
     353              :      * page was concurrently split since we visited the parent (in which case
     354              :      * parentlsn < nsn), or if the system crashed after a page split but
     355              :      * before the downlink was inserted into the parent.
     356              :      */
     357        38813 :     if (XLogRecPtrIsValid(pageItem->data.parentlsn) &&
     358        71598 :         (GistFollowRight(page) ||
     359        35799 :          pageItem->data.parentlsn < GistPageGetNSN(page)) &&
     360            0 :         opaque->rightlink != InvalidBlockNumber /* sanity check */ )
     361              :     {
     362              :         /* There was a page split, follow right link to add pages */
     363              :         GISTSearchItem *item;
     364              : 
     365              :         /* This can't happen when starting at the root */
     366              :         Assert(myDistances != NULL);
     367              : 
     368            0 :         oldcxt = MemoryContextSwitchTo(so->queueCxt);
     369              : 
     370              :         /* Create new GISTSearchItem for the right sibling index page */
     371            0 :         item = palloc(SizeOfGISTSearchItem(scan->numberOfOrderBys));
     372            0 :         item->blkno = opaque->rightlink;
     373            0 :         item->data.parentlsn = pageItem->data.parentlsn;
     374              : 
     375              :         /* Insert it into the queue using same distances as for this page */
     376            0 :         memcpy(item->distances, myDistances,
     377            0 :                sizeof(item->distances[0]) * scan->numberOfOrderBys);
     378              : 
     379            0 :         pairingheap_add(so->queue, &item->phNode);
     380              : 
     381            0 :         MemoryContextSwitchTo(oldcxt);
     382              :     }
     383              : 
     384              :     /*
     385              :      * Check if the page was deleted after we saw the downlink. There's
     386              :      * nothing of interest on a deleted page. Note that we must do this after
     387              :      * checking the NSN for concurrent splits! It's possible that the page
     388              :      * originally contained some tuples that are visible to us, but was split
     389              :      * so that all the visible tuples were moved to another page, and then
     390              :      * this page was deleted.
     391              :      */
     392        38813 :     if (GistPageIsDeleted(page))
     393              :     {
     394            0 :         UnlockReleaseBuffer(buffer);
     395            0 :         return;
     396              :     }
     397              : 
     398        38813 :     so->nPageData = so->curPageData = 0;
     399        38813 :     scan->xs_hitup = NULL;       /* might point into pageDataCxt */
     400        38813 :     if (so->pageDataCxt)
     401         3042 :         MemoryContextReset(so->pageDataCxt);
     402              : 
     403              :     /*
     404              :      * We save the LSN of the page as we read it, so that we know whether it
     405              :      * is safe to apply LP_DEAD hints to the page later. This allows us to
     406              :      * drop the pin for MVCC scans, which allows vacuum to avoid blocking.
     407              :      */
     408        38813 :     so->curPageLSN = BufferGetLSNAtomic(buffer);
     409              : 
     410              :     /*
     411              :      * check all tuples on page
     412              :      */
     413        38813 :     maxoff = PageGetMaxOffsetNumber(page);
     414      1103015 :     for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
     415              :     {
     416      1064202 :         ItemId      iid = PageGetItemId(page, i);
     417              :         IndexTuple  it;
     418              :         bool        match;
     419              :         bool        recheck;
     420              :         bool        recheck_distances;
     421              : 
     422              :         /*
     423              :          * If the scan specifies not to return killed tuples, then we treat a
     424              :          * killed tuple as not passing the qual.
     425              :          */
     426      1064202 :         if (scan->ignore_killed_tuples && ItemIdIsDead(iid))
     427       441092 :             continue;
     428              : 
     429      1064201 :         it = (IndexTuple) PageGetItem(page, iid);
     430              : 
     431              :         /*
     432              :          * Must call gistindex_keytest in tempCxt, and clean up any leftover
     433              :          * junk afterward.
     434              :          */
     435      1064201 :         oldcxt = MemoryContextSwitchTo(so->giststate->tempCxt);
     436              : 
     437      1064201 :         match = gistindex_keytest(scan, it, page, i,
     438              :                                   &recheck, &recheck_distances);
     439              : 
     440      1064201 :         MemoryContextSwitchTo(oldcxt);
     441      1064201 :         MemoryContextReset(so->giststate->tempCxt);
     442              : 
     443              :         /* Ignore tuple if it doesn't match */
     444      1064201 :         if (!match)
     445       441091 :             continue;
     446              : 
     447       623110 :         if (tbm && GistPageIsLeaf(page))
     448              :         {
     449              :             /*
     450              :              * getbitmap scan, so just push heap tuple TIDs into the bitmap
     451              :              * without worrying about ordering
     452              :              */
     453       198079 :             tbm_add_tuples(tbm, &it->t_tid, 1, recheck);
     454       198079 :             (*ntids)++;
     455              :         }
     456       425031 :         else if (scan->numberOfOrderBys == 0 && GistPageIsLeaf(page))
     457              :         {
     458              :             /*
     459              :              * Non-ordered scan, so report tuples in so->pageData[]
     460              :              */
     461       373630 :             so->pageData[so->nPageData].heapPtr = it->t_tid;
     462       373630 :             so->pageData[so->nPageData].recheck = recheck;
     463       373630 :             so->pageData[so->nPageData].offnum = i;
     464              : 
     465              :             /*
     466              :              * In an index-only scan, also fetch the data from the tuple.  The
     467              :              * reconstructed tuples are stored in pageDataCxt.
     468              :              */
     469       373630 :             if (scan->xs_want_itup)
     470              :             {
     471       258739 :                 oldcxt = MemoryContextSwitchTo(so->pageDataCxt);
     472       517478 :                 so->pageData[so->nPageData].recontup =
     473       258739 :                     gistFetchTuple(giststate, r, it);
     474       258739 :                 MemoryContextSwitchTo(oldcxt);
     475              :             }
     476       373630 :             so->nPageData++;
     477              :         }
     478              :         else
     479              :         {
     480              :             /*
     481              :              * Must push item into search queue.  We get here for any lower
     482              :              * index page, and also for heap tuples if doing an ordered
     483              :              * search.
     484              :              */
     485              :             GISTSearchItem *item;
     486        51401 :             int         nOrderBys = scan->numberOfOrderBys;
     487              : 
     488        51401 :             oldcxt = MemoryContextSwitchTo(so->queueCxt);
     489              : 
     490              :             /* Create new GISTSearchItem for this item */
     491        51401 :             item = palloc(SizeOfGISTSearchItem(scan->numberOfOrderBys));
     492              : 
     493        51401 :             if (GistPageIsLeaf(page))
     494              :             {
     495              :                 /* Creating heap-tuple GISTSearchItem */
     496        14031 :                 item->blkno = InvalidBlockNumber;
     497        14031 :                 item->data.heap.heapPtr = it->t_tid;
     498        14031 :                 item->data.heap.recheck = recheck;
     499        14031 :                 item->data.heap.recheckDistances = recheck_distances;
     500              : 
     501              :                 /*
     502              :                  * In an index-only scan, also fetch the data from the tuple.
     503              :                  */
     504        14031 :                 if (scan->xs_want_itup)
     505         7110 :                     item->data.heap.recontup = gistFetchTuple(giststate, r, it);
     506              :             }
     507              :             else
     508              :             {
     509              :                 /* Creating index-page GISTSearchItem */
     510        37370 :                 item->blkno = ItemPointerGetBlockNumber(&it->t_tid);
     511              : 
     512              :                 /*
     513              :                  * LSN of current page is lsn of parent page for child. We
     514              :                  * only have a shared lock, so we need to get the LSN
     515              :                  * atomically.
     516              :                  */
     517        37370 :                 item->data.parentlsn = BufferGetLSNAtomic(buffer);
     518              :             }
     519              : 
     520              :             /* Insert it into the queue using new distance data */
     521        51401 :             memcpy(item->distances, so->distances,
     522              :                    sizeof(item->distances[0]) * nOrderBys);
     523              : 
     524        51401 :             pairingheap_add(so->queue, &item->phNode);
     525              : 
     526        51401 :             MemoryContextSwitchTo(oldcxt);
     527              :         }
     528              :     }
     529              : 
     530        38813 :     UnlockReleaseBuffer(buffer);
     531              : }
     532              : 
     533              : /*
     534              :  * Extract next item (in order) from search queue
     535              :  *
     536              :  * Returns a GISTSearchItem or NULL.  Caller must pfree item when done with it.
     537              :  */
     538              : static GISTSearchItem *
     539        39243 : getNextGISTSearchItem(GISTScanOpaque so)
     540              : {
     541              :     GISTSearchItem *item;
     542              : 
     543        39243 :     if (!pairingheap_is_empty(so->queue))
     544              :     {
     545        36422 :         item = (GISTSearchItem *) pairingheap_remove_first(so->queue);
     546              :     }
     547              :     else
     548              :     {
     549              :         /* Done when both heaps are empty */
     550         2821 :         item = NULL;
     551              :     }
     552              : 
     553              :     /* Return item; caller is responsible to pfree it */
     554        39243 :     return item;
     555              : }
     556              : 
     557              : /*
     558              :  * Fetch next heap tuple in an ordered search
     559              :  */
     560              : static bool
     561          644 : getNextNearest(IndexScanDesc scan)
     562              : {
     563          644 :     GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
     564          644 :     bool        res = false;
     565              : 
     566          644 :     if (scan->xs_hitup)
     567              :     {
     568              :         /* free previously returned tuple */
     569          425 :         pfree(scan->xs_hitup);
     570          425 :         scan->xs_hitup = NULL;
     571              :     }
     572              : 
     573              :     do
     574              :     {
     575          810 :         GISTSearchItem *item = getNextGISTSearchItem(so);
     576              : 
     577          810 :         if (!item)
     578           21 :             break;
     579              : 
     580          789 :         if (GISTSearchItemIsHeap(*item))
     581              :         {
     582              :             /* found a heap item at currently minimal distance */
     583          623 :             scan->xs_heaptid = item->data.heap.heapPtr;
     584          623 :             scan->xs_recheck = item->data.heap.recheck;
     585              : 
     586          623 :             index_store_float8_orderby_distances(scan, so->orderByTypes,
     587          623 :                                                  item->distances,
     588          623 :                                                  item->data.heap.recheckDistances);
     589              : 
     590              :             /* in an index-only scan, also return the reconstructed tuple. */
     591          623 :             if (scan->xs_want_itup)
     592          459 :                 scan->xs_hitup = item->data.heap.recontup;
     593          623 :             res = true;
     594              :         }
     595              :         else
     596              :         {
     597              :             /* visit an index page, extract its items into queue */
     598          166 :             CHECK_FOR_INTERRUPTS();
     599              : 
     600          166 :             gistScanPage(scan, item, item->distances, NULL, NULL);
     601              :         }
     602              : 
     603          789 :         pfree(item);
     604          789 :     } while (!res);
     605              : 
     606          644 :     return res;
     607              : }
     608              : 
     609              : /*
     610              :  * gistgettuple() -- Get the next tuple in the scan
     611              :  */
     612              : bool
     613       376174 : gistgettuple(IndexScanDesc scan, ScanDirection dir)
     614              : {
     615       376174 :     GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
     616              : 
     617       376174 :     if (dir != ForwardScanDirection)
     618            0 :         elog(ERROR, "GiST only supports forward scan direction");
     619              : 
     620       376174 :     if (!so->qual_ok)
     621            0 :         return false;
     622              : 
     623       376174 :     if (so->firstCall)
     624              :     {
     625              :         /* Begin the scan by processing the root page */
     626              :         GISTSearchItem fakeItem;
     627              : 
     628         2193 :         pgstat_count_index_scan(scan->indexRelation);
     629         2193 :         if (scan->instrument)
     630         1053 :             scan->instrument->nsearches++;
     631              : 
     632         2193 :         so->firstCall = false;
     633         2193 :         so->curPageData = so->nPageData = 0;
     634         2193 :         scan->xs_hitup = NULL;
     635         2193 :         if (so->pageDataCxt)
     636          340 :             MemoryContextReset(so->pageDataCxt);
     637              : 
     638         2193 :         fakeItem.blkno = GIST_ROOT_BLKNO;
     639         2193 :         memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
     640         2193 :         gistScanPage(scan, &fakeItem, NULL, NULL, NULL);
     641              :     }
     642              : 
     643       376174 :     if (scan->numberOfOrderBys > 0)
     644              :     {
     645              :         /* Must fetch tuples in strict distance order */
     646          644 :         return getNextNearest(scan);
     647              :     }
     648              :     else
     649              :     {
     650              :         /* Fetch tuples index-page-at-a-time */
     651              :         for (;;)
     652              :         {
     653       379561 :             if (so->curPageData < so->nPageData)
     654              :             {
     655       373551 :                 if (scan->kill_prior_tuple && so->curPageData > 0)
     656              :                 {
     657              : 
     658          282 :                     if (so->killedItems == NULL)
     659              :                     {
     660              :                         MemoryContext oldCxt =
     661          224 :                             MemoryContextSwitchTo(so->giststate->scanCxt);
     662              : 
     663          224 :                         so->killedItems =
     664          224 :                             (OffsetNumber *) palloc(MaxIndexTuplesPerPage
     665              :                                                     * sizeof(OffsetNumber));
     666              : 
     667          224 :                         MemoryContextSwitchTo(oldCxt);
     668              :                     }
     669          282 :                     if (so->numKilled < MaxIndexTuplesPerPage)
     670          282 :                         so->killedItems[so->numKilled++] =
     671          282 :                             so->pageData[so->curPageData - 1].offnum;
     672              :                 }
     673              :                 /* continuing to return tuples from a leaf page */
     674       373551 :                 scan->xs_heaptid = so->pageData[so->curPageData].heapPtr;
     675       373551 :                 scan->xs_recheck = so->pageData[so->curPageData].recheck;
     676              : 
     677              :                 /* in an index-only scan, also return the reconstructed tuple */
     678       373551 :                 if (scan->xs_want_itup)
     679       258739 :                     scan->xs_hitup = so->pageData[so->curPageData].recontup;
     680              : 
     681       373551 :                 so->curPageData++;
     682              : 
     683       373551 :                 return true;
     684              :             }
     685              : 
     686              :             /*
     687              :              * Check the last returned tuple and add it to killedItems if
     688              :              * necessary
     689              :              */
     690         6010 :             if (scan->kill_prior_tuple
     691            5 :                 && so->curPageData > 0
     692            5 :                 && so->curPageData == so->nPageData)
     693              :             {
     694              : 
     695            5 :                 if (so->killedItems == NULL)
     696              :                 {
     697              :                     MemoryContext oldCxt =
     698            5 :                         MemoryContextSwitchTo(so->giststate->scanCxt);
     699              : 
     700            5 :                     so->killedItems =
     701            5 :                         (OffsetNumber *) palloc(MaxIndexTuplesPerPage
     702              :                                                 * sizeof(OffsetNumber));
     703              : 
     704            5 :                     MemoryContextSwitchTo(oldCxt);
     705              :                 }
     706            5 :                 if (so->numKilled < MaxIndexTuplesPerPage)
     707            5 :                     so->killedItems[so->numKilled++] =
     708            5 :                         so->pageData[so->curPageData - 1].offnum;
     709              :             }
     710              :             /* find and process the next index page */
     711              :             do
     712              :             {
     713              :                 GISTSearchItem *item;
     714              : 
     715         7579 :                 if ((so->curBlkno != InvalidBlockNumber) && (so->numKilled > 0))
     716            1 :                     gistkillitems(scan);
     717              : 
     718         7579 :                 item = getNextGISTSearchItem(so);
     719              : 
     720         7579 :                 if (!item)
     721         1979 :                     return false;
     722              : 
     723         5600 :                 CHECK_FOR_INTERRUPTS();
     724              : 
     725              :                 /* save current item BlockNumber for next gistkillitems() call */
     726         5600 :                 so->curBlkno = item->blkno;
     727              : 
     728              :                 /*
     729              :                  * While scanning a leaf page, ItemPointers of matching heap
     730              :                  * tuples are stored in so->pageData.  If there are any on
     731              :                  * this page, we fall out of the inner "do" and loop around to
     732              :                  * return them.
     733              :                  */
     734         5600 :                 gistScanPage(scan, item, item->distances, NULL, NULL);
     735              : 
     736         5600 :                 pfree(item);
     737         5600 :             } while (so->nPageData == 0);
     738              :         }
     739              :     }
     740              : }
     741              : 
     742              : /*
     743              :  * gistgetbitmap() -- Get a bitmap of all heap tuple locations
     744              :  */
     745              : int64
     746          821 : gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
     747              : {
     748          821 :     GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
     749          821 :     int64       ntids = 0;
     750              :     GISTSearchItem fakeItem;
     751              : 
     752          821 :     if (!so->qual_ok)
     753            0 :         return 0;
     754              : 
     755          821 :     pgstat_count_index_scan(scan->indexRelation);
     756          821 :     if (scan->instrument)
     757          821 :         scan->instrument->nsearches++;
     758              : 
     759              :     /* Begin the scan by processing the root page */
     760          821 :     so->curPageData = so->nPageData = 0;
     761          821 :     scan->xs_hitup = NULL;
     762          821 :     if (so->pageDataCxt)
     763            0 :         MemoryContextReset(so->pageDataCxt);
     764              : 
     765          821 :     fakeItem.blkno = GIST_ROOT_BLKNO;
     766          821 :     memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
     767          821 :     gistScanPage(scan, &fakeItem, NULL, tbm, &ntids);
     768              : 
     769              :     /*
     770              :      * While scanning a leaf page, ItemPointers of matching heap tuples will
     771              :      * be stored directly into tbm, so we don't need to deal with them here.
     772              :      */
     773              :     for (;;)
     774        30033 :     {
     775        30854 :         GISTSearchItem *item = getNextGISTSearchItem(so);
     776              : 
     777        30854 :         if (!item)
     778          821 :             break;
     779              : 
     780        30033 :         CHECK_FOR_INTERRUPTS();
     781              : 
     782        30033 :         gistScanPage(scan, item, item->distances, tbm, &ntids);
     783              : 
     784        30033 :         pfree(item);
     785              :     }
     786              : 
     787          821 :     return ntids;
     788              : }
     789              : 
     790              : /*
     791              :  * Can we do index-only scans on the given index column?
     792              :  *
     793              :  * Opclasses that implement a fetch function support index-only scans.
     794              :  * Opclasses without compression functions also support index-only scans.
     795              :  * Included attributes always can be fetched for index-only scans.
     796              :  */
     797              : bool
     798         3927 : gistcanreturn(Relation index, int attno)
     799              : {
     800         7791 :     if (attno > IndexRelationGetNumberOfKeyAttributes(index) ||
     801         7177 :         OidIsValid(index_getprocid(index, attno, GIST_FETCH_PROC)) ||
     802         3313 :         !OidIsValid(index_getprocid(index, attno, GIST_COMPRESS_PROC)))
     803         2606 :         return true;
     804              :     else
     805         1321 :         return false;
     806              : }
        

Generated by: LCOV version 2.0-1