/*-------------------------------------------------------------------------
 *
 * rumentrypage.c
 *	  page utilities routines for the postgres inverted index access method.
 *
 * Portions Copyright (c) Microsoft Corporation.  All rights reserved.
 * Portions Copyright (c) 2015-2022, Postgres Professional
 * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "pg_documentdb_rum.h"

/* How to prepare the entry page for insertion */
typedef enum EntryPrepareMode
{
	/* Force delete the existing tuple */
	EntryPrepareMode_ForceDelete = 0,

	/* Allow replacing the existing tuple if it exists and can fit */
	EntryPrepareMode_AllowReplace = 1,

	/* Delete the existing tuple, but we don't need to reclaim space */
	EntryPrepareMode_Delete = 2
} EntryPrepareMode;


/*
 * Form a tuple for entry tree.
 *
 * If the tuple would be too big to be stored, function throws a suitable
 * error if errorTooBig is true, or returns NULL if errorTooBig is false.
 *
 * See src/backend/access/gin/README for a description of the index tuple
 * format that is being built here.  We build on the assumption that we
 * are making a leaf-level key entry containing a posting list of nipd items.
 * If the caller is actually trying to make a posting-tree entry, non-leaf
 * entry, or pending-list entry, it should pass nipd = 0 and then overwrite
 * the t_tid fields as necessary.  In any case, items can be NULL to skip
 * copying any itempointers into the posting list; the caller is responsible
 * for filling the posting list afterwards, if items = NULL and nipd > 0.
 */
IndexTuple
RumFormTuple(RumState *rumstate,
			 OffsetNumber attnum, Datum key, RumNullCategory category,
			 RumItem *items, uint32 nipd, bool errorTooBig)
{
	Datum datums[3];
	bool isnull[3];
	IndexTuple itup;
	uint32 newsize;
	int i;
	ItemPointerData nullItemPointer = { { 0, 0 }, 0 };

	/* Build the basic tuple: optional column number, plus key datum */
	if (rumstate->oneCol)
	{
		datums[0] = key;
		isnull[0] = (category != RUM_CAT_NORM_KEY);
		isnull[1] = true;
	}
	else
	{
		datums[0] = UInt16GetDatum(attnum);
		isnull[0] = false;
		datums[1] = key;
		isnull[1] = (category != RUM_CAT_NORM_KEY);
		isnull[2] = true;
	}

	itup = index_form_tuple(rumstate->tupdesc[attnum - 1], datums, isnull);

	/*
	 * Determine and store offset to the posting list, making sure there is
	 * room for the category byte if needed.
	 *
	 * Note: because index_form_tuple MAXALIGNs the tuple size, there may well
	 * be some wasted pad space.  Is it worth recomputing the data length to
	 * prevent that?  That would also allow us to Assert that the real data
	 * doesn't overlap the RumNullCategory byte, which this code currently
	 * takes on faith.
	 */
	newsize = IndexTupleSize(itup);

	RumSetPostingOffset(itup, newsize);

	RumSetNPosting(itup, nipd);

	/*
	 * Add space needed for posting list, if any.  Then check that the tuple
	 * won't be too big to store.
	 */

	if (nipd > 0)
	{
		newsize = rumCheckPlaceToDataPageLeaf(attnum, &items[0],
											  &nullItemPointer,
											  rumstate, newsize);
		for (i = 1; i < nipd; i++)
		{
			newsize = rumCheckPlaceToDataPageLeaf(attnum, &items[i],
												  &items[i - 1].iptr,
												  rumstate, newsize);
		}
	}


	if (category != RUM_CAT_NORM_KEY)
	{
		Assert(IndexTupleHasNulls(itup));
		newsize = newsize + sizeof(RumNullCategory);
	}
	newsize = MAXALIGN(newsize);

	if (newsize > RumMaxItemSize)
	{
		if (errorTooBig)
		{
			ereport(ERROR,
					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
					 errmsg("index row size %lu exceeds maximum %lu for index \"%s\"",
							(unsigned long) newsize,
							(unsigned long) RumMaxItemSize,
							RelationGetRelationName(rumstate->index))));
		}
		pfree(itup);
		return NULL;
	}

	/*
	 * Resize tuple if needed
	 */
	if (newsize != IndexTupleSize(itup))
	{
		itup = repalloc(itup, newsize);

		memset((char *) itup + IndexTupleSize(itup),
			   0, newsize - IndexTupleSize(itup));

		/* set new size in tuple header */
		itup->t_info &= ~INDEX_SIZE_MASK;
		itup->t_info |= newsize;
	}

	/*
	 * Copy in the posting list, if provided
	 */
	if (nipd > 0)
	{
		char *ptr = RumGetPosting(itup);

		ptr = rumPlaceToDataPageLeaf(ptr, attnum, &items[0],
									 &nullItemPointer, rumstate);
		for (i = 1; i < nipd; i++)
		{
			ptr = rumPlaceToDataPageLeaf(ptr, attnum, &items[i],
										 &items[i - 1].iptr, rumstate);
		}

		Assert(MAXALIGN((ptr - ((char *) itup)) +
						((category == RUM_CAT_NORM_KEY) ? 0 : sizeof(RumNullCategory))) ==
			   newsize);
	}

	/*
	 * Insert category byte, if needed
	 */
	if (category != RUM_CAT_NORM_KEY)
	{
		Assert(IndexTupleHasNulls(itup));
		RumSetNullCategory(itup, category);
	}

	return itup;
}


