1 /*-------------------------------------------------------------------------
2  *
3  * ginvacuum.c
4  *	  delete & vacuum routines for the postgres GIN
5  *
6  *
7  * Portions Copyright (c) 1996-2021, 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, ReadNextTransactionId());
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_newly_deleted++;
235 	gvs->result->pages_deleted++;
236 }
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
ginScanToDelete(GinVacuumState * gvs,BlockNumber blkno,bool isRoot,DataPageDeleteStack * parent,OffsetNumber myoff)247 ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
248 				DataPageDeleteStack *parent, OffsetNumber myoff)
249 {
250 	DataPageDeleteStack *me;
251 	Buffer		buffer;
252 	Page		page;
253 	bool		meDelete = false;
254 	bool		isempty;
255 
256 	if (isRoot)
257 	{
258 		me = parent;
259 	}
260 	else
261 	{
262 		if (!parent->child)
263 		{
264 			me = (DataPageDeleteStack *) palloc0(sizeof(DataPageDeleteStack));
265 			me->parent = parent;
266 			parent->child = me;
267 			me->leftBuffer = InvalidBuffer;
268 		}
269 		else
270 			me = parent->child;
271 	}
272 
273 	buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
274 								RBM_NORMAL, gvs->strategy);
275 
276 	if (!isRoot)
277 		LockBuffer(buffer, GIN_EXCLUSIVE);
278 
279 	page = BufferGetPage(buffer);
280 
281 	Assert(GinPageIsData(page));
282 
283 	if (!GinPageIsLeaf(page))
284 	{
285 		OffsetNumber i;
286 
287 		me->blkno = blkno;
288 		for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
289 		{
290 			PostingItem *pitem = GinDataPageGetPostingItem(page, i);
291 
292 			if (ginScanToDelete(gvs, PostingItemGetBlockNumber(pitem), false, me, i))
293 				i--;
294 		}
295 
296 		if (GinPageRightMost(page) && BufferIsValid(me->child->leftBuffer))
297 		{
298 			UnlockReleaseBuffer(me->child->leftBuffer);
299 			me->child->leftBuffer = InvalidBuffer;
300 		}
301 	}
302 
303 	if (GinPageIsLeaf(page))
304 		isempty = GinDataLeafPageIsEmpty(page);
305 	else
306 		isempty = GinPageGetOpaque(page)->maxoff < FirstOffsetNumber;
307 
308 	if (isempty)
309 	{
310 		/* we never delete the left- or rightmost branch */
311 		if (BufferIsValid(me->leftBuffer) && !GinPageRightMost(page))
312 		{
313 			Assert(!isRoot);
314 			ginDeletePage(gvs, blkno, BufferGetBlockNumber(me->leftBuffer),
315 						  me->parent->blkno, myoff, me->parent->isRoot);
316 			meDelete = true;
317 		}
318 	}
319 
320 	if (!meDelete)
321 	{
322 		if (BufferIsValid(me->leftBuffer))
323 			UnlockReleaseBuffer(me->leftBuffer);
324 		me->leftBuffer = buffer;
325 	}
326 	else
327 	{
328 		if (!isRoot)
329 			LockBuffer(buffer, GIN_UNLOCK);
330 
331 		ReleaseBuffer(buffer);
332 	}
333 
334 	if (isRoot)
335 		ReleaseBuffer(buffer);
336 
337 	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
ginVacuumPostingTreeLeaves(GinVacuumState * gvs,BlockNumber blkno)346 ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno)
347 {
348 	Buffer		buffer;
349 	Page		page;
350 	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 	{
356 		PostingItem *pitem;
357 
358 		buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
359 									RBM_NORMAL, gvs->strategy);
360 		LockBuffer(buffer, GIN_SHARE);
361 		page = BufferGetPage(buffer);
362 
363 		Assert(GinPageIsData(page));
364 
365 		if (GinPageIsLeaf(page))
366 		{
367 			LockBuffer(buffer, GIN_UNLOCK);
368 			LockBuffer(buffer, GIN_EXCLUSIVE);
369 			break;
370 		}
371 
372 		Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
373 
374 		pitem = GinDataPageGetPostingItem(page, FirstOffsetNumber);
375 		blkno = PostingItemGetBlockNumber(pitem);
376 		Assert(blkno != InvalidBlockNumber);
377 
378 		UnlockReleaseBuffer(buffer);
379 	}
380 
381 	/* Iterate all posting tree leaves using rightlinks and vacuum them */
382 	while (true)
383 	{
384 		oldCxt = MemoryContextSwitchTo(gvs->tmpCxt);
385 		ginVacuumPostingTreeLeaf(gvs->index, buffer, gvs);
386 		MemoryContextSwitchTo(oldCxt);
387 		MemoryContextReset(gvs->tmpCxt);
388 
389 		if (GinDataLeafPageIsEmpty(page))
390 			hasVoidPage = true;
391 
392 		blkno = GinPageGetOpaque(page)->rightlink;
393 
394 		UnlockReleaseBuffer(buffer);
395 
396 		if (blkno == InvalidBlockNumber)
397 			break;
398 
399 		buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
400 									RBM_NORMAL, gvs->strategy);
401 		LockBuffer(buffer, GIN_EXCLUSIVE);
402 		page = BufferGetPage(buffer);
403 	}
404 
405 	return hasVoidPage;
406 }
407 
408 static void
ginVacuumPostingTree(GinVacuumState * gvs,BlockNumber rootBlkno)409 ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
410 {
411 	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 		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 		LockBufferForCleanup(buffer);
430 
431 		memset(&root, 0, sizeof(DataPageDeleteStack));
432 		root.leftBuffer = InvalidBuffer;
433 		root.isRoot = true;
434 
435 		ginScanToDelete(gvs, rootBlkno, true, &root, InvalidOffsetNumber);
436 
437 		ptr = root.child;
438 
439 		while (ptr)
440 		{
441 			tmp = ptr->child;
442 			pfree(ptr);
443 			ptr = tmp;
444 		}
445 
446 		UnlockReleaseBuffer(buffer);
447 	}
448 }
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
ginVacuumEntryPage(GinVacuumState * gvs,Buffer buffer,BlockNumber * roots,uint32 * nroot)456 ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint32 *nroot)
457 {
458 	Page		origpage = BufferGetPage(buffer),
459 				tmppage;
460 	OffsetNumber i,
461 				maxoff = PageGetMaxOffsetNumber(origpage);
462 
463 	tmppage = origpage;
464 
465 	*nroot = 0;
466 
467 	for (i = FirstOffsetNumber; i <= maxoff; i++)
468 	{
469 		IndexTuple	itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
470 
471 		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 			roots[*nroot] = GinGetDownlink(itup);
478 			(*nroot)++;
479 		}
480 		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 			if (GinItupIsCompressed(itup))
489 			{
490 				items_orig = ginPostingListDecode((GinPostingList *) GinGetPosting(itup), &nitems);
491 				free_items_orig = true;
492 			}
493 			else
494 			{
495 				items_orig = (ItemPointer) GinGetPosting(itup);
496 				nitems = GinGetNPosting(itup);
497 				free_items_orig = false;
498 			}
499 
500 			/* Remove any items from the list that need to be vacuumed. */
501 			items = ginVacuumItemPointers(gvs, items_orig, nitems, &nitems);
502 
503 			if (free_items_orig)
504 				pfree(items_orig);
505 
506 			/* If any item pointers were removed, recreate the tuple. */
507 			if (items)
508 			{
509 				OffsetNumber attnum;
510 				Datum		key;
511 				GinNullCategory category;
512 				GinPostingList *plist;
513 				int			plistsize;
514 
515 				if (nitems > 0)
516 				{
517 					plist = ginCompressPostingList(items, nitems, GinMaxItemSize, NULL);
518 					plistsize = SizeOfGinPostingList(plist);
519 				}
520 				else
521 				{
522 					plist = NULL;
523 					plistsize = 0;
524 				}
525 
526 				/*
527 				 * if we already created a temporary page, make changes in
528 				 * place
529 				 */
530 				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 					tmppage = PageGetTempPageCopy(origpage);
537 
538 					/* set itup pointer to new page */
539 					itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
540 				}
541 
542 				attnum = gintuple_get_attrnum(&gvs->ginstate, itup);
543 				key = gintuple_get_key(&gvs->ginstate, itup, &category);
544 				itup = GinFormTuple(&gvs->ginstate, attnum, key, category,
545 									(char *) plist, plistsize,
546 									nitems, true);
547 				if (plist)
548 					pfree(plist);
549 				PageIndexTupleDelete(tmppage, i);
550 
551 				if (PageAddItem(tmppage, (Item) itup, IndexTupleSize(itup), i, false, false) != i)
552 					elog(ERROR, "failed to add item to index page in \"%s\"",
553 						 RelationGetRelationName(gvs->index));
554 
555 				pfree(itup);
556 				pfree(items);
557 			}
558 		}
559 	}
560 
561 	return (tmppage == origpage) ? NULL : tmppage;
562 }
563 
564 IndexBulkDeleteResult *
ginbulkdelete(IndexVacuumInfo * info,IndexBulkDeleteResult * stats,IndexBulkDeleteCallback callback,void * callback_state)565 ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
566 			  IndexBulkDeleteCallback callback, void *callback_state)
567 {
568 	Relation	index = info->index;
569 	BlockNumber blkno = GIN_ROOT_BLKNO;
570 	GinVacuumState gvs;
571 	Buffer		buffer;
572 	BlockNumber rootOfPostingTree[BLCKSZ / (sizeof(IndexTupleData) + sizeof(ItemId))];
573 	uint32		nRoot;
574 
575 	gvs.tmpCxt = AllocSetContextCreate(CurrentMemoryContext,
576 									   "Gin vacuum temporary context",
577 									   ALLOCSET_DEFAULT_SIZES);
578 	gvs.index = index;
579 	gvs.callback = callback;
580 	gvs.callback_state = callback_state;
581 	gvs.strategy = info->strategy;
582 	initGinState(&gvs.ginstate, index);
583 
584 	/* first time through? */
585 	if (stats == NULL)
586 	{
587 		/* Yes, so initialize stats to zeroes */
588 		stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
589 
590 		/*
591 		 * and cleanup any pending inserts
592 		 */
593 		ginInsertCleanup(&gvs.ginstate, !IsAutoVacuumWorkerProcess(),
594 						 false, true, stats);
595 	}
596 
597 	/* we'll re-count the tuples each time */
598 	stats->num_index_tuples = 0;
599 	gvs.result = stats;
600 
601 	buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
602 								RBM_NORMAL, info->strategy);
603 
604 	/* find leaf page */
605 	for (;;)
606 	{
607 		Page		page = BufferGetPage(buffer);
608 		IndexTuple	itup;
609 
610 		LockBuffer(buffer, GIN_SHARE);
611 
612 		Assert(!GinPageIsData(page));
613 
614 		if (GinPageIsLeaf(page))
615 		{
616 			LockBuffer(buffer, GIN_UNLOCK);
617 			LockBuffer(buffer, GIN_EXCLUSIVE);
618 
619 			if (blkno == GIN_ROOT_BLKNO && !GinPageIsLeaf(page))
620 			{
621 				LockBuffer(buffer, GIN_UNLOCK);
622 				continue;		/* check it one more */
623 			}
624 			break;
625 		}
626 
627 		Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
628 
629 		itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
630 		blkno = GinGetDownlink(itup);
631 		Assert(blkno != InvalidBlockNumber);
632 
633 		UnlockReleaseBuffer(buffer);
634 		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 	{
642 		Page		page = BufferGetPage(buffer);
643 		Page		resPage;
644 		uint32		i;
645 
646 		Assert(!GinPageIsData(page));
647 
648 		resPage = ginVacuumEntryPage(&gvs, buffer, rootOfPostingTree, &nRoot);
649 
650 		blkno = GinPageGetOpaque(page)->rightlink;
651 
652 		if (resPage)
653 		{
654 			START_CRIT_SECTION();
655 			PageRestoreTempPage(resPage, page);
656 			MarkBufferDirty(buffer);
657 			xlogVacuumPage(gvs.index, buffer);
658 			UnlockReleaseBuffer(buffer);
659 			END_CRIT_SECTION();
660 		}
661 		else
662 		{
663 			UnlockReleaseBuffer(buffer);
664 		}
665 
666 		vacuum_delay_point();
667 
668 		for (i = 0; i < nRoot; i++)
669 		{
670 			ginVacuumPostingTree(&gvs, rootOfPostingTree[i]);
671 			vacuum_delay_point();
672 		}
673 
674 		if (blkno == InvalidBlockNumber)	/* rightmost page */
675 			break;
676 
677 		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
678 									RBM_NORMAL, info->strategy);
679 		LockBuffer(buffer, GIN_EXCLUSIVE);
680 	}
681 
682 	MemoryContextDelete(gvs.tmpCxt);
683 
684 	return gvs.result;
685 }
686 
687 IndexBulkDeleteResult *
ginvacuumcleanup(IndexVacuumInfo * info,IndexBulkDeleteResult * stats)688 ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
689 {
690 	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 	if (info->analyze_only)
703 	{
704 		if (IsAutoVacuumWorkerProcess())
705 		{
706 			initGinState(&ginstate, index);
707 			ginInsertCleanup(&ginstate, false, true, true, stats);
708 		}
709 		return stats;
710 	}
711 
712 	/*
713 	 * Set up all-zero stats and cleanup pending inserts if ginbulkdelete
714 	 * wasn't called
715 	 */
716 	if (stats == NULL)
717 	{
718 		stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
719 		initGinState(&ginstate, index);
720 		ginInsertCleanup(&ginstate, !IsAutoVacuumWorkerProcess(),
721 						 false, true, stats);
722 	}
723 
724 	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 	stats->num_index_tuples = Max(info->num_heap_tuples, 0);
732 	stats->estimated_count = info->estimated_count;
733 
734 	/*
735 	 * Need lock unless it's local to this backend.
736 	 */
737 	needLock = !RELATION_IS_LOCAL(index);
738 
739 	if (needLock)
740 		LockRelationForExtension(index, ExclusiveLock);
741 	npages = RelationGetNumberOfBlocks(index);
742 	if (needLock)
743 		UnlockRelationForExtension(index, ExclusiveLock);
744 
745 	totFreePages = 0;
746 
747 	for (blkno = GIN_ROOT_BLKNO; blkno < npages; blkno++)
748 	{
749 		Buffer		buffer;
750 		Page		page;
751 
752 		vacuum_delay_point();
753 
754 		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
755 									RBM_NORMAL, info->strategy);
756 		LockBuffer(buffer, GIN_SHARE);
757 		page = (Page) BufferGetPage(buffer);
758 
759 		if (GinPageIsRecyclable(page))
760 		{
761 			Assert(blkno != GIN_ROOT_BLKNO);
762 			RecordFreeIndexPage(index, blkno);
763 			totFreePages++;
764 		}
765 		else if (GinPageIsData(page))
766 		{
767 			idxStat.nDataPages++;
768 		}
769 		else if (!GinPageIsList(page))
770 		{
771 			idxStat.nEntryPages++;
772 
773 			if (GinPageIsLeaf(page))
774 				idxStat.nEntries += PageGetMaxOffsetNumber(page);
775 		}
776 
777 		UnlockReleaseBuffer(buffer);
778 	}
779 
780 	/* Update the metapage with accurate page and entry counts */
781 	idxStat.nTotalPages = npages;
782 	ginUpdateStats(info->index, &idxStat, false);
783 
784 	/* Finally, vacuum the FSM */
785 	IndexFreeSpaceMapVacuum(info->index);
786 
787 	stats->pages_free = totFreePages;
788 
789 	if (needLock)
790 		LockRelationForExtension(index, ExclusiveLock);
791 	stats->num_pages = RelationGetNumberOfBlocks(index);
792 	if (needLock)
793 		UnlockRelationForExtension(index, ExclusiveLock);
794 
795 	return stats;
796 }
797 
798 /*
799  * Return whether Page can safely be recycled.
800  */
801 bool
GinPageIsRecyclable(Page page)802 GinPageIsRecyclable(Page page)
803 {
804 	TransactionId delete_xid;
805 
806 	if (PageIsNew(page))
807 		return true;
808 
809 	if (!GinPageIsDeleted(page))
810 		return false;
811 
812 	delete_xid = GinPageGetDeleteXid(page);
813 
814 	if (!TransactionIdIsValid(delete_xid))
815 		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 	return GlobalVisCheckRemovableXid(NULL, delete_xid);
822 }
823