1996-07-09 08:22:35 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
1999-02-14 00:22:53 +01:00
|
|
|
* nbtpage.c
|
1997-09-07 07:04:48 +02:00
|
|
|
* BTree-specific page management code for the Postgres btree access
|
|
|
|
* method.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2003-08-04 04:40:20 +02:00
|
|
|
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
|
2000-01-26 06:58:53 +01:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2003-08-08 23:42:59 +02:00
|
|
|
* $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtpage.c,v 1.69 2003/08/08 21:41:27 momjian Exp $
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
1997-09-07 07:04:48 +02:00
|
|
|
* NOTES
|
|
|
|
* Postgres btree pages look like ordinary relation pages. The opaque
|
|
|
|
* data at high addresses includes pointers to left and right siblings
|
|
|
|
* and flag data describing page state. The first page in a btree, page
|
|
|
|
* zero, is special -- it stores meta-information describing the tree.
|
|
|
|
* Pages one and higher store the actual tree data.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
1999-07-16 01:04:24 +02:00
|
|
|
#include "postgres.h"
|
1996-11-03 13:35:27 +01:00
|
|
|
|
1999-07-16 07:00:38 +02:00
|
|
|
#include "access/nbtree.h"
|
|
|
|
#include "miscadmin.h"
|
2003-02-23 07:17:13 +01:00
|
|
|
#include "storage/freespace.h"
|
2000-11-30 02:39:08 +01:00
|
|
|
#include "storage/lmgr.h"
|
|
|
|
|
2003-02-21 01:06:22 +01:00
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/*
|
2003-02-21 01:06:22 +01:00
|
|
|
* _bt_metapinit() -- Initialize the metadata page of a new btree.
|
2003-02-22 01:45:05 +01:00
|
|
|
*
|
|
|
|
* Note: there's no real need for any locking here. Since the transaction
|
|
|
|
* creating the index hasn't committed yet, no one else can even see the index
|
|
|
|
* much less be trying to use it.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
_bt_metapinit(Relation rel)
|
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
Buffer buf;
|
|
|
|
Page pg;
|
2003-02-21 01:06:22 +01:00
|
|
|
BTMetaPageData *metad;
|
1997-09-08 04:41:22 +02:00
|
|
|
BTPageOpaque op;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-06-28 01:31:40 +02:00
|
|
|
if (RelationGetNumberOfBlocks(rel) != 0)
|
2003-07-21 22:29:40 +02:00
|
|
|
elog(ERROR, "cannot initialize non-empty btree index \"%s\"",
|
1997-09-07 07:04:48 +02:00
|
|
|
RelationGetRelationName(rel));
|
|
|
|
|
|
|
|
buf = ReadBuffer(rel, P_NEW);
|
2003-02-21 01:06:22 +01:00
|
|
|
Assert(BufferGetBlockNumber(buf) == BTREE_METAPAGE);
|
1997-09-07 07:04:48 +02:00
|
|
|
pg = BufferGetPage(buf);
|
2003-02-21 01:06:22 +01:00
|
|
|
|
|
|
|
/* NO ELOG(ERROR) from here till newmeta op is logged */
|
|
|
|
START_CRIT_SECTION();
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
_bt_pageinit(pg, BufferGetPageSize(buf));
|
|
|
|
|
2003-02-21 01:06:22 +01:00
|
|
|
metad = BTPageGetMeta(pg);
|
|
|
|
metad->btm_magic = BTREE_MAGIC;
|
|
|
|
metad->btm_version = BTREE_VERSION;
|
|
|
|
metad->btm_root = P_NONE;
|
|
|
|
metad->btm_level = 0;
|
|
|
|
metad->btm_fastroot = P_NONE;
|
|
|
|
metad->btm_fastlevel = 0;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
op = (BTPageOpaque) PageGetSpecialPointer(pg);
|
|
|
|
op->btpo_flags = BTP_META;
|
|
|
|
|
2003-02-21 01:06:22 +01:00
|
|
|
/* XLOG stuff */
|
|
|
|
if (!rel->rd_istemp)
|
|
|
|
{
|
|
|
|
xl_btree_newmeta xlrec;
|
|
|
|
XLogRecPtr recptr;
|
|
|
|
XLogRecData rdata[1];
|
|
|
|
|
|
|
|
xlrec.node = rel->rd_node;
|
|
|
|
xlrec.meta.root = metad->btm_root;
|
|
|
|
xlrec.meta.level = metad->btm_level;
|
|
|
|
xlrec.meta.fastroot = metad->btm_fastroot;
|
|
|
|
xlrec.meta.fastlevel = metad->btm_fastlevel;
|
|
|
|
|
|
|
|
rdata[0].buffer = InvalidBuffer;
|
|
|
|
rdata[0].data = (char *) &xlrec;
|
|
|
|
rdata[0].len = SizeOfBtreeNewmeta;
|
|
|
|
rdata[0].next = NULL;
|
|
|
|
|
|
|
|
recptr = XLogInsert(RM_BTREE_ID, XLOG_BTREE_NEWMETA, rdata);
|
|
|
|
|
|
|
|
PageSetLSN(pg, recptr);
|
|
|
|
PageSetSUI(pg, ThisStartUpID);
|
|
|
|
}
|
|
|
|
|
|
|
|
END_CRIT_SECTION();
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
WriteBuffer(buf);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
1997-09-07 07:04:48 +02:00
|
|
|
* _bt_getroot() -- Get the root page of the btree.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
1997-09-07 07:04:48 +02:00
|
|
|
* Since the root page can move around the btree file, we have to read
|
|
|
|
* its location from the metadata page, and then read the root page
|
|
|
|
* itself. If no root page exists yet, we have to create one. The
|
|
|
|
* standard class of race conditions exists here; I think I covered
|
|
|
|
* them all in the Hopi Indian rain dance of lock requests below.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2000-07-21 08:42:39 +02:00
|
|
|
* The access type parameter (BT_READ or BT_WRITE) controls whether
|
|
|
|
* a new root page will be created or not. If access = BT_READ,
|
2001-03-22 05:01:46 +01:00
|
|
|
* and no root page exists, we just return InvalidBuffer. For
|
2000-07-21 08:42:39 +02:00
|
|
|
* BT_WRITE, we try to create the root page if it doesn't exist.
|
|
|
|
* NOTE that the returned root page will have only a read lock set
|
|
|
|
* on it even if access = BT_WRITE!
|
|
|
|
*
|
2003-02-21 01:06:22 +01:00
|
|
|
* The returned page is not necessarily the true root --- it could be
|
|
|
|
* a "fast root" (a page that is alone in its level due to deletions).
|
|
|
|
* Also, if the root page is split while we are "in flight" to it,
|
|
|
|
* what we will return is the old root, which is now just the leftmost
|
|
|
|
* page on a probably-not-very-wide level. For most purposes this is
|
|
|
|
* as good as or better than the true root, so we do not bother to
|
2003-02-22 01:45:05 +01:00
|
|
|
* insist on finding the true root. We do, however, guarantee to
|
|
|
|
* return a live (not deleted or half-dead) page.
|
2003-02-21 01:06:22 +01:00
|
|
|
*
|
2000-07-21 08:42:39 +02:00
|
|
|
* On successful return, the root page is pinned and read-locked.
|
|
|
|
* The metadata page is not locked or pinned on exit.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
Buffer
|
|
|
|
_bt_getroot(Relation rel, int access)
|
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
Buffer metabuf;
|
|
|
|
Page metapg;
|
|
|
|
BTPageOpaque metaopaque;
|
|
|
|
Buffer rootbuf;
|
2000-10-04 02:04:43 +02:00
|
|
|
Page rootpage;
|
1997-09-08 04:41:22 +02:00
|
|
|
BTPageOpaque rootopaque;
|
|
|
|
BlockNumber rootblkno;
|
2003-02-22 01:45:05 +01:00
|
|
|
uint32 rootlevel;
|
1997-09-07 07:04:48 +02:00
|
|
|
BTMetaPageData *metad;
|
|
|
|
|
|
|
|
metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
|
1996-07-09 08:22:35 +02:00
|
|
|
metapg = BufferGetPage(metabuf);
|
|
|
|
metaopaque = (BTPageOpaque) PageGetSpecialPointer(metapg);
|
|
|
|
metad = BTPageGetMeta(metapg);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2003-02-22 01:45:05 +01:00
|
|
|
/* sanity-check the metapage */
|
2000-07-21 08:42:39 +02:00
|
|
|
if (!(metaopaque->btpo_flags & BTP_META) ||
|
|
|
|
metad->btm_magic != BTREE_MAGIC)
|
2003-07-21 22:29:40 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
|
|
|
errmsg("index \"%s\" is not a btree",
|
|
|
|
RelationGetRelationName(rel))));
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
if (metad->btm_version != BTREE_VERSION)
|
2003-07-21 22:29:40 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
|
|
|
errmsg("version mismatch in \"%s\": file version %d, code version %d",
|
|
|
|
RelationGetRelationName(rel),
|
|
|
|
metad->btm_version, BTREE_VERSION)));
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/* if no root page initialized yet, do it */
|
|
|
|
if (metad->btm_root == P_NONE)
|
|
|
|
{
|
2000-07-21 08:42:39 +02:00
|
|
|
/* If access = BT_READ, caller doesn't want us to create root yet */
|
|
|
|
if (access == BT_READ)
|
|
|
|
{
|
Restructure index AM interface for index building and index tuple deletion,
per previous discussion on pghackers. Most of the duplicate code in
different AMs' ambuild routines has been moved out to a common routine
in index.c; this means that all index types now do the right things about
inserting recently-dead tuples, etc. (I also removed support for EXTEND
INDEX in the ambuild routines, since that's about to go away anyway, and
it cluttered the code a lot.) The retail indextuple deletion routines have
been replaced by a "bulk delete" routine in which the indexscan is inside
the access method. I haven't pushed this change as far as it should go yet,
but it should allow considerable simplification of the internal bookkeeping
for deletions. Also, add flag columns to pg_am to eliminate various
hardcoded tests on AM OIDs, and remove unused pg_am columns.
Fix rtree and gist index types to not attempt to store NULLs; before this,
gist usually crashed, while rtree managed not to crash but computed wacko
bounding boxes for NULL entries (which might have had something to do with
the performance problems we've heard about occasionally).
Add AtEOXact routines to hash, rtree, and gist, all of which have static
state that needs to be reset after an error. We discovered this need long
ago for btree, but missed the other guys.
Oh, one more thing: concurrent VACUUM is now the default.
2001-07-16 00:48:19 +02:00
|
|
|
_bt_relbuf(rel, metabuf);
|
2000-07-21 08:42:39 +02:00
|
|
|
return InvalidBuffer;
|
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-07-21 08:42:39 +02:00
|
|
|
/* trade in our read lock for a write lock */
|
|
|
|
LockBuffer(metabuf, BUFFER_LOCK_UNLOCK);
|
|
|
|
LockBuffer(metabuf, BT_WRITE);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Race condition: if someone else initialized the metadata
|
|
|
|
* between the time we released the read lock and acquired the
|
2003-02-22 01:45:05 +01:00
|
|
|
* write lock, we must avoid doing it again.
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2003-02-22 01:45:05 +01:00
|
|
|
if (metad->btm_root != P_NONE)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
|
|
|
/*
|
2003-02-22 01:45:05 +01:00
|
|
|
* Metadata initialized by someone else. In order to
|
|
|
|
* guarantee no deadlocks, we have to release the metadata
|
2003-08-04 02:43:34 +02:00
|
|
|
* page and start all over again. (Is that really true? But
|
|
|
|
* it's hardly worth trying to optimize this case.)
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2003-02-22 01:45:05 +01:00
|
|
|
_bt_relbuf(rel, metabuf);
|
|
|
|
return _bt_getroot(rel, access);
|
|
|
|
}
|
2000-10-04 02:04:43 +02:00
|
|
|
|
2003-02-22 01:45:05 +01:00
|
|
|
/*
|
|
|
|
* Get, initialize, write, and leave a lock of the appropriate
|
2003-08-04 02:43:34 +02:00
|
|
|
* type on the new root page. Since this is the first page in the
|
|
|
|
* tree, it's a leaf as well as the root.
|
2003-02-22 01:45:05 +01:00
|
|
|
*/
|
|
|
|
rootbuf = _bt_getbuf(rel, P_NEW, BT_WRITE);
|
|
|
|
rootblkno = BufferGetBlockNumber(rootbuf);
|
|
|
|
rootpage = BufferGetPage(rootbuf);
|
|
|
|
|
|
|
|
_bt_pageinit(rootpage, BufferGetPageSize(rootbuf));
|
|
|
|
rootopaque = (BTPageOpaque) PageGetSpecialPointer(rootpage);
|
|
|
|
rootopaque->btpo_prev = rootopaque->btpo_next = P_NONE;
|
|
|
|
rootopaque->btpo_flags = (BTP_LEAF | BTP_ROOT);
|
|
|
|
rootopaque->btpo.level = 0;
|
|
|
|
|
|
|
|
/* NO ELOG(ERROR) till meta is updated */
|
|
|
|
START_CRIT_SECTION();
|
|
|
|
|
|
|
|
metad->btm_root = rootblkno;
|
|
|
|
metad->btm_level = 0;
|
|
|
|
metad->btm_fastroot = rootblkno;
|
|
|
|
metad->btm_fastlevel = 0;
|
|
|
|
|
|
|
|
/* XLOG stuff */
|
|
|
|
if (!rel->rd_istemp)
|
|
|
|
{
|
|
|
|
xl_btree_newroot xlrec;
|
|
|
|
XLogRecPtr recptr;
|
|
|
|
XLogRecData rdata;
|
2000-10-13 04:03:02 +02:00
|
|
|
|
2003-02-22 01:45:05 +01:00
|
|
|
xlrec.node = rel->rd_node;
|
|
|
|
xlrec.rootblk = rootblkno;
|
|
|
|
xlrec.level = 0;
|
2003-02-21 01:06:22 +01:00
|
|
|
|
2003-02-22 01:45:05 +01:00
|
|
|
rdata.buffer = InvalidBuffer;
|
|
|
|
rdata.data = (char *) &xlrec;
|
|
|
|
rdata.len = SizeOfBtreeNewroot;
|
|
|
|
rdata.next = NULL;
|
2000-10-04 02:04:43 +02:00
|
|
|
|
2003-02-22 01:45:05 +01:00
|
|
|
recptr = XLogInsert(RM_BTREE_ID, XLOG_BTREE_NEWROOT, &rdata);
|
2000-10-04 02:04:43 +02:00
|
|
|
|
2003-02-22 01:45:05 +01:00
|
|
|
PageSetLSN(rootpage, recptr);
|
|
|
|
PageSetSUI(rootpage, ThisStartUpID);
|
|
|
|
PageSetLSN(metapg, recptr);
|
|
|
|
PageSetSUI(metapg, ThisStartUpID);
|
|
|
|
}
|
2000-07-21 08:42:39 +02:00
|
|
|
|
2003-02-22 01:45:05 +01:00
|
|
|
END_CRIT_SECTION();
|
2000-07-21 08:42:39 +02:00
|
|
|
|
2003-02-22 01:45:05 +01:00
|
|
|
_bt_wrtnorelbuf(rel, rootbuf);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2003-02-22 01:45:05 +01:00
|
|
|
/*
|
2003-08-04 02:43:34 +02:00
|
|
|
* swap root write lock for read lock. There is no danger of
|
2003-02-22 01:45:05 +01:00
|
|
|
* anyone else accessing the new root page while it's unlocked,
|
|
|
|
* since no one else knows where it is yet.
|
|
|
|
*/
|
|
|
|
LockBuffer(rootbuf, BUFFER_LOCK_UNLOCK);
|
|
|
|
LockBuffer(rootbuf, BT_READ);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2003-02-22 01:45:05 +01:00
|
|
|
/* okay, metadata is correct, write and release it */
|
|
|
|
_bt_wrtbuf(rel, metabuf);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
else
|
|
|
|
{
|
2003-02-21 01:06:22 +01:00
|
|
|
rootblkno = metad->btm_fastroot;
|
2003-02-22 01:45:05 +01:00
|
|
|
Assert(rootblkno != P_NONE);
|
|
|
|
rootlevel = metad->btm_fastlevel;
|
2003-02-21 01:06:22 +01:00
|
|
|
|
Restructure index AM interface for index building and index tuple deletion,
per previous discussion on pghackers. Most of the duplicate code in
different AMs' ambuild routines has been moved out to a common routine
in index.c; this means that all index types now do the right things about
inserting recently-dead tuples, etc. (I also removed support for EXTEND
INDEX in the ambuild routines, since that's about to go away anyway, and
it cluttered the code a lot.) The retail indextuple deletion routines have
been replaced by a "bulk delete" routine in which the indexscan is inside
the access method. I haven't pushed this change as far as it should go yet,
but it should allow considerable simplification of the internal bookkeeping
for deletions. Also, add flag columns to pg_am to eliminate various
hardcoded tests on AM OIDs, and remove unused pg_am columns.
Fix rtree and gist index types to not attempt to store NULLs; before this,
gist usually crashed, while rtree managed not to crash but computed wacko
bounding boxes for NULL entries (which might have had something to do with
the performance problems we've heard about occasionally).
Add AtEOXact routines to hash, rtree, and gist, all of which have static
state that needs to be reset after an error. We discovered this need long
ago for btree, but missed the other guys.
Oh, one more thing: concurrent VACUUM is now the default.
2001-07-16 00:48:19 +02:00
|
|
|
_bt_relbuf(rel, metabuf); /* done with the meta page */
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2003-02-22 01:45:05 +01:00
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
rootbuf = _bt_getbuf(rel, rootblkno, BT_READ);
|
|
|
|
rootpage = BufferGetPage(rootbuf);
|
|
|
|
rootopaque = (BTPageOpaque) PageGetSpecialPointer(rootpage);
|
|
|
|
|
|
|
|
if (!P_IGNORE(rootopaque))
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* it's dead, Jim. step right one page */
|
|
|
|
if (P_RIGHTMOST(rootopaque))
|
2003-07-21 22:29:40 +02:00
|
|
|
elog(ERROR, "no live root page found in \"%s\"",
|
2003-02-22 01:45:05 +01:00
|
|
|
RelationGetRelationName(rel));
|
|
|
|
rootblkno = rootopaque->btpo_next;
|
|
|
|
|
|
|
|
_bt_relbuf(rel, rootbuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Note: can't check btpo.level on deleted pages */
|
|
|
|
if (rootopaque->btpo.level != rootlevel)
|
2003-07-21 22:29:40 +02:00
|
|
|
elog(ERROR, "root page %u of \"%s\" has level %u, expected %u",
|
2003-02-22 01:45:05 +01:00
|
|
|
rootblkno, RelationGetRelationName(rel),
|
|
|
|
rootopaque->btpo.level, rootlevel);
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2003-08-04 02:43:34 +02:00
|
|
|
* By here, we have a pin and read lock on the root page, and no lock
|
|
|
|
* set on the metadata page. Return the root page's buffer.
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2003-02-21 01:06:22 +01:00
|
|
|
return rootbuf;
|
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2003-02-21 01:06:22 +01:00
|
|
|
/*
|
|
|
|
* _bt_gettrueroot() -- Get the true root page of the btree.
|
|
|
|
*
|
|
|
|
* This is the same as the BT_READ case of _bt_getroot(), except
|
|
|
|
* we follow the true-root link not the fast-root link.
|
|
|
|
*
|
|
|
|
* By the time we acquire lock on the root page, it might have been split and
|
|
|
|
* not be the true root anymore. This is okay for the present uses of this
|
|
|
|
* routine; we only really need to be able to move up at least one tree level
|
2003-08-04 02:43:34 +02:00
|
|
|
* from whatever non-root page we were at. If we ever do need to lock the
|
2003-02-21 01:06:22 +01:00
|
|
|
* one true root page, we could loop here, re-reading the metapage on each
|
|
|
|
* failure. (Note that it wouldn't do to hold the lock on the metapage while
|
|
|
|
* moving to the root --- that'd deadlock against any concurrent root split.)
|
|
|
|
*/
|
|
|
|
Buffer
|
|
|
|
_bt_gettrueroot(Relation rel)
|
|
|
|
{
|
|
|
|
Buffer metabuf;
|
|
|
|
Page metapg;
|
|
|
|
BTPageOpaque metaopaque;
|
|
|
|
Buffer rootbuf;
|
2003-02-22 01:45:05 +01:00
|
|
|
Page rootpage;
|
|
|
|
BTPageOpaque rootopaque;
|
2003-02-21 01:06:22 +01:00
|
|
|
BlockNumber rootblkno;
|
2003-02-22 01:45:05 +01:00
|
|
|
uint32 rootlevel;
|
2003-02-21 01:06:22 +01:00
|
|
|
BTMetaPageData *metad;
|
2001-01-26 02:24:31 +01:00
|
|
|
|
2003-02-21 01:06:22 +01:00
|
|
|
metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
|
|
|
|
metapg = BufferGetPage(metabuf);
|
|
|
|
metaopaque = (BTPageOpaque) PageGetSpecialPointer(metapg);
|
|
|
|
metad = BTPageGetMeta(metapg);
|
2001-03-22 05:01:46 +01:00
|
|
|
|
2003-02-21 01:06:22 +01:00
|
|
|
if (!(metaopaque->btpo_flags & BTP_META) ||
|
|
|
|
metad->btm_magic != BTREE_MAGIC)
|
2003-07-21 22:29:40 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
|
|
|
errmsg("index \"%s\" is not a btree",
|
|
|
|
RelationGetRelationName(rel))));
|
2003-02-21 01:06:22 +01:00
|
|
|
|
|
|
|
if (metad->btm_version != BTREE_VERSION)
|
2003-07-21 22:29:40 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
|
|
|
errmsg("version mismatch in \"%s\": file version %d, code version %d",
|
|
|
|
RelationGetRelationName(rel),
|
|
|
|
metad->btm_version, BTREE_VERSION)));
|
2001-01-26 02:24:31 +01:00
|
|
|
|
2003-02-21 01:06:22 +01:00
|
|
|
/* if no root page initialized yet, fail */
|
|
|
|
if (metad->btm_root == P_NONE)
|
|
|
|
{
|
|
|
|
_bt_relbuf(rel, metabuf);
|
|
|
|
return InvalidBuffer;
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
|
|
|
|
2003-02-21 01:06:22 +01:00
|
|
|
rootblkno = metad->btm_root;
|
2003-02-22 01:45:05 +01:00
|
|
|
rootlevel = metad->btm_level;
|
2003-02-21 01:06:22 +01:00
|
|
|
|
|
|
|
_bt_relbuf(rel, metabuf); /* done with the meta page */
|
|
|
|
|
2003-02-22 01:45:05 +01:00
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
rootbuf = _bt_getbuf(rel, rootblkno, BT_READ);
|
|
|
|
rootpage = BufferGetPage(rootbuf);
|
|
|
|
rootopaque = (BTPageOpaque) PageGetSpecialPointer(rootpage);
|
|
|
|
|
|
|
|
if (!P_IGNORE(rootopaque))
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* it's dead, Jim. step right one page */
|
|
|
|
if (P_RIGHTMOST(rootopaque))
|
2003-07-21 22:29:40 +02:00
|
|
|
elog(ERROR, "no live root page found in \"%s\"",
|
2003-02-22 01:45:05 +01:00
|
|
|
RelationGetRelationName(rel));
|
|
|
|
rootblkno = rootopaque->btpo_next;
|
|
|
|
|
|
|
|
_bt_relbuf(rel, rootbuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Note: can't check btpo.level on deleted pages */
|
|
|
|
if (rootopaque->btpo.level != rootlevel)
|
2003-07-21 22:29:40 +02:00
|
|
|
elog(ERROR, "root page %u of \"%s\" has level %u, expected %u",
|
2003-02-22 01:45:05 +01:00
|
|
|
rootblkno, RelationGetRelationName(rel),
|
|
|
|
rootopaque->btpo.level, rootlevel);
|
2003-02-21 01:06:22 +01:00
|
|
|
|
1998-09-01 05:29:17 +02:00
|
|
|
return rootbuf;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
1997-09-07 07:04:48 +02:00
|
|
|
* _bt_getbuf() -- Get a buffer by block number for read or write.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2003-02-22 01:45:05 +01:00
|
|
|
* blkno == P_NEW means to get an unallocated index page.
|
|
|
|
*
|
1997-09-07 07:04:48 +02:00
|
|
|
* When this routine returns, the appropriate lock is set on the
|
2000-07-21 08:42:39 +02:00
|
|
|
* requested buffer and its reference count has been incremented
|
|
|
|
* (ie, the buffer is "locked and pinned").
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
Buffer
|
|
|
|
_bt_getbuf(Relation rel, BlockNumber blkno, int access)
|
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
Buffer buf;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
if (blkno != P_NEW)
|
|
|
|
{
|
2000-07-21 08:42:39 +02:00
|
|
|
/* Read an existing block of the relation */
|
1997-09-07 07:04:48 +02:00
|
|
|
buf = ReadBuffer(rel, blkno);
|
1999-05-25 20:20:31 +02:00
|
|
|
LockBuffer(buf, access);
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
1996-07-09 08:22:35 +02:00
|
|
|
else
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2003-02-22 01:45:05 +01:00
|
|
|
bool needLock;
|
2000-07-21 08:42:39 +02:00
|
|
|
Page page;
|
1999-05-26 00:04:56 +02:00
|
|
|
|
2003-02-23 07:17:13 +01:00
|
|
|
Assert(access == BT_WRITE);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* First see if the FSM knows of any free pages.
|
|
|
|
*
|
|
|
|
* We can't trust the FSM's report unreservedly; we have to check
|
2003-08-04 02:43:34 +02:00
|
|
|
* that the page is still free. (For example, an already-free
|
|
|
|
* page could have been re-used between the time the last VACUUM
|
|
|
|
* scanned it and the time the VACUUM made its FSM updates.)
|
2003-02-23 07:17:13 +01:00
|
|
|
*/
|
|
|
|
for (;;)
|
|
|
|
{
|
2003-03-04 22:51:22 +01:00
|
|
|
blkno = GetFreeIndexPage(&rel->rd_node);
|
2003-02-23 07:17:13 +01:00
|
|
|
if (blkno == InvalidBlockNumber)
|
|
|
|
break;
|
|
|
|
buf = ReadBuffer(rel, blkno);
|
|
|
|
LockBuffer(buf, access);
|
|
|
|
page = BufferGetPage(buf);
|
|
|
|
if (_bt_page_recyclable(page))
|
|
|
|
{
|
|
|
|
/* Okay to use page. Re-initialize and return it */
|
|
|
|
_bt_pageinit(page, BufferGetPageSize(buf));
|
|
|
|
return buf;
|
|
|
|
}
|
2003-07-21 22:29:40 +02:00
|
|
|
elog(DEBUG2, "FSM returned nonrecyclable page");
|
2003-02-23 07:17:13 +01:00
|
|
|
_bt_relbuf(rel, buf);
|
|
|
|
}
|
2003-02-22 01:45:05 +01:00
|
|
|
|
1999-05-25 20:20:31 +02:00
|
|
|
/*
|
2000-07-21 08:42:39 +02:00
|
|
|
* Extend the relation by one page.
|
|
|
|
*
|
2003-08-04 02:43:34 +02:00
|
|
|
* We have to use a lock to ensure no one else is extending the rel
|
|
|
|
* at the same time, else we will both try to initialize the same
|
|
|
|
* new page. We can skip locking for new or temp relations,
|
|
|
|
* however, since no one else could be accessing them.
|
1999-05-25 20:20:31 +02:00
|
|
|
*/
|
2003-02-22 01:45:05 +01:00
|
|
|
needLock = !(rel->rd_isnew || rel->rd_istemp);
|
|
|
|
|
|
|
|
if (needLock)
|
|
|
|
LockPage(rel, 0, ExclusiveLock);
|
|
|
|
|
|
|
|
buf = ReadBuffer(rel, P_NEW);
|
|
|
|
|
|
|
|
/*
|
2003-08-04 02:43:34 +02:00
|
|
|
* Release the file-extension lock; it's now OK for someone else
|
|
|
|
* to extend the relation some more.
|
2003-02-22 01:45:05 +01:00
|
|
|
*/
|
|
|
|
if (needLock)
|
|
|
|
UnlockPage(rel, 0, ExclusiveLock);
|
|
|
|
|
|
|
|
/* Acquire appropriate buffer lock on new page */
|
2000-07-21 08:42:39 +02:00
|
|
|
LockBuffer(buf, access);
|
|
|
|
|
|
|
|
/* Initialize the new page before returning it */
|
1997-09-07 07:04:48 +02:00
|
|
|
page = BufferGetPage(buf);
|
|
|
|
_bt_pageinit(page, BufferGetPageSize(buf));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ref count and lock type are correct */
|
1998-09-01 05:29:17 +02:00
|
|
|
return buf;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
1997-09-07 07:04:48 +02:00
|
|
|
* _bt_relbuf() -- release a locked buffer.
|
2000-07-21 08:42:39 +02:00
|
|
|
*
|
Restructure index AM interface for index building and index tuple deletion,
per previous discussion on pghackers. Most of the duplicate code in
different AMs' ambuild routines has been moved out to a common routine
in index.c; this means that all index types now do the right things about
inserting recently-dead tuples, etc. (I also removed support for EXTEND
INDEX in the ambuild routines, since that's about to go away anyway, and
it cluttered the code a lot.) The retail indextuple deletion routines have
been replaced by a "bulk delete" routine in which the indexscan is inside
the access method. I haven't pushed this change as far as it should go yet,
but it should allow considerable simplification of the internal bookkeeping
for deletions. Also, add flag columns to pg_am to eliminate various
hardcoded tests on AM OIDs, and remove unused pg_am columns.
Fix rtree and gist index types to not attempt to store NULLs; before this,
gist usually crashed, while rtree managed not to crash but computed wacko
bounding boxes for NULL entries (which might have had something to do with
the performance problems we've heard about occasionally).
Add AtEOXact routines to hash, rtree, and gist, all of which have static
state that needs to be reset after an error. We discovered this need long
ago for btree, but missed the other guys.
Oh, one more thing: concurrent VACUUM is now the default.
2001-07-16 00:48:19 +02:00
|
|
|
* Lock and pin (refcount) are both dropped. Note that either read or
|
|
|
|
* write lock can be dropped this way, but if we modified the buffer,
|
|
|
|
* this is NOT the right way to release a write lock.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
void
|
Restructure index AM interface for index building and index tuple deletion,
per previous discussion on pghackers. Most of the duplicate code in
different AMs' ambuild routines has been moved out to a common routine
in index.c; this means that all index types now do the right things about
inserting recently-dead tuples, etc. (I also removed support for EXTEND
INDEX in the ambuild routines, since that's about to go away anyway, and
it cluttered the code a lot.) The retail indextuple deletion routines have
been replaced by a "bulk delete" routine in which the indexscan is inside
the access method. I haven't pushed this change as far as it should go yet,
but it should allow considerable simplification of the internal bookkeeping
for deletions. Also, add flag columns to pg_am to eliminate various
hardcoded tests on AM OIDs, and remove unused pg_am columns.
Fix rtree and gist index types to not attempt to store NULLs; before this,
gist usually crashed, while rtree managed not to crash but computed wacko
bounding boxes for NULL entries (which might have had something to do with
the performance problems we've heard about occasionally).
Add AtEOXact routines to hash, rtree, and gist, all of which have static
state that needs to be reset after an error. We discovered this need long
ago for btree, but missed the other guys.
Oh, one more thing: concurrent VACUUM is now the default.
2001-07-16 00:48:19 +02:00
|
|
|
_bt_relbuf(Relation rel, Buffer buf)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1999-05-25 20:20:31 +02:00
|
|
|
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
|
1997-09-07 07:04:48 +02:00
|
|
|
ReleaseBuffer(buf);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
1997-09-07 07:04:48 +02:00
|
|
|
* _bt_wrtbuf() -- write a btree page to disk.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2000-07-21 08:42:39 +02:00
|
|
|
* This routine releases the lock held on the buffer and our refcount
|
|
|
|
* for it. It is an error to call _bt_wrtbuf() without a write lock
|
|
|
|
* and a pin on the buffer.
|
|
|
|
*
|
|
|
|
* NOTE: actually, the buffer manager just marks the shared buffer page
|
2003-08-04 02:43:34 +02:00
|
|
|
* dirty here; the real I/O happens later. This is okay since we are not
|
2003-02-22 01:45:05 +01:00
|
|
|
* relying on write ordering anyway. The WAL mechanism is responsible for
|
|
|
|
* guaranteeing correctness after a crash.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
_bt_wrtbuf(Relation rel, Buffer buf)
|
|
|
|
{
|
1999-05-25 20:20:31 +02:00
|
|
|
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
|
1997-09-07 07:04:48 +02:00
|
|
|
WriteBuffer(buf);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
1997-09-07 07:04:48 +02:00
|
|
|
* _bt_wrtnorelbuf() -- write a btree page to disk, but do not release
|
|
|
|
* our reference or lock.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
1997-09-07 07:04:48 +02:00
|
|
|
* It is an error to call _bt_wrtnorelbuf() without a write lock
|
2000-07-21 08:42:39 +02:00
|
|
|
* and a pin on the buffer.
|
|
|
|
*
|
|
|
|
* See above NOTE.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
_bt_wrtnorelbuf(Relation rel, Buffer buf)
|
|
|
|
{
|
1997-09-07 07:04:48 +02:00
|
|
|
WriteNoReleaseBuffer(buf);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
1997-09-07 07:04:48 +02:00
|
|
|
* _bt_pageinit() -- Initialize a new page.
|
2003-02-21 01:06:22 +01:00
|
|
|
*
|
|
|
|
* On return, the page header is initialized; data space is empty;
|
|
|
|
* special space is zeroed out.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
_bt_pageinit(Page page, Size size)
|
|
|
|
{
|
1997-09-07 07:04:48 +02:00
|
|
|
PageInit(page, size, sizeof(BTPageOpaqueData));
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
2003-02-23 07:17:13 +01:00
|
|
|
/*
|
|
|
|
* _bt_page_recyclable() -- Is an existing page recyclable?
|
|
|
|
*
|
|
|
|
* This exists to make sure _bt_getbuf and btvacuumcleanup have the same
|
|
|
|
* policy about whether a page is safe to re-use.
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
_bt_page_recyclable(Page page)
|
|
|
|
{
|
|
|
|
BTPageOpaque opaque;
|
|
|
|
|
|
|
|
/*
|
2003-08-04 02:43:34 +02:00
|
|
|
* It's possible to find an all-zeroes page in an index --- for
|
|
|
|
* example, a backend might successfully extend the relation one page
|
|
|
|
* and then crash before it is able to make a WAL entry for adding the
|
|
|
|
* page. If we find a zeroed page then reclaim it.
|
2003-02-23 07:17:13 +01:00
|
|
|
*/
|
|
|
|
if (PageIsNew(page))
|
|
|
|
return true;
|
2003-08-04 02:43:34 +02:00
|
|
|
|
2003-02-23 07:17:13 +01:00
|
|
|
/*
|
|
|
|
* Otherwise, recycle if deleted and too old to have any processes
|
|
|
|
* interested in it.
|
|
|
|
*/
|
|
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
|
|
if (P_ISDELETED(opaque) &&
|
2003-02-24 00:20:52 +01:00
|
|
|
TransactionIdPrecedesOrEquals(opaque->btpo.xact, RecentXmin))
|
2003-02-23 07:17:13 +01:00
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/*
|
1997-09-07 07:04:48 +02:00
|
|
|
* _bt_metaproot() -- Change the root page of the btree.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
1997-09-07 07:04:48 +02:00
|
|
|
* Lehman and Yao require that the root page move around in order to
|
|
|
|
* guarantee deadlock-free short-term, fine-granularity locking. When
|
|
|
|
* we split the root page, we record the new parent in the metadata page
|
|
|
|
* for the relation. This routine does the work.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2000-07-21 08:42:39 +02:00
|
|
|
* No direct preconditions, but if you don't have the write lock on
|
1997-09-07 07:04:48 +02:00
|
|
|
* at least the old root page when you call this, you're making a big
|
|
|
|
* mistake. On exit, metapage data is correct and we no longer have
|
2000-07-21 08:42:39 +02:00
|
|
|
* a pin or lock on the metapage.
|
2003-02-21 01:06:22 +01:00
|
|
|
*
|
2003-08-04 02:43:34 +02:00
|
|
|
* Actually this is not used for splitting on-the-fly anymore. It's only used
|
2003-02-22 01:45:05 +01:00
|
|
|
* in nbtsort.c at the completion of btree building, where we know we have
|
|
|
|
* sole access to the index anyway.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
void
|
2003-02-21 01:06:22 +01:00
|
|
|
_bt_metaproot(Relation rel, BlockNumber rootbknum, uint32 level)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
Buffer metabuf;
|
|
|
|
Page metap;
|
|
|
|
BTPageOpaque metaopaque;
|
1997-09-07 07:04:48 +02:00
|
|
|
BTMetaPageData *metad;
|
|
|
|
|
|
|
|
metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE);
|
|
|
|
metap = BufferGetPage(metabuf);
|
|
|
|
metaopaque = (BTPageOpaque) PageGetSpecialPointer(metap);
|
|
|
|
Assert(metaopaque->btpo_flags & BTP_META);
|
2003-02-21 01:06:22 +01:00
|
|
|
|
|
|
|
/* NO ELOG(ERROR) from here till newmeta op is logged */
|
|
|
|
START_CRIT_SECTION();
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
metad = BTPageGetMeta(metap);
|
|
|
|
metad->btm_root = rootbknum;
|
2003-02-21 01:06:22 +01:00
|
|
|
metad->btm_level = level;
|
|
|
|
metad->btm_fastroot = rootbknum;
|
|
|
|
metad->btm_fastlevel = level;
|
|
|
|
|
|
|
|
/* XLOG stuff */
|
|
|
|
if (!rel->rd_istemp)
|
|
|
|
{
|
|
|
|
xl_btree_newmeta xlrec;
|
|
|
|
XLogRecPtr recptr;
|
|
|
|
XLogRecData rdata[1];
|
|
|
|
|
|
|
|
xlrec.node = rel->rd_node;
|
|
|
|
xlrec.meta.root = metad->btm_root;
|
|
|
|
xlrec.meta.level = metad->btm_level;
|
|
|
|
xlrec.meta.fastroot = metad->btm_fastroot;
|
|
|
|
xlrec.meta.fastlevel = metad->btm_fastlevel;
|
|
|
|
|
|
|
|
rdata[0].buffer = InvalidBuffer;
|
|
|
|
rdata[0].data = (char *) &xlrec;
|
|
|
|
rdata[0].len = SizeOfBtreeNewmeta;
|
|
|
|
rdata[0].next = NULL;
|
|
|
|
|
|
|
|
recptr = XLogInsert(RM_BTREE_ID, XLOG_BTREE_NEWMETA, rdata);
|
|
|
|
|
|
|
|
PageSetLSN(metap, recptr);
|
|
|
|
PageSetSUI(metap, ThisStartUpID);
|
|
|
|
}
|
|
|
|
|
|
|
|
END_CRIT_SECTION();
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
_bt_wrtbuf(rel, metabuf);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2003-02-23 23:43:09 +01:00
|
|
|
* Delete item(s) from a btree page.
|
Restructure index AM interface for index building and index tuple deletion,
per previous discussion on pghackers. Most of the duplicate code in
different AMs' ambuild routines has been moved out to a common routine
in index.c; this means that all index types now do the right things about
inserting recently-dead tuples, etc. (I also removed support for EXTEND
INDEX in the ambuild routines, since that's about to go away anyway, and
it cluttered the code a lot.) The retail indextuple deletion routines have
been replaced by a "bulk delete" routine in which the indexscan is inside
the access method. I haven't pushed this change as far as it should go yet,
but it should allow considerable simplification of the internal bookkeeping
for deletions. Also, add flag columns to pg_am to eliminate various
hardcoded tests on AM OIDs, and remove unused pg_am columns.
Fix rtree and gist index types to not attempt to store NULLs; before this,
gist usually crashed, while rtree managed not to crash but computed wacko
bounding boxes for NULL entries (which might have had something to do with
the performance problems we've heard about occasionally).
Add AtEOXact routines to hash, rtree, and gist, all of which have static
state that needs to be reset after an error. We discovered this need long
ago for btree, but missed the other guys.
Oh, one more thing: concurrent VACUUM is now the default.
2001-07-16 00:48:19 +02:00
|
|
|
*
|
2003-08-04 02:43:34 +02:00
|
|
|
* This must only be used for deleting leaf items. Deleting an item on a
|
2003-02-22 01:45:05 +01:00
|
|
|
* non-leaf page has to be done as part of an atomic action that includes
|
|
|
|
* deleting the page it points to.
|
|
|
|
*
|
Restructure index AM interface for index building and index tuple deletion,
per previous discussion on pghackers. Most of the duplicate code in
different AMs' ambuild routines has been moved out to a common routine
in index.c; this means that all index types now do the right things about
inserting recently-dead tuples, etc. (I also removed support for EXTEND
INDEX in the ambuild routines, since that's about to go away anyway, and
it cluttered the code a lot.) The retail indextuple deletion routines have
been replaced by a "bulk delete" routine in which the indexscan is inside
the access method. I haven't pushed this change as far as it should go yet,
but it should allow considerable simplification of the internal bookkeeping
for deletions. Also, add flag columns to pg_am to eliminate various
hardcoded tests on AM OIDs, and remove unused pg_am columns.
Fix rtree and gist index types to not attempt to store NULLs; before this,
gist usually crashed, while rtree managed not to crash but computed wacko
bounding boxes for NULL entries (which might have had something to do with
the performance problems we've heard about occasionally).
Add AtEOXact routines to hash, rtree, and gist, all of which have static
state that needs to be reset after an error. We discovered this need long
ago for btree, but missed the other guys.
Oh, one more thing: concurrent VACUUM is now the default.
2001-07-16 00:48:19 +02:00
|
|
|
* This routine assumes that the caller has pinned and locked the buffer,
|
2003-02-23 23:43:09 +01:00
|
|
|
* and will write the buffer afterwards. Also, the given itemnos *must*
|
|
|
|
* appear in increasing order in the array.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
void
|
2003-02-23 23:43:09 +01:00
|
|
|
_bt_delitems(Relation rel, Buffer buf,
|
|
|
|
OffsetNumber *itemnos, int nitems)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
Restructure index AM interface for index building and index tuple deletion,
per previous discussion on pghackers. Most of the duplicate code in
different AMs' ambuild routines has been moved out to a common routine
in index.c; this means that all index types now do the right things about
inserting recently-dead tuples, etc. (I also removed support for EXTEND
INDEX in the ambuild routines, since that's about to go away anyway, and
it cluttered the code a lot.) The retail indextuple deletion routines have
been replaced by a "bulk delete" routine in which the indexscan is inside
the access method. I haven't pushed this change as far as it should go yet,
but it should allow considerable simplification of the internal bookkeeping
for deletions. Also, add flag columns to pg_am to eliminate various
hardcoded tests on AM OIDs, and remove unused pg_am columns.
Fix rtree and gist index types to not attempt to store NULLs; before this,
gist usually crashed, while rtree managed not to crash but computed wacko
bounding boxes for NULL entries (which might have had something to do with
the performance problems we've heard about occasionally).
Add AtEOXact routines to hash, rtree, and gist, all of which have static
state that needs to be reset after an error. We discovered this need long
ago for btree, but missed the other guys.
Oh, one more thing: concurrent VACUUM is now the default.
2001-07-16 00:48:19 +02:00
|
|
|
Page page = BufferGetPage(buf);
|
2003-02-23 23:43:09 +01:00
|
|
|
int i;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2003-07-21 22:29:40 +02:00
|
|
|
/* No ereport(ERROR) until changes are logged */
|
2001-01-12 22:54:01 +01:00
|
|
|
START_CRIT_SECTION();
|
Restructure index AM interface for index building and index tuple deletion,
per previous discussion on pghackers. Most of the duplicate code in
different AMs' ambuild routines has been moved out to a common routine
in index.c; this means that all index types now do the right things about
inserting recently-dead tuples, etc. (I also removed support for EXTEND
INDEX in the ambuild routines, since that's about to go away anyway, and
it cluttered the code a lot.) The retail indextuple deletion routines have
been replaced by a "bulk delete" routine in which the indexscan is inside
the access method. I haven't pushed this change as far as it should go yet,
but it should allow considerable simplification of the internal bookkeeping
for deletions. Also, add flag columns to pg_am to eliminate various
hardcoded tests on AM OIDs, and remove unused pg_am columns.
Fix rtree and gist index types to not attempt to store NULLs; before this,
gist usually crashed, while rtree managed not to crash but computed wacko
bounding boxes for NULL entries (which might have had something to do with
the performance problems we've heard about occasionally).
Add AtEOXact routines to hash, rtree, and gist, all of which have static
state that needs to be reset after an error. We discovered this need long
ago for btree, but missed the other guys.
Oh, one more thing: concurrent VACUUM is now the default.
2001-07-16 00:48:19 +02:00
|
|
|
|
2003-02-23 23:43:09 +01:00
|
|
|
/*
|
|
|
|
* Delete the items in reverse order so we don't have to think about
|
|
|
|
* adjusting item numbers for previous deletions.
|
|
|
|
*/
|
|
|
|
for (i = nitems - 1; i >= 0; i--)
|
|
|
|
PageIndexTupleDelete(page, itemnos[i]);
|
Restructure index AM interface for index building and index tuple deletion,
per previous discussion on pghackers. Most of the duplicate code in
different AMs' ambuild routines has been moved out to a common routine
in index.c; this means that all index types now do the right things about
inserting recently-dead tuples, etc. (I also removed support for EXTEND
INDEX in the ambuild routines, since that's about to go away anyway, and
it cluttered the code a lot.) The retail indextuple deletion routines have
been replaced by a "bulk delete" routine in which the indexscan is inside
the access method. I haven't pushed this change as far as it should go yet,
but it should allow considerable simplification of the internal bookkeeping
for deletions. Also, add flag columns to pg_am to eliminate various
hardcoded tests on AM OIDs, and remove unused pg_am columns.
Fix rtree and gist index types to not attempt to store NULLs; before this,
gist usually crashed, while rtree managed not to crash but computed wacko
bounding boxes for NULL entries (which might have had something to do with
the performance problems we've heard about occasionally).
Add AtEOXact routines to hash, rtree, and gist, all of which have static
state that needs to be reset after an error. We discovered this need long
ago for btree, but missed the other guys.
Oh, one more thing: concurrent VACUUM is now the default.
2001-07-16 00:48:19 +02:00
|
|
|
|
2000-12-29 21:47:17 +01:00
|
|
|
/* XLOG stuff */
|
2002-08-06 04:36:35 +02:00
|
|
|
if (!rel->rd_istemp)
|
2000-10-04 02:04:43 +02:00
|
|
|
{
|
2001-03-22 05:01:46 +01:00
|
|
|
xl_btree_delete xlrec;
|
|
|
|
XLogRecPtr recptr;
|
|
|
|
XLogRecData rdata[2];
|
2000-10-21 17:43:36 +02:00
|
|
|
|
2003-02-23 23:43:09 +01:00
|
|
|
xlrec.node = rel->rd_node;
|
|
|
|
xlrec.block = BufferGetBlockNumber(buf);
|
2003-02-21 01:06:22 +01:00
|
|
|
|
2000-12-28 14:00:29 +01:00
|
|
|
rdata[0].buffer = InvalidBuffer;
|
2001-03-22 05:01:46 +01:00
|
|
|
rdata[0].data = (char *) &xlrec;
|
2000-12-28 14:00:29 +01:00
|
|
|
rdata[0].len = SizeOfBtreeDelete;
|
|
|
|
rdata[0].next = &(rdata[1]);
|
|
|
|
|
2003-02-23 23:43:09 +01:00
|
|
|
/*
|
2003-08-04 02:43:34 +02:00
|
|
|
* The target-offsets array is not in the buffer, but pretend that
|
|
|
|
* it is. When XLogInsert stores the whole buffer, the offsets
|
2003-02-23 23:43:09 +01:00
|
|
|
* array need not be stored too.
|
|
|
|
*/
|
2000-12-28 14:00:29 +01:00
|
|
|
rdata[1].buffer = buf;
|
2003-02-23 23:43:09 +01:00
|
|
|
if (nitems > 0)
|
|
|
|
{
|
|
|
|
rdata[1].data = (char *) itemnos;
|
|
|
|
rdata[1].len = nitems * sizeof(OffsetNumber);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
rdata[1].data = NULL;
|
|
|
|
rdata[1].len = 0;
|
|
|
|
}
|
2000-12-28 14:00:29 +01:00
|
|
|
rdata[1].next = NULL;
|
|
|
|
|
|
|
|
recptr = XLogInsert(RM_BTREE_ID, XLOG_BTREE_DELETE, rdata);
|
2000-10-04 02:04:43 +02:00
|
|
|
|
|
|
|
PageSetLSN(page, recptr);
|
|
|
|
PageSetSUI(page, ThisStartUpID);
|
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
Restructure index AM interface for index building and index tuple deletion,
per previous discussion on pghackers. Most of the duplicate code in
different AMs' ambuild routines has been moved out to a common routine
in index.c; this means that all index types now do the right things about
inserting recently-dead tuples, etc. (I also removed support for EXTEND
INDEX in the ambuild routines, since that's about to go away anyway, and
it cluttered the code a lot.) The retail indextuple deletion routines have
been replaced by a "bulk delete" routine in which the indexscan is inside
the access method. I haven't pushed this change as far as it should go yet,
but it should allow considerable simplification of the internal bookkeeping
for deletions. Also, add flag columns to pg_am to eliminate various
hardcoded tests on AM OIDs, and remove unused pg_am columns.
Fix rtree and gist index types to not attempt to store NULLs; before this,
gist usually crashed, while rtree managed not to crash but computed wacko
bounding boxes for NULL entries (which might have had something to do with
the performance problems we've heard about occasionally).
Add AtEOXact routines to hash, rtree, and gist, all of which have static
state that needs to be reset after an error. We discovered this need long
ago for btree, but missed the other guys.
Oh, one more thing: concurrent VACUUM is now the default.
2001-07-16 00:48:19 +02:00
|
|
|
END_CRIT_SECTION();
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
2003-02-23 07:17:13 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* _bt_pagedel() -- Delete a page from the b-tree.
|
|
|
|
*
|
|
|
|
* This action unlinks the page from the b-tree structure, removing all
|
|
|
|
* pointers leading to it --- but not touching its own left and right links.
|
|
|
|
* The page cannot be physically reclaimed right away, since other processes
|
|
|
|
* may currently be trying to follow links leading to the page; they have to
|
|
|
|
* be allowed to use its right-link to recover. See nbtree/README.
|
|
|
|
*
|
2003-08-04 02:43:34 +02:00
|
|
|
* On entry, the target buffer must be pinned and read-locked. This lock and
|
2003-02-23 07:17:13 +01:00
|
|
|
* pin will be dropped before exiting.
|
|
|
|
*
|
|
|
|
* Returns the number of pages successfully deleted (zero on failure; could
|
|
|
|
* be more than one if parent blocks were deleted).
|
|
|
|
*
|
|
|
|
* NOTE: this leaks memory. Rather than trying to clean up everything
|
|
|
|
* carefully, it's better to run it in a temp context that can be reset
|
|
|
|
* frequently.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
_bt_pagedel(Relation rel, Buffer buf, bool vacuum_full)
|
|
|
|
{
|
2003-08-04 02:43:34 +02:00
|
|
|
BlockNumber target,
|
2003-02-23 07:17:13 +01:00
|
|
|
leftsib,
|
|
|
|
rightsib,
|
|
|
|
parent;
|
|
|
|
OffsetNumber poffset,
|
|
|
|
maxoff;
|
|
|
|
uint32 targetlevel,
|
|
|
|
ilevel;
|
|
|
|
ItemId itemid;
|
|
|
|
BTItem targetkey,
|
|
|
|
btitem;
|
|
|
|
ScanKey itup_scankey;
|
|
|
|
BTStack stack;
|
|
|
|
Buffer lbuf,
|
|
|
|
rbuf,
|
|
|
|
pbuf;
|
|
|
|
bool parent_half_dead;
|
|
|
|
bool parent_one_child;
|
|
|
|
bool rightsib_empty;
|
|
|
|
Buffer metabuf = InvalidBuffer;
|
|
|
|
Page metapg = NULL;
|
|
|
|
BTMetaPageData *metad = NULL;
|
|
|
|
Page page;
|
|
|
|
BTPageOpaque opaque;
|
|
|
|
|
|
|
|
/*
|
2003-08-04 02:43:34 +02:00
|
|
|
* We can never delete rightmost pages nor root pages. While at it,
|
2003-02-23 07:17:13 +01:00
|
|
|
* check that page is not already deleted and is empty.
|
|
|
|
*/
|
|
|
|
page = BufferGetPage(buf);
|
|
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
|
|
if (P_RIGHTMOST(opaque) || P_ISROOT(opaque) || P_ISDELETED(opaque) ||
|
2003-08-04 02:43:34 +02:00
|
|
|
P_FIRSTDATAKEY(opaque) <= PageGetMaxOffsetNumber(page))
|
2003-02-23 07:17:13 +01:00
|
|
|
{
|
|
|
|
_bt_relbuf(rel, buf);
|
|
|
|
return 0;
|
|
|
|
}
|
2003-08-04 02:43:34 +02:00
|
|
|
|
2003-02-23 07:17:13 +01:00
|
|
|
/*
|
|
|
|
* Save info about page, including a copy of its high key (it must
|
|
|
|
* have one, being non-rightmost).
|
|
|
|
*/
|
|
|
|
target = BufferGetBlockNumber(buf);
|
|
|
|
targetlevel = opaque->btpo.level;
|
|
|
|
leftsib = opaque->btpo_prev;
|
|
|
|
itemid = PageGetItemId(page, P_HIKEY);
|
|
|
|
targetkey = CopyBTItem((BTItem) PageGetItem(page, itemid));
|
2003-08-04 02:43:34 +02:00
|
|
|
|
2003-02-23 07:17:13 +01:00
|
|
|
/*
|
|
|
|
* We need to get an approximate pointer to the page's parent page.
|
2003-08-04 02:43:34 +02:00
|
|
|
* Use the standard search mechanism to search for the page's high
|
|
|
|
* key; this will give us a link to either the current parent or
|
|
|
|
* someplace to its left (if there are multiple equal high keys). To
|
|
|
|
* avoid deadlocks, we'd better drop the target page lock first.
|
2003-02-23 07:17:13 +01:00
|
|
|
*/
|
|
|
|
_bt_relbuf(rel, buf);
|
|
|
|
/* we need a scan key to do our search, so build one */
|
|
|
|
itup_scankey = _bt_mkscankey(rel, &(targetkey->bti_itup));
|
|
|
|
/* find the leftmost leaf page containing this key */
|
|
|
|
stack = _bt_search(rel, rel->rd_rel->relnatts, itup_scankey,
|
|
|
|
&lbuf, BT_READ);
|
|
|
|
/* don't need a pin on that either */
|
|
|
|
_bt_relbuf(rel, lbuf);
|
2003-08-04 02:43:34 +02:00
|
|
|
|
2003-02-23 07:17:13 +01:00
|
|
|
/*
|
|
|
|
* If we are trying to delete an interior page, _bt_search did more
|
2003-08-04 02:43:34 +02:00
|
|
|
* than we needed. Locate the stack item pointing to our parent
|
|
|
|
* level.
|
2003-02-23 07:17:13 +01:00
|
|
|
*/
|
|
|
|
ilevel = 0;
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
if (stack == NULL)
|
2003-07-21 22:29:40 +02:00
|
|
|
elog(ERROR, "not enough stack items");
|
2003-02-23 07:17:13 +01:00
|
|
|
if (ilevel == targetlevel)
|
|
|
|
break;
|
|
|
|
stack = stack->bts_parent;
|
|
|
|
ilevel++;
|
|
|
|
}
|
2003-08-04 02:43:34 +02:00
|
|
|
|
2003-02-23 07:17:13 +01:00
|
|
|
/*
|
|
|
|
* We have to lock the pages we need to modify in the standard order:
|
2003-08-04 02:43:34 +02:00
|
|
|
* moving right, then up. Else we will deadlock against other
|
|
|
|
* writers.
|
|
|
|
*
|
2003-02-23 07:17:13 +01:00
|
|
|
* So, we need to find and write-lock the current left sibling of the
|
|
|
|
* target page. The sibling that was current a moment ago could have
|
|
|
|
* split, so we may have to move right. This search could fail if
|
|
|
|
* either the sibling or the target page was deleted by someone else
|
|
|
|
* meanwhile; if so, give up. (Right now, that should never happen,
|
|
|
|
* since page deletion is only done in VACUUM and there shouldn't be
|
|
|
|
* multiple VACUUMs concurrently on the same table.)
|
|
|
|
*/
|
|
|
|
if (leftsib != P_NONE)
|
|
|
|
{
|
|
|
|
lbuf = _bt_getbuf(rel, leftsib, BT_WRITE);
|
|
|
|
page = BufferGetPage(lbuf);
|
|
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
|
|
while (P_ISDELETED(opaque) || opaque->btpo_next != target)
|
|
|
|
{
|
|
|
|
/* step right one page */
|
|
|
|
leftsib = opaque->btpo_next;
|
|
|
|
_bt_relbuf(rel, lbuf);
|
|
|
|
if (leftsib == P_NONE)
|
|
|
|
{
|
2003-07-21 22:29:40 +02:00
|
|
|
elog(LOG, "no left sibling (concurrent deletion?)");
|
2003-02-23 07:17:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
lbuf = _bt_getbuf(rel, leftsib, BT_WRITE);
|
|
|
|
page = BufferGetPage(lbuf);
|
|
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
lbuf = InvalidBuffer;
|
2003-08-04 02:43:34 +02:00
|
|
|
|
2003-02-23 07:17:13 +01:00
|
|
|
/*
|
2003-08-04 02:43:34 +02:00
|
|
|
* Next write-lock the target page itself. It should be okay to take
|
|
|
|
* just a write lock not a superexclusive lock, since no scans would
|
|
|
|
* stop on an empty page.
|
2003-02-23 07:17:13 +01:00
|
|
|
*/
|
|
|
|
buf = _bt_getbuf(rel, target, BT_WRITE);
|
|
|
|
page = BufferGetPage(buf);
|
|
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
2003-08-04 02:43:34 +02:00
|
|
|
|
2003-02-23 07:17:13 +01:00
|
|
|
/*
|
2003-08-04 02:43:34 +02:00
|
|
|
* Check page is still empty etc, else abandon deletion. The empty
|
|
|
|
* check is necessary since someone else might have inserted into it
|
|
|
|
* while we didn't have it locked; the others are just for paranoia's
|
|
|
|
* sake.
|
2003-02-23 07:17:13 +01:00
|
|
|
*/
|
|
|
|
if (P_RIGHTMOST(opaque) || P_ISROOT(opaque) || P_ISDELETED(opaque) ||
|
2003-08-04 02:43:34 +02:00
|
|
|
P_FIRSTDATAKEY(opaque) <= PageGetMaxOffsetNumber(page))
|
2003-02-23 07:17:13 +01:00
|
|
|
{
|
|
|
|
_bt_relbuf(rel, buf);
|
|
|
|
if (BufferIsValid(lbuf))
|
|
|
|
_bt_relbuf(rel, lbuf);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (opaque->btpo_prev != leftsib)
|
2003-07-21 22:29:40 +02:00
|
|
|
elog(ERROR, "left link changed unexpectedly");
|
2003-08-04 02:43:34 +02:00
|
|
|
|
2003-02-23 07:17:13 +01:00
|
|
|
/*
|
|
|
|
* And next write-lock the (current) right sibling.
|
|
|
|
*/
|
|
|
|
rightsib = opaque->btpo_next;
|
|
|
|
rbuf = _bt_getbuf(rel, rightsib, BT_WRITE);
|
2003-08-04 02:43:34 +02:00
|
|
|
|
2003-02-23 07:17:13 +01:00
|
|
|
/*
|
|
|
|
* Next find and write-lock the current parent of the target page.
|
2003-08-04 02:43:34 +02:00
|
|
|
* This is essentially the same as the corresponding step of
|
|
|
|
* splitting.
|
2003-02-23 07:17:13 +01:00
|
|
|
*/
|
|
|
|
ItemPointerSet(&(stack->bts_btitem.bti_itup.t_tid),
|
|
|
|
target, P_HIKEY);
|
|
|
|
pbuf = _bt_getstackbuf(rel, stack, BT_WRITE);
|
|
|
|
if (pbuf == InvalidBuffer)
|
2003-07-21 22:29:40 +02:00
|
|
|
elog(ERROR, "failed to re-find parent key in \"%s\"",
|
|
|
|
RelationGetRelationName(rel));
|
2003-02-23 07:17:13 +01:00
|
|
|
parent = stack->bts_blkno;
|
|
|
|
poffset = stack->bts_offset;
|
2003-08-04 02:43:34 +02:00
|
|
|
|
2003-02-23 07:17:13 +01:00
|
|
|
/*
|
|
|
|
* If the target is the rightmost child of its parent, then we can't
|
2003-08-04 02:43:34 +02:00
|
|
|
* delete, unless it's also the only child --- in which case the
|
|
|
|
* parent changes to half-dead status.
|
2003-02-23 07:17:13 +01:00
|
|
|
*/
|
|
|
|
page = BufferGetPage(pbuf);
|
|
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
|
|
maxoff = PageGetMaxOffsetNumber(page);
|
|
|
|
parent_half_dead = false;
|
|
|
|
parent_one_child = false;
|
|
|
|
if (poffset >= maxoff)
|
|
|
|
{
|
|
|
|
if (poffset == P_FIRSTDATAKEY(opaque))
|
|
|
|
parent_half_dead = true;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_bt_relbuf(rel, pbuf);
|
|
|
|
_bt_relbuf(rel, rbuf);
|
|
|
|
_bt_relbuf(rel, buf);
|
|
|
|
if (BufferIsValid(lbuf))
|
|
|
|
_bt_relbuf(rel, lbuf);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Will there be exactly one child left in this parent? */
|
|
|
|
if (OffsetNumberNext(P_FIRSTDATAKEY(opaque)) == maxoff)
|
|
|
|
parent_one_child = true;
|
|
|
|
}
|
2003-08-04 02:43:34 +02:00
|
|
|
|
2003-02-23 07:17:13 +01:00
|
|
|
/*
|
|
|
|
* If we are deleting the next-to-last page on the target's level,
|
2003-08-04 02:43:34 +02:00
|
|
|
* then the rightsib is a candidate to become the new fast root. (In
|
|
|
|
* theory, it might be possible to push the fast root even further
|
|
|
|
* down, but the odds of doing so are slim, and the locking
|
|
|
|
* considerations daunting.)
|
2003-02-23 07:17:13 +01:00
|
|
|
*
|
|
|
|
* We can safely acquire a lock on the metapage here --- see comments for
|
|
|
|
* _bt_newroot().
|
|
|
|
*/
|
|
|
|
if (leftsib == P_NONE)
|
|
|
|
{
|
|
|
|
page = BufferGetPage(rbuf);
|
|
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
|
|
Assert(opaque->btpo.level == targetlevel);
|
|
|
|
if (P_RIGHTMOST(opaque))
|
|
|
|
{
|
|
|
|
/* rightsib will be the only one left on the level */
|
|
|
|
metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE);
|
|
|
|
metapg = BufferGetPage(metabuf);
|
|
|
|
metad = BTPageGetMeta(metapg);
|
2003-08-04 02:43:34 +02:00
|
|
|
|
2003-02-23 07:17:13 +01:00
|
|
|
/*
|
|
|
|
* The expected case here is btm_fastlevel == targetlevel+1;
|
2003-08-04 02:43:34 +02:00
|
|
|
* if the fastlevel is <= targetlevel, something is wrong, and
|
|
|
|
* we choose to overwrite it to fix it.
|
2003-02-23 07:17:13 +01:00
|
|
|
*/
|
2003-08-04 02:43:34 +02:00
|
|
|
if (metad->btm_fastlevel > targetlevel + 1)
|
2003-02-23 07:17:13 +01:00
|
|
|
{
|
|
|
|
/* no update wanted */
|
|
|
|
_bt_relbuf(rel, metabuf);
|
|
|
|
metabuf = InvalidBuffer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Here we begin doing the deletion.
|
|
|
|
*/
|
|
|
|
|
2003-07-21 22:29:40 +02:00
|
|
|
/* No ereport(ERROR) until changes are logged */
|
2003-02-23 07:17:13 +01:00
|
|
|
START_CRIT_SECTION();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Update parent. The normal case is a tad tricky because we want to
|
2003-08-04 02:43:34 +02:00
|
|
|
* delete the target's downlink and the *following* key. Easiest way
|
|
|
|
* is to copy the right sibling's downlink over the target downlink,
|
|
|
|
* and then delete the following item.
|
2003-02-23 07:17:13 +01:00
|
|
|
*/
|
|
|
|
page = BufferGetPage(pbuf);
|
|
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
|
|
if (parent_half_dead)
|
|
|
|
{
|
|
|
|
PageIndexTupleDelete(page, poffset);
|
|
|
|
opaque->btpo_flags |= BTP_HALF_DEAD;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2003-08-04 02:43:34 +02:00
|
|
|
OffsetNumber nextoffset;
|
2003-02-23 07:17:13 +01:00
|
|
|
|
|
|
|
itemid = PageGetItemId(page, poffset);
|
|
|
|
btitem = (BTItem) PageGetItem(page, itemid);
|
|
|
|
Assert(ItemPointerGetBlockNumber(&(btitem->bti_itup.t_tid)) == target);
|
|
|
|
ItemPointerSet(&(btitem->bti_itup.t_tid), rightsib, P_HIKEY);
|
|
|
|
|
|
|
|
nextoffset = OffsetNumberNext(poffset);
|
|
|
|
/* This part is just for double-checking */
|
|
|
|
itemid = PageGetItemId(page, nextoffset);
|
|
|
|
btitem = (BTItem) PageGetItem(page, itemid);
|
|
|
|
if (ItemPointerGetBlockNumber(&(btitem->bti_itup.t_tid)) != rightsib)
|
2003-07-21 22:29:40 +02:00
|
|
|
elog(PANIC, "right sibling is not next child");
|
2003-02-23 07:17:13 +01:00
|
|
|
|
|
|
|
PageIndexTupleDelete(page, nextoffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2003-08-04 02:43:34 +02:00
|
|
|
* Update siblings' side-links. Note the target page's side-links
|
|
|
|
* will continue to point to the siblings.
|
2003-02-23 07:17:13 +01:00
|
|
|
*/
|
|
|
|
if (BufferIsValid(lbuf))
|
|
|
|
{
|
|
|
|
page = BufferGetPage(lbuf);
|
|
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
|
|
Assert(opaque->btpo_next == target);
|
|
|
|
opaque->btpo_next = rightsib;
|
|
|
|
}
|
|
|
|
page = BufferGetPage(rbuf);
|
|
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
|
|
Assert(opaque->btpo_prev == target);
|
|
|
|
opaque->btpo_prev = leftsib;
|
|
|
|
rightsib_empty = (P_FIRSTDATAKEY(opaque) > PageGetMaxOffsetNumber(page));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Mark the page itself deleted. It can be recycled when all current
|
|
|
|
* transactions are gone; or immediately if we're doing VACUUM FULL.
|
|
|
|
*/
|
|
|
|
page = BufferGetPage(buf);
|
|
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
|
|
opaque->btpo_flags |= BTP_DELETED;
|
|
|
|
opaque->btpo.xact =
|
|
|
|
vacuum_full ? FrozenTransactionId : ReadNewTransactionId();
|
|
|
|
|
|
|
|
/* And update the metapage, if needed */
|
|
|
|
if (BufferIsValid(metabuf))
|
|
|
|
{
|
|
|
|
metad->btm_fastroot = rightsib;
|
|
|
|
metad->btm_fastlevel = targetlevel;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* XLOG stuff */
|
|
|
|
if (!rel->rd_istemp)
|
|
|
|
{
|
|
|
|
xl_btree_delete_page xlrec;
|
|
|
|
xl_btree_metadata xlmeta;
|
|
|
|
uint8 xlinfo;
|
|
|
|
XLogRecPtr recptr;
|
|
|
|
XLogRecData rdata[5];
|
|
|
|
XLogRecData *nextrdata;
|
|
|
|
|
|
|
|
xlrec.target.node = rel->rd_node;
|
|
|
|
ItemPointerSet(&(xlrec.target.tid), parent, poffset);
|
|
|
|
xlrec.deadblk = target;
|
|
|
|
xlrec.leftblk = leftsib;
|
|
|
|
xlrec.rightblk = rightsib;
|
|
|
|
|
|
|
|
rdata[0].buffer = InvalidBuffer;
|
|
|
|
rdata[0].data = (char *) &xlrec;
|
|
|
|
rdata[0].len = SizeOfBtreeDeletePage;
|
|
|
|
rdata[0].next = nextrdata = &(rdata[1]);
|
|
|
|
|
|
|
|
if (BufferIsValid(metabuf))
|
|
|
|
{
|
|
|
|
xlmeta.root = metad->btm_root;
|
|
|
|
xlmeta.level = metad->btm_level;
|
|
|
|
xlmeta.fastroot = metad->btm_fastroot;
|
|
|
|
xlmeta.fastlevel = metad->btm_fastlevel;
|
|
|
|
|
|
|
|
nextrdata->buffer = InvalidBuffer;
|
|
|
|
nextrdata->data = (char *) &xlmeta;
|
|
|
|
nextrdata->len = sizeof(xl_btree_metadata);
|
|
|
|
nextrdata->next = nextrdata + 1;
|
|
|
|
nextrdata++;
|
|
|
|
xlinfo = XLOG_BTREE_DELETE_PAGE_META;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
xlinfo = XLOG_BTREE_DELETE_PAGE;
|
|
|
|
|
|
|
|
nextrdata->buffer = pbuf;
|
|
|
|
nextrdata->data = NULL;
|
|
|
|
nextrdata->len = 0;
|
|
|
|
nextrdata->next = nextrdata + 1;
|
|
|
|
nextrdata++;
|
|
|
|
|
|
|
|
nextrdata->buffer = rbuf;
|
|
|
|
nextrdata->data = NULL;
|
|
|
|
nextrdata->len = 0;
|
|
|
|
nextrdata->next = NULL;
|
|
|
|
|
|
|
|
if (BufferIsValid(lbuf))
|
|
|
|
{
|
|
|
|
nextrdata->next = nextrdata + 1;
|
|
|
|
nextrdata++;
|
|
|
|
nextrdata->buffer = lbuf;
|
|
|
|
nextrdata->data = NULL;
|
|
|
|
nextrdata->len = 0;
|
|
|
|
nextrdata->next = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
recptr = XLogInsert(RM_BTREE_ID, xlinfo, rdata);
|
|
|
|
|
|
|
|
if (BufferIsValid(metabuf))
|
|
|
|
{
|
|
|
|
PageSetLSN(metapg, recptr);
|
|
|
|
PageSetSUI(metapg, ThisStartUpID);
|
|
|
|
}
|
|
|
|
page = BufferGetPage(pbuf);
|
|
|
|
PageSetLSN(page, recptr);
|
|
|
|
PageSetSUI(page, ThisStartUpID);
|
|
|
|
page = BufferGetPage(rbuf);
|
|
|
|
PageSetLSN(page, recptr);
|
|
|
|
PageSetSUI(page, ThisStartUpID);
|
|
|
|
page = BufferGetPage(buf);
|
|
|
|
PageSetLSN(page, recptr);
|
|
|
|
PageSetSUI(page, ThisStartUpID);
|
|
|
|
if (BufferIsValid(lbuf))
|
|
|
|
{
|
|
|
|
page = BufferGetPage(lbuf);
|
|
|
|
PageSetLSN(page, recptr);
|
|
|
|
PageSetSUI(page, ThisStartUpID);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
END_CRIT_SECTION();
|
|
|
|
|
|
|
|
/* Write and release buffers */
|
|
|
|
if (BufferIsValid(metabuf))
|
|
|
|
_bt_wrtbuf(rel, metabuf);
|
|
|
|
_bt_wrtbuf(rel, pbuf);
|
|
|
|
_bt_wrtbuf(rel, rbuf);
|
|
|
|
_bt_wrtbuf(rel, buf);
|
|
|
|
if (BufferIsValid(lbuf))
|
|
|
|
_bt_wrtbuf(rel, lbuf);
|
|
|
|
|
|
|
|
/*
|
2003-08-08 23:42:59 +02:00
|
|
|
* If parent became half dead, recurse to try to delete it. Otherwise,
|
|
|
|
* if right sibling is empty and is now the last child of the parent,
|
|
|
|
* recurse to try to delete it. (These cases cannot apply at the same
|
|
|
|
* time, though the second case might itself recurse to the first.)
|
2003-02-23 07:17:13 +01:00
|
|
|
*/
|
|
|
|
if (parent_half_dead)
|
|
|
|
{
|
|
|
|
buf = _bt_getbuf(rel, parent, BT_READ);
|
|
|
|
return _bt_pagedel(rel, buf, vacuum_full) + 1;
|
|
|
|
}
|
|
|
|
if (parent_one_child && rightsib_empty)
|
|
|
|
{
|
|
|
|
buf = _bt_getbuf(rel, rightsib, BT_READ);
|
|
|
|
return _bt_pagedel(rel, buf, vacuum_full) + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|