/*
 * Read item pointers with additional information from leaf data page.
 * Information is stored in the same manner as in leaf data pages.
 */
void
rumReadTuple(RumState *rumstate, OffsetNumber attnum,
			 IndexTuple itup, RumItem *items, bool copyAddInfo)
{
	Pointer ptr = RumGetPosting(itup);
	RumItem item;
	int nipd = RumGetNPosting(itup),
		i;
	RumItemSetMin(&item);

	InitBlockNumberIncr(blockNumberIncr, (&item.iptr));
	for (i = 0; i < nipd; i++)
	{
		ptr = rumDataPageLeafReadWithBlockNumberIncr(ptr, attnum, &item, copyAddInfo,
													 rumstate, &blockNumberIncr);
		items[i] = item;
	}
}


/*
 * Read only item pointers from leaf data page.
 * Information is stored in the same manner as in leaf data pages.
 */
void
rumReadTuplePointers(RumState *rumstate, OffsetNumber attnum,
					 IndexTuple itup, ItemPointerData *ipd)
{
	Pointer ptr = RumGetPosting(itup);
	int nipd = RumGetNPosting(itup),
		i;
	RumItem item;

	ItemPointerSetMin(&item.iptr);
	for (i = 0; i < nipd; i++)
	{
		ptr = rumDataPageLeafReadPointer(ptr, attnum, &item, rumstate);
		ipd[i] = item.iptr;
	}
}


/*
 * Form a non-leaf entry tuple by copying the key data from the given tuple,
 * which can be either a leaf or non-leaf entry tuple.
 *
 * Any posting list in the source tuple is not copied.  The specified child
 * block number is inserted into t_tid.
 */
static IndexTuple
RumFormInteriorTuple(RumBtree btree, IndexTuple itup, Page page,
					 BlockNumber childblk)
{
	IndexTuple nitup;
	RumNullCategory category;

	if (RumPageIsLeaf(page) && !RumIsPostingTree(itup))
	{
		/* Tuple contains a posting list, just copy stuff before that */
		uint32 origsize = RumGetPostingOffset(itup);

		origsize = MAXALIGN(origsize);
		nitup = (IndexTuple) palloc(origsize);
		memcpy(nitup, itup, origsize);

		/* ... be sure to fix the size header field ... */
		nitup->t_info &= ~INDEX_SIZE_MASK;
		nitup->t_info |= origsize;
	}
	else
	{
		/* Copy the tuple as-is */
		nitup = (IndexTuple) palloc(IndexTupleSize(itup));
		memcpy(nitup, itup, IndexTupleSize(itup));
	}

	/* Now insert the correct downlink */
	RumSetDownlink(nitup, childblk);

	rumtuple_get_key(btree->rumstate, itup, &category);
	if (category != RUM_CAT_NORM_KEY)
	{
		Assert(IndexTupleHasNulls(itup));
		nitup->t_info |= INDEX_NULL_MASK;
		RumSetNullCategory(nitup, category);
	}

	return nitup;
}


