LCOV - code coverage report
Current view: top level - src/backend/access/hash - hashsearch.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 153 227 67.4 %
Date: 2024-04-26 10:11:36 Functions: 6 7 85.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * hashsearch.c
       4             :  *    search code for postgres hash tables
       5             :  *
       6             :  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
       7             :  * Portions Copyright (c) 1994, Regents of the University of California
       8             :  *
       9             :  *
      10             :  * IDENTIFICATION
      11             :  *    src/backend/access/hash/hashsearch.c
      12             :  *
      13             :  *-------------------------------------------------------------------------
      14             :  */
      15             : #include "postgres.h"
      16             : 
      17             : #include "access/hash.h"
      18             : #include "access/relscan.h"
      19             : #include "miscadmin.h"
      20             : #include "pgstat.h"
      21             : #include "storage/predicate.h"
      22             : #include "utils/rel.h"
      23             : 
      24             : static bool _hash_readpage(IndexScanDesc scan, Buffer *bufP,
      25             :                            ScanDirection dir);
      26             : static int  _hash_load_qualified_items(IndexScanDesc scan, Page page,
      27             :                                        OffsetNumber offnum, ScanDirection dir);
      28             : static inline void _hash_saveitem(HashScanOpaque so, int itemIndex,
      29             :                                   OffsetNumber offnum, IndexTuple itup);
      30             : static void _hash_readnext(IndexScanDesc scan, Buffer *bufp,
      31             :                            Page *pagep, HashPageOpaque *opaquep);
      32             : 
      33             : /*
      34             :  *  _hash_next() -- Get the next item in a scan.
      35             :  *
      36             :  *      On entry, so->currPos describes the current page, which may
      37             :  *      be pinned but not locked, and so->currPos.itemIndex identifies
      38             :  *      which item was previously returned.
      39             :  *
      40             :  *      On successful exit, scan->xs_heaptid is set to the TID of the next
      41             :  *      heap tuple.  so->currPos is updated as needed.
      42             :  *
      43             :  *      On failure exit (no more tuples), we return false with pin
      44             :  *      held on bucket page but no pins or locks held on overflow
      45             :  *      page.
      46             :  */
      47             : bool
      48      101162 : _hash_next(IndexScanDesc scan, ScanDirection dir)
      49             : {
      50      101162 :     Relation    rel = scan->indexRelation;
      51      101162 :     HashScanOpaque so = (HashScanOpaque) scan->opaque;
      52             :     HashScanPosItem *currItem;
      53             :     BlockNumber blkno;
      54             :     Buffer      buf;
      55      101162 :     bool        end_of_scan = false;
      56             : 
      57             :     /*
      58             :      * Advance to the next tuple on the current page; or if done, try to read
      59             :      * data from the next or previous page based on the scan direction. Before
      60             :      * moving to the next or previous page make sure that we deal with all the
      61             :      * killed items.
      62             :      */
      63      101162 :     if (ScanDirectionIsForward(dir))
      64             :     {
      65       68162 :         if (++so->currPos.itemIndex > so->currPos.lastItem)
      66             :         {
      67         596 :             if (so->numKilled > 0)
      68           0 :                 _hash_kill_items(scan);
      69             : 
      70         596 :             blkno = so->currPos.nextPage;
      71         596 :             if (BlockNumberIsValid(blkno))
      72             :             {
      73         156 :                 buf = _hash_getbuf(rel, blkno, HASH_READ, LH_OVERFLOW_PAGE);
      74         156 :                 if (!_hash_readpage(scan, &buf, dir))
      75           0 :                     end_of_scan = true;
      76             :             }
      77             :             else
      78         440 :                 end_of_scan = true;
      79             :         }
      80             :     }
      81             :     else
      82             :     {
      83       33000 :         if (--so->currPos.itemIndex < so->currPos.firstItem)
      84             :         {
      85          84 :             if (so->numKilled > 0)
      86           0 :                 _hash_kill_items(scan);
      87             : 
      88          84 :             blkno = so->currPos.prevPage;
      89          84 :             if (BlockNumberIsValid(blkno))
      90             :             {
      91          78 :                 buf = _hash_getbuf(rel, blkno, HASH_READ,
      92             :                                    LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
      93             : 
      94             :                 /*
      95             :                  * We always maintain the pin on bucket page for whole scan
      96             :                  * operation, so releasing the additional pin we have acquired
      97             :                  * here.
      98             :                  */
      99          78 :                 if (buf == so->hashso_bucket_buf ||
     100          72 :                     buf == so->hashso_split_bucket_buf)
     101           6 :                     _hash_dropbuf(rel, buf);
     102             : 
     103          78 :                 if (!_hash_readpage(scan, &buf, dir))
     104           0 :                     end_of_scan = true;
     105             :             }
     106             :             else
     107           6 :                 end_of_scan = true;
     108             :         }
     109             :     }
     110             : 
     111      101162 :     if (end_of_scan)
     112             :     {
     113         446 :         _hash_dropscanbuf(rel, so);
     114         446 :         HashScanPosInvalidate(so->currPos);
     115         446 :         return false;
     116             :     }
     117             : 
     118             :     /* OK, itemIndex says what to return */
     119      100716 :     currItem = &so->currPos.items[so->currPos.itemIndex];
     120      100716 :     scan->xs_heaptid = currItem->heapTid;
     121             : 
     122      100716 :     return true;
     123             : }
     124             : 
     125             : /*
     126             :  * Advance to next page in a bucket, if any.  If we are scanning the bucket
     127             :  * being populated during split operation then this function advances to the
     128             :  * bucket being split after the last bucket page of bucket being populated.
     129             :  */
     130             : static void
     131         222 : _hash_readnext(IndexScanDesc scan,
     132             :                Buffer *bufp, Page *pagep, HashPageOpaque *opaquep)
     133             : {
     134             :     BlockNumber blkno;
     135         222 :     Relation    rel = scan->indexRelation;
     136         222 :     HashScanOpaque so = (HashScanOpaque) scan->opaque;
     137         222 :     bool        block_found = false;
     138             : 
     139         222 :     blkno = (*opaquep)->hasho_nextblkno;
     140             : 
     141             :     /*
     142             :      * Retain the pin on primary bucket page till the end of scan.  Refer the
     143             :      * comments in _hash_first to know the reason of retaining pin.
     144             :      */
     145         222 :     if (*bufp == so->hashso_bucket_buf || *bufp == so->hashso_split_bucket_buf)
     146         150 :         LockBuffer(*bufp, BUFFER_LOCK_UNLOCK);
     147             :     else
     148          72 :         _hash_relbuf(rel, *bufp);
     149             : 
     150         222 :     *bufp = InvalidBuffer;
     151             :     /* check for interrupts while we're not holding any buffer lock */
     152         222 :     CHECK_FOR_INTERRUPTS();
     153         222 :     if (BlockNumberIsValid(blkno))
     154             :     {
     155          78 :         *bufp = _hash_getbuf(rel, blkno, HASH_READ, LH_OVERFLOW_PAGE);
     156          78 :         block_found = true;
     157             :     }
     158         144 :     else if (so->hashso_buc_populated && !so->hashso_buc_split)
     159             :     {
     160             :         /*
     161             :          * end of bucket, scan bucket being split if there was a split in
     162             :          * progress at the start of scan.
     163             :          */
     164           0 :         *bufp = so->hashso_split_bucket_buf;
     165             : 
     166             :         /*
     167             :          * buffer for bucket being split must be valid as we acquire the pin
     168             :          * on it before the start of scan and retain it till end of scan.
     169             :          */
     170             :         Assert(BufferIsValid(*bufp));
     171             : 
     172           0 :         LockBuffer(*bufp, BUFFER_LOCK_SHARE);
     173           0 :         PredicateLockPage(rel, BufferGetBlockNumber(*bufp), scan->xs_snapshot);
     174             : 
     175             :         /*
     176             :          * setting hashso_buc_split to true indicates that we are scanning
     177             :          * bucket being split.
     178             :          */
     179           0 :         so->hashso_buc_split = true;
     180             : 
     181           0 :         block_found = true;
     182             :     }
     183             : 
     184         222 :     if (block_found)
     185             :     {
     186          78 :         *pagep = BufferGetPage(*bufp);
     187          78 :         *opaquep = HashPageGetOpaque(*pagep);
     188             :     }
     189         222 : }
     190             : 
     191             : /*
     192             :  * Advance to previous page in a bucket, if any.  If the current scan has
     193             :  * started during split operation then this function advances to bucket
     194             :  * being populated after the first bucket page of bucket being split.
     195             :  */
     196             : static void
     197           0 : _hash_readprev(IndexScanDesc scan,
     198             :                Buffer *bufp, Page *pagep, HashPageOpaque *opaquep)
     199             : {
     200             :     BlockNumber blkno;
     201           0 :     Relation    rel = scan->indexRelation;
     202           0 :     HashScanOpaque so = (HashScanOpaque) scan->opaque;
     203             :     bool        haveprevblk;
     204             : 
     205           0 :     blkno = (*opaquep)->hasho_prevblkno;
     206             : 
     207             :     /*
     208             :      * Retain the pin on primary bucket page till the end of scan.  Refer the
     209             :      * comments in _hash_first to know the reason of retaining pin.
     210             :      */
     211           0 :     if (*bufp == so->hashso_bucket_buf || *bufp == so->hashso_split_bucket_buf)
     212             :     {
     213           0 :         LockBuffer(*bufp, BUFFER_LOCK_UNLOCK);
     214           0 :         haveprevblk = false;
     215             :     }
     216             :     else
     217             :     {
     218           0 :         _hash_relbuf(rel, *bufp);
     219           0 :         haveprevblk = true;
     220             :     }
     221             : 
     222           0 :     *bufp = InvalidBuffer;
     223             :     /* check for interrupts while we're not holding any buffer lock */
     224           0 :     CHECK_FOR_INTERRUPTS();
     225             : 
     226           0 :     if (haveprevblk)
     227             :     {
     228             :         Assert(BlockNumberIsValid(blkno));
     229           0 :         *bufp = _hash_getbuf(rel, blkno, HASH_READ,
     230             :                              LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
     231           0 :         *pagep = BufferGetPage(*bufp);
     232           0 :         *opaquep = HashPageGetOpaque(*pagep);
     233             : 
     234             :         /*
     235             :          * We always maintain the pin on bucket page for whole scan operation,
     236             :          * so releasing the additional pin we have acquired here.
     237             :          */
     238           0 :         if (*bufp == so->hashso_bucket_buf || *bufp == so->hashso_split_bucket_buf)
     239           0 :             _hash_dropbuf(rel, *bufp);
     240             :     }
     241           0 :     else if (so->hashso_buc_populated && so->hashso_buc_split)
     242             :     {
     243             :         /*
     244             :          * end of bucket, scan bucket being populated if there was a split in
     245             :          * progress at the start of scan.
     246             :          */
     247           0 :         *bufp = so->hashso_bucket_buf;
     248             : 
     249             :         /*
     250             :          * buffer for bucket being populated must be valid as we acquire the
     251             :          * pin on it before the start of scan and retain it till end of scan.
     252             :          */
     253             :         Assert(BufferIsValid(*bufp));
     254             : 
     255           0 :         LockBuffer(*bufp, BUFFER_LOCK_SHARE);
     256           0 :         *pagep = BufferGetPage(*bufp);
     257           0 :         *opaquep = HashPageGetOpaque(*pagep);
     258             : 
     259             :         /* move to the end of bucket chain */
     260           0 :         while (BlockNumberIsValid((*opaquep)->hasho_nextblkno))
     261           0 :             _hash_readnext(scan, bufp, pagep, opaquep);
     262             : 
     263             :         /*
     264             :          * setting hashso_buc_split to false indicates that we are scanning
     265             :          * bucket being populated.
     266             :          */
     267           0 :         so->hashso_buc_split = false;
     268             :     }
     269           0 : }
     270             : 
     271             : /*
     272             :  *  _hash_first() -- Find the first item in a scan.
     273             :  *
     274             :  *      We find the first item (or, if backward scan, the last item) in the
     275             :  *      index that satisfies the qualification associated with the scan
     276             :  *      descriptor.
     277             :  *
     278             :  *      On successful exit, if the page containing current index tuple is an
     279             :  *      overflow page, both pin and lock are released whereas if it is a bucket
     280             :  *      page then it is pinned but not locked and data about the matching
     281             :  *      tuple(s) on the page has been loaded into so->currPos,
     282             :  *      scan->xs_heaptid is set to the heap TID of the current tuple.
     283             :  *
     284             :  *      On failure exit (no more tuples), we return false, with pin held on
     285             :  *      bucket page but no pins or locks held on overflow page.
     286             :  */
     287             : bool
     288         598 : _hash_first(IndexScanDesc scan, ScanDirection dir)
     289             : {
     290         598 :     Relation    rel = scan->indexRelation;
     291         598 :     HashScanOpaque so = (HashScanOpaque) scan->opaque;
     292             :     ScanKey     cur;
     293             :     uint32      hashkey;
     294             :     Bucket      bucket;
     295             :     Buffer      buf;
     296             :     Page        page;
     297             :     HashPageOpaque opaque;
     298             :     HashScanPosItem *currItem;
     299             : 
     300         598 :     pgstat_count_index_scan(rel);
     301             : 
     302             :     /*
     303             :      * We do not support hash scans with no index qualification, because we
     304             :      * would have to read the whole index rather than just one bucket. That
     305             :      * creates a whole raft of problems, since we haven't got a practical way
     306             :      * to lock all the buckets against splits or compactions.
     307             :      */
     308         598 :     if (scan->numberOfKeys < 1)
     309           0 :         ereport(ERROR,
     310             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     311             :                  errmsg("hash indexes do not support whole-index scans")));
     312             : 
     313             :     /* There may be more than one index qual, but we hash only the first */
     314         598 :     cur = &scan->keyData[0];
     315             : 
     316             :     /* We support only single-column hash indexes */
     317             :     Assert(cur->sk_attno == 1);
     318             :     /* And there's only one operator strategy, too */
     319             :     Assert(cur->sk_strategy == HTEqualStrategyNumber);
     320             : 
     321             :     /*
     322             :      * If the constant in the index qual is NULL, assume it cannot match any
     323             :      * items in the index.
     324             :      */
     325         598 :     if (cur->sk_flags & SK_ISNULL)
     326           0 :         return false;
     327             : 
     328             :     /*
     329             :      * Okay to compute the hash key.  We want to do this before acquiring any
     330             :      * locks, in case a user-defined hash function happens to be slow.
     331             :      *
     332             :      * If scankey operator is not a cross-type comparison, we can use the
     333             :      * cached hash function; otherwise gotta look it up in the catalogs.
     334             :      *
     335             :      * We support the convention that sk_subtype == InvalidOid means the
     336             :      * opclass input type; this is a hack to simplify life for ScanKeyInit().
     337             :      */
     338         598 :     if (cur->sk_subtype == rel->rd_opcintype[0] ||
     339           8 :         cur->sk_subtype == InvalidOid)
     340         598 :         hashkey = _hash_datum2hashkey(rel, cur->sk_argument);
     341             :     else
     342           0 :         hashkey = _hash_datum2hashkey_type(rel, cur->sk_argument,
     343             :                                            cur->sk_subtype);
     344             : 
     345         598 :     so->hashso_sk_hash = hashkey;
     346             : 
     347         598 :     buf = _hash_getbucketbuf_from_hashkey(rel, hashkey, HASH_READ, NULL);
     348         598 :     PredicateLockPage(rel, BufferGetBlockNumber(buf), scan->xs_snapshot);
     349         598 :     page = BufferGetPage(buf);
     350         598 :     opaque = HashPageGetOpaque(page);
     351         598 :     bucket = opaque->hasho_bucket;
     352             : 
     353         598 :     so->hashso_bucket_buf = buf;
     354             : 
     355             :     /*
     356             :      * If a bucket split is in progress, then while scanning the bucket being
     357             :      * populated, we need to skip tuples that were copied from bucket being
     358             :      * split.  We also need to maintain a pin on the bucket being split to
     359             :      * ensure that split-cleanup work done by vacuum doesn't remove tuples
     360             :      * from it till this scan is done.  We need to maintain a pin on the
     361             :      * bucket being populated to ensure that vacuum doesn't squeeze that
     362             :      * bucket till this scan is complete; otherwise, the ordering of tuples
     363             :      * can't be maintained during forward and backward scans.  Here, we have
     364             :      * to be cautious about locking order: first, acquire the lock on bucket
     365             :      * being split; then, release the lock on it but not the pin; then,
     366             :      * acquire a lock on bucket being populated and again re-verify whether
     367             :      * the bucket split is still in progress.  Acquiring the lock on bucket
     368             :      * being split first ensures that the vacuum waits for this scan to
     369             :      * finish.
     370             :      */
     371         598 :     if (H_BUCKET_BEING_POPULATED(opaque))
     372             :     {
     373             :         BlockNumber old_blkno;
     374             :         Buffer      old_buf;
     375             : 
     376           0 :         old_blkno = _hash_get_oldblock_from_newbucket(rel, bucket);
     377             : 
     378             :         /*
     379             :          * release the lock on new bucket and re-acquire it after acquiring
     380             :          * the lock on old bucket.
     381             :          */
     382           0 :         LockBuffer(buf, BUFFER_LOCK_UNLOCK);
     383             : 
     384           0 :         old_buf = _hash_getbuf(rel, old_blkno, HASH_READ, LH_BUCKET_PAGE);
     385             : 
     386             :         /*
     387             :          * remember the split bucket buffer so as to use it later for
     388             :          * scanning.
     389             :          */
     390           0 :         so->hashso_split_bucket_buf = old_buf;
     391           0 :         LockBuffer(old_buf, BUFFER_LOCK_UNLOCK);
     392             : 
     393           0 :         LockBuffer(buf, BUFFER_LOCK_SHARE);
     394           0 :         page = BufferGetPage(buf);
     395           0 :         opaque = HashPageGetOpaque(page);
     396             :         Assert(opaque->hasho_bucket == bucket);
     397             : 
     398           0 :         if (H_BUCKET_BEING_POPULATED(opaque))
     399           0 :             so->hashso_buc_populated = true;
     400             :         else
     401             :         {
     402           0 :             _hash_dropbuf(rel, so->hashso_split_bucket_buf);
     403           0 :             so->hashso_split_bucket_buf = InvalidBuffer;
     404             :         }
     405             :     }
     406             : 
     407             :     /* If a backwards scan is requested, move to the end of the chain */
     408         598 :     if (ScanDirectionIsBackward(dir))
     409             :     {
     410             :         /*
     411             :          * Backward scans that start during split needs to start from end of
     412             :          * bucket being split.
     413             :          */
     414          84 :         while (BlockNumberIsValid(opaque->hasho_nextblkno) ||
     415           6 :                (so->hashso_buc_populated && !so->hashso_buc_split))
     416          78 :             _hash_readnext(scan, &buf, &page, &opaque);
     417             :     }
     418             : 
     419             :     /* remember which buffer we have pinned, if any */
     420             :     Assert(BufferIsInvalid(so->currPos.buf));
     421         598 :     so->currPos.buf = buf;
     422             : 
     423             :     /* Now find all the tuples satisfying the qualification from a page */
     424         598 :     if (!_hash_readpage(scan, &buf, dir))
     425         144 :         return false;
     426             : 
     427             :     /* OK, itemIndex says what to return */
     428         454 :     currItem = &so->currPos.items[so->currPos.itemIndex];
     429         454 :     scan->xs_heaptid = currItem->heapTid;
     430             : 
     431             :     /* if we're here, _hash_readpage found a valid tuples */
     432         454 :     return true;
     433             : }
     434             : 
     435             : /*
     436             :  *  _hash_readpage() -- Load data from current index page into so->currPos
     437             :  *
     438             :  *  We scan all the items in the current index page and save them into
     439             :  *  so->currPos if it satisfies the qualification. If no matching items
     440             :  *  are found in the current page, we move to the next or previous page
     441             :  *  in a bucket chain as indicated by the direction.
     442             :  *
     443             :  *  Return true if any matching items are found else return false.
     444             :  */
     445             : static bool
     446         832 : _hash_readpage(IndexScanDesc scan, Buffer *bufP, ScanDirection dir)
     447             : {
     448         832 :     Relation    rel = scan->indexRelation;
     449         832 :     HashScanOpaque so = (HashScanOpaque) scan->opaque;
     450             :     Buffer      buf;
     451             :     Page        page;
     452             :     HashPageOpaque opaque;
     453             :     OffsetNumber offnum;
     454             :     uint16      itemIndex;
     455             : 
     456         832 :     buf = *bufP;
     457             :     Assert(BufferIsValid(buf));
     458         832 :     _hash_checkpage(rel, buf, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
     459         832 :     page = BufferGetPage(buf);
     460         832 :     opaque = HashPageGetOpaque(page);
     461             : 
     462         832 :     so->currPos.buf = buf;
     463         832 :     so->currPos.currPage = BufferGetBlockNumber(buf);
     464             : 
     465         832 :     if (ScanDirectionIsForward(dir))
     466             :     {
     467         748 :         BlockNumber prev_blkno = InvalidBlockNumber;
     468             : 
     469             :         for (;;)
     470             :         {
     471             :             /* new page, locate starting position by binary search */
     472         748 :             offnum = _hash_binsearch(page, so->hashso_sk_hash);
     473             : 
     474         748 :             itemIndex = _hash_load_qualified_items(scan, page, offnum, dir);
     475             : 
     476         748 :             if (itemIndex != 0)
     477         604 :                 break;
     478             : 
     479             :             /*
     480             :              * Could not find any matching tuples in the current page, move to
     481             :              * the next page. Before leaving the current page, deal with any
     482             :              * killed items.
     483             :              */
     484         144 :             if (so->numKilled > 0)
     485           0 :                 _hash_kill_items(scan);
     486             : 
     487             :             /*
     488             :              * If this is a primary bucket page, hasho_prevblkno is not a real
     489             :              * block number.
     490             :              */
     491         144 :             if (so->currPos.buf == so->hashso_bucket_buf ||
     492           0 :                 so->currPos.buf == so->hashso_split_bucket_buf)
     493         144 :                 prev_blkno = InvalidBlockNumber;
     494             :             else
     495           0 :                 prev_blkno = opaque->hasho_prevblkno;
     496             : 
     497         144 :             _hash_readnext(scan, &buf, &page, &opaque);
     498         144 :             if (BufferIsValid(buf))
     499             :             {
     500           0 :                 so->currPos.buf = buf;
     501           0 :                 so->currPos.currPage = BufferGetBlockNumber(buf);
     502             :             }
     503             :             else
     504             :             {
     505             :                 /*
     506             :                  * Remember next and previous block numbers for scrollable
     507             :                  * cursors to know the start position and return false
     508             :                  * indicating that no more matching tuples were found. Also,
     509             :                  * don't reset currPage or lsn, because we expect
     510             :                  * _hash_kill_items to be called for the old page after this
     511             :                  * function returns.
     512             :                  */
     513         144 :                 so->currPos.prevPage = prev_blkno;
     514         144 :                 so->currPos.nextPage = InvalidBlockNumber;
     515         144 :                 so->currPos.buf = buf;
     516         144 :                 return false;
     517             :             }
     518             :         }
     519             : 
     520         604 :         so->currPos.firstItem = 0;
     521         604 :         so->currPos.lastItem = itemIndex - 1;
     522         604 :         so->currPos.itemIndex = 0;
     523             :     }
     524             :     else
     525             :     {
     526          84 :         BlockNumber next_blkno = InvalidBlockNumber;
     527             : 
     528             :         for (;;)
     529             :         {
     530             :             /* new page, locate starting position by binary search */
     531          84 :             offnum = _hash_binsearch_last(page, so->hashso_sk_hash);
     532             : 
     533          84 :             itemIndex = _hash_load_qualified_items(scan, page, offnum, dir);
     534             : 
     535          84 :             if (itemIndex != MaxIndexTuplesPerPage)
     536          84 :                 break;
     537             : 
     538             :             /*
     539             :              * Could not find any matching tuples in the current page, move to
     540             :              * the previous page. Before leaving the current page, deal with
     541             :              * any killed items.
     542             :              */
     543           0 :             if (so->numKilled > 0)
     544           0 :                 _hash_kill_items(scan);
     545             : 
     546           0 :             if (so->currPos.buf == so->hashso_bucket_buf ||
     547           0 :                 so->currPos.buf == so->hashso_split_bucket_buf)
     548           0 :                 next_blkno = opaque->hasho_nextblkno;
     549             : 
     550           0 :             _hash_readprev(scan, &buf, &page, &opaque);
     551           0 :             if (BufferIsValid(buf))
     552             :             {
     553           0 :                 so->currPos.buf = buf;
     554           0 :                 so->currPos.currPage = BufferGetBlockNumber(buf);
     555             :             }
     556             :             else
     557             :             {
     558             :                 /*
     559             :                  * Remember next and previous block numbers for scrollable
     560             :                  * cursors to know the start position and return false
     561             :                  * indicating that no more matching tuples were found. Also,
     562             :                  * don't reset currPage or lsn, because we expect
     563             :                  * _hash_kill_items to be called for the old page after this
     564             :                  * function returns.
     565             :                  */
     566           0 :                 so->currPos.prevPage = InvalidBlockNumber;
     567           0 :                 so->currPos.nextPage = next_blkno;
     568           0 :                 so->currPos.buf = buf;
     569           0 :                 return false;
     570             :             }
     571             :         }
     572             : 
     573          84 :         so->currPos.firstItem = itemIndex;
     574          84 :         so->currPos.lastItem = MaxIndexTuplesPerPage - 1;
     575          84 :         so->currPos.itemIndex = MaxIndexTuplesPerPage - 1;
     576             :     }
     577             : 
     578         688 :     if (so->currPos.buf == so->hashso_bucket_buf ||
     579         234 :         so->currPos.buf == so->hashso_split_bucket_buf)
     580             :     {
     581         454 :         so->currPos.prevPage = InvalidBlockNumber;
     582         454 :         so->currPos.nextPage = opaque->hasho_nextblkno;
     583         454 :         LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
     584             :     }
     585             :     else
     586             :     {
     587         234 :         so->currPos.prevPage = opaque->hasho_prevblkno;
     588         234 :         so->currPos.nextPage = opaque->hasho_nextblkno;
     589         234 :         _hash_relbuf(rel, so->currPos.buf);
     590         234 :         so->currPos.buf = InvalidBuffer;
     591             :     }
     592             : 
     593             :     Assert(so->currPos.firstItem <= so->currPos.lastItem);
     594         688 :     return true;
     595             : }
     596             : 
     597             : /*
     598             :  * Load all the qualified items from a current index page
     599             :  * into so->currPos. Helper function for _hash_readpage.
     600             :  */
     601             : static int
     602         832 : _hash_load_qualified_items(IndexScanDesc scan, Page page,
     603             :                            OffsetNumber offnum, ScanDirection dir)
     604             : {
     605         832 :     HashScanOpaque so = (HashScanOpaque) scan->opaque;
     606             :     IndexTuple  itup;
     607             :     int         itemIndex;
     608             :     OffsetNumber maxoff;
     609             : 
     610         832 :     maxoff = PageGetMaxOffsetNumber(page);
     611             : 
     612         832 :     if (ScanDirectionIsForward(dir))
     613             :     {
     614             :         /* load items[] in ascending order */
     615         748 :         itemIndex = 0;
     616             : 
     617       68918 :         while (offnum <= maxoff)
     618             :         {
     619             :             Assert(offnum >= FirstOffsetNumber);
     620       68496 :             itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
     621             : 
     622             :             /*
     623             :              * skip the tuples that are moved by split operation for the scan
     624             :              * that has started when split was in progress. Also, skip the
     625             :              * tuples that are marked as dead.
     626             :              */
     627       68496 :             if ((so->hashso_buc_populated && !so->hashso_buc_split &&
     628           0 :                  (itup->t_info & INDEX_MOVED_BY_SPLIT_MASK)) ||
     629       68496 :                 (scan->ignore_killed_tuples &&
     630       68496 :                  (ItemIdIsDead(PageGetItemId(page, offnum)))))
     631             :             {
     632           0 :                 offnum = OffsetNumberNext(offnum);  /* move forward */
     633           0 :                 continue;
     634             :             }
     635             : 
     636      136666 :             if (so->hashso_sk_hash == _hash_get_indextuple_hashkey(itup) &&
     637       68170 :                 _hash_checkqual(scan, itup))
     638             :             {
     639             :                 /* tuple is qualified, so remember it */
     640       68170 :                 _hash_saveitem(so, itemIndex, offnum, itup);
     641       68170 :                 itemIndex++;
     642             :             }
     643             :             else
     644             :             {
     645             :                 /*
     646             :                  * No more matching tuples exist in this page. so, exit while
     647             :                  * loop.
     648             :                  */
     649             :                 break;
     650             :             }
     651             : 
     652       68170 :             offnum = OffsetNumberNext(offnum);
     653             :         }
     654             : 
     655             :         Assert(itemIndex <= MaxIndexTuplesPerPage);
     656         748 :         return itemIndex;
     657             :     }
     658             :     else
     659             :     {
     660             :         /* load items[] in descending order */
     661          84 :         itemIndex = MaxIndexTuplesPerPage;
     662             : 
     663       33084 :         while (offnum >= FirstOffsetNumber)
     664             :         {
     665             :             Assert(offnum <= maxoff);
     666       33000 :             itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
     667             : 
     668             :             /*
     669             :              * skip the tuples that are moved by split operation for the scan
     670             :              * that has started when split was in progress. Also, skip the
     671             :              * tuples that are marked as dead.
     672             :              */
     673       33000 :             if ((so->hashso_buc_populated && !so->hashso_buc_split &&
     674           0 :                  (itup->t_info & INDEX_MOVED_BY_SPLIT_MASK)) ||
     675       33000 :                 (scan->ignore_killed_tuples &&
     676       33000 :                  (ItemIdIsDead(PageGetItemId(page, offnum)))))
     677             :             {
     678           0 :                 offnum = OffsetNumberPrev(offnum);  /* move back */
     679           0 :                 continue;
     680             :             }
     681             : 
     682       66000 :             if (so->hashso_sk_hash == _hash_get_indextuple_hashkey(itup) &&
     683       33000 :                 _hash_checkqual(scan, itup))
     684             :             {
     685       33000 :                 itemIndex--;
     686             :                 /* tuple is qualified, so remember it */
     687       33000 :                 _hash_saveitem(so, itemIndex, offnum, itup);
     688             :             }
     689             :             else
     690             :             {
     691             :                 /*
     692             :                  * No more matching tuples exist in this page. so, exit while
     693             :                  * loop.
     694             :                  */
     695             :                 break;
     696             :             }
     697             : 
     698       33000 :             offnum = OffsetNumberPrev(offnum);
     699             :         }
     700             : 
     701             :         Assert(itemIndex >= 0);
     702          84 :         return itemIndex;
     703             :     }
     704             : }
     705             : 
     706             : /* Save an index item into so->currPos.items[itemIndex] */
     707             : static inline void
     708      101170 : _hash_saveitem(HashScanOpaque so, int itemIndex,
     709             :                OffsetNumber offnum, IndexTuple itup)
     710             : {
     711      101170 :     HashScanPosItem *currItem = &so->currPos.items[itemIndex];
     712             : 
     713      101170 :     currItem->heapTid = itup->t_tid;
     714      101170 :     currItem->indexOffset = offnum;
     715      101170 : }

Generated by: LCOV version 1.14