LCOV - code coverage report
Current view: top level - src/backend/access/gin - ginvacuum.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 93.6 % 407 381
Test Date: 2026-01-26 10:56:24 Functions: 100.0 % 10 10
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 72.8 % 184 134

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * ginvacuum.c
       4                 :             :  *        delete & vacuum routines for the postgres GIN
       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/ginvacuum.c
      12                 :             :  *-------------------------------------------------------------------------
      13                 :             :  */
      14                 :             : 
      15                 :             : #include "postgres.h"
      16                 :             : 
      17                 :             : #include "access/gin_private.h"
      18                 :             : #include "access/ginxlog.h"
      19                 :             : #include "access/xloginsert.h"
      20                 :             : #include "commands/vacuum.h"
      21                 :             : #include "miscadmin.h"
      22                 :             : #include "storage/indexfsm.h"
      23                 :             : #include "storage/lmgr.h"
      24                 :             : #include "storage/predicate.h"
      25                 :             : #include "utils/memutils.h"
      26                 :             : 
      27                 :             : struct GinVacuumState
      28                 :             : {
      29                 :             :         Relation        index;
      30                 :             :         IndexBulkDeleteResult *result;
      31                 :             :         IndexBulkDeleteCallback callback;
      32                 :             :         void       *callback_state;
      33                 :             :         GinState        ginstate;
      34                 :             :         BufferAccessStrategy strategy;
      35                 :             :         MemoryContext tmpCxt;
      36                 :             : };
      37                 :             : 
      38                 :             : /*
      39                 :             :  * Vacuums an uncompressed posting list. The size of the must can be specified
      40                 :             :  * in number of items (nitems).
      41                 :             :  *
      42                 :             :  * If none of the items need to be removed, returns NULL. Otherwise returns
      43                 :             :  * a new palloc'd array with the remaining items. The number of remaining
      44                 :             :  * items is returned in *nremaining.
      45                 :             :  */
      46                 :             : ItemPointer
      47                 :       40148 : ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
      48                 :             :                                           int nitem, int *nremaining)
      49                 :             : {
      50                 :       40148 :         int                     i,
      51                 :       40148 :                                 remaining = 0;
      52                 :       40148 :         ItemPointer tmpitems = NULL;
      53                 :             : 
      54                 :             :         /*
      55                 :             :          * Iterate over TIDs array
      56                 :             :          */
      57         [ +  + ]:      166146 :         for (i = 0; i < nitem; i++)
      58                 :             :         {
      59         [ +  + ]:      125998 :                 if (gvs->callback(items + i, gvs->callback_state))
      60                 :             :                 {
      61                 :      122975 :                         gvs->result->tuples_removed += 1;
      62         [ +  + ]:      122975 :                         if (!tmpitems)
      63                 :             :                         {
      64                 :             :                                 /*
      65                 :             :                                  * First TID to be deleted: allocate memory to hold the
      66                 :             :                                  * remaining items.
      67                 :             :                                  */
      68                 :       40144 :                                 tmpitems = palloc_array(ItemPointerData, nitem);
      69                 :       40144 :                                 memcpy(tmpitems, items, sizeof(ItemPointerData) * i);
      70                 :       40144 :                         }
      71                 :      122975 :                 }
      72                 :             :                 else
      73                 :             :                 {
      74                 :        3023 :                         gvs->result->num_index_tuples += 1;
      75         [ +  + ]:        3023 :                         if (tmpitems)
      76                 :        3000 :                                 tmpitems[remaining] = items[i];
      77                 :        3023 :                         remaining++;
      78                 :             :                 }
      79                 :      125998 :         }
      80                 :             : 
      81                 :       40148 :         *nremaining = remaining;
      82                 :       80296 :         return tmpitems;
      83                 :       40148 : }
      84                 :             : 
      85                 :             : /*
      86                 :             :  * Create a WAL record for vacuuming entry tree leaf page.
      87                 :             :  */
      88                 :             : static void
      89                 :         288 : xlogVacuumPage(Relation index, Buffer buffer)
      90                 :             : {
      91                 :         288 :         Page            page = BufferGetPage(buffer);
      92                 :         288 :         XLogRecPtr      recptr;
      93                 :             : 
      94                 :             :         /* This is only used for entry tree leaf pages. */
      95         [ +  - ]:         288 :         Assert(!GinPageIsData(page));
      96         [ +  - ]:         288 :         Assert(GinPageIsLeaf(page));
      97                 :             : 
      98   [ +  +  +  -  :         288 :         if (!RelationNeedsWAL(index))
             +  -  -  + ]
      99                 :         287 :                 return;
     100                 :             : 
     101                 :             :         /*
     102                 :             :          * Always create a full image, we don't track the changes on the page at
     103                 :             :          * any more fine-grained level. This could obviously be improved...
     104                 :             :          */
     105                 :           1 :         XLogBeginInsert();
     106                 :           1 :         XLogRegisterBuffer(0, buffer, REGBUF_FORCE_IMAGE | REGBUF_STANDARD);
     107                 :             : 
     108                 :           1 :         recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_VACUUM_PAGE);
     109                 :           1 :         PageSetLSN(page, recptr);
     110         [ -  + ]:         288 : }
     111                 :             : 
     112                 :             : 
     113                 :             : typedef struct DataPageDeleteStack
     114                 :             : {
     115                 :             :         struct DataPageDeleteStack *child;
     116                 :             :         struct DataPageDeleteStack *parent;
     117                 :             : 
     118                 :             :         BlockNumber blkno;                      /* current block number */
     119                 :             :         Buffer          leftBuffer;             /* pinned and locked rightest non-deleted page
     120                 :             :                                                                  * on left */
     121                 :             :         bool            isRoot;
     122                 :             : } DataPageDeleteStack;
     123                 :             : 
     124                 :             : 
     125                 :             : /*
     126                 :             :  * Delete a posting tree page.
     127                 :             :  */
     128                 :             : static void
     129                 :           2 : ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkno,
     130                 :             :                           BlockNumber parentBlkno, OffsetNumber myoff, bool isParentRoot)
     131                 :             : {
     132                 :           2 :         Buffer          dBuffer;
     133                 :           2 :         Buffer          lBuffer;
     134                 :           2 :         Buffer          pBuffer;
     135                 :           2 :         Page            page,
     136                 :             :                                 parentPage;
     137                 :           2 :         BlockNumber rightlink;
     138                 :             : 
     139                 :             :         /*
     140                 :             :          * This function MUST be called only if someone of parent pages hold
     141                 :             :          * exclusive cleanup lock. This guarantees that no insertions currently
     142                 :             :          * happen in this subtree. Caller also acquires Exclusive locks on
     143                 :             :          * deletable, parent and left pages.
     144                 :             :          */
     145                 :           4 :         lBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, leftBlkno,
     146                 :           2 :                                                                  RBM_NORMAL, gvs->strategy);
     147                 :           4 :         dBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, deleteBlkno,
     148                 :           2 :                                                                  RBM_NORMAL, gvs->strategy);
     149                 :           4 :         pBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, parentBlkno,
     150                 :           2 :                                                                  RBM_NORMAL, gvs->strategy);
     151                 :             : 
     152                 :           2 :         page = BufferGetPage(dBuffer);
     153                 :           2 :         rightlink = GinPageGetOpaque(page)->rightlink;
     154                 :             : 
     155                 :             :         /*
     156                 :             :          * Any insert which would have gone on the leaf block will now go to its
     157                 :             :          * right sibling.
     158                 :             :          */
     159                 :           2 :         PredicateLockPageCombine(gvs->index, deleteBlkno, rightlink);
     160                 :             : 
     161                 :           2 :         START_CRIT_SECTION();
     162                 :             : 
     163                 :             :         /* Unlink the page by changing left sibling's rightlink */
     164                 :           2 :         page = BufferGetPage(lBuffer);
     165                 :           2 :         GinPageGetOpaque(page)->rightlink = rightlink;
     166                 :             : 
     167                 :             :         /* Delete downlink from parent */
     168                 :           2 :         parentPage = BufferGetPage(pBuffer);
     169                 :             : #ifdef USE_ASSERT_CHECKING
     170                 :           2 :         do
     171                 :             :         {
     172                 :           2 :                 PostingItem *tod = GinDataPageGetPostingItem(parentPage, myoff);
     173                 :             : 
     174         [ +  - ]:           2 :                 Assert(PostingItemGetBlockNumber(tod) == deleteBlkno);
     175                 :           2 :         } while (0);
     176                 :             : #endif
     177                 :           2 :         GinPageDeletePostingItem(parentPage, myoff);
     178                 :             : 
     179                 :           2 :         page = BufferGetPage(dBuffer);
     180                 :             : 
     181                 :             :         /*
     182                 :             :          * we shouldn't change rightlink field to save workability of running
     183                 :             :          * search scan
     184                 :             :          */
     185                 :             : 
     186                 :             :         /*
     187                 :             :          * Mark page as deleted, and remember last xid which could know its
     188                 :             :          * address.
     189                 :             :          */
     190                 :           2 :         GinPageSetDeleted(page);
     191                 :           2 :         GinPageSetDeleteXid(page, ReadNextTransactionId());
     192                 :             : 
     193                 :           2 :         MarkBufferDirty(pBuffer);
     194                 :           2 :         MarkBufferDirty(lBuffer);
     195                 :           2 :         MarkBufferDirty(dBuffer);
     196                 :             : 
     197   [ -  +  #  #  :           2 :         if (RelationNeedsWAL(gvs->index))
             #  #  #  # ]
     198                 :             :         {
     199                 :           0 :                 XLogRecPtr      recptr;
     200                 :           0 :                 ginxlogDeletePage data;
     201                 :             : 
     202                 :             :                 /*
     203                 :             :                  * We can't pass REGBUF_STANDARD for the deleted page, because we
     204                 :             :                  * didn't set pd_lower on pre-9.4 versions. The page might've been
     205                 :             :                  * binary-upgraded from an older version, and hence not have pd_lower
     206                 :             :                  * set correctly. Ditto for the left page, but removing the item from
     207                 :             :                  * the parent updated its pd_lower, so we know that's OK at this
     208                 :             :                  * point.
     209                 :             :                  */
     210                 :           0 :                 XLogBeginInsert();
     211                 :           0 :                 XLogRegisterBuffer(0, dBuffer, 0);
     212                 :           0 :                 XLogRegisterBuffer(1, pBuffer, REGBUF_STANDARD);
     213                 :           0 :                 XLogRegisterBuffer(2, lBuffer, 0);
     214                 :             : 
     215                 :           0 :                 data.parentOffset = myoff;
     216                 :           0 :                 data.rightLink = GinPageGetOpaque(page)->rightlink;
     217                 :           0 :                 data.deleteXid = GinPageGetDeleteXid(page);
     218                 :             : 
     219                 :           0 :                 XLogRegisterData(&data, sizeof(ginxlogDeletePage));
     220                 :             : 
     221                 :           0 :                 recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_DELETE_PAGE);
     222                 :           0 :                 PageSetLSN(page, recptr);
     223                 :           0 :                 PageSetLSN(parentPage, recptr);
     224                 :           0 :                 PageSetLSN(BufferGetPage(lBuffer), recptr);
     225                 :           0 :         }
     226                 :             : 
     227                 :           2 :         ReleaseBuffer(pBuffer);
     228                 :           2 :         ReleaseBuffer(lBuffer);
     229                 :           2 :         ReleaseBuffer(dBuffer);
     230                 :             : 
     231         [ +  - ]:           2 :         END_CRIT_SECTION();
     232                 :             : 
     233                 :           2 :         gvs->result->pages_newly_deleted++;
     234                 :           2 :         gvs->result->pages_deleted++;
     235                 :           2 : }
     236                 :             : 
     237                 :             : 
     238                 :             : /*
     239                 :             :  * Scans posting tree and deletes empty pages.  Caller must lock root page for
     240                 :             :  * cleanup.  During scan path from root to current page is kept exclusively
     241                 :             :  * locked.  Also keep left page exclusively locked, because ginDeletePage()
     242                 :             :  * needs it.  If we try to relock left page later, it could deadlock with
     243                 :             :  * ginStepRight().
     244                 :             :  */
     245                 :             : static bool
     246                 :           8 : ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
     247                 :             :                                 DataPageDeleteStack *parent, OffsetNumber myoff)
     248                 :             : {
     249                 :           8 :         DataPageDeleteStack *me;
     250                 :           8 :         Buffer          buffer;
     251                 :           8 :         Page            page;
     252                 :           8 :         bool            meDelete = false;
     253                 :           8 :         bool            isempty;
     254                 :             : 
     255         [ +  + ]:           8 :         if (isRoot)
     256                 :             :         {
     257                 :           2 :                 me = parent;
     258                 :           2 :         }
     259                 :             :         else
     260                 :             :         {
     261         [ +  + ]:           6 :                 if (!parent->child)
     262                 :             :                 {
     263                 :           2 :                         me = palloc0_object(DataPageDeleteStack);
     264                 :           2 :                         me->parent = parent;
     265                 :           2 :                         parent->child = me;
     266                 :           2 :                         me->leftBuffer = InvalidBuffer;
     267                 :           2 :                 }
     268                 :             :                 else
     269                 :           4 :                         me = parent->child;
     270                 :             :         }
     271                 :             : 
     272                 :          16 :         buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
     273                 :           8 :                                                                 RBM_NORMAL, gvs->strategy);
     274                 :             : 
     275         [ +  + ]:           8 :         if (!isRoot)
     276                 :           6 :                 LockBuffer(buffer, GIN_EXCLUSIVE);
     277                 :             : 
     278                 :           8 :         page = BufferGetPage(buffer);
     279                 :             : 
     280         [ +  - ]:           8 :         Assert(GinPageIsData(page));
     281                 :             : 
     282         [ +  + ]:           8 :         if (!GinPageIsLeaf(page))
     283                 :             :         {
     284                 :           2 :                 OffsetNumber i;
     285                 :             : 
     286                 :           2 :                 me->blkno = blkno;
     287         [ +  + ]:           8 :                 for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
     288                 :             :                 {
     289                 :           6 :                         PostingItem *pitem = GinDataPageGetPostingItem(page, i);
     290                 :             : 
     291         [ +  + ]:           6 :                         if (ginScanToDelete(gvs, PostingItemGetBlockNumber(pitem), false, me, i))
     292                 :           2 :                                 i--;
     293                 :           6 :                 }
     294                 :             : 
     295   [ +  -  -  + ]:           2 :                 if (GinPageRightMost(page) && BufferIsValid(me->child->leftBuffer))
     296                 :             :                 {
     297                 :           2 :                         UnlockReleaseBuffer(me->child->leftBuffer);
     298                 :           2 :                         me->child->leftBuffer = InvalidBuffer;
     299                 :           2 :                 }
     300                 :           2 :         }
     301                 :             : 
     302         [ +  + ]:           8 :         if (GinPageIsLeaf(page))
     303         [ +  - ]:           6 :                 isempty = GinDataLeafPageIsEmpty(page);
     304                 :             :         else
     305                 :           2 :                 isempty = GinPageGetOpaque(page)->maxoff < FirstOffsetNumber;
     306                 :             : 
     307         [ +  + ]:           8 :         if (isempty)
     308                 :             :         {
     309                 :             :                 /* we never delete the left- or rightmost branch */
     310   [ +  +  +  + ]:           5 :                 if (BufferIsValid(me->leftBuffer) && !GinPageRightMost(page))
     311                 :             :                 {
     312         [ +  - ]:           2 :                         Assert(!isRoot);
     313                 :           4 :                         ginDeletePage(gvs, blkno, BufferGetBlockNumber(me->leftBuffer),
     314                 :           2 :                                                   me->parent->blkno, myoff, me->parent->isRoot);
     315                 :           2 :                         meDelete = true;
     316                 :           2 :                 }
     317                 :           5 :         }
     318                 :             : 
     319         [ +  + ]:           8 :         if (!meDelete)
     320                 :             :         {
     321         [ +  + ]:           6 :                 if (BufferIsValid(me->leftBuffer))
     322                 :           2 :                         UnlockReleaseBuffer(me->leftBuffer);
     323                 :           6 :                 me->leftBuffer = buffer;
     324                 :           6 :         }
     325                 :             :         else
     326                 :             :         {
     327         [ -  + ]:           2 :                 if (!isRoot)
     328                 :           2 :                         LockBuffer(buffer, GIN_UNLOCK);
     329                 :             : 
     330                 :           2 :                 ReleaseBuffer(buffer);
     331                 :             :         }
     332                 :             : 
     333         [ +  + ]:           8 :         if (isRoot)
     334                 :           2 :                 ReleaseBuffer(buffer);
     335                 :             : 
     336                 :          16 :         return meDelete;
     337                 :           8 : }
     338                 :             : 
     339                 :             : 
     340                 :             : /*
     341                 :             :  * Scan through posting tree leafs, delete empty tuples.  Returns true if there
     342                 :             :  * is at least one empty page.
     343                 :             :  */
     344                 :             : static bool
     345                 :           2 : ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno)
     346                 :             : {
     347                 :           2 :         Buffer          buffer;
     348                 :           2 :         Page            page;
     349                 :           2 :         bool            hasVoidPage = false;
     350                 :           2 :         MemoryContext oldCxt;
     351                 :             : 
     352                 :             :         /* Find leftmost leaf page of posting tree and lock it in exclusive mode */
     353                 :           4 :         while (true)
     354                 :             :         {
     355                 :           4 :                 PostingItem *pitem;
     356                 :             : 
     357                 :           8 :                 buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
     358                 :           4 :                                                                         RBM_NORMAL, gvs->strategy);
     359                 :           4 :                 LockBuffer(buffer, GIN_SHARE);
     360                 :           4 :                 page = BufferGetPage(buffer);
     361                 :             : 
     362         [ +  - ]:           4 :                 Assert(GinPageIsData(page));
     363                 :             : 
     364         [ +  + ]:           4 :                 if (GinPageIsLeaf(page))
     365                 :             :                 {
     366                 :           2 :                         LockBuffer(buffer, GIN_UNLOCK);
     367                 :           2 :                         LockBuffer(buffer, GIN_EXCLUSIVE);
     368                 :           2 :                         break;
     369                 :             :                 }
     370                 :             : 
     371         [ +  - ]:           2 :                 Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
     372                 :             : 
     373                 :           2 :                 pitem = GinDataPageGetPostingItem(page, FirstOffsetNumber);
     374                 :           2 :                 blkno = PostingItemGetBlockNumber(pitem);
     375         [ -  + ]:           2 :                 Assert(blkno != InvalidBlockNumber);
     376                 :             : 
     377                 :           2 :                 UnlockReleaseBuffer(buffer);
     378      [ +  -  + ]:           4 :         }
     379                 :             : 
     380                 :             :         /* Iterate all posting tree leaves using rightlinks and vacuum them */
     381                 :           6 :         while (true)
     382                 :             :         {
     383                 :           6 :                 oldCxt = MemoryContextSwitchTo(gvs->tmpCxt);
     384                 :           6 :                 ginVacuumPostingTreeLeaf(gvs->index, buffer, gvs);
     385                 :           6 :                 MemoryContextSwitchTo(oldCxt);
     386                 :           6 :                 MemoryContextReset(gvs->tmpCxt);
     387                 :             : 
     388   [ +  -  +  + ]:           6 :                 if (GinDataLeafPageIsEmpty(page))
     389                 :           5 :                         hasVoidPage = true;
     390                 :             : 
     391                 :           6 :                 blkno = GinPageGetOpaque(page)->rightlink;
     392                 :             : 
     393                 :           6 :                 UnlockReleaseBuffer(buffer);
     394                 :             : 
     395         [ +  + ]:           6 :                 if (blkno == InvalidBlockNumber)
     396                 :           2 :                         break;
     397                 :             : 
     398                 :           8 :                 buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
     399                 :           4 :                                                                         RBM_NORMAL, gvs->strategy);
     400                 :           4 :                 LockBuffer(buffer, GIN_EXCLUSIVE);
     401                 :           4 :                 page = BufferGetPage(buffer);
     402                 :             :         }
     403                 :             : 
     404                 :           4 :         return hasVoidPage;
     405                 :           2 : }
     406                 :             : 
     407                 :             : static void
     408                 :           2 : ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
     409                 :             : {
     410         [ -  + ]:           2 :         if (ginVacuumPostingTreeLeaves(gvs, rootBlkno))
     411                 :             :         {
     412                 :             :                 /*
     413                 :             :                  * There is at least one empty page.  So we have to rescan the tree
     414                 :             :                  * deleting empty pages.
     415                 :             :                  */
     416                 :           2 :                 Buffer          buffer;
     417                 :           2 :                 DataPageDeleteStack root,
     418                 :             :                                    *ptr,
     419                 :             :                                    *tmp;
     420                 :             : 
     421                 :           4 :                 buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, rootBlkno,
     422                 :           2 :                                                                         RBM_NORMAL, gvs->strategy);
     423                 :             : 
     424                 :             :                 /*
     425                 :             :                  * Lock posting tree root for cleanup to ensure there are no
     426                 :             :                  * concurrent inserts.
     427                 :             :                  */
     428                 :           2 :                 LockBufferForCleanup(buffer);
     429                 :             : 
     430                 :           2 :                 memset(&root, 0, sizeof(DataPageDeleteStack));
     431                 :           2 :                 root.leftBuffer = InvalidBuffer;
     432                 :           2 :                 root.isRoot = true;
     433                 :             : 
     434                 :           2 :                 ginScanToDelete(gvs, rootBlkno, true, &root, InvalidOffsetNumber);
     435                 :             : 
     436                 :           2 :                 ptr = root.child;
     437                 :             : 
     438         [ +  + ]:           4 :                 while (ptr)
     439                 :             :                 {
     440                 :           2 :                         tmp = ptr->child;
     441                 :           2 :                         pfree(ptr);
     442                 :           2 :                         ptr = tmp;
     443                 :             :                 }
     444                 :             : 
     445                 :           2 :                 UnlockReleaseBuffer(buffer);
     446                 :           2 :         }
     447                 :           2 : }
     448                 :             : 
     449                 :             : /*
     450                 :             :  * returns modified page or NULL if page isn't modified.
     451                 :             :  * Function works with original page until first change is occurred,
     452                 :             :  * then page is copied into temporary one.
     453                 :             :  */
     454                 :             : static Page
     455                 :         288 : ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint32 *nroot)
     456                 :             : {
     457                 :         288 :         Page            origpage = BufferGetPage(buffer),
     458                 :             :                                 tmppage;
     459                 :         288 :         OffsetNumber i,
     460                 :         288 :                                 maxoff = PageGetMaxOffsetNumber(origpage);
     461                 :             : 
     462                 :         288 :         tmppage = origpage;
     463                 :             : 
     464                 :         288 :         *nroot = 0;
     465                 :             : 
     466         [ +  + ]:       40297 :         for (i = FirstOffsetNumber; i <= maxoff; i++)
     467                 :             :         {
     468                 :       40009 :                 IndexTuple      itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
     469                 :             : 
     470         [ +  + ]:       40009 :                 if (GinIsPostingTree(itup))
     471                 :             :                 {
     472                 :             :                         /*
     473                 :             :                          * store posting tree's roots for further processing, we can't
     474                 :             :                          * vacuum it just now due to risk of deadlocks with scans/inserts
     475                 :             :                          */
     476                 :           2 :                         roots[*nroot] = GinGetDownlink(itup);
     477                 :           2 :                         (*nroot)++;
     478                 :           2 :                 }
     479         [ -  + ]:       40007 :                 else if (GinGetNPosting(itup) > 0)
     480                 :             :                 {
     481                 :       40007 :                         int                     nitems;
     482                 :       40007 :                         ItemPointer items_orig;
     483                 :       40007 :                         bool            free_items_orig;
     484                 :       40007 :                         ItemPointer items;
     485                 :             : 
     486                 :             :                         /* Get list of item pointers from the tuple. */
     487         [ +  - ]:       40007 :                         if (GinItupIsCompressed(itup))
     488                 :             :                         {
     489                 :       40007 :                                 items_orig = ginPostingListDecode((GinPostingList *) GinGetPosting(itup), &nitems);
     490                 :       40007 :                                 free_items_orig = true;
     491                 :       40007 :                         }
     492                 :             :                         else
     493                 :             :                         {
     494                 :           0 :                                 items_orig = (ItemPointer) GinGetPosting(itup);
     495                 :           0 :                                 nitems = GinGetNPosting(itup);
     496                 :           0 :                                 free_items_orig = false;
     497                 :             :                         }
     498                 :             : 
     499                 :             :                         /* Remove any items from the list that need to be vacuumed. */
     500                 :       40007 :                         items = ginVacuumItemPointers(gvs, items_orig, nitems, &nitems);
     501                 :             : 
     502         [ -  + ]:       40007 :                         if (free_items_orig)
     503                 :       40007 :                                 pfree(items_orig);
     504                 :             : 
     505                 :             :                         /* If any item pointers were removed, recreate the tuple. */
     506         [ +  + ]:       40007 :                         if (items)
     507                 :             :                         {
     508                 :       40003 :                                 OffsetNumber attnum;
     509                 :       40003 :                                 Datum           key;
     510                 :       40003 :                                 GinNullCategory category;
     511                 :       40003 :                                 GinPostingList *plist;
     512                 :       40003 :                                 int                     plistsize;
     513                 :             : 
     514         [ +  + ]:       40003 :                                 if (nitems > 0)
     515                 :             :                                 {
     516                 :           6 :                                         plist = ginCompressPostingList(items, nitems, GinMaxItemSize, NULL);
     517                 :           6 :                                         plistsize = SizeOfGinPostingList(plist);
     518                 :           6 :                                 }
     519                 :             :                                 else
     520                 :             :                                 {
     521                 :       39997 :                                         plist = NULL;
     522                 :       39997 :                                         plistsize = 0;
     523                 :             :                                 }
     524                 :             : 
     525                 :             :                                 /*
     526                 :             :                                  * if we already created a temporary page, make changes in
     527                 :             :                                  * place
     528                 :             :                                  */
     529         [ +  + ]:       40003 :                                 if (tmppage == origpage)
     530                 :             :                                 {
     531                 :             :                                         /*
     532                 :             :                                          * On first difference, create a temporary copy of the
     533                 :             :                                          * page and copy the tuple's posting list to it.
     534                 :             :                                          */
     535                 :         288 :                                         tmppage = PageGetTempPageCopy(origpage);
     536                 :             : 
     537                 :             :                                         /* set itup pointer to new page */
     538                 :         288 :                                         itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
     539                 :         288 :                                 }
     540                 :             : 
     541                 :       40003 :                                 attnum = gintuple_get_attrnum(&gvs->ginstate, itup);
     542                 :       40003 :                                 key = gintuple_get_key(&gvs->ginstate, itup, &category);
     543                 :       80006 :                                 itup = GinFormTuple(&gvs->ginstate, attnum, key, category,
     544                 :       40003 :                                                                         (char *) plist, plistsize,
     545                 :       40003 :                                                                         nitems, true);
     546         [ +  + ]:       40003 :                                 if (plist)
     547                 :           6 :                                         pfree(plist);
     548                 :       40003 :                                 PageIndexTupleDelete(tmppage, i);
     549                 :             : 
     550         [ +  - ]:       40003 :                                 if (PageAddItem(tmppage, itup, IndexTupleSize(itup), i, false, false) != i)
     551   [ #  #  #  # ]:           0 :                                         elog(ERROR, "failed to add item to index page in \"%s\"",
     552                 :             :                                                  RelationGetRelationName(gvs->index));
     553                 :             : 
     554                 :       40003 :                                 pfree(itup);
     555                 :       40003 :                                 pfree(items);
     556                 :       40003 :                         }
     557                 :       40007 :                 }
     558                 :       40009 :         }
     559                 :             : 
     560         [ +  - ]:         288 :         return (tmppage == origpage) ? NULL : tmppage;
     561                 :         288 : }
     562                 :             : 
     563                 :             : IndexBulkDeleteResult *
     564                 :           2 : ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
     565                 :             :                           IndexBulkDeleteCallback callback, void *callback_state)
     566                 :             : {
     567                 :           2 :         Relation        index = info->index;
     568                 :           2 :         BlockNumber blkno = GIN_ROOT_BLKNO;
     569                 :           2 :         GinVacuumState gvs;
     570                 :           2 :         Buffer          buffer;
     571                 :           2 :         BlockNumber rootOfPostingTree[BLCKSZ / (sizeof(IndexTupleData) + sizeof(ItemId))];
     572                 :           2 :         uint32          nRoot;
     573                 :             : 
     574                 :           2 :         gvs.tmpCxt = AllocSetContextCreate(CurrentMemoryContext,
     575                 :             :                                                                            "Gin vacuum temporary context",
     576                 :             :                                                                            ALLOCSET_DEFAULT_SIZES);
     577                 :           2 :         gvs.index = index;
     578                 :           2 :         gvs.callback = callback;
     579                 :           2 :         gvs.callback_state = callback_state;
     580                 :           2 :         gvs.strategy = info->strategy;
     581                 :           2 :         initGinState(&gvs.ginstate, index);
     582                 :             : 
     583                 :             :         /* first time through? */
     584         [ -  + ]:           2 :         if (stats == NULL)
     585                 :             :         {
     586                 :             :                 /* Yes, so initialize stats to zeroes */
     587                 :           2 :                 stats = palloc0_object(IndexBulkDeleteResult);
     588                 :             : 
     589                 :             :                 /*
     590                 :             :                  * and cleanup any pending inserts
     591                 :             :                  */
     592                 :           4 :                 ginInsertCleanup(&gvs.ginstate, !AmAutoVacuumWorkerProcess(),
     593                 :           2 :                                                  false, true, stats);
     594                 :           2 :         }
     595                 :             : 
     596                 :             :         /* we'll re-count the tuples each time */
     597                 :           2 :         stats->num_index_tuples = 0;
     598                 :           2 :         gvs.result = stats;
     599                 :             : 
     600                 :           4 :         buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
     601                 :           2 :                                                                 RBM_NORMAL, info->strategy);
     602                 :             : 
     603                 :             :         /* find leaf page */
     604                 :           3 :         for (;;)
     605                 :             :         {
     606                 :           3 :                 Page            page = BufferGetPage(buffer);
     607                 :           3 :                 IndexTuple      itup;
     608                 :             : 
     609                 :           3 :                 LockBuffer(buffer, GIN_SHARE);
     610                 :             : 
     611         [ -  + ]:           3 :                 Assert(!GinPageIsData(page));
     612                 :             : 
     613         [ +  + ]:           3 :                 if (GinPageIsLeaf(page))
     614                 :             :                 {
     615                 :           2 :                         LockBuffer(buffer, GIN_UNLOCK);
     616                 :           2 :                         LockBuffer(buffer, GIN_EXCLUSIVE);
     617                 :             : 
     618   [ +  +  +  - ]:           2 :                         if (blkno == GIN_ROOT_BLKNO && !GinPageIsLeaf(page))
     619                 :             :                         {
     620                 :           0 :                                 LockBuffer(buffer, GIN_UNLOCK);
     621                 :           0 :                                 continue;               /* check it one more */
     622                 :             :                         }
     623                 :           2 :                         break;
     624                 :             :                 }
     625                 :             : 
     626         [ +  - ]:           1 :                 Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
     627                 :             : 
     628                 :           1 :                 itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
     629                 :           1 :                 blkno = GinGetDownlink(itup);
     630         [ +  - ]:           1 :                 Assert(blkno != InvalidBlockNumber);
     631                 :             : 
     632                 :           1 :                 UnlockReleaseBuffer(buffer);
     633                 :           2 :                 buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
     634                 :           1 :                                                                         RBM_NORMAL, info->strategy);
     635      [ -  +  + ]:           3 :         }
     636                 :             : 
     637                 :             :         /* right now we found leftmost page in entry's BTree */
     638                 :             : 
     639                 :         288 :         for (;;)
     640                 :             :         {
     641                 :         288 :                 Page            page = BufferGetPage(buffer);
     642                 :         288 :                 Page            resPage;
     643                 :         288 :                 uint32          i;
     644                 :             : 
     645         [ +  - ]:         288 :                 Assert(!GinPageIsData(page));
     646                 :             : 
     647                 :         288 :                 resPage = ginVacuumEntryPage(&gvs, buffer, rootOfPostingTree, &nRoot);
     648                 :             : 
     649                 :         288 :                 blkno = GinPageGetOpaque(page)->rightlink;
     650                 :             : 
     651         [ +  - ]:         288 :                 if (resPage)
     652                 :             :                 {
     653                 :         288 :                         START_CRIT_SECTION();
     654                 :         288 :                         PageRestoreTempPage(resPage, page);
     655                 :         288 :                         MarkBufferDirty(buffer);
     656                 :         288 :                         xlogVacuumPage(gvs.index, buffer);
     657                 :         288 :                         UnlockReleaseBuffer(buffer);
     658         [ -  + ]:         288 :                         END_CRIT_SECTION();
     659                 :         288 :                 }
     660                 :             :                 else
     661                 :             :                 {
     662                 :           0 :                         UnlockReleaseBuffer(buffer);
     663                 :             :                 }
     664                 :             : 
     665                 :         288 :                 vacuum_delay_point(false);
     666                 :             : 
     667         [ +  + ]:         290 :                 for (i = 0; i < nRoot; i++)
     668                 :             :                 {
     669                 :           2 :                         ginVacuumPostingTree(&gvs, rootOfPostingTree[i]);
     670                 :           2 :                         vacuum_delay_point(false);
     671                 :           2 :                 }
     672                 :             : 
     673         [ +  + ]:         288 :                 if (blkno == InvalidBlockNumber)        /* rightmost page */
     674                 :           2 :                         break;
     675                 :             : 
     676                 :         572 :                 buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
     677                 :         286 :                                                                         RBM_NORMAL, info->strategy);
     678                 :         286 :                 LockBuffer(buffer, GIN_EXCLUSIVE);
     679         [ +  + ]:         288 :         }
     680                 :             : 
     681                 :           2 :         MemoryContextDelete(gvs.tmpCxt);
     682                 :             : 
     683                 :           4 :         return gvs.result;
     684                 :           2 : }
     685                 :             : 
     686                 :             : IndexBulkDeleteResult *
     687                 :          10 : ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
     688                 :             : {
     689                 :          10 :         Relation        index = info->index;
     690                 :          10 :         bool            needLock;
     691                 :          10 :         BlockNumber npages,
     692                 :             :                                 blkno;
     693                 :          10 :         BlockNumber totFreePages;
     694                 :          10 :         GinState        ginstate;
     695                 :          10 :         GinStatsData idxStat;
     696                 :             : 
     697                 :             :         /*
     698                 :             :          * In an autovacuum analyze, we want to clean up pending insertions.
     699                 :             :          * Otherwise, an ANALYZE-only call is a no-op.
     700                 :             :          */
     701         [ +  + ]:          10 :         if (info->analyze_only)
     702                 :             :         {
     703         [ +  - ]:           1 :                 if (AmAutoVacuumWorkerProcess())
     704                 :             :                 {
     705                 :           0 :                         initGinState(&ginstate, index);
     706                 :           0 :                         ginInsertCleanup(&ginstate, false, true, true, stats);
     707                 :           0 :                 }
     708                 :           1 :                 return stats;
     709                 :             :         }
     710                 :             : 
     711                 :             :         /*
     712                 :             :          * Set up all-zero stats and cleanup pending inserts if ginbulkdelete
     713                 :             :          * wasn't called
     714                 :             :          */
     715         [ +  + ]:           9 :         if (stats == NULL)
     716                 :             :         {
     717                 :           7 :                 stats = palloc0_object(IndexBulkDeleteResult);
     718                 :           7 :                 initGinState(&ginstate, index);
     719                 :          14 :                 ginInsertCleanup(&ginstate, !AmAutoVacuumWorkerProcess(),
     720                 :           7 :                                                  false, true, stats);
     721                 :           7 :         }
     722                 :             : 
     723                 :           9 :         memset(&idxStat, 0, sizeof(idxStat));
     724                 :             : 
     725                 :             :         /*
     726                 :             :          * XXX we always report the heap tuple count as the number of index
     727                 :             :          * entries.  This is bogus if the index is partial, but it's real hard to
     728                 :             :          * tell how many distinct heap entries are referenced by a GIN index.
     729                 :             :          */
     730         [ +  - ]:           9 :         stats->num_index_tuples = Max(info->num_heap_tuples, 0);
     731                 :           9 :         stats->estimated_count = info->estimated_count;
     732                 :             : 
     733                 :             :         /*
     734                 :             :          * Need lock unless it's local to this backend.
     735                 :             :          */
     736         [ +  + ]:           9 :         needLock = !RELATION_IS_LOCAL(index);
     737                 :             : 
     738         [ +  + ]:           9 :         if (needLock)
     739                 :           8 :                 LockRelationForExtension(index, ExclusiveLock);
     740                 :           9 :         npages = RelationGetNumberOfBlocks(index);
     741         [ +  + ]:           9 :         if (needLock)
     742                 :           8 :                 UnlockRelationForExtension(index, ExclusiveLock);
     743                 :             : 
     744                 :           9 :         totFreePages = 0;
     745                 :             : 
     746         [ +  + ]:        1530 :         for (blkno = GIN_ROOT_BLKNO; blkno < npages; blkno++)
     747                 :             :         {
     748                 :        1521 :                 Buffer          buffer;
     749                 :        1521 :                 Page            page;
     750                 :             : 
     751                 :        1521 :                 vacuum_delay_point(false);
     752                 :             : 
     753                 :        3042 :                 buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
     754                 :        1521 :                                                                         RBM_NORMAL, info->strategy);
     755                 :        1521 :                 LockBuffer(buffer, GIN_SHARE);
     756                 :        1521 :                 page = BufferGetPage(buffer);
     757                 :             : 
     758         [ +  + ]:        1521 :                 if (GinPageIsRecyclable(page))
     759                 :             :                 {
     760         [ -  + ]:         774 :                         Assert(blkno != GIN_ROOT_BLKNO);
     761                 :         774 :                         RecordFreeIndexPage(index, blkno);
     762                 :         774 :                         totFreePages++;
     763                 :         774 :                 }
     764         [ +  + ]:         747 :                 else if (GinPageIsData(page))
     765                 :             :                 {
     766                 :          34 :                         idxStat.nDataPages++;
     767                 :          34 :                 }
     768         [ -  + ]:         713 :                 else if (!GinPageIsList(page))
     769                 :             :                 {
     770                 :         713 :                         idxStat.nEntryPages++;
     771                 :             : 
     772         [ +  + ]:         713 :                         if (GinPageIsLeaf(page))
     773                 :         708 :                                 idxStat.nEntries += PageGetMaxOffsetNumber(page);
     774                 :         713 :                 }
     775                 :             : 
     776                 :        1521 :                 UnlockReleaseBuffer(buffer);
     777                 :        1521 :         }
     778                 :             : 
     779                 :             :         /* Update the metapage with accurate page and entry counts */
     780                 :           9 :         idxStat.nTotalPages = npages;
     781                 :           9 :         ginUpdateStats(info->index, &idxStat, false);
     782                 :             : 
     783                 :             :         /* Finally, vacuum the FSM */
     784                 :           9 :         IndexFreeSpaceMapVacuum(info->index);
     785                 :             : 
     786                 :           9 :         stats->pages_free = totFreePages;
     787                 :             : 
     788         [ +  + ]:           9 :         if (needLock)
     789                 :           8 :                 LockRelationForExtension(index, ExclusiveLock);
     790                 :           9 :         stats->num_pages = RelationGetNumberOfBlocks(index);
     791         [ +  + ]:           9 :         if (needLock)
     792                 :           8 :                 UnlockRelationForExtension(index, ExclusiveLock);
     793                 :             : 
     794                 :           9 :         return stats;
     795                 :          10 : }
     796                 :             : 
     797                 :             : /*
     798                 :             :  * Return whether Page can safely be recycled.
     799                 :             :  */
     800                 :             : bool
     801                 :        1538 : GinPageIsRecyclable(Page page)
     802                 :             : {
     803                 :        1538 :         TransactionId delete_xid;
     804                 :             : 
     805         [ -  + ]:        1538 :         if (PageIsNew(page))
     806                 :           0 :                 return true;
     807                 :             : 
     808         [ +  + ]:        1538 :         if (!GinPageIsDeleted(page))
     809                 :         745 :                 return false;
     810                 :             : 
     811                 :         793 :         delete_xid = GinPageGetDeleteXid(page);
     812                 :             : 
     813         [ +  + ]:         793 :         if (!TransactionIdIsValid(delete_xid))
     814                 :         791 :                 return true;
     815                 :             : 
     816                 :             :         /*
     817                 :             :          * If no backend still could view delete_xid as in running, all scans
     818                 :             :          * concurrent with ginDeletePage() must have finished.
     819                 :             :          */
     820                 :           2 :         return GlobalVisCheckRemovableXid(NULL, delete_xid);
     821                 :        1538 : }
        

Generated by: LCOV version 2.3.2-1