/*
 * Entry tree is a "static", ie tuple never deletes from it,
 * so we don't use right bound, we use rightmost key instead.
 */
IndexTuple
rumEntryGetRightMostTuple(Page page)
{
	OffsetNumber maxoff = PageGetMaxOffsetNumber(page);

	Assert(maxoff != InvalidOffsetNumber);

	return (IndexTuple) PageGetItem(page, PageGetItemId(page, maxoff));
}


bool
entryIsMoveRight(RumBtree btree, Page page)
{
	IndexTuple itup;
	OffsetNumber attnum;
	Datum key;
	RumNullCategory category;

	if (RumPageRightMost(page))
	{
		return false;
	}

	if (RumPageIsHalfDead(page))
	{
		/* If on a half dead page, always move right */
		return true;
	}

	itup = rumEntryGetRightMostTuple(page);
	attnum = rumtuple_get_attrnum(btree->rumstate, itup);
	key = rumtuple_get_key(btree->rumstate, itup, &category);

	if (rumCompareAttEntries(btree->rumstate,
							 btree->entryAttnum, btree->entryKey, btree->entryCategory,
							 attnum, key, category) > 0)
	{
		return true;
	}

	return false;
}


/*
 * Find correct tuple in non-leaf page. It supposed that
 * page correctly chosen and searching value SHOULD be on page
 */
static BlockNumber
entryLocateEntry(RumBtree btree, RumBtreeStack *stack)
{
	OffsetNumber low,
				 high,
				 maxoff;
	IndexTuple itup = NULL;
	int result;
	Page page = BufferGetPage(stack->buffer);

	Assert(!RumPageIsLeaf(page));
	Assert(!RumPageIsData(page));

	if (btree->fullScan)
	{
		stack->off = FirstOffsetNumber;
		stack->predictNumber *= PageGetMaxOffsetNumber(page);
		return btree->getLeftMostPage(btree, page);
	}

	low = FirstOffsetNumber;
	maxoff = high = PageGetMaxOffsetNumber(page);
	Assert(high >= low);

	high++;

	while (high > low)
	{
		OffsetNumber mid = low + ((high - low) / 2);

		if (mid == maxoff && RumPageRightMost(page))
		{
			/* Right infinity */
			result = -1;
		}
		else
		{
			OffsetNumber attnum;
			Datum key;
			RumNullCategory category;

			itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid));
			attnum = rumtuple_get_attrnum(btree->rumstate, itup);
			key = rumtuple_get_key(btree->rumstate, itup, &category);
			result = rumCompareAttEntries(btree->rumstate,
										  btree->entryAttnum,
										  btree->entryKey,
										  btree->entryCategory,
										  attnum, key, category);
		}

		if (result == 0)
		{
			stack->off = mid;
			Assert(RumGetDownlink(itup) != RUM_ROOT_BLKNO);
			return RumGetDownlink(itup);
		}
		else if (result > 0)
		{
			low = mid + 1;
		}
		else
		{
			high = mid;
		}
	}

	Assert(high >= FirstOffsetNumber && high <= maxoff);

	stack->off = high;
	itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, high));
	Assert(RumGetDownlink(itup) != RUM_ROOT_BLKNO);
	return RumGetDownlink(itup);
}


/*
 * Searches correct position for value on leaf page.
 * Page should be correctly chosen.
 * Returns true if value found on page.
 */
