2005-06-20 10:29:37 +00:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* gistvacuum.c
|
|
|
|
* interface routines for the postgres GiST index access method.
|
|
|
|
*
|
|
|
|
*
|
2006-03-05 15:59:11 +00:00
|
|
|
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
|
2005-06-20 10:29:37 +00:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2006-05-17 16:34:59 +00:00
|
|
|
* $PostgreSQL: pgsql/src/backend/access/gist/gistvacuum.c,v 1.21 2006/05/17 16:34:59 teodor Exp $
|
2005-06-20 10:29:37 +00:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
#include "access/genam.h"
|
|
|
|
#include "access/gist_private.h"
|
|
|
|
#include "access/gistscan.h"
|
|
|
|
#include "access/heapam.h"
|
|
|
|
#include "catalog/index.h"
|
|
|
|
#include "commands/vacuum.h"
|
|
|
|
#include "miscadmin.h"
|
|
|
|
#include "utils/memutils.h"
|
|
|
|
#include "storage/freespace.h"
|
|
|
|
#include "storage/smgr.h"
|
|
|
|
|
|
|
|
|
2006-02-11 23:31:34 +00:00
|
|
|
typedef struct GistBulkDeleteResult
|
|
|
|
{
|
|
|
|
IndexBulkDeleteResult std; /* common state */
|
|
|
|
bool needFullVacuum;
|
|
|
|
} GistBulkDeleteResult;
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
typedef struct
|
|
|
|
{
|
2005-06-20 10:29:37 +00:00
|
|
|
GISTSTATE giststate;
|
|
|
|
Relation index;
|
2005-09-22 20:44:36 +00:00
|
|
|
MemoryContext opCtx;
|
2006-02-11 23:31:34 +00:00
|
|
|
GistBulkDeleteResult *result;
|
2005-06-20 10:29:37 +00:00
|
|
|
} GistVacuum;
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
IndexTuple *itup;
|
|
|
|
int ituplen;
|
2005-06-20 10:29:37 +00:00
|
|
|
bool emptypage;
|
|
|
|
} ArrayTuple;
|
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
/*
|
|
|
|
* Make union of keys on page
|
|
|
|
*/
|
|
|
|
static IndexTuple
|
|
|
|
PageMakeUnionKey(GistVacuum *gv, Buffer buffer) {
|
|
|
|
Page page = BufferGetPage( buffer );
|
|
|
|
IndexTuple *vec,
|
|
|
|
tmp, res;
|
|
|
|
int veclen = 0;
|
|
|
|
MemoryContext oldCtx = MemoryContextSwitchTo(gv->opCtx);
|
|
|
|
|
|
|
|
vec = gistextractpage(page, &veclen);
|
|
|
|
/* we call gistunion() in temprorary context because user-defined functions called in gistunion()
|
|
|
|
may do not free all memory */
|
|
|
|
tmp = gistunion(gv->index, vec, veclen, &(gv->giststate));
|
|
|
|
MemoryContextSwitchTo(oldCtx);
|
|
|
|
|
|
|
|
res = (IndexTuple) palloc(IndexTupleSize(tmp));
|
|
|
|
memcpy(res, tmp, IndexTupleSize(tmp));
|
|
|
|
|
|
|
|
ItemPointerSetBlockNumber(&(res->t_tid), BufferGetBlockNumber(buffer));
|
|
|
|
GistTupleSetValid(res);
|
|
|
|
|
|
|
|
MemoryContextReset(gv->opCtx);
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gistDeleteSubtree( GistVacuum *gv, BlockNumber blkno ) {
|
|
|
|
Buffer buffer;
|
|
|
|
Page page;
|
|
|
|
|
|
|
|
buffer = ReadBuffer(gv->index, blkno);
|
|
|
|
LockBuffer(buffer, GIST_EXCLUSIVE);
|
|
|
|
page = (Page) BufferGetPage(buffer);
|
|
|
|
|
|
|
|
if ( !GistPageIsLeaf(page) ) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = FirstOffsetNumber; i <= PageGetMaxOffsetNumber(page); i = OffsetNumberNext(i)) {
|
|
|
|
ItemId iid = PageGetItemId(page, i);
|
|
|
|
IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
|
|
|
|
gistDeleteSubtree(gv, ItemPointerGetBlockNumber(&(idxtuple->t_tid)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
START_CRIT_SECTION();
|
|
|
|
|
|
|
|
MarkBufferDirty(buffer);
|
|
|
|
|
|
|
|
page = (Page) BufferGetPage(buffer);
|
|
|
|
GistPageSetDeleted(page);
|
|
|
|
gv->result->std.pages_deleted++;
|
|
|
|
|
|
|
|
if (!gv->index->rd_istemp)
|
|
|
|
{
|
|
|
|
XLogRecData rdata;
|
|
|
|
XLogRecPtr recptr;
|
|
|
|
gistxlogPageDelete xlrec;
|
|
|
|
|
|
|
|
xlrec.node = gv->index->rd_node;
|
|
|
|
xlrec.blkno = blkno;
|
|
|
|
|
|
|
|
rdata.buffer = InvalidBuffer;
|
|
|
|
rdata.data = (char *) &xlrec;
|
|
|
|
rdata.len = sizeof(gistxlogPageDelete);
|
|
|
|
rdata.next = NULL;
|
|
|
|
|
|
|
|
recptr = XLogInsert(RM_GIST_ID, XLOG_GIST_PAGE_DELETE, &rdata);
|
|
|
|
PageSetLSN(page, recptr);
|
|
|
|
PageSetTLI(page, ThisTimeLineID);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
PageSetLSN(page, XLogRecPtrForTemp);
|
|
|
|
|
|
|
|
END_CRIT_SECTION();
|
|
|
|
|
|
|
|
UnlockReleaseBuffer(buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Page
|
|
|
|
GistPageGetCopyPage( Page page ) {
|
|
|
|
Size pageSize = PageGetPageSize( page );
|
|
|
|
Page tmppage;
|
|
|
|
|
|
|
|
tmppage=(Page)palloc( pageSize );
|
|
|
|
memcpy( tmppage, page, pageSize );
|
|
|
|
|
|
|
|
return tmppage;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ArrayTuple
|
|
|
|
vacuumSplitPage(GistVacuum *gv, Page tempPage, Buffer buffer, IndexTuple *addon, int curlenaddon) {
|
|
|
|
ArrayTuple res = {NULL, 0, false};
|
|
|
|
IndexTuple *vec;
|
|
|
|
SplitedPageLayout *dist = NULL,
|
|
|
|
*ptr;
|
|
|
|
int i, veclen=0;
|
|
|
|
BlockNumber blkno = BufferGetBlockNumber(buffer);
|
|
|
|
MemoryContext oldCtx = MemoryContextSwitchTo(gv->opCtx);
|
|
|
|
|
|
|
|
vec = gistextractpage(tempPage, &veclen);
|
|
|
|
vec = gistjoinvector(vec, &veclen, addon, curlenaddon);
|
|
|
|
dist = gistSplit(gv->index, tempPage, vec, veclen, &(gv->giststate));
|
|
|
|
|
|
|
|
MemoryContextSwitchTo(oldCtx);
|
|
|
|
|
|
|
|
if (blkno != GIST_ROOT_BLKNO) {
|
|
|
|
/* if non-root split then we should not allocate new buffer */
|
|
|
|
dist->buffer = buffer;
|
|
|
|
dist->page = tempPage;
|
|
|
|
/* during vacuum we never split leaf page */
|
|
|
|
GistPageGetOpaque(dist->page)->flags = 0;
|
|
|
|
} else
|
|
|
|
pfree(tempPage);
|
|
|
|
|
|
|
|
res.itup = (IndexTuple *) palloc(sizeof(IndexTuple) * veclen);
|
|
|
|
res.ituplen = 0;
|
|
|
|
|
|
|
|
/* make new pages and fills them */
|
|
|
|
for (ptr = dist; ptr; ptr = ptr->next) {
|
|
|
|
char *data;
|
|
|
|
|
|
|
|
if ( ptr->buffer == InvalidBuffer ) {
|
|
|
|
ptr->buffer = gistNewBuffer( gv->index );
|
|
|
|
GISTInitBuffer( ptr->buffer, 0 );
|
|
|
|
ptr->page = BufferGetPage(ptr->buffer);
|
|
|
|
}
|
|
|
|
ptr->block.blkno = BufferGetBlockNumber( ptr->buffer );
|
|
|
|
|
|
|
|
data = (char*)(ptr->list);
|
|
|
|
for(i=0;i<ptr->block.num;i++) {
|
|
|
|
if ( PageAddItem(ptr->page, (Item)data, IndexTupleSize((IndexTuple)data), i+FirstOffsetNumber, LP_USED) == InvalidOffsetNumber )
|
|
|
|
elog(ERROR, "failed to add item to index page in \"%s\"", RelationGetRelationName(gv->index));
|
|
|
|
data += IndexTupleSize((IndexTuple)data);
|
|
|
|
}
|
|
|
|
|
|
|
|
ItemPointerSetBlockNumber(&(ptr->itup->t_tid), ptr->block.blkno);
|
|
|
|
res.itup[ res.ituplen ] = (IndexTuple)palloc(IndexTupleSize(ptr->itup));
|
|
|
|
memcpy( res.itup[ res.ituplen ], ptr->itup, IndexTupleSize(ptr->itup) );
|
|
|
|
res.ituplen++;
|
|
|
|
}
|
|
|
|
|
|
|
|
START_CRIT_SECTION();
|
|
|
|
|
|
|
|
for (ptr = dist; ptr; ptr = ptr->next) {
|
|
|
|
MarkBufferDirty(ptr->buffer);
|
|
|
|
GistPageGetOpaque(ptr->page)->rightlink = InvalidBlockNumber;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* restore splitted non-root page */
|
|
|
|
if (blkno != GIST_ROOT_BLKNO) {
|
|
|
|
PageRestoreTempPage( dist->page, BufferGetPage( dist->buffer ) );
|
|
|
|
dist->page = BufferGetPage( dist->buffer );
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!gv->index->rd_istemp)
|
|
|
|
{
|
|
|
|
XLogRecPtr recptr;
|
|
|
|
XLogRecData *rdata;
|
|
|
|
ItemPointerData key; /* set key for incomplete
|
|
|
|
* insert */
|
|
|
|
char *xlinfo;
|
|
|
|
|
|
|
|
ItemPointerSet(&key, blkno, TUPLE_IS_VALID);
|
|
|
|
|
|
|
|
rdata = formSplitRdata(gv->index->rd_node, blkno,
|
|
|
|
false, &key, dist);
|
|
|
|
xlinfo = rdata->data;
|
|
|
|
|
|
|
|
recptr = XLogInsert(RM_GIST_ID, XLOG_GIST_PAGE_SPLIT, rdata);
|
|
|
|
for (ptr = dist; ptr; ptr = ptr->next)
|
|
|
|
{
|
|
|
|
PageSetLSN(BufferGetPage(ptr->buffer), recptr);
|
|
|
|
PageSetTLI(BufferGetPage(ptr->buffer), ThisTimeLineID);
|
|
|
|
}
|
|
|
|
|
|
|
|
pfree(xlinfo);
|
|
|
|
pfree(rdata);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (ptr = dist; ptr; ptr = ptr->next)
|
|
|
|
PageSetLSN(BufferGetPage(ptr->buffer), XLogRecPtrForTemp);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (ptr = dist; ptr; ptr = ptr->next)
|
|
|
|
{
|
|
|
|
/* we must keep the buffer pin on the head page */
|
|
|
|
if (BufferGetBlockNumber(ptr->buffer) != blkno)
|
|
|
|
UnlockReleaseBuffer( ptr->buffer );
|
|
|
|
}
|
|
|
|
|
|
|
|
if (blkno == GIST_ROOT_BLKNO)
|
|
|
|
{
|
|
|
|
ItemPointerData key; /* set key for incomplete
|
|
|
|
* insert */
|
|
|
|
|
|
|
|
ItemPointerSet(&key, blkno, TUPLE_IS_VALID);
|
|
|
|
|
|
|
|
gistnewroot(gv->index, buffer, res.itup, res.ituplen, &key);
|
|
|
|
}
|
|
|
|
|
|
|
|
END_CRIT_SECTION();
|
|
|
|
|
|
|
|
MemoryContextReset(gv->opCtx);
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
2006-02-11 23:31:34 +00:00
|
|
|
|
2005-06-20 10:29:37 +00:00
|
|
|
static ArrayTuple
|
2005-09-22 20:44:36 +00:00
|
|
|
gistVacuumUpdate(GistVacuum *gv, BlockNumber blkno, bool needunion)
|
|
|
|
{
|
2005-06-20 10:29:37 +00:00
|
|
|
ArrayTuple res = {NULL, 0, false};
|
|
|
|
Buffer buffer;
|
2006-05-17 16:34:59 +00:00
|
|
|
Page page, tempPage = NULL;
|
2005-09-22 20:44:36 +00:00
|
|
|
OffsetNumber i,
|
|
|
|
maxoff;
|
2005-06-20 10:29:37 +00:00
|
|
|
ItemId iid;
|
2005-09-22 20:44:36 +00:00
|
|
|
int lenaddon = 4,
|
|
|
|
curlenaddon = 0,
|
2006-05-17 16:34:59 +00:00
|
|
|
nOffToDelete = 0,
|
|
|
|
nBlkToDelete = 0;
|
2005-09-22 20:44:36 +00:00
|
|
|
IndexTuple idxtuple,
|
|
|
|
*addon = NULL;
|
|
|
|
bool needwrite = false;
|
2006-05-17 16:34:59 +00:00
|
|
|
OffsetNumber offToDelete[MaxOffsetNumber];
|
|
|
|
BlockNumber blkToDelete[MaxOffsetNumber];
|
2005-09-22 20:44:36 +00:00
|
|
|
ItemPointerData *completed = NULL;
|
|
|
|
int ncompleted = 0,
|
|
|
|
lencompleted = 16;
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2006-02-14 16:39:32 +00:00
|
|
|
vacuum_delay_point();
|
|
|
|
|
2005-06-20 10:29:37 +00:00
|
|
|
buffer = ReadBuffer(gv->index, blkno);
|
2006-03-31 23:32:07 +00:00
|
|
|
LockBuffer(buffer, GIST_EXCLUSIVE);
|
2005-11-06 22:39:21 +00:00
|
|
|
gistcheckpage(gv->index, buffer);
|
2005-06-20 10:29:37 +00:00
|
|
|
page = (Page) BufferGetPage(buffer);
|
|
|
|
maxoff = PageGetMaxOffsetNumber(page);
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
if (GistPageIsLeaf(page))
|
|
|
|
{
|
|
|
|
if (GistTuplesDeleted(page))
|
2005-06-20 10:29:37 +00:00
|
|
|
needunion = needwrite = true;
|
2005-09-22 20:44:36 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
completed = (ItemPointerData *) palloc(sizeof(ItemPointerData) * lencompleted);
|
|
|
|
addon = (IndexTuple *) palloc(sizeof(IndexTuple) * lenaddon);
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
/* get copy of page to work */
|
|
|
|
tempPage = GistPageGetCopyPage(page);
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
|
|
|
|
{
|
|
|
|
ArrayTuple chldtuple;
|
|
|
|
bool needchildunion;
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
iid = PageGetItemId(tempPage, i);
|
|
|
|
idxtuple = (IndexTuple) PageGetItem(tempPage, iid);
|
2005-06-20 10:29:37 +00:00
|
|
|
needchildunion = (GistTupleIsInvalid(idxtuple)) ? true : false;
|
2005-09-22 20:44:36 +00:00
|
|
|
|
|
|
|
if (needchildunion)
|
2005-09-22 18:49:45 +00:00
|
|
|
elog(DEBUG2, "gistVacuumUpdate: need union for block %u",
|
|
|
|
ItemPointerGetBlockNumber(&(idxtuple->t_tid)));
|
2005-09-22 20:44:36 +00:00
|
|
|
|
|
|
|
chldtuple = gistVacuumUpdate(gv, ItemPointerGetBlockNumber(&(idxtuple->t_tid)),
|
|
|
|
needchildunion);
|
|
|
|
if (chldtuple.ituplen || chldtuple.emptypage)
|
|
|
|
{
|
2006-05-17 16:34:59 +00:00
|
|
|
/* update tuple or/and inserts new */
|
|
|
|
if ( chldtuple.emptypage )
|
|
|
|
blkToDelete[nBlkToDelete++] = ItemPointerGetBlockNumber(&(idxtuple->t_tid));
|
|
|
|
offToDelete[nOffToDelete++] = i;
|
|
|
|
PageIndexTupleDelete(tempPage, i);
|
2005-09-22 20:44:36 +00:00
|
|
|
i--;
|
|
|
|
maxoff--;
|
|
|
|
needwrite = needunion = true;
|
|
|
|
|
|
|
|
if (chldtuple.ituplen)
|
|
|
|
{
|
2006-05-17 16:34:59 +00:00
|
|
|
|
|
|
|
Assert( chldtuple.emptypage == false );
|
2005-09-22 20:44:36 +00:00
|
|
|
while (curlenaddon + chldtuple.ituplen >= lenaddon)
|
|
|
|
{
|
|
|
|
lenaddon *= 2;
|
|
|
|
addon = (IndexTuple *) repalloc(addon, sizeof(IndexTuple) * lenaddon);
|
2005-06-20 10:29:37 +00:00
|
|
|
}
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
memcpy(addon + curlenaddon, chldtuple.itup, chldtuple.ituplen * sizeof(IndexTuple));
|
2005-06-20 10:29:37 +00:00
|
|
|
|
|
|
|
curlenaddon += chldtuple.ituplen;
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
if (chldtuple.ituplen > 1)
|
|
|
|
{
|
|
|
|
/*
|
2006-02-11 23:31:34 +00:00
|
|
|
* child was split, so we need mark completion
|
2005-09-22 20:44:36 +00:00
|
|
|
* insert(split)
|
|
|
|
*/
|
|
|
|
int j;
|
|
|
|
|
|
|
|
while (ncompleted + chldtuple.ituplen > lencompleted)
|
|
|
|
{
|
|
|
|
lencompleted *= 2;
|
|
|
|
completed = (ItemPointerData *) repalloc(completed, sizeof(ItemPointerData) * lencompleted);
|
|
|
|
}
|
|
|
|
for (j = 0; j < chldtuple.ituplen; j++)
|
|
|
|
{
|
|
|
|
ItemPointerCopy(&(chldtuple.itup[j]->t_tid), completed + ncompleted);
|
|
|
|
ncompleted++;
|
2005-06-20 10:29:37 +00:00
|
|
|
}
|
|
|
|
}
|
2005-09-22 20:44:36 +00:00
|
|
|
pfree(chldtuple.itup);
|
2005-06-20 10:29:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2006-05-17 16:34:59 +00:00
|
|
|
|
|
|
|
Assert( maxoff == PageGetMaxOffsetNumber(tempPage) );
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
if (curlenaddon)
|
|
|
|
{
|
2005-06-20 10:29:37 +00:00
|
|
|
/* insert updated tuples */
|
2006-05-17 16:34:59 +00:00
|
|
|
if (gistnospace(tempPage, addon, curlenaddon, InvalidOffsetNumber)) {
|
2005-06-20 10:29:37 +00:00
|
|
|
/* there is no space on page to insert tuples */
|
2006-05-17 16:34:59 +00:00
|
|
|
res = vacuumSplitPage(gv, tempPage, buffer, addon, curlenaddon);
|
|
|
|
tempPage=NULL; /* vacuumSplitPage() free tempPage */
|
|
|
|
needwrite = needunion = false; /* gistSplit already forms unions and writes pages */
|
|
|
|
} else
|
|
|
|
/* enough free space */
|
|
|
|
gistfillbuffer(gv->index, tempPage, addon, curlenaddon, InvalidOffsetNumber);
|
|
|
|
}
|
|
|
|
}
|
2005-09-22 20:44:36 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
/*
|
|
|
|
* If page is empty, we should remove pointer to it before
|
|
|
|
* deleting page (except root)
|
|
|
|
*/
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
if ( blkno != GIST_ROOT_BLKNO && ( PageIsEmpty(page) || (tempPage && PageIsEmpty(tempPage)) ) ) {
|
|
|
|
/*
|
|
|
|
* New version of page is empty, so leave it unchanged,
|
|
|
|
* upper call will mark our page as deleted.
|
|
|
|
* In case of page split we never will be here...
|
|
|
|
*
|
|
|
|
* If page was empty it can't become non-empty during processing
|
|
|
|
*/
|
|
|
|
res.emptypage = true;
|
|
|
|
UnlockReleaseBuffer(buffer);
|
|
|
|
} else {
|
|
|
|
/* write page and remove its childs if it need */
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
START_CRIT_SECTION();
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
if ( tempPage && needwrite ) {
|
|
|
|
PageRestoreTempPage(tempPage, page);
|
|
|
|
tempPage = NULL;
|
|
|
|
}
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
/* Empty index */
|
|
|
|
if (PageIsEmpty(page) && blkno == GIST_ROOT_BLKNO )
|
|
|
|
{
|
|
|
|
needwrite = true;
|
|
|
|
GistPageSetLeaf(page);
|
|
|
|
}
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
|
|
|
|
if (needwrite)
|
|
|
|
{
|
|
|
|
MarkBufferDirty(buffer);
|
|
|
|
GistClearTuplesDeleted(page);
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
if (!gv->index->rd_istemp)
|
|
|
|
{
|
|
|
|
XLogRecData *rdata;
|
|
|
|
XLogRecPtr recptr;
|
|
|
|
char *xlinfo;
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
rdata = formUpdateRdata(gv->index->rd_node, buffer,
|
|
|
|
offToDelete, nOffToDelete,
|
|
|
|
addon, curlenaddon, NULL);
|
|
|
|
xlinfo = rdata->next->data;
|
2005-09-22 20:44:36 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
recptr = XLogInsert(RM_GIST_ID, XLOG_GIST_PAGE_UPDATE, rdata);
|
|
|
|
PageSetLSN(page, recptr);
|
|
|
|
PageSetTLI(page, ThisTimeLineID);
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
pfree(xlinfo);
|
|
|
|
pfree(rdata);
|
2005-09-22 20:44:36 +00:00
|
|
|
}
|
|
|
|
else
|
2006-05-17 16:34:59 +00:00
|
|
|
PageSetLSN(page, XLogRecPtrForTemp);
|
2005-06-20 10:29:37 +00:00
|
|
|
}
|
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
END_CRIT_SECTION();
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
if ( needunion && !PageIsEmpty(page) )
|
|
|
|
{
|
2005-09-22 20:44:36 +00:00
|
|
|
res.itup = (IndexTuple *) palloc(sizeof(IndexTuple));
|
2005-06-20 10:29:37 +00:00
|
|
|
res.ituplen = 1;
|
2006-05-17 16:34:59 +00:00
|
|
|
res.itup[0] = PageMakeUnionKey(gv, buffer);
|
2005-06-20 10:29:37 +00:00
|
|
|
}
|
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
UnlockReleaseBuffer(buffer);
|
2005-09-22 20:44:36 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
/* delete empty children, now we havn't any links to pointed subtrees */
|
|
|
|
for(i=0;i<nBlkToDelete;i++)
|
|
|
|
gistDeleteSubtree(gv, blkToDelete[i]);
|
2005-06-27 12:45:23 +00:00
|
|
|
|
2006-05-17 16:34:59 +00:00
|
|
|
if (ncompleted && !gv->index->rd_istemp)
|
|
|
|
gistxlogInsertCompletion(gv->index->rd_node, completed, ncompleted);
|
2005-09-22 20:44:36 +00:00
|
|
|
}
|
2005-06-20 10:29:37 +00:00
|
|
|
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
for (i = 0; i < curlenaddon; i++)
|
|
|
|
pfree(addon[i]);
|
|
|
|
if (addon)
|
|
|
|
pfree(addon);
|
|
|
|
if (completed)
|
|
|
|
pfree(completed);
|
2006-05-17 16:34:59 +00:00
|
|
|
if (tempPage)
|
|
|
|
pfree(tempPage);
|
|
|
|
|
2005-06-20 10:29:37 +00:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2006-02-11 23:31:34 +00:00
|
|
|
* For usual vacuum just update FSM, for full vacuum
|
2005-06-20 10:29:37 +00:00
|
|
|
* reforms parent tuples if some of childs was deleted or changed,
|
2006-02-11 23:31:34 +00:00
|
|
|
* update invalid tuples (they can exist from last crash recovery only),
|
2005-06-20 10:29:37 +00:00
|
|
|
* tries to get smaller index
|
|
|
|
*/
|
|
|
|
|
|
|
|
Datum
|
2005-09-22 20:44:36 +00:00
|
|
|
gistvacuumcleanup(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2006-05-02 22:25:10 +00:00
|
|
|
IndexVacuumInfo *info = (IndexVacuumInfo *) PG_GETARG_POINTER(0);
|
|
|
|
GistBulkDeleteResult *stats = (GistBulkDeleteResult *) PG_GETARG_POINTER(1);
|
|
|
|
Relation rel = info->index;
|
2005-09-22 20:44:36 +00:00
|
|
|
BlockNumber npages,
|
|
|
|
blkno;
|
|
|
|
BlockNumber nFreePages,
|
|
|
|
*freePages,
|
|
|
|
maxFreePages;
|
|
|
|
BlockNumber lastBlock = GIST_ROOT_BLKNO,
|
|
|
|
lastFilledBlock = GIST_ROOT_BLKNO;
|
|
|
|
bool needLock;
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2006-05-02 22:25:10 +00:00
|
|
|
/* Set up all-zero stats if gistbulkdelete wasn't called */
|
|
|
|
if (stats == NULL)
|
|
|
|
{
|
|
|
|
stats = (GistBulkDeleteResult *) palloc0(sizeof(GistBulkDeleteResult));
|
|
|
|
/* use heap's tuple count */
|
|
|
|
Assert(info->num_heap_tuples >= 0);
|
|
|
|
stats->std.num_index_tuples = info->num_heap_tuples;
|
|
|
|
/*
|
|
|
|
* XXX the above is wrong if index is partial. Would it be OK to
|
|
|
|
* just return NULL, or is there work we must do below?
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
2005-06-20 10:29:37 +00:00
|
|
|
/* gistVacuumUpdate may cause hard work */
|
2005-09-22 20:44:36 +00:00
|
|
|
if (info->vacuum_full)
|
|
|
|
{
|
2005-06-20 10:29:37 +00:00
|
|
|
GistVacuum gv;
|
|
|
|
ArrayTuple res;
|
|
|
|
|
2005-06-27 12:45:23 +00:00
|
|
|
LockRelation(rel, AccessExclusiveLock);
|
|
|
|
|
2005-06-20 10:29:37 +00:00
|
|
|
gv.index = rel;
|
|
|
|
initGISTstate(&(gv.giststate), rel);
|
|
|
|
gv.opCtx = createTempGistContext();
|
|
|
|
gv.result = stats;
|
|
|
|
|
|
|
|
/* walk through the entire index for update tuples */
|
2005-09-22 20:44:36 +00:00
|
|
|
res = gistVacuumUpdate(&gv, GIST_ROOT_BLKNO, false);
|
|
|
|
/* cleanup */
|
|
|
|
if (res.itup)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < res.ituplen; i++)
|
|
|
|
pfree(res.itup[i]);
|
|
|
|
pfree(res.itup);
|
2005-06-20 10:29:37 +00:00
|
|
|
}
|
2005-09-22 20:44:36 +00:00
|
|
|
freeGISTstate(&(gv.giststate));
|
|
|
|
MemoryContextDelete(gv.opCtx);
|
|
|
|
}
|
2006-02-11 23:31:34 +00:00
|
|
|
else if (stats->needFullVacuum)
|
2005-09-22 18:49:45 +00:00
|
|
|
ereport(NOTICE,
|
|
|
|
(errmsg("index \"%s\" needs VACUUM FULL or REINDEX to finish crash recovery",
|
|
|
|
RelationGetRelationName(rel))));
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
if (info->vacuum_full)
|
|
|
|
needLock = false; /* relation locked with AccessExclusiveLock */
|
2005-11-06 22:39:21 +00:00
|
|
|
else
|
|
|
|
needLock = !RELATION_IS_LOCAL(rel);
|
2005-06-27 12:45:23 +00:00
|
|
|
|
2005-06-20 10:29:37 +00:00
|
|
|
/* try to find deleted pages */
|
2005-06-27 12:45:23 +00:00
|
|
|
if (needLock)
|
|
|
|
LockRelationForExtension(rel, ExclusiveLock);
|
2005-06-20 10:29:37 +00:00
|
|
|
npages = RelationGetNumberOfBlocks(rel);
|
2005-06-27 12:45:23 +00:00
|
|
|
if (needLock)
|
|
|
|
UnlockRelationForExtension(rel, ExclusiveLock);
|
|
|
|
|
|
|
|
maxFreePages = npages;
|
2005-09-22 20:44:36 +00:00
|
|
|
if (maxFreePages > MaxFSMPages)
|
2005-06-20 10:29:37 +00:00
|
|
|
maxFreePages = MaxFSMPages;
|
2005-06-27 12:45:23 +00:00
|
|
|
|
2005-06-20 10:29:37 +00:00
|
|
|
nFreePages = 0;
|
2005-09-22 20:44:36 +00:00
|
|
|
freePages = (BlockNumber *) palloc(sizeof(BlockNumber) * maxFreePages);
|
|
|
|
for (blkno = GIST_ROOT_BLKNO + 1; blkno < npages; blkno++)
|
|
|
|
{
|
2006-02-14 16:39:32 +00:00
|
|
|
Buffer buffer;
|
2005-09-22 20:44:36 +00:00
|
|
|
Page page;
|
|
|
|
|
2006-02-14 16:39:32 +00:00
|
|
|
vacuum_delay_point();
|
|
|
|
|
|
|
|
buffer = ReadBuffer(rel, blkno);
|
2005-09-22 20:44:36 +00:00
|
|
|
LockBuffer(buffer, GIST_SHARE);
|
|
|
|
page = (Page) BufferGetPage(buffer);
|
|
|
|
|
2005-11-06 22:39:21 +00:00
|
|
|
if (PageIsNew(page) || GistPageIsDeleted(page))
|
2005-09-22 20:44:36 +00:00
|
|
|
{
|
|
|
|
if (nFreePages < maxFreePages)
|
|
|
|
{
|
|
|
|
freePages[nFreePages] = blkno;
|
2005-06-20 10:29:37 +00:00
|
|
|
nFreePages++;
|
|
|
|
}
|
2005-09-22 20:44:36 +00:00
|
|
|
}
|
|
|
|
else
|
2005-06-20 10:29:37 +00:00
|
|
|
lastFilledBlock = blkno;
|
2006-03-31 23:32:07 +00:00
|
|
|
UnlockReleaseBuffer(buffer);
|
2005-06-20 10:29:37 +00:00
|
|
|
}
|
2005-09-22 20:44:36 +00:00
|
|
|
lastBlock = npages - 1;
|
|
|
|
|
|
|
|
if (info->vacuum_full && nFreePages > 0)
|
|
|
|
{ /* try to truncate index */
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < nFreePages; i++)
|
|
|
|
if (freePages[i] >= lastFilledBlock)
|
|
|
|
{
|
2005-06-27 12:45:23 +00:00
|
|
|
nFreePages = i;
|
|
|
|
break;
|
|
|
|
}
|
2005-09-22 20:44:36 +00:00
|
|
|
|
|
|
|
if (lastBlock > lastFilledBlock)
|
|
|
|
RelationTruncate(rel, lastFilledBlock + 1);
|
2006-02-11 23:31:34 +00:00
|
|
|
stats->std.pages_removed = lastBlock - lastFilledBlock;
|
2005-06-20 10:29:37 +00:00
|
|
|
}
|
2005-09-22 20:44:36 +00:00
|
|
|
|
|
|
|
RecordIndexFreeSpace(&rel->rd_node, nFreePages, freePages);
|
|
|
|
pfree(freePages);
|
2005-06-20 10:29:37 +00:00
|
|
|
|
|
|
|
/* return statistics */
|
2006-02-11 23:31:34 +00:00
|
|
|
stats->std.pages_free = nFreePages;
|
2005-06-27 12:45:23 +00:00
|
|
|
if (needLock)
|
|
|
|
LockRelationForExtension(rel, ExclusiveLock);
|
2006-02-11 23:31:34 +00:00
|
|
|
stats->std.num_pages = RelationGetNumberOfBlocks(rel);
|
2005-06-27 12:45:23 +00:00
|
|
|
if (needLock)
|
|
|
|
UnlockRelationForExtension(rel, ExclusiveLock);
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2005-06-27 12:45:23 +00:00
|
|
|
if (info->vacuum_full)
|
2005-09-22 20:44:36 +00:00
|
|
|
UnlockRelation(rel, AccessExclusiveLock);
|
2005-06-20 10:29:37 +00:00
|
|
|
|
|
|
|
PG_RETURN_POINTER(stats);
|
|
|
|
}
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
typedef struct GistBDItem
|
|
|
|
{
|
2005-06-27 12:45:23 +00:00
|
|
|
GistNSN parentlsn;
|
2005-09-22 20:44:36 +00:00
|
|
|
BlockNumber blkno;
|
|
|
|
struct GistBDItem *next;
|
2005-06-20 10:29:37 +00:00
|
|
|
} GistBDItem;
|
|
|
|
|
2005-06-27 12:45:23 +00:00
|
|
|
static void
|
2005-09-22 20:44:36 +00:00
|
|
|
pushStackIfSplited(Page page, GistBDItem *stack)
|
|
|
|
{
|
2005-06-27 12:45:23 +00:00
|
|
|
GISTPageOpaque opaque = GistPageGetOpaque(page);
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
if (stack->blkno != GIST_ROOT_BLKNO && !XLogRecPtrIsInvalid(stack->parentlsn) &&
|
|
|
|
XLByteLT(stack->parentlsn, opaque->nsn) &&
|
|
|
|
opaque->rightlink != InvalidBlockNumber /* sanity check */ )
|
|
|
|
{
|
2005-06-27 12:45:23 +00:00
|
|
|
/* split page detected, install right link to the stack */
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
GistBDItem *ptr = (GistBDItem *) palloc(sizeof(GistBDItem));
|
|
|
|
|
2005-06-27 12:45:23 +00:00
|
|
|
ptr->blkno = opaque->rightlink;
|
|
|
|
ptr->parentlsn = stack->parentlsn;
|
|
|
|
ptr->next = stack->next;
|
|
|
|
stack->next = ptr;
|
|
|
|
}
|
2005-09-22 20:44:36 +00:00
|
|
|
}
|
2005-06-27 12:45:23 +00:00
|
|
|
|
|
|
|
|
2005-06-20 10:29:37 +00:00
|
|
|
/*
|
|
|
|
* Bulk deletion of all index entries pointing to a set of heap tuples and
|
2005-06-27 12:45:23 +00:00
|
|
|
* check invalid tuples after crash recovery.
|
2005-06-20 10:29:37 +00:00
|
|
|
* The set of target tuples is specified via a callback routine that tells
|
|
|
|
* whether any given heap tuple (identified by ItemPointer) is being deleted.
|
|
|
|
*
|
|
|
|
* Result: a palloc'd struct containing statistical info for VACUUM displays.
|
|
|
|
*/
|
|
|
|
Datum
|
2005-09-22 20:44:36 +00:00
|
|
|
gistbulkdelete(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2006-05-02 22:25:10 +00:00
|
|
|
IndexVacuumInfo *info = (IndexVacuumInfo *) PG_GETARG_POINTER(0);
|
|
|
|
GistBulkDeleteResult *stats = (GistBulkDeleteResult *) PG_GETARG_POINTER(1);
|
|
|
|
IndexBulkDeleteCallback callback = (IndexBulkDeleteCallback) PG_GETARG_POINTER(2);
|
|
|
|
void *callback_state = (void *) PG_GETARG_POINTER(3);
|
|
|
|
Relation rel = info->index;
|
2005-09-22 20:44:36 +00:00
|
|
|
GistBDItem *stack,
|
|
|
|
*ptr;
|
|
|
|
|
2006-05-02 22:25:10 +00:00
|
|
|
/* first time through? */
|
|
|
|
if (stats == NULL)
|
|
|
|
stats = (GistBulkDeleteResult *) palloc0(sizeof(GistBulkDeleteResult));
|
|
|
|
/* we'll re-count the tuples each time */
|
|
|
|
stats->std.num_index_tuples = 0;
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2006-05-02 22:25:10 +00:00
|
|
|
stack = (GistBDItem *) palloc0(sizeof(GistBDItem));
|
|
|
|
stack->blkno = GIST_ROOT_BLKNO;
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
while (stack)
|
|
|
|
{
|
|
|
|
Buffer buffer = ReadBuffer(rel, stack->blkno);
|
|
|
|
Page page;
|
|
|
|
OffsetNumber i,
|
|
|
|
maxoff;
|
2005-06-20 10:29:37 +00:00
|
|
|
IndexTuple idxtuple;
|
|
|
|
ItemId iid;
|
2005-06-27 12:45:23 +00:00
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
LockBuffer(buffer, GIST_SHARE);
|
2005-11-06 22:39:21 +00:00
|
|
|
gistcheckpage(rel, buffer);
|
2005-09-22 20:44:36 +00:00
|
|
|
page = (Page) BufferGetPage(buffer);
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
if (GistPageIsLeaf(page))
|
|
|
|
{
|
2005-09-02 19:02:20 +00:00
|
|
|
OffsetNumber todelete[MaxOffsetNumber];
|
2005-09-22 20:44:36 +00:00
|
|
|
int ntodelete = 0;
|
2005-06-27 12:45:23 +00:00
|
|
|
|
|
|
|
LockBuffer(buffer, GIST_UNLOCK);
|
|
|
|
LockBuffer(buffer, GIST_EXCLUSIVE);
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
page = (Page) BufferGetPage(buffer);
|
|
|
|
if (stack->blkno == GIST_ROOT_BLKNO && !GistPageIsLeaf(page))
|
|
|
|
{
|
2006-02-14 16:39:32 +00:00
|
|
|
/* only the root can become non-leaf during relock */
|
2006-03-31 23:32:07 +00:00
|
|
|
UnlockReleaseBuffer(buffer);
|
2005-06-27 12:45:23 +00:00
|
|
|
/* one more check */
|
|
|
|
continue;
|
|
|
|
}
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
/*
|
|
|
|
* check for split proceeded after look at parent, we should check
|
|
|
|
* it after relock
|
|
|
|
*/
|
2005-06-27 12:45:23 +00:00
|
|
|
pushStackIfSplited(page, stack);
|
|
|
|
|
2006-03-30 23:03:10 +00:00
|
|
|
/*
|
|
|
|
* Remove deletable tuples from page
|
|
|
|
*/
|
|
|
|
|
2005-06-27 12:45:23 +00:00
|
|
|
maxoff = PageGetMaxOffsetNumber(page);
|
2005-06-20 10:29:37 +00:00
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
|
|
|
|
{
|
|
|
|
iid = PageGetItemId(page, i);
|
2005-06-20 10:29:37 +00:00
|
|
|
idxtuple = (IndexTuple) PageGetItem(page, iid);
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
if (callback(&(idxtuple->t_tid), callback_state))
|
|
|
|
{
|
2006-05-10 09:19:54 +00:00
|
|
|
todelete[ntodelete] = i-ntodelete;
|
2005-09-22 20:44:36 +00:00
|
|
|
ntodelete++;
|
2006-05-02 22:25:10 +00:00
|
|
|
stats->std.tuples_removed += 1;
|
2005-09-22 20:44:36 +00:00
|
|
|
}
|
|
|
|
else
|
2006-05-02 22:25:10 +00:00
|
|
|
stats->std.num_index_tuples += 1;
|
2005-06-20 10:29:37 +00:00
|
|
|
}
|
2005-06-27 12:45:23 +00:00
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
if (ntodelete)
|
|
|
|
{
|
2006-05-10 09:19:54 +00:00
|
|
|
START_CRIT_SECTION();
|
2005-06-27 12:45:23 +00:00
|
|
|
|
2006-03-31 23:32:07 +00:00
|
|
|
MarkBufferDirty(buffer);
|
|
|
|
|
2006-05-10 09:19:54 +00:00
|
|
|
for(i=0;i<ntodelete;i++)
|
|
|
|
PageIndexTupleDelete(page, todelete[i]);
|
|
|
|
GistMarkTuplesDeleted(page);
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
if (!rel->rd_istemp)
|
|
|
|
{
|
2005-06-27 12:45:23 +00:00
|
|
|
XLogRecData *rdata;
|
2005-09-22 20:44:36 +00:00
|
|
|
XLogRecPtr recptr;
|
2006-03-30 23:03:10 +00:00
|
|
|
gistxlogPageUpdate *xlinfo;
|
2005-06-27 12:45:23 +00:00
|
|
|
|
2006-03-30 23:03:10 +00:00
|
|
|
rdata = formUpdateRdata(rel->rd_node, buffer,
|
2006-05-17 16:34:59 +00:00
|
|
|
todelete, ntodelete,
|
2006-03-30 23:03:10 +00:00
|
|
|
NULL, 0,
|
|
|
|
NULL);
|
2006-05-17 16:34:59 +00:00
|
|
|
xlinfo = (gistxlogPageUpdate *) rdata->next->data;
|
2005-06-27 12:45:23 +00:00
|
|
|
|
2006-03-30 23:03:10 +00:00
|
|
|
recptr = XLogInsert(RM_GIST_ID, XLOG_GIST_PAGE_UPDATE, rdata);
|
2005-06-27 12:45:23 +00:00
|
|
|
PageSetLSN(page, recptr);
|
|
|
|
PageSetTLI(page, ThisTimeLineID);
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
pfree(xlinfo);
|
|
|
|
pfree(rdata);
|
|
|
|
}
|
|
|
|
else
|
2005-06-27 12:45:23 +00:00
|
|
|
PageSetLSN(page, XLogRecPtrForTemp);
|
2006-05-10 09:19:54 +00:00
|
|
|
|
|
|
|
END_CRIT_SECTION();
|
2005-06-27 12:45:23 +00:00
|
|
|
}
|
2006-03-30 23:03:10 +00:00
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2005-06-27 12:45:23 +00:00
|
|
|
/* check for split proceeded after look at parent */
|
|
|
|
pushStackIfSplited(page, stack);
|
|
|
|
|
|
|
|
maxoff = PageGetMaxOffsetNumber(page);
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
|
|
|
|
{
|
2005-06-20 10:29:37 +00:00
|
|
|
iid = PageGetItemId(page, i);
|
|
|
|
idxtuple = (IndexTuple) PageGetItem(page, iid);
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
ptr = (GistBDItem *) palloc(sizeof(GistBDItem));
|
|
|
|
ptr->blkno = ItemPointerGetBlockNumber(&(idxtuple->t_tid));
|
|
|
|
ptr->parentlsn = PageGetLSN(page);
|
2005-06-20 10:29:37 +00:00
|
|
|
ptr->next = stack->next;
|
|
|
|
stack->next = ptr;
|
|
|
|
|
2005-09-22 20:44:36 +00:00
|
|
|
if (GistTupleIsInvalid(idxtuple))
|
2006-05-02 22:25:10 +00:00
|
|
|
stats->needFullVacuum = true;
|
2005-06-20 10:29:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-03-31 23:32:07 +00:00
|
|
|
UnlockReleaseBuffer(buffer);
|
2005-06-20 10:29:37 +00:00
|
|
|
|
|
|
|
ptr = stack->next;
|
2005-09-22 20:44:36 +00:00
|
|
|
pfree(stack);
|
2005-06-20 10:29:37 +00:00
|
|
|
stack = ptr;
|
2005-06-20 15:22:38 +00:00
|
|
|
|
|
|
|
vacuum_delay_point();
|
2005-06-20 10:29:37 +00:00
|
|
|
}
|
|
|
|
|
2006-05-02 22:25:10 +00:00
|
|
|
PG_RETURN_POINTER(stats);
|
2005-06-20 10:29:37 +00:00
|
|
|
}
|