1 : /*-------------------------------------------------------------------------
2 : *
3 : * gistvacuum.c
4 : * interface routines for the postgres GiST index access method.
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/gist/gistvacuum.c,v 1.33 2007/11/15 21:14:31 momjian Exp $
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include "access/genam.h"
18 : #include "access/gist_private.h"
19 : #include "access/heapam.h"
20 : #include "commands/vacuum.h"
21 : #include "miscadmin.h"
22 : #include "storage/freespace.h"
23 : #include "utils/memutils.h"
24 :
25 :
26 : typedef struct GistBulkDeleteResult
27 : {
28 : IndexBulkDeleteResult std; /* common state */
29 : bool needFullVacuum;
30 : } GistBulkDeleteResult;
31 :
32 : typedef struct
33 : {
34 : GISTSTATE giststate;
35 : Relation index;
36 : MemoryContext opCtx;
37 : GistBulkDeleteResult *result;
38 : BufferAccessStrategy strategy;
39 : } GistVacuum;
40 :
41 : typedef struct
42 : {
43 : IndexTuple *itup;
44 : int ituplen;
45 : bool emptypage;
46 : } ArrayTuple;
47 :
48 : /*
49 : * Make union of keys on page
50 : */
51 : static IndexTuple
52 : PageMakeUnionKey(GistVacuum *gv, Buffer buffer)
53 0 : {
54 0 : Page page = BufferGetPage(buffer);
55 : IndexTuple *vec,
56 : tmp,
57 : res;
58 0 : int veclen = 0;
59 0 : MemoryContext oldCtx = MemoryContextSwitchTo(gv->opCtx);
60 :
61 0 : vec = gistextractpage(page, &veclen);
62 :
63 : /*
64 : * we call gistunion() in temprorary context because user-defined
65 : * functions called in gistunion() may do not free all memory
66 : */
67 0 : tmp = gistunion(gv->index, vec, veclen, &(gv->giststate));
68 : MemoryContextSwitchTo(oldCtx);
69 :
70 0 : res = (IndexTuple) palloc(IndexTupleSize(tmp));
71 0 : memcpy(res, tmp, IndexTupleSize(tmp));
72 :
73 0 : ItemPointerSetBlockNumber(&(res->t_tid), BufferGetBlockNumber(buffer));
74 0 : GistTupleSetValid(res);
75 :
76 0 : MemoryContextReset(gv->opCtx);
77 :
78 0 : return res;
79 : }
80 :
81 : static void
82 : gistDeleteSubtree(GistVacuum *gv, BlockNumber blkno)
83 0 : {
84 : Buffer buffer;
85 : Page page;
86 :
87 0 : buffer = ReadBufferWithStrategy(gv->index, blkno, gv->strategy);
88 0 : LockBuffer(buffer, GIST_EXCLUSIVE);
89 0 : page = (Page) BufferGetPage(buffer);
90 :
91 0 : if (!GistPageIsLeaf(page))
92 : {
93 : int i;
94 :
95 0 : for (i = FirstOffsetNumber; i <= PageGetMaxOffsetNumber(page); i = OffsetNumberNext(i))
96 : {
97 0 : ItemId iid = PageGetItemId(page, i);
98 0 : IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
99 :
100 0 : gistDeleteSubtree(gv, ItemPointerGetBlockNumber(&(idxtuple->t_tid)));
101 : }
102 : }
103 :
104 0 : START_CRIT_SECTION();
105 :
106 0 : MarkBufferDirty(buffer);
107 :
108 0 : page = (Page) BufferGetPage(buffer);
109 0 : GistPageSetDeleted(page);
110 0 : gv->result->std.pages_deleted++;
111 :
112 0 : if (!gv->index->rd_istemp)
113 : {
114 : XLogRecData rdata[2];
115 : XLogRecPtr recptr;
116 : gistxlogPageDelete xlrec;
117 :
118 0 : xlrec.node = gv->index->rd_node;
119 0 : xlrec.blkno = blkno;
120 :
121 0 : rdata[0].buffer = buffer;
122 0 : rdata[0].buffer_std = true;
123 0 : rdata[0].data = NULL;
124 0 : rdata[0].len = 0;
125 0 : rdata[0].next = &(rdata[1]);
126 :
127 0 : rdata[1].buffer = InvalidBuffer;
128 0 : rdata[1].data = (char *) &xlrec;
129 0 : rdata[1].len = sizeof(gistxlogPageDelete);
130 0 : rdata[1].next = NULL;
131 :
132 0 : recptr = XLogInsert(RM_GIST_ID, XLOG_GIST_PAGE_DELETE, rdata);
133 0 : PageSetLSN(page, recptr);
134 0 : PageSetTLI(page, ThisTimeLineID);
135 : }
136 : else
137 0 : PageSetLSN(page, XLogRecPtrForTemp);
138 :
139 0 : END_CRIT_SECTION();
140 :
141 0 : UnlockReleaseBuffer(buffer);
142 0 : }
143 :
144 : static Page
145 : GistPageGetCopyPage(Page page)
146 0 : {
147 0 : Size pageSize = PageGetPageSize(page);
148 : Page tmppage;
149 :
150 0 : tmppage = (Page) palloc(pageSize);
151 0 : memcpy(tmppage, page, pageSize);
152 :
153 0 : return tmppage;
154 : }
155 :
156 : static ArrayTuple
157 : vacuumSplitPage(GistVacuum *gv, Page tempPage, Buffer buffer, IndexTuple *addon, int curlenaddon)
158 0 : {
159 0 : ArrayTuple res = {NULL, 0, false};
160 : IndexTuple *vec;
161 0 : SplitedPageLayout *dist = NULL,
162 : *ptr;
163 : int i,
164 0 : veclen = 0;
165 0 : BlockNumber blkno = BufferGetBlockNumber(buffer);
166 0 : MemoryContext oldCtx = MemoryContextSwitchTo(gv->opCtx);
167 :
168 0 : vec = gistextractpage(tempPage, &veclen);
169 0 : vec = gistjoinvector(vec, &veclen, addon, curlenaddon);
170 0 : dist = gistSplit(gv->index, tempPage, vec, veclen, &(gv->giststate));
171 :
172 : MemoryContextSwitchTo(oldCtx);
173 :
174 0 : if (blkno != GIST_ROOT_BLKNO)
175 : {
176 : /* if non-root split then we should not allocate new buffer */
177 0 : dist->buffer = buffer;
178 0 : dist->page = tempPage;
179 : /* during vacuum we never split leaf page */
180 0 : GistPageGetOpaque(dist->page)->flags = 0;
181 : }
182 : else
183 0 : pfree(tempPage);
184 :
185 0 : res.itup = (IndexTuple *) palloc(sizeof(IndexTuple) * veclen);
186 0 : res.ituplen = 0;
187 :
188 : /* make new pages and fills them */
189 0 : for (ptr = dist; ptr; ptr = ptr->next)
190 : {
191 : char *data;
192 :
193 0 : if (ptr->buffer == InvalidBuffer)
194 : {
195 0 : ptr->buffer = gistNewBuffer(gv->index);
196 0 : GISTInitBuffer(ptr->buffer, 0);
197 0 : ptr->page = BufferGetPage(ptr->buffer);
198 : }
199 0 : ptr->block.blkno = BufferGetBlockNumber(ptr->buffer);
200 :
201 0 : data = (char *) (ptr->list);
202 0 : for (i = 0; i < ptr->block.num; i++)
203 : {
204 0 : if (PageAddItem(ptr->page, (Item) data, IndexTupleSize((IndexTuple) data), i + FirstOffsetNumber, false, false) == InvalidOffsetNumber)
205 0 : elog(ERROR, "failed to add item to index page in \"%s\"", RelationGetRelationName(gv->index));
206 0 : data += IndexTupleSize((IndexTuple) data);
207 : }
208 :
209 0 : ItemPointerSetBlockNumber(&(ptr->itup->t_tid), ptr->block.blkno);
210 0 : res.itup[res.ituplen] = (IndexTuple) palloc(IndexTupleSize(ptr->itup));
211 0 : memcpy(res.itup[res.ituplen], ptr->itup, IndexTupleSize(ptr->itup));
212 0 : res.ituplen++;
213 : }
214 :
215 0 : START_CRIT_SECTION();
216 :
217 0 : for (ptr = dist; ptr; ptr = ptr->next)
218 : {
219 0 : MarkBufferDirty(ptr->buffer);
220 0 : GistPageGetOpaque(ptr->page)->rightlink = InvalidBlockNumber;
221 : }
222 :
223 : /* restore splitted non-root page */
224 0 : if (blkno != GIST_ROOT_BLKNO)
225 : {
226 0 : PageRestoreTempPage(dist->page, BufferGetPage(dist->buffer));
227 0 : dist->page = BufferGetPage(dist->buffer);
228 : }
229 :
230 0 : if (!gv->index->rd_istemp)
231 : {
232 : XLogRecPtr recptr;
233 : XLogRecData *rdata;
234 : ItemPointerData key; /* set key for incomplete insert */
235 : char *xlinfo;
236 :
237 0 : ItemPointerSet(&key, blkno, TUPLE_IS_VALID);
238 :
239 0 : rdata = formSplitRdata(gv->index->rd_node, blkno,
240 : false, &key, dist);
241 0 : xlinfo = rdata->data;
242 :
243 0 : recptr = XLogInsert(RM_GIST_ID, XLOG_GIST_PAGE_SPLIT, rdata);
244 0 : for (ptr = dist; ptr; ptr = ptr->next)
245 : {
246 0 : PageSetLSN(BufferGetPage(ptr->buffer), recptr);
247 0 : PageSetTLI(BufferGetPage(ptr->buffer), ThisTimeLineID);
248 : }
249 :
250 0 : pfree(xlinfo);
251 0 : pfree(rdata);
252 : }
253 : else
254 : {
255 0 : for (ptr = dist; ptr; ptr = ptr->next)
256 0 : PageSetLSN(BufferGetPage(ptr->buffer), XLogRecPtrForTemp);
257 : }
258 :
259 0 : for (ptr = dist; ptr; ptr = ptr->next)
260 : {
261 : /* we must keep the buffer pin on the head page */
262 0 : if (BufferGetBlockNumber(ptr->buffer) != blkno)
263 0 : UnlockReleaseBuffer(ptr->buffer);
264 : }
265 :
266 0 : if (blkno == GIST_ROOT_BLKNO)
267 : {
268 : ItemPointerData key; /* set key for incomplete insert */
269 :
270 0 : ItemPointerSet(&key, blkno, TUPLE_IS_VALID);
271 :
272 0 : gistnewroot(gv->index, buffer, res.itup, res.ituplen, &key);
273 : }
274 :
275 0 : END_CRIT_SECTION();
276 :
277 0 : MemoryContextReset(gv->opCtx);
278 :
279 0 : return res;
280 : }
281 :
282 : static ArrayTuple
283 : gistVacuumUpdate(GistVacuum *gv, BlockNumber blkno, bool needunion)
284 0 : {
285 0 : ArrayTuple res = {NULL, 0, false};
286 : Buffer buffer;
287 : Page page,
288 0 : tempPage = NULL;
289 : OffsetNumber i,
290 : maxoff;
291 : ItemId iid;
292 0 : int lenaddon = 4,
293 0 : curlenaddon = 0,
294 0 : nOffToDelete = 0,
295 0 : nBlkToDelete = 0;
296 : IndexTuple idxtuple,
297 0 : *addon = NULL;
298 0 : bool needwrite = false;
299 : OffsetNumber offToDelete[MaxOffsetNumber];
300 : BlockNumber blkToDelete[MaxOffsetNumber];
301 0 : ItemPointerData *completed = NULL;
302 0 : int ncompleted = 0,
303 0 : lencompleted = 16;
304 :
305 0 : vacuum_delay_point();
306 :
307 0 : buffer = ReadBufferWithStrategy(gv->index, blkno, gv->strategy);
308 0 : LockBuffer(buffer, GIST_EXCLUSIVE);
309 0 : gistcheckpage(gv->index, buffer);
310 0 : page = (Page) BufferGetPage(buffer);
311 0 : maxoff = PageGetMaxOffsetNumber(page);
312 :
313 0 : if (GistPageIsLeaf(page))
314 : {
315 0 : if (GistTuplesDeleted(page))
316 0 : needunion = needwrite = true;
317 : }
318 : else
319 : {
320 0 : completed = (ItemPointerData *) palloc(sizeof(ItemPointerData) * lencompleted);
321 0 : addon = (IndexTuple *) palloc(sizeof(IndexTuple) * lenaddon);
322 :
323 : /* get copy of page to work */
324 0 : tempPage = GistPageGetCopyPage(page);
325 :
326 0 : for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
327 : {
328 : ArrayTuple chldtuple;
329 : bool needchildunion;
330 :
331 0 : iid = PageGetItemId(tempPage, i);
332 0 : idxtuple = (IndexTuple) PageGetItem(tempPage, iid);
333 0 : needchildunion = (GistTupleIsInvalid(idxtuple)) ? true : false;
334 :
335 0 : if (needchildunion)
336 0 : elog(DEBUG2, "gistVacuumUpdate: need union for block %u",
337 : ItemPointerGetBlockNumber(&(idxtuple->t_tid)));
338 :
339 0 : chldtuple = gistVacuumUpdate(gv, ItemPointerGetBlockNumber(&(idxtuple->t_tid)),
340 : needchildunion);
341 0 : if (chldtuple.ituplen || chldtuple.emptypage)
342 : {
343 : /* update tuple or/and inserts new */
344 0 : if (chldtuple.emptypage)
345 0 : blkToDelete[nBlkToDelete++] = ItemPointerGetBlockNumber(&(idxtuple->t_tid));
346 0 : offToDelete[nOffToDelete++] = i;
347 0 : PageIndexTupleDelete(tempPage, i);
348 0 : i--;
349 0 : maxoff--;
350 0 : needwrite = needunion = true;
351 :
352 0 : if (chldtuple.ituplen)
353 : {
354 :
355 : Assert(chldtuple.emptypage == false);
356 0 : while (curlenaddon + chldtuple.ituplen >= lenaddon)
357 : {
358 0 : lenaddon *= 2;
359 0 : addon = (IndexTuple *) repalloc(addon, sizeof(IndexTuple) * lenaddon);
360 : }
361 :
362 0 : memcpy(addon + curlenaddon, chldtuple.itup, chldtuple.ituplen * sizeof(IndexTuple));
363 :
364 0 : curlenaddon += chldtuple.ituplen;
365 :
366 0 : if (chldtuple.ituplen > 1)
367 : {
368 : /*
369 : * child was split, so we need mark completion
370 : * insert(split)
371 : */
372 : int j;
373 :
374 0 : while (ncompleted + chldtuple.ituplen > lencompleted)
375 : {
376 0 : lencompleted *= 2;
377 0 : completed = (ItemPointerData *) repalloc(completed, sizeof(ItemPointerData) * lencompleted);
378 : }
379 0 : for (j = 0; j < chldtuple.ituplen; j++)
380 : {
381 0 : ItemPointerCopy(&(chldtuple.itup[j]->t_tid), completed + ncompleted);
382 0 : ncompleted++;
383 : }
384 : }
385 0 : pfree(chldtuple.itup);
386 : }
387 : }
388 : }
389 :
390 : Assert(maxoff == PageGetMaxOffsetNumber(tempPage));
391 :
392 0 : if (curlenaddon)
393 : {
394 : /* insert updated tuples */
395 0 : if (gistnospace(tempPage, addon, curlenaddon, InvalidOffsetNumber, 0))
396 : {
397 : /* there is no space on page to insert tuples */
398 0 : res = vacuumSplitPage(gv, tempPage, buffer, addon, curlenaddon);
399 0 : tempPage = NULL; /* vacuumSplitPage() free tempPage */
400 0 : needwrite = needunion = false; /* gistSplit already forms
401 : * unions and writes pages */
402 : }
403 : else
404 : /* enough free space */
405 0 : gistfillbuffer(gv->index, tempPage, addon, curlenaddon, InvalidOffsetNumber);
406 : }
407 : }
408 :
409 : /*
410 : * If page is empty, we should remove pointer to it before deleting page
411 : * (except root)
412 : */
413 :
414 0 : if (blkno != GIST_ROOT_BLKNO && (PageIsEmpty(page) || (tempPage && PageIsEmpty(tempPage))))
415 : {
416 : /*
417 : * New version of page is empty, so leave it unchanged, upper call
418 : * will mark our page as deleted. In case of page split we never will
419 : * be here...
420 : *
421 : * If page was empty it can't become non-empty during processing
422 : */
423 0 : res.emptypage = true;
424 0 : UnlockReleaseBuffer(buffer);
425 : }
426 : else
427 : {
428 : /* write page and remove its childs if it need */
429 :
430 0 : START_CRIT_SECTION();
431 :
432 0 : if (tempPage && needwrite)
433 : {
434 0 : PageRestoreTempPage(tempPage, page);
435 0 : tempPage = NULL;
436 : }
437 :
438 : /* Empty index */
439 0 : if (PageIsEmpty(page) && blkno == GIST_ROOT_BLKNO)
440 : {
441 0 : needwrite = true;
442 0 : GistPageSetLeaf(page);
443 : }
444 :
445 :
446 0 : if (needwrite)
447 : {
448 0 : MarkBufferDirty(buffer);
449 0 : GistClearTuplesDeleted(page);
450 :
451 0 : if (!gv->index->rd_istemp)
452 : {
453 : XLogRecData *rdata;
454 : XLogRecPtr recptr;
455 : char *xlinfo;
456 :
457 0 : rdata = formUpdateRdata(gv->index->rd_node, buffer,
458 : offToDelete, nOffToDelete,
459 : addon, curlenaddon, NULL);
460 0 : xlinfo = rdata->next->data;
461 :
462 0 : recptr = XLogInsert(RM_GIST_ID, XLOG_GIST_PAGE_UPDATE, rdata);
463 0 : PageSetLSN(page, recptr);
464 0 : PageSetTLI(page, ThisTimeLineID);
465 :
466 0 : pfree(xlinfo);
467 0 : pfree(rdata);
468 : }
469 : else
470 0 : PageSetLSN(page, XLogRecPtrForTemp);
471 : }
472 :
473 0 : END_CRIT_SECTION();
474 :
475 0 : if (needunion && !PageIsEmpty(page))
476 : {
477 0 : res.itup = (IndexTuple *) palloc(sizeof(IndexTuple));
478 0 : res.ituplen = 1;
479 0 : res.itup[0] = PageMakeUnionKey(gv, buffer);
480 : }
481 :
482 0 : UnlockReleaseBuffer(buffer);
483 :
484 : /* delete empty children, now we havn't any links to pointed subtrees */
485 0 : for (i = 0; i < nBlkToDelete; i++)
486 0 : gistDeleteSubtree(gv, blkToDelete[i]);
487 :
488 0 : if (ncompleted && !gv->index->rd_istemp)
489 0 : gistxlogInsertCompletion(gv->index->rd_node, completed, ncompleted);
490 : }
491 :
492 :
493 0 : for (i = 0; i < curlenaddon; i++)
494 0 : pfree(addon[i]);
495 0 : if (addon)
496 0 : pfree(addon);
497 0 : if (completed)
498 0 : pfree(completed);
499 0 : if (tempPage)
500 0 : pfree(tempPage);
501 :
502 0 : return res;
503 : }
504 :
505 : /*
506 : * For usual vacuum just update FSM, for full vacuum
507 : * reforms parent tuples if some of childs was deleted or changed,
508 : * update invalid tuples (they can exist from last crash recovery only),
509 : * tries to get smaller index
510 : */
511 :
512 : Datum
513 : gistvacuumcleanup(PG_FUNCTION_ARGS)
514 3 : {
515 3 : IndexVacuumInfo *info = (IndexVacuumInfo *) PG_GETARG_POINTER(0);
516 3 : GistBulkDeleteResult *stats = (GistBulkDeleteResult *) PG_GETARG_POINTER(1);
517 3 : Relation rel = info->index;
518 : BlockNumber npages,
519 : blkno;
520 : BlockNumber totFreePages,
521 : nFreePages,
522 : *freePages,
523 : maxFreePages;
524 3 : BlockNumber lastBlock = GIST_ROOT_BLKNO,
525 3 : lastFilledBlock = GIST_ROOT_BLKNO;
526 : bool needLock;
527 :
528 : /* Set up all-zero stats if gistbulkdelete wasn't called */
529 3 : if (stats == NULL)
530 : {
531 3 : stats = (GistBulkDeleteResult *) palloc0(sizeof(GistBulkDeleteResult));
532 : /* use heap's tuple count */
533 : Assert(info->num_heap_tuples >= 0);
534 3 : stats->std.num_index_tuples = info->num_heap_tuples;
535 :
536 : /*
537 : * XXX the above is wrong if index is partial. Would it be OK to just
538 : * return NULL, or is there work we must do below?
539 : */
540 : }
541 :
542 : /* gistVacuumUpdate may cause hard work */
543 3 : if (info->vacuum_full)
544 : {
545 : GistVacuum gv;
546 : ArrayTuple res;
547 :
548 : /* note: vacuum.c already acquired AccessExclusiveLock on index */
549 :
550 0 : gv.index = rel;
551 0 : initGISTstate(&(gv.giststate), rel);
552 0 : gv.opCtx = createTempGistContext();
553 0 : gv.result = stats;
554 0 : gv.strategy = info->strategy;
555 :
556 : /* walk through the entire index for update tuples */
557 0 : res = gistVacuumUpdate(&gv, GIST_ROOT_BLKNO, false);
558 : /* cleanup */
559 0 : if (res.itup)
560 : {
561 : int i;
562 :
563 0 : for (i = 0; i < res.ituplen; i++)
564 0 : pfree(res.itup[i]);
565 0 : pfree(res.itup);
566 : }
567 0 : freeGISTstate(&(gv.giststate));
568 0 : MemoryContextDelete(gv.opCtx);
569 : }
570 3 : else if (stats->needFullVacuum)
571 0 : ereport(NOTICE,
572 : (errmsg("index \"%s\" needs VACUUM FULL or REINDEX to finish crash recovery",
573 : RelationGetRelationName(rel))));
574 :
575 : /*
576 : * If vacuum full, we already have exclusive lock on the index. Otherwise,
577 : * need lock unless it's local to this backend.
578 : */
579 3 : if (info->vacuum_full)
580 0 : needLock = false;
581 : else
582 3 : needLock = !RELATION_IS_LOCAL(rel);
583 :
584 : /* try to find deleted pages */
585 3 : if (needLock)
586 3 : LockRelationForExtension(rel, ExclusiveLock);
587 3 : npages = RelationGetNumberOfBlocks(rel);
588 3 : if (needLock)
589 3 : UnlockRelationForExtension(rel, ExclusiveLock);
590 :
591 3 : maxFreePages = npages;
592 3 : if (maxFreePages > MaxFSMPages)
593 0 : maxFreePages = MaxFSMPages;
594 :
595 3 : totFreePages = nFreePages = 0;
596 3 : freePages = (BlockNumber *) palloc(sizeof(BlockNumber) * maxFreePages);
597 :
598 35 : for (blkno = GIST_ROOT_BLKNO + 1; blkno < npages; blkno++)
599 : {
600 : Buffer buffer;
601 : Page page;
602 :
603 32 : vacuum_delay_point();
604 :
605 32 : buffer = ReadBufferWithStrategy(rel, blkno, info->strategy);
606 32 : LockBuffer(buffer, GIST_SHARE);
607 32 : page = (Page) BufferGetPage(buffer);
608 :
609 32 : if (PageIsNew(page) || GistPageIsDeleted(page))
610 : {
611 0 : if (nFreePages < maxFreePages)
612 0 : freePages[nFreePages++] = blkno;
613 0 : totFreePages++;
614 : }
615 : else
616 32 : lastFilledBlock = blkno;
617 32 : UnlockReleaseBuffer(buffer);
618 : }
619 3 : lastBlock = npages - 1;
620 :
621 3 : if (info->vacuum_full && nFreePages > 0)
622 : { /* try to truncate index */
623 : int i;
624 :
625 0 : for (i = 0; i < nFreePages; i++)
626 0 : if (freePages[i] >= lastFilledBlock)
627 : {
628 0 : totFreePages = nFreePages = i;
629 0 : break;
630 : }
631 :
632 0 : if (lastBlock > lastFilledBlock)
633 0 : RelationTruncate(rel, lastFilledBlock + 1);
634 0 : stats->std.pages_removed = lastBlock - lastFilledBlock;
635 : }
636 :
637 3 : RecordIndexFreeSpace(&rel->rd_node, totFreePages, nFreePages, freePages);
638 3 : pfree(freePages);
639 :
640 : /* return statistics */
641 3 : stats->std.pages_free = totFreePages;
642 3 : if (needLock)
643 3 : LockRelationForExtension(rel, ExclusiveLock);
644 3 : stats->std.num_pages = RelationGetNumberOfBlocks(rel);
645 3 : if (needLock)
646 3 : UnlockRelationForExtension(rel, ExclusiveLock);
647 :
648 3 : PG_RETURN_POINTER(stats);
649 : }
650 :
651 : typedef struct GistBDItem
652 : {
653 : GistNSN parentlsn;
654 : BlockNumber blkno;
655 : struct GistBDItem *next;
656 : } GistBDItem;
657 :
658 : static void
659 : pushStackIfSplited(Page page, GistBDItem *stack)
660 0 : {
661 0 : GISTPageOpaque opaque = GistPageGetOpaque(page);
662 :
663 0 : if (stack->blkno != GIST_ROOT_BLKNO && !XLogRecPtrIsInvalid(stack->parentlsn) &&
664 : XLByteLT(stack->parentlsn, opaque->nsn) &&
665 : opaque->rightlink != InvalidBlockNumber /* sanity check */ )
666 : {
667 : /* split page detected, install right link to the stack */
668 :
669 0 : GistBDItem *ptr = (GistBDItem *) palloc(sizeof(GistBDItem));
670 :
671 0 : ptr->blkno = opaque->rightlink;
672 0 : ptr->parentlsn = stack->parentlsn;
673 0 : ptr->next = stack->next;
674 0 : stack->next = ptr;
675 : }
676 0 : }
677 :
678 :
679 : /*
680 : * Bulk deletion of all index entries pointing to a set of heap tuples and
681 : * check invalid tuples after crash recovery.
682 : * The set of target tuples is specified via a callback routine that tells
683 : * whether any given heap tuple (identified by ItemPointer) is being deleted.
684 : *
685 : * Result: a palloc'd struct containing statistical info for VACUUM displays.
686 : */
687 : Datum
688 : gistbulkdelete(PG_FUNCTION_ARGS)
689 0 : {
690 0 : IndexVacuumInfo *info = (IndexVacuumInfo *) PG_GETARG_POINTER(0);
691 0 : GistBulkDeleteResult *stats = (GistBulkDeleteResult *) PG_GETARG_POINTER(1);
692 0 : IndexBulkDeleteCallback callback = (IndexBulkDeleteCallback) PG_GETARG_POINTER(2);
693 0 : void *callback_state = (void *) PG_GETARG_POINTER(3);
694 0 : Relation rel = info->index;
695 : GistBDItem *stack,
696 : *ptr;
697 :
698 : /* first time through? */
699 0 : if (stats == NULL)
700 0 : stats = (GistBulkDeleteResult *) palloc0(sizeof(GistBulkDeleteResult));
701 : /* we'll re-count the tuples each time */
702 0 : stats->std.num_index_tuples = 0;
703 :
704 0 : stack = (GistBDItem *) palloc0(sizeof(GistBDItem));
705 0 : stack->blkno = GIST_ROOT_BLKNO;
706 :
707 0 : while (stack)
708 : {
709 0 : Buffer buffer = ReadBufferWithStrategy(rel, stack->blkno, info->strategy);
710 : Page page;
711 : OffsetNumber i,
712 : maxoff;
713 : IndexTuple idxtuple;
714 : ItemId iid;
715 :
716 0 : LockBuffer(buffer, GIST_SHARE);
717 0 : gistcheckpage(rel, buffer);
718 0 : page = (Page) BufferGetPage(buffer);
719 :
720 0 : if (GistPageIsLeaf(page))
721 : {
722 : OffsetNumber todelete[MaxOffsetNumber];
723 0 : int ntodelete = 0;
724 :
725 0 : LockBuffer(buffer, GIST_UNLOCK);
726 0 : LockBuffer(buffer, GIST_EXCLUSIVE);
727 :
728 0 : page = (Page) BufferGetPage(buffer);
729 0 : if (stack->blkno == GIST_ROOT_BLKNO && !GistPageIsLeaf(page))
730 : {
731 : /* only the root can become non-leaf during relock */
732 0 : UnlockReleaseBuffer(buffer);
733 : /* one more check */
734 0 : continue;
735 : }
736 :
737 : /*
738 : * check for split proceeded after look at parent, we should check
739 : * it after relock
740 : */
741 0 : pushStackIfSplited(page, stack);
742 :
743 : /*
744 : * Remove deletable tuples from page
745 : */
746 :
747 0 : maxoff = PageGetMaxOffsetNumber(page);
748 :
749 0 : for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
750 : {
751 0 : iid = PageGetItemId(page, i);
752 0 : idxtuple = (IndexTuple) PageGetItem(page, iid);
753 :
754 0 : if (callback(&(idxtuple->t_tid), callback_state))
755 : {
756 0 : todelete[ntodelete] = i - ntodelete;
757 0 : ntodelete++;
758 0 : stats->std.tuples_removed += 1;
759 : }
760 : else
761 0 : stats->std.num_index_tuples += 1;
762 : }
763 :
764 0 : if (ntodelete)
765 : {
766 0 : START_CRIT_SECTION();
767 :
768 0 : MarkBufferDirty(buffer);
769 :
770 0 : for (i = 0; i < ntodelete; i++)
771 0 : PageIndexTupleDelete(page, todelete[i]);
772 0 : GistMarkTuplesDeleted(page);
773 :
774 0 : if (!rel->rd_istemp)
775 : {
776 : XLogRecData *rdata;
777 : XLogRecPtr recptr;
778 : gistxlogPageUpdate *xlinfo;
779 :
780 0 : rdata = formUpdateRdata(rel->rd_node, buffer,
781 : todelete, ntodelete,
782 : NULL, 0,
783 : NULL);
784 0 : xlinfo = (gistxlogPageUpdate *) rdata->next->data;
785 :
786 0 : recptr = XLogInsert(RM_GIST_ID, XLOG_GIST_PAGE_UPDATE, rdata);
787 0 : PageSetLSN(page, recptr);
788 0 : PageSetTLI(page, ThisTimeLineID);
789 :
790 0 : pfree(xlinfo);
791 0 : pfree(rdata);
792 : }
793 : else
794 0 : PageSetLSN(page, XLogRecPtrForTemp);
795 :
796 0 : END_CRIT_SECTION();
797 : }
798 :
799 : }
800 : else
801 : {
802 : /* check for split proceeded after look at parent */
803 0 : pushStackIfSplited(page, stack);
804 :
805 0 : maxoff = PageGetMaxOffsetNumber(page);
806 :
807 0 : for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
808 : {
809 0 : iid = PageGetItemId(page, i);
810 0 : idxtuple = (IndexTuple) PageGetItem(page, iid);
811 :
812 0 : ptr = (GistBDItem *) palloc(sizeof(GistBDItem));
813 0 : ptr->blkno = ItemPointerGetBlockNumber(&(idxtuple->t_tid));
814 0 : ptr->parentlsn = PageGetLSN(page);
815 0 : ptr->next = stack->next;
816 0 : stack->next = ptr;
817 :
818 0 : if (GistTupleIsInvalid(idxtuple))
819 0 : stats->needFullVacuum = true;
820 : }
821 : }
822 :
823 0 : UnlockReleaseBuffer(buffer);
824 :
825 0 : ptr = stack->next;
826 0 : pfree(stack);
827 0 : stack = ptr;
828 :
829 0 : vacuum_delay_point();
830 : }
831 :
832 0 : PG_RETURN_POINTER(stats);
833 : }
|