static bool
entryLocateLeafEntry(RumBtree btree, RumBtreeStack *stack)
{
	Page page = BufferGetPage(stack->buffer);
	OffsetNumber low,
				 high;

	Assert(RumPageIsLeaf(page));
	Assert(!RumPageIsData(page));

	if (btree->fullScan)
	{
		stack->off = FirstOffsetNumber;
		return true;
	}

	low = FirstOffsetNumber;
	high = PageGetMaxOffsetNumber(page);

	return entryLocateLeafEntryBounds(btree, page, low, high, &stack->off);
}


bool
entryLocateLeafEntryBounds(RumBtree btree, Page page,
						   OffsetNumber low, OffsetNumber high,
						   OffsetNumber *targetOffset)
{
	Assert(!RumPageIsData(page));

	if (high < low)
	{
		*targetOffset = low;
		return false;
	}

	high++;

	while (high > low)
	{
		OffsetNumber mid = low + ((high - low) / 2);
		IndexTuple itup;
		OffsetNumber attnum;
		Datum key;
		RumNullCategory category;
		int result;

		itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid));
		attnum = rumtuple_get_attrnum(btree->rumstate, itup);
		key = rumtuple_get_key(btree->rumstate, itup, &category);
		result = rumCompareAttEntries(btree->rumstate,
									  btree->entryAttnum,
									  btree->entryKey,
									  btree->entryCategory,
									  attnum, key, category);
		if (result == 0)
		{
			*targetOffset = mid;
			return true;
		}
		else if (result > 0)
		{
			low = mid + 1;
		}
		else
		{
			high = mid;
		}
	}

	*targetOffset = high;
	return false;
}


static OffsetNumber
entryFindChildPtr(RumBtree btree, Page page, BlockNumber blkno, OffsetNumber storedOff)
{
	OffsetNumber i,
				 maxoff = PageGetMaxOffsetNumber(page);
	IndexTuple itup;

	Assert(!RumPageIsLeaf(page));
	Assert(!RumPageIsData(page));

	/* if page isn't changed, we returns storedOff */
	if (storedOff >= FirstOffsetNumber && storedOff <= maxoff)
	{
		itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, storedOff));
		if (RumGetDownlink(itup) == blkno)
		{
			return storedOff;
		}

		/*
		 * we hope, that needed pointer goes to right. It's true if there
		 * wasn't a deletion
		 */
		for (i = storedOff + 1; i <= maxoff; i++)
		{
			itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
			if (RumGetDownlink(itup) == blkno)
			{
				return i;
			}
		}
		maxoff = storedOff - 1;
	}

	/* last chance */
	for (i = FirstOffsetNumber; i <= maxoff; i++)
	{
		itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
		if (RumGetDownlink(itup) == blkno)
		{
			return i;
		}
	}

	return InvalidOffsetNumber;
}


static BlockNumber
entryGetLeftMostPage(RumBtree btree, Page page)
{
	IndexTuple itup;

	Assert(!RumPageIsLeaf(page));
	Assert(!RumPageIsData(page));
	Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);

	itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
	return RumGetDownlink(itup);
}


static bool
entryIsEnoughSpace(RumBtree btree, Buffer buf, OffsetNumber off)
{
	Size itupsz = 0;
	Page page = BufferGetPage(buf);

	Assert(btree->entry);
	Assert(!RumPageIsData(page));

	if (btree->isDelete)
	{
		IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off));

		itupsz = MAXALIGN(IndexTupleSize(itup)) + sizeof(ItemIdData);
	}

	if (PageGetFreeSpace(page) + itupsz >= MAXALIGN(IndexTupleSize(btree->entry)) +
		sizeof(ItemIdData))
	{
		return true;
	}

	return false;
}


/*
 * Delete tuple on leaf page if tuples existed and we
 * should update it, update old child blkno to new right page
 * if child split occurred
 */
