1 : /*-------------------------------------------------------------------------
2 : *
3 : * ginvacuum.c
4 : * delete & vacuum routines for the postgres GIN
5 : *
6 : *
7 : * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
8 : * Portions Copyright (c) 1994, Regents of the University of California
9 : *
10 : * IDENTIFICATION
11 : * $PostgreSQL: pgsql/src/backend/access/gin/ginvacuum.c,v 1.18 2007/11/15 21:14:31 momjian Exp $
12 : *-------------------------------------------------------------------------
13 : */
14 :
15 : #include "postgres.h"
16 : #include "access/genam.h"
17 : #include "access/gin.h"
18 : #include "access/heapam.h"
19 : #include "miscadmin.h"
20 : #include "storage/freespace.h"
21 : #include "storage/freespace.h"
22 : #include "commands/vacuum.h"
23 :
24 : typedef struct
25 : {
26 : Relation index;
27 : IndexBulkDeleteResult *result;
28 : IndexBulkDeleteCallback callback;
29 : void *callback_state;
30 : GinState ginstate;
31 : BufferAccessStrategy strategy;
32 : } GinVacuumState;
33 :
34 :
35 : /*
36 : * Cleans array of ItemPointer (removes dead pointers)
37 : * Results are always stored in *cleaned, which will be allocated
38 : * if it's needed. In case of *cleaned!=NULL caller is responsible to
39 : * have allocated enough space. *cleaned and items may point to the same
40 : * memory address.
41 : */
42 :
43 : static uint32
44 : ginVacuumPostingList(GinVacuumState *gvs, ItemPointerData *items, uint32 nitem, ItemPointerData **cleaned)
45 0 : {
46 : uint32 i,
47 0 : j = 0;
48 :
49 : /*
50 : * just scan over ItemPointer array
51 : */
52 :
53 0 : for (i = 0; i < nitem; i++)
54 : {
55 0 : if (gvs->callback(items + i, gvs->callback_state))
56 : {
57 0 : gvs->result->tuples_removed += 1;
58 0 : if (!*cleaned)
59 : {
60 0 : *cleaned = (ItemPointerData *) palloc(sizeof(ItemPointerData) * nitem);
61 0 : if (i != 0)
62 0 : memcpy(*cleaned, items, sizeof(ItemPointerData) * i);
63 : }
64 : }
65 : else
66 : {
67 0 : gvs->result->num_index_tuples += 1;
68 0 : if (i != j)
69 0 : (*cleaned)[j] = items[i];
70 0 : j++;
71 : }
72 : }
73 :
74 0 : return j;
75 : }
76 :
77 : /*
78 : * fills WAL record for vacuum leaf page
79 : */
80 : static void
81 : xlogVacuumPage(Relation index, Buffer buffer)
82 0 : {
83 0 : Page page = BufferGetPage(buffer);
84 : XLogRecPtr recptr;
85 : XLogRecData rdata[3];
86 : ginxlogVacuumPage data;
87 : char *backup;
88 : char itups[BLCKSZ];
89 0 : uint32 len = 0;
90 :
91 : Assert(GinPageIsLeaf(page));
92 :
93 0 : if (index->rd_istemp)
94 0 : return;
95 :
96 0 : data.node = index->rd_node;
97 0 : data.blkno = BufferGetBlockNumber(buffer);
98 :
99 0 : if (GinPageIsData(page))
100 : {
101 0 : backup = GinDataPageGetData(page);
102 0 : data.nitem = GinPageGetOpaque(page)->maxoff;
103 0 : if (data.nitem)
104 0 : len = MAXALIGN(sizeof(ItemPointerData) * data.nitem);
105 : }
106 : else
107 : {
108 : char *ptr;
109 : OffsetNumber i;
110 :
111 0 : ptr = backup = itups;
112 0 : for (i = FirstOffsetNumber; i <= PageGetMaxOffsetNumber(page); i++)
113 : {
114 0 : IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
115 :
116 0 : memcpy(ptr, itup, IndexTupleSize(itup));
117 0 : ptr += MAXALIGN(IndexTupleSize(itup));
118 : }
119 :
120 0 : data.nitem = PageGetMaxOffsetNumber(page);
121 0 : len = ptr - backup;
122 : }
123 :
124 0 : rdata[0].buffer = buffer;
125 0 : rdata[0].buffer_std = (GinPageIsData(page)) ? FALSE : TRUE;
126 0 : rdata[0].len = 0;
127 0 : rdata[0].data = NULL;
128 0 : rdata[0].next = rdata + 1;
129 :
130 0 : rdata[1].buffer = InvalidBuffer;
131 0 : rdata[1].len = sizeof(ginxlogVacuumPage);
132 0 : rdata[1].data = (char *) &data;
133 :
134 0 : if (len == 0)
135 : {
136 0 : rdata[1].next = NULL;
137 : }
138 : else
139 : {
140 0 : rdata[1].next = rdata + 2;
141 :
142 0 : rdata[2].buffer = InvalidBuffer;
143 0 : rdata[2].len = len;
144 0 : rdata[2].data = backup;
145 0 : rdata[2].next = NULL;
146 : }
147 :
148 0 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_VACUUM_PAGE, rdata);
149 0 : PageSetLSN(page, recptr);
150 0 : PageSetTLI(page, ThisTimeLineID);
151 : }
152 :
153 : static bool
154 : ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno, bool isRoot, Buffer *rootBuffer)
155 0 : {
156 0 : Buffer buffer = ReadBufferWithStrategy(gvs->index, blkno, gvs->strategy);
157 0 : Page page = BufferGetPage(buffer);
158 0 : bool hasVoidPage = FALSE;
159 :
160 : /*
161 : * We should be sure that we don't concurrent with inserts, insert process
162 : * never release root page until end (but it can unlock it and lock
163 : * again). New scan can't start but previously started ones work
164 : * concurrently.
165 : */
166 :
167 0 : if (isRoot)
168 0 : LockBufferForCleanup(buffer);
169 : else
170 0 : LockBuffer(buffer, GIN_EXCLUSIVE);
171 :
172 : Assert(GinPageIsData(page));
173 :
174 0 : if (GinPageIsLeaf(page))
175 : {
176 : OffsetNumber newMaxOff,
177 0 : oldMaxOff = GinPageGetOpaque(page)->maxoff;
178 0 : ItemPointerData *cleaned = NULL;
179 :
180 0 : newMaxOff = ginVacuumPostingList(gvs,
181 : (ItemPointer) GinDataPageGetData(page), oldMaxOff, &cleaned);
182 :
183 : /* saves changes about deleted tuple ... */
184 0 : if (oldMaxOff != newMaxOff)
185 : {
186 :
187 0 : START_CRIT_SECTION();
188 :
189 0 : if (newMaxOff > 0)
190 0 : memcpy(GinDataPageGetData(page), cleaned, sizeof(ItemPointerData) * newMaxOff);
191 0 : pfree(cleaned);
192 0 : GinPageGetOpaque(page)->maxoff = newMaxOff;
193 :
194 0 : MarkBufferDirty(buffer);
195 0 : xlogVacuumPage(gvs->index, buffer);
196 :
197 0 : END_CRIT_SECTION();
198 :
199 : /* if root is a leaf page, we don't desire further processing */
200 0 : if (!isRoot && GinPageGetOpaque(page)->maxoff < FirstOffsetNumber)
201 0 : hasVoidPage = TRUE;
202 : }
203 : }
204 : else
205 : {
206 : OffsetNumber i;
207 0 : bool isChildHasVoid = FALSE;
208 :
209 0 : for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
210 : {
211 0 : PostingItem *pitem = (PostingItem *) GinDataPageGetItem(page, i);
212 :
213 0 : if (ginVacuumPostingTreeLeaves(gvs, PostingItemGetBlockNumber(pitem), FALSE, NULL))
214 0 : isChildHasVoid = TRUE;
215 : }
216 :
217 0 : if (isChildHasVoid)
218 0 : hasVoidPage = TRUE;
219 : }
220 :
221 : /*
222 : * if we have root and theres void pages in tree, then we don't release
223 : * lock to go further processing and guarantee that tree is unused
224 : */
225 0 : if (!(isRoot && hasVoidPage))
226 : {
227 0 : UnlockReleaseBuffer(buffer);
228 : }
229 : else
230 : {
231 : Assert(rootBuffer);
232 0 : *rootBuffer = buffer;
233 : }
234 :
235 0 : return hasVoidPage;
236 : }
237 :
238 : static void
239 : ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkno,
240 : BlockNumber parentBlkno, OffsetNumber myoff, bool isParentRoot)
241 0 : {
242 0 : Buffer dBuffer = ReadBufferWithStrategy(gvs->index, deleteBlkno, gvs->strategy);
243 : Buffer lBuffer = (leftBlkno == InvalidBlockNumber) ?
244 0 : InvalidBuffer : ReadBufferWithStrategy(gvs->index, leftBlkno, gvs->strategy);
245 0 : Buffer pBuffer = ReadBufferWithStrategy(gvs->index, parentBlkno, gvs->strategy);
246 : Page page,
247 : parentPage;
248 :
249 0 : LockBuffer(dBuffer, GIN_EXCLUSIVE);
250 0 : if (!isParentRoot) /* parent is already locked by
251 : * LockBufferForCleanup() */
252 0 : LockBuffer(pBuffer, GIN_EXCLUSIVE);
253 0 : if (leftBlkno != InvalidBlockNumber)
254 0 : LockBuffer(lBuffer, GIN_EXCLUSIVE);
255 :
256 0 : START_CRIT_SECTION();
257 :
258 0 : if (leftBlkno != InvalidBlockNumber)
259 : {
260 : BlockNumber rightlink;
261 :
262 0 : page = BufferGetPage(dBuffer);
263 0 : rightlink = GinPageGetOpaque(page)->rightlink;
264 :
265 0 : page = BufferGetPage(lBuffer);
266 0 : GinPageGetOpaque(page)->rightlink = rightlink;
267 : }
268 :
269 0 : parentPage = BufferGetPage(pBuffer);
270 : #ifdef USE_ASSERT_CHECKING
271 : do
272 : {
273 : PostingItem *tod = (PostingItem *) GinDataPageGetItem(parentPage, myoff);
274 :
275 : Assert(PostingItemGetBlockNumber(tod) == deleteBlkno);
276 : } while (0);
277 : #endif
278 0 : PageDeletePostingItem(parentPage, myoff);
279 :
280 0 : page = BufferGetPage(dBuffer);
281 :
282 : /*
283 : * we shouldn't change rightlink field to save workability of running
284 : * search scan
285 : */
286 0 : GinPageGetOpaque(page)->flags = GIN_DELETED;
287 :
288 0 : MarkBufferDirty(pBuffer);
289 0 : if (leftBlkno != InvalidBlockNumber)
290 0 : MarkBufferDirty(lBuffer);
291 0 : MarkBufferDirty(dBuffer);
292 :
293 0 : if (!gvs->index->rd_istemp)
294 : {
295 : XLogRecPtr recptr;
296 : XLogRecData rdata[4];
297 : ginxlogDeletePage data;
298 : int n;
299 :
300 0 : data.node = gvs->index->rd_node;
301 0 : data.blkno = deleteBlkno;
302 0 : data.parentBlkno = parentBlkno;
303 0 : data.parentOffset = myoff;
304 0 : data.leftBlkno = leftBlkno;
305 0 : data.rightLink = GinPageGetOpaque(page)->rightlink;
306 :
307 0 : rdata[0].buffer = dBuffer;
308 0 : rdata[0].buffer_std = FALSE;
309 0 : rdata[0].data = NULL;
310 0 : rdata[0].len = 0;
311 0 : rdata[0].next = rdata + 1;
312 :
313 0 : rdata[1].buffer = pBuffer;
314 0 : rdata[1].buffer_std = FALSE;
315 0 : rdata[1].data = NULL;
316 0 : rdata[1].len = 0;
317 0 : rdata[1].next = rdata + 2;
318 :
319 0 : if (leftBlkno != InvalidBlockNumber)
320 : {
321 0 : rdata[2].buffer = lBuffer;
322 0 : rdata[2].buffer_std = FALSE;
323 0 : rdata[2].data = NULL;
324 0 : rdata[2].len = 0;
325 0 : rdata[2].next = rdata + 3;
326 0 : n = 3;
327 : }
328 : else
329 0 : n = 2;
330 :
331 0 : rdata[n].buffer = InvalidBuffer;
332 0 : rdata[n].buffer_std = FALSE;
333 0 : rdata[n].len = sizeof(ginxlogDeletePage);
334 0 : rdata[n].data = (char *) &data;
335 0 : rdata[n].next = NULL;
336 :
337 0 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_DELETE_PAGE, rdata);
338 0 : PageSetLSN(page, recptr);
339 0 : PageSetTLI(page, ThisTimeLineID);
340 0 : PageSetLSN(parentPage, recptr);
341 0 : PageSetTLI(parentPage, ThisTimeLineID);
342 0 : if (leftBlkno != InvalidBlockNumber)
343 : {
344 0 : page = BufferGetPage(lBuffer);
345 0 : PageSetLSN(page, recptr);
346 0 : PageSetTLI(page, ThisTimeLineID);
347 : }
348 : }
349 :
350 0 : if (!isParentRoot)
351 0 : LockBuffer(pBuffer, GIN_UNLOCK);
352 0 : ReleaseBuffer(pBuffer);
353 :
354 0 : if (leftBlkno != InvalidBlockNumber)
355 0 : UnlockReleaseBuffer(lBuffer);
356 :
357 0 : UnlockReleaseBuffer(dBuffer);
358 :
359 0 : END_CRIT_SECTION();
360 :
361 0 : gvs->result->pages_deleted++;
362 0 : }
363 :
364 : typedef struct DataPageDeleteStack
365 : {
366 : struct DataPageDeleteStack *child;
367 : struct DataPageDeleteStack *parent;
368 :
369 : BlockNumber blkno; /* current block number */
370 : BlockNumber leftBlkno; /* rightest non-deleted page on left */
371 : bool isRoot;
372 : } DataPageDeleteStack;
373 :
374 : /*
375 : * scans posting tree and deletes empty pages
376 : */
377 : static bool
378 : ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot, DataPageDeleteStack *parent, OffsetNumber myoff)
379 0 : {
380 : DataPageDeleteStack *me;
381 : Buffer buffer;
382 : Page page;
383 0 : bool meDelete = FALSE;
384 :
385 0 : if (isRoot)
386 : {
387 0 : me = parent;
388 : }
389 : else
390 : {
391 0 : if (!parent->child)
392 : {
393 0 : me = (DataPageDeleteStack *) palloc0(sizeof(DataPageDeleteStack));
394 0 : me->parent = parent;
395 0 : parent->child = me;
396 0 : me->leftBlkno = InvalidBlockNumber;
397 : }
398 : else
399 0 : me = parent->child;
400 : }
401 :
402 0 : buffer = ReadBufferWithStrategy(gvs->index, blkno, gvs->strategy);
403 0 : page = BufferGetPage(buffer);
404 :
405 : Assert(GinPageIsData(page));
406 :
407 0 : if (!GinPageIsLeaf(page))
408 : {
409 : OffsetNumber i;
410 :
411 0 : me->blkno = blkno;
412 0 : for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
413 : {
414 0 : PostingItem *pitem = (PostingItem *) GinDataPageGetItem(page, i);
415 :
416 0 : if (ginScanToDelete(gvs, PostingItemGetBlockNumber(pitem), FALSE, me, i))
417 0 : i--;
418 : }
419 : }
420 :
421 0 : if (GinPageGetOpaque(page)->maxoff < FirstOffsetNumber)
422 : {
423 0 : if (!(me->leftBlkno == InvalidBlockNumber && GinPageRightMost(page)))
424 : {
425 : /* we never delete right most branch */
426 : Assert(!isRoot);
427 0 : if (GinPageGetOpaque(page)->maxoff < FirstOffsetNumber)
428 : {
429 0 : ginDeletePage(gvs, blkno, me->leftBlkno, me->parent->blkno, myoff, me->parent->isRoot);
430 0 : meDelete = TRUE;
431 : }
432 : }
433 : }
434 :
435 0 : ReleaseBuffer(buffer);
436 :
437 0 : if (!meDelete)
438 0 : me->leftBlkno = blkno;
439 :
440 0 : return meDelete;
441 : }
442 :
443 : static void
444 : ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
445 0 : {
446 0 : Buffer rootBuffer = InvalidBuffer;
447 : DataPageDeleteStack root,
448 : *ptr,
449 : *tmp;
450 :
451 0 : if (ginVacuumPostingTreeLeaves(gvs, rootBlkno, TRUE, &rootBuffer) == FALSE)
452 : {
453 : Assert(rootBuffer == InvalidBuffer);
454 0 : return;
455 : }
456 :
457 0 : memset(&root, 0, sizeof(DataPageDeleteStack));
458 0 : root.leftBlkno = InvalidBlockNumber;
459 0 : root.isRoot = TRUE;
460 :
461 0 : vacuum_delay_point();
462 :
463 0 : ginScanToDelete(gvs, rootBlkno, TRUE, &root, InvalidOffsetNumber);
464 :
465 0 : ptr = root.child;
466 0 : while (ptr)
467 : {
468 0 : tmp = ptr->child;
469 0 : pfree(ptr);
470 0 : ptr = tmp;
471 : }
472 :
473 0 : UnlockReleaseBuffer(rootBuffer);
474 : }
475 :
476 : /*
477 : * returns modified page or NULL if page isn't modified.
478 : * Function works with original page until first change is occurred,
479 : * then page is copied into temporary one.
480 : */
481 : static Page
482 : ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint32 *nroot)
483 0 : {
484 0 : Page origpage = BufferGetPage(buffer),
485 : tmppage;
486 : OffsetNumber i,
487 0 : maxoff = PageGetMaxOffsetNumber(origpage);
488 :
489 0 : tmppage = origpage;
490 :
491 0 : *nroot = 0;
492 :
493 0 : for (i = FirstOffsetNumber; i <= maxoff; i++)
494 : {
495 0 : IndexTuple itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
496 :
497 0 : if (GinIsPostingTree(itup))
498 : {
499 : /*
500 : * store posting tree's roots for further processing, we can't
501 : * vacuum it just now due to risk of deadlocks with scans/inserts
502 : */
503 0 : roots[*nroot] = GinItemPointerGetBlockNumber(&itup->t_tid);
504 0 : (*nroot)++;
505 : }
506 0 : else if (GinGetNPosting(itup) > 0)
507 : {
508 : /*
509 : * if we already create temporary page, we will make changes in
510 : * place
511 : */
512 0 : ItemPointerData *cleaned = (tmppage == origpage) ? NULL : GinGetPosting(itup);
513 0 : uint32 newN = ginVacuumPostingList(gvs, GinGetPosting(itup), GinGetNPosting(itup), &cleaned);
514 :
515 0 : if (GinGetNPosting(itup) != newN)
516 : {
517 : bool isnull;
518 : Datum value;
519 :
520 : /*
521 : * Some ItemPointers was deleted, so we should remake our
522 : * tuple
523 : */
524 :
525 0 : if (tmppage == origpage)
526 : {
527 : /*
528 : * On first difference we create temporary page in memory
529 : * and copies content in to it.
530 : */
531 0 : tmppage = GinPageGetCopyPage(origpage);
532 :
533 0 : if (newN > 0)
534 : {
535 0 : Size pos = ((char *) GinGetPosting(itup)) - ((char *) origpage);
536 :
537 0 : memcpy(tmppage + pos, cleaned, sizeof(ItemPointerData) * newN);
538 : }
539 :
540 0 : pfree(cleaned);
541 :
542 : /* set itup pointer to new page */
543 0 : itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
544 : }
545 :
546 0 : value = index_getattr(itup, FirstOffsetNumber, gvs->ginstate.tupdesc, &isnull);
547 0 : itup = GinFormTuple(&gvs->ginstate, value, GinGetPosting(itup), newN);
548 0 : PageIndexTupleDelete(tmppage, i);
549 :
550 0 : if (PageAddItem(tmppage, (Item) itup, IndexTupleSize(itup), i, false, false) != i)
551 0 : elog(ERROR, "failed to add item to index page in \"%s\"",
552 : RelationGetRelationName(gvs->index));
553 :
554 0 : pfree(itup);
555 : }
556 : }
557 : }
558 :
559 0 : return (tmppage == origpage) ? NULL : tmppage;
560 : }
561 :
562 : Datum
563 : ginbulkdelete(PG_FUNCTION_ARGS)
564 0 : {
565 0 : IndexVacuumInfo *info = (IndexVacuumInfo *) PG_GETARG_POINTER(0);
566 0 : IndexBulkDeleteResult *stats = (IndexBulkDeleteResult *) PG_GETARG_POINTER(1);
567 0 : IndexBulkDeleteCallback callback = (IndexBulkDeleteCallback) PG_GETARG_POINTER(2);
568 0 : void *callback_state = (void *) PG_GETARG_POINTER(3);
569 0 : Relation index = info->index;
570 0 : BlockNumber blkno = GIN_ROOT_BLKNO;
571 : GinVacuumState gvs;
572 : Buffer buffer;
573 : BlockNumber rootOfPostingTree[BLCKSZ / (sizeof(IndexTupleData) + sizeof(ItemId))];
574 : uint32 nRoot;
575 :
576 : /* first time through? */
577 0 : if (stats == NULL)
578 0 : stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
579 : /* we'll re-count the tuples each time */
580 0 : stats->num_index_tuples = 0;
581 :
582 0 : gvs.index = index;
583 0 : gvs.result = stats;
584 0 : gvs.callback = callback;
585 0 : gvs.callback_state = callback_state;
586 0 : gvs.strategy = info->strategy;
587 0 : initGinState(&gvs.ginstate, index);
588 :
589 0 : buffer = ReadBufferWithStrategy(index, blkno, info->strategy);
590 :
591 : /* find leaf page */
592 : for (;;)
593 : {
594 0 : Page page = BufferGetPage(buffer);
595 : IndexTuple itup;
596 :
597 0 : LockBuffer(buffer, GIN_SHARE);
598 :
599 : Assert(!GinPageIsData(page));
600 :
601 0 : if (GinPageIsLeaf(page))
602 : {
603 0 : LockBuffer(buffer, GIN_UNLOCK);
604 0 : LockBuffer(buffer, GIN_EXCLUSIVE);
605 :
606 0 : if (blkno == GIN_ROOT_BLKNO && !GinPageIsLeaf(page))
607 : {
608 0 : LockBuffer(buffer, GIN_UNLOCK);
609 0 : continue; /* check it one more */
610 : }
611 : break;
612 : }
613 :
614 : Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
615 :
616 0 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
617 0 : blkno = GinItemPointerGetBlockNumber(&(itup)->t_tid);
618 : Assert(blkno != InvalidBlockNumber);
619 :
620 0 : UnlockReleaseBuffer(buffer);
621 0 : buffer = ReadBufferWithStrategy(index, blkno, info->strategy);
622 : }
623 :
624 : /* right now we found leftmost page in entry's BTree */
625 :
626 : for (;;)
627 : {
628 0 : Page page = BufferGetPage(buffer);
629 : Page resPage;
630 : uint32 i;
631 :
632 : Assert(!GinPageIsData(page));
633 :
634 0 : resPage = ginVacuumEntryPage(&gvs, buffer, rootOfPostingTree, &nRoot);
635 :
636 0 : blkno = GinPageGetOpaque(page)->rightlink;
637 :
638 0 : if (resPage)
639 : {
640 0 : START_CRIT_SECTION();
641 0 : PageRestoreTempPage(resPage, page);
642 0 : MarkBufferDirty(buffer);
643 0 : xlogVacuumPage(gvs.index, buffer);
644 0 : UnlockReleaseBuffer(buffer);
645 0 : END_CRIT_SECTION();
646 : }
647 : else
648 : {
649 0 : UnlockReleaseBuffer(buffer);
650 : }
651 :
652 0 : vacuum_delay_point();
653 :
654 0 : for (i = 0; i < nRoot; i++)
655 : {
656 0 : ginVacuumPostingTree(&gvs, rootOfPostingTree[i]);
657 0 : vacuum_delay_point();
658 : }
659 :
660 0 : if (blkno == InvalidBlockNumber) /* rightmost page */
661 0 : break;
662 :
663 0 : buffer = ReadBufferWithStrategy(index, blkno, info->strategy);
664 0 : LockBuffer(buffer, GIN_EXCLUSIVE);
665 0 : }
666 :
667 0 : PG_RETURN_POINTER(gvs.result);
668 : }
669 :
670 : Datum
671 : ginvacuumcleanup(PG_FUNCTION_ARGS)
672 2 : {
673 2 : IndexVacuumInfo *info = (IndexVacuumInfo *) PG_GETARG_POINTER(0);
674 2 : IndexBulkDeleteResult *stats = (IndexBulkDeleteResult *) PG_GETARG_POINTER(1);
675 2 : Relation index = info->index;
676 : bool needLock;
677 : BlockNumber npages,
678 : blkno;
679 : BlockNumber totFreePages,
680 : nFreePages,
681 : *freePages,
682 : maxFreePages;
683 2 : BlockNumber lastBlock = GIN_ROOT_BLKNO,
684 2 : lastFilledBlock = GIN_ROOT_BLKNO;
685 :
686 : /* Set up all-zero stats if ginbulkdelete wasn't called */
687 2 : if (stats == NULL)
688 2 : stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
689 :
690 : /*
691 : * XXX we always report the heap tuple count as the number of index
692 : * entries. This is bogus if the index is partial, but it's real hard to
693 : * tell how many distinct heap entries are referenced by a GIN index.
694 : */
695 2 : stats->num_index_tuples = info->num_heap_tuples;
696 :
697 : /*
698 : * If vacuum full, we already have exclusive lock on the index. Otherwise,
699 : * need lock unless it's local to this backend.
700 : */
701 2 : if (info->vacuum_full)
702 0 : needLock = false;
703 : else
704 2 : needLock = !RELATION_IS_LOCAL(index);
705 :
706 2 : if (needLock)
707 2 : LockRelationForExtension(index, ExclusiveLock);
708 2 : npages = RelationGetNumberOfBlocks(index);
709 2 : if (needLock)
710 2 : UnlockRelationForExtension(index, ExclusiveLock);
711 :
712 2 : maxFreePages = npages;
713 2 : if (maxFreePages > MaxFSMPages)
714 0 : maxFreePages = MaxFSMPages;
715 :
716 2 : totFreePages = nFreePages = 0;
717 2 : freePages = (BlockNumber *) palloc(sizeof(BlockNumber) * maxFreePages);
718 :
719 4 : for (blkno = GIN_ROOT_BLKNO + 1; blkno < npages; blkno++)
720 : {
721 : Buffer buffer;
722 : Page page;
723 :
724 2 : vacuum_delay_point();
725 :
726 2 : buffer = ReadBufferWithStrategy(index, blkno, info->strategy);
727 2 : LockBuffer(buffer, GIN_SHARE);
728 2 : page = (Page) BufferGetPage(buffer);
729 :
730 2 : if (GinPageIsDeleted(page))
731 : {
732 0 : if (nFreePages < maxFreePages)
733 0 : freePages[nFreePages++] = blkno;
734 0 : totFreePages++;
735 : }
736 : else
737 2 : lastFilledBlock = blkno;
738 :
739 2 : UnlockReleaseBuffer(buffer);
740 : }
741 2 : lastBlock = npages - 1;
742 :
743 2 : if (info->vacuum_full && nFreePages > 0)
744 : {
745 : /* try to truncate index */
746 : int i;
747 :
748 0 : for (i = 0; i < nFreePages; i++)
749 0 : if (freePages[i] >= lastFilledBlock)
750 : {
751 0 : totFreePages = nFreePages = i;
752 0 : break;
753 : }
754 :
755 0 : if (lastBlock > lastFilledBlock)
756 0 : RelationTruncate(index, lastFilledBlock + 1);
757 :
758 0 : stats->pages_removed = lastBlock - lastFilledBlock;
759 : }
760 :
761 2 : RecordIndexFreeSpace(&index->rd_node, totFreePages, nFreePages, freePages);
762 2 : stats->pages_free = totFreePages;
763 :
764 2 : if (needLock)
765 2 : LockRelationForExtension(index, ExclusiveLock);
766 2 : stats->num_pages = RelationGetNumberOfBlocks(index);
767 2 : if (needLock)
768 2 : UnlockRelationForExtension(index, ExclusiveLock);
769 :
770 2 : PG_RETURN_POINTER(stats);
771 : }
|