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

Generated by: LCOV version 1.11