static BlockNumber
entryPreparePage(RumBtree btree, Page page, OffsetNumber off,
				 EntryPrepareMode prepareMode,
				 bool *doReplace, bool *needsOverwrite)
{
	BlockNumber ret = InvalidBlockNumber;

	Assert(btree->entry);
	Assert(!RumPageIsData(page));

	*doReplace = false;
	if (btree->isDelete)
	{
		Assert(RumPageIsLeaf(page));
		if (RumAllowReplaceOnInsertTuple && prepareMode != EntryPrepareMode_ForceDelete)
		{
			ItemId currentItemId = PageGetItemId(page, off);
			if (!ItemIdIsNormal(currentItemId))
			{
				/* LP_DEAD entries - needs to replace and rewrite and revive */
				btree->isDelete = true;
				PageIndexTupleDelete(page, off);
			}
			else if (prepareMode != EntryPrepareMode_AllowReplace)
			{
				/* Since we're just about to add PageAddItem - skip the compact on this delete */
				btree->isDelete = true;
				PageIndexTupleDeleteNoCompact(page, off);
				*needsOverwrite = true;
			}
			else
			{
				IndexTuple itup = (IndexTuple) PageGetItem(page, currentItemId);
				if (MAXALIGN(IndexTupleSize(itup)) >= MAXALIGN(IndexTupleSize(
																   btree->entry)))
				{
					*doReplace = true;
				}
				else
				{
					/* Since we're just about to add PageAddItem - skip the compact on this delete */
					btree->isDelete = true;
					PageIndexTupleDeleteNoCompact(page, off);
					*needsOverwrite = true;
				}
			}
		}
		else
		{
			PageIndexTupleDelete(page, off);
		}
	}

	if (!RumPageIsLeaf(page) && btree->rightblkno != InvalidBlockNumber)
	{
		IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off));

		RumSetDownlink(itup, btree->rightblkno);
		ret = btree->rightblkno;
	}

	btree->rightblkno = InvalidBlockNumber;

	return ret;
}


/*
 * Place tuple on page and fills WAL record
 */
static bool
entryPlaceToPage(RumBtree btree, Page page, OffsetNumber off,
				 bool requireWalFromPlaceToPage)
{
	OffsetNumber placed;

	/* Don't do a replace if we're writing custom WAL - this ensures that
	 * the physical bits layout is identical between the primary and standby.
	 */
	EntryPrepareMode prepareMode = EntryPrepareMode_ForceDelete;
	bool needsOverwrite = false;
	bool doReplace = false;

	if (RumPageIsLeaf(page))
	{
		prepareMode = requireWalFromPlaceToPage ? EntryPrepareMode_Delete :
					  EntryPrepareMode_AllowReplace;
	}

	entryPreparePage(btree, page, off, prepareMode, &doReplace, &needsOverwrite);

	if (doReplace)
	{
		Assert(!needsOverwrite);
		if (!PageIndexTupleOverwrite(page, off, (Item) btree->entry, IndexTupleSize(
										 btree->entry)))
		{
			elog(ERROR, "failed to replace index tuple in tree in \"%s\" offset %d",
				 RelationGetRelationName(btree->index), off);
		}
	}
	else
	{
		placed = PageAddItem(page, (Item) btree->entry, IndexTupleSize(btree->entry), off,
							 needsOverwrite, false);
		if (placed != off)
		{
			elog(ERROR, "failed to add item to index page in \"%s\" offset %d",
				 RelationGetRelationName(btree->index), off);
		}
	}

	Assert(ItemIdIsNormal(PageGetItemId(page, off)));

	if (requireWalFromPlaceToPage)
	{
		/* In the standby we need to redo for delete + add for both delete and replace */
		WriteInsertEntryWalRecord(btree->isDelete, off, btree->entry);
	}

	btree->entry = NULL;
	return requireWalFromPlaceToPage;
}


/*
 * Place tuple and split page, original buffer(lbuf) leaves untouched,
 * returns shadow page of lbuf filled new data.
 * Tuples are distributed between pages by equal size on its, not
 * an equal number!
 */
