Header And Logo

PostgreSQL
| The world's most advanced open source database.

ginvacuum.c

Go to the documentation of this file.
00001 /*-------------------------------------------------------------------------
00002  *
00003  * ginvacuum.c
00004  *    delete & vacuum routines for the postgres GIN
00005  *
00006  *
00007  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
00008  * Portions Copyright (c) 1994, Regents of the University of California
00009  *
00010  * IDENTIFICATION
00011  *          src/backend/access/gin/ginvacuum.c
00012  *-------------------------------------------------------------------------
00013  */
00014 
00015 #include "postgres.h"
00016 
00017 #include "access/gin_private.h"
00018 #include "commands/vacuum.h"
00019 #include "miscadmin.h"
00020 #include "postmaster/autovacuum.h"
00021 #include "storage/indexfsm.h"
00022 #include "storage/lmgr.h"
00023 
00024 typedef struct
00025 {
00026     Relation    index;
00027     IndexBulkDeleteResult *result;
00028     IndexBulkDeleteCallback callback;
00029     void       *callback_state;
00030     GinState    ginstate;
00031     BufferAccessStrategy strategy;
00032 } GinVacuumState;
00033 
00034 
00035 /*
00036  * Cleans array of ItemPointer (removes dead pointers)
00037  * Results are always stored in *cleaned, which will be allocated
00038  * if it's needed. In case of *cleaned!=NULL caller is responsible to
00039  * have allocated enough space. *cleaned and items may point to the same
00040  * memory address.
00041  */
00042 
00043 static uint32
00044 ginVacuumPostingList(GinVacuumState *gvs, ItemPointerData *items, uint32 nitem, ItemPointerData **cleaned)
00045 {
00046     uint32      i,
00047                 j = 0;
00048 
00049     /*
00050      * just scan over ItemPointer array
00051      */
00052 
00053     for (i = 0; i < nitem; i++)
00054     {
00055         if (gvs->callback(items + i, gvs->callback_state))
00056         {
00057             gvs->result->tuples_removed += 1;
00058             if (!*cleaned)
00059             {
00060                 *cleaned = (ItemPointerData *) palloc(sizeof(ItemPointerData) * nitem);
00061                 if (i != 0)
00062                     memcpy(*cleaned, items, sizeof(ItemPointerData) * i);
00063             }
00064         }
00065         else
00066         {
00067             gvs->result->num_index_tuples += 1;
00068             if (i != j)
00069                 (*cleaned)[j] = items[i];
00070             j++;
00071         }
00072     }
00073 
00074     return j;
00075 }
00076 
00077 /*
00078  * fills WAL record for vacuum leaf page
00079  */
00080 static void
00081 xlogVacuumPage(Relation index, Buffer buffer)
00082 {
00083     Page        page = BufferGetPage(buffer);
00084     XLogRecPtr  recptr;
00085     XLogRecData rdata[3];
00086     ginxlogVacuumPage data;
00087     char       *backup;
00088     char        itups[BLCKSZ];
00089     uint32      len = 0;
00090 
00091     Assert(GinPageIsLeaf(page));
00092 
00093     if (!RelationNeedsWAL(index))
00094         return;
00095 
00096     data.node = index->rd_node;
00097     data.blkno = BufferGetBlockNumber(buffer);
00098 
00099     if (GinPageIsData(page))
00100     {
00101         backup = GinDataPageGetData(page);
00102         data.nitem = GinPageGetOpaque(page)->maxoff;
00103         if (data.nitem)
00104             len = MAXALIGN(sizeof(ItemPointerData) * data.nitem);
00105     }
00106     else
00107     {
00108         char       *ptr;
00109         OffsetNumber i;
00110 
00111         ptr = backup = itups;
00112         for (i = FirstOffsetNumber; i <= PageGetMaxOffsetNumber(page); i++)
00113         {
00114             IndexTuple  itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
00115 
00116             memcpy(ptr, itup, IndexTupleSize(itup));
00117             ptr += MAXALIGN(IndexTupleSize(itup));
00118         }
00119 
00120         data.nitem = PageGetMaxOffsetNumber(page);
00121         len = ptr - backup;
00122     }
00123 
00124     rdata[0].buffer = buffer;
00125     rdata[0].buffer_std = (GinPageIsData(page)) ? FALSE : TRUE;
00126     rdata[0].len = 0;
00127     rdata[0].data = NULL;
00128     rdata[0].next = rdata + 1;
00129 
00130     rdata[1].buffer = InvalidBuffer;
00131     rdata[1].len = sizeof(ginxlogVacuumPage);
00132     rdata[1].data = (char *) &data;
00133 
00134     if (len == 0)
00135     {
00136         rdata[1].next = NULL;
00137     }
00138     else
00139     {
00140         rdata[1].next = rdata + 2;
00141 
00142         rdata[2].buffer = InvalidBuffer;
00143         rdata[2].len = len;
00144         rdata[2].data = backup;
00145         rdata[2].next = NULL;
00146     }
00147 
00148     recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_VACUUM_PAGE, rdata);
00149     PageSetLSN(page, recptr);
00150 }
00151 
00152 static bool
00153 ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno, bool isRoot, Buffer *rootBuffer)
00154 {
00155     Buffer      buffer;
00156     Page        page;
00157     bool        hasVoidPage = FALSE;
00158 
00159     buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
00160                                 RBM_NORMAL, gvs->strategy);
00161     page = BufferGetPage(buffer);
00162 
00163     /*
00164      * We should be sure that we don't concurrent with inserts, insert process
00165      * never release root page until end (but it can unlock it and lock
00166      * again). New scan can't start but previously started ones work
00167      * concurrently.
00168      */
00169 
00170     if (isRoot)
00171         LockBufferForCleanup(buffer);
00172     else
00173         LockBuffer(buffer, GIN_EXCLUSIVE);
00174 
00175     Assert(GinPageIsData(page));
00176 
00177     if (GinPageIsLeaf(page))
00178     {
00179         OffsetNumber newMaxOff,
00180                     oldMaxOff = GinPageGetOpaque(page)->maxoff;
00181         ItemPointerData *cleaned = NULL;
00182 
00183         newMaxOff = ginVacuumPostingList(gvs,
00184                 (ItemPointer) GinDataPageGetData(page), oldMaxOff, &cleaned);
00185 
00186         /* saves changes about deleted tuple ... */
00187         if (oldMaxOff != newMaxOff)
00188         {
00189             START_CRIT_SECTION();
00190 
00191             if (newMaxOff > 0)
00192                 memcpy(GinDataPageGetData(page), cleaned, sizeof(ItemPointerData) * newMaxOff);
00193             pfree(cleaned);
00194             GinPageGetOpaque(page)->maxoff = newMaxOff;
00195 
00196             MarkBufferDirty(buffer);
00197             xlogVacuumPage(gvs->index, buffer);
00198 
00199             END_CRIT_SECTION();
00200 
00201             /* if root is a leaf page, we don't desire further processing */
00202             if (!isRoot && GinPageGetOpaque(page)->maxoff < FirstOffsetNumber)
00203                 hasVoidPage = TRUE;
00204         }
00205     }
00206     else
00207     {
00208         OffsetNumber i;
00209         bool        isChildHasVoid = FALSE;
00210 
00211         for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
00212         {
00213             PostingItem *pitem = (PostingItem *) GinDataPageGetItem(page, i);
00214 
00215             if (ginVacuumPostingTreeLeaves(gvs, PostingItemGetBlockNumber(pitem), FALSE, NULL))
00216                 isChildHasVoid = TRUE;
00217         }
00218 
00219         if (isChildHasVoid)
00220             hasVoidPage = TRUE;
00221     }
00222 
00223     /*
00224      * if we have root and theres void pages in tree, then we don't release
00225      * lock to go further processing and guarantee that tree is unused
00226      */
00227     if (!(isRoot && hasVoidPage))
00228     {
00229         UnlockReleaseBuffer(buffer);
00230     }
00231     else
00232     {
00233         Assert(rootBuffer);
00234         *rootBuffer = buffer;
00235     }
00236 
00237     return hasVoidPage;
00238 }
00239 
00240 static void
00241 ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkno,
00242               BlockNumber parentBlkno, OffsetNumber myoff, bool isParentRoot)
00243 {
00244     Buffer      dBuffer;
00245     Buffer      lBuffer;
00246     Buffer      pBuffer;
00247     Page        page,
00248                 parentPage;
00249 
00250     dBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, deleteBlkno,
00251                                  RBM_NORMAL, gvs->strategy);
00252 
00253     if (leftBlkno != InvalidBlockNumber)
00254         lBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, leftBlkno,
00255                                      RBM_NORMAL, gvs->strategy);
00256     else
00257         lBuffer = InvalidBuffer;
00258 
00259     pBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, parentBlkno,
00260                                  RBM_NORMAL, gvs->strategy);
00261 
00262     LockBuffer(dBuffer, GIN_EXCLUSIVE);
00263     if (!isParentRoot)          /* parent is already locked by
00264                                  * LockBufferForCleanup() */
00265         LockBuffer(pBuffer, GIN_EXCLUSIVE);
00266     if (leftBlkno != InvalidBlockNumber)
00267         LockBuffer(lBuffer, GIN_EXCLUSIVE);
00268 
00269     START_CRIT_SECTION();
00270 
00271     if (leftBlkno != InvalidBlockNumber)
00272     {
00273         BlockNumber rightlink;
00274 
00275         page = BufferGetPage(dBuffer);
00276         rightlink = GinPageGetOpaque(page)->rightlink;
00277 
00278         page = BufferGetPage(lBuffer);
00279         GinPageGetOpaque(page)->rightlink = rightlink;
00280     }
00281 
00282     parentPage = BufferGetPage(pBuffer);
00283 #ifdef USE_ASSERT_CHECKING
00284     do
00285     {
00286         PostingItem *tod = (PostingItem *) GinDataPageGetItem(parentPage, myoff);
00287 
00288         Assert(PostingItemGetBlockNumber(tod) == deleteBlkno);
00289     } while (0);
00290 #endif
00291     GinPageDeletePostingItem(parentPage, myoff);
00292 
00293     page = BufferGetPage(dBuffer);
00294 
00295     /*
00296      * we shouldn't change rightlink field to save workability of running
00297      * search scan
00298      */
00299     GinPageGetOpaque(page)->flags = GIN_DELETED;
00300 
00301     MarkBufferDirty(pBuffer);
00302     if (leftBlkno != InvalidBlockNumber)
00303         MarkBufferDirty(lBuffer);
00304     MarkBufferDirty(dBuffer);
00305 
00306     if (RelationNeedsWAL(gvs->index))
00307     {
00308         XLogRecPtr  recptr;
00309         XLogRecData rdata[4];
00310         ginxlogDeletePage data;
00311         int         n;
00312 
00313         data.node = gvs->index->rd_node;
00314         data.blkno = deleteBlkno;
00315         data.parentBlkno = parentBlkno;
00316         data.parentOffset = myoff;
00317         data.leftBlkno = leftBlkno;
00318         data.rightLink = GinPageGetOpaque(page)->rightlink;
00319 
00320         rdata[0].buffer = dBuffer;
00321         rdata[0].buffer_std = FALSE;
00322         rdata[0].data = NULL;
00323         rdata[0].len = 0;
00324         rdata[0].next = rdata + 1;
00325 
00326         rdata[1].buffer = pBuffer;
00327         rdata[1].buffer_std = FALSE;
00328         rdata[1].data = NULL;
00329         rdata[1].len = 0;
00330         rdata[1].next = rdata + 2;
00331 
00332         if (leftBlkno != InvalidBlockNumber)
00333         {
00334             rdata[2].buffer = lBuffer;
00335             rdata[2].buffer_std = FALSE;
00336             rdata[2].data = NULL;
00337             rdata[2].len = 0;
00338             rdata[2].next = rdata + 3;
00339             n = 3;
00340         }
00341         else
00342             n = 2;
00343 
00344         rdata[n].buffer = InvalidBuffer;
00345         rdata[n].buffer_std = FALSE;
00346         rdata[n].len = sizeof(ginxlogDeletePage);
00347         rdata[n].data = (char *) &data;
00348         rdata[n].next = NULL;
00349 
00350         recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_DELETE_PAGE, rdata);
00351         PageSetLSN(page, recptr);
00352         PageSetLSN(parentPage, recptr);
00353         if (leftBlkno != InvalidBlockNumber)
00354         {
00355             page = BufferGetPage(lBuffer);
00356             PageSetLSN(page, recptr);
00357         }
00358     }
00359 
00360     if (!isParentRoot)
00361         LockBuffer(pBuffer, GIN_UNLOCK);
00362     ReleaseBuffer(pBuffer);
00363 
00364     if (leftBlkno != InvalidBlockNumber)
00365         UnlockReleaseBuffer(lBuffer);
00366 
00367     UnlockReleaseBuffer(dBuffer);
00368 
00369     END_CRIT_SECTION();
00370 
00371     gvs->result->pages_deleted++;
00372 }
00373 
00374 typedef struct DataPageDeleteStack
00375 {
00376     struct DataPageDeleteStack *child;
00377     struct DataPageDeleteStack *parent;
00378 
00379     BlockNumber blkno;          /* current block number */
00380     BlockNumber leftBlkno;      /* rightest non-deleted page on left */
00381     bool        isRoot;
00382 } DataPageDeleteStack;
00383 
00384 /*
00385  * scans posting tree and deletes empty pages
00386  */
00387 static bool
00388 ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot, DataPageDeleteStack *parent, OffsetNumber myoff)
00389 {
00390     DataPageDeleteStack *me;
00391     Buffer      buffer;
00392     Page        page;
00393     bool        meDelete = FALSE;
00394 
00395     if (isRoot)
00396     {
00397         me = parent;
00398     }
00399     else
00400     {
00401         if (!parent->child)
00402         {
00403             me = (DataPageDeleteStack *) palloc0(sizeof(DataPageDeleteStack));
00404             me->parent = parent;
00405             parent->child = me;
00406             me->leftBlkno = InvalidBlockNumber;
00407         }
00408         else
00409             me = parent->child;
00410     }
00411 
00412     buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
00413                                 RBM_NORMAL, gvs->strategy);
00414     page = BufferGetPage(buffer);
00415 
00416     Assert(GinPageIsData(page));
00417 
00418     if (!GinPageIsLeaf(page))
00419     {
00420         OffsetNumber i;
00421 
00422         me->blkno = blkno;
00423         for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
00424         {
00425             PostingItem *pitem = (PostingItem *) GinDataPageGetItem(page, i);
00426 
00427             if (ginScanToDelete(gvs, PostingItemGetBlockNumber(pitem), FALSE, me, i))
00428                 i--;
00429         }
00430     }
00431 
00432     if (GinPageGetOpaque(page)->maxoff < FirstOffsetNumber)
00433     {
00434         if (!(me->leftBlkno == InvalidBlockNumber && GinPageRightMost(page)))
00435         {
00436             /* we never delete right most branch */
00437             Assert(!isRoot);
00438             if (GinPageGetOpaque(page)->maxoff < FirstOffsetNumber)
00439             {
00440                 ginDeletePage(gvs, blkno, me->leftBlkno, me->parent->blkno, myoff, me->parent->isRoot);
00441                 meDelete = TRUE;
00442             }
00443         }
00444     }
00445 
00446     ReleaseBuffer(buffer);
00447 
00448     if (!meDelete)
00449         me->leftBlkno = blkno;
00450 
00451     return meDelete;
00452 }
00453 
00454 static void
00455 ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
00456 {
00457     Buffer      rootBuffer = InvalidBuffer;
00458     DataPageDeleteStack root,
00459                *ptr,
00460                *tmp;
00461 
00462     if (ginVacuumPostingTreeLeaves(gvs, rootBlkno, TRUE, &rootBuffer) == FALSE)
00463     {
00464         Assert(rootBuffer == InvalidBuffer);
00465         return;
00466     }
00467 
00468     memset(&root, 0, sizeof(DataPageDeleteStack));
00469     root.leftBlkno = InvalidBlockNumber;
00470     root.isRoot = TRUE;
00471 
00472     vacuum_delay_point();
00473 
00474     ginScanToDelete(gvs, rootBlkno, TRUE, &root, InvalidOffsetNumber);
00475 
00476     ptr = root.child;
00477     while (ptr)
00478     {
00479         tmp = ptr->child;
00480         pfree(ptr);
00481         ptr = tmp;
00482     }
00483 
00484     UnlockReleaseBuffer(rootBuffer);
00485 }
00486 
00487 /*
00488  * returns modified page or NULL if page isn't modified.
00489  * Function works with original page until first change is occurred,
00490  * then page is copied into temporary one.
00491  */
00492 static Page
00493 ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint32 *nroot)
00494 {
00495     Page        origpage = BufferGetPage(buffer),
00496                 tmppage;
00497     OffsetNumber i,
00498                 maxoff = PageGetMaxOffsetNumber(origpage);
00499 
00500     tmppage = origpage;
00501 
00502     *nroot = 0;
00503 
00504     for (i = FirstOffsetNumber; i <= maxoff; i++)
00505     {
00506         IndexTuple  itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
00507 
00508         if (GinIsPostingTree(itup))
00509         {
00510             /*
00511              * store posting tree's roots for further processing, we can't
00512              * vacuum it just now due to risk of deadlocks with scans/inserts
00513              */
00514             roots[*nroot] = GinGetDownlink(itup);
00515             (*nroot)++;
00516         }
00517         else if (GinGetNPosting(itup) > 0)
00518         {
00519             /*
00520              * if we already create temporary page, we will make changes in
00521              * place
00522              */
00523             ItemPointerData *cleaned = (tmppage == origpage) ? NULL : GinGetPosting(itup);
00524             uint32      newN = ginVacuumPostingList(gvs, GinGetPosting(itup), GinGetNPosting(itup), &cleaned);
00525 
00526             if (GinGetNPosting(itup) != newN)
00527             {
00528                 OffsetNumber attnum;
00529                 Datum       key;
00530                 GinNullCategory category;
00531 
00532                 /*
00533                  * Some ItemPointers was deleted, so we should remake our
00534                  * tuple
00535                  */
00536 
00537                 if (tmppage == origpage)
00538                 {
00539                     /*
00540                      * On first difference we create temporary page in memory
00541                      * and copies content in to it.
00542                      */
00543                     tmppage = PageGetTempPageCopy(origpage);
00544 
00545                     if (newN > 0)
00546                     {
00547                         Size        pos = ((char *) GinGetPosting(itup)) - ((char *) origpage);
00548 
00549                         memcpy(tmppage + pos, cleaned, sizeof(ItemPointerData) * newN);
00550                     }
00551 
00552                     pfree(cleaned);
00553 
00554                     /* set itup pointer to new page */
00555                     itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
00556                 }
00557 
00558                 attnum = gintuple_get_attrnum(&gvs->ginstate, itup);
00559                 key = gintuple_get_key(&gvs->ginstate, itup, &category);
00560                 itup = GinFormTuple(&gvs->ginstate, attnum, key, category,
00561                                     GinGetPosting(itup), newN, true);
00562                 PageIndexTupleDelete(tmppage, i);
00563 
00564                 if (PageAddItem(tmppage, (Item) itup, IndexTupleSize(itup), i, false, false) != i)
00565                     elog(ERROR, "failed to add item to index page in \"%s\"",
00566                          RelationGetRelationName(gvs->index));
00567 
00568                 pfree(itup);
00569             }
00570         }
00571     }
00572 
00573     return (tmppage == origpage) ? NULL : tmppage;
00574 }
00575 
00576 Datum
00577 ginbulkdelete(PG_FUNCTION_ARGS)
00578 {
00579     IndexVacuumInfo *info = (IndexVacuumInfo *) PG_GETARG_POINTER(0);
00580     IndexBulkDeleteResult *stats = (IndexBulkDeleteResult *) PG_GETARG_POINTER(1);
00581     IndexBulkDeleteCallback callback = (IndexBulkDeleteCallback) PG_GETARG_POINTER(2);
00582     void       *callback_state = (void *) PG_GETARG_POINTER(3);
00583     Relation    index = info->index;
00584     BlockNumber blkno = GIN_ROOT_BLKNO;
00585     GinVacuumState gvs;
00586     Buffer      buffer;
00587     BlockNumber rootOfPostingTree[BLCKSZ / (sizeof(IndexTupleData) + sizeof(ItemId))];
00588     uint32      nRoot;
00589 
00590     gvs.index = index;
00591     gvs.callback = callback;
00592     gvs.callback_state = callback_state;
00593     gvs.strategy = info->strategy;
00594     initGinState(&gvs.ginstate, index);
00595 
00596     /* first time through? */
00597     if (stats == NULL)
00598     {
00599         /* Yes, so initialize stats to zeroes */
00600         stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
00601         /* and cleanup any pending inserts */
00602         ginInsertCleanup(&gvs.ginstate, true, stats);
00603     }
00604 
00605     /* we'll re-count the tuples each time */
00606     stats->num_index_tuples = 0;
00607     gvs.result = stats;
00608 
00609     buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
00610                                 RBM_NORMAL, info->strategy);
00611 
00612     /* find leaf page */
00613     for (;;)
00614     {
00615         Page        page = BufferGetPage(buffer);
00616         IndexTuple  itup;
00617 
00618         LockBuffer(buffer, GIN_SHARE);
00619 
00620         Assert(!GinPageIsData(page));
00621 
00622         if (GinPageIsLeaf(page))
00623         {
00624             LockBuffer(buffer, GIN_UNLOCK);
00625             LockBuffer(buffer, GIN_EXCLUSIVE);
00626 
00627             if (blkno == GIN_ROOT_BLKNO && !GinPageIsLeaf(page))
00628             {
00629                 LockBuffer(buffer, GIN_UNLOCK);
00630                 continue;       /* check it one more */
00631             }
00632             break;
00633         }
00634 
00635         Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
00636 
00637         itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
00638         blkno = GinGetDownlink(itup);
00639         Assert(blkno != InvalidBlockNumber);
00640 
00641         UnlockReleaseBuffer(buffer);
00642         buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
00643                                     RBM_NORMAL, info->strategy);
00644     }
00645 
00646     /* right now we found leftmost page in entry's BTree */
00647 
00648     for (;;)
00649     {
00650         Page        page = BufferGetPage(buffer);
00651         Page        resPage;
00652         uint32      i;
00653 
00654         Assert(!GinPageIsData(page));
00655 
00656         resPage = ginVacuumEntryPage(&gvs, buffer, rootOfPostingTree, &nRoot);
00657 
00658         blkno = GinPageGetOpaque(page)->rightlink;
00659 
00660         if (resPage)
00661         {
00662             START_CRIT_SECTION();
00663             PageRestoreTempPage(resPage, page);
00664             MarkBufferDirty(buffer);
00665             xlogVacuumPage(gvs.index, buffer);
00666             UnlockReleaseBuffer(buffer);
00667             END_CRIT_SECTION();
00668         }
00669         else
00670         {
00671             UnlockReleaseBuffer(buffer);
00672         }
00673 
00674         vacuum_delay_point();
00675 
00676         for (i = 0; i < nRoot; i++)
00677         {
00678             ginVacuumPostingTree(&gvs, rootOfPostingTree[i]);
00679             vacuum_delay_point();
00680         }
00681 
00682         if (blkno == InvalidBlockNumber)        /* rightmost page */
00683             break;
00684 
00685         buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
00686                                     RBM_NORMAL, info->strategy);
00687         LockBuffer(buffer, GIN_EXCLUSIVE);
00688     }
00689 
00690     PG_RETURN_POINTER(gvs.result);
00691 }
00692 
00693 Datum
00694 ginvacuumcleanup(PG_FUNCTION_ARGS)
00695 {
00696     IndexVacuumInfo *info = (IndexVacuumInfo *) PG_GETARG_POINTER(0);
00697     IndexBulkDeleteResult *stats = (IndexBulkDeleteResult *) PG_GETARG_POINTER(1);
00698     Relation    index = info->index;
00699     bool        needLock;
00700     BlockNumber npages,
00701                 blkno;
00702     BlockNumber totFreePages;
00703     GinState    ginstate;
00704     GinStatsData idxStat;
00705 
00706     /*
00707      * In an autovacuum analyze, we want to clean up pending insertions.
00708      * Otherwise, an ANALYZE-only call is a no-op.
00709      */
00710     if (info->analyze_only)
00711     {
00712         if (IsAutoVacuumWorkerProcess())
00713         {
00714             initGinState(&ginstate, index);
00715             ginInsertCleanup(&ginstate, true, stats);
00716         }
00717         PG_RETURN_POINTER(stats);
00718     }
00719 
00720     /*
00721      * Set up all-zero stats and cleanup pending inserts if ginbulkdelete
00722      * wasn't called
00723      */
00724     if (stats == NULL)
00725     {
00726         stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
00727         initGinState(&ginstate, index);
00728         ginInsertCleanup(&ginstate, true, stats);
00729     }
00730 
00731     memset(&idxStat, 0, sizeof(idxStat));
00732 
00733     /*
00734      * XXX we always report the heap tuple count as the number of index
00735      * entries.  This is bogus if the index is partial, but it's real hard to
00736      * tell how many distinct heap entries are referenced by a GIN index.
00737      */
00738     stats->num_index_tuples = info->num_heap_tuples;
00739     stats->estimated_count = info->estimated_count;
00740 
00741     /*
00742      * Need lock unless it's local to this backend.
00743      */
00744     needLock = !RELATION_IS_LOCAL(index);
00745 
00746     if (needLock)
00747         LockRelationForExtension(index, ExclusiveLock);
00748     npages = RelationGetNumberOfBlocks(index);
00749     if (needLock)
00750         UnlockRelationForExtension(index, ExclusiveLock);
00751 
00752     totFreePages = 0;
00753 
00754     for (blkno = GIN_ROOT_BLKNO; blkno < npages; blkno++)
00755     {
00756         Buffer      buffer;
00757         Page        page;
00758 
00759         vacuum_delay_point();
00760 
00761         buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
00762                                     RBM_NORMAL, info->strategy);
00763         LockBuffer(buffer, GIN_SHARE);
00764         page = (Page) BufferGetPage(buffer);
00765 
00766         if (GinPageIsDeleted(page))
00767         {
00768             Assert(blkno != GIN_ROOT_BLKNO);
00769             RecordFreeIndexPage(index, blkno);
00770             totFreePages++;
00771         }
00772         else if (GinPageIsData(page))
00773         {
00774             idxStat.nDataPages++;
00775         }
00776         else if (!GinPageIsList(page))
00777         {
00778             idxStat.nEntryPages++;
00779 
00780             if (GinPageIsLeaf(page))
00781                 idxStat.nEntries += PageGetMaxOffsetNumber(page);
00782         }
00783 
00784         UnlockReleaseBuffer(buffer);
00785     }
00786 
00787     /* Update the metapage with accurate page and entry counts */
00788     idxStat.nTotalPages = npages;
00789     ginUpdateStats(info->index, &idxStat);
00790 
00791     /* Finally, vacuum the FSM */
00792     IndexFreeSpaceMapVacuum(info->index);
00793 
00794     stats->pages_free = totFreePages;
00795 
00796     if (needLock)
00797         LockRelationForExtension(index, ExclusiveLock);
00798     stats->num_pages = RelationGetNumberOfBlocks(index);
00799     if (needLock)
00800         UnlockRelationForExtension(index, ExclusiveLock);
00801 
00802     PG_RETURN_POINTER(stats);
00803 }