LCOV - code coverage report
Current view: top level - src/backend/access/gin - ginvacuum.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 93.4 % 290 271
Test Date: 2026-02-28 09:15:17 Functions: 100.0 % 10 10
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-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       125665 : ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
      48              :                       int nitem, int *nremaining)
      49              : {
      50              :     int         i,
      51       125665 :                 remaining = 0;
      52       125665 :     ItemPointer tmpitems = NULL;
      53              : 
      54              :     /*
      55              :      * Iterate over TIDs array
      56              :      */
      57       539235 :     for (i = 0; i < nitem; i++)
      58              :     {
      59       413570 :         if (gvs->callback(items + i, gvs->callback_state))
      60              :         {
      61       371841 :             gvs->result->tuples_removed += 1;
      62       371841 :             if (!tmpitems)
      63              :             {
      64              :                 /*
      65              :                  * First TID to be deleted: allocate memory to hold the
      66              :                  * remaining items.
      67              :                  */
      68       120447 :                 tmpitems = palloc_array(ItemPointerData, nitem);
      69       120447 :                 memcpy(tmpitems, items, sizeof(ItemPointerData) * i);
      70              :             }
      71              :         }
      72              :         else
      73              :         {
      74        41729 :             gvs->result->num_index_tuples += 1;
      75        41729 :             if (tmpitems)
      76         6426 :                 tmpitems[remaining] = items[i];
      77        41729 :             remaining++;
      78              :         }
      79              :     }
      80              : 
      81       125665 :     *nremaining = remaining;
      82       125665 :     return tmpitems;
      83              : }
      84              : 
      85              : /*
      86              :  * Create a WAL record for vacuuming entry tree leaf page.
      87              :  */
      88              : static void
      89          863 : xlogVacuumPage(Relation index, Buffer buffer)
      90              : {
      91          863 :     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          863 :     if (!RelationNeedsWAL(index))
      99          861 :         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            2 :     XLogBeginInsert();
     106            2 :     XLogRegisterBuffer(0, buffer, REGBUF_FORCE_IMAGE | REGBUF_STANDARD);
     107              : 
     108            2 :     recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_VACUUM_PAGE);
     109            2 :     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            6 : 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            6 :     lBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, leftBlkno,
     146              :                                  RBM_NORMAL, gvs->strategy);
     147            6 :     dBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, deleteBlkno,
     148              :                                  RBM_NORMAL, gvs->strategy);
     149            6 :     pBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, parentBlkno,
     150              :                                  RBM_NORMAL, gvs->strategy);
     151              : 
     152            6 :     page = BufferGetPage(dBuffer);
     153            6 :     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            6 :     PredicateLockPageCombine(gvs->index, deleteBlkno, rightlink);
     160              : 
     161            6 :     START_CRIT_SECTION();
     162              : 
     163              :     /* Unlink the page by changing left sibling's rightlink */
     164            6 :     page = BufferGetPage(lBuffer);
     165            6 :     GinPageGetOpaque(page)->rightlink = rightlink;
     166              : 
     167              :     /* Delete downlink from parent */
     168            6 :     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            6 :     GinPageDeletePostingItem(parentPage, myoff);
     178              : 
     179            6 :     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            6 :     GinPageSetDeleted(page);
     191            6 :     GinPageSetDeleteXid(page, ReadNextTransactionId());
     192              : 
     193            6 :     MarkBufferDirty(pBuffer);
     194            6 :     MarkBufferDirty(lBuffer);
     195            6 :     MarkBufferDirty(dBuffer);
     196              : 
     197            6 :     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(&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            6 :     END_CRIT_SECTION();
     228              : 
     229            6 :     ReleaseBuffer(pBuffer);
     230            6 :     ReleaseBuffer(lBuffer);
     231            6 :     ReleaseBuffer(dBuffer);
     232              : 
     233            6 :     gvs->result->pages_newly_deleted++;
     234            6 :     gvs->result->pages_deleted++;
     235            6 : }
     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           24 : ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
     247              :                 DataPageDeleteStack *parent, OffsetNumber myoff)
     248              : {
     249              :     DataPageDeleteStack *me;
     250              :     Buffer      buffer;
     251              :     Page        page;
     252           24 :     bool        meDelete = false;
     253              :     bool        isempty;
     254              : 
     255           24 :     if (isRoot)
     256              :     {
     257            6 :         me = parent;
     258              :     }
     259              :     else
     260              :     {
     261           18 :         if (!parent->child)
     262              :         {
     263            6 :             me = palloc0_object(DataPageDeleteStack);
     264            6 :             me->parent = parent;
     265            6 :             parent->child = me;
     266            6 :             me->leftBuffer = InvalidBuffer;
     267              :         }
     268              :         else
     269           12 :             me = parent->child;
     270              :     }
     271              : 
     272           24 :     buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
     273              :                                 RBM_NORMAL, gvs->strategy);
     274              : 
     275           24 :     if (!isRoot)
     276           18 :         LockBuffer(buffer, GIN_EXCLUSIVE);
     277              : 
     278           24 :     page = BufferGetPage(buffer);
     279              : 
     280              :     Assert(GinPageIsData(page));
     281              : 
     282           24 :     if (!GinPageIsLeaf(page))
     283              :     {
     284              :         OffsetNumber i;
     285              : 
     286            6 :         me->blkno = blkno;
     287           24 :         for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
     288              :         {
     289           18 :             PostingItem *pitem = GinDataPageGetPostingItem(page, i);
     290              : 
     291           18 :             if (ginScanToDelete(gvs, PostingItemGetBlockNumber(pitem), false, me, i))
     292            6 :                 i--;
     293              :         }
     294              : 
     295            6 :         if (GinPageRightMost(page) && BufferIsValid(me->child->leftBuffer))
     296              :         {
     297            6 :             UnlockReleaseBuffer(me->child->leftBuffer);
     298            6 :             me->child->leftBuffer = InvalidBuffer;
     299              :         }
     300              :     }
     301              : 
     302           24 :     if (GinPageIsLeaf(page))
     303           18 :         isempty = GinDataLeafPageIsEmpty(page);
     304              :     else
     305            6 :         isempty = GinPageGetOpaque(page)->maxoff < FirstOffsetNumber;
     306              : 
     307           24 :     if (isempty)
     308              :     {
     309              :         /* we never delete the left- or rightmost branch */
     310           15 :         if (BufferIsValid(me->leftBuffer) && !GinPageRightMost(page))
     311              :         {
     312              :             Assert(!isRoot);
     313            6 :             ginDeletePage(gvs, blkno, BufferGetBlockNumber(me->leftBuffer),
     314            6 :                           me->parent->blkno, myoff, me->parent->isRoot);
     315            6 :             meDelete = true;
     316              :         }
     317              :     }
     318              : 
     319           24 :     if (!meDelete)
     320              :     {
     321           18 :         if (BufferIsValid(me->leftBuffer))
     322            6 :             UnlockReleaseBuffer(me->leftBuffer);
     323           18 :         me->leftBuffer = buffer;
     324              :     }
     325              :     else
     326              :     {
     327            6 :         if (!isRoot)
     328            6 :             LockBuffer(buffer, GIN_UNLOCK);
     329              : 
     330            6 :         ReleaseBuffer(buffer);
     331              :     }
     332              : 
     333           24 :     if (isRoot)
     334            6 :         ReleaseBuffer(buffer);
     335              : 
     336           24 :     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            9 : ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno)
     346              : {
     347              :     Buffer      buffer;
     348              :     Page        page;
     349            9 :     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            6 :     {
     355              :         PostingItem *pitem;
     356              : 
     357           15 :         buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
     358              :                                     RBM_NORMAL, gvs->strategy);
     359           15 :         LockBuffer(buffer, GIN_SHARE);
     360           15 :         page = BufferGetPage(buffer);
     361              : 
     362              :         Assert(GinPageIsData(page));
     363              : 
     364           15 :         if (GinPageIsLeaf(page))
     365              :         {
     366            9 :             LockBuffer(buffer, GIN_UNLOCK);
     367            9 :             LockBuffer(buffer, GIN_EXCLUSIVE);
     368            9 :             break;
     369              :         }
     370              : 
     371              :         Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
     372              : 
     373            6 :         pitem = GinDataPageGetPostingItem(page, FirstOffsetNumber);
     374            6 :         blkno = PostingItemGetBlockNumber(pitem);
     375              :         Assert(blkno != InvalidBlockNumber);
     376              : 
     377            6 :         UnlockReleaseBuffer(buffer);
     378              :     }
     379              : 
     380              :     /* Iterate all posting tree leaves using rightlinks and vacuum them */
     381              :     while (true)
     382              :     {
     383           21 :         oldCxt = MemoryContextSwitchTo(gvs->tmpCxt);
     384           21 :         ginVacuumPostingTreeLeaf(gvs->index, buffer, gvs);
     385           21 :         MemoryContextSwitchTo(oldCxt);
     386           21 :         MemoryContextReset(gvs->tmpCxt);
     387              : 
     388           21 :         if (GinDataLeafPageIsEmpty(page))
     389           15 :             hasVoidPage = true;
     390              : 
     391           21 :         blkno = GinPageGetOpaque(page)->rightlink;
     392              : 
     393           21 :         UnlockReleaseBuffer(buffer);
     394              : 
     395           21 :         if (blkno == InvalidBlockNumber)
     396            9 :             break;
     397              : 
     398           12 :         buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
     399              :                                     RBM_NORMAL, gvs->strategy);
     400           12 :         LockBuffer(buffer, GIN_EXCLUSIVE);
     401           12 :         page = BufferGetPage(buffer);
     402              :     }
     403              : 
     404            9 :     return hasVoidPage;
     405              : }
     406              : 
     407              : static void
     408            9 : ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
     409              : {
     410            9 :     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            6 :         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            6 :         LockBufferForCleanup(buffer);
     429              : 
     430            6 :         memset(&root, 0, sizeof(DataPageDeleteStack));
     431            6 :         root.leftBuffer = InvalidBuffer;
     432            6 :         root.isRoot = true;
     433              : 
     434            6 :         ginScanToDelete(gvs, rootBlkno, true, &root, InvalidOffsetNumber);
     435              : 
     436            6 :         ptr = root.child;
     437              : 
     438           12 :         while (ptr)
     439              :         {
     440            6 :             tmp = ptr->child;
     441            6 :             pfree(ptr);
     442            6 :             ptr = tmp;
     443              :         }
     444              : 
     445            6 :         UnlockReleaseBuffer(buffer);
     446              :     }
     447            9 : }
     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          923 : ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint32 *nroot)
     456              : {
     457          923 :     Page        origpage = BufferGetPage(buffer),
     458              :                 tmppage;
     459              :     OffsetNumber i,
     460          923 :                 maxoff = PageGetMaxOffsetNumber(origpage);
     461              : 
     462          923 :     tmppage = origpage;
     463              : 
     464          923 :     *nroot = 0;
     465              : 
     466       126147 :     for (i = FirstOffsetNumber; i <= maxoff; i++)
     467              :     {
     468       125224 :         IndexTuple  itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
     469              : 
     470       125224 :         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            9 :             roots[*nroot] = GinGetDownlink(itup);
     477            9 :             (*nroot)++;
     478              :         }
     479       125215 :         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       125215 :             if (GinItupIsCompressed(itup))
     488              :             {
     489       125215 :                 items_orig = ginPostingListDecode((GinPostingList *) GinGetPosting(itup), &nitems);
     490       125215 :                 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       125215 :             items = ginVacuumItemPointers(gvs, items_orig, nitems, &nitems);
     501              : 
     502       125215 :             if (free_items_orig)
     503       125215 :                 pfree(items_orig);
     504              : 
     505              :             /* If any item pointers were removed, recreate the tuple. */
     506       125215 :             if (items)
     507              :             {
     508              :                 OffsetNumber attnum;
     509              :                 Datum       key;
     510              :                 GinNullCategory category;
     511              :                 GinPostingList *plist;
     512              :                 int         plistsize;
     513              : 
     514       120006 :                 if (nitems > 0)
     515              :                 {
     516           15 :                     plist = ginCompressPostingList(items, nitems, GinMaxItemSize, NULL);
     517           15 :                     plistsize = SizeOfGinPostingList(plist);
     518              :                 }
     519              :                 else
     520              :                 {
     521       119991 :                     plist = NULL;
     522       119991 :                     plistsize = 0;
     523              :                 }
     524              : 
     525              :                 /*
     526              :                  * if we already created a temporary page, make changes in
     527              :                  * place
     528              :                  */
     529       120006 :                 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          863 :                     tmppage = PageGetTempPageCopy(origpage);
     536              : 
     537              :                     /* set itup pointer to new page */
     538          863 :                     itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
     539              :                 }
     540              : 
     541       120006 :                 attnum = gintuple_get_attrnum(&gvs->ginstate, itup);
     542       120006 :                 key = gintuple_get_key(&gvs->ginstate, itup, &category);
     543       120006 :                 itup = GinFormTuple(&gvs->ginstate, attnum, key, category,
     544              :                                     (char *) plist, plistsize,
     545              :                                     nitems, true);
     546       120006 :                 if (plist)
     547           15 :                     pfree(plist);
     548       120006 :                 PageIndexTupleDelete(tmppage, i);
     549              : 
     550       120006 :                 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       120006 :                 pfree(itup);
     555       120006 :                 pfree(items);
     556              :             }
     557              :         }
     558              :     }
     559              : 
     560          923 :     return (tmppage == origpage) ? NULL : tmppage;
     561              : }
     562              : 
     563              : IndexBulkDeleteResult *
     564           29 : ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
     565              :               IndexBulkDeleteCallback callback, void *callback_state)
     566              : {
     567           29 :     Relation    index = info->index;
     568           29 :     BlockNumber blkno = GIN_ROOT_BLKNO;
     569              :     GinVacuumState gvs;
     570              :     Buffer      buffer;
     571              :     BlockNumber rootOfPostingTree[BLCKSZ / (sizeof(IndexTupleData) + sizeof(ItemId))];
     572              :     uint32      nRoot;
     573              : 
     574           29 :     gvs.tmpCxt = AllocSetContextCreate(CurrentMemoryContext,
     575              :                                        "Gin vacuum temporary context",
     576              :                                        ALLOCSET_DEFAULT_SIZES);
     577           29 :     gvs.index = index;
     578           29 :     gvs.callback = callback;
     579           29 :     gvs.callback_state = callback_state;
     580           29 :     gvs.strategy = info->strategy;
     581           29 :     initGinState(&gvs.ginstate, index);
     582              : 
     583              :     /* first time through? */
     584           29 :     if (stats == NULL)
     585              :     {
     586              :         /* Yes, so initialize stats to zeroes */
     587           29 :         stats = palloc0_object(IndexBulkDeleteResult);
     588              : 
     589              :         /*
     590              :          * and cleanup any pending inserts
     591              :          */
     592           29 :         ginInsertCleanup(&gvs.ginstate, !AmAutoVacuumWorkerProcess(),
     593              :                          false, true, stats);
     594              :     }
     595              : 
     596              :     /* we'll re-count the tuples each time */
     597           29 :     stats->num_index_tuples = 0;
     598           29 :     gvs.result = stats;
     599              : 
     600           29 :     buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
     601              :                                 RBM_NORMAL, info->strategy);
     602              : 
     603              :     /* find leaf page */
     604              :     for (;;)
     605            6 :     {
     606           35 :         Page        page = BufferGetPage(buffer);
     607              :         IndexTuple  itup;
     608              : 
     609           35 :         LockBuffer(buffer, GIN_SHARE);
     610              : 
     611              :         Assert(!GinPageIsData(page));
     612              : 
     613           35 :         if (GinPageIsLeaf(page))
     614              :         {
     615           29 :             LockBuffer(buffer, GIN_UNLOCK);
     616           29 :             LockBuffer(buffer, GIN_EXCLUSIVE);
     617              : 
     618           29 :             if (blkno == GIN_ROOT_BLKNO && !GinPageIsLeaf(page))
     619              :             {
     620            0 :                 LockBuffer(buffer, GIN_UNLOCK);
     621            0 :                 continue;       /* check it one more */
     622              :             }
     623           29 :             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          894 :     {
     641          923 :         Page        page = BufferGetPage(buffer);
     642              :         Page        resPage;
     643              :         uint32      i;
     644              : 
     645              :         Assert(!GinPageIsData(page));
     646              : 
     647          923 :         resPage = ginVacuumEntryPage(&gvs, buffer, rootOfPostingTree, &nRoot);
     648              : 
     649          923 :         blkno = GinPageGetOpaque(page)->rightlink;
     650              : 
     651          923 :         if (resPage)
     652              :         {
     653          863 :             START_CRIT_SECTION();
     654          863 :             PageRestoreTempPage(resPage, page);
     655          863 :             MarkBufferDirty(buffer);
     656          863 :             xlogVacuumPage(gvs.index, buffer);
     657          863 :             END_CRIT_SECTION();
     658          863 :             UnlockReleaseBuffer(buffer);
     659              :         }
     660              :         else
     661              :         {
     662           60 :             UnlockReleaseBuffer(buffer);
     663              :         }
     664              : 
     665          923 :         vacuum_delay_point(false);
     666              : 
     667          932 :         for (i = 0; i < nRoot; i++)
     668              :         {
     669            9 :             ginVacuumPostingTree(&gvs, rootOfPostingTree[i]);
     670            9 :             vacuum_delay_point(false);
     671              :         }
     672              : 
     673          923 :         if (blkno == InvalidBlockNumber)    /* rightmost page */
     674           29 :             break;
     675              : 
     676          894 :         buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
     677              :                                     RBM_NORMAL, info->strategy);
     678          894 :         LockBuffer(buffer, GIN_EXCLUSIVE);
     679              :     }
     680              : 
     681           29 :     MemoryContextDelete(gvs.tmpCxt);
     682              : 
     683           29 :     return gvs.result;
     684              : }
     685              : 
     686              : IndexBulkDeleteResult *
     687           38 : ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
     688              : {
     689           38 :     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           38 :     if (info->analyze_only)
     702              :     {
     703           10 :         if (AmAutoVacuumWorkerProcess())
     704              :         {
     705            7 :             initGinState(&ginstate, index);
     706            7 :             ginInsertCleanup(&ginstate, false, true, true, stats);
     707              :         }
     708           10 :         return stats;
     709              :     }
     710              : 
     711              :     /*
     712              :      * Set up all-zero stats and cleanup pending inserts if ginbulkdelete
     713              :      * wasn't called
     714              :      */
     715           28 :     if (stats == NULL)
     716              :     {
     717           22 :         stats = palloc0_object(IndexBulkDeleteResult);
     718           22 :         initGinState(&ginstate, index);
     719           22 :         ginInsertCleanup(&ginstate, !AmAutoVacuumWorkerProcess(),
     720              :                          false, true, stats);
     721              :     }
     722              : 
     723           28 :     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           28 :     stats->num_index_tuples = Max(info->num_heap_tuples, 0);
     731           28 :     stats->estimated_count = info->estimated_count;
     732              : 
     733              :     /*
     734              :      * Need lock unless it's local to this backend.
     735              :      */
     736           28 :     needLock = !RELATION_IS_LOCAL(index);
     737              : 
     738           28 :     if (needLock)
     739           25 :         LockRelationForExtension(index, ExclusiveLock);
     740           28 :     npages = RelationGetNumberOfBlocks(index);
     741           28 :     if (needLock)
     742           25 :         UnlockRelationForExtension(index, ExclusiveLock);
     743              : 
     744           28 :     totFreePages = 0;
     745              : 
     746         4602 :     for (blkno = GIN_ROOT_BLKNO; blkno < npages; blkno++)
     747              :     {
     748              :         Buffer      buffer;
     749              :         Page        page;
     750              : 
     751         4574 :         vacuum_delay_point(false);
     752              : 
     753         4574 :         buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
     754              :                                     RBM_NORMAL, info->strategy);
     755         4574 :         LockBuffer(buffer, GIN_SHARE);
     756         4574 :         page = BufferGetPage(buffer);
     757              : 
     758         4574 :         if (GinPageIsRecyclable(page))
     759              :         {
     760              :             Assert(blkno != GIN_ROOT_BLKNO);
     761         2329 :             RecordFreeIndexPage(index, blkno);
     762         2329 :             totFreePages++;
     763              :         }
     764         2245 :         else if (GinPageIsData(page))
     765              :         {
     766          105 :             idxStat.nDataPages++;
     767              :         }
     768         2140 :         else if (!GinPageIsList(page))
     769              :         {
     770         2140 :             idxStat.nEntryPages++;
     771              : 
     772         2140 :             if (GinPageIsLeaf(page))
     773         2125 :                 idxStat.nEntries += PageGetMaxOffsetNumber(page);
     774              :         }
     775              : 
     776         4574 :         UnlockReleaseBuffer(buffer);
     777              :     }
     778              : 
     779              :     /* Update the metapage with accurate page and entry counts */
     780           28 :     idxStat.nTotalPages = npages;
     781           28 :     ginUpdateStats(info->index, &idxStat, false);
     782              : 
     783              :     /* Finally, vacuum the FSM */
     784           28 :     IndexFreeSpaceMapVacuum(info->index);
     785              : 
     786           28 :     stats->pages_free = totFreePages;
     787              : 
     788           28 :     if (needLock)
     789           25 :         LockRelationForExtension(index, ExclusiveLock);
     790           28 :     stats->num_pages = RelationGetNumberOfBlocks(index);
     791           28 :     if (needLock)
     792           25 :         UnlockRelationForExtension(index, ExclusiveLock);
     793              : 
     794           28 :     return stats;
     795              : }
     796              : 
     797              : /*
     798              :  * Return whether Page can safely be recycled.
     799              :  */
     800              : bool
     801         4626 : GinPageIsRecyclable(Page page)
     802              : {
     803              :     TransactionId delete_xid;
     804              : 
     805         4626 :     if (PageIsNew(page))
     806            0 :         return true;
     807              : 
     808         4626 :     if (!GinPageIsDeleted(page))
     809         2239 :         return false;
     810              : 
     811         2387 :     delete_xid = GinPageGetDeleteXid(page);
     812              : 
     813         2387 :     if (!TransactionIdIsValid(delete_xid))
     814         2381 :         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            6 :     return GlobalVisCheckRemovableXid(NULL, delete_xid);
     821              : }
        

Generated by: LCOV version 2.0-1