mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-06 21:19:21 +02:00
appropriate btree three-way comparison routine. Not clear why the three-way comparison routines were being used in some paths and not others in btree --- incomplete changes by someone long ago, maybe? Anyway, this makes for a nice speedup in CREATE INDEX.
1691 lines
50 KiB
C
1691 lines
50 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* btinsert.c
|
|
* Item insertion in Lehman and Yao btrees for Postgres.
|
|
*
|
|
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtinsert.c,v 1.55 2000/02/18 06:32:33 tgl Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/heapam.h"
|
|
#include "access/nbtree.h"
|
|
|
|
|
|
static InsertIndexResult _bt_insertonpg(Relation rel, Buffer buf, BTStack stack, int keysz, ScanKey scankey, BTItem btitem, BTItem afteritem);
|
|
static Buffer _bt_split(Relation rel, Size keysz, ScanKey scankey,
|
|
Buffer buf, OffsetNumber firstright);
|
|
static OffsetNumber _bt_findsplitloc(Relation rel, Size keysz, ScanKey scankey,
|
|
Page page, OffsetNumber start,
|
|
OffsetNumber maxoff, Size llimit);
|
|
static void _bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf);
|
|
static OffsetNumber _bt_pgaddtup(Relation rel, Buffer buf, int keysz, ScanKey itup_scankey, Size itemsize, BTItem btitem, BTItem afteritem);
|
|
static bool _bt_goesonpg(Relation rel, Buffer buf, Size keysz, ScanKey scankey, BTItem afteritem);
|
|
static void _bt_updateitem(Relation rel, Size keysz, Buffer buf, BTItem oldItem, BTItem newItem);
|
|
static bool _bt_isequal(TupleDesc itupdesc, Page page, OffsetNumber offnum, int keysz, ScanKey scankey);
|
|
|
|
/*
|
|
* _bt_doinsert() -- Handle insertion of a single btitem in the tree.
|
|
*
|
|
* This routine is called by the public interface routines, btbuild
|
|
* and btinsert. By here, btitem is filled in, and has a unique
|
|
* (xid, seqno) pair.
|
|
*/
|
|
InsertIndexResult
|
|
_bt_doinsert(Relation rel, BTItem btitem, bool index_is_unique, Relation heapRel)
|
|
{
|
|
ScanKey itup_scankey;
|
|
IndexTuple itup;
|
|
BTStack stack;
|
|
Buffer buf;
|
|
BlockNumber blkno;
|
|
int natts = rel->rd_rel->relnatts;
|
|
InsertIndexResult res;
|
|
Buffer buffer;
|
|
|
|
itup = &(btitem->bti_itup);
|
|
|
|
/* we need a scan key to do our search, so build one */
|
|
itup_scankey = _bt_mkscankey(rel, itup);
|
|
|
|
/* find the page containing this key */
|
|
stack = _bt_search(rel, natts, itup_scankey, &buf);
|
|
|
|
/* trade in our read lock for a write lock */
|
|
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
|
|
LockBuffer(buf, BT_WRITE);
|
|
|
|
l1:
|
|
|
|
/*
|
|
* If the page was split between the time that we surrendered our read
|
|
* lock and acquired our write lock, then this page may no longer be
|
|
* the right place for the key we want to insert. In this case, we
|
|
* need to move right in the tree. See Lehman and Yao for an
|
|
* excruciatingly precise description.
|
|
*/
|
|
|
|
buf = _bt_moveright(rel, buf, natts, itup_scankey, BT_WRITE);
|
|
blkno = BufferGetBlockNumber(buf);
|
|
|
|
/* if we're not allowing duplicates, make sure the key isn't */
|
|
/* already in the node */
|
|
if (index_is_unique)
|
|
{
|
|
OffsetNumber offset,
|
|
maxoff;
|
|
Page page;
|
|
|
|
page = BufferGetPage(buf);
|
|
maxoff = PageGetMaxOffsetNumber(page);
|
|
|
|
offset = _bt_binsrch(rel, buf, natts, itup_scankey, BT_DESCENT);
|
|
|
|
/* make sure the offset we're given points to an actual */
|
|
/* key on the page before trying to compare it */
|
|
if (!PageIsEmpty(page) && offset <= maxoff)
|
|
{
|
|
TupleDesc itupdesc;
|
|
BTItem cbti;
|
|
HeapTupleData htup;
|
|
BTPageOpaque opaque;
|
|
Buffer nbuf;
|
|
BlockNumber nblkno;
|
|
bool chtup = true;
|
|
|
|
itupdesc = RelationGetDescr(rel);
|
|
nbuf = InvalidBuffer;
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
|
|
/*
|
|
* _bt_compare returns 0 for (1,NULL) and (1,NULL) - this's
|
|
* how we handling NULLs - and so we must not use _bt_compare
|
|
* in real comparison, but only for ordering/finding items on
|
|
* pages. - vadim 03/24/97
|
|
*
|
|
* while ( !_bt_compare (rel, itupdesc, page, natts,
|
|
* itup_scankey, offset) )
|
|
*/
|
|
while (_bt_isequal(itupdesc, page, offset, natts, itup_scankey))
|
|
{ /* they're equal */
|
|
|
|
/*
|
|
* Have to check is inserted heap tuple deleted one (i.e.
|
|
* just moved to another place by vacuum)!
|
|
*/
|
|
if (chtup)
|
|
{
|
|
htup.t_self = btitem->bti_itup.t_tid;
|
|
heap_fetch(heapRel, SnapshotDirty, &htup, &buffer);
|
|
if (htup.t_data == NULL) /* YES! */
|
|
break;
|
|
/* Live tuple was inserted */
|
|
ReleaseBuffer(buffer);
|
|
chtup = false;
|
|
}
|
|
cbti = (BTItem) PageGetItem(page, PageGetItemId(page, offset));
|
|
htup.t_self = cbti->bti_itup.t_tid;
|
|
heap_fetch(heapRel, SnapshotDirty, &htup, &buffer);
|
|
if (htup.t_data != NULL) /* it is a duplicate */
|
|
{
|
|
TransactionId xwait =
|
|
(TransactionIdIsValid(SnapshotDirty->xmin)) ?
|
|
SnapshotDirty->xmin : SnapshotDirty->xmax;
|
|
|
|
/*
|
|
* If this tuple is being updated by other transaction
|
|
* then we have to wait for its commit/abort.
|
|
*/
|
|
ReleaseBuffer(buffer);
|
|
if (TransactionIdIsValid(xwait))
|
|
{
|
|
if (nbuf != InvalidBuffer)
|
|
_bt_relbuf(rel, nbuf, BT_READ);
|
|
_bt_relbuf(rel, buf, BT_WRITE);
|
|
XactLockTableWait(xwait);
|
|
buf = _bt_getbuf(rel, blkno, BT_WRITE);
|
|
goto l1;/* continue from the begin */
|
|
}
|
|
elog(ERROR, "Cannot insert a duplicate key into unique index %s", RelationGetRelationName(rel));
|
|
}
|
|
/* htup null so no buffer to release */
|
|
/* get next offnum */
|
|
if (offset < maxoff)
|
|
offset = OffsetNumberNext(offset);
|
|
else
|
|
{ /* move right ? */
|
|
if (P_RIGHTMOST(opaque))
|
|
break;
|
|
if (!_bt_isequal(itupdesc, page, P_HIKEY,
|
|
natts, itup_scankey))
|
|
break;
|
|
|
|
/*
|
|
* min key of the right page is the same, ooh - so
|
|
* many dead duplicates...
|
|
*/
|
|
nblkno = opaque->btpo_next;
|
|
if (nbuf != InvalidBuffer)
|
|
_bt_relbuf(rel, nbuf, BT_READ);
|
|
for (nbuf = InvalidBuffer;;)
|
|
{
|
|
nbuf = _bt_getbuf(rel, nblkno, BT_READ);
|
|
page = BufferGetPage(nbuf);
|
|
maxoff = PageGetMaxOffsetNumber(page);
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
offset = P_RIGHTMOST(opaque) ? P_HIKEY : P_FIRSTKEY;
|
|
if (!PageIsEmpty(page) && offset <= maxoff)
|
|
{ /* Found some key */
|
|
break;
|
|
}
|
|
else
|
|
{ /* Empty or "pseudo"-empty page - get next */
|
|
nblkno = opaque->btpo_next;
|
|
_bt_relbuf(rel, nbuf, BT_READ);
|
|
nbuf = InvalidBuffer;
|
|
if (nblkno == P_NONE)
|
|
break;
|
|
}
|
|
}
|
|
if (nbuf == InvalidBuffer)
|
|
break;
|
|
}
|
|
}
|
|
if (nbuf != InvalidBuffer)
|
|
_bt_relbuf(rel, nbuf, BT_READ);
|
|
}
|
|
}
|
|
|
|
/* do the insertion */
|
|
res = _bt_insertonpg(rel, buf, stack, natts, itup_scankey,
|
|
btitem, (BTItem) NULL);
|
|
|
|
/* be tidy */
|
|
_bt_freestack(stack);
|
|
_bt_freeskey(itup_scankey);
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* _bt_insertonpg() -- Insert a tuple on a particular page in the index.
|
|
*
|
|
* This recursive procedure does the following things:
|
|
*
|
|
* + if necessary, splits the target page.
|
|
* + finds the right place to insert the tuple (taking into
|
|
* account any changes induced by a split).
|
|
* + inserts the tuple.
|
|
* + if the page was split, pops the parent stack, and finds the
|
|
* right place to insert the new child pointer (by walking
|
|
* right using information stored in the parent stack).
|
|
* + invoking itself with the appropriate tuple for the right
|
|
* child page on the parent.
|
|
*
|
|
* On entry, we must have the right buffer on which to do the
|
|
* insertion, and the buffer must be pinned and locked. On return,
|
|
* we will have dropped both the pin and the write lock on the buffer.
|
|
*
|
|
* The locking interactions in this code are critical. You should
|
|
* grok Lehman and Yao's paper before making any changes. In addition,
|
|
* you need to understand how we disambiguate duplicate keys in this
|
|
* implementation, in order to be able to find our location using
|
|
* L&Y "move right" operations. Since we may insert duplicate user
|
|
* keys, and since these dups may propogate up the tree, we use the
|
|
* 'afteritem' parameter to position ourselves correctly for the
|
|
* insertion on internal pages.
|
|
*/
|
|
static InsertIndexResult
|
|
_bt_insertonpg(Relation rel,
|
|
Buffer buf,
|
|
BTStack stack,
|
|
int keysz,
|
|
ScanKey scankey,
|
|
BTItem btitem,
|
|
BTItem afteritem)
|
|
{
|
|
InsertIndexResult res;
|
|
Page page;
|
|
BTPageOpaque lpageop;
|
|
BlockNumber itup_blkno;
|
|
OffsetNumber itup_off;
|
|
OffsetNumber firstright = InvalidOffsetNumber;
|
|
int itemsz;
|
|
bool do_split = false;
|
|
bool keys_equal = false;
|
|
|
|
page = BufferGetPage(buf);
|
|
lpageop = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
|
|
itemsz = IndexTupleDSize(btitem->bti_itup)
|
|
+ (sizeof(BTItemData) - sizeof(IndexTupleData));
|
|
|
|
itemsz = MAXALIGN(itemsz); /* be safe, PageAddItem will do
|
|
* this but we need to be
|
|
* consistent */
|
|
|
|
/*
|
|
* Check whether the item can fit on a btree page at all.
|
|
* (Eventually, we ought to try to apply TOAST methods if not.)
|
|
* We actually need to be able to fit three items on every page,
|
|
* so restrict any one item to 1/3 the per-page available space.
|
|
* Note that at this point, itemsz doesn't include the ItemId.
|
|
*/
|
|
if (itemsz > (PageGetPageSize(page)-sizeof(PageHeaderData)-MAXALIGN(sizeof(BTPageOpaqueData)))/3 - sizeof(ItemIdData))
|
|
elog(ERROR, "btree: index item size %d exceeds maximum %ld",
|
|
itemsz,
|
|
(PageGetPageSize(page)-sizeof(PageHeaderData)-MAXALIGN(sizeof(BTPageOpaqueData)))/3 - sizeof(ItemIdData));
|
|
|
|
/*
|
|
* If we have to insert item on the leftmost page which is the first
|
|
* page in the chain of duplicates then: 1. if scankey == hikey (i.e.
|
|
* - new duplicate item) then insert it here; 2. if scankey < hikey
|
|
* then: 2.a if there is duplicate key(s) here - we force splitting;
|
|
* 2.b else - we may "eat" this page from duplicates chain.
|
|
*/
|
|
if (lpageop->btpo_flags & BTP_CHAIN)
|
|
{
|
|
OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
|
|
ItemId hitemid;
|
|
BTItem hitem;
|
|
|
|
Assert(!P_RIGHTMOST(lpageop));
|
|
hitemid = PageGetItemId(page, P_HIKEY);
|
|
hitem = (BTItem) PageGetItem(page, hitemid);
|
|
if (maxoff > P_HIKEY &&
|
|
!_bt_itemcmp(rel, keysz, scankey, hitem,
|
|
(BTItem) PageGetItem(page, PageGetItemId(page, P_FIRSTKEY)),
|
|
BTEqualStrategyNumber))
|
|
elog(FATAL, "btree: bad key on the page in the chain of duplicates");
|
|
|
|
if (!_bt_skeycmp(rel, keysz, scankey, page, hitemid,
|
|
BTEqualStrategyNumber))
|
|
{
|
|
if (!P_LEFTMOST(lpageop))
|
|
elog(FATAL, "btree: attempt to insert bad key on the non-leftmost page in the chain of duplicates");
|
|
if (!_bt_skeycmp(rel, keysz, scankey, page, hitemid,
|
|
BTLessStrategyNumber))
|
|
elog(FATAL, "btree: attempt to insert higher key on the leftmost page in the chain of duplicates");
|
|
if (maxoff > P_HIKEY) /* have duplicate(s) */
|
|
{
|
|
firstright = P_FIRSTKEY;
|
|
do_split = true;
|
|
}
|
|
else
|
|
/* "eat" page */
|
|
{
|
|
Buffer pbuf;
|
|
Page ppage;
|
|
|
|
itup_blkno = BufferGetBlockNumber(buf);
|
|
itup_off = PageAddItem(page, (Item) btitem, itemsz,
|
|
P_FIRSTKEY, LP_USED);
|
|
if (itup_off == InvalidOffsetNumber)
|
|
elog(FATAL, "btree: failed to add item");
|
|
lpageop->btpo_flags &= ~BTP_CHAIN;
|
|
pbuf = _bt_getstackbuf(rel, stack, BT_WRITE);
|
|
ppage = BufferGetPage(pbuf);
|
|
PageIndexTupleDelete(ppage, stack->bts_offset);
|
|
pfree(stack->bts_btitem);
|
|
stack->bts_btitem = _bt_formitem(&(btitem->bti_itup));
|
|
ItemPointerSet(&(stack->bts_btitem->bti_itup.t_tid),
|
|
itup_blkno, P_HIKEY);
|
|
_bt_wrtbuf(rel, buf);
|
|
res = _bt_insertonpg(rel, pbuf, stack->bts_parent,
|
|
keysz, scankey, stack->bts_btitem,
|
|
NULL);
|
|
ItemPointerSet(&(res->pointerData), itup_blkno, itup_off);
|
|
return res;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
keys_equal = true;
|
|
if (PageGetFreeSpace(page) < itemsz)
|
|
do_split = true;
|
|
}
|
|
}
|
|
else if (PageGetFreeSpace(page) < itemsz)
|
|
do_split = true;
|
|
else if (PageGetFreeSpace(page) < 3 * itemsz + 2 * sizeof(ItemIdData))
|
|
{
|
|
OffsetNumber offnum = (P_RIGHTMOST(lpageop)) ? P_HIKEY : P_FIRSTKEY;
|
|
OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
|
|
|
|
if (offnum < maxoff) /* can't split unless at least 2 items... */
|
|
{
|
|
ItemId itid;
|
|
BTItem previtem,
|
|
chkitem;
|
|
Size maxsize;
|
|
Size currsize;
|
|
|
|
/* find largest group of identically-keyed items on page */
|
|
itid = PageGetItemId(page, offnum);
|
|
previtem = (BTItem) PageGetItem(page, itid);
|
|
maxsize = currsize = (ItemIdGetLength(itid) + sizeof(ItemIdData));
|
|
for (offnum = OffsetNumberNext(offnum);
|
|
offnum <= maxoff; offnum = OffsetNumberNext(offnum))
|
|
{
|
|
itid = PageGetItemId(page, offnum);
|
|
chkitem = (BTItem) PageGetItem(page, itid);
|
|
if (!_bt_itemcmp(rel, keysz, scankey,
|
|
previtem, chkitem,
|
|
BTEqualStrategyNumber))
|
|
{
|
|
if (currsize > maxsize)
|
|
maxsize = currsize;
|
|
currsize = 0;
|
|
previtem = chkitem;
|
|
}
|
|
currsize += (ItemIdGetLength(itid) + sizeof(ItemIdData));
|
|
}
|
|
if (currsize > maxsize)
|
|
maxsize = currsize;
|
|
/* Decide to split if largest group is > 1/2 page size */
|
|
maxsize += sizeof(PageHeaderData) +
|
|
MAXALIGN(sizeof(BTPageOpaqueData));
|
|
if (maxsize >= PageGetPageSize(page) / 2)
|
|
do_split = true;
|
|
}
|
|
}
|
|
|
|
if (do_split)
|
|
{
|
|
Buffer rbuf;
|
|
Page rpage;
|
|
BTItem ritem;
|
|
BlockNumber rbknum;
|
|
BTPageOpaque rpageop;
|
|
Buffer pbuf;
|
|
Page ppage;
|
|
BTPageOpaque ppageop;
|
|
BlockNumber bknum = BufferGetBlockNumber(buf);
|
|
BTItem lowLeftItem;
|
|
OffsetNumber maxoff;
|
|
bool shifted = false;
|
|
bool left_chained = (lpageop->btpo_flags & BTP_CHAIN) ? true : false;
|
|
bool is_root = lpageop->btpo_flags & BTP_ROOT;
|
|
|
|
/*
|
|
* Instead of splitting leaf page in the chain of duplicates
|
|
* by new duplicate, insert it into some right page.
|
|
*/
|
|
if ((lpageop->btpo_flags & BTP_CHAIN) &&
|
|
(lpageop->btpo_flags & BTP_LEAF) && keys_equal)
|
|
{
|
|
rbuf = _bt_getbuf(rel, lpageop->btpo_next, BT_WRITE);
|
|
rpage = BufferGetPage(rbuf);
|
|
rpageop = (BTPageOpaque) PageGetSpecialPointer(rpage);
|
|
/*
|
|
* some checks
|
|
*/
|
|
if (!P_RIGHTMOST(rpageop)) /* non-rightmost page */
|
|
{ /* If we have the same hikey here then
|
|
* it's yet another page in chain. */
|
|
if (_bt_skeycmp(rel, keysz, scankey, rpage,
|
|
PageGetItemId(rpage, P_HIKEY),
|
|
BTEqualStrategyNumber))
|
|
{
|
|
if (!(rpageop->btpo_flags & BTP_CHAIN))
|
|
elog(FATAL, "btree: lost page in the chain of duplicates");
|
|
}
|
|
else if (_bt_skeycmp(rel, keysz, scankey, rpage,
|
|
PageGetItemId(rpage, P_HIKEY),
|
|
BTGreaterStrategyNumber))
|
|
elog(FATAL, "btree: hikey is out of order");
|
|
else if (rpageop->btpo_flags & BTP_CHAIN)
|
|
/*
|
|
* If hikey > scankey then it's last page in chain and
|
|
* BTP_CHAIN must be OFF
|
|
*/
|
|
elog(FATAL, "btree: lost last page in the chain of duplicates");
|
|
}
|
|
else
|
|
/* rightmost page */
|
|
{
|
|
Assert(!(rpageop->btpo_flags & BTP_CHAIN));
|
|
}
|
|
_bt_relbuf(rel, buf, BT_WRITE);
|
|
return (_bt_insertonpg(rel, rbuf, stack, keysz,
|
|
scankey, btitem, afteritem));
|
|
}
|
|
|
|
/*
|
|
* If after splitting un-chained page we'll got chain of pages
|
|
* with duplicates then we want to know 1. on which of two pages
|
|
* new btitem will go (current _bt_findsplitloc is quite bad); 2.
|
|
* what parent (if there's one) thinking about it (remember about
|
|
* deletions)
|
|
*/
|
|
else if (!(lpageop->btpo_flags & BTP_CHAIN))
|
|
{
|
|
OffsetNumber start = (P_RIGHTMOST(lpageop)) ? P_HIKEY : P_FIRSTKEY;
|
|
Size llimit;
|
|
|
|
maxoff = PageGetMaxOffsetNumber(page);
|
|
llimit = PageGetPageSize(page) - sizeof(PageHeaderData) -
|
|
MAXALIGN(sizeof(BTPageOpaqueData))
|
|
+sizeof(ItemIdData);
|
|
llimit /= 2;
|
|
firstright = _bt_findsplitloc(rel, keysz, scankey,
|
|
page, start, maxoff, llimit);
|
|
|
|
if (_bt_itemcmp(rel, keysz, scankey,
|
|
(BTItem) PageGetItem(page, PageGetItemId(page, start)),
|
|
(BTItem) PageGetItem(page, PageGetItemId(page, firstright)),
|
|
BTEqualStrategyNumber))
|
|
{
|
|
if (_bt_skeycmp(rel, keysz, scankey, page,
|
|
PageGetItemId(page, firstright),
|
|
BTLessStrategyNumber))
|
|
|
|
/*
|
|
* force moving current items to the new page: new
|
|
* item will go on the current page.
|
|
*/
|
|
firstright = start;
|
|
else
|
|
|
|
/*
|
|
* new btitem >= firstright, start item == firstright
|
|
* - new chain of duplicates: if this non-leftmost
|
|
* leaf page and parent item < start item then force
|
|
* moving all items to the new page - current page
|
|
* will be "empty" after it.
|
|
*/
|
|
{
|
|
if (!P_LEFTMOST(lpageop) &&
|
|
(lpageop->btpo_flags & BTP_LEAF))
|
|
{
|
|
ItemPointerSet(&(stack->bts_btitem->bti_itup.t_tid),
|
|
bknum, P_HIKEY);
|
|
pbuf = _bt_getstackbuf(rel, stack, BT_WRITE);
|
|
if (_bt_itemcmp(rel, keysz, scankey,
|
|
stack->bts_btitem,
|
|
(BTItem) PageGetItem(page,
|
|
PageGetItemId(page, start)),
|
|
BTLessStrategyNumber))
|
|
{
|
|
firstright = start;
|
|
shifted = true;
|
|
}
|
|
_bt_relbuf(rel, pbuf, BT_WRITE);
|
|
}
|
|
}
|
|
} /* else - no new chain if start item <
|
|
* firstright one */
|
|
}
|
|
|
|
/* split the buffer into left and right halves */
|
|
rbuf = _bt_split(rel, keysz, scankey, buf, firstright);
|
|
|
|
/* which new page (left half or right half) gets the tuple? */
|
|
if (_bt_goesonpg(rel, buf, keysz, scankey, afteritem))
|
|
{
|
|
/* left page */
|
|
itup_off = _bt_pgaddtup(rel, buf, keysz, scankey,
|
|
itemsz, btitem, afteritem);
|
|
itup_blkno = BufferGetBlockNumber(buf);
|
|
}
|
|
else
|
|
{
|
|
/* right page */
|
|
itup_off = _bt_pgaddtup(rel, rbuf, keysz, scankey,
|
|
itemsz, btitem, afteritem);
|
|
itup_blkno = BufferGetBlockNumber(rbuf);
|
|
}
|
|
|
|
maxoff = PageGetMaxOffsetNumber(page);
|
|
if (shifted)
|
|
{
|
|
if (maxoff > P_FIRSTKEY)
|
|
elog(FATAL, "btree: shifted page is not empty");
|
|
lowLeftItem = (BTItem) NULL;
|
|
}
|
|
else
|
|
{
|
|
if (maxoff < P_FIRSTKEY)
|
|
elog(FATAL, "btree: un-shifted page is empty");
|
|
lowLeftItem = (BTItem) PageGetItem(page,
|
|
PageGetItemId(page, P_FIRSTKEY));
|
|
if (_bt_itemcmp(rel, keysz, scankey, lowLeftItem,
|
|
(BTItem) PageGetItem(page, PageGetItemId(page, P_HIKEY)),
|
|
BTEqualStrategyNumber))
|
|
lpageop->btpo_flags |= BTP_CHAIN;
|
|
}
|
|
|
|
/*
|
|
* By here,
|
|
*
|
|
* + our target page has been split; + the original tuple has been
|
|
* inserted; + we have write locks on both the old (left half)
|
|
* and new (right half) buffers, after the split; and + we have
|
|
* the key we want to insert into the parent.
|
|
*
|
|
* Do the parent insertion. We need to hold onto the locks for the
|
|
* child pages until we locate the parent, but we can release them
|
|
* before doing the actual insertion (see Lehman and Yao for the
|
|
* reasoning).
|
|
*/
|
|
|
|
l_spl: ;
|
|
if (stack == (BTStack) NULL)
|
|
{
|
|
if (!is_root) /* if this page was not root page */
|
|
{
|
|
elog(DEBUG, "btree: concurrent ROOT page split");
|
|
stack = (BTStack) palloc(sizeof(BTStackData));
|
|
stack->bts_blkno = lpageop->btpo_parent;
|
|
stack->bts_offset = InvalidOffsetNumber;
|
|
stack->bts_btitem = (BTItem) palloc(sizeof(BTItemData));
|
|
/* bts_btitem will be initialized below */
|
|
stack->bts_parent = NULL;
|
|
goto l_spl;
|
|
}
|
|
/* create a new root node and release the split buffers */
|
|
_bt_newroot(rel, buf, rbuf);
|
|
}
|
|
else
|
|
{
|
|
ScanKey newskey;
|
|
InsertIndexResult newres;
|
|
BTItem new_item;
|
|
OffsetNumber upditem_offset = P_HIKEY;
|
|
bool do_update = false;
|
|
bool update_in_place = true;
|
|
bool parent_chained;
|
|
|
|
/* form a index tuple that points at the new right page */
|
|
rbknum = BufferGetBlockNumber(rbuf);
|
|
rpage = BufferGetPage(rbuf);
|
|
rpageop = (BTPageOpaque) PageGetSpecialPointer(rpage);
|
|
|
|
/*
|
|
* By convention, the first entry (1) on every non-rightmost
|
|
* page is the high key for that page. In order to get the
|
|
* lowest key on the new right page, we actually look at its
|
|
* second (2) entry.
|
|
*/
|
|
|
|
if (!P_RIGHTMOST(rpageop))
|
|
{
|
|
ritem = (BTItem) PageGetItem(rpage,
|
|
PageGetItemId(rpage, P_FIRSTKEY));
|
|
if (_bt_itemcmp(rel, keysz, scankey,
|
|
ritem,
|
|
(BTItem) PageGetItem(rpage,
|
|
PageGetItemId(rpage, P_HIKEY)),
|
|
BTEqualStrategyNumber))
|
|
rpageop->btpo_flags |= BTP_CHAIN;
|
|
}
|
|
else
|
|
ritem = (BTItem) PageGetItem(rpage,
|
|
PageGetItemId(rpage, P_HIKEY));
|
|
|
|
/* get a unique btitem for this key */
|
|
new_item = _bt_formitem(&(ritem->bti_itup));
|
|
|
|
ItemPointerSet(&(new_item->bti_itup.t_tid), rbknum, P_HIKEY);
|
|
|
|
/*
|
|
* Find the parent buffer and get the parent page.
|
|
*
|
|
* Oops - if we were moved right then we need to change stack
|
|
* item! We want to find parent pointing to where we are,
|
|
* right ? - vadim 05/27/97
|
|
*/
|
|
ItemPointerSet(&(stack->bts_btitem->bti_itup.t_tid),
|
|
bknum, P_HIKEY);
|
|
pbuf = _bt_getstackbuf(rel, stack, BT_WRITE);
|
|
ppage = BufferGetPage(pbuf);
|
|
ppageop = (BTPageOpaque) PageGetSpecialPointer(ppage);
|
|
parent_chained = ((ppageop->btpo_flags & BTP_CHAIN)) ? true : false;
|
|
|
|
if (parent_chained && !left_chained)
|
|
elog(FATAL, "nbtree: unexpected chained parent of unchained page");
|
|
|
|
/*
|
|
* If the key of new_item is < than the key of the item in the
|
|
* parent page pointing to the left page (stack->bts_btitem),
|
|
* we have to update the latter key; otherwise the keys on the
|
|
* parent page wouldn't be monotonically increasing after we
|
|
* inserted the new pointer to the right page (new_item). This
|
|
* only happens if our left page is the leftmost page and a
|
|
* new minimum key had been inserted before, which is not
|
|
* reflected in the parent page but didn't matter so far. If
|
|
* there are duplicate keys and this new minimum key spills
|
|
* over to our new right page, we get an inconsistency if we
|
|
* don't update the left key in the parent page.
|
|
*
|
|
* Also, new duplicates handling code require us to update parent
|
|
* item if some smaller items left on the left page (which is
|
|
* possible in splitting leftmost page) and current parent
|
|
* item == new_item. - vadim 05/27/97
|
|
*/
|
|
if (_bt_itemcmp(rel, keysz, scankey,
|
|
stack->bts_btitem, new_item,
|
|
BTGreaterStrategyNumber) ||
|
|
(!shifted &&
|
|
_bt_itemcmp(rel, keysz, scankey,
|
|
stack->bts_btitem, new_item,
|
|
BTEqualStrategyNumber) &&
|
|
_bt_itemcmp(rel, keysz, scankey,
|
|
lowLeftItem, new_item,
|
|
BTLessStrategyNumber)))
|
|
{
|
|
do_update = true;
|
|
|
|
/*
|
|
* figure out which key is leftmost (if the parent page is
|
|
* rightmost, too, it must be the root)
|
|
*/
|
|
if (P_RIGHTMOST(ppageop))
|
|
upditem_offset = P_HIKEY;
|
|
else
|
|
upditem_offset = P_FIRSTKEY;
|
|
if (!P_LEFTMOST(lpageop) ||
|
|
stack->bts_offset != upditem_offset)
|
|
elog(FATAL, "btree: items are out of order (leftmost %d, stack %u, update %u)",
|
|
P_LEFTMOST(lpageop), stack->bts_offset, upditem_offset);
|
|
}
|
|
|
|
if (do_update)
|
|
{
|
|
if (shifted)
|
|
elog(FATAL, "btree: attempt to update parent for shifted page");
|
|
|
|
/*
|
|
* Try to update in place. If out parent page is chained
|
|
* then we must forse insertion.
|
|
*/
|
|
if (!parent_chained &&
|
|
MAXALIGN(IndexTupleDSize(lowLeftItem->bti_itup)) ==
|
|
MAXALIGN(IndexTupleDSize(stack->bts_btitem->bti_itup)))
|
|
{
|
|
_bt_updateitem(rel, keysz, pbuf,
|
|
stack->bts_btitem, lowLeftItem);
|
|
_bt_wrtbuf(rel, buf);
|
|
_bt_wrtbuf(rel, rbuf);
|
|
}
|
|
else
|
|
{
|
|
update_in_place = false;
|
|
PageIndexTupleDelete(ppage, upditem_offset);
|
|
|
|
/*
|
|
* don't write anything out yet--we still have the
|
|
* write lock, and now we call another _bt_insertonpg
|
|
* to insert the correct key. First, make a new item,
|
|
* using the tuple data from lowLeftItem. Point it to
|
|
* the left child. Update it on the stack at the same
|
|
* time.
|
|
*/
|
|
pfree(stack->bts_btitem);
|
|
stack->bts_btitem = _bt_formitem(&(lowLeftItem->bti_itup));
|
|
ItemPointerSet(&(stack->bts_btitem->bti_itup.t_tid),
|
|
bknum, P_HIKEY);
|
|
|
|
/*
|
|
* Unlock the children before doing this
|
|
*/
|
|
_bt_wrtbuf(rel, buf);
|
|
_bt_wrtbuf(rel, rbuf);
|
|
|
|
/*
|
|
* A regular _bt_binsrch should find the right place
|
|
* to put the new entry, since it should be lower than
|
|
* any other key on the page. Therefore set afteritem
|
|
* to NULL.
|
|
*/
|
|
newskey = _bt_mkscankey(rel, &(stack->bts_btitem->bti_itup));
|
|
newres = _bt_insertonpg(rel, pbuf, stack->bts_parent,
|
|
keysz, newskey, stack->bts_btitem,
|
|
NULL);
|
|
|
|
pfree(newres);
|
|
pfree(newskey);
|
|
|
|
/*
|
|
* we have now lost our lock on the parent buffer, and
|
|
* need to get it back.
|
|
*/
|
|
pbuf = _bt_getstackbuf(rel, stack, BT_WRITE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_bt_wrtbuf(rel, buf);
|
|
_bt_wrtbuf(rel, rbuf);
|
|
}
|
|
|
|
newskey = _bt_mkscankey(rel, &(new_item->bti_itup));
|
|
|
|
afteritem = stack->bts_btitem;
|
|
if (parent_chained && !update_in_place)
|
|
{
|
|
ppage = BufferGetPage(pbuf);
|
|
ppageop = (BTPageOpaque) PageGetSpecialPointer(ppage);
|
|
if (ppageop->btpo_flags & BTP_CHAIN)
|
|
elog(FATAL, "btree: unexpected BTP_CHAIN flag in parent after update");
|
|
if (P_RIGHTMOST(ppageop))
|
|
elog(FATAL, "btree: chained parent is RIGHTMOST after update");
|
|
maxoff = PageGetMaxOffsetNumber(ppage);
|
|
if (maxoff != P_FIRSTKEY)
|
|
elog(FATAL, "btree: FIRSTKEY was unexpected in parent after update");
|
|
if (_bt_skeycmp(rel, keysz, newskey, ppage,
|
|
PageGetItemId(ppage, P_FIRSTKEY),
|
|
BTLessEqualStrategyNumber))
|
|
elog(FATAL, "btree: parent FIRSTKEY is >= duplicate key after update");
|
|
if (!_bt_skeycmp(rel, keysz, newskey, ppage,
|
|
PageGetItemId(ppage, P_HIKEY),
|
|
BTEqualStrategyNumber))
|
|
elog(FATAL, "btree: parent HIGHKEY is not equal duplicate key after update");
|
|
afteritem = (BTItem) NULL;
|
|
}
|
|
else if (left_chained && !update_in_place)
|
|
{
|
|
ppage = BufferGetPage(pbuf);
|
|
ppageop = (BTPageOpaque) PageGetSpecialPointer(ppage);
|
|
if (!P_RIGHTMOST(ppageop) &&
|
|
_bt_skeycmp(rel, keysz, newskey, ppage,
|
|
PageGetItemId(ppage, P_HIKEY),
|
|
BTGreaterStrategyNumber))
|
|
afteritem = (BTItem) NULL;
|
|
}
|
|
if (afteritem == (BTItem) NULL)
|
|
{
|
|
rbuf = _bt_getbuf(rel, ppageop->btpo_next, BT_WRITE);
|
|
_bt_relbuf(rel, pbuf, BT_WRITE);
|
|
pbuf = rbuf;
|
|
}
|
|
|
|
newres = _bt_insertonpg(rel, pbuf, stack->bts_parent,
|
|
keysz, newskey, new_item,
|
|
afteritem);
|
|
|
|
/* be tidy */
|
|
pfree(newres);
|
|
pfree(newskey);
|
|
pfree(new_item);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
itup_off = _bt_pgaddtup(rel, buf, keysz, scankey,
|
|
itemsz, btitem, afteritem);
|
|
itup_blkno = BufferGetBlockNumber(buf);
|
|
|
|
_bt_relbuf(rel, buf, BT_WRITE);
|
|
}
|
|
|
|
/* by here, the new tuple is inserted */
|
|
res = (InsertIndexResult) palloc(sizeof(InsertIndexResultData));
|
|
ItemPointerSet(&(res->pointerData), itup_blkno, itup_off);
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* _bt_split() -- split a page in the btree.
|
|
*
|
|
* On entry, buf is the page to split, and is write-locked and pinned.
|
|
* Returns the new right sibling of buf, pinned and write-locked. The
|
|
* pin and lock on buf are maintained.
|
|
*/
|
|
static Buffer
|
|
_bt_split(Relation rel, Size keysz, ScanKey scankey,
|
|
Buffer buf, OffsetNumber firstright)
|
|
{
|
|
Buffer rbuf;
|
|
Page origpage;
|
|
Page leftpage,
|
|
rightpage;
|
|
BTPageOpaque ropaque,
|
|
lopaque,
|
|
oopaque;
|
|
Buffer sbuf;
|
|
Page spage;
|
|
BTPageOpaque sopaque;
|
|
Size itemsz;
|
|
ItemId itemid;
|
|
BTItem item;
|
|
OffsetNumber leftoff,
|
|
rightoff;
|
|
OffsetNumber start;
|
|
OffsetNumber maxoff;
|
|
OffsetNumber i;
|
|
|
|
rbuf = _bt_getbuf(rel, P_NEW, BT_WRITE);
|
|
origpage = BufferGetPage(buf);
|
|
leftpage = PageGetTempPage(origpage, sizeof(BTPageOpaqueData));
|
|
rightpage = BufferGetPage(rbuf);
|
|
|
|
_bt_pageinit(rightpage, BufferGetPageSize(rbuf));
|
|
_bt_pageinit(leftpage, BufferGetPageSize(buf));
|
|
|
|
/* init btree private data */
|
|
oopaque = (BTPageOpaque) PageGetSpecialPointer(origpage);
|
|
lopaque = (BTPageOpaque) PageGetSpecialPointer(leftpage);
|
|
ropaque = (BTPageOpaque) PageGetSpecialPointer(rightpage);
|
|
|
|
/* if we're splitting this page, it won't be the root when we're done */
|
|
oopaque->btpo_flags &= ~BTP_ROOT;
|
|
oopaque->btpo_flags &= ~BTP_CHAIN;
|
|
lopaque->btpo_flags = ropaque->btpo_flags = oopaque->btpo_flags;
|
|
lopaque->btpo_prev = oopaque->btpo_prev;
|
|
ropaque->btpo_prev = BufferGetBlockNumber(buf);
|
|
lopaque->btpo_next = BufferGetBlockNumber(rbuf);
|
|
ropaque->btpo_next = oopaque->btpo_next;
|
|
|
|
lopaque->btpo_parent = ropaque->btpo_parent = oopaque->btpo_parent;
|
|
|
|
/*
|
|
* If the page we're splitting is not the rightmost page at its level
|
|
* in the tree, then the first (0) entry on the page is the high key
|
|
* for the page. We need to copy that to the right half. Otherwise
|
|
* (meaning the rightmost page case), we should treat the line
|
|
* pointers beginning at zero as user data.
|
|
*
|
|
* We leave a blank space at the start of the line table for the left
|
|
* page. We'll come back later and fill it in with the high key item
|
|
* we get from the right key.
|
|
*/
|
|
|
|
leftoff = P_FIRSTKEY;
|
|
ropaque->btpo_next = oopaque->btpo_next;
|
|
if (!P_RIGHTMOST(oopaque))
|
|
{
|
|
/* splitting a non-rightmost page, start at the first data item */
|
|
start = P_FIRSTKEY;
|
|
|
|
itemid = PageGetItemId(origpage, P_HIKEY);
|
|
itemsz = ItemIdGetLength(itemid);
|
|
item = (BTItem) PageGetItem(origpage, itemid);
|
|
if (PageAddItem(rightpage, (Item) item, itemsz, P_HIKEY, LP_USED) == InvalidOffsetNumber)
|
|
elog(FATAL, "btree: failed to add hikey to the right sibling");
|
|
rightoff = P_FIRSTKEY;
|
|
}
|
|
else
|
|
{
|
|
/* splitting a rightmost page, "high key" is the first data item */
|
|
start = P_HIKEY;
|
|
|
|
/* the new rightmost page will not have a high key */
|
|
rightoff = P_HIKEY;
|
|
}
|
|
maxoff = PageGetMaxOffsetNumber(origpage);
|
|
if (firstright == InvalidOffsetNumber)
|
|
{
|
|
Size llimit = PageGetFreeSpace(leftpage) / 2;
|
|
|
|
firstright = _bt_findsplitloc(rel, keysz, scankey,
|
|
origpage, start, maxoff, llimit);
|
|
}
|
|
|
|
for (i = start; i <= maxoff; i = OffsetNumberNext(i))
|
|
{
|
|
itemid = PageGetItemId(origpage, i);
|
|
itemsz = ItemIdGetLength(itemid);
|
|
item = (BTItem) PageGetItem(origpage, itemid);
|
|
|
|
/* decide which page to put it on */
|
|
if (i < firstright)
|
|
{
|
|
if (PageAddItem(leftpage, (Item) item, itemsz, leftoff,
|
|
LP_USED) == InvalidOffsetNumber)
|
|
elog(FATAL, "btree: failed to add item to the left sibling");
|
|
leftoff = OffsetNumberNext(leftoff);
|
|
}
|
|
else
|
|
{
|
|
if (PageAddItem(rightpage, (Item) item, itemsz, rightoff,
|
|
LP_USED) == InvalidOffsetNumber)
|
|
elog(FATAL, "btree: failed to add item to the right sibling");
|
|
rightoff = OffsetNumberNext(rightoff);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Okay, page has been split, high key on right page is correct. Now
|
|
* set the high key on the left page to be the min key on the right
|
|
* page.
|
|
*/
|
|
|
|
if (P_RIGHTMOST(ropaque))
|
|
itemid = PageGetItemId(rightpage, P_HIKEY);
|
|
else
|
|
itemid = PageGetItemId(rightpage, P_FIRSTKEY);
|
|
itemsz = ItemIdGetLength(itemid);
|
|
item = (BTItem) PageGetItem(rightpage, itemid);
|
|
|
|
/*
|
|
* We left a hole for the high key on the left page; fill it. The
|
|
* modal crap is to tell the page manager to put the new item on the
|
|
* page and not screw around with anything else. Whoever designed
|
|
* this interface has presumably crawled back into the dung heap they
|
|
* came from. No one here will admit to it.
|
|
*/
|
|
|
|
PageManagerModeSet(OverwritePageManagerMode);
|
|
if (PageAddItem(leftpage, (Item) item, itemsz, P_HIKEY, LP_USED) == InvalidOffsetNumber)
|
|
elog(FATAL, "btree: failed to add hikey to the left sibling");
|
|
PageManagerModeSet(ShufflePageManagerMode);
|
|
|
|
/*
|
|
* By here, the original data page has been split into two new halves,
|
|
* and these are correct. The algorithm requires that the left page
|
|
* never move during a split, so we copy the new left page back on top
|
|
* of the original. Note that this is not a waste of time, since we
|
|
* also require (in the page management code) that the center of a
|
|
* page always be clean, and the most efficient way to guarantee this
|
|
* is just to compact the data by reinserting it into a new left page.
|
|
*/
|
|
|
|
PageRestoreTempPage(leftpage, origpage);
|
|
|
|
/* write these guys out */
|
|
_bt_wrtnorelbuf(rel, rbuf);
|
|
_bt_wrtnorelbuf(rel, buf);
|
|
|
|
/*
|
|
* Finally, we need to grab the right sibling (if any) and fix the
|
|
* prev pointer there. We are guaranteed that this is deadlock-free
|
|
* since no other writer will be moving holding a lock on that page
|
|
* and trying to move left, and all readers release locks on a page
|
|
* before trying to fetch its neighbors.
|
|
*/
|
|
|
|
if (!P_RIGHTMOST(ropaque))
|
|
{
|
|
sbuf = _bt_getbuf(rel, ropaque->btpo_next, BT_WRITE);
|
|
spage = BufferGetPage(sbuf);
|
|
sopaque = (BTPageOpaque) PageGetSpecialPointer(spage);
|
|
sopaque->btpo_prev = BufferGetBlockNumber(rbuf);
|
|
|
|
/* write and release the old right sibling */
|
|
_bt_wrtbuf(rel, sbuf);
|
|
}
|
|
|
|
/* split's done */
|
|
return rbuf;
|
|
}
|
|
|
|
/*
|
|
* _bt_findsplitloc() -- find a safe place to split a page.
|
|
*
|
|
* In order to guarantee the proper handling of searches for duplicate
|
|
* keys, the first duplicate in the chain must either be the first
|
|
* item on the page after the split, or the entire chain must be on
|
|
* one of the two pages. That is,
|
|
* [1 2 2 2 3 4 5]
|
|
* must become
|
|
* [1] [2 2 2 3 4 5]
|
|
* or
|
|
* [1 2 2 2] [3 4 5]
|
|
* but not
|
|
* [1 2 2] [2 3 4 5].
|
|
* However,
|
|
* [2 2 2 2 2 3 4]
|
|
* may be split as
|
|
* [2 2 2 2] [2 3 4].
|
|
*/
|
|
static OffsetNumber
|
|
_bt_findsplitloc(Relation rel,
|
|
Size keysz,
|
|
ScanKey scankey,
|
|
Page page,
|
|
OffsetNumber start,
|
|
OffsetNumber maxoff,
|
|
Size llimit)
|
|
{
|
|
OffsetNumber i;
|
|
OffsetNumber saferight;
|
|
ItemId nxtitemid,
|
|
safeitemid;
|
|
BTItem safeitem,
|
|
nxtitem;
|
|
Size nbytes;
|
|
|
|
if (start >= maxoff)
|
|
elog(FATAL, "btree: cannot split if start (%d) >= maxoff (%d)",
|
|
start, maxoff);
|
|
saferight = start;
|
|
safeitemid = PageGetItemId(page, saferight);
|
|
nbytes = ItemIdGetLength(safeitemid) + sizeof(ItemIdData);
|
|
safeitem = (BTItem) PageGetItem(page, safeitemid);
|
|
|
|
i = OffsetNumberNext(start);
|
|
|
|
while (nbytes < llimit)
|
|
{
|
|
/* check the next item on the page */
|
|
nxtitemid = PageGetItemId(page, i);
|
|
nbytes += (ItemIdGetLength(nxtitemid) + sizeof(ItemIdData));
|
|
nxtitem = (BTItem) PageGetItem(page, nxtitemid);
|
|
|
|
/*
|
|
* Test against last known safe item: if the tuple we're looking
|
|
* at isn't equal to the last safe one we saw, then it's our new
|
|
* safe tuple.
|
|
*/
|
|
if (!_bt_itemcmp(rel, keysz, scankey,
|
|
safeitem, nxtitem, BTEqualStrategyNumber))
|
|
{
|
|
safeitem = nxtitem;
|
|
saferight = i;
|
|
}
|
|
if (i < maxoff)
|
|
i = OffsetNumberNext(i);
|
|
else
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If the chain of dups starts at the beginning of the page and
|
|
* extends past the halfway mark, we can split it in the middle.
|
|
*/
|
|
|
|
if (saferight == start)
|
|
saferight = i;
|
|
|
|
if (saferight == maxoff && (maxoff - start) > 1)
|
|
saferight = start + (maxoff - start) / 2;
|
|
|
|
return saferight;
|
|
}
|
|
|
|
/*
|
|
* _bt_newroot() -- Create a new root page for the index.
|
|
*
|
|
* We've just split the old root page and need to create a new one.
|
|
* In order to do this, we add a new root page to the file, then lock
|
|
* the metadata page and update it. This is guaranteed to be deadlock-
|
|
* free, because all readers release their locks on the metadata page
|
|
* before trying to lock the root, and all writers lock the root before
|
|
* trying to lock the metadata page. We have a write lock on the old
|
|
* root page, so we have not introduced any cycles into the waits-for
|
|
* graph.
|
|
*
|
|
* On entry, lbuf (the old root) and rbuf (its new peer) are write-
|
|
* locked. We don't drop the locks in this routine; that's done by
|
|
* the caller. On exit, a new root page exists with entries for the
|
|
* two new children. The new root page is neither pinned nor locked.
|
|
*/
|
|
static void
|
|
_bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf)
|
|
{
|
|
Buffer rootbuf;
|
|
Page lpage,
|
|
rpage,
|
|
rootpage;
|
|
BlockNumber lbkno,
|
|
rbkno;
|
|
BlockNumber rootbknum;
|
|
BTPageOpaque rootopaque;
|
|
ItemId itemid;
|
|
BTItem item;
|
|
Size itemsz;
|
|
BTItem new_item;
|
|
|
|
/* get a new root page */
|
|
rootbuf = _bt_getbuf(rel, P_NEW, BT_WRITE);
|
|
rootpage = BufferGetPage(rootbuf);
|
|
rootbknum = BufferGetBlockNumber(rootbuf);
|
|
_bt_pageinit(rootpage, BufferGetPageSize(rootbuf));
|
|
|
|
/* set btree special data */
|
|
rootopaque = (BTPageOpaque) PageGetSpecialPointer(rootpage);
|
|
rootopaque->btpo_prev = rootopaque->btpo_next = P_NONE;
|
|
rootopaque->btpo_flags |= BTP_ROOT;
|
|
|
|
/*
|
|
* Insert the internal tuple pointers.
|
|
*/
|
|
|
|
lbkno = BufferGetBlockNumber(lbuf);
|
|
rbkno = BufferGetBlockNumber(rbuf);
|
|
lpage = BufferGetPage(lbuf);
|
|
rpage = BufferGetPage(rbuf);
|
|
|
|
((BTPageOpaque) PageGetSpecialPointer(lpage))->btpo_parent =
|
|
((BTPageOpaque) PageGetSpecialPointer(rpage))->btpo_parent =
|
|
rootbknum;
|
|
|
|
/*
|
|
* step over the high key on the left page while building the left
|
|
* page pointer.
|
|
*/
|
|
itemid = PageGetItemId(lpage, P_FIRSTKEY);
|
|
itemsz = ItemIdGetLength(itemid);
|
|
item = (BTItem) PageGetItem(lpage, itemid);
|
|
new_item = _bt_formitem(&(item->bti_itup));
|
|
ItemPointerSet(&(new_item->bti_itup.t_tid), lbkno, P_HIKEY);
|
|
|
|
/*
|
|
* insert the left page pointer into the new root page. the root page
|
|
* is the rightmost page on its level so the "high key" item is the
|
|
* first data item.
|
|
*/
|
|
if (PageAddItem(rootpage, (Item) new_item, itemsz, P_HIKEY, LP_USED) == InvalidOffsetNumber)
|
|
elog(FATAL, "btree: failed to add leftkey to new root page");
|
|
pfree(new_item);
|
|
|
|
/*
|
|
* the right page is the rightmost page on the second level, so the
|
|
* "high key" item is the first data item on that page as well.
|
|
*/
|
|
itemid = PageGetItemId(rpage, P_HIKEY);
|
|
itemsz = ItemIdGetLength(itemid);
|
|
item = (BTItem) PageGetItem(rpage, itemid);
|
|
new_item = _bt_formitem(&(item->bti_itup));
|
|
ItemPointerSet(&(new_item->bti_itup.t_tid), rbkno, P_HIKEY);
|
|
|
|
/*
|
|
* insert the right page pointer into the new root page.
|
|
*/
|
|
if (PageAddItem(rootpage, (Item) new_item, itemsz, P_FIRSTKEY, LP_USED) == InvalidOffsetNumber)
|
|
elog(FATAL, "btree: failed to add rightkey to new root page");
|
|
pfree(new_item);
|
|
|
|
/* write and let go of the root buffer */
|
|
_bt_wrtbuf(rel, rootbuf);
|
|
|
|
/* update metadata page with new root block number */
|
|
_bt_metaproot(rel, rootbknum, 0);
|
|
|
|
_bt_wrtbuf(rel, lbuf);
|
|
_bt_wrtbuf(rel, rbuf);
|
|
}
|
|
|
|
/*
|
|
* _bt_pgaddtup() -- add a tuple to a particular page in the index.
|
|
*
|
|
* This routine adds the tuple to the page as requested, and keeps the
|
|
* write lock and reference associated with the page's buffer. It is
|
|
* an error to call pgaddtup() without a write lock and reference. If
|
|
* afteritem is non-null, it's the item that we expect our new item
|
|
* to follow. Otherwise, we do a binary search for the correct place
|
|
* and insert the new item there.
|
|
*/
|
|
static OffsetNumber
|
|
_bt_pgaddtup(Relation rel,
|
|
Buffer buf,
|
|
int keysz,
|
|
ScanKey itup_scankey,
|
|
Size itemsize,
|
|
BTItem btitem,
|
|
BTItem afteritem)
|
|
{
|
|
OffsetNumber itup_off;
|
|
OffsetNumber first;
|
|
Page page;
|
|
BTPageOpaque opaque;
|
|
BTItem chkitem;
|
|
|
|
page = BufferGetPage(buf);
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
first = P_RIGHTMOST(opaque) ? P_HIKEY : P_FIRSTKEY;
|
|
|
|
if (afteritem == (BTItem) NULL)
|
|
itup_off = _bt_binsrch(rel, buf, keysz, itup_scankey, BT_INSERTION);
|
|
else
|
|
{
|
|
itup_off = first;
|
|
|
|
do
|
|
{
|
|
chkitem = (BTItem) PageGetItem(page, PageGetItemId(page, itup_off));
|
|
itup_off = OffsetNumberNext(itup_off);
|
|
} while (!BTItemSame(chkitem, afteritem));
|
|
}
|
|
|
|
if (PageAddItem(page, (Item) btitem, itemsize, itup_off, LP_USED) == InvalidOffsetNumber)
|
|
elog(FATAL, "btree: failed to add item to the page");
|
|
|
|
/* write the buffer, but hold our lock */
|
|
_bt_wrtnorelbuf(rel, buf);
|
|
|
|
return itup_off;
|
|
}
|
|
|
|
/*
|
|
* _bt_goesonpg() -- Does a new tuple belong on this page?
|
|
*
|
|
* This is part of the complexity introduced by allowing duplicate
|
|
* keys into the index. The tuple belongs on this page if:
|
|
*
|
|
* + there is no page to the right of this one; or
|
|
* + it is less than the high key on the page; or
|
|
* + the item it is to follow ("afteritem") appears on this
|
|
* page.
|
|
*/
|
|
static bool
|
|
_bt_goesonpg(Relation rel,
|
|
Buffer buf,
|
|
Size keysz,
|
|
ScanKey scankey,
|
|
BTItem afteritem)
|
|
{
|
|
Page page;
|
|
ItemId hikey;
|
|
BTPageOpaque opaque;
|
|
BTItem chkitem;
|
|
OffsetNumber offnum,
|
|
maxoff;
|
|
bool found;
|
|
|
|
page = BufferGetPage(buf);
|
|
|
|
/* no right neighbor? */
|
|
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
if (P_RIGHTMOST(opaque))
|
|
return true;
|
|
|
|
/*
|
|
* this is a non-rightmost page, so it must have a high key item.
|
|
*
|
|
* If the scan key is < the high key (the min key on the next page), then
|
|
* it for sure belongs here.
|
|
*/
|
|
hikey = PageGetItemId(page, P_HIKEY);
|
|
if (_bt_skeycmp(rel, keysz, scankey, page, hikey, BTLessStrategyNumber))
|
|
return true;
|
|
|
|
/*
|
|
* If the scan key is > the high key, then it for sure doesn't belong
|
|
* here.
|
|
*/
|
|
|
|
if (_bt_skeycmp(rel, keysz, scankey, page, hikey, BTGreaterStrategyNumber))
|
|
return false;
|
|
|
|
/*
|
|
* If we have no adjacency information, and the item is equal to the
|
|
* high key on the page (by here it is), then the item does not belong
|
|
* on this page.
|
|
*
|
|
* Now it's not true in all cases. - vadim 06/10/97
|
|
*/
|
|
|
|
if (afteritem == (BTItem) NULL)
|
|
{
|
|
if (opaque->btpo_flags & BTP_LEAF)
|
|
return false;
|
|
if (opaque->btpo_flags & BTP_CHAIN)
|
|
return true;
|
|
if (_bt_skeycmp(rel, keysz, scankey, page,
|
|
PageGetItemId(page, P_FIRSTKEY),
|
|
BTEqualStrategyNumber))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* damn, have to work for it. i hate that. */
|
|
maxoff = PageGetMaxOffsetNumber(page);
|
|
|
|
/*
|
|
* Search the entire page for the afteroid. We need to do this,
|
|
* rather than doing a binary search and starting from there, because
|
|
* if the key we're searching for is the leftmost key in the tree at
|
|
* this level, then a binary search will do the wrong thing. Splits
|
|
* are pretty infrequent, so the cost isn't as bad as it could be.
|
|
*/
|
|
|
|
found = false;
|
|
for (offnum = P_FIRSTKEY;
|
|
offnum <= maxoff;
|
|
offnum = OffsetNumberNext(offnum))
|
|
{
|
|
chkitem = (BTItem) PageGetItem(page, PageGetItemId(page, offnum));
|
|
|
|
if (BTItemSame(chkitem, afteritem))
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
/*
|
|
* _bt_tuplecompare() -- compare two IndexTuples,
|
|
* return -1, 0, or +1
|
|
*
|
|
*/
|
|
int32
|
|
_bt_tuplecompare(Relation rel,
|
|
Size keysz,
|
|
ScanKey scankey,
|
|
IndexTuple tuple1,
|
|
IndexTuple tuple2)
|
|
{
|
|
TupleDesc tupDes;
|
|
int i;
|
|
int32 compare = 0;
|
|
|
|
tupDes = RelationGetDescr(rel);
|
|
|
|
for (i = 1; i <= keysz; i++)
|
|
{
|
|
ScanKey entry = &scankey[i - 1];
|
|
Datum attrDatum1,
|
|
attrDatum2;
|
|
bool isFirstNull,
|
|
isSecondNull;
|
|
|
|
attrDatum1 = index_getattr(tuple1, i, tupDes, &isFirstNull);
|
|
attrDatum2 = index_getattr(tuple2, i, tupDes, &isSecondNull);
|
|
|
|
/* see comments about NULLs handling in btbuild */
|
|
if (isFirstNull) /* attr in tuple1 is NULL */
|
|
{
|
|
if (isSecondNull) /* attr in tuple2 is NULL too */
|
|
compare = 0;
|
|
else
|
|
compare = 1; /* NULL ">" not-NULL */
|
|
}
|
|
else if (isSecondNull) /* attr in tuple1 is NOT_NULL and */
|
|
{ /* attr in tuple2 is NULL */
|
|
compare = -1; /* not-NULL "<" NULL */
|
|
}
|
|
else
|
|
{
|
|
compare = (int32) FMGR_PTR2(&entry->sk_func,
|
|
attrDatum1, attrDatum2);
|
|
}
|
|
|
|
if (compare != 0)
|
|
break; /* done when we find unequal attributes */
|
|
}
|
|
|
|
return compare;
|
|
}
|
|
|
|
/*
|
|
* _bt_itemcmp() -- compare two BTItems using a requested
|
|
* strategy (<, <=, =, >=, >)
|
|
*
|
|
*/
|
|
bool
|
|
_bt_itemcmp(Relation rel,
|
|
Size keysz,
|
|
ScanKey scankey,
|
|
BTItem item1,
|
|
BTItem item2,
|
|
StrategyNumber strat)
|
|
{
|
|
int32 compare;
|
|
|
|
compare = _bt_tuplecompare(rel, keysz, scankey,
|
|
&(item1->bti_itup),
|
|
&(item2->bti_itup));
|
|
|
|
switch (strat)
|
|
{
|
|
case BTLessStrategyNumber:
|
|
return (bool) (compare < 0);
|
|
case BTLessEqualStrategyNumber:
|
|
return (bool) (compare <= 0);
|
|
case BTEqualStrategyNumber:
|
|
return (bool) (compare == 0);
|
|
case BTGreaterEqualStrategyNumber:
|
|
return (bool) (compare >= 0);
|
|
case BTGreaterStrategyNumber:
|
|
return (bool) (compare > 0);
|
|
}
|
|
|
|
elog(ERROR, "_bt_itemcmp: bogus strategy %d", (int) strat);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* _bt_updateitem() -- updates the key of the item identified by the
|
|
* oid with the key of newItem (done in place if
|
|
* possible)
|
|
*
|
|
*/
|
|
static void
|
|
_bt_updateitem(Relation rel,
|
|
Size keysz,
|
|
Buffer buf,
|
|
BTItem oldItem,
|
|
BTItem newItem)
|
|
{
|
|
Page page;
|
|
OffsetNumber maxoff;
|
|
OffsetNumber i;
|
|
ItemPointerData itemPtrData;
|
|
BTItem item;
|
|
IndexTuple oldIndexTuple,
|
|
newIndexTuple;
|
|
int first;
|
|
|
|
page = BufferGetPage(buf);
|
|
maxoff = PageGetMaxOffsetNumber(page);
|
|
|
|
/* locate item on the page */
|
|
first = P_RIGHTMOST((BTPageOpaque) PageGetSpecialPointer(page))
|
|
? P_HIKEY : P_FIRSTKEY;
|
|
i = first;
|
|
do
|
|
{
|
|
item = (BTItem) PageGetItem(page, PageGetItemId(page, i));
|
|
i = OffsetNumberNext(i);
|
|
} while (i <= maxoff && !BTItemSame(item, oldItem));
|
|
|
|
/* this should never happen (in theory) */
|
|
if (!BTItemSame(item, oldItem))
|
|
elog(FATAL, "_bt_getstackbuf was lying!!");
|
|
|
|
/*
|
|
* It's defined by caller (_bt_insertonpg)
|
|
*/
|
|
|
|
/*
|
|
* if(IndexTupleDSize(newItem->bti_itup) >
|
|
* IndexTupleDSize(item->bti_itup)) { elog(NOTICE, "trying to
|
|
* overwrite a smaller value with a bigger one in _bt_updateitem");
|
|
* elog(ERROR, "this is not good."); }
|
|
*/
|
|
|
|
oldIndexTuple = &(item->bti_itup);
|
|
newIndexTuple = &(newItem->bti_itup);
|
|
|
|
/* keep the original item pointer */
|
|
ItemPointerCopy(&(oldIndexTuple->t_tid), &itemPtrData);
|
|
CopyIndexTuple(newIndexTuple, &oldIndexTuple);
|
|
ItemPointerCopy(&itemPtrData, &(oldIndexTuple->t_tid));
|
|
|
|
}
|
|
|
|
/*
|
|
* _bt_isequal - used in _bt_doinsert in check for duplicates.
|
|
*
|
|
* Rule is simple: NOT_NULL not equal NULL, NULL not_equal NULL too.
|
|
*/
|
|
static bool
|
|
_bt_isequal(TupleDesc itupdesc, Page page, OffsetNumber offnum,
|
|
int keysz, ScanKey scankey)
|
|
{
|
|
Datum datum;
|
|
BTItem btitem;
|
|
IndexTuple itup;
|
|
ScanKey entry;
|
|
AttrNumber attno;
|
|
long result;
|
|
int i;
|
|
bool null;
|
|
|
|
btitem = (BTItem) PageGetItem(page, PageGetItemId(page, offnum));
|
|
itup = &(btitem->bti_itup);
|
|
|
|
for (i = 1; i <= keysz; i++)
|
|
{
|
|
entry = &scankey[i - 1];
|
|
attno = entry->sk_attno;
|
|
Assert(attno == i);
|
|
datum = index_getattr(itup, attno, itupdesc, &null);
|
|
|
|
/* NULLs are not equal */
|
|
if (entry->sk_flags & SK_ISNULL || null)
|
|
return false;
|
|
|
|
result = (long) FMGR_PTR2(&entry->sk_func, entry->sk_argument, datum);
|
|
if (result != 0)
|
|
return false;
|
|
}
|
|
|
|
/* by here, the keys are equal */
|
|
return true;
|
|
}
|
|
|
|
#ifdef NOT_USED
|
|
/*
|
|
* _bt_shift - insert btitem on the passed page after shifting page
|
|
* to the right in the tree.
|
|
*
|
|
* NOTE: tested for shifting leftmost page only, having btitem < hikey.
|
|
*/
|
|
static InsertIndexResult
|
|
_bt_shift(Relation rel, Buffer buf, BTStack stack, int keysz,
|
|
ScanKey scankey, BTItem btitem, BTItem hikey)
|
|
{
|
|
InsertIndexResult res;
|
|
int itemsz;
|
|
Page page;
|
|
BlockNumber bknum;
|
|
BTPageOpaque pageop;
|
|
Buffer rbuf;
|
|
Page rpage;
|
|
BTPageOpaque rpageop;
|
|
Buffer pbuf;
|
|
Page ppage;
|
|
BTPageOpaque ppageop;
|
|
Buffer nbuf;
|
|
Page npage;
|
|
BTPageOpaque npageop;
|
|
BlockNumber nbknum;
|
|
BTItem nitem;
|
|
OffsetNumber afteroff;
|
|
|
|
btitem = _bt_formitem(&(btitem->bti_itup));
|
|
hikey = _bt_formitem(&(hikey->bti_itup));
|
|
|
|
page = BufferGetPage(buf);
|
|
|
|
/* grab new page */
|
|
nbuf = _bt_getbuf(rel, P_NEW, BT_WRITE);
|
|
nbknum = BufferGetBlockNumber(nbuf);
|
|
npage = BufferGetPage(nbuf);
|
|
_bt_pageinit(npage, BufferGetPageSize(nbuf));
|
|
npageop = (BTPageOpaque) PageGetSpecialPointer(npage);
|
|
|
|
/* copy content of the passed page */
|
|
memmove((char *) npage, (char *) page, BufferGetPageSize(buf));
|
|
|
|
/* re-init old (passed) page */
|
|
_bt_pageinit(page, BufferGetPageSize(buf));
|
|
pageop = (BTPageOpaque) PageGetSpecialPointer(page);
|
|
|
|
/* init old page opaque */
|
|
pageop->btpo_flags = npageop->btpo_flags; /* restore flags */
|
|
pageop->btpo_flags &= ~BTP_CHAIN;
|
|
if (_bt_itemcmp(rel, keysz, scankey, hikey, btitem, BTEqualStrategyNumber))
|
|
pageop->btpo_flags |= BTP_CHAIN;
|
|
pageop->btpo_prev = npageop->btpo_prev; /* restore prev */
|
|
pageop->btpo_next = nbknum; /* next points to the new page */
|
|
pageop->btpo_parent = npageop->btpo_parent;
|
|
|
|
/* init shifted page opaque */
|
|
npageop->btpo_prev = bknum = BufferGetBlockNumber(buf);
|
|
|
|
/* shifted page is ok, populate old page */
|
|
|
|
/* add passed hikey */
|
|
itemsz = IndexTupleDSize(hikey->bti_itup)
|
|
+ (sizeof(BTItemData) - sizeof(IndexTupleData));
|
|
itemsz = MAXALIGN(itemsz);
|
|
if (PageAddItem(page, (Item) hikey, itemsz, P_HIKEY, LP_USED) == InvalidOffsetNumber)
|
|
elog(FATAL, "btree: failed to add hikey in _bt_shift");
|
|
pfree(hikey);
|
|
|
|
/* add btitem */
|
|
itemsz = IndexTupleDSize(btitem->bti_itup)
|
|
+ (sizeof(BTItemData) - sizeof(IndexTupleData));
|
|
itemsz = MAXALIGN(itemsz);
|
|
if (PageAddItem(page, (Item) btitem, itemsz, P_FIRSTKEY, LP_USED) == InvalidOffsetNumber)
|
|
elog(FATAL, "btree: failed to add firstkey in _bt_shift");
|
|
pfree(btitem);
|
|
nitem = (BTItem) PageGetItem(page, PageGetItemId(page, P_FIRSTKEY));
|
|
btitem = _bt_formitem(&(nitem->bti_itup));
|
|
ItemPointerSet(&(btitem->bti_itup.t_tid), bknum, P_HIKEY);
|
|
|
|
/* ok, write them out */
|
|
_bt_wrtnorelbuf(rel, nbuf);
|
|
_bt_wrtnorelbuf(rel, buf);
|
|
|
|
/* fix btpo_prev on right sibling of old page */
|
|
if (!P_RIGHTMOST(npageop))
|
|
{
|
|
rbuf = _bt_getbuf(rel, npageop->btpo_next, BT_WRITE);
|
|
rpage = BufferGetPage(rbuf);
|
|
rpageop = (BTPageOpaque) PageGetSpecialPointer(rpage);
|
|
rpageop->btpo_prev = nbknum;
|
|
_bt_wrtbuf(rel, rbuf);
|
|
}
|
|
|
|
/* get parent pointing to the old page */
|
|
ItemPointerSet(&(stack->bts_btitem->bti_itup.t_tid),
|
|
bknum, P_HIKEY);
|
|
pbuf = _bt_getstackbuf(rel, stack, BT_WRITE);
|
|
ppage = BufferGetPage(pbuf);
|
|
ppageop = (BTPageOpaque) PageGetSpecialPointer(ppage);
|
|
|
|
_bt_relbuf(rel, nbuf, BT_WRITE);
|
|
_bt_relbuf(rel, buf, BT_WRITE);
|
|
|
|
/* re-set parent' pointer - we shifted our page to the right ! */
|
|
nitem = (BTItem) PageGetItem(ppage,
|
|
PageGetItemId(ppage, stack->bts_offset));
|
|
ItemPointerSet(&(nitem->bti_itup.t_tid), nbknum, P_HIKEY);
|
|
ItemPointerSet(&(stack->bts_btitem->bti_itup.t_tid), nbknum, P_HIKEY);
|
|
_bt_wrtnorelbuf(rel, pbuf);
|
|
|
|
/*
|
|
* Now we want insert into the parent pointer to our old page. It has
|
|
* to be inserted before the pointer to new page. You may get problems
|
|
* here (in the _bt_goesonpg and/or _bt_pgaddtup), but may be not - I
|
|
* don't know. It works if old page is leftmost (nitem is NULL) and
|
|
* btitem < hikey and it's all what we need currently. - vadim
|
|
* 05/30/97
|
|
*/
|
|
nitem = NULL;
|
|
afteroff = P_FIRSTKEY;
|
|
if (!P_RIGHTMOST(ppageop))
|
|
afteroff = OffsetNumberNext(afteroff);
|
|
if (stack->bts_offset >= afteroff)
|
|
{
|
|
afteroff = OffsetNumberPrev(stack->bts_offset);
|
|
nitem = (BTItem) PageGetItem(ppage, PageGetItemId(ppage, afteroff));
|
|
nitem = _bt_formitem(&(nitem->bti_itup));
|
|
}
|
|
res = _bt_insertonpg(rel, pbuf, stack->bts_parent,
|
|
keysz, scankey, btitem, nitem);
|
|
pfree(btitem);
|
|
|
|
ItemPointerSet(&(res->pointerData), nbknum, P_HIKEY);
|
|
|
|
return res;
|
|
}
|
|
|
|
#endif
|