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