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

Generated by: LCOV version 1.14