00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
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
00037
00038
00039
00040
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
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
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
00165
00166
00167
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
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
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
00225
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)
00264
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
00297
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;
00380 BlockNumber leftBlkno;
00381 bool isRoot;
00382 } DataPageDeleteStack;
00383
00384
00385
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
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
00489
00490
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
00512
00513
00514 roots[*nroot] = GinGetDownlink(itup);
00515 (*nroot)++;
00516 }
00517 else if (GinGetNPosting(itup) > 0)
00518 {
00519
00520
00521
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
00534
00535
00536
00537 if (tmppage == origpage)
00538 {
00539
00540
00541
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
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
00597 if (stats == NULL)
00598 {
00599
00600 stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
00601
00602 ginInsertCleanup(&gvs.ginstate, true, stats);
00603 }
00604
00605
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
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;
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
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)
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
00708
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
00722
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
00735
00736
00737
00738 stats->num_index_tuples = info->num_heap_tuples;
00739 stats->estimated_count = info->estimated_count;
00740
00741
00742
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
00788 idxStat.nTotalPages = npages;
00789 ginUpdateStats(info->index, &idxStat);
00790
00791
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 }