static Page
entrySplitPage(RumBtree btree, Buffer lbuf, Buffer rbuf,
			   Page lPage, Page rPage, OffsetNumber off)
{
	OffsetNumber i, maxoff, maxoffOrig;
	Size totalsize = 0, splitPointSize = 0;
	Size lsize = 0,
		 size;
	char *ptr;
	IndexTuple itup,
			   leftrightmost = NULL;
	Page page;
	Page newlPage = PageGetTempPageCopy(lPage);
	Size pageSize = PageGetPageSize(newlPage);

	/*
	 * Must have tupstore MAXALIGNed to use PG macros to access data in
	 * it. Should not rely on compiler alignment preferences to avoid
	 * tupstore overflow related to PG in-memory page items alignment
	 * inside rumDataPageLeafRead() or elsewhere.
	 */
	static char tupstoreStorage[2 * BLCKSZ + MAXIMUM_ALIGNOF];
	char *tupstore = (char *) MAXALIGN(tupstoreStorage);

	bool needsOverwrite = false;
	bool doReplace = false;
	entryPreparePage(btree, newlPage, off, EntryPrepareMode_ForceDelete, &doReplace,
					 &needsOverwrite);

	Assert(!needsOverwrite && !doReplace);
	maxoff = PageGetMaxOffsetNumber(newlPage);
	ptr = tupstore;

	for (i = FirstOffsetNumber; i <= maxoff; i++)
	{
		if (i == off)
		{
			size = MAXALIGN(IndexTupleSize(btree->entry));
			memcpy(ptr, btree->entry, size);
			ptr += size;
			totalsize += size + sizeof(ItemIdData);
		}

		itup = (IndexTuple) PageGetItem(newlPage, PageGetItemId(newlPage, i));
		size = MAXALIGN(IndexTupleSize(itup));
		memcpy(ptr, itup, size);
		ptr += size;
		totalsize += size + sizeof(ItemIdData);
	}

	if (off == maxoff + 1)
	{
		size = MAXALIGN(IndexTupleSize(btree->entry));
		memcpy(ptr, btree->entry, size);
		totalsize += size + sizeof(ItemIdData);
	}

	RumInitPage(rPage, RumPageGetOpaque(newlPage)->flags, pageSize);
	RumInitPage(newlPage, RumPageGetOpaque(rPage)->flags, pageSize);

	if (RumEnableNewBulkDelete)
	{
		RumPageGetCycleId(newlPage) = rum_vacuum_get_cycleId(btree->index);
		RumPageGetCycleId(rPage) = RumPageGetCycleId(newlPage);
	}

	/* By default we split in half by size */
	splitPointSize = totalsize / 2;

	/* For now we consider split scenarios on tail insertions of the leaf to be
	 * the case where we consider fillfactor. This can be improved in the future.
	 */
	if (RumEnablePageFillFactor && btree->rumstate->fillFactor != 50 &&
		RumPageIsLeaf(lPage) && RumPageRightMost(lPage))
	{
		splitPointSize = totalsize * btree->rumstate->fillFactor / 100;
	}

	ptr = tupstore;
	maxoffOrig = maxoff;
	maxoff++;
	lsize = 0;
	page = newlPage;
	for (i = FirstOffsetNumber; i <= maxoff; i++)
	{
		PG_USED_FOR_ASSERTS_ONLY OffsetNumber writtenoffset = InvalidOffsetNumber;
		itup = (IndexTuple) ptr;

		/*
		 * If the size exceeds the split point, move to the new page.
		 * Don't go over the original max on the left page since we know that the sum total
		 * of the original offset + the new item exceeds the size of 1 page.
		 */
		if (lsize > splitPointSize || i > maxoffOrig)
		{
			page = rPage;
		}
		else
		{
			leftrightmost = itup;
			lsize += MAXALIGN(IndexTupleSize(itup)) + sizeof(ItemIdData);
		}

		if ((writtenoffset = PageAddItem(page, (Item) itup, IndexTupleSize(itup),
										 InvalidOffsetNumber,
										 false, false)) == InvalidOffsetNumber)
		{
			elog(ERROR, "failed to add item to index page in \"%s\"",
				 RelationGetRelationName(btree->index));
		}

		Assert(ItemIdIsNormal(PageGetItemId(page, writtenoffset)));
		ptr += MAXALIGN(IndexTupleSize(itup));
	}

	btree->entry = RumFormInteriorTuple(btree, leftrightmost, newlPage,
										BufferGetBlockNumber(lbuf));

	btree->rightblkno = BufferGetBlockNumber(rbuf);

	return newlPage;
}


