LCOV - code coverage report
Current view: top level - src/backend/access/gin - ginget.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 82.9 % 814 675
Test Date: 2026-01-26 10:56:24 Functions: 93.8 % 16 15
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 67.6 % 447 302

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * ginget.c
       4                 :             :  *        fetch tuples from a GIN 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/gin/ginget.c
      12                 :             :  *-------------------------------------------------------------------------
      13                 :             :  */
      14                 :             : 
      15                 :             : #include "postgres.h"
      16                 :             : 
      17                 :             : #include "access/gin_private.h"
      18                 :             : #include "access/relscan.h"
      19                 :             : #include "common/pg_prng.h"
      20                 :             : #include "miscadmin.h"
      21                 :             : #include "storage/predicate.h"
      22                 :             : #include "utils/datum.h"
      23                 :             : #include "utils/memutils.h"
      24                 :             : #include "utils/rel.h"
      25                 :             : 
      26                 :             : /* GUC parameter */
      27                 :             : int                     GinFuzzySearchLimit = 0;
      28                 :             : 
      29                 :             : typedef struct pendingPosition
      30                 :             : {
      31                 :             :         Buffer          pendingBuffer;
      32                 :             :         OffsetNumber firstOffset;
      33                 :             :         OffsetNumber lastOffset;
      34                 :             :         ItemPointerData item;
      35                 :             :         bool       *hasMatchKey;
      36                 :             : } pendingPosition;
      37                 :             : 
      38                 :             : 
      39                 :             : /*
      40                 :             :  * Goes to the next page if current offset is outside of bounds
      41                 :             :  */
      42                 :             : static bool
      43                 :       66549 : moveRightIfItNeeded(GinBtreeData *btree, GinBtreeStack *stack, Snapshot snapshot)
      44                 :             : {
      45                 :       66549 :         Page            page = BufferGetPage(stack->buffer);
      46                 :             : 
      47         [ +  + ]:       66549 :         if (stack->off > PageGetMaxOffsetNumber(page))
      48                 :             :         {
      49                 :             :                 /*
      50                 :             :                  * We scanned the whole page, so we should take right page
      51                 :             :                  */
      52         [ +  + ]:         597 :                 if (GinPageRightMost(page))
      53                 :          40 :                         return false;           /* no more pages */
      54                 :             : 
      55                 :         557 :                 stack->buffer = ginStepRight(stack->buffer, btree->index, GIN_SHARE);
      56                 :         557 :                 stack->blkno = BufferGetBlockNumber(stack->buffer);
      57                 :         557 :                 stack->off = FirstOffsetNumber;
      58                 :         557 :                 PredicateLockPage(btree->index, stack->blkno, snapshot);
      59                 :         557 :         }
      60                 :             : 
      61                 :       66509 :         return true;
      62                 :       66549 : }
      63                 :             : 
      64                 :             : /*
      65                 :             :  * Scan all pages of a posting tree and save all its heap ItemPointers
      66                 :             :  * in scanEntry->matchBitmap
      67                 :             :  */
      68                 :             : static void
      69                 :           2 : scanPostingTree(Relation index, GinScanEntry scanEntry,
      70                 :             :                                 BlockNumber rootPostingTree)
      71                 :             : {
      72                 :           2 :         GinBtreeData btree;
      73                 :           2 :         GinBtreeStack *stack;
      74                 :           2 :         Buffer          buffer;
      75                 :           2 :         Page            page;
      76                 :             : 
      77                 :             :         /* Descend to the leftmost leaf page */
      78                 :           2 :         stack = ginScanBeginPostingTree(&btree, index, rootPostingTree);
      79                 :           2 :         buffer = stack->buffer;
      80                 :             : 
      81                 :           2 :         IncrBufferRefCount(buffer); /* prevent unpin in freeGinBtreeStack */
      82                 :             : 
      83                 :           2 :         freeGinBtreeStack(stack);
      84                 :             : 
      85                 :             :         /*
      86                 :             :          * Loop iterates through all leaf pages of posting tree
      87                 :             :          */
      88                 :           5 :         for (;;)
      89                 :             :         {
      90                 :           5 :                 page = BufferGetPage(buffer);
      91         [ -  + ]:           5 :                 if ((GinPageGetOpaque(page)->flags & GIN_DELETED) == 0)
      92                 :             :                 {
      93                 :           5 :                         int                     n = GinDataLeafPageGetItemsToTbm(page, scanEntry->matchBitmap);
      94                 :             : 
      95                 :           5 :                         scanEntry->predictNumberResult += n;
      96                 :           5 :                 }
      97                 :             : 
      98         [ +  + ]:           5 :                 if (GinPageRightMost(page))
      99                 :           2 :                         break;                          /* no more pages */
     100                 :             : 
     101                 :           3 :                 buffer = ginStepRight(buffer, index, GIN_SHARE);
     102                 :             :         }
     103                 :             : 
     104                 :           2 :         UnlockReleaseBuffer(buffer);
     105                 :           2 : }
     106                 :             : 
     107                 :             : /*
     108                 :             :  * Collects TIDs into scanEntry->matchBitmap for all heap tuples that
     109                 :             :  * match the search entry.  This supports three different match modes:
     110                 :             :  *
     111                 :             :  * 1. Partial-match support: scan from current point until the
     112                 :             :  *        comparePartialFn says we're done.
     113                 :             :  * 2. SEARCH_MODE_ALL: scan from current point (which should be first
     114                 :             :  *        key for the current attnum) until we hit null items or end of attnum
     115                 :             :  * 3. SEARCH_MODE_EVERYTHING: scan from current point (which should be first
     116                 :             :  *        key for the current attnum) until we hit end of attnum
     117                 :             :  *
     118                 :             :  * Returns true if done, false if it's necessary to restart scan from scratch
     119                 :             :  */
     120                 :             : static bool
     121                 :          46 : collectMatchBitmap(GinBtreeData *btree, GinBtreeStack *stack,
     122                 :             :                                    GinScanEntry scanEntry, Snapshot snapshot)
     123                 :             : {
     124                 :          46 :         OffsetNumber attnum;
     125                 :          46 :         CompactAttribute *attr;
     126                 :             : 
     127                 :             :         /* Initialize empty bitmap result */
     128                 :          46 :         scanEntry->matchBitmap = tbm_create(work_mem * (Size) 1024, NULL);
     129                 :             : 
     130                 :             :         /* Null query cannot partial-match anything */
     131   [ +  +  -  + ]:          46 :         if (scanEntry->isPartialMatch &&
     132                 :           2 :                 scanEntry->queryCategory != GIN_CAT_NORM_KEY)
     133                 :           0 :                 return true;
     134                 :             : 
     135                 :             :         /* Locate tupdesc entry for key column (for attbyval/attlen data) */
     136                 :          46 :         attnum = scanEntry->attnum;
     137                 :          46 :         attr = TupleDescCompactAttr(btree->ginstate->origTupdesc, attnum - 1);
     138                 :             : 
     139                 :             :         /*
     140                 :             :          * Predicate lock entry leaf page, following pages will be locked by
     141                 :             :          * moveRightIfItNeeded()
     142                 :             :          */
     143                 :          92 :         PredicateLockPage(btree->index,
     144                 :          46 :                                           BufferGetBlockNumber(stack->buffer),
     145                 :          46 :                                           snapshot);
     146                 :             : 
     147                 :       66547 :         for (;;)
     148                 :             :         {
     149                 :       66547 :                 Page            page;
     150                 :       66547 :                 IndexTuple      itup;
     151                 :       66547 :                 Datum           idatum;
     152                 :       66547 :                 GinNullCategory icategory;
     153                 :             : 
     154                 :             :                 /*
     155                 :             :                  * stack->off points to the interested entry, buffer is already locked
     156                 :             :                  */
     157         [ +  + ]:       66547 :                 if (moveRightIfItNeeded(btree, stack, snapshot) == false)
     158                 :          40 :                         return true;
     159                 :             : 
     160                 :       66507 :                 page = BufferGetPage(stack->buffer);
     161                 :       66507 :                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off));
     162                 :             : 
     163                 :             :                 /*
     164                 :             :                  * If tuple stores another attribute then stop scan
     165                 :             :                  */
     166         [ -  + ]:       66507 :                 if (gintuple_get_attrnum(btree->ginstate, itup) != attnum)
     167                 :           0 :                         return true;
     168                 :             : 
     169                 :             :                 /* Safe to fetch attribute value */
     170                 :       66507 :                 idatum = gintuple_get_key(btree->ginstate, itup, &icategory);
     171                 :             : 
     172                 :             :                 /*
     173                 :             :                  * Check for appropriate scan stop conditions
     174                 :             :                  */
     175         [ +  + ]:       66507 :                 if (scanEntry->isPartialMatch)
     176                 :             :                 {
     177                 :          74 :                         int32           cmp;
     178                 :             : 
     179                 :             :                         /*
     180                 :             :                          * In partial match, stop scan at any null (including
     181                 :             :                          * placeholders); partial matches never match nulls
     182                 :             :                          */
     183         [ -  + ]:          74 :                         if (icategory != GIN_CAT_NORM_KEY)
     184                 :           0 :                                 return true;
     185                 :             : 
     186                 :             :                         /*----------
     187                 :             :                          * Check of partial match.
     188                 :             :                          * case cmp == 0 => match
     189                 :             :                          * case cmp > 0 => not match and finish scan
     190                 :             :                          * case cmp < 0 => not match and continue scan
     191                 :             :                          *----------
     192                 :             :                          */
     193                 :         148 :                         cmp = DatumGetInt32(FunctionCall4Coll(&btree->ginstate->comparePartialFn[attnum - 1],
     194                 :          74 :                                                                                                   btree->ginstate->supportCollation[attnum - 1],
     195                 :          74 :                                                                                                   scanEntry->queryKey,
     196                 :          74 :                                                                                                   idatum,
     197                 :          74 :                                                                                                   UInt16GetDatum(scanEntry->strategy),
     198                 :          74 :                                                                                                   PointerGetDatum(scanEntry->extra_data)));
     199                 :             : 
     200         [ +  + ]:          74 :                         if (cmp > 0)
     201                 :           2 :                                 return true;
     202         [ +  - ]:          72 :                         else if (cmp < 0)
     203                 :             :                         {
     204                 :           0 :                                 stack->off++;
     205                 :           0 :                                 continue;
     206                 :             :                         }
     207         [ +  + ]:          74 :                 }
     208         [ -  + ]:       66433 :                 else if (scanEntry->searchMode == GIN_SEARCH_MODE_ALL)
     209                 :             :                 {
     210                 :             :                         /*
     211                 :             :                          * In ALL mode, we are not interested in null items, so we can
     212                 :             :                          * stop if we get to a null-item placeholder (which will be the
     213                 :             :                          * last entry for a given attnum).  We do want to include NULL_KEY
     214                 :             :                          * and EMPTY_ITEM entries, though.
     215                 :             :                          */
     216         [ +  + ]:       66433 :                         if (icategory == GIN_CAT_NULL_ITEM)
     217                 :           4 :                                 return true;
     218                 :       66429 :                 }
     219                 :             : 
     220                 :             :                 /*
     221                 :             :                  * OK, we want to return the TIDs listed in this entry.
     222                 :             :                  */
     223         [ +  + ]:       66501 :                 if (GinIsPostingTree(itup))
     224                 :             :                 {
     225                 :           2 :                         BlockNumber rootPostingTree = GinGetPostingTree(itup);
     226                 :             : 
     227                 :             :                         /*
     228                 :             :                          * We should unlock current page (but not unpin) during tree scan
     229                 :             :                          * to prevent deadlock with vacuum processes.
     230                 :             :                          *
     231                 :             :                          * We save current entry value (idatum) to be able to re-find our
     232                 :             :                          * tuple after re-locking
     233                 :             :                          */
     234         [ -  + ]:           2 :                         if (icategory == GIN_CAT_NORM_KEY)
     235                 :           2 :                                 idatum = datumCopy(idatum, attr->attbyval, attr->attlen);
     236                 :             : 
     237                 :           2 :                         LockBuffer(stack->buffer, GIN_UNLOCK);
     238                 :             : 
     239                 :             :                         /*
     240                 :             :                          * Acquire predicate lock on the posting tree.  We already hold a
     241                 :             :                          * lock on the entry page, but insertions to the posting tree
     242                 :             :                          * don't check for conflicts on that level.
     243                 :             :                          */
     244                 :           2 :                         PredicateLockPage(btree->index, rootPostingTree, snapshot);
     245                 :             : 
     246                 :             :                         /* Collect all the TIDs in this entry's posting tree */
     247                 :           2 :                         scanPostingTree(btree->index, scanEntry, rootPostingTree);
     248                 :             : 
     249                 :             :                         /*
     250                 :             :                          * We lock again the entry page and while it was unlocked insert
     251                 :             :                          * might have occurred, so we need to re-find our position.
     252                 :             :                          */
     253                 :           2 :                         LockBuffer(stack->buffer, GIN_SHARE);
     254                 :           2 :                         page = BufferGetPage(stack->buffer);
     255         [ +  - ]:           2 :                         if (!GinPageIsLeaf(page))
     256                 :             :                         {
     257                 :             :                                 /*
     258                 :             :                                  * Root page becomes non-leaf while we unlock it. We will
     259                 :             :                                  * start again, this situation doesn't occur often - root can
     260                 :             :                                  * became a non-leaf only once per life of index.
     261                 :             :                                  */
     262                 :           0 :                                 return false;
     263                 :             :                         }
     264                 :             : 
     265                 :             :                         /* Search forward to re-find idatum */
     266                 :           2 :                         for (;;)
     267                 :             :                         {
     268         [ +  - ]:           2 :                                 if (moveRightIfItNeeded(btree, stack, snapshot) == false)
     269   [ #  #  #  # ]:           0 :                                         ereport(ERROR,
     270                 :             :                                                         (errcode(ERRCODE_INTERNAL_ERROR),
     271                 :             :                                                          errmsg("failed to re-find tuple within index \"%s\"",
     272                 :             :                                                                         RelationGetRelationName(btree->index))));
     273                 :             : 
     274                 :           2 :                                 page = BufferGetPage(stack->buffer);
     275                 :           2 :                                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off));
     276                 :             : 
     277         [ -  + ]:           2 :                                 if (gintuple_get_attrnum(btree->ginstate, itup) == attnum)
     278                 :             :                                 {
     279                 :           2 :                                         Datum           newDatum;
     280                 :           2 :                                         GinNullCategory newCategory;
     281                 :             : 
     282                 :           2 :                                         newDatum = gintuple_get_key(btree->ginstate, itup,
     283                 :             :                                                                                                 &newCategory);
     284                 :             : 
     285                 :           4 :                                         if (ginCompareEntries(btree->ginstate, attnum,
     286                 :           2 :                                                                                   newDatum, newCategory,
     287   [ -  +  -  + ]:           4 :                                                                                   idatum, icategory) == 0)
     288                 :           2 :                                                 break;  /* Found! */
     289      [ +  -  - ]:           2 :                                 }
     290                 :             : 
     291                 :           0 :                                 stack->off++;
     292                 :             :                         }
     293                 :             : 
     294   [ +  -  +  - ]:           2 :                         if (icategory == GIN_CAT_NORM_KEY && !attr->attbyval)
     295                 :           0 :                                 pfree(DatumGetPointer(idatum));
     296         [ -  + ]:           2 :                 }
     297                 :             :                 else
     298                 :             :                 {
     299                 :       66499 :                         ItemPointer ipd;
     300                 :       66499 :                         int                     nipd;
     301                 :             : 
     302                 :       66499 :                         ipd = ginReadTuple(btree->ginstate, scanEntry->attnum, itup, &nipd);
     303                 :       66499 :                         tbm_add_tuples(scanEntry->matchBitmap, ipd, nipd, false);
     304                 :       66499 :                         scanEntry->predictNumberResult += GinGetNPosting(itup);
     305                 :       66499 :                         pfree(ipd);
     306                 :       66499 :                 }
     307                 :             : 
     308                 :             :                 /*
     309                 :             :                  * Done with this entry, go to the next
     310                 :             :                  */
     311                 :       66501 :                 stack->off++;
     312      [ -  +  + ]:       66547 :         }
     313                 :          46 : }
     314                 :             : 
     315                 :             : /*
     316                 :             :  * Start* functions setup beginning state of searches: finds correct buffer and pins it.
     317                 :             :  */
     318                 :             : static void
     319                 :         289 : startScanEntry(GinState *ginstate, GinScanEntry entry, Snapshot snapshot)
     320                 :             : {
     321                 :         289 :         GinBtreeData btreeEntry;
     322                 :         289 :         GinBtreeStack *stackEntry;
     323                 :         289 :         Page            page;
     324                 :         289 :         bool            needUnlock;
     325                 :             : 
     326                 :             : restartScanEntry:
     327                 :         289 :         entry->buffer = InvalidBuffer;
     328                 :         289 :         ItemPointerSetMin(&entry->curItem);
     329                 :         289 :         entry->offset = InvalidOffsetNumber;
     330         [ +  - ]:         289 :         if (entry->list)
     331                 :           0 :                 pfree(entry->list);
     332                 :         289 :         entry->list = NULL;
     333                 :         289 :         entry->nlist = 0;
     334                 :         289 :         entry->matchBitmap = NULL;
     335                 :         289 :         entry->matchNtuples = -1;
     336                 :         289 :         entry->matchResult.blockno = InvalidBlockNumber;
     337                 :         289 :         entry->reduceResult = false;
     338                 :         289 :         entry->predictNumberResult = 0;
     339                 :             : 
     340                 :             :         /*
     341                 :             :          * we should find entry, and begin scan of posting tree or just store
     342                 :             :          * posting list in memory
     343                 :             :          */
     344                 :         578 :         ginPrepareEntryScan(&btreeEntry, entry->attnum,
     345                 :         289 :                                                 entry->queryKey, entry->queryCategory,
     346                 :         289 :                                                 ginstate);
     347                 :         289 :         stackEntry = ginFindLeafPage(&btreeEntry, true, false);
     348                 :         289 :         page = BufferGetPage(stackEntry->buffer);
     349                 :             : 
     350                 :             :         /* ginFindLeafPage() will have already checked snapshot age. */
     351                 :         289 :         needUnlock = true;
     352                 :             : 
     353                 :         289 :         entry->isFinished = true;
     354                 :             : 
     355   [ +  +  +  + ]:         289 :         if (entry->isPartialMatch ||
     356                 :         287 :                 entry->queryCategory == GIN_CAT_EMPTY_QUERY)
     357                 :             :         {
     358                 :             :                 /*
     359                 :             :                  * btreeEntry.findItem locates the first item >= given search key.
     360                 :             :                  * (For GIN_CAT_EMPTY_QUERY, it will find the leftmost index item
     361                 :             :                  * because of the way the GIN_CAT_EMPTY_QUERY category code is
     362                 :             :                  * assigned.)  We scan forward from there and collect all TIDs needed
     363                 :             :                  * for the entry type.
     364                 :             :                  */
     365                 :          46 :                 btreeEntry.findItem(&btreeEntry, stackEntry);
     366                 :          46 :                 if (collectMatchBitmap(&btreeEntry, stackEntry, entry, snapshot)
     367         [ +  - ]:          46 :                         == false)
     368                 :             :                 {
     369                 :             :                         /*
     370                 :             :                          * GIN tree was seriously restructured, so we will cleanup all
     371                 :             :                          * found data and rescan. See comments near 'return false' in
     372                 :             :                          * collectMatchBitmap()
     373                 :             :                          */
     374         [ #  # ]:           0 :                         if (entry->matchBitmap)
     375                 :             :                         {
     376         [ #  # ]:           0 :                                 if (entry->matchIterator)
     377                 :           0 :                                         tbm_end_private_iterate(entry->matchIterator);
     378                 :           0 :                                 entry->matchIterator = NULL;
     379                 :           0 :                                 tbm_free(entry->matchBitmap);
     380                 :           0 :                                 entry->matchBitmap = NULL;
     381                 :           0 :                         }
     382                 :           0 :                         LockBuffer(stackEntry->buffer, GIN_UNLOCK);
     383                 :           0 :                         freeGinBtreeStack(stackEntry);
     384                 :           0 :                         goto restartScanEntry;
     385                 :             :                 }
     386                 :             : 
     387   [ +  -  +  + ]:          46 :                 if (entry->matchBitmap && !tbm_is_empty(entry->matchBitmap))
     388                 :             :                 {
     389                 :          28 :                         entry->matchIterator =
     390                 :          28 :                                 tbm_begin_private_iterate(entry->matchBitmap);
     391                 :          28 :                         entry->isFinished = false;
     392                 :          28 :                 }
     393                 :          46 :         }
     394         [ +  + ]:         243 :         else if (btreeEntry.findItem(&btreeEntry, stackEntry))
     395                 :             :         {
     396                 :         202 :                 IndexTuple      itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stackEntry->off));
     397                 :             : 
     398         [ +  + ]:         202 :                 if (GinIsPostingTree(itup))
     399                 :             :                 {
     400                 :           7 :                         BlockNumber rootPostingTree = GinGetPostingTree(itup);
     401                 :           7 :                         GinBtreeStack *stack;
     402                 :           7 :                         Page            entrypage;
     403                 :           7 :                         ItemPointerData minItem;
     404                 :             : 
     405                 :             :                         /*
     406                 :             :                          * This is an equality scan, so lock the root of the posting tree.
     407                 :             :                          * It represents a lock on the exact key value, and covers all the
     408                 :             :                          * items in the posting tree.
     409                 :             :                          */
     410                 :           7 :                         PredicateLockPage(ginstate->index, rootPostingTree, snapshot);
     411                 :             : 
     412                 :             :                         /*
     413                 :             :                          * We should unlock entry page before touching posting tree to
     414                 :             :                          * prevent deadlocks with vacuum processes. Because entry is never
     415                 :             :                          * deleted from page and posting tree is never reduced to the
     416                 :             :                          * posting list, we can unlock page after getting BlockNumber of
     417                 :             :                          * root of posting tree.
     418                 :             :                          */
     419                 :           7 :                         LockBuffer(stackEntry->buffer, GIN_UNLOCK);
     420                 :           7 :                         needUnlock = false;
     421                 :             : 
     422                 :          14 :                         stack = ginScanBeginPostingTree(&entry->btree, ginstate->index,
     423                 :           7 :                                                                                         rootPostingTree);
     424                 :           7 :                         entry->buffer = stack->buffer;
     425                 :             : 
     426                 :             :                         /*
     427                 :             :                          * We keep buffer pinned because we need to prevent deletion of
     428                 :             :                          * page during scan. See GIN's vacuum implementation. RefCount is
     429                 :             :                          * increased to keep buffer pinned after freeGinBtreeStack() call.
     430                 :             :                          */
     431                 :           7 :                         IncrBufferRefCount(entry->buffer);
     432                 :             : 
     433                 :           7 :                         entrypage = BufferGetPage(entry->buffer);
     434                 :             : 
     435                 :             :                         /*
     436                 :             :                          * Load the first page into memory.
     437                 :             :                          */
     438                 :           7 :                         ItemPointerSetMin(&minItem);
     439                 :           7 :                         entry->list = GinDataLeafPageGetItems(entrypage, &entry->nlist, minItem);
     440                 :             : 
     441                 :           7 :                         entry->predictNumberResult = stack->predictNumber * entry->nlist;
     442                 :             : 
     443                 :           7 :                         LockBuffer(entry->buffer, GIN_UNLOCK);
     444                 :           7 :                         freeGinBtreeStack(stack);
     445                 :           7 :                         entry->isFinished = false;
     446                 :           7 :                 }
     447                 :             :                 else
     448                 :             :                 {
     449                 :             :                         /*
     450                 :             :                          * Lock the entry leaf page.  This is more coarse-grained than
     451                 :             :                          * necessary, because it will conflict with any insertions that
     452                 :             :                          * land on the same leaf page, not only the exact key we searched
     453                 :             :                          * for.  But locking an individual tuple would require updating
     454                 :             :                          * that lock whenever it moves because of insertions or vacuums,
     455                 :             :                          * which seems too complicated.
     456                 :             :                          */
     457                 :         390 :                         PredicateLockPage(ginstate->index,
     458                 :         195 :                                                           BufferGetBlockNumber(stackEntry->buffer),
     459                 :         195 :                                                           snapshot);
     460         [ +  + ]:         195 :                         if (GinGetNPosting(itup) > 0)
     461                 :             :                         {
     462                 :         388 :                                 entry->list = ginReadTuple(ginstate, entry->attnum, itup,
     463                 :         194 :                                                                                    &entry->nlist);
     464                 :         194 :                                 entry->predictNumberResult = entry->nlist;
     465                 :             : 
     466                 :         194 :                                 entry->isFinished = false;
     467                 :         194 :                         }
     468                 :             :                 }
     469                 :         202 :         }
     470                 :             :         else
     471                 :             :         {
     472                 :             :                 /*
     473                 :             :                  * No entry found.  Predicate lock the leaf page, to lock the place
     474                 :             :                  * where the entry would've been, had there been one.
     475                 :             :                  */
     476                 :          82 :                 PredicateLockPage(ginstate->index,
     477                 :          41 :                                                   BufferGetBlockNumber(stackEntry->buffer), snapshot);
     478                 :             :         }
     479                 :             : 
     480         [ +  + ]:         289 :         if (needUnlock)
     481                 :         282 :                 LockBuffer(stackEntry->buffer, GIN_UNLOCK);
     482                 :         289 :         freeGinBtreeStack(stackEntry);
     483                 :         289 : }
     484                 :             : 
     485                 :             : /*
     486                 :             :  * Comparison function for scan entry indexes. Sorts by predictNumberResult,
     487                 :             :  * least frequent items first.
     488                 :             :  */
     489                 :             : static int
     490                 :         132 : entryIndexByFrequencyCmp(const void *a1, const void *a2, void *arg)
     491                 :             : {
     492                 :         132 :         const GinScanKeyData *key = arg;
     493                 :         132 :         int                     i1 = *(const int *) a1;
     494                 :         132 :         int                     i2 = *(const int *) a2;
     495                 :         132 :         uint32          n1 = key->scanEntry[i1]->predictNumberResult;
     496                 :         132 :         uint32          n2 = key->scanEntry[i2]->predictNumberResult;
     497                 :             : 
     498         [ +  + ]:         132 :         if (n1 < n2)
     499                 :          42 :                 return -1;
     500         [ +  + ]:          90 :         else if (n1 == n2)
     501                 :          18 :                 return 0;
     502                 :             :         else
     503                 :          72 :                 return 1;
     504                 :         132 : }
     505                 :             : 
     506                 :             : static void
     507                 :         192 : startScanKey(GinState *ginstate, GinScanOpaque so, GinScanKey key)
     508                 :             : {
     509                 :         192 :         MemoryContext oldCtx = CurrentMemoryContext;
     510                 :         192 :         int                     i;
     511                 :         192 :         int                     j;
     512                 :         192 :         int                *entryIndexes;
     513                 :             : 
     514                 :         192 :         ItemPointerSetMin(&key->curItem);
     515                 :         192 :         key->curItemMatches = false;
     516                 :         192 :         key->recheckCurItem = false;
     517                 :         192 :         key->isFinished = false;
     518                 :             : 
     519                 :             :         /*
     520                 :             :          * Divide the entries into two distinct sets: required and additional.
     521                 :             :          * Additional entries are not enough for a match alone, without any items
     522                 :             :          * from the required set, but are needed by the consistent function to
     523                 :             :          * decide if an item matches. When scanning, we can skip over items from
     524                 :             :          * additional entries that have no corresponding matches in any of the
     525                 :             :          * required entries. That speeds up queries like "frequent & rare"
     526                 :             :          * considerably, if the frequent term can be put in the additional set.
     527                 :             :          *
     528                 :             :          * There can be many legal ways to divide them entries into these two
     529                 :             :          * sets. A conservative division is to just put everything in the required
     530                 :             :          * set, but the more you can put in the additional set, the more you can
     531                 :             :          * skip during the scan. To maximize skipping, we try to put as many
     532                 :             :          * frequent items as possible into additional, and less frequent ones into
     533                 :             :          * required. To do that, sort the entries by frequency
     534                 :             :          * (predictNumberResult), and put entries into the required set in that
     535                 :             :          * order, until the consistent function says that none of the remaining
     536                 :             :          * entries can form a match, without any items from the required set. The
     537                 :             :          * rest go to the additional set.
     538                 :             :          *
     539                 :             :          * Exclude-only scan keys are known to have no required entries.
     540                 :             :          */
     541         [ +  + ]:         192 :         if (key->excludeOnly)
     542                 :             :         {
     543                 :           5 :                 MemoryContextSwitchTo(so->keyCtx);
     544                 :             : 
     545                 :           5 :                 key->nrequired = 0;
     546                 :           5 :                 key->nadditional = key->nentries;
     547                 :           5 :                 key->additionalEntries = palloc(key->nadditional * sizeof(GinScanEntry));
     548         [ +  + ]:           6 :                 for (i = 0; i < key->nadditional; i++)
     549                 :           1 :                         key->additionalEntries[i] = key->scanEntry[i];
     550                 :           5 :         }
     551         [ +  + ]:         187 :         else if (key->nentries > 1)
     552                 :             :         {
     553                 :          67 :                 MemoryContextSwitchTo(so->tempCtx);
     554                 :             : 
     555                 :          67 :                 entryIndexes = palloc_array(int, key->nentries);
     556         [ +  + ]:         235 :                 for (i = 0; i < key->nentries; i++)
     557                 :         168 :                         entryIndexes[i] = i;
     558                 :         134 :                 qsort_arg(entryIndexes, key->nentries, sizeof(int),
     559                 :          67 :                                   entryIndexByFrequencyCmp, key);
     560                 :             : 
     561         [ +  + ]:         168 :                 for (i = 1; i < key->nentries; i++)
     562                 :         101 :                         key->entryRes[entryIndexes[i]] = GIN_MAYBE;
     563         [ +  + ]:         112 :                 for (i = 0; i < key->nentries - 1; i++)
     564                 :             :                 {
     565                 :             :                         /* Pass all entries <= i as FALSE, and the rest as MAYBE */
     566                 :          94 :                         key->entryRes[entryIndexes[i]] = GIN_FALSE;
     567                 :             : 
     568         [ +  + ]:          94 :                         if (key->triConsistentFn(key) == GIN_FALSE)
     569                 :          49 :                                 break;
     570                 :             : 
     571                 :             :                         /* Make this loop interruptible in case there are many keys */
     572         [ +  - ]:          45 :                         CHECK_FOR_INTERRUPTS();
     573                 :          45 :                 }
     574                 :             :                 /* i is now the last required entry. */
     575                 :             : 
     576                 :          67 :                 MemoryContextSwitchTo(so->keyCtx);
     577                 :             : 
     578                 :          67 :                 key->nrequired = i + 1;
     579                 :          67 :                 key->nadditional = key->nentries - key->nrequired;
     580                 :          67 :                 key->requiredEntries = palloc(key->nrequired * sizeof(GinScanEntry));
     581                 :          67 :                 key->additionalEntries = palloc(key->nadditional * sizeof(GinScanEntry));
     582                 :             : 
     583                 :          67 :                 j = 0;
     584         [ +  + ]:         179 :                 for (i = 0; i < key->nrequired; i++)
     585                 :         112 :                         key->requiredEntries[i] = key->scanEntry[entryIndexes[j++]];
     586         [ +  + ]:         123 :                 for (i = 0; i < key->nadditional; i++)
     587                 :          56 :                         key->additionalEntries[i] = key->scanEntry[entryIndexes[j++]];
     588                 :             : 
     589                 :             :                 /* clean up after consistentFn calls (also frees entryIndexes) */
     590                 :          67 :                 MemoryContextReset(so->tempCtx);
     591                 :          67 :         }
     592                 :             :         else
     593                 :             :         {
     594                 :         120 :                 MemoryContextSwitchTo(so->keyCtx);
     595                 :             : 
     596                 :         120 :                 key->nrequired = 1;
     597                 :         120 :                 key->nadditional = 0;
     598                 :         120 :                 key->requiredEntries = palloc(1 * sizeof(GinScanEntry));
     599                 :         120 :                 key->requiredEntries[0] = key->scanEntry[0];
     600                 :             :         }
     601                 :         192 :         MemoryContextSwitchTo(oldCtx);
     602                 :         192 : }
     603                 :             : 
     604                 :             : static void
     605                 :         172 : startScan(IndexScanDesc scan)
     606                 :             : {
     607                 :         172 :         GinScanOpaque so = (GinScanOpaque) scan->opaque;
     608                 :         172 :         GinState   *ginstate = &so->ginstate;
     609                 :         172 :         uint32          i;
     610                 :             : 
     611         [ +  + ]:         461 :         for (i = 0; i < so->totalentries; i++)
     612                 :         289 :                 startScanEntry(ginstate, so->entries[i], scan->xs_snapshot);
     613                 :             : 
     614         [ +  + ]:         172 :         if (GinFuzzySearchLimit > 0)
     615                 :             :         {
     616                 :             :                 /*
     617                 :             :                  * If all of keys more than threshold we will try to reduce result, we
     618                 :             :                  * hope (and only hope, for intersection operation of array our
     619                 :             :                  * supposition isn't true), that total result will not more than
     620                 :             :                  * minimal predictNumberResult.
     621                 :             :                  */
     622                 :           1 :                 bool            reduce = true;
     623                 :             : 
     624         [ +  + ]:           2 :                 for (i = 0; i < so->totalentries; i++)
     625                 :             :                 {
     626         [ -  + ]:           1 :                         if (so->entries[i]->predictNumberResult <= so->totalentries * GinFuzzySearchLimit)
     627                 :             :                         {
     628                 :           0 :                                 reduce = false;
     629                 :           0 :                                 break;
     630                 :             :                         }
     631                 :           1 :                 }
     632         [ -  + ]:           1 :                 if (reduce)
     633                 :             :                 {
     634         [ +  + ]:           2 :                         for (i = 0; i < so->totalentries; i++)
     635                 :             :                         {
     636                 :           1 :                                 so->entries[i]->predictNumberResult /= so->totalentries;
     637                 :           1 :                                 so->entries[i]->reduceResult = true;
     638                 :           1 :                         }
     639                 :           1 :                 }
     640                 :           1 :         }
     641                 :             : 
     642                 :             :         /*
     643                 :             :          * Now that we have the estimates for the entry frequencies, finish
     644                 :             :          * initializing the scan keys.
     645                 :             :          */
     646         [ +  + ]:         364 :         for (i = 0; i < so->nkeys; i++)
     647                 :         192 :                 startScanKey(ginstate, so, so->keys + i);
     648                 :         172 : }
     649                 :             : 
     650                 :             : /*
     651                 :             :  * Load the next batch of item pointers from a posting tree.
     652                 :             :  *
     653                 :             :  * Note that we copy the page into GinScanEntry->list array and unlock it, but
     654                 :             :  * keep it pinned to prevent interference with vacuum.
     655                 :             :  */
     656                 :             : static void
     657                 :          13 : entryLoadMoreItems(GinState *ginstate, GinScanEntry entry,
     658                 :             :                                    ItemPointerData advancePast)
     659                 :             : {
     660                 :          13 :         Page            page;
     661                 :          13 :         int                     i;
     662                 :          13 :         bool            stepright;
     663                 :             : 
     664         [ -  + ]:          13 :         if (!BufferIsValid(entry->buffer))
     665                 :             :         {
     666                 :           0 :                 entry->isFinished = true;
     667                 :           0 :                 return;
     668                 :             :         }
     669                 :             : 
     670                 :             :         /*
     671                 :             :          * We have two strategies for finding the correct page: step right from
     672                 :             :          * the current page, or descend the tree again from the root. If
     673                 :             :          * advancePast equals the current item, the next matching item should be
     674                 :             :          * on the next page, so we step right. Otherwise, descend from root.
     675                 :             :          */
     676         [ +  + ]:          13 :         if (ginCompareItemPointers(&entry->curItem, &advancePast) == 0)
     677                 :             :         {
     678                 :          12 :                 stepright = true;
     679                 :          12 :                 LockBuffer(entry->buffer, GIN_SHARE);
     680                 :          12 :         }
     681                 :             :         else
     682                 :             :         {
     683                 :           1 :                 GinBtreeStack *stack;
     684                 :             : 
     685                 :           1 :                 ReleaseBuffer(entry->buffer);
     686                 :             : 
     687                 :             :                 /*
     688                 :             :                  * Set the search key, and find the correct leaf page.
     689                 :             :                  */
     690   [ -  +  #  # ]:           1 :                 if (ItemPointerIsLossyPage(&advancePast))
     691                 :             :                 {
     692                 :           0 :                         ItemPointerSet(&entry->btree.itemptr,
     693                 :           0 :                                                    GinItemPointerGetBlockNumber(&advancePast) + 1,
     694                 :             :                                                    FirstOffsetNumber);
     695                 :           0 :                 }
     696                 :             :                 else
     697                 :             :                 {
     698                 :           2 :                         ItemPointerSet(&entry->btree.itemptr,
     699                 :           1 :                                                    GinItemPointerGetBlockNumber(&advancePast),
     700                 :           1 :                                                    OffsetNumberNext(GinItemPointerGetOffsetNumber(&advancePast)));
     701                 :             :                 }
     702                 :           1 :                 entry->btree.fullScan = false;
     703                 :           1 :                 stack = ginFindLeafPage(&entry->btree, true, false);
     704                 :             : 
     705                 :             :                 /* we don't need the stack, just the buffer. */
     706                 :           1 :                 entry->buffer = stack->buffer;
     707                 :           1 :                 IncrBufferRefCount(entry->buffer);
     708                 :           1 :                 freeGinBtreeStack(stack);
     709                 :           1 :                 stepright = false;
     710                 :           1 :         }
     711                 :             : 
     712   [ -  +  -  + ]:          13 :         elog(DEBUG2, "entryLoadMoreItems, %u/%u, skip: %d",
     713                 :             :                  GinItemPointerGetBlockNumber(&advancePast),
     714                 :             :                  GinItemPointerGetOffsetNumber(&advancePast),
     715                 :             :                  !stepright);
     716                 :             : 
     717                 :          13 :         page = BufferGetPage(entry->buffer);
     718                 :          14 :         for (;;)
     719                 :             :         {
     720                 :          14 :                 entry->offset = InvalidOffsetNumber;
     721         [ +  + ]:          14 :                 if (entry->list)
     722                 :             :                 {
     723                 :          12 :                         pfree(entry->list);
     724                 :          12 :                         entry->list = NULL;
     725                 :          12 :                         entry->nlist = 0;
     726                 :          12 :                 }
     727                 :             : 
     728         [ +  + ]:          14 :                 if (stepright)
     729                 :             :                 {
     730                 :             :                         /*
     731                 :             :                          * We've processed all the entries on this page. If it was the
     732                 :             :                          * last page in the tree, we're done.
     733                 :             :                          */
     734         [ +  + ]:          13 :                         if (GinPageRightMost(page))
     735                 :             :                         {
     736                 :           1 :                                 UnlockReleaseBuffer(entry->buffer);
     737                 :           1 :                                 entry->buffer = InvalidBuffer;
     738                 :           1 :                                 entry->isFinished = true;
     739                 :           1 :                                 return;
     740                 :             :                         }
     741                 :             : 
     742                 :             :                         /*
     743                 :             :                          * Step to next page, following the right link. then find the
     744                 :             :                          * first ItemPointer greater than advancePast.
     745                 :             :                          */
     746                 :          24 :                         entry->buffer = ginStepRight(entry->buffer,
     747                 :          12 :                                                                                  ginstate->index,
     748                 :             :                                                                                  GIN_SHARE);
     749                 :          12 :                         page = BufferGetPage(entry->buffer);
     750                 :          12 :                 }
     751                 :          13 :                 stepright = true;
     752                 :             : 
     753         [ -  + ]:          13 :                 if (GinPageGetOpaque(page)->flags & GIN_DELETED)
     754                 :           0 :                         continue;                       /* page was deleted by concurrent vacuum */
     755                 :             : 
     756                 :             :                 /*
     757                 :             :                  * The first item > advancePast might not be on this page, but
     758                 :             :                  * somewhere to the right, if the page was split, or a non-match from
     759                 :             :                  * another key in the query allowed us to skip some items from this
     760                 :             :                  * entry. Keep following the right-links until we re-find the correct
     761                 :             :                  * page.
     762                 :             :                  */
     763   [ +  +  +  - ]:          13 :                 if (!GinPageRightMost(page) &&
     764                 :           6 :                         ginCompareItemPointers(&advancePast, GinDataPageGetRightBound(page)) >= 0)
     765                 :             :                 {
     766                 :             :                         /*
     767                 :             :                          * the item we're looking is > the right bound of the page, so it
     768                 :             :                          * can't be on this page.
     769                 :             :                          */
     770                 :           0 :                         continue;
     771                 :             :                 }
     772                 :             : 
     773                 :          13 :                 entry->list = GinDataLeafPageGetItems(page, &entry->nlist, advancePast);
     774                 :             : 
     775         [ +  + ]:         317 :                 for (i = 0; i < entry->nlist; i++)
     776                 :             :                 {
     777         [ +  + ]:         316 :                         if (ginCompareItemPointers(&advancePast, &entry->list[i]) < 0)
     778                 :             :                         {
     779                 :          12 :                                 entry->offset = i;
     780                 :             : 
     781         [ +  + ]:          12 :                                 if (GinPageRightMost(page))
     782                 :             :                                 {
     783                 :             :                                         /* after processing the copied items, we're done. */
     784                 :           6 :                                         UnlockReleaseBuffer(entry->buffer);
     785                 :           6 :                                         entry->buffer = InvalidBuffer;
     786                 :           6 :                                 }
     787                 :             :                                 else
     788                 :           6 :                                         LockBuffer(entry->buffer, GIN_UNLOCK);
     789                 :          12 :                                 return;
     790                 :             :                         }
     791                 :         304 :                 }
     792                 :             :         }
     793                 :          13 : }
     794                 :             : 
     795                 :             : #define gin_rand() pg_prng_double(&pg_global_prng_state)
     796                 :             : #define dropItem(e) ( gin_rand() > ((double)GinFuzzySearchLimit)/((double)((e)->predictNumberResult)) )
     797                 :             : 
     798                 :             : /*
     799                 :             :  * Sets entry->curItem to next heap item pointer > advancePast, for one entry
     800                 :             :  * of one scan key, or sets entry->isFinished to true if there are no more.
     801                 :             :  *
     802                 :             :  * Item pointers are returned in ascending order.
     803                 :             :  *
     804                 :             :  * Note: this can return a "lossy page" item pointer, indicating that the
     805                 :             :  * entry potentially matches all items on that heap page.  However, it is
     806                 :             :  * not allowed to return both a lossy page pointer and exact (regular)
     807                 :             :  * item pointers for the same page.  (Doing so would break the key-combination
     808                 :             :  * logic in keyGetItem and scanGetItem; see comment in scanGetItem.)  In the
     809                 :             :  * current implementation this is guaranteed by the behavior of tidbitmaps.
     810                 :             :  */
     811                 :             : static void
     812                 :      207847 : entryGetItem(GinState *ginstate, GinScanEntry entry,
     813                 :             :                          ItemPointerData advancePast)
     814                 :             : {
     815         [ +  - ]:      207847 :         Assert(!entry->isFinished);
     816                 :             : 
     817   [ +  +  +  - ]:      207847 :         Assert(!ItemPointerIsValid(&entry->curItem) ||
     818                 :             :                    ginCompareItemPointers(&entry->curItem, &advancePast) <= 0);
     819                 :             : 
     820         [ +  + ]:      207847 :         if (entry->matchBitmap)
     821                 :             :         {
     822                 :             :                 /* A bitmap result */
     823                 :      118184 :                 BlockNumber advancePastBlk = GinItemPointerGetBlockNumber(&advancePast);
     824                 :      118184 :                 OffsetNumber advancePastOff = GinItemPointerGetOffsetNumber(&advancePast);
     825                 :             : 
     826                 :      118184 :                 for (;;)
     827                 :             :                 {
     828                 :             :                         /*
     829                 :             :                          * If we've exhausted all items on this block, move to next block
     830                 :             :                          * in the bitmap. tbm_private_iterate() sets matchResult.blockno
     831                 :             :                          * to InvalidBlockNumber when the bitmap is exhausted.
     832                 :             :                          */
     833   [ +  +  +  + ]:      237844 :                         while ((!BlockNumberIsValid(entry->matchResult.blockno)) ||
     834         [ +  - ]:       40128 :                                    (!entry->matchResult.lossy &&
     835                 :       40128 :                                         entry->offset >= entry->matchNtuples) ||
     836         [ +  + ]:       39404 :                                    entry->matchResult.blockno < advancePastBlk ||
     837   [ -  +  #  # ]:       39404 :                                    (ItemPointerIsLossyPage(&advancePast) &&
     838                 :           0 :                                         entry->matchResult.blockno == advancePastBlk))
     839                 :             :                         {
     840         [ +  + ]:         752 :                                 if (!tbm_private_iterate(entry->matchIterator, &entry->matchResult))
     841                 :             :                                 {
     842         [ -  + ]:          28 :                                         Assert(!BlockNumberIsValid(entry->matchResult.blockno));
     843                 :          28 :                                         ItemPointerSetInvalid(&entry->curItem);
     844                 :          28 :                                         tbm_end_private_iterate(entry->matchIterator);
     845                 :          28 :                                         entry->matchIterator = NULL;
     846                 :          28 :                                         entry->isFinished = true;
     847                 :          28 :                                         break;
     848                 :             :                                 }
     849                 :             : 
     850                 :             :                                 /* Exact pages need their tuple offsets extracted. */
     851         [ -  + ]:         724 :                                 if (!entry->matchResult.lossy)
     852                 :        1448 :                                         entry->matchNtuples = tbm_extract_page_tuple(&entry->matchResult,
     853                 :         724 :                                                                                                                                  entry->matchOffsets,
     854                 :             :                                                                                                                                  TBM_MAX_TUPLES_PER_PAGE);
     855                 :             : 
     856                 :             :                                 /*
     857                 :             :                                  * Reset counter to the beginning of entry->matchResult. Note:
     858                 :             :                                  * entry->offset is still greater than matchResult.ntuples if
     859                 :             :                                  * matchResult is lossy.  So, on next call we will get next
     860                 :             :                                  * result from TIDBitmap.
     861                 :             :                                  */
     862                 :         724 :                                 entry->offset = 0;
     863                 :             :                         }
     864         [ +  + ]:       39432 :                         if (entry->isFinished)
     865                 :          28 :                                 break;
     866                 :             : 
     867                 :             :                         /*
     868                 :             :                          * We're now on the first page after advancePast which has any
     869                 :             :                          * items on it. If it's a lossy result, return that.
     870                 :             :                          */
     871         [ +  - ]:       39404 :                         if (entry->matchResult.lossy)
     872                 :             :                         {
     873                 :           0 :                                 ItemPointerSetLossyPage(&entry->curItem,
     874                 :             :                                                                                 entry->matchResult.blockno);
     875                 :             : 
     876                 :             :                                 /*
     877                 :             :                                  * We might as well fall out of the loop; we could not
     878                 :             :                                  * estimate number of results on this page to support correct
     879                 :             :                                  * reducing of result even if it's enabled.
     880                 :             :                                  */
     881                 :           0 :                                 break;
     882                 :             :                         }
     883                 :             : 
     884                 :             :                         /*
     885                 :             :                          * Not a lossy page. If tuple offsets were extracted,
     886                 :             :                          * entry->matchNtuples must be > -1
     887                 :             :                          */
     888         [ -  + ]:       39404 :                         Assert(entry->matchNtuples > -1);
     889                 :             : 
     890                 :             :                         /* Skip over any offsets <= advancePast, and return that. */
     891         [ +  + ]:       39404 :                         if (entry->matchResult.blockno == advancePastBlk)
     892                 :             :                         {
     893         [ +  - ]:       38708 :                                 Assert(entry->matchNtuples > 0);
     894                 :             : 
     895                 :             :                                 /*
     896                 :             :                                  * First, do a quick check against the last offset on the
     897                 :             :                                  * page. If that's > advancePast, so are all the other
     898                 :             :                                  * offsets, so just go back to the top to get the next page.
     899                 :             :                                  */
     900         [ -  + ]:       38708 :                                 if (entry->matchOffsets[entry->matchNtuples - 1] <= advancePastOff)
     901                 :             :                                 {
     902                 :           0 :                                         entry->offset = entry->matchNtuples;
     903                 :           0 :                                         continue;
     904                 :             :                                 }
     905                 :             : 
     906                 :             :                                 /* Otherwise scan to find the first item > advancePast */
     907         [ -  + ]:       38708 :                                 while (entry->matchOffsets[entry->offset] <= advancePastOff)
     908                 :           0 :                                         entry->offset++;
     909                 :       38708 :                         }
     910                 :             : 
     911                 :       78808 :                         ItemPointerSet(&entry->curItem,
     912                 :       39404 :                                                    entry->matchResult.blockno,
     913                 :       39404 :                                                    entry->matchOffsets[entry->offset]);
     914                 :       39404 :                         entry->offset++;
     915                 :             : 
     916                 :             :                         /* Done unless we need to reduce the result */
     917   [ -  +  #  # ]:       39404 :                         if (!entry->reduceResult || !dropItem(entry))
     918                 :       39404 :                                 break;
     919                 :             :                 }
     920                 :       39432 :         }
     921         [ +  + ]:       89663 :         else if (!BufferIsValid(entry->buffer))
     922                 :             :         {
     923                 :             :                 /*
     924                 :             :                  * A posting list from an entry tuple, or the last page of a posting
     925                 :             :                  * tree.
     926                 :             :                  */
     927                 :       36391 :                 for (;;)
     928                 :             :                 {
     929         [ +  + ]:       42861 :                         if (entry->offset >= entry->nlist)
     930                 :             :                         {
     931                 :         150 :                                 ItemPointerSetInvalid(&entry->curItem);
     932                 :         150 :                                 entry->isFinished = true;
     933                 :         150 :                                 break;
     934                 :             :                         }
     935                 :             : 
     936                 :       42711 :                         entry->curItem = entry->list[entry->offset++];
     937                 :             : 
     938                 :             :                         /* If we're not past advancePast, keep scanning */
     939         [ +  + ]:       42711 :                         if (ginCompareItemPointers(&entry->curItem, &advancePast) <= 0)
     940                 :        6470 :                                 continue;
     941                 :             : 
     942                 :             :                         /* Done unless we need to reduce the result */
     943   [ +  +  +  + ]:       36241 :                         if (!entry->reduceResult || !dropItem(entry))
     944                 :       32840 :                                 break;
     945                 :             :                 }
     946                 :       32990 :         }
     947                 :             :         else
     948                 :             :         {
     949                 :             :                 /* A posting tree */
     950                 :       76475 :                 for (;;)
     951                 :             :                 {
     952                 :             :                         /* If we've processed the current batch, load more items */
     953         [ +  + ]:       84338 :                         while (entry->offset >= entry->nlist)
     954                 :             :                         {
     955                 :          13 :                                 entryLoadMoreItems(ginstate, entry, advancePast);
     956                 :             : 
     957         [ +  + ]:          13 :                                 if (entry->isFinished)
     958                 :             :                                 {
     959                 :           1 :                                         ItemPointerSetInvalid(&entry->curItem);
     960                 :           1 :                                         return;
     961                 :             :                                 }
     962                 :             :                         }
     963                 :             : 
     964                 :       84325 :                         entry->curItem = entry->list[entry->offset++];
     965                 :             : 
     966                 :             :                         /* If we're not past advancePast, keep scanning */
     967         [ +  + ]:       84325 :                         if (ginCompareItemPointers(&entry->curItem, &advancePast) <= 0)
     968                 :        7851 :                                 continue;
     969                 :             : 
     970                 :             :                         /* Done unless we need to reduce the result */
     971   [ +  +  +  + ]:       76474 :                         if (!entry->reduceResult || !dropItem(entry))
     972                 :       56672 :                                 break;
     973                 :             : 
     974                 :             :                         /*
     975                 :             :                          * Advance advancePast (so that entryLoadMoreItems will load the
     976                 :             :                          * right data), and keep scanning
     977                 :             :                          */
     978                 :       19802 :                         advancePast = entry->curItem;
     979                 :             :                 }
     980                 :             :         }
     981                 :      129095 : }
     982                 :             : 
     983                 :             : /*
     984                 :             :  * Identify the "current" item among the input entry streams for this scan key
     985                 :             :  * that is greater than advancePast, and test whether it passes the scan key
     986                 :             :  * qual condition.
     987                 :             :  *
     988                 :             :  * The current item is the smallest curItem among the inputs.  key->curItem
     989                 :             :  * is set to that value.  key->curItemMatches is set to indicate whether that
     990                 :             :  * TID passes the consistentFn test.  If so, key->recheckCurItem is set true
     991                 :             :  * iff recheck is needed for this item pointer (including the case where the
     992                 :             :  * item pointer is a lossy page pointer).
     993                 :             :  *
     994                 :             :  * If all entry streams are exhausted, sets key->isFinished to true.
     995                 :             :  *
     996                 :             :  * Item pointers must be returned in ascending order.
     997                 :             :  *
     998                 :             :  * Note: this can return a "lossy page" item pointer, indicating that the
     999                 :             :  * key potentially matches all items on that heap page.  However, it is
    1000                 :             :  * not allowed to return both a lossy page pointer and exact (regular)
    1001                 :             :  * item pointers for the same page.  (Doing so would break the key-combination
    1002                 :             :  * logic in scanGetItem.)
    1003                 :             :  */
    1004                 :             : static void
    1005                 :      126257 : keyGetItem(GinState *ginstate, MemoryContext tempCtx, GinScanKey key,
    1006                 :             :                    ItemPointerData advancePast)
    1007                 :             : {
    1008                 :      126257 :         ItemPointerData minItem;
    1009                 :      126257 :         ItemPointerData curPageLossy;
    1010                 :      126257 :         uint32          i;
    1011                 :      126257 :         bool            haveLossyEntry;
    1012                 :      126257 :         GinScanEntry entry;
    1013                 :      126257 :         GinTernaryValue res;
    1014                 :      126257 :         MemoryContext oldCtx;
    1015                 :      126257 :         bool            allFinished;
    1016                 :             : 
    1017         [ +  - ]:      126257 :         Assert(!key->isFinished);
    1018                 :             : 
    1019                 :             :         /*
    1020                 :             :          * We might have already tested this item; if so, no need to repeat work.
    1021                 :             :          * (Note: the ">" case can happen, if advancePast is exact but we
    1022                 :             :          * previously had to set curItem to a lossy-page pointer.)
    1023                 :             :          */
    1024         [ +  + ]:      126257 :         if (ginCompareItemPointers(&key->curItem, &advancePast) > 0)
    1025                 :           2 :                 return;
    1026                 :             : 
    1027                 :             :         /*
    1028                 :             :          * Find the minimum item > advancePast among the active entry streams.
    1029                 :             :          *
    1030                 :             :          * Note: a lossy-page entry is encoded by a ItemPointer with max value for
    1031                 :             :          * offset (0xffff), so that it will sort after any exact entries for the
    1032                 :             :          * same page.  So we'll prefer to return exact pointers not lossy
    1033                 :             :          * pointers, which is good.
    1034                 :             :          */
    1035                 :      126255 :         ItemPointerSetMax(&minItem);
    1036                 :      126255 :         allFinished = true;
    1037         [ +  + ]:      261049 :         for (i = 0; i < key->nrequired; i++)
    1038                 :             :         {
    1039                 :      134794 :                 entry = key->requiredEntries[i];
    1040                 :             : 
    1041         [ +  + ]:      134794 :                 if (entry->isFinished)
    1042                 :        1148 :                         continue;
    1043                 :             : 
    1044                 :             :                 /*
    1045                 :             :                  * Advance this stream if necessary.
    1046                 :             :                  *
    1047                 :             :                  * In particular, since entry->curItem was initialized with
    1048                 :             :                  * ItemPointerSetMin, this ensures we fetch the first item for each
    1049                 :             :                  * entry on the first call.
    1050                 :             :                  */
    1051         [ +  + ]:      133646 :                 if (ginCompareItemPointers(&entry->curItem, &advancePast) <= 0)
    1052                 :             :                 {
    1053                 :      127462 :                         entryGetItem(ginstate, entry, advancePast);
    1054         [ +  + ]:      127462 :                         if (entry->isFinished)
    1055                 :         174 :                                 continue;
    1056                 :      127288 :                 }
    1057                 :             : 
    1058                 :      133472 :                 allFinished = false;
    1059         [ +  + ]:      133472 :                 if (ginCompareItemPointers(&entry->curItem, &minItem) < 0)
    1060                 :      130376 :                         minItem = entry->curItem;
    1061                 :      133472 :         }
    1062                 :             : 
    1063   [ +  +  +  + ]:      126255 :         if (allFinished && !key->excludeOnly)
    1064                 :             :         {
    1065                 :             :                 /* all entries are finished */
    1066                 :         172 :                 key->isFinished = true;
    1067                 :         172 :                 return;
    1068                 :             :         }
    1069                 :             : 
    1070         [ +  + ]:      126083 :         if (!key->excludeOnly)
    1071                 :             :         {
    1072                 :             :                 /*
    1073                 :             :                  * For a normal scan key, we now know there are no matches < minItem.
    1074                 :             :                  *
    1075                 :             :                  * If minItem is lossy, it means that there were no exact items on the
    1076                 :             :                  * page among requiredEntries, because lossy pointers sort after exact
    1077                 :             :                  * items. However, there might be exact items for the same page among
    1078                 :             :                  * additionalEntries, so we mustn't advance past them.
    1079                 :             :                  */
    1080   [ -  +  #  # ]:      126006 :                 if (ItemPointerIsLossyPage(&minItem))
    1081                 :             :                 {
    1082   [ #  #  #  # ]:           0 :                         if (GinItemPointerGetBlockNumber(&advancePast) <
    1083                 :           0 :                                 GinItemPointerGetBlockNumber(&minItem))
    1084                 :             :                         {
    1085                 :           0 :                                 ItemPointerSet(&advancePast,
    1086                 :           0 :                                                            GinItemPointerGetBlockNumber(&minItem),
    1087                 :             :                                                            InvalidOffsetNumber);
    1088                 :           0 :                         }
    1089                 :           0 :                 }
    1090                 :             :                 else
    1091                 :             :                 {
    1092         [ +  - ]:      126006 :                         Assert(GinItemPointerGetOffsetNumber(&minItem) > 0);
    1093                 :      126006 :                         ItemPointerSet(&advancePast,
    1094                 :      126006 :                                                    GinItemPointerGetBlockNumber(&minItem),
    1095                 :      126006 :                                                    OffsetNumberPrev(GinItemPointerGetOffsetNumber(&minItem)));
    1096                 :             :                 }
    1097                 :      126006 :         }
    1098                 :             :         else
    1099                 :             :         {
    1100                 :             :                 /*
    1101                 :             :                  * excludeOnly scan keys don't have any entries that are necessarily
    1102                 :             :                  * present in matching items.  So, we consider the item just after
    1103                 :             :                  * advancePast.
    1104                 :             :                  */
    1105         [ +  - ]:          77 :                 Assert(key->nrequired == 0);
    1106                 :          77 :                 ItemPointerSet(&minItem,
    1107                 :          77 :                                            GinItemPointerGetBlockNumber(&advancePast),
    1108                 :          77 :                                            OffsetNumberNext(GinItemPointerGetOffsetNumber(&advancePast)));
    1109                 :             :         }
    1110                 :             : 
    1111                 :             :         /*
    1112                 :             :          * We might not have loaded all the entry streams for this TID yet. We
    1113                 :             :          * could call the consistent function, passing MAYBE for those entries, to
    1114                 :             :          * see if it can decide if this TID matches based on the information we
    1115                 :             :          * have. But if the consistent-function is expensive, and cannot in fact
    1116                 :             :          * decide with partial information, that could be a big loss. So, load all
    1117                 :             :          * the additional entries, before calling the consistent function.
    1118                 :             :          */
    1119         [ +  + ]:      128514 :         for (i = 0; i < key->nadditional; i++)
    1120                 :             :         {
    1121                 :        2431 :                 entry = key->additionalEntries[i];
    1122                 :             : 
    1123         [ +  + ]:        2431 :                 if (entry->isFinished)
    1124                 :           1 :                         continue;
    1125                 :             : 
    1126         [ +  + ]:        2430 :                 if (ginCompareItemPointers(&entry->curItem, &advancePast) <= 0)
    1127                 :             :                 {
    1128                 :        1633 :                         entryGetItem(ginstate, entry, advancePast);
    1129         [ +  + ]:        1633 :                         if (entry->isFinished)
    1130                 :           5 :                                 continue;
    1131                 :        1628 :                 }
    1132                 :             : 
    1133                 :             :                 /*
    1134                 :             :                  * Normally, none of the items in additionalEntries can have a curItem
    1135                 :             :                  * larger than minItem. But if minItem is a lossy page, then there
    1136                 :             :                  * might be exact items on the same page among additionalEntries.
    1137                 :             :                  */
    1138         [ +  - ]:        2425 :                 if (ginCompareItemPointers(&entry->curItem, &minItem) < 0)
    1139                 :             :                 {
    1140         [ #  # ]:           0 :                         Assert(ItemPointerIsLossyPage(&minItem));
    1141                 :           0 :                         minItem = entry->curItem;
    1142                 :           0 :                 }
    1143                 :        2425 :         }
    1144                 :             : 
    1145                 :             :         /*
    1146                 :             :          * Ok, we've advanced all the entries up to minItem now. Set key->curItem,
    1147                 :             :          * and perform consistentFn test.
    1148                 :             :          *
    1149                 :             :          * Lossy-page entries pose a problem, since we don't know the correct
    1150                 :             :          * entryRes state to pass to the consistentFn, and we also don't know what
    1151                 :             :          * its combining logic will be (could be AND, OR, or even NOT). If the
    1152                 :             :          * logic is OR then the consistentFn might succeed for all items in the
    1153                 :             :          * lossy page even when none of the other entries match.
    1154                 :             :          *
    1155                 :             :          * Our strategy is to call the tri-state consistent function, with the
    1156                 :             :          * lossy-page entries set to MAYBE, and all the other entries FALSE. If it
    1157                 :             :          * returns FALSE, none of the lossy items alone are enough for a match, so
    1158                 :             :          * we don't need to return a lossy-page pointer. Otherwise, return a
    1159                 :             :          * lossy-page pointer to indicate that the whole heap page must be
    1160                 :             :          * checked.  (On subsequent calls, we'll do nothing until minItem is past
    1161                 :             :          * the page altogether, thus ensuring that we never return both regular
    1162                 :             :          * and lossy pointers for the same page.)
    1163                 :             :          *
    1164                 :             :          * An exception is that it doesn't matter what we pass for lossy pointers
    1165                 :             :          * in "hidden" entries, because the consistentFn's result can't depend on
    1166                 :             :          * them. We could pass them as MAYBE as well, but if we're using the
    1167                 :             :          * "shim" implementation of a tri-state consistent function (see
    1168                 :             :          * ginlogic.c), it's better to pass as few MAYBEs as possible. So pass
    1169                 :             :          * them as true.
    1170                 :             :          *
    1171                 :             :          * Note that only lossy-page entries pointing to the current item's page
    1172                 :             :          * should trigger this processing; we might have future lossy pages in the
    1173                 :             :          * entry array, but they aren't relevant yet.
    1174                 :             :          */
    1175                 :      126083 :         key->curItem = minItem;
    1176                 :      126083 :         ItemPointerSetLossyPage(&curPageLossy,
    1177                 :             :                                                         GinItemPointerGetBlockNumber(&key->curItem));
    1178                 :      126083 :         haveLossyEntry = false;
    1179         [ +  + ]:      263091 :         for (i = 0; i < key->nentries; i++)
    1180                 :             :         {
    1181                 :      137008 :                 entry = key->scanEntry[i];
    1182   [ +  +  +  - ]:      137008 :                 if (entry->isFinished == false &&
    1183                 :      135897 :                         ginCompareItemPointers(&entry->curItem, &curPageLossy) == 0)
    1184                 :             :                 {
    1185         [ #  # ]:           0 :                         if (i < key->nuserentries)
    1186                 :           0 :                                 key->entryRes[i] = GIN_MAYBE;
    1187                 :             :                         else
    1188                 :           0 :                                 key->entryRes[i] = GIN_TRUE;
    1189                 :           0 :                         haveLossyEntry = true;
    1190                 :           0 :                 }
    1191                 :             :                 else
    1192                 :      137008 :                         key->entryRes[i] = GIN_FALSE;
    1193                 :      137008 :         }
    1194                 :             : 
    1195                 :             :         /* prepare for calling consistentFn in temp context */
    1196                 :      126083 :         oldCtx = MemoryContextSwitchTo(tempCtx);
    1197                 :             : 
    1198         [ +  - ]:      126083 :         if (haveLossyEntry)
    1199                 :             :         {
    1200                 :             :                 /* Have lossy-page entries, so see if whole page matches */
    1201                 :           0 :                 res = key->triConsistentFn(key);
    1202                 :             : 
    1203   [ #  #  #  # ]:           0 :                 if (res == GIN_TRUE || res == GIN_MAYBE)
    1204                 :             :                 {
    1205                 :             :                         /* Yes, so clean up ... */
    1206                 :           0 :                         MemoryContextSwitchTo(oldCtx);
    1207                 :           0 :                         MemoryContextReset(tempCtx);
    1208                 :             : 
    1209                 :             :                         /* and return lossy pointer for whole page */
    1210                 :           0 :                         key->curItem = curPageLossy;
    1211                 :           0 :                         key->curItemMatches = true;
    1212                 :           0 :                         key->recheckCurItem = true;
    1213                 :           0 :                         return;
    1214                 :             :                 }
    1215                 :           0 :         }
    1216                 :             : 
    1217                 :             :         /*
    1218                 :             :          * At this point we know that we don't need to return a lossy whole-page
    1219                 :             :          * pointer, but we might have matches for individual exact item pointers,
    1220                 :             :          * possibly in combination with a lossy pointer. Pass lossy pointers as
    1221                 :             :          * MAYBE to the ternary consistent function, to let it decide if this
    1222                 :             :          * tuple satisfies the overall key, even though we don't know if the lossy
    1223                 :             :          * entries match.
    1224                 :             :          *
    1225                 :             :          * Prepare entryRes array to be passed to consistentFn.
    1226                 :             :          */
    1227         [ +  + ]:      263091 :         for (i = 0; i < key->nentries; i++)
    1228                 :             :         {
    1229                 :      137008 :                 entry = key->scanEntry[i];
    1230         [ +  + ]:      137008 :                 if (entry->isFinished)
    1231                 :        1111 :                         key->entryRes[i] = GIN_FALSE;
    1232                 :             : #if 0
    1233                 :             : 
    1234                 :             :                 /*
    1235                 :             :                  * This case can't currently happen, because we loaded all the entries
    1236                 :             :                  * for this item earlier.
    1237                 :             :                  */
    1238                 :             :                 else if (ginCompareItemPointers(&entry->curItem, &advancePast) <= 0)
    1239                 :             :                         key->entryRes[i] = GIN_MAYBE;
    1240                 :             : #endif
    1241         [ -  + ]:      135897 :                 else if (ginCompareItemPointers(&entry->curItem, &curPageLossy) == 0)
    1242                 :           0 :                         key->entryRes[i] = GIN_MAYBE;
    1243         [ +  + ]:      135897 :                 else if (ginCompareItemPointers(&entry->curItem, &minItem) == 0)
    1244                 :      127954 :                         key->entryRes[i] = GIN_TRUE;
    1245                 :             :                 else
    1246                 :        7943 :                         key->entryRes[i] = GIN_FALSE;
    1247                 :      137008 :         }
    1248                 :             : 
    1249                 :      126083 :         res = key->triConsistentFn(key);
    1250                 :             : 
    1251   [ +  -  +  + ]:      126083 :         switch (res)
    1252                 :             :         {
    1253                 :             :                 case GIN_TRUE:
    1254                 :      109017 :                         key->curItemMatches = true;
    1255                 :             :                         /* triConsistentFn set recheckCurItem */
    1256                 :      109017 :                         break;
    1257                 :             : 
    1258                 :             :                 case GIN_FALSE:
    1259                 :        1431 :                         key->curItemMatches = false;
    1260                 :        1431 :                         break;
    1261                 :             : 
    1262                 :             :                 case GIN_MAYBE:
    1263                 :       15635 :                         key->curItemMatches = true;
    1264                 :       15635 :                         key->recheckCurItem = true;
    1265                 :       15635 :                         break;
    1266                 :             : 
    1267                 :             :                 default:
    1268                 :             : 
    1269                 :             :                         /*
    1270                 :             :                          * the 'default' case shouldn't happen, but if the consistent
    1271                 :             :                          * function returns something bogus, this is the safe result
    1272                 :             :                          */
    1273                 :           0 :                         key->curItemMatches = true;
    1274                 :           0 :                         key->recheckCurItem = true;
    1275                 :           0 :                         break;
    1276                 :             :         }
    1277                 :             : 
    1278                 :             :         /*
    1279                 :             :          * We have a tuple, and we know if it matches or not. If it's a non-match,
    1280                 :             :          * we could continue to find the next matching tuple, but let's break out
    1281                 :             :          * and give scanGetItem a chance to advance the other keys. They might be
    1282                 :             :          * able to skip past to a much higher TID, allowing us to save work.
    1283                 :             :          */
    1284                 :             : 
    1285                 :             :         /* clean up after consistentFn calls */
    1286                 :      126083 :         MemoryContextSwitchTo(oldCtx);
    1287                 :      126083 :         MemoryContextReset(tempCtx);
    1288         [ -  + ]:      126257 : }
    1289                 :             : 
    1290                 :             : /*
    1291                 :             :  * Get next heap item pointer (after advancePast) from scan.
    1292                 :             :  * Returns true if anything found.
    1293                 :             :  * On success, *item and *recheck are set.
    1294                 :             :  *
    1295                 :             :  * Note: this is very nearly the same logic as in keyGetItem(), except
    1296                 :             :  * that we know the keys are to be combined with AND logic, whereas in
    1297                 :             :  * keyGetItem() the combination logic is known only to the consistentFn.
    1298                 :             :  */
    1299                 :             : static bool
    1300                 :      124731 : scanGetItem(IndexScanDesc scan, ItemPointerData advancePast,
    1301                 :             :                         ItemPointerData *item, bool *recheck)
    1302                 :             : {
    1303                 :      124731 :         GinScanOpaque so = (GinScanOpaque) scan->opaque;
    1304                 :      124731 :         uint32          i;
    1305                 :      124731 :         bool            match;
    1306                 :             : 
    1307                 :             :         /*----------
    1308                 :             :          * Advance the scan keys in lock-step, until we find an item that matches
    1309                 :             :          * all the keys. If any key reports isFinished, meaning its subset of the
    1310                 :             :          * entries is exhausted, we can stop.  Otherwise, set *item to the next
    1311                 :             :          * matching item.
    1312                 :             :          *
    1313                 :             :          * This logic works only if a keyGetItem stream can never contain both
    1314                 :             :          * exact and lossy pointers for the same page.  Else we could have a
    1315                 :             :          * case like
    1316                 :             :          *
    1317                 :             :          *              stream 1                stream 2
    1318                 :             :          *              ...             ...
    1319                 :             :          *              42/6                    42/7
    1320                 :             :          *              50/1                    42/0xffff
    1321                 :             :          *              ...             ...
    1322                 :             :          *
    1323                 :             :          * We would conclude that 42/6 is not a match and advance stream 1,
    1324                 :             :          * thus never detecting the match to the lossy pointer in stream 2.
    1325                 :             :          * (keyGetItem has a similar problem versus entryGetItem.)
    1326                 :             :          *----------
    1327                 :             :          */
    1328                 :      124731 :         do
    1329                 :             :         {
    1330         [ +  - ]:      126170 :                 CHECK_FOR_INTERRUPTS();
    1331                 :             : 
    1332                 :      126170 :                 ItemPointerSetMin(item);
    1333                 :      126170 :                 match = true;
    1334   [ +  +  +  + ]:      250824 :                 for (i = 0; i < so->nkeys && match; i++)
    1335                 :             :                 {
    1336                 :      126257 :                         GinScanKey      key = so->keys + i;
    1337                 :             : 
    1338                 :             :                         /*
    1339                 :             :                          * If we're considering a lossy page, skip excludeOnly keys. They
    1340                 :             :                          * can't exclude the whole page anyway.
    1341                 :             :                          */
    1342   [ -  +  #  #  :      126257 :                         if (ItemPointerIsLossyPage(item) && key->excludeOnly)
                   #  # ]
    1343                 :             :                         {
    1344                 :             :                                 /*
    1345                 :             :                                  * ginNewScanKey() should never mark the first key as
    1346                 :             :                                  * excludeOnly.
    1347                 :             :                                  */
    1348         [ #  # ]:           0 :                                 Assert(i > 0);
    1349                 :           0 :                                 continue;
    1350                 :             :                         }
    1351                 :             : 
    1352                 :             :                         /* Fetch the next item for this key that is > advancePast. */
    1353                 :      126257 :                         keyGetItem(&so->ginstate, so->tempCtx, key, advancePast);
    1354                 :             : 
    1355         [ +  + ]:      126257 :                         if (key->isFinished)
    1356                 :         172 :                                 return false;
    1357                 :             : 
    1358                 :             :                         /*
    1359                 :             :                          * If it's not a match, we can immediately conclude that nothing
    1360                 :             :                          * <= this item matches, without checking the rest of the keys.
    1361                 :             :                          */
    1362         [ +  + ]:      126085 :                         if (!key->curItemMatches)
    1363                 :             :                         {
    1364                 :        1431 :                                 advancePast = key->curItem;
    1365                 :        1431 :                                 match = false;
    1366                 :        1431 :                                 break;
    1367                 :             :                         }
    1368                 :             : 
    1369                 :             :                         /*
    1370                 :             :                          * It's a match. We can conclude that nothing < matches, so the
    1371                 :             :                          * other key streams can skip to this item.
    1372                 :             :                          *
    1373                 :             :                          * Beware of lossy pointers, though; from a lossy pointer, we can
    1374                 :             :                          * only conclude that nothing smaller than this *block* matches.
    1375                 :             :                          */
    1376   [ -  +  #  # ]:      124654 :                         if (ItemPointerIsLossyPage(&key->curItem))
    1377                 :             :                         {
    1378   [ #  #  #  # ]:           0 :                                 if (GinItemPointerGetBlockNumber(&advancePast) <
    1379                 :           0 :                                         GinItemPointerGetBlockNumber(&key->curItem))
    1380                 :             :                                 {
    1381                 :           0 :                                         ItemPointerSet(&advancePast,
    1382                 :           0 :                                                                    GinItemPointerGetBlockNumber(&key->curItem),
    1383                 :             :                                                                    InvalidOffsetNumber);
    1384                 :           0 :                                 }
    1385                 :           0 :                         }
    1386                 :             :                         else
    1387                 :             :                         {
    1388         [ -  + ]:      124654 :                                 Assert(GinItemPointerGetOffsetNumber(&key->curItem) > 0);
    1389                 :      124654 :                                 ItemPointerSet(&advancePast,
    1390                 :      124654 :                                                            GinItemPointerGetBlockNumber(&key->curItem),
    1391                 :      124654 :                                                            OffsetNumberPrev(GinItemPointerGetOffsetNumber(&key->curItem)));
    1392                 :             :                         }
    1393                 :             : 
    1394                 :             :                         /*
    1395                 :             :                          * If this is the first key, remember this location as a potential
    1396                 :             :                          * match, and proceed to check the rest of the keys.
    1397                 :             :                          *
    1398                 :             :                          * Otherwise, check if this is the same item that we checked the
    1399                 :             :                          * previous keys for (or a lossy pointer for the same page). If
    1400                 :             :                          * not, loop back to check the previous keys for this item (we
    1401                 :             :                          * will check this key again too, but keyGetItem returns quickly
    1402                 :             :                          * for that)
    1403                 :             :                          */
    1404         [ +  + ]:      124654 :                         if (i == 0)
    1405                 :             :                         {
    1406                 :      124584 :                                 *item = key->curItem;
    1407                 :      124584 :                         }
    1408                 :             :                         else
    1409                 :             :                         {
    1410   [ -  +  #  # ]:          70 :                                 if (ItemPointerIsLossyPage(&key->curItem) ||
    1411         [ -  + ]:          70 :                                         ItemPointerIsLossyPage(item))
    1412                 :             :                                 {
    1413         [ #  # ]:           0 :                                         Assert(GinItemPointerGetBlockNumber(&key->curItem) >= GinItemPointerGetBlockNumber(item));
    1414                 :           0 :                                         match = (GinItemPointerGetBlockNumber(&key->curItem) ==
    1415                 :           0 :                                                          GinItemPointerGetBlockNumber(item));
    1416                 :           0 :                                 }
    1417                 :             :                                 else
    1418                 :             :                                 {
    1419         [ +  - ]:          70 :                                         Assert(ginCompareItemPointers(&key->curItem, item) >= 0);
    1420                 :          70 :                                         match = (ginCompareItemPointers(&key->curItem, item) == 0);
    1421                 :             :                                 }
    1422                 :             :                         }
    1423   [ +  -  +  + ]:      126257 :                 }
    1424         [ +  + ]:      125998 :         } while (!match);
    1425                 :             : 
    1426   [ -  +  #  # ]:      124559 :         Assert(!ItemPointerIsMin(item));
    1427                 :             : 
    1428                 :             :         /*
    1429                 :             :          * Now *item contains the first ItemPointer after previous result that
    1430                 :             :          * satisfied all the keys for that exact TID, or a lossy reference to the
    1431                 :             :          * same page.
    1432                 :             :          *
    1433                 :             :          * We must return recheck = true if any of the keys are marked recheck.
    1434                 :             :          */
    1435                 :      124559 :         *recheck = false;
    1436         [ +  + ]:      230920 :         for (i = 0; i < so->nkeys; i++)
    1437                 :             :         {
    1438                 :      124621 :                 GinScanKey      key = so->keys + i;
    1439                 :             : 
    1440         [ +  + ]:      124621 :                 if (key->recheckCurItem)
    1441                 :             :                 {
    1442                 :       18260 :                         *recheck = true;
    1443                 :       18260 :                         break;
    1444                 :             :                 }
    1445      [ -  +  + ]:      124621 :         }
    1446                 :             : 
    1447                 :      124559 :         return true;
    1448                 :      124731 : }
    1449                 :             : 
    1450                 :             : 
    1451                 :             : /*
    1452                 :             :  * Functions for scanning the pending list
    1453                 :             :  */
    1454                 :             : 
    1455                 :             : 
    1456                 :             : /*
    1457                 :             :  * Get ItemPointer of next heap row to be checked from pending list.
    1458                 :             :  * Returns false if there are no more. On pages with several heap rows
    1459                 :             :  * it returns each row separately, on page with part of heap row returns
    1460                 :             :  * per page data.  pos->firstOffset and pos->lastOffset are set to identify
    1461                 :             :  * the range of pending-list tuples belonging to this heap row.
    1462                 :             :  *
    1463                 :             :  * The pendingBuffer is presumed pinned and share-locked on entry, and is
    1464                 :             :  * pinned and share-locked on success exit.  On failure exit it's released.
    1465                 :             :  */
    1466                 :             : static bool
    1467                 :         267 : scanGetCandidate(IndexScanDesc scan, pendingPosition *pos)
    1468                 :             : {
    1469                 :         267 :         OffsetNumber maxoff;
    1470                 :         267 :         Page            page;
    1471                 :         267 :         IndexTuple      itup;
    1472                 :             : 
    1473                 :         267 :         ItemPointerSetInvalid(&pos->item);
    1474                 :         267 :         for (;;)
    1475                 :             :         {
    1476                 :         267 :                 page = BufferGetPage(pos->pendingBuffer);
    1477                 :             : 
    1478                 :         267 :                 maxoff = PageGetMaxOffsetNumber(page);
    1479         [ +  + ]:         267 :                 if (pos->firstOffset > maxoff)
    1480                 :             :                 {
    1481                 :          27 :                         BlockNumber blkno = GinPageGetOpaque(page)->rightlink;
    1482                 :             : 
    1483         [ -  + ]:          27 :                         if (blkno == InvalidBlockNumber)
    1484                 :             :                         {
    1485                 :          27 :                                 UnlockReleaseBuffer(pos->pendingBuffer);
    1486                 :          27 :                                 pos->pendingBuffer = InvalidBuffer;
    1487                 :             : 
    1488                 :          27 :                                 return false;
    1489                 :             :                         }
    1490                 :             :                         else
    1491                 :             :                         {
    1492                 :             :                                 /*
    1493                 :             :                                  * Here we must prevent deletion of next page by insertcleanup
    1494                 :             :                                  * process, which may be trying to obtain exclusive lock on
    1495                 :             :                                  * current page.  So, we lock next page before releasing the
    1496                 :             :                                  * current one
    1497                 :             :                                  */
    1498                 :           0 :                                 Buffer          tmpbuf = ReadBuffer(scan->indexRelation, blkno);
    1499                 :             : 
    1500                 :           0 :                                 LockBuffer(tmpbuf, GIN_SHARE);
    1501                 :           0 :                                 UnlockReleaseBuffer(pos->pendingBuffer);
    1502                 :             : 
    1503                 :           0 :                                 pos->pendingBuffer = tmpbuf;
    1504                 :           0 :                                 pos->firstOffset = FirstOffsetNumber;
    1505                 :           0 :                         }
    1506         [ +  - ]:          27 :                 }
    1507                 :             :                 else
    1508                 :             :                 {
    1509                 :         240 :                         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, pos->firstOffset));
    1510                 :         240 :                         pos->item = itup->t_tid;
    1511         [ +  - ]:         240 :                         if (GinPageHasFullRow(page))
    1512                 :             :                         {
    1513                 :             :                                 /*
    1514                 :             :                                  * find itempointer to the next row
    1515                 :             :                                  */
    1516         [ +  + ]:         547 :                                 for (pos->lastOffset = pos->firstOffset + 1; pos->lastOffset <= maxoff; pos->lastOffset++)
    1517                 :             :                                 {
    1518                 :         520 :                                         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, pos->lastOffset));
    1519         [ +  + ]:         520 :                                         if (!ItemPointerEquals(&pos->item, &itup->t_tid))
    1520                 :         213 :                                                 break;
    1521                 :         307 :                                 }
    1522                 :         240 :                         }
    1523                 :             :                         else
    1524                 :             :                         {
    1525                 :             :                                 /*
    1526                 :             :                                  * All itempointers are the same on this page
    1527                 :             :                                  */
    1528                 :           0 :                                 pos->lastOffset = maxoff + 1;
    1529                 :             :                         }
    1530                 :             : 
    1531                 :             :                         /*
    1532                 :             :                          * Now pos->firstOffset points to the first tuple of current heap
    1533                 :             :                          * row, pos->lastOffset points to the first tuple of next heap row
    1534                 :             :                          * (or to the end of page)
    1535                 :             :                          */
    1536                 :         240 :                         break;
    1537                 :             :                 }
    1538                 :             :         }
    1539                 :             : 
    1540                 :         240 :         return true;
    1541                 :         267 : }
    1542                 :             : 
    1543                 :             : /*
    1544                 :             :  * Scan pending-list page from current tuple (off) up till the first of:
    1545                 :             :  * - match is found (then returns true)
    1546                 :             :  * - no later match is possible
    1547                 :             :  * - tuple's attribute number is not equal to entry's attrnum
    1548                 :             :  * - reach end of page
    1549                 :             :  *
    1550                 :             :  * datum[]/category[]/datumExtracted[] arrays are used to cache the results
    1551                 :             :  * of gintuple_get_key() on the current page.
    1552                 :             :  */
    1553                 :             : static bool
    1554                 :           0 : matchPartialInPendingList(GinState *ginstate, Page page,
    1555                 :             :                                                   OffsetNumber off, OffsetNumber maxoff,
    1556                 :             :                                                   GinScanEntry entry,
    1557                 :             :                                                   Datum *datum, GinNullCategory *category,
    1558                 :             :                                                   bool *datumExtracted)
    1559                 :             : {
    1560                 :           0 :         IndexTuple      itup;
    1561                 :           0 :         int32           cmp;
    1562                 :             : 
    1563                 :             :         /* Partial match to a null is not possible */
    1564         [ #  # ]:           0 :         if (entry->queryCategory != GIN_CAT_NORM_KEY)
    1565                 :           0 :                 return false;
    1566                 :             : 
    1567         [ #  # ]:           0 :         while (off < maxoff)
    1568                 :             :         {
    1569                 :           0 :                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off));
    1570                 :             : 
    1571         [ #  # ]:           0 :                 if (gintuple_get_attrnum(ginstate, itup) != entry->attnum)
    1572                 :           0 :                         return false;
    1573                 :             : 
    1574         [ #  # ]:           0 :                 if (datumExtracted[off - 1] == false)
    1575                 :             :                 {
    1576                 :           0 :                         datum[off - 1] = gintuple_get_key(ginstate, itup,
    1577                 :           0 :                                                                                           &category[off - 1]);
    1578                 :           0 :                         datumExtracted[off - 1] = true;
    1579                 :           0 :                 }
    1580                 :             : 
    1581                 :             :                 /* Once we hit nulls, no further match is possible */
    1582         [ #  # ]:           0 :                 if (category[off - 1] != GIN_CAT_NORM_KEY)
    1583                 :           0 :                         return false;
    1584                 :             : 
    1585                 :             :                 /*----------
    1586                 :             :                  * Check partial match.
    1587                 :             :                  * case cmp == 0 => match
    1588                 :             :                  * case cmp > 0 => not match and end scan (no later match possible)
    1589                 :             :                  * case cmp < 0 => not match and continue scan
    1590                 :             :                  *----------
    1591                 :             :                  */
    1592                 :           0 :                 cmp = DatumGetInt32(FunctionCall4Coll(&ginstate->comparePartialFn[entry->attnum - 1],
    1593                 :           0 :                                                                                           ginstate->supportCollation[entry->attnum - 1],
    1594                 :           0 :                                                                                           entry->queryKey,
    1595                 :           0 :                                                                                           datum[off - 1],
    1596                 :           0 :                                                                                           UInt16GetDatum(entry->strategy),
    1597                 :           0 :                                                                                           PointerGetDatum(entry->extra_data)));
    1598         [ #  # ]:           0 :                 if (cmp == 0)
    1599                 :           0 :                         return true;
    1600         [ #  # ]:           0 :                 else if (cmp > 0)
    1601                 :           0 :                         return false;
    1602                 :             : 
    1603                 :           0 :                 off++;
    1604                 :             :         }
    1605                 :             : 
    1606                 :           0 :         return false;
    1607                 :           0 : }
    1608                 :             : 
    1609                 :             : /*
    1610                 :             :  * Set up the entryRes array for each key by looking at
    1611                 :             :  * every entry for current heap row in pending list.
    1612                 :             :  *
    1613                 :             :  * Returns true if each scan key has at least one entryRes match.
    1614                 :             :  * This corresponds to the situations where the normal index search will
    1615                 :             :  * try to apply the key's consistentFn.  (A tuple not meeting that requirement
    1616                 :             :  * cannot be returned by the normal search since no entry stream will
    1617                 :             :  * source its TID.)
    1618                 :             :  *
    1619                 :             :  * The pendingBuffer is presumed pinned and share-locked on entry.
    1620                 :             :  */
    1621                 :             : static bool
    1622                 :         240 : collectMatchesForHeapRow(IndexScanDesc scan, pendingPosition *pos)
    1623                 :             : {
    1624                 :         240 :         GinScanOpaque so = (GinScanOpaque) scan->opaque;
    1625                 :         240 :         OffsetNumber attrnum;
    1626                 :         240 :         Page            page;
    1627                 :         240 :         IndexTuple      itup;
    1628                 :         240 :         int                     i,
    1629                 :             :                                 j;
    1630                 :             : 
    1631                 :             :         /*
    1632                 :             :          * Reset all entryRes and hasMatchKey flags
    1633                 :             :          */
    1634         [ +  + ]:         650 :         for (i = 0; i < so->nkeys; i++)
    1635                 :             :         {
    1636                 :         410 :                 GinScanKey      key = so->keys + i;
    1637                 :             : 
    1638                 :         410 :                 memset(key->entryRes, GIN_FALSE, key->nentries);
    1639                 :         410 :         }
    1640                 :         240 :         memset(pos->hasMatchKey, false, so->nkeys);
    1641                 :             : 
    1642                 :             :         /*
    1643                 :             :          * Outer loop iterates over multiple pending-list pages when a single heap
    1644                 :             :          * row has entries spanning those pages.
    1645                 :             :          */
    1646                 :         240 :         for (;;)
    1647                 :             :         {
    1648                 :         240 :                 Datum           datum[BLCKSZ / sizeof(IndexTupleData)];
    1649                 :         240 :                 GinNullCategory category[BLCKSZ / sizeof(IndexTupleData)];
    1650                 :         240 :                 bool            datumExtracted[BLCKSZ / sizeof(IndexTupleData)];
    1651                 :             : 
    1652         [ +  - ]:         240 :                 Assert(pos->lastOffset > pos->firstOffset);
    1653                 :         240 :                 memset(datumExtracted + pos->firstOffset - 1, 0,
    1654                 :             :                            sizeof(bool) * (pos->lastOffset - pos->firstOffset));
    1655                 :             : 
    1656                 :         240 :                 page = BufferGetPage(pos->pendingBuffer);
    1657                 :             : 
    1658         [ +  + ]:         650 :                 for (i = 0; i < so->nkeys; i++)
    1659                 :             :                 {
    1660                 :         410 :                         GinScanKey      key = so->keys + i;
    1661                 :             : 
    1662         [ +  + ]:         790 :                         for (j = 0; j < key->nentries; j++)
    1663                 :             :                         {
    1664                 :         380 :                                 GinScanEntry entry = key->scanEntry[j];
    1665                 :         760 :                                 OffsetNumber StopLow = pos->firstOffset,
    1666                 :         380 :                                                         StopHigh = pos->lastOffset,
    1667                 :             :                                                         StopMiddle;
    1668                 :             : 
    1669                 :             :                                 /* If already matched on earlier page, do no extra work */
    1670         [ -  + ]:         380 :                                 if (key->entryRes[j])
    1671                 :           0 :                                         continue;
    1672                 :             : 
    1673                 :             :                                 /*
    1674                 :             :                                  * Interesting tuples are from pos->firstOffset to
    1675                 :             :                                  * pos->lastOffset and they are ordered by (attnum, Datum) as
    1676                 :             :                                  * it's done in entry tree.  So we can use binary search to
    1677                 :             :                                  * avoid linear scanning.
    1678                 :             :                                  */
    1679         [ +  + ]:        1041 :                                 while (StopLow < StopHigh)
    1680                 :             :                                 {
    1681                 :         661 :                                         int                     res;
    1682                 :             : 
    1683                 :         661 :                                         StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
    1684                 :             : 
    1685                 :         661 :                                         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, StopMiddle));
    1686                 :             : 
    1687                 :         661 :                                         attrnum = gintuple_get_attrnum(&so->ginstate, itup);
    1688                 :             : 
    1689         [ +  + ]:         661 :                                         if (key->attnum < attrnum)
    1690                 :             :                                         {
    1691                 :         133 :                                                 StopHigh = StopMiddle;
    1692                 :         133 :                                                 continue;
    1693                 :             :                                         }
    1694         [ +  + ]:         528 :                                         if (key->attnum > attrnum)
    1695                 :             :                                         {
    1696                 :         110 :                                                 StopLow = StopMiddle + 1;
    1697                 :         110 :                                                 continue;
    1698                 :             :                                         }
    1699                 :             : 
    1700         [ +  + ]:         418 :                                         if (datumExtracted[StopMiddle - 1] == false)
    1701                 :             :                                         {
    1702                 :         408 :                                                 datum[StopMiddle - 1] =
    1703                 :         816 :                                                         gintuple_get_key(&so->ginstate, itup,
    1704                 :         408 :                                                                                          &category[StopMiddle - 1]);
    1705                 :         408 :                                                 datumExtracted[StopMiddle - 1] = true;
    1706                 :         408 :                                         }
    1707                 :             : 
    1708         [ +  + ]:         418 :                                         if (entry->queryCategory == GIN_CAT_EMPTY_QUERY)
    1709                 :             :                                         {
    1710                 :             :                                                 /* special behavior depending on searchMode */
    1711         [ +  - ]:         180 :                                                 if (entry->searchMode == GIN_SEARCH_MODE_ALL)
    1712                 :             :                                                 {
    1713                 :             :                                                         /* match anything except NULL_ITEM */
    1714         [ +  + ]:         180 :                                                         if (category[StopMiddle - 1] == GIN_CAT_NULL_ITEM)
    1715                 :          63 :                                                                 res = -1;
    1716                 :             :                                                         else
    1717                 :         117 :                                                                 res = 0;
    1718                 :         180 :                                                 }
    1719                 :             :                                                 else
    1720                 :             :                                                 {
    1721                 :             :                                                         /* match everything */
    1722                 :           0 :                                                         res = 0;
    1723                 :             :                                                 }
    1724                 :         180 :                                         }
    1725                 :             :                                         else
    1726                 :             :                                         {
    1727                 :         476 :                                                 res = ginCompareEntries(&so->ginstate,
    1728                 :         238 :                                                                                                 entry->attnum,
    1729                 :         238 :                                                                                                 entry->queryKey,
    1730                 :         238 :                                                                                                 entry->queryCategory,
    1731                 :         238 :                                                                                                 datum[StopMiddle - 1],
    1732                 :         238 :                                                                                                 category[StopMiddle - 1]);
    1733                 :             :                                         }
    1734                 :             : 
    1735         [ +  + ]:         418 :                                         if (res == 0)
    1736                 :             :                                         {
    1737                 :             :                                                 /*
    1738                 :             :                                                  * Found exact match (there can be only one, except in
    1739                 :             :                                                  * EMPTY_QUERY mode).
    1740                 :             :                                                  *
    1741                 :             :                                                  * If doing partial match, scan forward from here to
    1742                 :             :                                                  * end of page to check for matches.
    1743                 :             :                                                  *
    1744                 :             :                                                  * See comment above about tuple's ordering.
    1745                 :             :                                                  */
    1746         [ -  + ]:         197 :                                                 if (entry->isPartialMatch)
    1747                 :           0 :                                                         key->entryRes[j] =
    1748                 :           0 :                                                                 matchPartialInPendingList(&so->ginstate,
    1749                 :           0 :                                                                                                                   page,
    1750                 :           0 :                                                                                                                   StopMiddle,
    1751                 :           0 :                                                                                                                   pos->lastOffset,
    1752                 :           0 :                                                                                                                   entry,
    1753                 :           0 :                                                                                                                   datum,
    1754                 :           0 :                                                                                                                   category,
    1755                 :           0 :                                                                                                                   datumExtracted);
    1756                 :             :                                                 else
    1757                 :         197 :                                                         key->entryRes[j] = true;
    1758                 :             : 
    1759                 :             :                                                 /* done with binary search */
    1760                 :         197 :                                                 break;
    1761                 :             :                                         }
    1762         [ +  + ]:         221 :                                         else if (res < 0)
    1763                 :         217 :                                                 StopHigh = StopMiddle;
    1764                 :             :                                         else
    1765                 :           4 :                                                 StopLow = StopMiddle + 1;
    1766         [ +  + ]:         661 :                                 }
    1767                 :             : 
    1768   [ +  +  +  - ]:         380 :                                 if (StopLow >= StopHigh && entry->isPartialMatch)
    1769                 :             :                                 {
    1770                 :             :                                         /*
    1771                 :             :                                          * No exact match on this page.  If doing partial match,
    1772                 :             :                                          * scan from the first tuple greater than target value to
    1773                 :             :                                          * end of page.  Note that since we don't remember whether
    1774                 :             :                                          * the comparePartialFn told us to stop early on a
    1775                 :             :                                          * previous page, we will uselessly apply comparePartialFn
    1776                 :             :                                          * to the first tuple on each subsequent page.
    1777                 :             :                                          */
    1778                 :           0 :                                         key->entryRes[j] =
    1779                 :           0 :                                                 matchPartialInPendingList(&so->ginstate,
    1780                 :           0 :                                                                                                   page,
    1781                 :           0 :                                                                                                   StopHigh,
    1782                 :           0 :                                                                                                   pos->lastOffset,
    1783                 :           0 :                                                                                                   entry,
    1784                 :           0 :                                                                                                   datum,
    1785                 :           0 :                                                                                                   category,
    1786                 :           0 :                                                                                                   datumExtracted);
    1787                 :           0 :                                 }
    1788                 :             : 
    1789                 :         380 :                                 pos->hasMatchKey[i] |= key->entryRes[j];
    1790         [ -  + ]:         380 :                         }
    1791                 :         410 :                 }
    1792                 :             : 
    1793                 :             :                 /* Advance firstOffset over the scanned tuples */
    1794                 :         240 :                 pos->firstOffset = pos->lastOffset;
    1795                 :             : 
    1796         [ +  - ]:         240 :                 if (GinPageHasFullRow(page))
    1797                 :             :                 {
    1798                 :             :                         /*
    1799                 :             :                          * We have examined all pending entries for the current heap row.
    1800                 :             :                          * Break out of loop over pages.
    1801                 :             :                          */
    1802                 :         240 :                         break;
    1803                 :             :                 }
    1804                 :             :                 else
    1805                 :             :                 {
    1806                 :             :                         /*
    1807                 :             :                          * Advance to next page of pending entries for the current heap
    1808                 :             :                          * row.  Complain if there isn't one.
    1809                 :             :                          */
    1810                 :           0 :                         ItemPointerData item = pos->item;
    1811                 :             : 
    1812         [ #  # ]:           0 :                         if (scanGetCandidate(scan, pos) == false ||
    1813                 :           0 :                                 !ItemPointerEquals(&pos->item, &item))
    1814   [ #  #  #  # ]:           0 :                                 elog(ERROR, "could not find additional pending pages for same heap tuple");
    1815                 :           0 :                 }
    1816         [ -  + ]:         240 :         }
    1817                 :             : 
    1818                 :             :         /*
    1819                 :             :          * All scan keys except excludeOnly require at least one entry to match.
    1820                 :             :          * excludeOnly keys are an exception, because their implied
    1821                 :             :          * GIN_CAT_EMPTY_QUERY scanEntry always matches.  So return "true" if all
    1822                 :             :          * non-excludeOnly scan keys have at least one match.
    1823                 :             :          */
    1824         [ +  + ]:         415 :         for (i = 0; i < so->nkeys; i++)
    1825                 :             :         {
    1826   [ +  +  +  + ]:         324 :                 if (pos->hasMatchKey[i] == false && !so->keys[i].excludeOnly)
    1827                 :         149 :                         return false;
    1828                 :         175 :         }
    1829                 :             : 
    1830                 :          91 :         return true;
    1831                 :         240 : }
    1832                 :             : 
    1833                 :             : /*
    1834                 :             :  * Collect all matched rows from pending list into bitmap.
    1835                 :             :  */
    1836                 :             : static void
    1837                 :         172 : scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
    1838                 :             : {
    1839                 :         172 :         GinScanOpaque so = (GinScanOpaque) scan->opaque;
    1840                 :         172 :         MemoryContext oldCtx;
    1841                 :         172 :         bool            recheck,
    1842                 :             :                                 match;
    1843                 :         172 :         int                     i;
    1844                 :         172 :         pendingPosition pos;
    1845                 :         172 :         Buffer          metabuffer = ReadBuffer(scan->indexRelation, GIN_METAPAGE_BLKNO);
    1846                 :         172 :         Page            page;
    1847                 :         172 :         BlockNumber blkno;
    1848                 :             : 
    1849                 :         172 :         *ntids = 0;
    1850                 :             : 
    1851                 :             :         /*
    1852                 :             :          * Acquire predicate lock on the metapage, to conflict with any fastupdate
    1853                 :             :          * insertions.
    1854                 :             :          */
    1855                 :         172 :         PredicateLockPage(scan->indexRelation, GIN_METAPAGE_BLKNO, scan->xs_snapshot);
    1856                 :             : 
    1857                 :         172 :         LockBuffer(metabuffer, GIN_SHARE);
    1858                 :         172 :         page = BufferGetPage(metabuffer);
    1859                 :         172 :         blkno = GinPageGetMeta(page)->head;
    1860                 :             : 
    1861                 :             :         /*
    1862                 :             :          * fetch head of list before unlocking metapage. head page must be pinned
    1863                 :             :          * to prevent deletion by vacuum process
    1864                 :             :          */
    1865         [ +  + ]:         172 :         if (blkno == InvalidBlockNumber)
    1866                 :             :         {
    1867                 :             :                 /* No pending list, so proceed with normal scan */
    1868                 :         145 :                 UnlockReleaseBuffer(metabuffer);
    1869                 :         145 :                 return;
    1870                 :             :         }
    1871                 :             : 
    1872                 :          27 :         pos.pendingBuffer = ReadBuffer(scan->indexRelation, blkno);
    1873                 :          27 :         LockBuffer(pos.pendingBuffer, GIN_SHARE);
    1874                 :          27 :         pos.firstOffset = FirstOffsetNumber;
    1875                 :          27 :         UnlockReleaseBuffer(metabuffer);
    1876                 :          27 :         pos.hasMatchKey = palloc_array(bool, so->nkeys);
    1877                 :             : 
    1878                 :             :         /*
    1879                 :             :          * loop for each heap row. scanGetCandidate returns full row or row's
    1880                 :             :          * tuples from first page.
    1881                 :             :          */
    1882         [ +  + ]:         267 :         while (scanGetCandidate(scan, &pos))
    1883                 :             :         {
    1884                 :             :                 /*
    1885                 :             :                  * Check entries in tuple and set up entryRes array.
    1886                 :             :                  *
    1887                 :             :                  * If pending tuples belonging to the current heap row are spread
    1888                 :             :                  * across several pages, collectMatchesForHeapRow will read all of
    1889                 :             :                  * those pages.
    1890                 :             :                  */
    1891         [ +  + ]:         240 :                 if (!collectMatchesForHeapRow(scan, &pos))
    1892                 :         149 :                         continue;
    1893                 :             : 
    1894                 :             :                 /*
    1895                 :             :                  * Matching of entries of one row is finished, so check row using
    1896                 :             :                  * consistent functions.
    1897                 :             :                  */
    1898                 :          91 :                 oldCtx = MemoryContextSwitchTo(so->tempCtx);
    1899                 :          91 :                 recheck = false;
    1900                 :          91 :                 match = true;
    1901                 :             : 
    1902         [ +  + ]:         230 :                 for (i = 0; i < so->nkeys; i++)
    1903                 :             :                 {
    1904                 :         139 :                         GinScanKey      key = so->keys + i;
    1905                 :             : 
    1906         [ +  - ]:         139 :                         if (!key->boolConsistentFn(key))
    1907                 :             :                         {
    1908                 :           0 :                                 match = false;
    1909                 :           0 :                                 break;
    1910                 :             :                         }
    1911                 :         139 :                         recheck |= key->recheckCurItem;
    1912         [ -  + ]:         139 :                 }
    1913                 :             : 
    1914                 :          91 :                 MemoryContextSwitchTo(oldCtx);
    1915                 :          91 :                 MemoryContextReset(so->tempCtx);
    1916                 :             : 
    1917         [ -  + ]:          91 :                 if (match)
    1918                 :             :                 {
    1919                 :          91 :                         tbm_add_tuples(tbm, &pos.item, 1, recheck);
    1920                 :          91 :                         (*ntids)++;
    1921                 :          91 :                 }
    1922                 :             :         }
    1923                 :             : 
    1924                 :          27 :         pfree(pos.hasMatchKey);
    1925                 :         172 : }
    1926                 :             : 
    1927                 :             : 
    1928                 :             : #define GinIsVoidRes(s)         ( ((GinScanOpaque) scan->opaque)->isVoidRes )
    1929                 :             : 
    1930                 :             : int64
    1931                 :         174 : gingetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
    1932                 :             : {
    1933                 :         174 :         GinScanOpaque so = (GinScanOpaque) scan->opaque;
    1934                 :         174 :         int64           ntids;
    1935                 :         174 :         ItemPointerData iptr;
    1936                 :         174 :         bool            recheck;
    1937                 :             : 
    1938                 :             :         /*
    1939                 :             :          * Set up the scan keys, and check for unsatisfiable query.
    1940                 :             :          */
    1941                 :         174 :         ginFreeScanKeys(so);            /* there should be no keys yet, but just to be
    1942                 :             :                                                                  * sure */
    1943                 :         174 :         ginNewScanKey(scan);
    1944                 :             : 
    1945         [ +  + ]:         174 :         if (GinIsVoidRes(scan))
    1946                 :           2 :                 return 0;
    1947                 :             : 
    1948                 :         172 :         ntids = 0;
    1949                 :             : 
    1950                 :             :         /*
    1951                 :             :          * First, scan the pending list and collect any matching entries into the
    1952                 :             :          * bitmap.  After we scan a pending item, some other backend could post it
    1953                 :             :          * into the main index, and so we might visit it a second time during the
    1954                 :             :          * main scan.  This is okay because we'll just re-set the same bit in the
    1955                 :             :          * bitmap.  (The possibility of duplicate visits is a major reason why GIN
    1956                 :             :          * can't support the amgettuple API, however.) Note that it would not do
    1957                 :             :          * to scan the main index before the pending list, since concurrent
    1958                 :             :          * cleanup could then make us miss entries entirely.
    1959                 :             :          */
    1960                 :         172 :         scanPendingInsert(scan, tbm, &ntids);
    1961                 :             : 
    1962                 :             :         /*
    1963                 :             :          * Now scan the main index.
    1964                 :             :          */
    1965                 :         172 :         startScan(scan);
    1966                 :             : 
    1967                 :         172 :         ItemPointerSetMin(&iptr);
    1968                 :             : 
    1969                 :      124731 :         for (;;)
    1970                 :             :         {
    1971         [ +  + ]:      124731 :                 if (!scanGetItem(scan, iptr, &iptr, &recheck))
    1972                 :         172 :                         break;
    1973                 :             : 
    1974   [ -  +  #  # ]:      124559 :                 if (ItemPointerIsLossyPage(&iptr))
    1975                 :           0 :                         tbm_add_page(tbm, ItemPointerGetBlockNumber(&iptr));
    1976                 :             :                 else
    1977                 :      124559 :                         tbm_add_tuples(tbm, &iptr, 1, recheck);
    1978                 :      124559 :                 ntids++;
    1979                 :             :         }
    1980                 :             : 
    1981                 :         172 :         return ntids;
    1982                 :         174 : }
        

Generated by: LCOV version 2.3.2-1