LCOV - code coverage report
Current view: top level - src/backend/access/gin - ginvacuum.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 269 290 92.8 %
Date: 2024-11-21 08:14:44 Functions: 10 10 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * ginvacuum.c
       4             :  *    delete & vacuum routines for the postgres GIN
       5             :  *
       6             :  *
       7             :  * Portions Copyright (c) 1996-2024, 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      240942 : ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
      48             :                       int nitem, int *nremaining)
      49             : {
      50             :     int         i,
      51      240942 :                 remaining = 0;
      52      240942 :     ItemPointer tmpitems = NULL;
      53             : 
      54             :     /*
      55             :      * Iterate over TIDs array
      56             :      */
      57     1014354 :     for (i = 0; i < nitem; i++)
      58             :     {
      59      773412 :         if (gvs->callback(items + i, gvs->callback_state))
      60             :         {
      61      743442 :             gvs->result->tuples_removed += 1;
      62      743442 :             if (!tmpitems)
      63             :             {
      64             :                 /*
      65             :                  * First TID to be deleted: allocate memory to hold the
      66             :                  * remaining items.
      67             :                  */
      68      240882 :                 tmpitems = palloc(sizeof(ItemPointerData) * nitem);
      69      240882 :                 memcpy(tmpitems, items, sizeof(ItemPointerData) * i);
      70             :             }
      71             :         }
      72             :         else
      73             :         {
      74       29970 :             gvs->result->num_index_tuples += 1;
      75       29970 :             if (tmpitems)
      76       12954 :                 tmpitems[remaining] = items[i];
      77       29970 :             remaining++;
      78             :         }
      79             :     }
      80             : 
      81      240942 :     *nremaining = remaining;
      82      240942 :     return tmpitems;
      83             : }
      84             : 
      85             : /*
      86             :  * Create a WAL record for vacuuming entry tree leaf page.
      87             :  */
      88             : static void
      89        1728 : xlogVacuumPage(Relation index, Buffer buffer)
      90             : {
      91        1728 :     Page        page = BufferGetPage(buffer);
      92             :     XLogRecPtr  recptr;
      93             : 
      94             :     /* This is only used for entry tree leaf pages. */
      95             :     Assert(!GinPageIsData(page));
      96             :     Assert(GinPageIsLeaf(page));
      97             : 
      98        1728 :     if (!RelationNeedsWAL(index))
      99        1722 :         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           6 :     XLogBeginInsert();
     106           6 :     XLogRegisterBuffer(0, buffer, REGBUF_FORCE_IMAGE | REGBUF_STANDARD);
     107             : 
     108           6 :     recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_VACUUM_PAGE);
     109           6 :     PageSetLSN(page, recptr);
     110             : }
     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          12 : ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkno,
     130             :               BlockNumber parentBlkno, OffsetNumber myoff, bool isParentRoot)
     131             : {
     132             :     Buffer      dBuffer;
     133             :     Buffer      lBuffer;
     134             :     Buffer      pBuffer;
     135             :     Page        page,
     136             :                 parentPage;
     137             :     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          12 :     lBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, leftBlkno,
     146             :                                  RBM_NORMAL, gvs->strategy);
     147          12 :     dBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, deleteBlkno,
     148             :                                  RBM_NORMAL, gvs->strategy);
     149          12 :     pBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, parentBlkno,
     150             :                                  RBM_NORMAL, gvs->strategy);
     151             : 
     152          12 :     page = BufferGetPage(dBuffer);
     153          12 :     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          12 :     PredicateLockPageCombine(gvs->index, deleteBlkno, rightlink);
     160             : 
     161          12 :     START_CRIT_SECTION();
     162             : 
     163             :     /* Unlink the page by changing left sibling's rightlink */
     164          12 :     page = BufferGetPage(lBuffer);
     165          12 :     GinPageGetOpaque(page)->rightlink = rightlink;
     166             : 
     167             :     /* Delete downlink from parent */
     168          12 :     parentPage = BufferGetPage(pBuffer);
     169             : #ifdef USE_ASSERT_CHECKING
     170             :     do
     171             :     {
     172             :         PostingItem *tod = GinDataPageGetPostingItem(parentPage, myoff);
     173             : 
     174             :         Assert(PostingItemGetBlockNumber(tod) == deleteBlkno);
     175             :     } while (0);
     176             : #endif
     177          12 :     GinPageDeletePostingItem(parentPage, myoff);
     178             : 
     179          12 :     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          12 :     GinPageSetDeleted(page);
     191          12 :     GinPageSetDeleteXid(page, ReadNextTransactionId());
     192             : 
     193          12 :     MarkBufferDirty(pBuffer);
     194          12 :     MarkBufferDirty(lBuffer);
     195          12 :     MarkBufferDirty(dBuffer);
     196             : 
     197          12 :     if (RelationNeedsWAL(gvs->index))
     198             :     {
     199             :         XLogRecPtr  recptr;
     200             :         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((char *) &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             :     }
     226             : 
     227          12 :     ReleaseBuffer(pBuffer);
     228          12 :     ReleaseBuffer(lBuffer);
     229          12 :     ReleaseBuffer(dBuffer);
     230             : 
     231          12 :     END_CRIT_SECTION();
     232             : 
     233          12 :     gvs->result->pages_newly_deleted++;
     234          12 :     gvs->result->pages_deleted++;
     235          12 : }
     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          48 : ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
     247             :                 DataPageDeleteStack *parent, OffsetNumber myoff)
     248             : {
     249             :     DataPageDeleteStack *me;
     250             :     Buffer      buffer;
     251             :     Page        page;
     252          48 :     bool        meDelete = false;
     253             :     bool        isempty;
     254             : 
     255          48 :     if (isRoot)
     256             :     {
     257          12 :         me = parent;
     258             :     }
     259             :     else
     260             :     {
     261          36 :         if (!parent->child)
     262             :         {
     263          12 :             me = (DataPageDeleteStack *) palloc0(sizeof(DataPageDeleteStack));
     264          12 :             me->parent = parent;
     265          12 :             parent->child = me;
     266          12 :             me->leftBuffer = InvalidBuffer;
     267             :         }
     268             :         else
     269          24 :             me = parent->child;
     270             :     }
     271             : 
     272          48 :     buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
     273             :                                 RBM_NORMAL, gvs->strategy);
     274             : 
     275          48 :     if (!isRoot)
     276          36 :         LockBuffer(buffer, GIN_EXCLUSIVE);
     277             : 
     278          48 :     page = BufferGetPage(buffer);
     279             : 
     280             :     Assert(GinPageIsData(page));
     281             : 
     282          48 :     if (!GinPageIsLeaf(page))
     283             :     {
     284             :         OffsetNumber i;
     285             : 
     286          12 :         me->blkno = blkno;
     287          48 :         for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
     288             :         {
     289          36 :             PostingItem *pitem = GinDataPageGetPostingItem(page, i);
     290             : 
     291          36 :             if (ginScanToDelete(gvs, PostingItemGetBlockNumber(pitem), false, me, i))
     292          12 :                 i--;
     293             :         }
     294             : 
     295          12 :         if (GinPageRightMost(page) && BufferIsValid(me->child->leftBuffer))
     296             :         {
     297          12 :             UnlockReleaseBuffer(me->child->leftBuffer);
     298          12 :             me->child->leftBuffer = InvalidBuffer;
     299             :         }
     300             :     }
     301             : 
     302          48 :     if (GinPageIsLeaf(page))
     303          36 :         isempty = GinDataLeafPageIsEmpty(page);
     304             :     else
     305          12 :         isempty = GinPageGetOpaque(page)->maxoff < FirstOffsetNumber;
     306             : 
     307          48 :     if (isempty)
     308             :     {
     309             :         /* we never delete the left- or rightmost branch */
     310          30 :         if (BufferIsValid(me->leftBuffer) && !GinPageRightMost(page))
     311             :         {
     312             :             Assert(!isRoot);
     313          12 :             ginDeletePage(gvs, blkno, BufferGetBlockNumber(me->leftBuffer),
     314          12 :                           me->parent->blkno, myoff, me->parent->isRoot);
     315          12 :             meDelete = true;
     316             :         }
     317             :     }
     318             : 
     319          48 :     if (!meDelete)
     320             :     {
     321          36 :         if (BufferIsValid(me->leftBuffer))
     322          12 :             UnlockReleaseBuffer(me->leftBuffer);
     323          36 :         me->leftBuffer = buffer;
     324             :     }
     325             :     else
     326             :     {
     327          12 :         if (!isRoot)
     328          12 :             LockBuffer(buffer, GIN_UNLOCK);
     329             : 
     330          12 :         ReleaseBuffer(buffer);
     331             :     }
     332             : 
     333          48 :     if (isRoot)
     334          12 :         ReleaseBuffer(buffer);
     335             : 
     336          48 :     return meDelete;
     337             : }
     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          18 : ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno)
     346             : {
     347             :     Buffer      buffer;
     348             :     Page        page;
     349          18 :     bool        hasVoidPage = false;
     350             :     MemoryContext oldCxt;
     351             : 
     352             :     /* Find leftmost leaf page of posting tree and lock it in exclusive mode */
     353             :     while (true)
     354          12 :     {
     355             :         PostingItem *pitem;
     356             : 
     357          30 :         buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
     358             :                                     RBM_NORMAL, gvs->strategy);
     359          30 :         LockBuffer(buffer, GIN_SHARE);
     360          30 :         page = BufferGetPage(buffer);
     361             : 
     362             :         Assert(GinPageIsData(page));
     363             : 
     364          30 :         if (GinPageIsLeaf(page))
     365             :         {
     366          18 :             LockBuffer(buffer, GIN_UNLOCK);
     367          18 :             LockBuffer(buffer, GIN_EXCLUSIVE);
     368          18 :             break;
     369             :         }
     370             : 
     371             :         Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
     372             : 
     373          12 :         pitem = GinDataPageGetPostingItem(page, FirstOffsetNumber);
     374          12 :         blkno = PostingItemGetBlockNumber(pitem);
     375             :         Assert(blkno != InvalidBlockNumber);
     376             : 
     377          12 :         UnlockReleaseBuffer(buffer);
     378             :     }
     379             : 
     380             :     /* Iterate all posting tree leaves using rightlinks and vacuum them */
     381             :     while (true)
     382             :     {
     383          42 :         oldCxt = MemoryContextSwitchTo(gvs->tmpCxt);
     384          42 :         ginVacuumPostingTreeLeaf(gvs->index, buffer, gvs);
     385          42 :         MemoryContextSwitchTo(oldCxt);
     386          42 :         MemoryContextReset(gvs->tmpCxt);
     387             : 
     388          42 :         if (GinDataLeafPageIsEmpty(page))
     389          30 :             hasVoidPage = true;
     390             : 
     391          42 :         blkno = GinPageGetOpaque(page)->rightlink;
     392             : 
     393          42 :         UnlockReleaseBuffer(buffer);
     394             : 
     395          42 :         if (blkno == InvalidBlockNumber)
     396          18 :             break;
     397             : 
     398          24 :         buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
     399             :                                     RBM_NORMAL, gvs->strategy);
     400          24 :         LockBuffer(buffer, GIN_EXCLUSIVE);
     401          24 :         page = BufferGetPage(buffer);
     402             :     }
     403             : 
     404          18 :     return hasVoidPage;
     405             : }
     406             : 
     407             : static void
     408          18 : ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
     409             : {
     410          18 :     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             :         Buffer      buffer;
     417             :         DataPageDeleteStack root,
     418             :                    *ptr,
     419             :                    *tmp;
     420             : 
     421          12 :         buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, rootBlkno,
     422             :                                     RBM_NORMAL, gvs->strategy);
     423             : 
     424             :         /*
     425             :          * Lock posting tree root for cleanup to ensure there are no
     426             :          * concurrent inserts.
     427             :          */
     428          12 :         LockBufferForCleanup(buffer);
     429             : 
     430          12 :         memset(&root, 0, sizeof(DataPageDeleteStack));
     431          12 :         root.leftBuffer = InvalidBuffer;
     432          12 :         root.isRoot = true;
     433             : 
     434          12 :         ginScanToDelete(gvs, rootBlkno, true, &root, InvalidOffsetNumber);
     435             : 
     436          12 :         ptr = root.child;
     437             : 
     438          24 :         while (ptr)
     439             :         {
     440          12 :             tmp = ptr->child;
     441          12 :             pfree(ptr);
     442          12 :             ptr = tmp;
     443             :         }
     444             : 
     445          12 :         UnlockReleaseBuffer(buffer);
     446             :     }
     447          18 : }
     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        1730 : ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint32 *nroot)
     456             : {
     457        1730 :     Page        origpage = BufferGetPage(buffer),
     458             :                 tmppage;
     459             :     OffsetNumber i,
     460        1730 :                 maxoff = PageGetMaxOffsetNumber(origpage);
     461             : 
     462        1730 :     tmppage = origpage;
     463             : 
     464        1730 :     *nroot = 0;
     465             : 
     466      241790 :     for (i = FirstOffsetNumber; i <= maxoff; i++)
     467             :     {
     468      240060 :         IndexTuple  itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
     469             : 
     470      240060 :         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          18 :             roots[*nroot] = GinGetDownlink(itup);
     477          18 :             (*nroot)++;
     478             :         }
     479      240042 :         else if (GinGetNPosting(itup) > 0)
     480             :         {
     481             :             int         nitems;
     482             :             ItemPointer items_orig;
     483             :             bool        free_items_orig;
     484             :             ItemPointer items;
     485             : 
     486             :             /* Get list of item pointers from the tuple. */
     487      240042 :             if (GinItupIsCompressed(itup))
     488             :             {
     489      240042 :                 items_orig = ginPostingListDecode((GinPostingList *) GinGetPosting(itup), &nitems);
     490      240042 :                 free_items_orig = true;
     491             :             }
     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      240042 :             items = ginVacuumItemPointers(gvs, items_orig, nitems, &nitems);
     501             : 
     502      240042 :             if (free_items_orig)
     503      240042 :                 pfree(items_orig);
     504             : 
     505             :             /* If any item pointers were removed, recreate the tuple. */
     506      240042 :             if (items)
     507             :             {
     508             :                 OffsetNumber attnum;
     509             :                 Datum       key;
     510             :                 GinNullCategory category;
     511             :                 GinPostingList *plist;
     512             :                 int         plistsize;
     513             : 
     514      240018 :                 if (nitems > 0)
     515             :                 {
     516          36 :                     plist = ginCompressPostingList(items, nitems, GinMaxItemSize, NULL);
     517          36 :                     plistsize = SizeOfGinPostingList(plist);
     518             :                 }
     519             :                 else
     520             :                 {
     521      239982 :                     plist = NULL;
     522      239982 :                     plistsize = 0;
     523             :                 }
     524             : 
     525             :                 /*
     526             :                  * if we already created a temporary page, make changes in
     527             :                  * place
     528             :                  */
     529      240018 :                 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        1728 :                     tmppage = PageGetTempPageCopy(origpage);
     536             : 
     537             :                     /* set itup pointer to new page */
     538        1728 :                     itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
     539             :                 }
     540             : 
     541      240018 :                 attnum = gintuple_get_attrnum(&gvs->ginstate, itup);
     542      240018 :                 key = gintuple_get_key(&gvs->ginstate, itup, &category);
     543      240018 :                 itup = GinFormTuple(&gvs->ginstate, attnum, key, category,
     544             :                                     (char *) plist, plistsize,
     545             :                                     nitems, true);
     546      240018 :                 if (plist)
     547          36 :                     pfree(plist);
     548      240018 :                 PageIndexTupleDelete(tmppage, i);
     549             : 
     550      240018 :                 if (PageAddItem(tmppage, (Item) 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      240018 :                 pfree(itup);
     555      240018 :                 pfree(items);
     556             :             }
     557             :         }
     558             :     }
     559             : 
     560        1730 :     return (tmppage == origpage) ? NULL : tmppage;
     561             : }
     562             : 
     563             : IndexBulkDeleteResult *
     564          14 : ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
     565             :               IndexBulkDeleteCallback callback, void *callback_state)
     566             : {
     567          14 :     Relation    index = info->index;
     568          14 :     BlockNumber blkno = GIN_ROOT_BLKNO;
     569             :     GinVacuumState gvs;
     570             :     Buffer      buffer;
     571             :     BlockNumber rootOfPostingTree[BLCKSZ / (sizeof(IndexTupleData) + sizeof(ItemId))];
     572             :     uint32      nRoot;
     573             : 
     574          14 :     gvs.tmpCxt = AllocSetContextCreate(CurrentMemoryContext,
     575             :                                        "Gin vacuum temporary context",
     576             :                                        ALLOCSET_DEFAULT_SIZES);
     577          14 :     gvs.index = index;
     578          14 :     gvs.callback = callback;
     579          14 :     gvs.callback_state = callback_state;
     580          14 :     gvs.strategy = info->strategy;
     581          14 :     initGinState(&gvs.ginstate, index);
     582             : 
     583             :     /* first time through? */
     584          14 :     if (stats == NULL)
     585             :     {
     586             :         /* Yes, so initialize stats to zeroes */
     587          14 :         stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
     588             : 
     589             :         /*
     590             :          * and cleanup any pending inserts
     591             :          */
     592          14 :         ginInsertCleanup(&gvs.ginstate, !AmAutoVacuumWorkerProcess(),
     593             :                          false, true, stats);
     594             :     }
     595             : 
     596             :     /* we'll re-count the tuples each time */
     597          14 :     stats->num_index_tuples = 0;
     598          14 :     gvs.result = stats;
     599             : 
     600          14 :     buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
     601             :                                 RBM_NORMAL, info->strategy);
     602             : 
     603             :     /* find leaf page */
     604             :     for (;;)
     605           6 :     {
     606          20 :         Page        page = BufferGetPage(buffer);
     607             :         IndexTuple  itup;
     608             : 
     609          20 :         LockBuffer(buffer, GIN_SHARE);
     610             : 
     611             :         Assert(!GinPageIsData(page));
     612             : 
     613          20 :         if (GinPageIsLeaf(page))
     614             :         {
     615          14 :             LockBuffer(buffer, GIN_UNLOCK);
     616          14 :             LockBuffer(buffer, GIN_EXCLUSIVE);
     617             : 
     618          14 :             if (blkno == GIN_ROOT_BLKNO && !GinPageIsLeaf(page))
     619             :             {
     620           0 :                 LockBuffer(buffer, GIN_UNLOCK);
     621           0 :                 continue;       /* check it one more */
     622             :             }
     623          14 :             break;
     624             :         }
     625             : 
     626             :         Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
     627             : 
     628           6 :         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
     629           6 :         blkno = GinGetDownlink(itup);
     630             :         Assert(blkno != InvalidBlockNumber);
     631             : 
     632           6 :         UnlockReleaseBuffer(buffer);
     633           6 :         buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
     634             :                                     RBM_NORMAL, info->strategy);
     635             :     }
     636             : 
     637             :     /* right now we found leftmost page in entry's BTree */
     638             : 
     639             :     for (;;)
     640        1716 :     {
     641        1730 :         Page        page = BufferGetPage(buffer);
     642             :         Page        resPage;
     643             :         uint32      i;
     644             : 
     645             :         Assert(!GinPageIsData(page));
     646             : 
     647        1730 :         resPage = ginVacuumEntryPage(&gvs, buffer, rootOfPostingTree, &nRoot);
     648             : 
     649        1730 :         blkno = GinPageGetOpaque(page)->rightlink;
     650             : 
     651        1730 :         if (resPage)
     652             :         {
     653        1728 :             START_CRIT_SECTION();
     654        1728 :             PageRestoreTempPage(resPage, page);
     655        1728 :             MarkBufferDirty(buffer);
     656        1728 :             xlogVacuumPage(gvs.index, buffer);
     657        1728 :             UnlockReleaseBuffer(buffer);
     658        1728 :             END_CRIT_SECTION();
     659             :         }
     660             :         else
     661             :         {
     662           2 :             UnlockReleaseBuffer(buffer);
     663             :         }
     664             : 
     665        1730 :         vacuum_delay_point();
     666             : 
     667        1748 :         for (i = 0; i < nRoot; i++)
     668             :         {
     669          18 :             ginVacuumPostingTree(&gvs, rootOfPostingTree[i]);
     670          18 :             vacuum_delay_point();
     671             :         }
     672             : 
     673        1730 :         if (blkno == InvalidBlockNumber)    /* rightmost page */
     674          14 :             break;
     675             : 
     676        1716 :         buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
     677             :                                     RBM_NORMAL, info->strategy);
     678        1716 :         LockBuffer(buffer, GIN_EXCLUSIVE);
     679             :     }
     680             : 
     681          14 :     MemoryContextDelete(gvs.tmpCxt);
     682             : 
     683          14 :     return gvs.result;
     684             : }
     685             : 
     686             : IndexBulkDeleteResult *
     687          62 : ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
     688             : {
     689          62 :     Relation    index = info->index;
     690             :     bool        needLock;
     691             :     BlockNumber npages,
     692             :                 blkno;
     693             :     BlockNumber totFreePages;
     694             :     GinState    ginstate;
     695             :     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          62 :     if (info->analyze_only)
     702             :     {
     703           6 :         if (AmAutoVacuumWorkerProcess())
     704             :         {
     705           0 :             initGinState(&ginstate, index);
     706           0 :             ginInsertCleanup(&ginstate, false, true, true, stats);
     707             :         }
     708           6 :         return stats;
     709             :     }
     710             : 
     711             :     /*
     712             :      * Set up all-zero stats and cleanup pending inserts if ginbulkdelete
     713             :      * wasn't called
     714             :      */
     715          56 :     if (stats == NULL)
     716             :     {
     717          42 :         stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
     718          42 :         initGinState(&ginstate, index);
     719          42 :         ginInsertCleanup(&ginstate, !AmAutoVacuumWorkerProcess(),
     720             :                          false, true, stats);
     721             :     }
     722             : 
     723          56 :     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          56 :     stats->num_index_tuples = Max(info->num_heap_tuples, 0);
     731          56 :     stats->estimated_count = info->estimated_count;
     732             : 
     733             :     /*
     734             :      * Need lock unless it's local to this backend.
     735             :      */
     736          56 :     needLock = !RELATION_IS_LOCAL(index);
     737             : 
     738          56 :     if (needLock)
     739          50 :         LockRelationForExtension(index, ExclusiveLock);
     740          56 :     npages = RelationGetNumberOfBlocks(index);
     741          56 :     if (needLock)
     742          50 :         UnlockRelationForExtension(index, ExclusiveLock);
     743             : 
     744          56 :     totFreePages = 0;
     745             : 
     746        9204 :     for (blkno = GIN_ROOT_BLKNO; blkno < npages; blkno++)
     747             :     {
     748             :         Buffer      buffer;
     749             :         Page        page;
     750             : 
     751        9148 :         vacuum_delay_point();
     752             : 
     753        9148 :         buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
     754             :                                     RBM_NORMAL, info->strategy);
     755        9148 :         LockBuffer(buffer, GIN_SHARE);
     756        9148 :         page = (Page) BufferGetPage(buffer);
     757             : 
     758        9148 :         if (GinPageIsRecyclable(page))
     759             :         {
     760             :             Assert(blkno != GIN_ROOT_BLKNO);
     761        4658 :             RecordFreeIndexPage(index, blkno);
     762        4658 :             totFreePages++;
     763             :         }
     764        4490 :         else if (GinPageIsData(page))
     765             :         {
     766         210 :             idxStat.nDataPages++;
     767             :         }
     768        4280 :         else if (!GinPageIsList(page))
     769             :         {
     770        4280 :             idxStat.nEntryPages++;
     771             : 
     772        4280 :             if (GinPageIsLeaf(page))
     773        4250 :                 idxStat.nEntries += PageGetMaxOffsetNumber(page);
     774             :         }
     775             : 
     776        9148 :         UnlockReleaseBuffer(buffer);
     777             :     }
     778             : 
     779             :     /* Update the metapage with accurate page and entry counts */
     780          56 :     idxStat.nTotalPages = npages;
     781          56 :     ginUpdateStats(info->index, &idxStat, false);
     782             : 
     783             :     /* Finally, vacuum the FSM */
     784          56 :     IndexFreeSpaceMapVacuum(info->index);
     785             : 
     786          56 :     stats->pages_free = totFreePages;
     787             : 
     788          56 :     if (needLock)
     789          50 :         LockRelationForExtension(index, ExclusiveLock);
     790          56 :     stats->num_pages = RelationGetNumberOfBlocks(index);
     791          56 :     if (needLock)
     792          50 :         UnlockRelationForExtension(index, ExclusiveLock);
     793             : 
     794          56 :     return stats;
     795             : }
     796             : 
     797             : /*
     798             :  * Return whether Page can safely be recycled.
     799             :  */
     800             : bool
     801        9252 : GinPageIsRecyclable(Page page)
     802             : {
     803             :     TransactionId delete_xid;
     804             : 
     805        9252 :     if (PageIsNew(page))
     806           0 :         return true;
     807             : 
     808        9252 :     if (!GinPageIsDeleted(page))
     809        4478 :         return false;
     810             : 
     811        4774 :     delete_xid = GinPageGetDeleteXid(page);
     812             : 
     813        4774 :     if (!TransactionIdIsValid(delete_xid))
     814        4762 :         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          12 :     return GlobalVisCheckRemovableXid(NULL, delete_xid);
     821             : }

Generated by: LCOV version 1.14