/*
 * return newly allocated rightmost tuple
 */
IndexTuple
rumPageGetLinkItup(RumBtree btree, Buffer buf, Page page)
{
	IndexTuple itup,
			   nitup;

	itup = rumEntryGetRightMostTuple(page);
	nitup = RumFormInteriorTuple(btree, itup, page, BufferGetBlockNumber(buf));

	return nitup;
}


/*
 * Fills new root by rightest values from child.
 * Also called from rumxlog, should not use btree
 */
void
rumEntryFillRoot(RumBtree btree, Buffer root, Buffer lbuf, Buffer rbuf,
				 Page page, Page lpage, Page rpage)
{
	IndexTuple itup;

	itup = rumPageGetLinkItup(btree, lbuf, lpage);
	if (PageAddItem(page, (Item) itup, IndexTupleSize(itup), InvalidOffsetNumber, false,
					false) == InvalidOffsetNumber)
	{
		elog(ERROR, "failed to add item to index root page");
	}
	pfree(itup);

	itup = rumPageGetLinkItup(btree, rbuf, rpage);
	if (PageAddItem(page, (Item) itup, IndexTupleSize(itup), InvalidOffsetNumber, false,
					false) == InvalidOffsetNumber)
	{
		elog(ERROR, "failed to add item to index root page");
	}
	pfree(itup);
}


static void
rumEntryFillBtreeForIncompleteSplit(RumBtree btree, RumBtreeStack *stack, Buffer buffer)
{
	IndexTuple itup;
	Page page = BufferGetPage(buffer);

	/* Fill rightblkno to ensure we're tracking the split page */
	btree->rightblkno = RumPageGetOpaque(page)->rightlink;

	itup = rumEntryGetRightMostTuple(page);
	btree->entry = RumFormInteriorTuple(btree, itup, page, BufferGetBlockNumber(buffer));
}


/*
 * Set up RumBtree for entry page access
 *
 * Note: during WAL recovery, there may be no valid data in rumstate
 * other than a faked-up Relation pointer; the key datum is bogus too.
 */
void
rumPrepareEntryScan(RumBtree btree, OffsetNumber attnum,
					Datum key, RumNullCategory category,
					RumState *rumstate)
{
	memset(btree, 0, sizeof(RumBtreeData));

	btree->index = rumstate->index;
	btree->rumstate = rumstate;

	btree->findChildPage = entryLocateEntry;
	btree->isMoveRight = entryIsMoveRight;
	btree->findItem = entryLocateLeafEntry;
	btree->findChildPtr = entryFindChildPtr;
	btree->getLeftMostPage = entryGetLeftMostPage;
	btree->isEnoughSpace = entryIsEnoughSpace;
	btree->placeToPage = entryPlaceToPage;
	btree->splitPage = entrySplitPage;
	btree->fillRoot = rumEntryFillRoot;
	btree->fillBtreeForIncompleteSplit = rumEntryFillBtreeForIncompleteSplit;

	btree->isData = false;
	btree->searchMode = false;
	btree->fullScan = false;

	btree->entryAttnum = attnum;
	btree->entryKey = key;
	btree->entryCategory = category;
	btree->isDelete = false;
}
