LCOV - differential code coverage report
Current view: top level - src/backend/access/gin - ginvacuum.c (source / functions) Coverage Total Hit UBC CBC
Current: Differential Code Coverage HEAD vs 15 Lines: 93.8 % 306 287 19 287
Current Date: 2023-04-08 15:15:32 Functions: 100.0 % 10 10 10
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

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

Generated by: LCOV version v1.16-55-g56c0a2a