//------------------------------------------------------------------------------
// Desc:	This class handles all of operations on a given B-Tree.
//
// Tabs:	3
//
//		Copyright (c) 2002-2006 Novell, Inc. All Rights Reserved.
//
//		This program is free software; you can redistribute it and/or
//		modify it under the terms of version 2 of the GNU General Public
//		License as published by the Free Software Foundation.
//
//		This program is distributed in the hope that it will be useful,
//		but WITHOUT ANY WARRANTY; without even the implied warranty of
//		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//		GNU General Public License for more details.
//
//		You should have received a copy of the GNU General Public License
//		along with this program; if not, contact Novell, Inc.
//
//		To contact Novell about this file by physical or electronic mail,
//		you may find current contact information at www.novell.com
//
// $Id: f_btree.cpp 3111 2006-01-19 13:10:50 -0700 (Thu, 19 Jan 2006) dsanders $
//------------------------------------------------------------------------------

#include "flaimsys.h"

FSTATIC FLMUINT btGetEntryDataLength(
	FLMBYTE *			pucEntry,
	const FLMBYTE **	ppucDataRV,
	FLMUINT *			puiOADataLengthRV,
	FLMBOOL *			pbDOBlockRV);

FSTATIC RCODE btGetEntryData(
	FLMBYTE *		pucEntry,
	FLMBYTE *		pucBufferRV,
	FLMUINT			uiBufferSize,
	FLMUINT *		puiLenDataRV);

/***************************************************************************
Desc:	Constructor
****************************************************************************/
F_Btree::F_Btree( void)
{
	m_bOpened = FALSE;
	m_pStack = NULL;
	m_uiStackLevels = 0;
	m_uiRootLevel = 0;
	f_memset(m_Stack, 0, sizeof(m_Stack));
	m_pLFile = NULL;
	m_pDb = NULL;
	m_bTempDb = FALSE;
	m_pucTempBlk = NULL;
	m_pucTempDefragBlk = NULL;
	m_bCounts = FALSE;
	m_bData = TRUE;		// Default
	m_bSetupForRead = FALSE;
	m_bSetupForWrite = FALSE;
	m_bSetupForReplace = FALSE;
	m_uiBlockSize = 0;
	m_uiDefragThreshold = 0;
	m_uiOverflowThreshold = 0;
	m_pReplaceInfo = NULL;
	m_pReplaceStruct = NULL;
	m_uiReplaceLevels = 0;
	m_ui64CurrTransID = 0;
	m_ui64LastBlkTransId = 0;
	m_ui64PrimaryBlkTransId = 0;
	m_uiBlkChangeCnt = 0;
	m_ui64LowTransId = FLM_MAX_UINT64;
	m_bMostCurrent = FALSE;
	m_uiDataLength = 0;
	m_uiPrimaryDataLen = 0;
	m_uiOADataLength = 0;
	m_uiDataRemaining = 0;
	m_uiOADataRemaining = 0;
	m_uiOffsetAtStart = 0;
	m_bDataOnlyBlock = FALSE;
	m_bOrigInDOBlocks = FALSE;
	m_ui32PrimaryBlkAddr = 0;
	m_uiPrimaryOffset = 0;
	m_ui32DOBlkAddr = 0;
	m_ui32CurBlkAddr = 0;
	m_uiCurOffset = 0;
	m_pucDataPtr = NULL;
	m_bFirstRead = FALSE;
	m_pSCache = NULL;
	m_pBlkHdr = NULL;
	m_uiSearchLevel = BH_MAX_LEVELS;
	m_pNext = NULL;
	m_pCompare = NULL;
}

/***************************************************************************
Desc:	Destructor
****************************************************************************/
F_Btree::~F_Btree( void)
{
	if ( m_bOpened)
	{
		btClose();
	}
}

/***************************************************************************
Desc: Function to create a new (empty) B-Tree.  To do this, we create the
		root block.  Upon return, the Root block address and the block address
		will be set in the LFile.
****************************************************************************/
RCODE F_Btree::btCreate(
	F_Db *					pDb,					// In
	LFILE *					pLFile,				// In/Out
	FLMBOOL					bCounts,				// In
	FLMBOOL					bData					// In
	)
{
	RCODE						rc = NE_XFLM_OK;
	F_CachedBlock *		pSCache = NULL;
	F_BTREE_BLK_HDR *		pBlkHdr = NULL;
	FLMUINT16 *				pui16Offset;
	FLMBYTE *				pucEntry;
	FLMBYTE					ucLEMEntry[ 3];
	FLMUINT					uiFlags = 0;
	FLMUINT					uiLEMSize;

	// We can't create a new Btree if we have already been initialized.
	
	if (m_bOpened)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	// Verify that we are in an update transaction.
	if (pDb->m_eTransType != XFLM_UPDATE_TRANS && !pDb->m_pDatabase->m_bTempDb)
	{
		rc = RC_SET_AND_ASSERT( pDb->m_eTransType == XFLM_NO_TRANS
								? NE_XFLM_NO_TRANS_ACTIVE
								: NE_XFLM_ILLEGAL_TRANS_OP);
		goto Exit;
	}

	// Initialize the returned root block address to 0 incase of an error.
	pLFile->uiRootBlk = 0;

	// Call createBlock to create a new block
	if (RC_BAD( rc = pDb->m_pDatabase->createBlock( pDb, &pSCache)))
	{
		goto Exit;
	}

	// Save the new block address as the root block address
	pLFile->uiRootBlk = pSCache->m_uiBlkAddress;

	// Save the block address and identify the block as the root block.
	if (RC_BAD( rc = btOpen( pDb, pLFile, bCounts, bData)))
	{
		goto Exit;
	}
	pBlkHdr = (F_BTREE_BLK_HDR *)pSCache->m_pBlkHdr;

	setRootBlk( pBlkHdr);
	pBlkHdr->ui16LogicalFile = (FLMUINT16)pLFile->uiLfNum;
	setBlkLfType( pBlkHdr, pLFile->eLfType);
	pBlkHdr->ui8BlkLevel = 0;
	pBlkHdr->stdBlkHdr.ui8BlkType = (bData ? BT_LEAF_DATA : BT_LEAF);

	pBlkHdr->stdBlkHdr.ui32PrevBlkInChain = 0;
	pBlkHdr->stdBlkHdr.ui32NextBlkInChain = 0;
	
	if (pLFile->uiEncId)
	{
		setBlockEncrypted( (F_BLK_HDR *)pBlkHdr);
	}

	// Insert a LEM into the block
	uiFlags = BTE_FLAG_FIRST_ELEMENT | BTE_FLAG_LAST_ELEMENT;

	if (RC_BAD( rc = buildAndStoreEntry( (bData ? BT_LEAF_DATA : BT_LEAF),
		uiFlags, NULL, 0, NULL, 0, 0, 0, 0, &ucLEMEntry[0],
		3, &uiLEMSize)))
	{
		goto Exit;
	}

	pui16Offset = BtOffsetArray((FLMBYTE *)pBlkHdr, 0);
	pucEntry = (FLMBYTE *)pBlkHdr + m_uiBlockSize - uiLEMSize;

	bteSetEntryOffset( pui16Offset, 0, (FLMUINT16)(pucEntry - (FLMBYTE *)pBlkHdr));
	f_memcpy( pucEntry, ucLEMEntry, uiLEMSize);

	// Offset Entry & 2 byte LEM
	pBlkHdr->stdBlkHdr.ui16BlkBytesAvail = (FLMUINT16)(m_uiBlockSize -
															sizeofBTreeBlkHdr(pBlkHdr) -
															uiLEMSize - 2);
	pBlkHdr->ui16HeapSize = pBlkHdr->stdBlkHdr.ui16BlkBytesAvail;

	// There is one entry now.
	pBlkHdr->ui16NumKeys = 1;

Exit:

	if (pSCache)
	{
		ScaReleaseCache( pSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc: Btree initialization function.
****************************************************************************/
RCODE F_Btree::btOpen(
	F_Db *						pDb,
	LFILE *						pLFile,
 	FLMBOOL						bCounts,
	FLMBOOL						bData,
	IF_ResultSetCompare *	pCompare)
{
	RCODE				rc = NE_XFLM_OK;
	F_Database *	pDatabase= pDb->m_pDatabase;

	if ( m_bOpened)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	if (pDb->m_eTransType == XFLM_NO_TRANS && !pDatabase->m_bTempDb)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_NO_TRANS_ACTIVE);
		goto Exit;
	}


	if( !pLFile->uiRootBlk)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_FAILURE);
		goto Exit;
	}

	m_pLFile = pLFile;
	m_uiBlockSize = pDatabase->m_uiBlockSize;
	m_uiDefragThreshold = m_uiBlockSize / 20;
	m_uiOverflowThreshold = (m_uiBlockSize * 8) / 5;
	m_bCounts = bCounts;
	m_bData = bData;
	m_pDb = pDb;
	m_bTempDb = pDatabase->m_bTempDb;
	m_pReplaceInfo = NULL;
	m_uiReplaceLevels = 0;
	m_ui64CurrTransID = 0;
	m_ui64LastBlkTransId = 0;
	m_ui64PrimaryBlkTransId = 0;
	m_uiBlkChangeCnt = 0;
	m_uiSearchLevel = BH_MAX_LEVELS;

	m_bSetupForRead = FALSE;
	m_bSetupForWrite = FALSE;
	m_bSetupForReplace = FALSE;

	// Buffer is required to hold at least the maximum number of
	// offsets possible given the block size with a minimum entry size of
	// 5 bytes.  Each offset is 2 bytes.  We only need this buffer when we
	// are in an update transaction.

	if (pDb->m_eTransType == XFLM_UPDATE_TRANS || m_bTempDb)
	{
		m_uiBufferSize = m_uiBlockSize * 2;
	}
	else
	{
		m_uiBufferSize = 0;
	}

	// If we are in an update transaction, there are certain buffers that
	// are used.  Make sure these are allocated.

	if( (pDb->m_eTransType == XFLM_UPDATE_TRANS || m_bTempDb) &&
		!pDatabase->m_pucUpdBuffer)
	{
		// Buffer should be at least 80% of two blocks.  To make sure the other
		// structures being allocated here are aligned, we allocate 2 times the
		// database's block size for the update buffer.

		pDatabase->m_uiUpdBufferSize = m_uiBlockSize * 2;
		flmAssert( pDatabase->m_uiUpdBufferSize >= m_uiOverflowThreshold);
		
		if( RC_BAD( rc = f_alloc(
							pDatabase->m_uiUpdBufferSize +
							(m_uiBlockSize * 2) +
							m_uiBufferSize +
							sizeof( BTREE_REPLACE_STRUCT) * BH_MAX_LEVELS,
							&pDatabase->m_pucUpdBuffer)))
		{
			goto Exit;
		}

		// Temporary buffers for the F_Btree class will immediately follow
		// the update buffer.

		pDatabase->m_pucBTreeTmpBlk =
						pDatabase->m_pucUpdBuffer + pDatabase->m_uiUpdBufferSize;
		pDatabase->m_pucBTreeTmpDefragBlk =
						pDatabase->m_pucBTreeTmpBlk + pDatabase->m_uiBlockSize;
		pDatabase->m_pucBtreeBuffer =
						pDatabase->m_pucBTreeTmpDefragBlk +	pDatabase->m_uiBlockSize;
		pDatabase->m_pucReplaceStruct =
						pDatabase->m_pucBtreeBuffer + m_uiBufferSize;
	}

	// NOTE: These temporary buffers may be NULL.  They are only allocated the
	// first time we detect that we are operating inside an update
	// transaction - because we need to make sure that only one thread
	// actually does the allocation.  The assumption here is that the
	// buffers are only ever used during update operations, which will
	// *always* be inside update transactions.

	m_pucTempBlk = pDatabase->m_pucBTreeTmpBlk;
	m_pucTempDefragBlk = pDatabase->m_pucBTreeTmpDefragBlk;
	m_pucBuffer = pDatabase->m_pucBtreeBuffer;
	m_pReplaceStruct = (BTREE_REPLACE_STRUCT *)pDatabase->m_pucReplaceStruct;
	
	flmAssert( !m_pCompare);
	if ((m_pCompare = pCompare) != NULL)
	{
		m_pCompare->AddRef();
	}

	m_bOpened = TRUE;
	
Exit:

	return( rc);
}

/***************************************************************************
Desc: Btree close function
****************************************************************************/
void F_Btree::btClose()
{
	FLMUINT			uiLoop;

	// Ok to close multiple times.
	if (!m_bOpened)
	{
		// Btree is not open.  Just return.
		return;
	}

	m_pLFile = NULL;
	m_pDb = NULL;
	m_bTempDb = FALSE;

	for (uiLoop = 0; uiLoop < BH_MAX_LEVELS; uiLoop++)
	{
		m_Stack[ uiLoop].pucKeyBuf = NULL;
	}

	// Release any blocks still held in the stack.
	btRelease();

	if (m_pSCache)
	{
		flmAssert( 0);
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}
	
	if (m_pCompare)
	{
		m_pCompare->Release();
		m_pCompare = NULL;
	}

	m_bOpened = FALSE;
}

/***************************************************************************
Desc: Delete the entire tree
****************************************************************************/
RCODE F_Btree::btDeleteTree(
	IF_DeleteStatus *		ifpDeleteStatus)
{
	RCODE			rc = NE_XFLM_OK;
	FLMUINT		uiNumLevels;
	FLMUINT		puiBlkAddrs[ BH_MAX_LEVELS];
	FLMUINT		uiLoop;

	flmAssert( m_bOpened);

	// Verify the transaction type

	if (m_pDb->m_eTransType != XFLM_UPDATE_TRANS && !m_bTempDb)
	{
		rc = RC_SET_AND_ASSERT( m_pDb->m_eTransType == XFLM_NO_TRANS
								? NE_XFLM_NO_TRANS_ACTIVE
								: NE_XFLM_ILLEGAL_TRANS_OP);
		goto Exit;
	}

	// Fill up uiBlkAddrs and calculate the number of levels.

	if( RC_BAD( rc = btGetBlockChains( puiBlkAddrs, &uiNumLevels)))
	{
		goto Exit;
	}

	// Iterate over the list of block chains and free all of the blocks

	for( uiLoop = 0; uiLoop < uiNumLevels; uiLoop++)
	{
		if( RC_BAD( rc = btFreeBlockChain( 
			m_pDb, m_pLFile, puiBlkAddrs[ uiLoop], 0, NULL, NULL,
			ifpDeleteStatus)))
		{
			goto Exit;
		}
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:
****************************************************************************/
RCODE btFreeBlockChain(
	F_Db *					pDb,
	LFILE *					pLFile,
	FLMUINT					uiStartAddr,
	FLMUINT					uiBlocksToFree,
	FLMUINT *				puiBlocksFreed,
	FLMUINT *				puiEndAddr,
	IF_DeleteStatus *		ifpDeleteStatus)
{
	RCODE					rc = NE_XFLM_OK;
	F_Database *		pDatabase = pDb->getDatabase();
	F_CachedBlock *	pCurrentBlk = NULL;
	F_CachedBlock *	pDOSCache = NULL;
	FLMBYTE *			pBlk;
	FLMBYTE *			pucEntry;
	FLMUINT				uiEntryNum;
	FLMUINT				uiDOBlkAddr;
	FLMBYTE				ucDOBlkAddr[ 4];
	FLMUINT				uiStatusCounter = 0;
	FLMUINT				uiNextBlkAddr = 0;
	FLMUINT				uiCurrentBlkAddr = 0;
	FLMUINT				uiTreeBlocksFreed = 0;
	FLMUINT				uiDataBlocksFreed = 0;
	FLMBOOL				bFreeAll = FALSE;

	if( !uiBlocksToFree)
	{
		bFreeAll = TRUE;
	}

	// Verify the transaction type

	if( pDb->getTransType() != XFLM_UPDATE_TRANS)
	{
		rc = RC_SET_AND_ASSERT( pDb->getTransType() == XFLM_NO_TRANS
								? NE_XFLM_NO_TRANS_ACTIVE
								: NE_XFLM_ILLEGAL_TRANS_OP);
		goto Exit;
	}

	// Now, go through the chain and delete the blocks...

	uiCurrentBlkAddr = uiStartAddr;
	while( uiCurrentBlkAddr)
	{
		if( !bFreeAll && uiTreeBlocksFreed >= uiBlocksToFree)
		{
			break;
		}

		if( RC_BAD( pDatabase->getBlock( pDb, pLFile,
								uiCurrentBlkAddr, NULL, &pCurrentBlk)))
		{
			goto Exit;
		}

		pBlk = (FLMBYTE *)pCurrentBlk->getBlockPtr();
		uiNextBlkAddr = ((F_BLK_HDR *)pBlk)->ui32NextBlkInChain;

		// If this is a leaf block, then there may be entries 
		// with data-only references that will need to be cleaned up too.

		if( getBlkType( pBlk) == BT_LEAF_DATA)
		{
			for( uiEntryNum = 0;
				  uiEntryNum < ((F_BTREE_BLK_HDR *)pBlk)->ui16NumKeys;
				  uiEntryNum++)
			{
				pucEntry = BtEntry( pBlk, uiEntryNum);

				if( bteDataBlockFlag( pucEntry))
				{
					// Get the data-only block address

					if( RC_BAD( rc = btGetEntryData( 
						pucEntry, &ucDOBlkAddr[ 0], 4, NULL)))
					{
						goto Exit;
					}

					uiDOBlkAddr = bteGetBlkAddr( (FLMBYTE *)&ucDOBlkAddr[ 0]);
					while( uiDOBlkAddr)
					{
						if( RC_BAD(
							rc = pDatabase->getBlock( pDb, pLFile,
												  uiDOBlkAddr, NULL, &pDOSCache)))
						{
							goto Exit;
						}

						uiDOBlkAddr = pDOSCache->getBlockPtr()->ui32NextBlkInChain;
						rc = pDatabase->blockFree( pDb, pDOSCache);
						pDOSCache = NULL;

						if (RC_BAD( rc))
						{
							goto Exit;
						}

						uiDataBlocksFreed++;
					}
				}
			}
		}

		rc = pDatabase->blockFree( pDb, pCurrentBlk);
		pCurrentBlk = NULL;

		if( RC_BAD( rc))
		{
			goto Exit;
		}

		if( ifpDeleteStatus && pLFile && ++uiStatusCounter >= 25)
		{
			uiStatusCounter = 0;
			if( RC_BAD( rc = ifpDeleteStatus->reportDelete( 
					uiTreeBlocksFreed + uiDataBlocksFreed,
					pDatabase->getBlockSize())))
			{
				goto Exit;
			}
		}

		uiTreeBlocksFreed++;
		uiCurrentBlkAddr = uiNextBlkAddr;
	}

	if( puiBlocksFreed)
	{
		*puiBlocksFreed = uiTreeBlocksFreed;
	}

	if( puiEndAddr)
	{
		*puiEndAddr = uiCurrentBlkAddr;
	}

Exit:

	if( pDOSCache)
	{
		ScaReleaseCache( pDOSCache, FALSE);
	}

	if( pCurrentBlk)
	{
		ScaReleaseCache( pCurrentBlk, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc:	Returns the address of the first block at each level of the tree
Note:	puiBlockAddrs is assumed to point to a buffer that can store
		BH_MAX_LEVELS FLMUINT values
****************************************************************************/
RCODE F_Btree::btGetBlockChains(
	FLMUINT *	puiBlockAddrs,
	FLMUINT *	puiNumLevels)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT				uiNumLevels = 0;
	FLMUINT				uiLFileNum;
	FLMBOOL				bIsIndex;
	FLMUINT32			ui32NextBlkAddr;
	F_CachedBlock *	pCurrentBlk = NULL;
	FLMBYTE *			pucBlk;
	FLMBYTE *			pucEntry;

	flmAssert( m_bOpened);

	// Verify the transaction type

	if( m_pDb->m_eTransType != XFLM_UPDATE_TRANS && !m_bTempDb)
	{
		rc = RC_SET_AND_ASSERT( m_pDb->m_eTransType == XFLM_NO_TRANS
								? NE_XFLM_NO_TRANS_ACTIVE
								: NE_XFLM_ILLEGAL_TRANS_OP);
		goto Exit;
	}

	// Fill puiBlockAddrs and calculate the number of levels.
	// NOTE: Normally, level 0 is the leaf level.  In this function,
	// puiBlockAddrs[ 0] is the ROOT and puiBlockAddrs[ uiNumLevels - 1] 
	// is the LEAF!

	ui32NextBlkAddr = (FLMUINT32)m_pLFile->uiRootBlk;
	uiLFileNum = m_pLFile->uiLfNum;
	bIsIndex = m_pLFile->eLfType == XFLM_LF_INDEX ? TRUE : FALSE;

	while( ui32NextBlkAddr)
	{
		puiBlockAddrs[ uiNumLevels++] = ui32NextBlkAddr;

		if( RC_BAD( m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
										ui32NextBlkAddr, NULL, &pCurrentBlk)))
		{
			goto Exit;
		}

		pucBlk = (FLMBYTE *)pCurrentBlk->m_pBlkHdr;

		if( getBlkType( pucBlk) == BT_LEAF || getBlkType( pucBlk) == BT_LEAF_DATA)
		{
			ui32NextBlkAddr = 0;
		}
		else
		{
			// The child block address is the first part of the entry

			pucEntry = BtEntry( pucBlk, 0);
			ui32NextBlkAddr = bteGetBlkAddr( pucEntry);
		}

		// Release the current block

		ScaReleaseCache( pCurrentBlk, FALSE);
		pCurrentBlk = NULL;
	}

	*puiNumLevels = uiNumLevels;

Exit:

	if( pCurrentBlk)
	{
		ScaReleaseCache( pCurrentBlk, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc: Function to insert an entry into the Btree.
****************************************************************************/
RCODE F_Btree::btInsertEntry(
	const FLMBYTE *	pucKey,
	FLMUINT				uiKeyLen,
	const FLMBYTE *	pucData,
	FLMUINT				uiDataLen,
	FLMBOOL				bFirst,
	FLMBOOL				bLast,
	FLMUINT32 *			pui32BlkAddr,
	FLMUINT *			puiOffsetIndex)
{
	RCODE				rc = NE_XFLM_OK;
	F_BLK_HDR *		pBlkHdr;
	FLMBYTE			pucDOAddr[ 4];
	
	if ( !m_bOpened || m_bSetupForRead || m_bSetupForReplace ||
		  (m_bSetupForWrite && bFirst))
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	flmAssert( m_uiSearchLevel >= BH_MAX_LEVELS);
	flmAssert( !m_pDb->m_pDatabase->m_pRfl || 
				  !m_pDb->m_pDatabase->m_pRfl->isLoggingEnabled());

	if( !uiKeyLen)
	{
		rc = RC_SET( NE_XFLM_ILLEGAL_OP);
		goto Exit;
	}

	// Verify the transaction type
	
	if( m_pDb->m_eTransType != XFLM_UPDATE_TRANS && !m_bTempDb)
	{
		rc = RC_SET_AND_ASSERT( m_pDb->m_eTransType == XFLM_NO_TRANS
								? NE_XFLM_NO_TRANS_ACTIVE
								: NE_XFLM_ILLEGAL_TRANS_OP);
		goto Exit;
	}

	// Be sure to clear the Data Only flag.
	
	if( bFirst)
	{
		m_bDataOnlyBlock = FALSE;
	}

	if( bLast)
	{

		// We need to locate where we should insert the new entry.
		
		rc = findEntry( pucKey, uiKeyLen, XFLM_EXACT);

		// Should not find this entry.  If we get back anything other than
		// an NE_XFLM_NOT_FOUND, then there is a problem.
		
		if( rc != NE_XFLM_NOT_FOUND)
		{
			if( RC_OK( rc))
			{
				rc = RC_SET( NE_XFLM_NOT_UNIQUE);
			}
			goto Exit;
		}
	}

	if( bFirst && (!bLast || (uiKeyLen + uiDataLen > m_uiOverflowThreshold)))
	{
		// If bLast is not set, then we will setup to store the data in
		// data only blocks.  The assumption is that whenever we don't see bLast
		// set when starting an insert, then the data is so large that it must
		// be placed in a chain of Data Only blocks.  There is no way for me to
		// check the final size of the data ahead of time, so I rely on the
		// calling routine to figure this out for me.

		// Get one empty block to begin with.

		flmAssert( m_pSCache == NULL);
		if( RC_BAD( rc = m_pDb->m_pDatabase->createBlock( m_pDb, &m_pSCache)))
		{
			goto Exit;
		}

		// The data going in will be stored in Data-only blocks.
		// Setup the block header...

		pBlkHdr = m_pSCache->m_pBlkHdr;
		pBlkHdr->ui8BlkType = BT_DATA_ONLY;
		pBlkHdr->ui32PrevBlkInChain = 0;
		pBlkHdr->ui32NextBlkInChain = 0;

		if( m_pLFile->uiEncId)
		{
			((F_ENC_DO_BLK_HDR *)pBlkHdr)->ui32EncId = (FLMUINT32)m_pLFile->uiEncId;
			setBlockEncrypted( pBlkHdr);
		}

		pBlkHdr->ui16BlkBytesAvail =
				(FLMUINT16)(m_uiBlockSize - sizeofDOBlkHdr( (F_BLK_HDR *)pBlkHdr));

		m_uiDataRemaining = m_uiBlockSize - sizeofDOBlkHdr( (F_BLK_HDR *)pBlkHdr);
		m_uiDataLength = 0;
		m_uiOADataLength = 0;
		m_bDataOnlyBlock = TRUE;
		m_bSetupForWrite = TRUE;
		m_ui32DOBlkAddr = m_pSCache->m_pBlkHdr->ui32BlkAddr;
		m_ui32CurBlkAddr = m_ui32DOBlkAddr;
	}

	if( m_bDataOnlyBlock)
	{
		if( RC_BAD( rc = storeDataOnlyBlocks( pucKey, uiKeyLen, bFirst,
			pucData, uiDataLen)))
		{
			goto Exit;
		}
	}

	if( bLast)
	{
		const FLMBYTE *		pucLocalData;
		FLMUINT					uiLocalDataLen;
		F_ELM_UPD_ACTION		eAction;

		if( m_bDataOnlyBlock)
		{
			// build an entry that points to the DO block.

			UD2FBA( m_ui32DOBlkAddr, pucDOAddr);
			pucLocalData = &pucDOAddr[0];
			uiLocalDataLen = m_uiOADataLength;
			eAction = ELM_INSERT_DO;

		}
		else
		{
			pucLocalData = pucData;
			uiLocalDataLen = uiDataLen;
			eAction = ELM_INSERT;
		}

		if( RC_BAD( rc = updateEntry( pucKey, uiKeyLen, pucLocalData,
			uiLocalDataLen, eAction)))
		{
			goto Exit;
		}

		if( pui32BlkAddr)
		{
			*pui32BlkAddr = m_ui32PrimaryBlkAddr;
		}

		if( puiOffsetIndex)
		{
			*puiOffsetIndex = m_uiCurOffset;
		}

		m_bSetupForWrite = FALSE;
	}

Exit:

	if( m_pSCache)
	{
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}

	releaseBlocks( TRUE);
	return( rc);
}

/***************************************************************************
Desc: Function to remove an entry into the Btree.
****************************************************************************/
RCODE F_Btree::btRemoveEntry(
	const FLMBYTE *	pucKey,
	FLMUINT				uiKeyLen)
{
	RCODE				rc = NE_XFLM_OK;
	FLMBYTE *		pucValue = NULL;
	FLMUINT			uiLen = 0;

	if ( !m_bOpened)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	// Verify the Txn type
	if (m_pDb->m_eTransType != XFLM_UPDATE_TRANS && !m_bTempDb)
	{
		rc = RC_SET_AND_ASSERT( m_pDb->m_eTransType == XFLM_NO_TRANS
								? NE_XFLM_NO_TRANS_ACTIVE
								: NE_XFLM_ILLEGAL_TRANS_OP);
		goto Exit;
	}

	flmAssert( !m_pDb->m_pDatabase->m_pRfl || 
				  !m_pDb->m_pDatabase->m_pRfl->isLoggingEnabled());

	btResetBtree();

	// We need to locate where we should remove the entry.
	if (RC_BAD( rc = findEntry( pucKey,
										 uiKeyLen,
										 XFLM_EXACT)))
	{
		goto Exit;
	}

	if (RC_BAD( rc = updateEntry( pucKey, uiKeyLen, pucValue, uiLen,
			ELM_REMOVE)))
	{
		goto Exit;
	}

Exit:

	releaseBlocks( TRUE);
	return( rc);
}

/***************************************************************************
Desc:	Function to provide a streaming interface for replacing large 
		data elements.
****************************************************************************/
RCODE F_Btree::btReplaceEntry(
	const FLMBYTE *	pucKey,
	FLMUINT				uiKeyLen,
	const FLMBYTE *	pucData,
	FLMUINT				uiDataLen,
	FLMBOOL				bFirst,
	FLMBOOL				bLast,
	FLMBOOL				bTruncate,
	FLMUINT32 *			pui32BlkAddr,
	FLMUINT *			puiOffsetIndex)
{
	RCODE					rc = NE_XFLM_OK;
	FLMBYTE *			pucEntry;
	F_BLK_HDR *			pBlkHdr;
	const FLMBYTE *	pucLocalData = NULL;
	FLMUINT				uiOADataLength = 0;
	FLMBYTE				pucDOAddr[ 4];

	if( !m_bOpened || m_bSetupForRead || m_bSetupForWrite ||
		  (m_bSetupForReplace && bFirst))
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	flmAssert( m_uiSearchLevel >= BH_MAX_LEVELS);
	flmAssert( !m_pDb->m_pDatabase->m_pRfl || 
				  !m_pDb->m_pDatabase->m_pRfl->isLoggingEnabled());

	if (!uiKeyLen)
	{
		rc = RC_SET( NE_XFLM_ILLEGAL_OP);
		goto Exit;
	}

	// Verify the Txn type
	if (m_pDb->m_eTransType != XFLM_UPDATE_TRANS && !m_bTempDb)
	{
		rc = RC_SET_AND_ASSERT( m_pDb->m_eTransType == XFLM_NO_TRANS
								? NE_XFLM_NO_TRANS_ACTIVE
								: NE_XFLM_ILLEGAL_TRANS_OP);
		goto Exit;
	}


	// Be sure to clear the Data Only flags
	
	if( bFirst)
	{
		m_bDataOnlyBlock = FALSE;
		m_bOrigInDOBlocks = FALSE;
	}

	if( bFirst || bLast)
	{

		// We need to locate the entry we want to replace
		
		if( RC_BAD( rc = findEntry( pucKey, uiKeyLen, XFLM_EXACT, NULL,
			pui32BlkAddr, puiOffsetIndex)))
		{
			goto Exit;
		}

		// We must first determine if the existing entry is stored
		// in data only blocks.
		
		pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr,
								  m_pStack->uiCurOffset);

		btGetEntryDataLength( pucEntry, &pucLocalData,
			&uiOADataLength, &m_bOrigInDOBlocks);

	}

	if( bFirst && (!bLast || (bLast && !bTruncate && m_bOrigInDOBlocks) ||
			(uiKeyLen + uiDataLen > m_uiOverflowThreshold)))
	{
		// If bLast is not set, then we will setup to store the data in
		// data only blocks.
		
		m_bDataOnlyBlock = TRUE;
		flmAssert( m_pSCache == NULL);
		
		if( m_bOrigInDOBlocks)
		{
			// Need to get the first DO block, and work from there.
			
			m_ui32DOBlkAddr = bteGetBlkAddr( pucLocalData);
			
			if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
				m_ui32DOBlkAddr, NULL, &m_pSCache)))
			{
				goto Exit;
			}
		}
		else
		{
			// Get one empty block to begin with
			
			if( RC_BAD( rc = m_pDb->m_pDatabase->createBlock( m_pDb, &m_pSCache)))
			{
				goto Exit;
			}

			// The data going in will be stored in Data-only blocks.
			// Setup the block header...
			
			pBlkHdr = m_pSCache->m_pBlkHdr;
			pBlkHdr->ui8BlkType = BT_DATA_ONLY;
			pBlkHdr->ui32PrevBlkInChain = 0;
			pBlkHdr->ui32NextBlkInChain = 0;

			if (m_pLFile->uiEncId)
			{
				((F_ENC_DO_BLK_HDR *)pBlkHdr)->ui32EncId = (FLMUINT32)m_pLFile->uiEncId;
				setBlockEncrypted( pBlkHdr);
			}

			pBlkHdr->ui16BlkBytesAvail =
						(FLMUINT16)(m_uiBlockSize - 
							sizeofDOBlkHdr( (F_BLK_HDR *)pBlkHdr));
		}

		m_uiDataRemaining =	m_uiBlockSize - sizeofDOBlkHdr( (F_BLK_HDR *)m_pSCache->m_pBlkHdr);
		m_uiDataLength = 0;
		m_uiOADataLength = 0;
		m_bDataOnlyBlock = TRUE;
		m_bSetupForReplace = TRUE;
		m_ui32DOBlkAddr = m_pSCache->m_pBlkHdr->ui32BlkAddr;
		m_ui32CurBlkAddr = m_ui32DOBlkAddr;
	}

	if( m_bDataOnlyBlock)
	{
		if( !bTruncate && !m_bOrigInDOBlocks)
		{
			bTruncate = TRUE;
		}

		// May need to skip over the key that is stored in the first DO block.
		// We only want to do this the first time in here.  The test to determine
		// if this is our first time in this block is to see if the m_uiDataLength
		// is equal to the m_uiDataRemaining.  They would only be the same on the
		// first time for each DO block.
		
		if( m_bOrigInDOBlocks && m_pSCache &&
			m_pSCache->m_pBlkHdr->ui32PrevBlkInChain == 0 && !m_uiDataLength)
		{
			m_uiDataRemaining -= (uiKeyLen + 2);
		}

		if( RC_BAD( rc = replaceDataOnlyBlocks( pucKey, uiKeyLen,
			!m_bOrigInDOBlocks && bFirst, pucData, uiDataLen, bLast,
			bTruncate)))
		{
			goto Exit;
		}
	}

	// If we were writing to Data Only Blocks and we are not truncating the
	// data, then we are done here.
	
	if( m_bDataOnlyBlock && !bTruncate)
	{
		if( bLast && (uiOADataLength <= m_uiOADataLength))
		{
			bTruncate = TRUE;
		}
		else
		{
			goto Exit;
		}
	}

	// Only replace the entry on the last call.
	
	if( bLast)
	{
		FLMUINT					uiLocalDataLen;
		F_ELM_UPD_ACTION		eAction;

		if (m_bDataOnlyBlock)
		{
			// build an entry that points to the DO block.

			UD2FBA( m_ui32DOBlkAddr, pucDOAddr);

			pucLocalData = &pucDOAddr[0];
			uiLocalDataLen = m_uiOADataLength;
			eAction = ELM_REPLACE_DO;

		}
		else
		{
			pucLocalData = pucData;
			uiLocalDataLen = uiDataLen;
			eAction = ELM_REPLACE;
		}

		if( RC_BAD( rc = updateEntry( pucKey, uiKeyLen, pucLocalData,
			uiLocalDataLen, eAction, bTruncate)))
		{
			goto Exit;
		}
	}

Exit:

	if (RC_OK( rc))
	{
		if (pui32BlkAddr)
		{
			*pui32BlkAddr = m_ui32PrimaryBlkAddr;
		}

		if (puiOffsetIndex)
		{
			*puiOffsetIndex = m_uiCurOffset;
		}
	}

	if( bLast)
	{
		m_bSetupForReplace = FALSE;
	}

	if (m_pSCache)
	{
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}

	releaseBlocks( TRUE);
	return( rc);
}

/***************************************************************************
Desc: Function to search the Btree for a specific key.
****************************************************************************/
RCODE F_Btree::btLocateEntry(
	FLMBYTE *		pucKey,
	FLMUINT			uiKeyBufSize,
	FLMUINT *		puiKeyLen,
	FLMUINT			uiMatch,
	FLMUINT *		puiPosition,		// May be NULL
	FLMUINT *		puiDataLength,
	FLMUINT32 *		pui32BlkAddr,
	FLMUINT *		puiOffsetIndex)
{
	RCODE				rc = NE_XFLM_OK;
	FLMBYTE *		pucEntry = NULL;

	flmAssert( pucKey && uiKeyBufSize && puiKeyLen);

	if (!m_bOpened || m_bSetupForWrite || m_bSetupForReplace)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	m_bSetupForRead = FALSE;

	// Verify the Txn type
	
	if (m_pDb->m_eTransType == XFLM_NO_TRANS && !m_bTempDb)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_NO_TRANS_ACTIVE);
		goto Exit;
	}

	// Find the entry we are interested in.
	if (RC_BAD(rc = findEntry( pucKey,
										*puiKeyLen,
										uiMatch,
										puiPosition,
										pui32BlkAddr,
										puiOffsetIndex)))
	{
		goto Exit;
	}

	m_ui64LowTransId = m_pStack->pBlkHdr->stdBlkHdr.ui64TransID;
	m_bMostCurrent = (m_pStack->pSCache->m_ui64HighTransID == FLM_MAX_UINT64)
											? TRUE
											: FALSE;

	m_ui32PrimaryBlkAddr =	m_pStack->ui32BlkAddr;
	m_uiPrimaryOffset =		m_pStack->uiCurOffset;
	m_ui32CurBlkAddr =		m_ui32PrimaryBlkAddr;
	m_uiCurOffset =			m_uiPrimaryOffset;

	// Point to the entry...
	pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr,
							  m_pStack->uiCurOffset);

	// Return the optional data length - get the overall data length only.
	if (puiDataLength &&
		m_pStack->pSCache->m_pBlkHdr->ui8BlkType == BT_LEAF_DATA)
	{
		btGetEntryDataLength( pucEntry, NULL, puiDataLength, NULL);
	}
	else if (puiDataLength)
	{
		*puiDataLength = 0;
	}

	if( RC_BAD( rc = setupReadState( m_pStack->pSCache->m_pBlkHdr, pucEntry)))
	{
		goto Exit;
	}

	// In case the returning key is not what was originally requested, such as
	// in the case of XFLM_FIRST, XFLM_LAST, XFLM_EXCL and possibly XFLM_INCL, 
	// we will pass back the key we actually found.

	if( uiMatch != XFLM_EXACT)
	{
		if( RC_BAD( rc = setReturnKey( pucEntry, 
			m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType, pucKey, puiKeyLen,
			uiKeyBufSize)))
		{
			goto Exit;
		}
	}

	m_bFirstRead =		FALSE;
	m_bSetupForRead = TRUE;

Exit:

	releaseBlocks( FALSE);
	return( rc);
}

/***************************************************************************
Desc: Method to get the data after a call to btLocateEntry, btNextEntry,
		btPrevEntry, btFirstEntry or btLastEntry.
****************************************************************************/
RCODE F_Btree::btGetEntry(
	FLMBYTE *				pucKey,
	FLMUINT					uiKeyBufSize,
	FLMUINT					uiKeyLen,
	FLMBYTE *				pucData,
	FLMUINT					uiDataBufSize,
	FLMUINT *				puiDataLen
	)
{
	RCODE					rc = NE_XFLM_OK;
	FLMBYTE *			pucEntry;

	if( !m_bOpened || !m_bSetupForRead || 
		 m_bSetupForWrite || m_bSetupForReplace)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	if( puiDataLen)
	{
		*puiDataLen = 0;
	}

	// Is there anything there to get?
	
	if( m_uiOADataRemaining == 0)
	{
		rc = RC_SET( NE_XFLM_EOF_HIT);
		goto Exit;
	}

	// If the transaction Id or the Block Change Count has changed,
	// we must re-sync ourselves.

	if( !m_bTempDb &&
		 ((m_ui64CurrTransID != m_pDb->m_ui64CurrTransID) ||
		  (m_uiBlkChangeCnt != m_pDb->m_uiBlkChangeCnt)))
	{
		// Test to see if we really need to re-sync ...
		
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			m_ui32CurBlkAddr, NULL, &m_pSCache)))
		{
			goto Exit;
		}

		if( m_pSCache->m_pBlkHdr->ui64TransID != m_ui64LastBlkTransId ||
				(m_pDb->m_eTransType == XFLM_UPDATE_TRANS &&
					m_pDb->m_ui64CurrTransID == m_pSCache->m_pBlkHdr->ui64TransID))
		{
			// We must call btLocateEntry so we can re-initialize the read
			
			if( !m_bFirstRead)
			{
				if( RC_BAD( rc = btLocateEntry( pucKey, uiKeyBufSize,
					&uiKeyLen, XFLM_EXACT)))
				{
					goto Exit;
				}

				// Will need a new version of this block.
				
				ScaReleaseCache( m_pSCache, FALSE);
				m_pSCache = NULL;
			}
			else
			{
				rc = RC_SET(NE_XFLM_BTREE_BAD_STATE);
				goto Exit;
			}
		}
	}

	// Get the current block.  It is either a DO or a Btree block.
	
	if( m_pSCache == NULL)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			m_ui32CurBlkAddr, NULL, &m_pSCache)))
		{
			goto Exit;
		}
	}

	updateTransInfo( m_pSCache->m_pBlkHdr->ui64TransID,
						  m_pSCache->m_ui64HighTransID);

	// Now to find where we were the last time through.
	
	if( !m_bDataOnlyBlock)
	{
		pucEntry = BtEntry( (FLMBYTE *)m_pSCache->m_pBlkHdr,
								  m_uiCurOffset);

		btGetEntryDataLength( pucEntry, &m_pucDataPtr, NULL, NULL);
	}
	else
	{
		m_pucDataPtr = (FLMBYTE *)m_pSCache->m_pBlkHdr +
							sizeofDOBlkHdr( (F_BLK_HDR *)m_pSCache->m_pBlkHdr);

		// May need to skip over the key that is stored in the first DO block.
		// We only want to do this the first time in here.  The test to determine
		// if this is our first time in this block is to see if the m_uiDataLength
		// is equal to the m_uiDataRemaining.  They would only be the same on the
		// first time for each DO block.
		
		if( m_pSCache && m_pSCache->m_pBlkHdr->ui32PrevBlkInChain == 0)
		{
			FLMUINT16	ui16KeyLen = FB2UW( m_pucDataPtr);

			// Key lengths should be the same
			
			flmAssert( uiKeyLen == (FLMUINT)ui16KeyLen);

			m_pucDataPtr += (ui16KeyLen + 2);
		}
	}

	m_pucDataPtr += (m_uiDataLength - m_uiDataRemaining);
	
	if( RC_BAD( rc = extractEntryData( pucKey, uiKeyLen, pucData,
		uiDataBufSize, puiDataLen)))
	{
		goto Exit;
	}

	// Mark that we have completed our first read operation.
	// No more read synchronization allowed.
	
	m_bFirstRead = TRUE;

Exit:

	if( m_pSCache)
	{
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}

	releaseBlocks( FALSE);
	return( rc);
}

/***************************************************************************
Desc: Function to locate the next entry in the Btree.  The key buffer and
		actual size is passed in.
****************************************************************************/
RCODE F_Btree::btNextEntry(
	FLMBYTE *				pucKey,
	FLMUINT					uiKeyBufSize,
	FLMUINT *				puiKeyLen,
	FLMUINT *				puiDataLength,
	FLMUINT32 *				pui32BlkAddr,
	FLMUINT *				puiOffsetIndex)
{
	RCODE				rc = NE_XFLM_OK;
	FLMBYTE *		pucEntry = NULL;
	FLMBOOL			bAdvanced = FALSE;

	if( !m_bOpened || !m_bSetupForRead)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	// Verify the transaction type
	
	if( m_pDb->m_eTransType == XFLM_NO_TRANS && !m_bTempDb)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_NO_TRANS_ACTIVE);
		goto Exit;
	}

	// Make sure we are looking at btree  block.  If the m_bDataOnlyBlock
	// flag is set, then the block address in m_ui32CurBlkAddr is a
	// data only block.  We must reset it to the primary block address.
	
	if( m_bDataOnlyBlock)
	{
		m_ui32CurBlkAddr = m_ui32PrimaryBlkAddr;
	}
	else
	{
		// If the entry did not reference a DO block, then we need to
		// reset the primary block and offset with where we currently
		// are incase the current block is further ahead.  This saves time
		// so that we don't have to scan past old blocks we are not intereseted
		// in.
		
		m_ui32PrimaryBlkAddr = m_ui32CurBlkAddr;
		m_uiPrimaryOffset = m_uiCurOffset;
		m_ui64PrimaryBlkTransId = m_ui64LastBlkTransId;
	}

	// Do we need to resynchronize?
	
	if( !m_bTempDb &&
		 ((m_ui64CurrTransID != m_pDb->m_ui64CurrTransID) ||
		  (m_uiBlkChangeCnt != m_pDb->m_uiBlkChangeCnt)))
	{
		// Test to see if we really need to re-sync ...
		
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			m_ui32CurBlkAddr, NULL, &m_pSCache)))
		{
			goto Exit;
		}

		if( m_pSCache->m_pBlkHdr->ui64TransID != m_ui64LastBlkTransId ||
				(m_pDb->m_eTransType == XFLM_UPDATE_TRANS &&
					m_pDb->m_ui64CurrTransID == m_pSCache->m_pBlkHdr->ui64TransID))
		{
			// Will need a new version of this block.
			
			ScaReleaseCache( m_pSCache, FALSE);
			m_pSCache = NULL;

			// Doing a find with XFLM_EXCL will result in our being positioned at
			// the next entry.
			
			if( RC_BAD( rc = btLocateEntry( pucKey, uiKeyBufSize, puiKeyLen,
				XFLM_EXCL, puiDataLength)))
			{
				goto Exit;
			}
			
			bAdvanced = TRUE;
		}
	}

	// Get the current block if we need it.
	
	if( !m_pSCache)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			m_ui32CurBlkAddr, NULL, &m_pSCache)))
		{
			goto Exit;
		}
	}

	// If we have already advanced due to a resynch, then we don't need to call
	// the advanceToNextElement function, however, we do need to get the
	// current entry.
	
	if( bAdvanced)
	{
		pucEntry = BtEntry( (FLMBYTE *)m_pSCache->m_pBlkHdr, m_uiCurOffset);
	}
	else
	{
		for (;;)
		{
			// Advance to the next entry in the block.  We don't have a stack so
			// don't advance it.
			
			if( RC_BAD( rc = advanceToNextElement( FALSE)))
			{
				goto Exit;
			}

			pucEntry = BtEntry( (FLMBYTE *)m_pSCache->m_pBlkHdr, m_uiCurOffset);

			if( m_bData)
			{
				if( bteFirstElementFlag(pucEntry))
				{
					break;
				}
			}
			else
			{
				break;
			}
		}
	}

	// Return the optional data length - get the overall data length only.
	
	if( puiDataLength)
	{
		btGetEntryDataLength( pucEntry, NULL, puiDataLength, NULL);
	}

	if( RC_BAD( rc = setupReadState( m_pSCache->m_pBlkHdr, pucEntry)))
	{
		goto Exit;
	}

	// Incase the returning key is not what was originally requested, such as in
	// the case of XFLM_FIRST, XFLM_LAST, XFLM_EXCL and possibly XFLM_INCL,
	// we will pass back the key we actually found.
	
	if( RC_BAD( rc = setReturnKey( pucEntry, m_pSCache->m_pBlkHdr->ui8BlkType,
		pucKey, puiKeyLen, uiKeyBufSize)))
	{
		goto Exit;
	}

	if( pui32BlkAddr)
	{
		*pui32BlkAddr = m_pSCache->m_pBlkHdr->ui32BlkAddr;
	}

	if( puiOffsetIndex)
	{
		*puiOffsetIndex = m_uiCurOffset;
	}

	m_bFirstRead = FALSE;

Exit:

	if( m_pSCache)
	{
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}

	releaseBlocks( FALSE);
	return( rc);
}

/***************************************************************************
Desc: Function to get the previous entry in the Btree.
****************************************************************************/
RCODE F_Btree::btPrevEntry(
	FLMBYTE *				pucKey,
	FLMUINT					uiKeyBufSize,
	FLMUINT *				puiKeyLen,
	FLMUINT *				puiDataLength,
	FLMUINT32 *				pui32BlkAddr,
	FLMUINT *				puiOffsetIndex)
{
	RCODE				rc = NE_XFLM_OK;
	FLMBYTE *		pucEntry = NULL;

	if( !m_bOpened || !m_bSetupForRead)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	// Verify the transaction type
	
	if( m_pDb->m_eTransType == XFLM_NO_TRANS && !m_bTempDb)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_NO_TRANS_ACTIVE);
		goto Exit;
	}

	// Make sure we are looking at the first block of the
	// current entry.  Reading of the entry could have moved us
	// to another block, or if it was in a DO block, we would be
	// looking at the wrong block altogether.
	
	m_ui32CurBlkAddr = m_ui32PrimaryBlkAddr;
	m_uiCurOffset = m_uiPrimaryOffset;
	m_ui64LastBlkTransId = m_ui64PrimaryBlkTransId;

	// Do we need to resynchronize?
	
	if( !m_bTempDb &&
		 ((m_ui64CurrTransID != m_pDb->m_ui64CurrTransID) ||
		  (m_uiBlkChangeCnt != m_pDb->m_uiBlkChangeCnt)))
	{
		// Test to see if we really need to re-sync ...
		
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			m_ui32CurBlkAddr, NULL, &m_pSCache)))
		{
			goto Exit;
		}

		if( m_pSCache->m_pBlkHdr->ui64TransID != m_ui64LastBlkTransId ||
				(m_pDb->m_eTransType == XFLM_UPDATE_TRANS &&
					m_pDb->m_ui64CurrTransID == m_pSCache->m_pBlkHdr->ui64TransID))
		{
			// Will need a new version of this block.
			
			ScaReleaseCache( m_pSCache, FALSE);
			m_pSCache = NULL;

			// Doing a find with XFLM_INCL will allow for the possibility that
			// the original entry is no longer there.  We will still have
			// to backup to the previous entry.
			
			if( RC_BAD( rc = btLocateEntry( pucKey, uiKeyBufSize, puiKeyLen,
				XFLM_INCL, puiDataLength)))
			{
				goto Exit;
			}
		}
	}

	if( !m_pSCache)
	{
		// Fetch the current block, then backup from there.
		
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			m_ui32CurBlkAddr, NULL, &m_pSCache)))
		{
			goto Exit;
		}
	}

	for (;;)
	{
		// Backup to the previous entry in the block.
		
		if( RC_BAD( rc = backupToPrevElement( FALSE)))
		{
			goto Exit;
		}

		// Get the entry, size etc.
		
		pucEntry = BtEntry( (FLMBYTE *)m_pSCache->m_pBlkHdr, m_uiCurOffset);

		if( m_bData)
		{
			if( bteFirstElementFlag( pucEntry))
			{
				break;
			}
		}
		else
		{
			break;
		}
	}

	// Return the optional data length - get the overall data length only.
	
	if( puiDataLength)
	{
		btGetEntryDataLength( pucEntry, NULL, puiDataLength, NULL);
	}

	if( RC_BAD( rc = setupReadState( m_pSCache->m_pBlkHdr, pucEntry)))
	{
		goto Exit;
	}

	// In case the returning key is not what was originally requested, such as in
	// the case of XFLM_FIRST, XFLM_LAST, XFLM_EXCL and possibly XFLM_INCL,
	// we will pass back the key we actually found.

	if( RC_BAD( rc = setReturnKey( pucEntry, m_pSCache->m_pBlkHdr->ui8BlkType,
		pucKey, puiKeyLen, uiKeyBufSize)))
	{
		goto Exit;
	}

	if( pui32BlkAddr)
	{
		*pui32BlkAddr = m_pSCache->m_pBlkHdr->ui32BlkAddr;
	}

	if( puiOffsetIndex)
	{
		*puiOffsetIndex = m_uiCurOffset;
	}

	m_bFirstRead = FALSE;

Exit:

	if( m_pSCache)
	{
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}

	releaseBlocks( FALSE);
	return( rc);
}

/***************************************************************************
Desc:	Locate the first entry in the Btree and return the key.
****************************************************************************/
RCODE F_Btree::btFirstEntry(
	FLMBYTE *				pucKey,
	FLMUINT					uiKeyBufSize,
	FLMUINT *				puiKeyLen,
	FLMUINT *				puiDataLength,
	FLMUINT32 *				pui32BlkAddr,
	FLMUINT *				puiOffsetIndex)
{
	RCODE					rc = NE_XFLM_OK;

	m_Stack[ 0].pucKeyBuf = pucKey;

	if( RC_BAD( rc = btLocateEntry( pucKey, uiKeyBufSize, puiKeyLen,
		XFLM_FIRST, NULL, puiDataLength, pui32BlkAddr, puiOffsetIndex)))
	{
		goto Exit;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Locate the last entry in the Btree and return the key.
****************************************************************************/
RCODE F_Btree::btLastEntry(
	FLMBYTE *				pucKey,
	FLMUINT					uiKeyBufSize,
	FLMUINT *				puiKeyLen,
	FLMUINT *				puiDataLength,
	FLMUINT32 *				pui32BlkAddr,
	FLMUINT *				puiOffsetIndex)
{
	RCODE				rc = NE_XFLM_OK;

	m_Stack[ 0].pucKeyBuf = pucKey;

	if( RC_BAD( rc = btLocateEntry( pucKey, uiKeyBufSize, puiKeyLen,
		XFLM_LAST, NULL, puiDataLength, pui32BlkAddr, puiOffsetIndex)))
	{
		if( rc == NE_XFLM_BOF_HIT)
		{
			rc = RC_SET( NE_XFLM_EOF_HIT);
		}
		
		goto Exit;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc: Function to search the Btree for a specific key.
****************************************************************************/
RCODE F_Btree::btPositionTo(
	FLMUINT					uiPosition,
	FLMBYTE *				pucKey,
	FLMUINT					uiKeyBufSize,
	FLMUINT *				puiKeyLen)
{
	RCODE				rc = NE_XFLM_OK;
	FLMBYTE *		pucEntry = NULL;

	flmAssert( pucKey && uiKeyBufSize && puiKeyLen);

	m_bSetupForRead = FALSE;

	if( !m_bOpened || !m_bCounts)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	// Verify the transaction type
	
	if( m_pDb->m_eTransType == XFLM_NO_TRANS && !m_bTempDb)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_NO_TRANS_ACTIVE);
		goto Exit;
	}

	// Find the entry we are interested in.
	
	if( RC_BAD(rc = positionToEntry( uiPosition)))
	{
		goto Exit;
	}

	m_ui64LowTransId = m_pStack->pBlkHdr->stdBlkHdr.ui64TransID;
	m_bMostCurrent = (m_pStack->pSCache->m_ui64HighTransID == FLM_MAX_UINT64)
																? TRUE
																: FALSE;

	m_ui32PrimaryBlkAddr = m_pStack->ui32BlkAddr;
	m_uiPrimaryOffset = m_pStack->uiCurOffset;
	m_ui32CurBlkAddr = m_ui32PrimaryBlkAddr;
	m_uiCurOffset = m_uiPrimaryOffset;

	// Point to the entry ...
	
	pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr, m_pStack->uiCurOffset);

	if( RC_BAD( rc = setupReadState( m_pStack->pSCache->m_pBlkHdr, pucEntry)))
	{
		goto Exit;
	}

	// In case the returning key is not what was originally requested, such
	// as in the case of XFLM_FIRST, XFLM_LAST, XFLM_EXCL and 
	// possibly XFLM_INCL, we will pass back the key we actually found.
	
	if( RC_BAD( rc = setReturnKey( pucEntry,
		m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType, pucKey, puiKeyLen,
		uiKeyBufSize)))
	{
		goto Exit;
	}

	m_bFirstRead = FALSE;
	m_bSetupForRead = TRUE;

Exit:

	if( m_pSCache)
	{
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}

	releaseBlocks( FALSE);
	return( rc);
}

/***************************************************************************
Desc:	Method to get the actual poisition of the entry.  Note: Must be
		maintaining counts in the Btree AND also have located to an entry
		first.  The key that is passed in is used only if we have to
		resynchronize due to a transaction change.
****************************************************************************/
RCODE F_Btree::btGetPosition(
	FLMBYTE *		pucKey,
	FLMUINT			uiKeyLen,
	FLMUINT *		puiPosition)
{
	RCODE				rc = NE_XFLM_OK;
	FLMUINT			uiKeyBufSize = uiKeyLen;
	FLMUINT			uiLocalKeyLen = uiKeyLen;

	if( !m_bOpened || !m_bSetupForRead || !m_bCounts)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	// Verify the transaction type
	
	if( m_pDb->m_eTransType == XFLM_NO_TRANS && !m_bTempDb)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_NO_TRANS_ACTIVE);
		goto Exit;
	}

	*puiPosition = 0;

	m_ui32CurBlkAddr = m_ui32PrimaryBlkAddr;
	m_uiCurOffset = m_uiPrimaryOffset;
	m_ui64LastBlkTransId = m_ui64PrimaryBlkTransId;

	// Do we need to resynchronize?
	
	if( !m_bTempDb &&
		 ((m_ui64CurrTransID != m_pDb->m_ui64CurrTransID) ||
		  (m_uiBlkChangeCnt != m_pDb->m_uiBlkChangeCnt)))
	{
		// Test to see if we really need to re-sync ...
		
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			m_ui32CurBlkAddr, NULL, &m_pSCache)))
		{
			goto Exit;
		}

		if( m_pSCache->m_pBlkHdr->ui64TransID != m_ui64LastBlkTransId ||
				(m_pDb->m_eTransType == XFLM_UPDATE_TRANS &&
					m_pDb->m_ui64CurrTransID == m_pSCache->m_pBlkHdr->ui64TransID))
		{
			// We can get the position easily if we have to re-sync.
			
			if( RC_BAD( rc = btLocateEntry( pucKey, uiKeyBufSize, &uiLocalKeyLen,
				XFLM_EXACT, puiPosition)))
			{
				goto Exit;
			}

			// Will need a new version of this block.
			
			ScaReleaseCache( m_pSCache, FALSE);
			m_pSCache = NULL;
		}
	}
	else
	{
		// To calculate the position, we will have to reconstruct the stack.
		
		m_pStack = &m_Stack[ m_uiStackLevels - 1];
		for (;;)
		{
			// Get the block at this level.
			
			flmAssert( m_pStack->ui32BlkAddr);
			flmAssert( m_pStack->pSCache == NULL);

			if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
				m_pStack->ui32BlkAddr, NULL, &m_pStack->pSCache)))
			{
				goto Exit;
			}

			m_pStack->pBlkHdr = (F_BTREE_BLK_HDR *)m_pStack->pSCache->m_pBlkHdr;

			*puiPosition += countRangeOfKeys( m_pStack, 0, m_pStack->uiCurOffset);

			if( (getBlkType( (FLMBYTE *)m_pStack->pBlkHdr) == BT_LEAF) ||
				 (getBlkType( (FLMBYTE *)m_pStack->pBlkHdr) == BT_LEAF_DATA))
			{
				break;
			}
			else
			{
				// Next level down. (stack is inverted).
				
				m_pStack--;
			}
		}
	}

Exit:

	releaseBlocks( FALSE);
	return( rc);
}

/***************************************************************************
Desc:	Method to rewind back to the beginning of the current entry.
****************************************************************************/
RCODE F_Btree::btRewind(
	FLMBYTE *			pucKey,
	FLMUINT				uiKeyBufSize,
	FLMUINT *			puiKeyLen)
{
	RCODE					rc = NE_XFLM_OK;
	F_CachedBlock *	pSCache = NULL;

	if( !m_bSetupForRead)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	m_ui32CurBlkAddr = m_ui32PrimaryBlkAddr;
	m_uiCurOffset = m_uiPrimaryOffset;
	m_ui64LastBlkTransId = m_ui64PrimaryBlkTransId;

	// Do we need to resync?
	
	if( !m_bTempDb &&
		 ((m_ui64CurrTransID != m_pDb->m_ui64CurrTransID) ||
		  (m_uiBlkChangeCnt != m_pDb->m_uiBlkChangeCnt)))
	{
		flmAssert( m_pSCache == NULL);

		// Test to see if we really need to re-sync ...
		
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			m_ui32CurBlkAddr, NULL, &m_pSCache)))
		{
			goto Exit;
		}

		if( m_pSCache->m_pBlkHdr->ui64TransID != m_ui64LastBlkTransId ||
				(m_pDb->m_eTransType == XFLM_UPDATE_TRANS &&
				  m_pDb->m_ui64CurrTransID == m_pSCache->m_pBlkHdr->ui64TransID))
		{
			// Won't need this block anymore.
			
			ScaReleaseCache( m_pSCache, FALSE);
			m_pSCache = NULL;

			if( RC_BAD( rc = btLocateEntry( pucKey, uiKeyBufSize, puiKeyLen,
													  XFLM_EXACT)))
			{
				goto Exit;
			}
			
			goto Exit;
		}
	}

	m_uiOADataRemaining = m_uiOADataLength;	// Track the overall length progress
	m_uiDataLength = m_uiPrimaryDataLen;		// Restore the primary block data length
	m_uiDataRemaining = m_uiDataLength;			// Track the local entry progress

	if( m_bDataOnlyBlock)
	{
		m_ui32CurBlkAddr = m_ui32DOBlkAddr;

		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			m_ui32DOBlkAddr, NULL, &pSCache)))
		{
			goto Exit;
		}
		
		m_ui64LastBlkTransId = pSCache->m_pBlkHdr->ui64TransID;

		// Local amount of data in this block
		
		m_uiDataRemaining = m_uiBlockSize -
										sizeofDOBlkHdr((F_BLK_HDR *)pSCache->m_pBlkHdr) -
										pSCache->m_pBlkHdr->ui16BlkBytesAvail;

		// Keep the actual local data size for later.
		
		m_uiDataLength = m_uiDataRemaining;

		// Now release the DO Block.  We will get it again when we need it.
		
		ScaReleaseCache( pSCache, FALSE);
		pSCache = NULL;
	}

	m_bFirstRead = FALSE;
	m_bSetupForRead = TRUE;

Exit:

	if( m_pSCache)
	{
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}

	releaseBlocks( FALSE);
	return( rc);
}

/***************************************************************************
Desc:	Method for computing the number of keys and blocks between two points 
		in the Btree.  The key count is inclusive of the two end points and
		the block count is exclusive of the two end points.
****************************************************************************/
RCODE F_Btree::btComputeCounts(
	F_Btree *		pUntilBtree,
	FLMUINT *		puiBlkCount,
	FLMUINT *		puiKeyCount,
	FLMBOOL *		pbTotalsEstimated,
	FLMUINT			uiAvgBlkFullness)
{
	RCODE			rc = NE_XFLM_OK;

	if( !m_bSetupForRead || !pUntilBtree->m_bSetupForRead)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	// Ensure that both Btrees are from the same container.
	
	if( m_pLFile->uiRootBlk != pUntilBtree->m_pLFile->uiRootBlk)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_FAILURE);
		goto Exit;
	}

	rc = computeCounts( m_pStack, pUntilBtree->m_pStack, puiBlkCount,
			  puiKeyCount, pbTotalsEstimated, uiAvgBlkFullness);

Exit:

	releaseBlocks( FALSE);
	pUntilBtree->releaseBlocks( FALSE);
	return( rc);
}

/***************************************************************************
Desc:	Function to release the blocks in the stack, and optionally, reset
		the stack
****************************************************************************/
void F_Btree::releaseBlocks(
	FLMBOOL			bResetStack)
{
	FLMUINT		uiLevel;

	// Release any blocks still held in the stack.
	
	for( uiLevel = 0; uiLevel <= m_uiRootLevel; uiLevel++)
	{
		if( m_Stack[ uiLevel].pSCache)
		{
			if( m_Stack[ uiLevel].pSCache->m_uiUseCount)
			{
				ScaReleaseCache( m_Stack[ uiLevel].pSCache, FALSE);
			}
			
			m_Stack[ uiLevel].pSCache = NULL;
			m_Stack[ uiLevel].pBlkHdr = NULL;
		}
		
		if( bResetStack)
		{
			m_Stack[ uiLevel].ui32BlkAddr = 0;
			m_Stack[ uiLevel].uiKeyLen = 0;
			m_Stack[ uiLevel].uiCurOffset = 0;
			m_Stack[ uiLevel].uiLevel = 0;
		}
	}
	
	if( bResetStack)
	{
		m_uiStackLevels = 0;
		m_uiRootLevel = 0;
		m_bStackSetup = FALSE;
		m_pStack = NULL;
	}
}

/***************************************************************************
Desc: Function to create a new block at the current level.  The new block 
		will always be inserted previous to the current block.  All entries
		that sort ahead of the current insertion point will be moved into
		the new block.  If there is room, the new entry will be inserted
		into the current block.  Otherwise, if there is room, the new entry
		will be inserted into the new block.  If there is still not enough
		room, then if possible, it try to store a partial entry in the new
		block. If we still cannot store anything, we will see if we can 
		store a partial entry in the current block.  If that does not work,
		then it will set the remaining amount and return.  Another block
		split will be needed before we store this entry.
****************************************************************************/
RCODE F_Btree::splitBlock(
	const FLMBYTE *		pucKey,
	FLMUINT					uiKeyLen,
	const FLMBYTE *		pucValue,
	FLMUINT					uiLen,
	FLMUINT					uiFlags,
	FLMUINT					uiOADataLen,
	FLMUINT					uiChildBlkAddr,
	FLMUINT 					uiCounts,
	const FLMBYTE **		ppucRemainingValue,
	FLMUINT *				puiRemainingLen,
	FLMBOOL *				pbBlockSplit)
{
	RCODE						rc = NE_XFLM_OK;
	F_CachedBlock *		pNewSCache = NULL;
	F_CachedBlock *		pPrevSCache = NULL;
	F_BTREE_BLK_HDR *		pBlkHdr = NULL;
	FLMUINT					uiBlkAddr;
	FLMUINT					uiEntrySize;
	FLMBOOL					bHaveRoom;
	FLMBOOL					bMovedToPrev = FALSE;
	FLMBOOL					bLastEntry;
	FLMUINT					uiMinEntrySize;
	FLMBOOL					bDefragBlk = FALSE;
	FLMBOOL					bSavedReplaceInfo = FALSE;

	// If the current block is a root block, then we will have to introduce
	// a new level into the B-Tree.
	
	if( isRootBlk( m_pStack->pBlkHdr))
	{
		if( RC_BAD( rc = createNewLevel()))
		{
			goto Exit;
		}
	}

	// If the current block is empty we must insert what we can here.
	// This scenario only occurs when we are engaged in a ReplaceByInsert
	// operation. Normal inserts would never result in an empty block.
	// Since we know we are part of a replace operation, we know that the
	// parent of this block only needs the counts updated, not the key.
	
	if( m_pStack->uiLevel == 0 && m_pStack->pBlkHdr->ui16NumKeys == 0)
	{
		if( RC_BAD( rc = storePartialEntry( pucKey, uiKeyLen, pucValue,
				uiLen, uiFlags, uiChildBlkAddr, uiCounts, ppucRemainingValue,
				puiRemainingLen, FALSE)))
		{
			goto Exit;
		}
		
		*pbBlockSplit = FALSE;
		goto MoveToPrev;
	}

	// Create a new block and insert it as previous to this block.
	
	if( RC_BAD( rc = m_pDb->m_pDatabase->createBlock( m_pDb, &pNewSCache)))
	{
		goto Exit;
	}

	*pbBlockSplit = TRUE;

	// Setup the header ...
	
	pBlkHdr = (F_BTREE_BLK_HDR *)pNewSCache->m_pBlkHdr;

	unsetRootBlk( pBlkHdr);
	setBlkLfType( pBlkHdr, m_pLFile->eLfType);

	pBlkHdr->ui16NumKeys = 0;
	pBlkHdr->ui8BlkLevel = (FLMUINT8)m_pStack->uiLevel;
	pBlkHdr->stdBlkHdr.ui8BlkType = m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType;
	pBlkHdr->ui16LogicalFile = m_pStack->pBlkHdr->ui16LogicalFile;
	
	// Check for encrypted block.
	
	if( isEncryptedBlk( (F_BLK_HDR *)m_pStack->pBlkHdr))
	{
		setBlockEncrypted( (F_BLK_HDR *)pBlkHdr);
	}

	pBlkHdr->stdBlkHdr.ui16BlkBytesAvail =
		(FLMUINT16)(m_uiBlockSize - sizeofBTreeBlkHdr(pBlkHdr));

	pBlkHdr->ui16HeapSize =
		(FLMUINT16)(m_uiBlockSize - sizeofBTreeBlkHdr(pBlkHdr));

	// We are going to make changes to the current block.  The pSCache could
	// have changed since making this call, so we need to update the block
	// header
	
	if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk(
		m_pDb, &m_pStack->pSCache)))
	{
		goto Exit;
	}

	m_pStack->pBlkHdr = (F_BTREE_BLK_HDR *)m_pStack->pSCache->m_pBlkHdr;
	m_pStack->pui16OffsetArray =
							  BtOffsetArray( (FLMBYTE *)m_pStack->pBlkHdr, 0);

	// Get the current previous block if there is one.
	
	uiBlkAddr = m_pStack->pBlkHdr->stdBlkHdr.ui32PrevBlkInChain;

	if( uiBlkAddr)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			uiBlkAddr, NULL, &pPrevSCache)))
		{
			goto Exit;
		}

		if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &pPrevSCache)))
		{
			goto Exit;
		}
	}

	// Link the new block between the current and it's previous
	
	pBlkHdr->stdBlkHdr.ui32NextBlkInChain = m_pStack->ui32BlkAddr;
	pBlkHdr->stdBlkHdr.ui32PrevBlkInChain = (FLMUINT32)uiBlkAddr;

	m_pStack->pBlkHdr->stdBlkHdr.ui32PrevBlkInChain =
											pBlkHdr->stdBlkHdr.ui32BlkAddr;

	// There may not be a previous block.
	
	if( pPrevSCache)
	{
		pPrevSCache->m_pBlkHdr->ui32NextBlkInChain =
											pBlkHdr->stdBlkHdr.ui32BlkAddr;

		// Release the old previous block since we no longer need it.
		
		ScaReleaseCache( pPrevSCache, FALSE);
		pPrevSCache = NULL;
	}

	// We will move all entries in the current block up to but NOT including
	// the entry pointed to by uiCurOffset to the new block.
	
	if( m_pStack->uiCurOffset > 0)
	{
		if( RC_BAD( rc = moveToPrev( 0, m_pStack->uiCurOffset - 1, &pNewSCache)))
		{
			goto Exit;
		}
		
		// All entries prior to the old insertion point were moved.
		// Therefore, the new insertion point must be at the beginning.
		
		m_pStack->uiCurOffset = 0;

		// If we emptied the block.  This will require us to update the parent.

		if( m_pStack->pBlkHdr->ui16NumKeys == 0)
		{
			if (RC_BAD( rc = saveReplaceInfo( pucKey, uiKeyLen)))
			{
				goto Exit;
			}
			
			bSavedReplaceInfo = TRUE;
		}
	}

	// If the block is now empty, we will store a partial entry in it here.
	// This scenario only occurs when we are engaged in a ReplaceByInsert
	// operation. Normal inserts would never result in an empty block.
	// Since we know we are part of a replace operation, we know that the
	// parent of this block only needs the counts updated, not the key.
	
	if( m_pStack->uiLevel == 0 && m_pStack->pBlkHdr->ui16NumKeys == 0)
	{
		if( RC_BAD( rc = storePartialEntry( pucKey, uiKeyLen, pucValue, uiLen,
			uiFlags, uiChildBlkAddr, uiCounts, ppucRemainingValue, 
			puiRemainingLen, FALSE)))
		{
			goto Exit;
		}

		goto MoveToPrev;
	}

	// Is there room for the new entry now in the current block?
	
	if( RC_BAD( rc = calcNewEntrySize( uiKeyLen, uiLen, &uiEntrySize,
		&bHaveRoom, &bDefragBlk)))
	{
		goto Exit;
	}

	if( bHaveRoom)
	{
		if( bDefragBlk)
		{
			if( RC_BAD( rc = defragmentBlock( &m_pStack->pSCache)))
			{
				goto Exit;
			}
		}

		if( RC_BAD( rc = storeEntry( pucKey, uiKeyLen, pucValue, uiLen,
				uiFlags, uiOADataLen, uiChildBlkAddr, uiCounts, uiEntrySize,
				&bLastEntry)))
		{
			goto Exit;
		}

		if( bLastEntry && !bSavedReplaceInfo)
		{
			// Since we just added/replaced an entry to the last position of the
			// current block. we will need to preserve the current stack so that
			// we can finish updating the parentage later. Should only happen as
			// a result of a replace operation where the new entry is larger than
			// the existing one while in the upper levels.
			
			if( RC_BAD( rc = saveReplaceInfo( pucKey, uiKeyLen)))
			{
				goto Exit;
			}
		}

		// If we are keeping counts, we must update those too.
		
		if( m_bCounts && !isRootBlk( m_pStack->pBlkHdr))
		{
			if( RC_BAD( rc = updateCounts()))
			{
				goto Exit;
			}
		}

		if( m_pStack->uiLevel == 0)
		{
			*ppucRemainingValue = NULL;
			*puiRemainingLen = 0;
		}

		goto MoveToPrev;
	}

	// Can we store the whole thing in the new block?
	
	if( uiEntrySize <= pBlkHdr->stdBlkHdr.ui16BlkBytesAvail)
	{
		// If this block has a parent block, and the btree is maintaining counts
		// we will want to update the counts on the parent block.
		
		if( m_bCounts && !isRootBlk( m_pStack->pBlkHdr))
		{
			if (RC_BAD( rc = updateCounts()))
			{
				goto Exit;
			}
		}

		// We can release the current block since it is no longer needed.
		
		ScaReleaseCache( m_pStack->pSCache, FALSE);

		m_pStack->pSCache = pNewSCache;
		pNewSCache = NULL;
		m_pStack->pBlkHdr = (F_BTREE_BLK_HDR *)pBlkHdr;
		m_pStack->ui32BlkAddr = pBlkHdr->stdBlkHdr.ui32BlkAddr;
		m_pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pBlkHdr, 0);

		// Setting the uiCurOffset to the actual number of keys will cause the
		// new entry to go in as the last element.
		
		m_pStack->uiCurOffset = m_pStack->pBlkHdr->ui16NumKeys;

		// We don't need to check to see if we need to defragment this block
		// because it is "new".  Anything that just got written to it will
		// be contiguous already.

		if( RC_BAD( rc = storeEntry( pucKey, uiKeyLen, pucValue, uiLen,
			uiFlags, uiOADataLen, uiChildBlkAddr, uiCounts, uiEntrySize,
			&bLastEntry)))
		{
			goto Exit;
		}

		flmAssert( bLastEntry);

		if( m_pStack->uiLevel == 0)
		{
			*ppucRemainingValue = NULL;
			*puiRemainingLen = 0;
		}

		bMovedToPrev = TRUE;
		goto MoveToPrev;
	}

	// Can we store part of the new entry into the new block?
	// Calculate the minimum entry size to store.
	
	if( RC_BAD( rc = calcNewEntrySize( uiKeyLen, 1, &uiMinEntrySize,
		&bHaveRoom, &bDefragBlk)))
	{
		goto Exit;
	}

	// bHaveRoom refers to the current block, and we want to put this into
	// the previous  block.
	
	if( uiMinEntrySize <= pBlkHdr->stdBlkHdr.ui16BlkBytesAvail)
	{
		// If this block has a parent block, and the btree is maintaining counts
		// we will want to update the counts on the parent block.
		
		if( !isRootBlk( m_pStack->pBlkHdr))
		{
			if( m_bCounts)
			{
				if( RC_BAD( rc = updateCounts()))
				{
					goto Exit;
				}
			}
		}

		// We can release the current block since it is no longer needed.
		
		ScaReleaseCache( m_pStack->pSCache, FALSE);

		m_pStack->pSCache = pNewSCache;
		pNewSCache = NULL;
		m_pStack->pBlkHdr = pBlkHdr;
		m_pStack->ui32BlkAddr = pBlkHdr->stdBlkHdr.ui32BlkAddr;
		m_pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pBlkHdr, 0);

		// Setting the uiCurOffset to the actual number of keys will cause the
		// new entry to go in as the last element.
		
		m_pStack->uiCurOffset = m_pStack->pBlkHdr->ui16NumKeys;

		if( RC_BAD( rc = storePartialEntry( pucKey, uiKeyLen, pucValue,
			uiLen, uiFlags, uiChildBlkAddr, uiCounts, ppucRemainingValue,
			puiRemainingLen, TRUE)))
		{
			goto Exit;
		}

		bMovedToPrev = TRUE;
	}
	else if( uiMinEntrySize <= m_pStack->pBlkHdr->stdBlkHdr.ui16BlkBytesAvail)
	{
		// We will store part of the entry in the current block
		
		if( RC_BAD( rc = storePartialEntry(
			pucKey, uiKeyLen, pucValue, uiLen, uiFlags, uiChildBlkAddr, uiCounts,
			ppucRemainingValue, puiRemainingLen, FALSE)))
		{
			goto Exit;
		}
	}
	else
	{
		// Couldn't store anything, so try again after updating the parents.
		
		*ppucRemainingValue = pucValue;
		*puiRemainingLen = uiLen;
	}

MoveToPrev:

	if( *pbBlockSplit)
	{
		// Release the current entry if it hasn't already been released.
		
		if( !bMovedToPrev && RC_OK( rc))
		{

			// If this block has a parent block, and the btree is maintaining counts
			// we will want to update the counts on the parent block.
			
			if( !isRootBlk( m_pStack->pBlkHdr) && m_bCounts)
			{
				if( RC_BAD( rc = updateCounts()))
				{
					goto Exit;
				}
			}

			ScaReleaseCache( m_pStack->pSCache, FALSE);

			flmAssert( pNewSCache);

			m_pStack->pSCache = pNewSCache;
			pNewSCache = NULL;
			m_pStack->pBlkHdr = (F_BTREE_BLK_HDR *)pBlkHdr;
			m_pStack->ui32BlkAddr = pBlkHdr->stdBlkHdr.ui32BlkAddr;
			m_pStack->uiCurOffset = m_pStack->pBlkHdr->ui16NumKeys - 1;
			m_pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pBlkHdr, 0);
		}
	}

Exit:

	if( *pbBlockSplit)
	{
		if( m_pDb->m_pDbStats)
		{
			XFLM_LFILE_STATS *		pLFileStats;

			if( (pLFileStats = m_pDb->getLFileStatPtr( m_pLFile)) != NULL)
			{
				pLFileStats->bHaveStats = TRUE;
				pLFileStats->ui64BlockSplits++;
			}
		}
	}

	if( pPrevSCache)
	{
		ScaReleaseCache( pPrevSCache, FALSE);
	}

	if( pNewSCache)
	{
		ScaReleaseCache( pNewSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc: Function to create a new level in the Btree.
		This function will ensure that the F_BTSK stack is consistent with
		the way it was configured before the function was called.

		This function will create a new block and copy the current contents
		of the root block into it.  It will then insert a single entry into
		the root block to point to the new child.

		Note that there is a maximum of BH_MAX_LEVELS levels to the Btree.
		Any effort to exceed that level will result in an error.
****************************************************************************/
RCODE F_Btree::createNewLevel( void)
{
	RCODE					rc = NE_XFLM_OK;
	F_CachedBlock *	pNewSCache = NULL;
	FLMBYTE *			pSrcBlk;
	FLMBYTE *			pDstBlk;
	F_BTREE_BLK_HDR *	pBlkHdr;
	FLMUINT				uiCounts = 0;
	FLMBYTE *			pucEntry;
	FLMBYTE *			pucNull = NULL;
	FLMBYTE				ucBuffer[ XFLM_MAX_KEY_SIZE + BTE_NLC_KEY_START];
	FLMUINT				uiMaxNLKey = XFLM_MAX_KEY_SIZE + BTE_NLC_KEY_START;
	FLMUINT				uiEntrySize;
	F_BTSK *				pRootStack;
	FLMUINT				uiFlags;

	// Assert that we are looking at the root block!
	
	flmAssert( isRootBlk( m_pStack->pBlkHdr));

	// Check the root level
	
	if( m_pStack->uiLevel >= BH_MAX_LEVELS - 1)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_FULL);
		goto Exit;
	}

	// Create a new block to copy the contents of the root block into
	
	if( RC_BAD( rc = m_pDb->m_pDatabase->createBlock( m_pDb, &pNewSCache)))
	{
		RC_UNEXPECTED_ASSERT( rc);
		goto Exit;
	}

	// Log that we are about to change the root block
	
	if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &m_pStack->pSCache)))
	{
		goto Exit;
	}

	// Update the stack since the pSCache could have changed
	
	m_pStack->pBlkHdr = (F_BTREE_BLK_HDR *)m_pStack->pSCache->m_pBlkHdr;
	m_pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)m_pStack->pBlkHdr, 0);

	// Copy the data from the root block to the new block
	
	pSrcBlk = (FLMBYTE *)m_pStack->pui16OffsetArray;
	pBlkHdr = (F_BTREE_BLK_HDR *)pNewSCache->m_pBlkHdr;

	// Check for encryption

	if( isEncryptedBlk( (F_BLK_HDR *)m_pStack->pBlkHdr))
	{
		setBlockEncrypted( (F_BLK_HDR *)pBlkHdr);
	}

	pDstBlk = (FLMBYTE *)BtOffsetArray( (FLMBYTE *)pBlkHdr, 0);

	unsetRootBlk( pBlkHdr);
	setBlkLfType( pBlkHdr, m_pLFile->eLfType);

	pBlkHdr->ui16LogicalFile = (FLMUINT16)m_pLFile->uiLfNum;
	pBlkHdr->ui16NumKeys = m_pStack->pBlkHdr->ui16NumKeys;
	pBlkHdr->ui8BlkLevel = m_pStack->pBlkHdr->ui8BlkLevel;
	pBlkHdr->ui16HeapSize = m_pStack->pBlkHdr->ui16HeapSize;

	pBlkHdr->stdBlkHdr.ui8BlkType =
		((F_BLK_HDR *)m_pStack->pBlkHdr)->ui8BlkType;

	pBlkHdr->stdBlkHdr.ui16BlkBytesAvail =
		((F_BLK_HDR *)m_pStack->pBlkHdr)->ui16BlkBytesAvail;

	pBlkHdr->stdBlkHdr.ui32PrevBlkInChain = 0;
	pBlkHdr->stdBlkHdr.ui32NextBlkInChain = 0;

	// Copy the data from the root block to the new block.
	
	f_memcpy( pDstBlk, pSrcBlk, m_uiBlockSize - sizeofBTreeBlkHdr( pBlkHdr));

	// Empty out the root block data.

#ifdef FLM_DEBUG
	f_memset( BtOffsetArray( (FLMBYTE *)m_pStack->pBlkHdr, 0),
				 0, m_uiBlockSize - sizeofBTreeBlkHdr( m_pStack->pBlkHdr));
#endif

	m_pStack->pBlkHdr->ui16NumKeys = 0;
	m_pStack->pBlkHdr->ui16HeapSize = 
		((F_BLK_HDR *)m_pStack->pBlkHdr)->ui16BlkBytesAvail =
			(FLMUINT16)(m_uiBlockSize - sizeofBTreeBlkHdr( m_pStack->pBlkHdr));

	// Check the root block type to see if we need to change it.  The root
	// block may have been a leaf node.
	
	if( (m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType == BT_LEAF) ||
		 (m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType == BT_LEAF_DATA))
	{
		// Need to set the block type to either 
		// BT_NON_LEAF or BT_NON_LEAF_COUNTS
		
		if( m_bCounts)
		{
			m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType = BT_NON_LEAF_COUNTS;
		}
		else
		{
			m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType = BT_NON_LEAF;
		}
	}

	// Now add a new entry to the stack.
	
	pRootStack = m_pStack;
	pRootStack++;

	f_memcpy( pRootStack, m_pStack, sizeof( F_BTSK));

	// Now fix the entries in the stack.
	
	pRootStack->uiLevel++;
	pRootStack->pBlkHdr->ui8BlkLevel++;
	pRootStack->uiCurOffset = 0;  // First entry
	pRootStack->pui16OffsetArray = BtOffsetArray( 
												(FLMBYTE *)pRootStack->pBlkHdr, 0);

	m_pStack->pBlkHdr = pBlkHdr;  // Point to new block
	m_pStack->ui32BlkAddr = (FLMUINT32)pNewSCache->m_uiBlkAddress;
	m_pStack->pSCache = pNewSCache;
	pNewSCache = NULL;
	m_pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pBlkHdr, 0);

	// Build a new entry for the root block that will point to the newly created
	// child block. If the root block type is BT_NON_LEAF_COUNTS, then we
	// need to sum the counts from the child block
	
	if( m_bCounts)
	{
		uiCounts = countKeys( (FLMBYTE *)m_pStack->pBlkHdr);
	}

	// Create and insert a LEM entry to mark the last position in the block.
	
	uiFlags = BTE_FLAG_LAST_ELEMENT | BTE_FLAG_FIRST_ELEMENT;

	if( RC_BAD( rc = buildAndStoreEntry( 
		((F_BLK_HDR *)pRootStack->pBlkHdr)->ui8BlkType,
		uiFlags, pucNull, 0, pucNull, 0, 0, m_pStack->ui32BlkAddr,
		uiCounts, &ucBuffer[ 0], uiMaxNLKey, &uiEntrySize)))
	{
		goto Exit;
	}

	// Copy the entry into the root block.
	
	pucEntry = (FLMBYTE *)pRootStack->pBlkHdr + m_uiBlockSize - uiEntrySize;
	f_memcpy( pucEntry, &ucBuffer[ 0], uiEntrySize);
	bteSetEntryOffset( pRootStack->pui16OffsetArray, 0,
							 (FLMUINT16)(pucEntry - (FLMBYTE *)pRootStack->pBlkHdr));

	pRootStack->pBlkHdr->ui16NumKeys++;

	pRootStack->pBlkHdr->stdBlkHdr.ui16BlkBytesAvail -= (FLMUINT16)uiEntrySize + 2;

	pRootStack->pBlkHdr->ui16HeapSize -= (FLMUINT16)uiEntrySize + 2;

	m_uiStackLevels++;
	m_uiRootLevel++;

Exit:

	if( pNewSCache)
	{
		ScaReleaseCache( pNewSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc:	Method to calculate the optimal data length size to store.  This 
		method is called when storing a partial entry, and we need to know
		what the largest data size we c an store is.
****************************************************************************/
RCODE F_Btree::calcOptimalDataLength(
	FLMUINT			uiKeyLen,
	FLMUINT			uiDataLen,
	FLMUINT			uiBytesAvail,
	FLMUINT *		puiNewDataLen)
{
	RCODE				rc = NE_XFLM_OK;
	FLMUINT			uiFixedAmounts;
	FLMUINT			uiRemainder;

	switch( ((F_BLK_HDR *)m_pStack->pBlkHdr)->ui8BlkType)
	{
		case BT_LEAF:
		case BT_NON_LEAF:
		case BT_NON_LEAF_COUNTS:
		{
			// These blocks do not have any data.
			
			*puiNewDataLen = 0;
			break;
		}

		case BT_LEAF_DATA:
		{
			// These amounts don't change. Note that the overhead includes the
			// Overall Data Length Field, even though it may not be there in
			// the end.
			
			uiFixedAmounts = BTE_LEAF_DATA_OVHD +
								  (uiKeyLen > ONE_BYTE_SIZE ? 2 : 1) +
								  uiKeyLen;

			uiRemainder = uiBytesAvail - uiFixedAmounts;

			if (uiRemainder >= (ONE_BYTE_SIZE + 2))
			{
				*puiNewDataLen = uiRemainder - 2;
			}
			else
			{
				*puiNewDataLen = uiRemainder - 1;
			}
			break;
		}

		default:
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
			goto Exit;
		}
	}

	if( uiDataLen < *puiNewDataLen)
	{
		*puiNewDataLen = uiDataLen;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	This function will count the total number of keys in the block. 
		Typically the value ui16NumKeys will yield this number, however, if
		the block type is BT_NON_LEAF_COUNTS, we also want to include the 
		counts in each entry.
****************************************************************************/
RCODE F_Btree::updateParentCounts(
	F_CachedBlock *	pChildSCache,
	F_CachedBlock **	ppParentSCache,
	FLMUINT				uiParentElm)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT				uiCounts;
	FLMBYTE *			pucCounts;
	F_CachedBlock *	pParentSCache;
	FLMBYTE *			pBlk = (FLMBYTE *)pChildSCache->m_pBlkHdr; 

	flmAssert( getBlkType( pBlk) == BT_NON_LEAF_COUNTS);
	uiCounts = countKeys( pBlk);

	if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, ppParentSCache)))
	{
		goto Exit;
	}

	pParentSCache = *ppParentSCache;
	pucCounts = BtEntry( (FLMBYTE *)pParentSCache->m_pBlkHdr, uiParentElm);
	pucCounts += 4;
	UD2FBA( (FLMUINT32)uiCounts, pucCounts);

Exit:

	return( rc);
}

/***************************************************************************
Desc:	This function will count the total number of keys in the block.
		Typically the value ui16NumKeys will yield this number, however, if 
		the block type is BT_NON_LEAF_COUNTS, we also want to include the 
		counts in each entry.
****************************************************************************/
FLMUINT F_Btree::countKeys(
	FLMBYTE *			pBlk)
{
	FLMUINT				uiTotal = 0;
	FLMUINT				uiIndex;
	FLMBYTE *			pucEntry;
	FLMUINT16 *			puiOffsetArray;

	puiOffsetArray = BtOffsetArray( pBlk, 0);

	if( getBlkType(pBlk) != BT_NON_LEAF_COUNTS)
	{
		uiTotal = ((F_BTREE_BLK_HDR *)pBlk)->ui16NumKeys;
	}
	else
	{
		for (uiIndex = 0; uiIndex <
			((F_BTREE_BLK_HDR *)pBlk)->ui16NumKeys; uiIndex++)
		{
			pucEntry = BtEntry( pBlk, uiIndex);
			uiTotal += FB2UD( &pucEntry[ BTE_NLC_COUNTS]);
		}
	}

	return( uiTotal);
}

/***************************************************************************
Desc: Function to store an entry in a Data-only block.
****************************************************************************/
RCODE F_Btree::storeDataOnlyBlocks(
	const FLMBYTE *	pucKey,
	FLMUINT				uiKeyLen,
	FLMBOOL				bSaveKey,
	const FLMBYTE *	pucData,
	FLMUINT				uiDataLen)
{
	RCODE					rc = NE_XFLM_OK;
	F_CachedBlock *	pPrevSCache = NULL;
	const FLMBYTE *	pucLocalData = pucData;
	FLMUINT				uiDataToWrite = uiDataLen;
	F_BLK_HDR *			pBlkHdr = NULL;
	FLMBYTE *			pDestPtr = NULL;
	FLMUINT				uiAmtToCopy;

	if( bSaveKey)
	{
		if( !m_pSCache)
		{
			if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
				m_ui32CurBlkAddr, NULL, &m_pSCache)))
			{
				goto Exit;
			}
		}

		if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &m_pSCache)))
		{
			goto Exit;
		}

		// Assert that the current block is empty and has no previous link.
		
		flmAssert( m_pSCache->m_pBlkHdr->ui16BlkBytesAvail ==
			m_uiBlockSize - sizeofDOBlkHdr( (F_BLK_HDR *)m_pSCache->m_pBlkHdr));

		flmAssert( m_pSCache->m_pBlkHdr->ui32PrevBlkInChain == 0);

		pBlkHdr =  m_pSCache->m_pBlkHdr;
		pDestPtr = (FLMBYTE *)pBlkHdr + 
							sizeofDOBlkHdr( (F_BLK_HDR *)m_pSCache->m_pBlkHdr);

		UW2FBA( (FLMUINT16)uiKeyLen, pDestPtr);
		pDestPtr += sizeof( FLMUINT16);

		f_memcpy( pDestPtr, pucKey, uiKeyLen);
		pDestPtr += uiKeyLen;
		
		m_uiDataRemaining -= (uiKeyLen + sizeof( FLMUINT16));
		pBlkHdr->ui16BlkBytesAvail = (FLMUINT16)m_uiDataRemaining;
	}

	while( uiDataToWrite > 0)
	{
		if( !m_pSCache)
		{
			if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
				m_ui32CurBlkAddr, NULL, &m_pSCache)))
			{
				goto Exit;
			}
		}

		if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &m_pSCache)))
		{
			goto Exit;
		}

		if( !bSaveKey)
		{
			pBlkHdr = m_pSCache->m_pBlkHdr;

			// Now copy as much of the remaining data as we  can into the new block.
			
			pDestPtr = (FLMBYTE *)pBlkHdr + sizeofDOBlkHdr( (F_BLK_HDR *)pBlkHdr );
			pDestPtr += (m_uiBlockSize - 
								sizeofDOBlkHdr( (F_BLK_HDR *)pBlkHdr ) -
								m_uiDataRemaining);
		}
		else
		{
			bSaveKey = FALSE;
		}

		uiAmtToCopy = (uiDataToWrite <= m_uiDataRemaining
								? uiDataToWrite
								: m_uiDataRemaining);

		f_memcpy( pDestPtr, pucLocalData, uiAmtToCopy);

		m_uiDataRemaining -= uiAmtToCopy;
		m_uiOADataLength += uiAmtToCopy;
		uiDataToWrite -= uiAmtToCopy;
		pucLocalData += uiAmtToCopy;
		pBlkHdr->ui16BlkBytesAvail = (FLMUINT16)m_uiDataRemaining;

		// Now get the next block (if needed)
		
		if( uiDataToWrite)
		{
			pPrevSCache = m_pSCache;
			m_pSCache = NULL;

			// Now create a new block
			
			if( RC_BAD( rc = m_pDb->m_pDatabase->createBlock( m_pDb, &m_pSCache)))
			{
				goto Exit;
			}

			pBlkHdr = m_pSCache->m_pBlkHdr;
			pBlkHdr->ui8BlkType = BT_DATA_ONLY;
			pBlkHdr->ui32PrevBlkInChain = pPrevSCache->m_pBlkHdr->ui32BlkAddr;
			pBlkHdr->ui32NextBlkInChain = 0;

			if( m_pLFile->uiEncId)
			{
				((F_ENC_DO_BLK_HDR *)pBlkHdr)->ui32EncId = (FLMUINT32)m_pLFile->uiEncId;
				setBlockEncrypted( pBlkHdr);
			}

			pBlkHdr->ui16BlkBytesAvail =	
				(FLMUINT16)(m_uiBlockSize - sizeofDOBlkHdr( pBlkHdr));

			pPrevSCache->m_pBlkHdr->ui32NextBlkInChain = pBlkHdr->ui32BlkAddr;

			m_ui32CurBlkAddr = pBlkHdr->ui32BlkAddr;
			m_uiDataRemaining = m_uiBlockSize - sizeofDOBlkHdr( pBlkHdr);

			if( pPrevSCache)
			{
				ScaReleaseCache( pPrevSCache, FALSE);
				pPrevSCache = NULL;
			}
		}
	}

Exit:

	if( m_pSCache)
	{
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}
	
	if( pPrevSCache)
	{
		ScaReleaseCache( pPrevSCache, FALSE);
	}
	
	return( rc);
}

/***************************************************************************
Desc: Function to Replace data in data only blocks.
****************************************************************************/
RCODE F_Btree::replaceDataOnlyBlocks(
	const FLMBYTE *	pucKey,
	FLMUINT				uiKeyLen,
	FLMBOOL				bSaveKey,
	const FLMBYTE *	pucData,
	FLMUINT				uiDataLen,
	FLMBOOL				bLast,
	FLMBOOL				bTruncate)
{
	RCODE						rc = NE_XFLM_OK;
	F_CachedBlock *		pPrevSCache = NULL;
	const FLMBYTE *		pucLocalData = pucData;
	FLMUINT					uiDataToWrite = uiDataLen;
	F_BLK_HDR *				pBlkHdr = NULL;
	FLMBYTE *				pDestPtr = NULL;
	FLMUINT					uiAmtToCopy;
	FLMUINT32				ui32NextBlkAddr;

	// Do we need to store the key too?
	
	if( bSaveKey)
	{
		if( !m_pSCache)
		{
			if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
				m_ui32CurBlkAddr, NULL, &m_pSCache)))
			{
				goto Exit;
			}
		}

		if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &m_pSCache)))
		{
			goto Exit;
		}

		// Assert that the current block is empty and has no previous link.
		
		flmAssert( m_pSCache->m_pBlkHdr->ui16BlkBytesAvail ==
			m_uiBlockSize - sizeofDOBlkHdr( (F_BLK_HDR *)m_pSCache->m_pBlkHdr));

		flmAssert( m_pSCache->m_pBlkHdr->ui32PrevBlkInChain == 0);

		pBlkHdr = m_pSCache->m_pBlkHdr;
		pDestPtr = (FLMBYTE *)pBlkHdr + 
							sizeofDOBlkHdr( (F_BLK_HDR *)m_pSCache->m_pBlkHdr );

		UW2FBA( (FLMUINT16)uiKeyLen, pDestPtr);
		pDestPtr += sizeof( FLMUINT16);

		f_memcpy( pDestPtr, pucKey, uiKeyLen);
		pDestPtr += uiKeyLen;
		
		m_uiDataRemaining -= (uiKeyLen + sizeof( FLMUINT16));
		pBlkHdr->ui16BlkBytesAvail = (FLMUINT16)m_uiDataRemaining;
	}

	while( uiDataToWrite > 0)
	{
		if( !m_pSCache)
		{
			if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
				m_ui32CurBlkAddr, NULL, &m_pSCache)))
			{
				goto Exit;
			}
		}

		if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &m_pSCache)))
		{
			goto Exit;
		}

		if( !bSaveKey)
		{
			pBlkHdr = m_pSCache->m_pBlkHdr;

			// Now copy as much of the remaining data as we  can into the new block.
			
			pDestPtr = (FLMBYTE *)pBlkHdr + sizeofDOBlkHdr( (F_BLK_HDR *)pBlkHdr);
			pDestPtr += (m_uiBlockSize - sizeofDOBlkHdr( 
								(F_BLK_HDR *)pBlkHdr ) - m_uiDataRemaining);
		}
		else
		{
			bSaveKey = FALSE;
		}

		uiAmtToCopy = (uiDataToWrite <= m_uiDataRemaining
								? uiDataToWrite
								: m_uiDataRemaining);

		f_memcpy( pDestPtr, pucLocalData, uiAmtToCopy);

		m_uiDataRemaining -= uiAmtToCopy;
		m_uiOADataLength += uiAmtToCopy;
		uiDataToWrite -= uiAmtToCopy;
		pucLocalData += uiAmtToCopy;

		if( bTruncate || (m_uiDataRemaining < pBlkHdr->ui16BlkBytesAvail))
		{
			pBlkHdr->ui16BlkBytesAvail = (FLMUINT16)m_uiDataRemaining;
		}

		// Now get the next block (if needed)
		
		if( uiDataToWrite)
		{
			pPrevSCache = m_pSCache;
			m_pSCache = NULL;
			ui32NextBlkAddr = pPrevSCache->m_pBlkHdr->ui32NextBlkInChain;
			
			if( ui32NextBlkAddr)
			{
				if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
					ui32NextBlkAddr, NULL, &m_pSCache)))
				{
					goto Exit;
				}

				if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( 
					m_pDb, &m_pSCache)))
				{
					goto Exit;
				}
				pBlkHdr = m_pSCache->m_pBlkHdr;
			}
			else
			{
				// Now create a new block
				
				if( RC_BAD( rc = m_pDb->m_pDatabase->createBlock(
					m_pDb, &m_pSCache)))
				{
					goto Exit;
				}

				pBlkHdr = m_pSCache->m_pBlkHdr;
				pBlkHdr->ui8BlkType = BT_DATA_ONLY;
				pBlkHdr->ui32PrevBlkInChain = pPrevSCache->m_pBlkHdr->ui32BlkAddr;
				pBlkHdr->ui32NextBlkInChain = 0;

				if( m_pLFile->uiEncId)
				{
					setBlockEncrypted( pBlkHdr);
					((F_ENC_DO_BLK_HDR *)pBlkHdr)->ui32EncId = (FLMUINT32)m_pLFile->uiEncId;
				}

				pBlkHdr->ui16BlkBytesAvail =
						(FLMUINT16)(m_uiBlockSize - sizeofDOBlkHdr( pBlkHdr));
			}

			pPrevSCache->m_pBlkHdr->ui32NextBlkInChain = pBlkHdr->ui32BlkAddr;

			m_ui32CurBlkAddr = pBlkHdr->ui32BlkAddr;
			m_uiDataRemaining = m_uiBlockSize - sizeofDOBlkHdr( pBlkHdr);

			if( pPrevSCache)
			{
				ScaReleaseCache( pPrevSCache, FALSE);
				pPrevSCache = NULL;
			}
		}
	}

	// If this was the last pass to store the data, then see if we need to
	// remove any left over blocks.  We will not truncate the data if
	// the bTruncate parameter is not set.
	
	if( bLast && bTruncate)
	{
		flmAssert( m_pSCache);

		ui32NextBlkAddr = m_pSCache->m_pBlkHdr->ui32NextBlkInChain;
		m_pSCache->m_pBlkHdr->ui32NextBlkInChain = 0;
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;

		// If there are any blocks left over, they must be freed.
		
		while( ui32NextBlkAddr)
		{
			if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
				ui32NextBlkAddr, NULL, &m_pSCache)))
			{
				goto Exit;
			}

			ui32NextBlkAddr = m_pSCache->m_pBlkHdr->ui32NextBlkInChain;

			rc = m_pDb->m_pDatabase->blockFree( m_pDb, m_pSCache);
			m_pSCache = NULL;
			
			if( RC_BAD( rc))
			{
				goto Exit;
			}
		}
	}

Exit:

	if( m_pSCache)
	{
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}
	
	if( pPrevSCache)
	{
		ScaReleaseCache( pPrevSCache, FALSE);
	}
	
	return( rc);
}

/***************************************************************************
Desc:	Method to construct a new leaf entry using the key and value
		information passed in.
****************************************************************************/
RCODE F_Btree::buildAndStoreEntry(
	FLMUINT				uiBlkType,
	FLMUINT				uiFlags,
	const FLMBYTE *	pucKey,
	FLMUINT				uiKeyLen,
	const FLMBYTE *	pucData,
	FLMUINT				uiDataLen,
	FLMUINT				uiOADataLen,		// If zero, it will not be used.
	FLMUINT				uiChildBlkAddr,
	FLMUINT				uiCounts,
	FLMBYTE *			pucBuffer,
	FLMUINT				uiBufferSize,
	FLMUINT *			puiEntrySize)
{
	RCODE					rc = NE_XFLM_OK;
	FLMBYTE *			pucTemp = pucBuffer;

	if( puiEntrySize)
	{
		*puiEntrySize = calcEntrySize( uiBlkType, uiFlags, 
									uiKeyLen, uiDataLen, uiOADataLen);

		if( !(*puiEntrySize) || *puiEntrySize > uiBufferSize)
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_CONV_DEST_OVERFLOW);
			goto Exit;
		}
	}

	switch( uiBlkType)
	{
		case BT_LEAF:
		{
			// No Data in this entry, so it is easy to make.

			UW2FBA( (FLMUINT16)uiKeyLen, pucTemp);
			pucTemp += 2;
			
			f_memcpy( pucTemp, pucKey, uiKeyLen);
			break;
		}

		case BT_LEAF_DATA:
		{
			// Make sure the correct flags are set...

			if( uiKeyLen > ONE_BYTE_SIZE)
			{
				uiFlags |= BTE_FLAG_KEY_LEN;
			}
			else
			{
				uiFlags &= ~BTE_FLAG_KEY_LEN;
			}

			if( uiDataLen > ONE_BYTE_SIZE)
			{
				uiFlags |= BTE_FLAG_DATA_LEN;
			}
			else
			{
				uiFlags &= ~BTE_FLAG_DATA_LEN;
			}

			// Only the first element of an entry that spans elements
			// will hold an OADataLen field.

			if( uiOADataLen && (uiFlags & BTE_FLAG_FIRST_ELEMENT))
			{
				uiFlags |= BTE_FLAG_OA_DATA_LEN;
			}
			else
			{
				uiFlags &= ~BTE_FLAG_OA_DATA_LEN;
			}

			// Now start setting the elements of the entry.
			// Flags first.

			*pucTemp = (FLMBYTE)uiFlags;
			pucTemp++;

			// KeyLen

			if( uiFlags & BTE_FLAG_KEY_LEN)
			{
				UW2FBA( (FLMUINT16)uiKeyLen, pucTemp);
				pucTemp += 2;
			}
			else
			{
				*pucTemp = (FLMBYTE)uiKeyLen;
				pucTemp++;
			}

			if( uiFlags & BTE_FLAG_DATA_LEN)
			{
				UW2FBA( (FLMUINT16)uiDataLen, pucTemp);
				pucTemp += 2;
			}
			else
			{
				*pucTemp = (FLMBYTE)uiDataLen;
				pucTemp++;
			}

			if( uiFlags & BTE_FLAG_OA_DATA_LEN)
			{
				UD2FBA( (FLMUINT32)uiOADataLen, pucTemp);
				pucTemp += 4;
			}

			// Key

			f_memcpy( pucTemp, pucKey, uiKeyLen);
			pucTemp += uiKeyLen;

			// Data

			f_memcpy( pucTemp, pucData, uiDataLen);
			break;
		}

		case BT_NON_LEAF:
		case BT_NON_LEAF_COUNTS:
		{
			// Child block address - 4 bytes

			pucTemp = pucBuffer;

			flmAssert( uiChildBlkAddr);
			UD2FBA( (FLMUINT32)uiChildBlkAddr, pucTemp);
			pucTemp += 4;

			// Counts - 4 bytes

			if( uiBlkType == BT_NON_LEAF_COUNTS)
			{
				UD2FBA( (FLMUINT32)uiCounts, pucTemp);
				pucTemp += 4;
			}

			// KeyLen field - 2 bytes

			UW2FBA( (FLMUINT16)uiKeyLen, pucTemp);
			pucTemp += 2;

			// Key - variable length (uiKeyLen)

			f_memcpy( pucTemp, pucKey, uiKeyLen);
			break;
		}

		default:
		{
			// Invalid block type

			rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
			goto Exit;
		}
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Method to remove an entry from a block. This method will delete the
		entry pointed to by the current Stack. This method does NOT defragment
		the block.  If the entry points to any data only blocks, they will
		also be removed from circulation if the parameter bDeleteDOBlocks is
		set to true.  Otherwise, they will not be freed.  This is so we can
		call this method when we are moving entries between blocks or
		replacing entries etc.
****************************************************************************/
RCODE F_Btree::remove(
	FLMBOOL			bDeleteDOBlocks)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT16 *			pui16OffsetArray;
	FLMUINT				uiNumKeys;
	FLMUINT				uiEntrySize;
	FLMUINT				uiTmp;
	FLMBYTE *			pucEntry;
	FLMBOOL				bDOBlock;
	F_CachedBlock *	pSCache = NULL;
	FLMUINT				uiBlkAddr;
	FLMBYTE *			pucEndOfHeap;
	F_BTREE_BLK_HDR * pBlkHdr;

	if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb,
		&m_pStack->pSCache)))
	{
		goto Exit;
	}

	pBlkHdr = m_pStack->pBlkHdr = 
					(F_BTREE_BLK_HDR *)m_pStack->pSCache->getBlockPtr();
					
	m_pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pBlkHdr, 0);

	uiNumKeys = pBlkHdr->ui16NumKeys;

	if( !uiNumKeys)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
		goto Exit;
	}

	// Point to the entry...
	
	pucEntry = BtEntry( (FLMBYTE *)pBlkHdr, m_pStack->uiCurOffset);
	uiEntrySize = 	getEntrySize( (FLMBYTE *)pBlkHdr, m_pStack->uiCurOffset);

	pucEndOfHeap = (FLMBYTE *)pBlkHdr + sizeofBTreeBlkHdr(pBlkHdr) +
						(uiNumKeys * 2) + pBlkHdr->ui16HeapSize;

	// We are only going to have data only blocks if we are storing data
	// in the btree.
	
	if( m_bData)
	{
		bDOBlock = bteDataBlockFlag( pucEntry);

		// If the data for this entry is in one or more Data Only blocks, then
		// we must delete those blocks first.
		
		if( bDOBlock && bDeleteDOBlocks)
		{
			FLMBYTE	ucDOBlkAddr[ 4];

			// Get the block address of the DO Block.
			
			if( RC_BAD( rc = btGetEntryData( pucEntry, ucDOBlkAddr,
				sizeof( FLMUINT), NULL)))
			{
				goto Exit;
			}

			uiBlkAddr = bteGetBlkAddr( (FLMBYTE *)&ucDOBlkAddr[ 0]);
			while( uiBlkAddr)
			{
				// We need to delete the data only blocks first.
				
				if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
					uiBlkAddr, NULL, &pSCache)))
				{
					goto Exit;
				}

				// Get the next block address (if any)
				
				uiBlkAddr = pSCache->m_pBlkHdr->ui32NextBlkInChain;

				// Now put the block into the Avail list.
				
				rc = m_pDb->m_pDatabase->blockFree( m_pDb, pSCache);
				pSCache = NULL;
				
				if( RC_BAD( rc))
				{
					goto Exit;
				}
			}
		}
	}

	pui16OffsetArray = m_pStack->pui16OffsetArray;

	// Move the offsets around to effectively remove the entry.
	
	for( uiTmp = m_pStack->uiCurOffset; (uiTmp + 1) < uiNumKeys; uiTmp++)
	{
		bteSetEntryOffset( pui16OffsetArray, uiTmp,
								 bteGetEntryOffset( pui16OffsetArray, (uiTmp + 1)));
	}

#ifdef FLM_DEBUG
	// Erase the last offset entry.
	
	bteSetEntryOffset( pui16OffsetArray, uiTmp, 0);
#endif

	pBlkHdr->ui16NumKeys--;
	pBlkHdr->stdBlkHdr.ui16BlkBytesAvail += (FLMUINT16)uiEntrySize;
	pBlkHdr->ui16HeapSize += 2;  // One offset was removed.

	// Was this entry we just removed adjacent to the heap space?  If
	// so then we can increase the heap space.
	
	if( pucEndOfHeap == pucEntry)
	{
		pBlkHdr->ui16HeapSize += (FLMUINT16)actualEntrySize(uiEntrySize);
	}

#ifdef FLM_DEBUG
	// Let's erase whatever was in the entry space.
	
	f_memset( pucEntry, 0, actualEntrySize(uiEntrySize));
#endif

Exit:

	if( pSCache)
	{
		ScaReleaseCache( pSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc:	Method to remove multiple entries from a block. The entries must be
		contiguous.  If any entries store data in data-only blocks, they will
		be freed and put into the avail list.
****************************************************************************/
RCODE F_Btree::removeRange(
	FLMUINT			uiStartElm,
	FLMUINT			uiEndElm,
	FLMBOOL			bDeleteDOBlocks)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT16 *			pui16OffsetArray;
	FLMUINT				uiNumKeys;
	FLMUINT				uiEntrySize;
	FLMBYTE *			pucEntry;
	FLMBOOL				bDOBlock;
	F_CachedBlock *	pSCache = NULL;
	FLMUINT				uiBlkAddr;
	FLMUINT				uiCurOffset;
	FLMUINT				uiCounter;
	FLMBYTE *			pucEndOfHeap;
	FLMBYTE *			pucStartOfHeap;
	F_BTREE_BLK_HDR * pBlkHdr;

	if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( 
		m_pDb, &m_pStack->pSCache)))
	{
		goto Exit;
	}

	pBlkHdr = m_pStack->pBlkHdr = 
				(F_BTREE_BLK_HDR *)m_pStack->pSCache->getBlockPtr();
	m_pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pBlkHdr, 0);
	uiNumKeys = pBlkHdr->ui16NumKeys;
	
	if( !uiNumKeys)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
		goto Exit;
	}

	flmAssert( uiEndElm < uiNumKeys);

	// Point to the entry ...
	
	for( uiCurOffset = uiStartElm; uiCurOffset <= uiEndElm; uiCurOffset++)
	{
		pucEntry = BtEntry( (FLMBYTE *)pBlkHdr, uiCurOffset);
		uiEntrySize = getEntrySize( (FLMBYTE *)pBlkHdr, uiCurOffset);
		pBlkHdr->stdBlkHdr.ui16BlkBytesAvail += (FLMUINT16)uiEntrySize;
		pBlkHdr->ui16NumKeys--;

		bDOBlock = bteDataBlockFlag(pucEntry);

		// If the data for this entry is in a Data Only block, then we must delete
		// those blocks first.
		
		if( bDOBlock && bDeleteDOBlocks)
		{
			FLMBYTE	ucDOBlkAddr[ 4];

			// Get the block address of the DO Block.
			
			if( RC_BAD( rc = btGetEntryData( pucEntry, ucDOBlkAddr, 4, NULL)))
			{
				goto Exit;
			}

			uiBlkAddr = bteGetBlkAddr( (FLMBYTE *)&ucDOBlkAddr[ 0]);
			while( uiBlkAddr)
			{
				// We need to delete the data only blocks first.
				
				if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
					uiBlkAddr, NULL, &pSCache)))
				{
					goto Exit;
				}

				// Get the next block address (if any)
				
				uiBlkAddr = pSCache->m_pBlkHdr->ui32NextBlkInChain;

				// Now put the block into the Avail list.
				
				rc = m_pDb->m_pDatabase->blockFree( m_pDb, pSCache);
				pSCache = NULL;
				
				if( RC_BAD( rc))
				{
					goto Exit;
				}
			}
		}

		// Now erase the old entry
		
#ifdef FLM_DEBUG
		f_memset( pucEntry, 0, actualEntrySize(uiEntrySize));
#endif
	}

	// Move the offsets around to effectively remove the entries.
	
	pui16OffsetArray = m_pStack->pui16OffsetArray;
	if( uiEndElm < (uiNumKeys - 1))
	{
		// We will need to move the remaining offsets forward
		// to delete the desired range.

		for (uiCurOffset = uiStartElm, uiCounter = 0;
			  uiCounter < (uiNumKeys - (uiEndElm + 1));
			  uiCounter++, uiCurOffset++)
		{
			bteSetEntryOffset( pui16OffsetArray, uiCurOffset,
									 bteGetEntryOffset( pui16OffsetArray,
															  (uiEndElm + uiCounter + 1)));
		}
	}

#ifdef FLM_DEBUG
	// Erase the remaining offsets
	
	while (uiCurOffset < (uiNumKeys - 1))
	{
		bteSetEntryOffset( pui16OffsetArray, uiCurOffset++, 0);
	}
	
#endif

	// We need to determine if we have gained any more heap space.  We start
	// by pointing to the end of the block, them moving forward until we reach
	// the closest entry.
	
	pucEndOfHeap = (FLMBYTE *)pBlkHdr + m_uiBlockSize;
	
	for ( uiCurOffset = 0; uiCurOffset < pBlkHdr->ui16NumKeys; uiCurOffset++)
	{
		pucEntry = BtEntry( (FLMBYTE *)pBlkHdr, uiCurOffset);
		
		if (pucEntry < pucEndOfHeap)
		{
			pucEndOfHeap = pucEntry;
		}
	}
	
	// Now clean up the heap space.
	
	pucStartOfHeap = (FLMBYTE *)pBlkHdr + sizeofBTreeBlkHdr( pBlkHdr) +
						  (pBlkHdr->ui16NumKeys * 2);

	pBlkHdr->ui16HeapSize = (FLMUINT16)(pucEndOfHeap - pucStartOfHeap);

#ifdef FLM_DEBUG
	f_memset( pucStartOfHeap, 0, pBlkHdr->ui16HeapSize);
#endif

Exit:

	if( pSCache)
	{
		ScaReleaseCache( pSCache, FALSE);
	}
	
	return( rc);
}

/***************************************************************************
Desc:	Method to try to move entries (whole) from the target block to the
		previous block.  The entries may be moved, up to but not including
		the current entry position.  We do not want to change the parentage
		of this block.  We need to use the stack to fix up the parentage of
		the previous block. Entries are moved from the lowest to highest.
****************************************************************************/
RCODE F_Btree::moveEntriesToPrevBlk(
	FLMUINT				uiNewEntrySize,
	F_CachedBlock **	ppPrevSCache,
	FLMBOOL *			pbEntriesWereMoved)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT				uiLocalAvailSpace;
	FLMUINT				uiAvailSpace;
	FLMUINT				uiHeapSize;
	F_CachedBlock *	pPrevSCache = NULL;
	FLMUINT				uiPrevBlkAddr;
	FLMUINT				uiOAEntrySize = 0;
	FLMUINT				uiStart;
	FLMUINT				uiFinish;
	FLMUINT				uiCount;
	FLMUINT				uiOffset;

	// Assume nothing to move.
	
	*pbEntriesWereMoved = FALSE;

	// If we are already at the first entry in the block, there
	// is nothing that we can move since we will always insert ahead of
	// the current position.
	
	if( !m_pStack->uiCurOffset)
	{
		goto Exit;
	}

	// Get the previous block.
	
	if( (uiPrevBlkAddr = 
		m_pStack->pSCache->m_pBlkHdr->ui32PrevBlkInChain) == 0)
	{
		goto Exit;
	}

	if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
		uiPrevBlkAddr, NULL, &pPrevSCache)))
	{
		goto Exit;
	}

	uiLocalAvailSpace = m_pStack->pBlkHdr->stdBlkHdr.ui16BlkBytesAvail;
	uiAvailSpace = pPrevSCache->m_pBlkHdr->ui16BlkBytesAvail;
	uiHeapSize = ((F_BTREE_BLK_HDR *)pPrevSCache->m_pBlkHdr)->ui16HeapSize;

	// If we add the available space in this block and the previous block, would
	// it be enough to make room for the new entry?  If so, then we will
	// see if we can make that room by moving ( whole) entries.
	
	if( (uiAvailSpace + uiLocalAvailSpace) < uiNewEntrySize)
	{
		goto Exit;
	}

	uiStart = 0;
	uiFinish = m_pStack->uiCurOffset;

	// Get the size of each entry until we are over the available size limit
	
	for( uiOffset = 0, uiCount = 0 ; uiOffset < uiFinish; uiOffset++)
	{
		FLMUINT		uiLocalEntrySize;

		uiLocalEntrySize = getEntrySize( (FLMBYTE *)m_pStack->pBlkHdr, uiOffset);

		if( (uiLocalEntrySize + uiOAEntrySize) < uiAvailSpace)
		{
			uiOAEntrySize += uiLocalEntrySize;
			uiLocalAvailSpace += uiLocalEntrySize;
			uiCount++;
		}
		else
		{
			break;
		}
	}

	if( !uiCount)
	{
		goto Exit;
	}

	// It looks like we can move at least one entry.
	// Will this give use enough room to store the new entry?
	
	if( uiLocalAvailSpace < uiNewEntrySize)
	{
		// Moving these entries will not benefit us, so don't bother
		
		goto Exit;
	}

	// Do we need to defragment the block first?
	
	if( uiHeapSize < uiOAEntrySize)
	{
		flmAssert( uiHeapSize != uiAvailSpace);
		if( RC_BAD( rc = defragmentBlock( &pPrevSCache)))
		{
			goto Exit;
		}
	}

	// We are going to get some benefit from moving, so let's do it...
	
	if (RC_BAD( rc = moveToPrev( uiStart, uiStart + uiCount - 1, &pPrevSCache)))
	{
		goto Exit;
	}

	// We will need to return this block.
	
	*ppPrevSCache = pPrevSCache;
	pPrevSCache = NULL;

	// Adjust the current offset in the stack so we are still pointing to the
	// same entry.
	
	m_pStack->uiCurOffset -= uiCount;

	// If this block has a parent block, and the btree is maintaining counts
	// we will want to update the counts on the parent block.
	
	if( !isRootBlk( m_pStack->pBlkHdr))
	{
		if( m_bCounts)
		{
			if( RC_BAD( rc = updateCounts()))
			{
				goto Exit;
			}
		}
	}

	*pbEntriesWereMoved = TRUE;

Exit:

	if (pPrevSCache)
	{
		ScaReleaseCache( pPrevSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc: This method will move entries beginning at uiStart, up to and
		including uiFinish from the current block (m_pStack) to pPrevSCache.
		As a part of this operation, both the target block and the source 
		block will be changed.  A call to logPhysBlock will be made before
		each block is changed.  Never move the highest key in the block 
		because we don't want to have to update the parentage of the 
		current block...
****************************************************************************/
RCODE F_Btree::moveToPrev(
	FLMUINT				uiStart,
	FLMUINT				uiFinish,
	F_CachedBlock **	ppPrevSCache)
{
	RCODE							rc = NE_XFLM_OK;
	FLMUINT16 *					pui16DstOffsetA = NULL;
	F_BTREE_BLK_HDR *			pSrcBlkHdr = NULL;
	F_BTREE_BLK_HDR *			pDstBlkHdr = NULL;
	FLMBYTE *					pucSrcEntry;
	FLMBYTE *					pucDstEntry;
	FLMUINT						uiEntrySize;
	FLMUINT						uiIndex;
	F_CachedBlock *			pPrevSCache;
	FLMBOOL						bEntriesCombined = FALSE;

	// Make sure we have logged the block we are changing.
	// Note that the source block will be logged in the removeRange method.

	if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, ppPrevSCache)))
	{
		goto Exit;
	}

	pPrevSCache = *ppPrevSCache;
	pSrcBlkHdr = m_pStack->pBlkHdr;
	pDstBlkHdr = (F_BTREE_BLK_HDR *)pPrevSCache->m_pBlkHdr;
	pui16DstOffsetA = BtOffsetArray( (FLMBYTE *)pDstBlkHdr, 0);

	pucDstEntry = getBlockEnd( pDstBlkHdr);

	// Beginning at the start, copy each entry over from the source
	// to the destination block.
	
	for( uiIndex = uiStart; uiIndex <= uiFinish; uiIndex++)
	{
		if( RC_BAD( rc = combineEntries( pSrcBlkHdr, uiIndex, pDstBlkHdr,
													(pDstBlkHdr->ui16NumKeys
														? pDstBlkHdr->ui16NumKeys - 1
														: 0),
													&bEntriesCombined, &uiEntrySize)))
		{
			goto Exit;
		}

		if( bEntriesCombined)
		{
			F_BTSK		tmpStack;
			F_BTSK_p		pTmpStack;

			tmpStack.pSCache = pPrevSCache;
			tmpStack.pBlkHdr = pDstBlkHdr;
			tmpStack.uiCurOffset = pDstBlkHdr->ui16NumKeys - 1;  // Last entry

			pTmpStack = m_pStack;
			m_pStack = &tmpStack;

			rc = remove( FALSE);
			m_pStack = pTmpStack;
			
			if( RC_BAD( rc))
			{
				goto Exit;
			}

			if( pDstBlkHdr->ui16HeapSize != 
						pDstBlkHdr->stdBlkHdr.ui16BlkBytesAvail)
			{
				if( RC_BAD( rc = defragmentBlock( &pPrevSCache)))
				{
					goto Exit;
				}
			}

			pucDstEntry = getBlockEnd( pDstBlkHdr) - uiEntrySize;
			f_memcpy( pucDstEntry, m_pucTempBlk, uiEntrySize);

			bteSetEntryOffset( pui16DstOffsetA,
									 pDstBlkHdr->ui16NumKeys++,
									 (FLMUINT16)(pucDstEntry - (FLMBYTE *)pDstBlkHdr));

			pDstBlkHdr->stdBlkHdr.ui16BlkBytesAvail -=
												((FLMUINT16)uiEntrySize + 2);

			pDstBlkHdr->ui16HeapSize -= ((FLMUINT16)uiEntrySize + 2);
			bEntriesCombined = FALSE;
		}
		else
		{
			pucSrcEntry = BtEntry( (FLMBYTE *)pSrcBlkHdr, uiIndex);
			uiEntrySize = getEntrySize( (FLMBYTE *)pSrcBlkHdr, uiIndex);
			pucDstEntry -= actualEntrySize(uiEntrySize);

			f_memcpy( pucDstEntry, pucSrcEntry, actualEntrySize(uiEntrySize));

			bteSetEntryOffset( pui16DstOffsetA,
									 pDstBlkHdr->ui16NumKeys++,
									 (FLMUINT16)(pucDstEntry - (FLMBYTE *)pDstBlkHdr));

			pDstBlkHdr->stdBlkHdr.ui16BlkBytesAvail -= (FLMUINT16)uiEntrySize;
			pDstBlkHdr->ui16HeapSize -= (FLMUINT16)uiEntrySize;
		}
	}

	// Now remove the entries from the Src block.
	
	if( RC_BAD( rc = removeRange( uiStart, uiFinish, FALSE)))
	{
		goto Exit;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Method to try to move entries (whole) from the target block to the
		next block.  The entries may be moved up to but not including
		the current entry position depending on how much room is available if
		any.  Entries are moved from the highest to lowest.
****************************************************************************/
RCODE F_Btree::moveEntriesToNextBlk(
	FLMUINT		uiNewEntrySize,
	FLMBOOL *	pbEntriesWereMoved)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT				uiLocalAvailSpace;
	FLMUINT				uiAvailSpace;
	FLMUINT				uiHeapSize;
	F_CachedBlock *	pNextSCache = NULL;
	FLMUINT				uiNextBlkAddr;
	FLMUINT				uiOAEntrySize = 0;
	FLMUINT				uiStart;
	FLMUINT				uiFinish;
	FLMUINT				uiCount;
	FLMUINT				uiOffset;
	F_CachedBlock *	pChildSCache = NULL;
	F_CachedBlock *	pParentSCache = NULL;
	F_BTSK_p				pParentStack;
	FLMUINT				uiLevel;
	FLMBOOL				bReleaseChild = FALSE;
	FLMBOOL				bReleaseParent = FALSE;
	FLMBOOL				bCommonParent = FALSE;

	// Assume nothing to move.
	
	*pbEntriesWereMoved = FALSE;

	// Get the next block.
	
	if( (uiNextBlkAddr = m_pStack->pSCache->m_pBlkHdr->ui32NextBlkInChain) == 0)
	{
		goto Exit;
	}

	if( (FLMUINT16)m_pStack->uiCurOffset >= (m_pStack->pBlkHdr->ui16NumKeys - 1))
	{
		goto Exit;
	}

	if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
		uiNextBlkAddr, NULL, &pNextSCache)))
	{
		goto Exit;
	}

	// Our first task is to determine if we can move anything at all.
	// How much free space is there in the next block?
	
	uiLocalAvailSpace = m_pStack->pBlkHdr->stdBlkHdr.ui16BlkBytesAvail;
	uiAvailSpace = pNextSCache->m_pBlkHdr->ui16BlkBytesAvail;
	uiHeapSize = ((F_BTREE_BLK_HDR *)pNextSCache->m_pBlkHdr)->ui16HeapSize;

	// If we add the available space in this block and the next block, would
	// it be enough to make room for the new entry?  If so, then we will
	// see if we can make that room by moving ( whole) entries.
	
	if( (uiAvailSpace + uiLocalAvailSpace) < uiNewEntrySize)
	{
		goto Exit;
	}

	// Begin at the last entry and work backward.
	
	uiStart = m_pStack->pBlkHdr->ui16NumKeys - 1;
	uiFinish = m_pStack->uiCurOffset;

	// Get the size of each entry (plus 2 for the offset entry) until we are
	// over the available size limit.
	
	for( uiOffset = uiStart, uiCount = 0 ; uiOffset > uiFinish; uiOffset--)
	{
		FLMUINT		uiLocalEntrySize;

		uiLocalEntrySize = getEntrySize( (FLMBYTE *)m_pStack->pBlkHdr,
													uiOffset);

		if( (uiLocalEntrySize + uiOAEntrySize) < uiAvailSpace)
		{
			uiOAEntrySize += uiLocalEntrySize;
			uiLocalAvailSpace += uiLocalEntrySize;
			uiCount++;
		}
		else
		{
			break;
		}
	}

	if( uiCount == 0)
	{
		goto Exit;
	}

	// It looks like we can move at least one entry.
	// Will this give use enough room to store the new entry?
	
	if( uiLocalAvailSpace < uiNewEntrySize)
	{
		goto Exit;
	}

	flmAssert( uiStart > uiFinish);

	// Do we need to defragment the block first before we do the move?
	
	if( uiHeapSize < uiOAEntrySize)
	{
		flmAssert( uiHeapSize != uiAvailSpace);
		if( RC_BAD( rc = defragmentBlock( &pNextSCache)))
		{
			goto Exit;
		}
	}

	// We are going to get some benefit from moving, so let's do it...
	
	if( RC_BAD( rc = moveToNext( uiStart, uiStart - uiCount + 1,
		&pNextSCache)))
	{
		goto Exit;
	}

	// If this block has a parent block, and the btree is maintaining counts
	// we will need to update the counts on the parent blocks.
	
	if( m_bCounts)
	{
		for( uiLevel = m_pStack->uiLevel;
			  uiLevel < m_uiStackLevels - 1;
			  uiLevel++)
		{
			pParentStack = &m_Stack[ uiLevel + 1];

			// If we are at "current" level, then we want to use the pNextSCache
			// block as the child.  Otherwise, we want to use the previous parent
			// block as the child.
			
			if( uiLevel == m_pStack->uiLevel)
			{
				pChildSCache =	 pNextSCache;
				bReleaseChild = TRUE;
				pNextSCache =	 NULL;
			}
			else
			{
				pChildSCache =	  pParentSCache;
				bReleaseChild =  bReleaseParent;
				bReleaseParent = FALSE;
			}

			// Check to see if the parent entry is the last entry in the
			// block.   If it is, then we will need to get the next block.
			// If the parent block is the same for both blocks, then we
			// only need to reference the next entry.  We don't want to release
			// the parent as it is referenced in the stack.
			
			if( bCommonParent ||
				  (pParentStack->uiCurOffset <
							(FLMUINT)(pParentStack->pBlkHdr->ui16NumKeys - 1)))
			{
				pParentSCache = pParentStack->pSCache;
				bReleaseParent = FALSE;

				if (RC_BAD( rc = updateParentCounts( pChildSCache, &pParentSCache,
					(bCommonParent
						? pParentStack->uiCurOffset
						: pParentStack->uiCurOffset + 1))))
				{
					goto Exit;
				}
				
				// The parent has changed, so update the stack.
				
				pParentStack->pBlkHdr =
					(F_BTREE_BLK_HDR *)pParentSCache->m_pBlkHdr;

				pParentStack->pSCache = pParentSCache;
				bCommonParent = TRUE;
			}
			else
			{
				// We need to get the next block at the parent level first.  We
				// release the previous parent if there was one.
				
				uiNextBlkAddr = pParentStack->pBlkHdr->stdBlkHdr.ui32NextBlkInChain;

				flmAssert( uiNextBlkAddr);

				if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
					uiNextBlkAddr, NULL, &pParentSCache)))
				{
					goto Exit;
				}

				bReleaseParent = TRUE;

				if( RC_BAD( rc = updateParentCounts( pChildSCache,
					&pParentSCache, 0)))
				{
					goto Exit;
				}
			}

			if( bReleaseChild)
			{
				ScaReleaseCache( pChildSCache, FALSE);
				pChildSCache = NULL;
				bReleaseChild = FALSE;
			}
		}
	}

	*pbEntriesWereMoved = TRUE;

Exit:

	if( pChildSCache && bReleaseChild)
	{
		ScaReleaseCache( pChildSCache, FALSE);
	}

	if( pParentSCache && bReleaseParent)
	{
		ScaReleaseCache( pParentSCache, FALSE);
	}

	if( pNextSCache)
	{
		ScaReleaseCache( pNextSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc: This method will move entries beginning at uiStart, down to and
		including uiFinish from the current block (m_pStack) to pNextSCache.
		As a part of this operation, both the target block and the source
		block will be changed.
****************************************************************************/
RCODE F_Btree::moveToNext(
	FLMUINT				uiStart,
	FLMUINT				uiFinish,
	F_CachedBlock **	ppNextSCache)
{
	RCODE							rc = NE_XFLM_OK;
	FLMUINT16 *					pui16DstOffsetA = NULL;
	F_BTREE_BLK_HDR *			pSrcBlkHdr = NULL;
	F_BTREE_BLK_HDR *			pDstBlkHdr = NULL;
	FLMBYTE *					pucSrcEntry;
	FLMBYTE *					pucDstEntry;
	FLMUINT						uiEntrySize;
	FLMINT						iIndex;
	FLMUINT						uiBytesToCopy;
	FLMUINT						uiNumKeysToAdd;
	F_CachedBlock *			pNextSCache = *ppNextSCache;
	FLMBOOL						bEntriesCombined;
	FLMBYTE *					pucOffsetArray;

	// Make sure we have logged the block we are changing.
	// Note that the source block will be logged in the removeRange method.
	
	if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &pNextSCache)))
	{
		goto Exit;
	}
	
	// SCache block may have changed.  Need to pass it back.
	
	*ppNextSCache = pNextSCache;
	
	pSrcBlkHdr = m_pStack->pBlkHdr;
	pDstBlkHdr = (F_BTREE_BLK_HDR *)pNextSCache->m_pBlkHdr;

	// We will need to save off the current offset array.  We will do this
	// by copying it into our temporary block.
	
	uiBytesToCopy = pDstBlkHdr->ui16NumKeys * 2;
	if( uiBytesToCopy > m_uiBufferSize)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
		goto Exit;
	}

	pui16DstOffsetA = BtOffsetArray((FLMBYTE *)pDstBlkHdr, 0);
	pucOffsetArray = &m_pucBuffer[ m_uiBufferSize] - uiBytesToCopy;

	f_memcpy( pucOffsetArray, (FLMBYTE *)pui16DstOffsetA, uiBytesToCopy);

	// Point to the last entry in the block.
	
	pucDstEntry = getBlockEnd( pDstBlkHdr);

	// Beginning at the start, copy each entry over from the Src to the Dst
	// block.  Note that the uiStart parameter represents a higher position
	// in the block.  In otherwords, we are actually copying from the end or
	// highest position to a lower position in the block.  Therefore we want
	// to make sure the offset array is copied in the same way, otherwise it
	// would reverse the order of the entries.
	
	uiNumKeysToAdd = uiStart - uiFinish + 1;
	pui16DstOffsetA = (FLMUINT16 *)pucOffsetArray;

	for( iIndex = uiStart; iIndex >= (FLMINT)uiFinish; iIndex--)
	{
		if( RC_BAD( rc = combineEntries( pSrcBlkHdr, iIndex, pDstBlkHdr,
			0, &bEntriesCombined, &uiEntrySize)))
		{
			goto Exit;
		}

		if( bEntriesCombined)
		{
			F_BTSK		tmpStack;
			F_BTSK_p		pTmpStack;

			tmpStack.pSCache = pNextSCache;
			tmpStack.pBlkHdr = pDstBlkHdr;
			tmpStack.uiCurOffset = 0;  // 1st entry.

			pTmpStack = m_pStack;
			m_pStack = &tmpStack;

			rc = remove( FALSE);
			m_pStack = pTmpStack;
			
			if (RC_BAD( rc))
			{
				goto Exit;
			}

			if( pDstBlkHdr->ui16HeapSize !=
						pDstBlkHdr->stdBlkHdr.ui16BlkBytesAvail)
			{
				if( RC_BAD( rc = defragmentBlock( &pNextSCache)))
				{
					goto Exit;
				}

				// Refresh the saved offset array.
				
				uiBytesToCopy -= 2;
				pucOffsetArray = &m_pucBuffer[ m_uiBufferSize] - uiBytesToCopy;

				f_memcpy( pucOffsetArray,
							(FLMBYTE *)BtOffsetArray( (FLMBYTE *)pDstBlkHdr, 0),
							uiBytesToCopy);
			}

			pucDstEntry = getBlockEnd( pDstBlkHdr) - uiEntrySize;
			f_memcpy( pucDstEntry, m_pucTempBlk, uiEntrySize);

			bteSetEntryOffset( pui16DstOffsetA, 0, 
				(FLMUINT16)(pucDstEntry - (FLMBYTE *)pDstBlkHdr));

			pDstBlkHdr->ui16NumKeys++;

			pDstBlkHdr->stdBlkHdr.ui16BlkBytesAvail -=
												((FLMUINT16)uiEntrySize + 2);

			pDstBlkHdr->ui16HeapSize -= ((FLMUINT16)uiEntrySize + 2);

			bEntriesCombined = FALSE;
		}
		else
		{
			pucSrcEntry = BtEntry( (FLMBYTE *)pSrcBlkHdr, iIndex);
			uiEntrySize = getEntrySize( (FLMBYTE *)pSrcBlkHdr, iIndex);

			pucDstEntry -= actualEntrySize(uiEntrySize);

			f_memcpy( pucDstEntry, pucSrcEntry,
						 actualEntrySize(uiEntrySize));

			pui16DstOffsetA--;

			bteSetEntryOffset( pui16DstOffsetA, 0,
									 (FLMUINT16)(pucDstEntry - (FLMBYTE *)pDstBlkHdr));

			pDstBlkHdr->ui16NumKeys++;
			pDstBlkHdr->stdBlkHdr.ui16BlkBytesAvail -= (FLMUINT16)uiEntrySize;
			pDstBlkHdr->ui16HeapSize -= (FLMUINT16)uiEntrySize;
		}
	}

	// Now put the new offset array into the block.
	
	f_memcpy( BtOffsetArray( (FLMBYTE *)pDstBlkHdr, 0),
				 pui16DstOffsetA,
				 &m_pucBuffer[ m_uiBufferSize] - (FLMBYTE *)pui16DstOffsetA);

	// Now remove the entries from the Src block.
	
	if( RC_BAD( rc = removeRange( uiFinish, uiStart, FALSE)))
	{
		goto Exit;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Method to advance to the next entry.  If there are no more entries
		in the block, it will release the current block and get the next in
		the chain. If there are no more entries, i.e. no more blocks in 
		the chain, NE_XFLM_EOF_HIT will be returned.
****************************************************************************/
RCODE F_Btree::advanceToNextElement(
	FLMBOOL				bAdvanceStack)
{
	RCODE							rc = NE_XFLM_OK;
	F_BTREE_BLK_HDR *			pBlkHdr;

	flmAssert( m_pSCache);

	pBlkHdr = (F_BTREE_BLK_HDR *)m_pSCache->m_pBlkHdr;

	if( m_uiCurOffset + 1 >= pBlkHdr->ui16NumKeys)
	{
		// We are out of entries in this block, so we will release it
		// and get the next block in the chain (if any).
		
		if( RC_BAD( rc = getNextBlock( &m_pSCache)))
		{
			goto Exit;
		}

		m_ui32PrimaryBlkAddr = m_pSCache->m_pBlkHdr->ui32BlkAddr;
		m_uiPrimaryOffset = 0;
		m_ui32CurBlkAddr = m_ui32PrimaryBlkAddr;
		m_uiCurOffset = 0;

		if( bAdvanceStack)
		{
			if( RC_BAD( rc = moveStackToNext( m_pSCache)))
			{
				goto Exit;
			}
			
			// This block now has two uses.  It will be released twice.
			
			m_pSCache->m_uiUseCount++;
		}
	}
	else
	{
		m_uiPrimaryOffset++;
		m_uiCurOffset++;
		m_pStack->uiCurOffset++;
	}

Exit:

	// We do not want to release the m_pSCache here.  That is to be done by the
	// caller.
	
	return( rc);
}

/***************************************************************************
Desc:	Method to backup the stack to the previous entry.  If there are no
		more entries in the block, it will release the current block and get
		the previous in the chain. If there are no more entries, i.e. no
		more blocks in the chain, NE_XFLM_BOF_HIT will be returned.
****************************************************************************/
RCODE F_Btree::backupToPrevElement(
	FLMBOOL				bBackupStack)
{
	RCODE							rc = NE_XFLM_OK;
	F_BTREE_BLK_HDR *			pBlkHdr;

	flmAssert( m_pSCache);

	pBlkHdr = (F_BTREE_BLK_HDR *)m_pSCache->m_pBlkHdr;

	if( !m_uiCurOffset)
	{
		// We are out of entries in this block, so we will release it
		// and get the previous block in the chain (if any).
		
		if( RC_BAD( rc = getPrevBlock( &m_pSCache)))
		{
			goto Exit;
		}

		m_ui32PrimaryBlkAddr = m_pSCache->m_pBlkHdr->ui32BlkAddr;

		m_uiPrimaryOffset =
			((F_BTREE_BLK_HDR *)m_pSCache->m_pBlkHdr)->ui16NumKeys - 1;

		m_ui32CurBlkAddr = m_ui32PrimaryBlkAddr;
		m_uiCurOffset = m_uiPrimaryOffset;

		if( bBackupStack)
		{
			if( RC_BAD( rc = moveStackToPrev( m_pSCache)))
			{
				goto Exit;
			}
			
			// This block now has two uses.  It will be released twice.
			
			m_pSCache->m_uiUseCount++;
		}
	}
	else
	{
		m_uiPrimaryOffset--;
		m_uiCurOffset--;
		m_pStack->uiCurOffset--;
	}

Exit:

	// We do not want to release the m_pSCache here.  That is to be done by the
	// caller.
	
	return( rc);
}

/***************************************************************************
Desc:	Method to extract the key length from a given entry.  The optional
		pucKeyRV is a buffer where we can return the address of the start of
		the actual key.
****************************************************************************/
FLMUINT F_Btree::getEntryKeyLength(
	FLMBYTE *			pucEntry,
	FLMUINT				uiBlockType,
	const FLMBYTE **	ppucKeyRV)
{
	FLMUINT				uiKeyLength;
	FLMBYTE *			pucTmp = NULL;

	// The way we get the key length depends on the type of block we have.

	switch( uiBlockType)
	{
		case BT_LEAF_DATA:
		{
			pucTmp = &pucEntry[ 1];  // skip past the flags
			
			if( bteKeyLenFlag( pucEntry))
			{
				uiKeyLength = FB2UW( pucTmp);
				pucTmp += 2;
			}
			else
			{
				uiKeyLength = *pucTmp;
				pucTmp += 1;
			}

			if( bteDataLenFlag(pucEntry))
			{
				pucTmp += 2;
			}
			else
			{
				pucTmp += 1;
			}

			// Check for the presence of the OverallDataLength field (4 bytes).
			
			if( bteOADataLenFlag( pucEntry))
			{
				pucTmp += 4;
			}

			break;
		}
		
		case BT_LEAF:
		{
			uiKeyLength = FB2UW( pucEntry);

			if( ppucKeyRV)
			{
				pucTmp = &pucEntry[ BTE_KEY_START];
			}

			break;
		}
		
		case BT_NON_LEAF:
		{
			uiKeyLength = FB2UW( &pucEntry[ BTE_NL_KEY_LEN]);

			if( ppucKeyRV)
			{
				pucTmp = &pucEntry[ BTE_NL_KEY_START];
			}

			break;
		}
		
		case BT_NON_LEAF_COUNTS:
		{
			uiKeyLength = FB2UW( &pucEntry[ BTE_NLC_KEY_LEN]);

			if( ppucKeyRV)
			{
				pucTmp = &pucEntry[ BTE_NLC_KEY_START];
			}

			break;
		}
		
		default:
		{
			flmAssert( 0);
			uiKeyLength = 0;
			pucTmp = NULL;
			break;
		}
	}

	// Do we need to return the key pointer?
	
	if( ppucKeyRV)
	{
		*ppucKeyRV = pucTmp;
	}

	return( uiKeyLength);
}

/***************************************************************************
Desc:	Method to extract the data length from a given entry. The parameter
		pucDataRV is an optional return value that will hold the address
		of the beginning of the data in the entry.  This method 
		** assumes ** the entry is from a BT_LEAF_DATA block.  No other block
		type has any data.
****************************************************************************/
FSTATIC FLMUINT btGetEntryDataLength(
	FLMBYTE *			pucEntry,
	const FLMBYTE **	ppucDataRV,				// Optional
	FLMUINT *			puiOADataLengthRV,	// Optional
	FLMBOOL *			pbDOBlockRV)			// Optional
{
	const FLMBYTE *	pucTmp;
	FLMUINT				uiDataLength;
	FLMUINT				uiKeyLength;

	pucTmp = &pucEntry[ 1];  // skip past the flags
	
	if( bteKeyLenFlag( pucEntry))
	{
		uiKeyLength = FB2UW( pucTmp);
		pucTmp += 2; 
	}
	else
	{
		uiKeyLength = *pucTmp;
		pucTmp += 1;
	}

	if( bteDataLenFlag(pucEntry))
	{
		uiDataLength = FB2UW( pucTmp);
		pucTmp += 2;
	}
	else
	{
		uiDataLength = *pucTmp;
		pucTmp += 1;
	}

	// Check for the presence of the OverallDataLength field (4 bytes).
	
	if( bteOADataLenFlag(pucEntry))
	{
		if( puiOADataLengthRV)
		{
			*puiOADataLengthRV = FB2UD( pucTmp);
		}
		pucTmp += 4;
	}
	else if (puiOADataLengthRV)
	{
		*puiOADataLengthRV = uiDataLength;
	}

	// Are we to return a pointer to the data?
	
	if( ppucDataRV)
	{
		// Advance to the Data since we are currently pointing to the Key.
		
		*ppucDataRV = (FLMBYTE *)(pucTmp + uiKeyLength);
	}

	if( pbDOBlockRV)
	{
		*pbDOBlockRV = bteDataBlockFlag( pucEntry);
	}

	return( uiDataLength);
}

/***************************************************************************
Desc:	Method to extract the data value from a given block. This method
		expects to receive a buffer to copy the data into.  This method does
		not read data across blocks.  The puiLenDataRV is an optional 
		parameter that will hold the actual data size returned.
****************************************************************************/
FSTATIC RCODE btGetEntryData(
	FLMBYTE *		pucEntry,	// Pointer to the entry containing the data
	FLMBYTE *		pucBufferRV,
	FLMUINT			uiBufferSize,
	FLMUINT *		puiLenDataRV)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT				uiDataLength;
	const FLMBYTE *	pucData;

	// Get the data length
	
	uiDataLength = btGetEntryDataLength( pucEntry, &pucData, NULL, NULL);

	if( uiDataLength > uiBufferSize)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_CONV_DEST_OVERFLOW);
		goto Exit;
	}

#ifdef FLM_DEBUG
	f_memset( pucBufferRV, 0, uiBufferSize);
#endif
	f_memcpy( pucBufferRV, pucData, uiDataLength);

	// Do we need to return the data length?

	if( puiLenDataRV)
	{
		*puiLenDataRV = uiDataLength;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	This method will return the overall size of the entry at uiOffset in
		pBlk.  The size returned includes a two byte allowance for the offset
		entry used by this entry.
****************************************************************************/
FLMUINT F_Btree::getEntrySize(
	FLMBYTE *		pBlk,
	FLMUINT			uiOffset,
	FLMBYTE **		ppucEntry)
{
	FLMBYTE *		pucEntry;
	FLMUINT			uiEntrySize;

	// Point to the entry ...
	
	pucEntry = BtEntry( pBlk, uiOffset);

	if( ppucEntry)
	{
		*ppucEntry = pucEntry;
	}

	// Different block types have different entry formats.
	
	switch( getBlkType( pBlk))
	{
		case BT_LEAF:
		{
			uiEntrySize =  4 + FB2UW( pucEntry);
			break;
		}
		case BT_LEAF_DATA:
		{
			FLMBYTE * pucTmp = &pucEntry[ 1];

			// Stuff we know
			
			uiEntrySize = 3;

			// Get the key length
			
			if( *pucEntry & BTE_FLAG_KEY_LEN)
			{
				uiEntrySize += FB2UW( pucTmp) + 2;
				pucTmp += 2;
			}
			else
			{
				uiEntrySize += (*pucTmp + 1);
				pucTmp++;
			}

			// Get the data length
			
			if( *pucEntry & BTE_FLAG_DATA_LEN)
			{
				// 2 byte data length field
				
				uiEntrySize += (FB2UW( pucTmp) + 2);
			}
			else
			{
				// 1 byte data length field
				
				uiEntrySize += (FLMUINT)*pucTmp + 1;
			}

			// Get the Overall Data length (if present)
			
			if( *pucEntry & BTE_FLAG_OA_DATA_LEN)
			{
				uiEntrySize += 4;
			}
			
			break;
		}
		
		case BT_NON_LEAF:
		{
			uiEntrySize = 8 + FB2UW( &pucEntry[ BTE_NL_KEY_LEN]);
			break;
		}
		
		case BT_NON_LEAF_COUNTS:
		{
			uiEntrySize = 12 + FB2UW( &pucEntry[ BTE_NLC_KEY_LEN]);
			break;
		}
		
		default:
		{
			flmAssert( 0);
			uiEntrySize = 0;
			break;
		}
	}

	return( uiEntrySize);
}

/***************************************************************************
Desc:	Method to search the BTree for a specific entry. Upon a successful
		return from this method, the local stack will be setup and pointing
		to either the desired entry, or if the entry does not exist, it will
		be pointing to the entry that would be immediately after the desired
		entry.  This method therefore can be used both for reads and updates
		where we want to insert a new entry into the BTree.
****************************************************************************/
RCODE F_Btree::findEntry(
	const FLMBYTE *	pucKey,				// In
	FLMUINT 				uiKeyLen,			// In
	FLMUINT				uiMatch,				// In
	FLMUINT *			puiPosition,		// Out
	FLMUINT32 *			pui32BlkAddr,		// In/Out
	FLMUINT *			puiOffsetIndex)	// In/Out
{
	RCODE					rc = NE_XFLM_OK;
	F_BTSK *				pStack = NULL;
	FLMUINT32			ui32BlkAddress;
	F_CachedBlock *	pSCache = NULL;
	FLMBYTE *			pucEntry;
	FLMUINT				uiPrevCounts = 0;
	FLMUINT				uiLevel;

	// Make sure the stack is clean before we start.
	
	btRelease();

	// No input key is needed to get the first or last key.
	
	if( uiMatch == XFLM_FIRST || uiMatch == XFLM_LAST)
	{
		uiKeyLen = 0;
	}

	if( uiKeyLen > XFLM_MAX_KEY_SIZE)
	{
		rc = RC_SET( NE_XFLM_BTREE_KEY_SIZE);
		goto Exit;
	}

	// Have we been passed a block address to look in?
	
	if( pui32BlkAddr && *pui32BlkAddr)
	{
		if( RC_OK( rc = findInBlock( pucKey, uiKeyLen, uiMatch, puiPosition,
			pui32BlkAddr, puiOffsetIndex)))
		{
			goto Exit;
		}
	}

	// Beginning at the root node, we will scan until we find the first key
	// that is greater than or equal to our target key.  If we don't find any
	// key that is larger than our target key, we will use the last block found.
	
	ui32BlkAddress = (FLMUINT32)m_pLFile->uiRootBlk;

	for( ;;)
	{
		// Get the block - Note that this will place a use on the block.
		// It must be properly released when done.

		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			ui32BlkAddress, NULL, &pSCache)))
		{
			goto Exit;
		}

		// We are building the stack inverted to make traversing it a bit easier.
		
		uiLevel = ((F_BTREE_BLK_HDR *)pSCache->m_pBlkHdr)->ui8BlkLevel;
		pStack = &m_Stack[ uiLevel];

		m_uiStackLevels++;

		pStack->pBlkHdr = (F_BTREE_BLK_HDR *)pSCache->m_pBlkHdr;
		pStack->ui32BlkAddr = ui32BlkAddress;
		pStack->pSCache = pSCache;
		pSCache = NULL;
		pStack->uiLevel = uiLevel;
		pStack->uiKeyLen = uiKeyLen;
		pStack->pucKeyBuf = pucKey;
		pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pStack->pBlkHdr, 0);

		if( isRootBlk( pStack->pBlkHdr))
		{
			m_uiRootLevel = uiLevel;
		}

		// Search the block for the key.  When we return from this method
		// the pStack will be pointing to the last entry we looked at.
		
		if( RC_BAD( rc = scanBlock( pStack, uiMatch)))
		{
			// It is okay if we couldn't find the key.  Especially if
			// we are still in the upper levels of the B-tree.
			
			if( (rc != NE_XFLM_NOT_FOUND) && (rc != NE_XFLM_EOF_HIT))
			{
				goto Exit;
			}
		}

		// Are we at the end of our search?
		
		if( (pStack->pBlkHdr->stdBlkHdr.ui8BlkType == BT_LEAF_DATA) ||
			 (pStack->pBlkHdr->stdBlkHdr.ui8BlkType == BT_LEAF) ||
			 (m_uiStackLevels - 1 >= m_uiSearchLevel))
		{
			if( m_bCounts && puiPosition)
			{
				flmAssert( m_uiSearchLevel >= BH_MAX_LEVELS);
				*puiPosition = uiPrevCounts + pStack->uiCurOffset;
			}

			// If this is a search for the last entry, then we should adjust the
			// uiCurOffset so that it points to a valid entry.
			
			if( uiMatch == XFLM_LAST)
			{
				m_pStack = pStack;

				for (;;)
				{
					if( RC_BAD( rc = moveStackToPrev( NULL)))
					{
						goto Exit;
					}
					
					// If we are on the leaf level, we need to make sure we are
					// looking at a first occurrence of an entry.
					
					if( pStack->pBlkHdr->stdBlkHdr.ui8BlkType == BT_LEAF_DATA)
					{
						pucEntry = BtEntry(
									(FLMBYTE *)m_pStack->pBlkHdr, m_pStack->uiCurOffset);

						if( bteFirstElementFlag( pucEntry))
						{
							break;
						}
					}
					else
					{
						break;
					}
				}
			}

			break;
		}
		else
		{
			if( m_bCounts && puiPosition)
			{
				uiPrevCounts += countRangeOfKeys( pStack, 0, pStack->uiCurOffset);
			}

			// Get the Child Block Address
			
			pucEntry = BtEntry(
				(FLMBYTE *)pStack->pSCache->m_pBlkHdr, pStack->uiCurOffset);
				
			ui32BlkAddress = bteGetBlkAddr( pucEntry);
		}
	}

	// Return the block and offset if needed.
	
	if( pui32BlkAddr)
	{
		*pui32BlkAddr = pStack->ui32BlkAddr;
	}

	if( puiOffsetIndex)
	{
		*puiOffsetIndex = pStack->uiCurOffset;
	}

	m_bStackSetup = TRUE;

Exit:

	if( RC_OK( rc) || (rc == NE_XFLM_NOT_FOUND) || (rc == NE_XFLM_EOF_HIT))
	{
		if( pStack)
		{
			m_pStack = pStack;
		}
	}

	if( pSCache)
	{
		ScaReleaseCache( pSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc:	Private method to search for a particular key in a pre-designted
		block offset.  If we don't find it at the given offset, we will do a
		binary search for it.  Note that a uiMatch of XFLM_FIRST & XFLM_LAST
		will be ignored if we locate the entry by the puiOffsetIndex parameter.  
		Also, this method does not setup the full stack.  Only the level where
		the block address passed in resides.
****************************************************************************/
RCODE F_Btree::findInBlock(
	const FLMBYTE *		pucKey,
	FLMUINT					uiKeyLen,
	FLMUINT					uiMatch,
	FLMUINT *				puiPosition,
	FLMUINT32 *				pui32BlkAddr,
	FLMUINT *				puiOffsetIndex)
{
	RCODE					rc = NE_XFLM_OK;
	F_BTSK *				pStack;
	F_CachedBlock *	pSCache = NULL;
	FLMBYTE *			pucEntry;
	const FLMBYTE *	pucBlkKey;
	FLMUINT				uiBlkKeyLen;

	// Get the block - Note that this will place a use on the block.
	// It must be properly released when done.

	if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
		*pui32BlkAddr, NULL, &pSCache)))
	{
		goto Exit;
	}

	if( !blkIsBTree( pSCache->getBlockPtr()))
	{
		rc = RC_SET( NE_XFLM_NOT_FOUND);
		goto Exit;
	}
	
	// Verify that the block belongs to the correct collection number
	
	if( ((F_BTREE_BLK_HDR *)pSCache->m_pBlkHdr)->ui16LogicalFile !=
		m_pLFile->uiLfNum)
	{
		rc = RC_SET( NE_XFLM_NOT_FOUND);
		goto Exit;
	}
	
	// Verify that we are looking at the same type of block,
	// i.e. collection vs index.
	
	if( ((F_BTREE_BLK_HDR *)pSCache->m_pBlkHdr)->ui8BTreeFlags & BLK_IS_INDEX &&
		m_pLFile->eLfType != XFLM_LF_INDEX)
	{
		rc = RC_SET( NE_XFLM_NOT_FOUND);
		goto Exit;
	}

	// If the block is not a leaf block, the caller will
	// need to do a full search down the B-Tree

	if( ((F_BTREE_BLK_HDR *)pSCache->m_pBlkHdr)->ui8BlkLevel != 0)
	{
		rc = RC_SET( NE_XFLM_NOT_FOUND);
		goto Exit;
	}

	pStack = &m_Stack[ 0];
	m_uiStackLevels++;

	pStack->pBlkHdr = (F_BTREE_BLK_HDR *)pSCache->m_pBlkHdr;
	pStack->ui32BlkAddr = *pui32BlkAddr;
	pStack->pSCache = pSCache;
	pSCache = NULL;
	pStack->uiLevel = 0;
	pStack->uiKeyLen = uiKeyLen;
	pStack->pucKeyBuf = pucKey;
	pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pStack->pBlkHdr, 0);
	pStack->uiCurOffset = puiOffsetIndex ? *puiOffsetIndex : 0;

	if( isRootBlk( pStack->pBlkHdr))
	{
		m_uiRootLevel = 0;
	}

	// See if the entry we are looking for is at the passed offset
	
	if( puiOffsetIndex)
	{
		if( *puiOffsetIndex < pStack->pBlkHdr->ui16NumKeys)
		{
			pucEntry = BtEntry( (FLMBYTE *)pStack->pBlkHdr, *puiOffsetIndex);

			uiBlkKeyLen = getEntryKeyLength( pucEntry,
								getBlkType( (FLMBYTE *)pStack->pBlkHdr), &pucBlkKey);

			if( uiKeyLen == uiBlkKeyLen)
			{
				if( f_memcmp( pucKey, pucBlkKey, uiKeyLen) == 0)
				{
					goto GotEntry;
				}
			}
		}
	}

	// Search the block for the key.  When we return from this method
	// the pStack will be pointing to the last entry we looked at.

	if( RC_BAD( rc = scanBlock( pStack, uiMatch)))
	{
		goto Exit;
	}

GotEntry:

	if( m_bCounts && puiPosition)
	{
		flmAssert( m_uiSearchLevel >= BH_MAX_LEVELS);

		// VISIT: These counts aren't accurate in this case.
		
		*puiPosition = pStack->uiCurOffset;
	}

	// Verify that we are looking at an entry with the firstElement flag set.
	
	m_pStack = pStack;

	for (;;)
	{
		// If we are on the leaf level, we need to make sure we are
		// looking at a first occurrence of an entry.
		
		if( m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType == BT_LEAF_DATA)
		{
			pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr,
									  m_pStack->uiCurOffset);

			if( bteFirstElementFlag( pucEntry))
			{
				break;
			}
		}
		else
		{
			break;
		}

		if( RC_BAD( rc = moveStackToPrev( NULL)))
		{
			goto Exit;
		}
	}

	*pui32BlkAddr = m_pStack->ui32BlkAddr;

	if( puiOffsetIndex)
	{
		*puiOffsetIndex = m_pStack->uiCurOffset;
	}

Exit:

	if( pSCache)
	{
		ScaReleaseCache( pSCache, FALSE);
	}

	if( RC_BAD( rc))
	{
		btRelease();
	}

	return( rc);
}

/***************************************************************************
Desc:	Method to search through a BTree block to find a specific key.  If 
		that key cannot be found, then the pStack will be positioned right 
		after the last entry in the block.  The search is a binary search that
		is looking for the first key that is >= the target key.  The uiMatch
		parameter further qualifies the search.  The XFLM_FIRST & XFLM_LAST
		values will ignore the key altogether and just return the first or last
		key respectively.  The XFLM_INCL value will return the key if found or the 
		first key following if not found.  The XFLM_EXACT will return an 
		NE_XFLM_NOT_FOUND if the key cannot be found.  XFLM_EXCL will return
		the first key following the target key.
****************************************************************************/
RCODE F_Btree::scanBlock(
	F_BTSK_p			pStack,
	FLMUINT			uiMatch)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT				uiTop;
	FLMUINT				uiMid;
	FLMUINT				uiBottom;
	FLMINT				iResult;
	F_CachedBlock *	pSCache = NULL;
	const FLMBYTE *	pucBlockKey;
	FLMBYTE *			pucEntry;
	FLMUINT				uiBlockKeyLen;

	if( pStack->pBlkHdr->ui16NumKeys == 0)
	{
		rc = RC_SET( NE_XFLM_BOF_HIT);
		goto Exit;
	}

	uiTop = 0;
	uiBottom = (FLMUINT)(pStack->pBlkHdr->ui16NumKeys - 1);

	if( uiMatch == XFLM_FIRST)
	{
		pStack->uiCurOffset = uiTop;
		goto Exit;
	}

	if( uiMatch == XFLM_LAST || pStack->uiKeyLen == 0)
	{
		pStack->uiCurOffset = uiBottom;
		goto Exit;
	}

	flmAssert( uiMatch == XFLM_INCL || uiMatch == XFLM_EXCL || 
				  uiMatch == XFLM_EXACT);

	// Test the first entry
	
	pucEntry = (FLMBYTE *)pStack->pBlkHdr +
										bteGetEntryOffset( pStack->pui16OffsetArray,
										uiTop);
											 
	uiBlockKeyLen = getEntryKeyLength( pucEntry,
										((F_BLK_HDR *)pStack->pBlkHdr)->ui8BlkType,
										 &pucBlockKey);

	// Compare the entries ...
	
	if( !uiBlockKeyLen)
	{
		// The LEM entry will always sort last!!

		iResult = 1;
		goto ResultGreater1;
	}
	else
	{
		if( RC_BAD( rc = compareBlkKeys( pucBlockKey, uiBlockKeyLen,
				pStack->pucKeyBuf, pStack->uiKeyLen, &iResult)))
		{
			goto Exit;
		}
	}
	
	if( iResult >= 0)
	{
ResultGreater1:

		if( iResult && uiMatch == XFLM_EXACT)
		{
			rc = RC_SET( NE_XFLM_NOT_FOUND);
		}
		
		uiMid = uiTop;
		goto VerifyPosition;
	}

	// If there is more than one entry in the block, we can skip the first
	// one since we have already seen it.
	
	if( uiTop < uiBottom)
	{
		uiTop++;
	}

	// Test the last
	
	pucEntry = (FLMBYTE *)pStack->pBlkHdr +
					bteGetEntryOffset( pStack->pui16OffsetArray,
											 uiBottom);
											 
	uiBlockKeyLen = getEntryKeyLength( pucEntry,
										((F_BLK_HDR *)pStack->pBlkHdr)->ui8BlkType,
										&pucBlockKey);

	if( !uiBlockKeyLen)
	{
		// The LEM entry will always sort last!!

		iResult = 1;
		goto ResultGreater2;
	}
	else
	{
		if( RC_BAD( rc = compareBlkKeys( pucBlockKey, uiBlockKeyLen,
				pStack->pucKeyBuf, pStack->uiKeyLen, &iResult)))
		{
			goto Exit;
		}
	}
	
	if( iResult <= 0)
	{
		if( iResult < 0 && uiMatch != XFLM_INCL)
		{
			rc = RC_SET( NE_XFLM_NOT_FOUND);
		}
		
		uiMid = uiBottom;
		goto VerifyPosition;
	}

ResultGreater2:

	for( ;;)
	{

		if( uiTop == uiBottom)
		{
			// We're done - didn't find it.
			
			if( uiMatch == XFLM_EXACT)
			{
				rc = RC_SET( NE_XFLM_NOT_FOUND);
			}
			
			uiMid = uiTop;
			break;
		}

		// Get the midpoint
		
		uiMid = (uiTop + uiBottom) / 2;

		pucEntry = (FLMBYTE *)pStack->pBlkHdr +
						bteGetEntryOffset( pStack->pui16OffsetArray,
												 uiMid);
												 
		uiBlockKeyLen = getEntryKeyLength( pucEntry,
								((F_BLK_HDR *)pStack->pBlkHdr)->ui8BlkType,
								&pucBlockKey);

		// Compare the entries

		if( !uiBlockKeyLen)
		{
			// The LEM entry will always sort last!!

			iResult = 1;
			goto ResultGreater;
		}
		else
		{
			if( RC_BAD( rc = compareBlkKeys( pucBlockKey, uiBlockKeyLen,
				pStack->pucKeyBuf, pStack->uiKeyLen, &iResult)))
			{
				goto Exit;
			}
		}

		if( iResult > 0)
		{
ResultGreater:

			// Midpoint (block key) is > Target key
			
			uiBottom = uiMid;
			continue;
		}

		if( iResult < 0)
		{
			// Midpoint (block key) is < Target key
			// Since we want to find the first key that is >= to the target key,
			// and we have aleady visited the key at uiMid and know that it is <
			// our target key, we can skip it and advance to the key that is one
			// beyond it.
			
			flmAssert( uiMid < uiBottom);
			uiTop = uiMid + 1;
			continue;
		}
		
		break;
	}

VerifyPosition:

	if( uiMatch != XFLM_EXCL)
	{
		// Verify that we are looking at the first occurrence of this key.
		
		while( iResult == 0)
		{
			if( uiMid > 0)
			{
				pucEntry = (FLMBYTE *)pStack->pBlkHdr +
								bteGetEntryOffset( pStack->pui16OffsetArray,
														 (uiMid - 1));

				uiBlockKeyLen = getEntryKeyLength( pucEntry,
										((F_BLK_HDR *)pStack->pBlkHdr)->ui8BlkType,
										&pucBlockKey);

				if( !uiBlockKeyLen)
				{
					// The LEM entry will always sort last!!

					iResult = 1;
				}
				else
				{
					if( RC_BAD( rc = compareBlkKeys( pucBlockKey, uiBlockKeyLen,
						pStack->pucKeyBuf, pStack->uiKeyLen, &iResult)))
					{
						goto Exit;
					}

					if( iResult == 0)
					{
						uiMid--;
					}
				}
			}
			else
			{
				break;
			}
		}
		
		pStack->uiCurOffset = uiMid;
	}
	else if( uiMatch == XFLM_EXCL)
	{
		// If we are at the leaf level, then we want to see if
		// this is the last entry in the last block.
		// If it is, then we cannot satisfy the request, otherwise
		// we will position to the next key and return ok.
		
		if( pStack->pBlkHdr->ui8BlkLevel == 0 &&
			 pStack->pBlkHdr->stdBlkHdr.ui32NextBlkInChain == 0 &&
			 uiMid == (FLMUINT)pStack->pBlkHdr->ui16NumKeys - 1 &&
			 iResult == 0)
		{
			rc = RC_SET( NE_XFLM_EOF_HIT);
		}
		else if( pStack->pBlkHdr->ui8BlkLevel == 0)
		{
			// Check for the next entry at leaf level			
			
			while( iResult == 0)
			{
				// Are we on the last key?
				
				if( uiMid == (FLMUINT)(pStack->pBlkHdr->ui16NumKeys - 1))
				{
					if( pStack->pBlkHdr->stdBlkHdr.ui32NextBlkInChain == 0)
					{
						rc = RC_SET( NE_XFLM_NOT_FOUND);
					}
					else
					{
						pStack->uiCurOffset = uiMid;
						m_pStack = pStack;

						if( RC_BAD( rc = moveStackToNext( NULL)))
						{
							goto Exit;
						}
						
						uiMid = 0;
					}
				}
				else
				{
					uiMid++;
				}

				pucEntry = (FLMBYTE *)pStack->pBlkHdr +
								bteGetEntryOffset( pStack->pui16OffsetArray,
														 uiMid);

				uiBlockKeyLen = getEntryKeyLength( pucEntry,
											((F_BLK_HDR *)pStack->pBlkHdr)->ui8BlkType,
											&pucBlockKey);

				if( !uiBlockKeyLen)
				{
					// The LEM entry will always sort last!!

					iResult = 1;
				}
				else
				{
					if( RC_BAD( rc = compareBlkKeys( pucBlockKey, uiBlockKeyLen,
						pStack->pucKeyBuf, pStack->uiKeyLen, &iResult)))
					{
						goto Exit;
					}
				}
			}
			
			pStack->uiCurOffset = uiMid;
			if( uiMid == (FLMUINT)(pStack->pBlkHdr->ui16NumKeys - 1) &&
							pStack->pBlkHdr->stdBlkHdr.ui32NextBlkInChain == 0)
			{
				rc = RC_SET( NE_XFLM_EOF_HIT);
			}
		}
		else
		{
			pStack->uiCurOffset = uiMid;
		}
	}

Exit:

	if( pSCache)
	{
		ScaReleaseCache( pSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc: This method will compare two key fields.
		Returned values:  0 - Keys are equal
								1 - Key in Block is > Target key
							  -1 - Key in Block is < Target key
****************************************************************************/
RCODE F_Btree::compareKeys(
	const FLMBYTE *	pucKey1,
	FLMUINT				uiKeyLen1,
	const FLMBYTE *	pucKey2,
	FLMUINT				uiKeyLen2,
	FLMINT *				piCompare)
{
	RCODE		rc = NE_XFLM_OK;
	
	if( !m_pCompare)
	{
		if( (*piCompare = f_memcmp( pucKey1, pucKey2, 
				f_min( uiKeyLen1, uiKeyLen2))) == 0)
		{
			*piCompare = uiKeyLen1 == uiKeyLen2
								? 0
								: uiKeyLen1 < uiKeyLen2 
									? -1
									: 1;
		}
	}
	else
	{
		if( RC_BAD( rc = m_pCompare->compare( pucKey1, uiKeyLen1,
										pucKey2, uiKeyLen2, piCompare)))
		{
			goto Exit;
		}
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Method for positioning to a specific entry.
****************************************************************************/
RCODE F_Btree::positionToEntry(
	FLMUINT			uiPosition)
{
	RCODE					rc = NE_XFLM_OK;
	F_BTSK *				pStack = NULL;
	FLMUINT32			ui32BlkAddress;
	F_CachedBlock *	pSCache = NULL;
	FLMUINT				uiLevel;
	FLMBYTE *			pucEntry;
	FLMUINT				uiPrevCounts = 0;

	// Make sure the stack is clean before we start.
	
	btRelease();

	// Beginning at the root node.
	
	ui32BlkAddress = (FLMUINT32)m_pLFile->uiRootBlk;

	// Get the block - Note that this will place a use on the block.
	// It must be properly released when done.
	
	while( ui32BlkAddress)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			ui32BlkAddress, NULL, &pSCache)))
		{
			goto Exit;
		}

		uiLevel = ((F_BTREE_BLK_HDR *)pSCache->m_pBlkHdr)->ui8BlkLevel;
		pStack = &m_Stack[ uiLevel];

		pStack->pBlkHdr = (F_BTREE_BLK_HDR *)pSCache->m_pBlkHdr;
		pStack->ui32BlkAddr = ui32BlkAddress;
		pStack->pSCache = pSCache;
		pSCache = NULL;
		pStack->uiLevel = uiLevel;
		pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pStack->pBlkHdr, 0);

		m_uiStackLevels++;

		if( RC_BAD( rc = searchBlock( pStack->pBlkHdr, &uiPrevCounts,
			uiPosition, &pStack->uiCurOffset)))
		{
			goto Exit;
		}

		if( (pStack->pBlkHdr->stdBlkHdr.ui8BlkType == BT_LEAF_DATA) ||
			 (pStack->pBlkHdr->stdBlkHdr.ui8BlkType == BT_LEAF))
		{
			ui32BlkAddress = 0;
		}
		else
		{
			// Get the next child block address
			
			pucEntry = BtEntry( (FLMBYTE *)pStack->pBlkHdr,
									  pStack->uiCurOffset);

			ui32BlkAddress = bteGetBlkAddr( pucEntry);
		}
	}

	m_uiRootLevel = m_uiStackLevels - 1;

Exit:

	if( RC_OK( rc) || (rc == NE_XFLM_NOT_FOUND) || (rc == NE_XFLM_EOF_HIT))
	{
		m_pStack = pStack;
	}

	if( pSCache)
	{
		ScaReleaseCache( pSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc:
****************************************************************************/
RCODE F_Btree::searchBlock(
	F_BTREE_BLK_HDR *		pBlkHdr,
	FLMUINT *				puiPrevCounts,
	FLMUINT					uiPosition,
	FLMUINT *				puiOffset)
{
	RCODE				rc = NE_XFLM_OK;
	FLMUINT			uiOffset;
	FLMUINT			uiNumKeys;
	FLMUINT			uiCounts;
	FLMBYTE *		pucEntry;

	uiNumKeys = pBlkHdr->ui16NumKeys;

	if( getBlkType( (FLMBYTE *)pBlkHdr) != BT_NON_LEAF_COUNTS)
	{
		flmAssert( uiPosition >= *puiPrevCounts);
		
		uiOffset = uiPosition - *puiPrevCounts;
		*puiPrevCounts = uiPosition;
	}
	else
	{
		for( uiOffset = 0; uiOffset < uiNumKeys; uiOffset++)
		{
			pucEntry = BtEntry( (FLMBYTE *)pBlkHdr, uiOffset);
			pucEntry += 4;
			
			uiCounts = FB2UD( pucEntry);

			if( *puiPrevCounts + uiCounts >= (uiPosition + 1))
			{
				break;
			}
			else
			{
				*puiPrevCounts += uiCounts;
			}
		}
	}

	if( uiOffset >= uiNumKeys)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
	}

	*puiOffset = uiOffset;
	return( rc);
}

/***************************************************************************
Desc:	Method to move all the data in the block into a contiguous space.
****************************************************************************/
RCODE F_Btree::defragmentBlock(
	F_CachedBlock **			ppSCache)
{
	RCODE							rc = NE_XFLM_OK;
	FLMUINT						uiNumKeys;
	FLMBOOL						bSorted;
	FLMBYTE *					pucCurEntry;
	FLMBYTE *					pucPrevEntry;
	FLMBYTE *					pucTempEntry;
	FLMUINT						uiTempToMove;
	FLMUINT						uiIndex;
	FLMUINT						uiAmtToMove;
	FLMUINT						uiFirstHole;
	FLMUINT16					ui16BlkBytesAvail;
	FLMUINT16 *					pui16OffsetArray;
	F_CachedBlock *			pSCache = *ppSCache;
	F_BTREE_BLK_HDR *			pBlk = (F_BTREE_BLK_HDR *)pSCache->m_pBlkHdr;
	F_BTREE_BLK_HDR *			pOldBlk = NULL;
	FLMBYTE *					pucHeap;
	FLMBYTE *					pucBlkEnd;
	F_CachedBlock *			pOldSCache = NULL;

	flmAssert( pBlk->stdBlkHdr.ui16BlkBytesAvail != pBlk->ui16HeapSize);

	if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( 
		m_pDb, &pSCache, &pOldSCache)))
	{
		goto Exit;
	}

	pBlk = (F_BTREE_BLK_HDR *)pSCache->m_pBlkHdr;
	*ppSCache = pSCache;
	uiNumKeys = getBlkEntryCount( (FLMBYTE *)pBlk);
	
	// Determine if the entries are sorted

	pucPrevEntry = (FLMBYTE *)pBlk + m_uiBlockSize;
	bSorted = TRUE;
	uiFirstHole = 0;
	pucHeap = (FLMBYTE *)pBlk + m_uiBlockSize;

	for( uiIndex = 0; uiIndex < uiNumKeys; uiIndex++)
	{
		pucCurEntry = BtEntry( (FLMBYTE *)pBlk, uiIndex);

		if( pucPrevEntry < pucCurEntry)
		{
			bSorted = FALSE;
			break;
		}
		else
		{
			uiAmtToMove = actualEntrySize( 
									getEntrySize( (FLMBYTE *)pBlk, uiIndex));
			pucHeap -= uiAmtToMove;

			if( !uiFirstHole && pucHeap != pucCurEntry)
			{
				uiFirstHole = uiIndex + 1;
			}
		}

		pucPrevEntry = pucCurEntry;
	}
	
	ui16BlkBytesAvail = (FLMUINT16)(m_uiBlockSize - sizeofBTreeBlkHdr( pBlk)) -
							  (FLMUINT16)(uiNumKeys * 2);
	pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pBlk, 0);
	pucBlkEnd = (FLMBYTE *)pBlk + m_uiBlockSize;

	if( uiFirstHole > 1)
	{
		uiFirstHole--;
		pucHeap = BtEntry( (FLMBYTE *)pBlk, uiFirstHole - 1);
		ui16BlkBytesAvail -= (FLMUINT16)(pucBlkEnd - pucHeap);
	}
	else
	{
		uiFirstHole = 0;
		pucHeap = pucBlkEnd;
	}

	if( !bSorted)
	{
		FLMUINT16 *			pui16OldOffsetArray;

		// If old and new blocks are the same (because of a 
		// prior call to logBlock), we need to save a copy of the block
		// before making changes.

		if( !pOldSCache)
		{
			f_memcpy( m_pucTempDefragBlk, pSCache->m_pBlkHdr, m_uiBlockSize);
			pOldBlk = (F_BTREE_BLK_HDR *)m_pucTempDefragBlk;
		}
		else
		{
			pOldBlk = (F_BTREE_BLK_HDR *)pOldSCache->m_pBlkHdr;
		}

		pui16OldOffsetArray = BtOffsetArray( (FLMBYTE *)pOldBlk, 0);

		// Rebuild the block so that all of the entries are in order

		for( uiIndex = uiFirstHole; uiIndex < uiNumKeys; uiIndex++)
		{
			pucCurEntry = BtEntry( (FLMBYTE *)pOldBlk, uiIndex);
			uiAmtToMove = actualEntrySize( getEntrySize( (FLMBYTE *)pOldBlk, uiIndex));
			pucHeap -= uiAmtToMove;
			bteSetEntryOffset( pui16OffsetArray, uiIndex,
				(FLMUINT16)(pucHeap - (FLMBYTE *)pBlk));
			uiIndex++;

			while( uiIndex < uiNumKeys)
			{
				pucTempEntry = BtEntry( (FLMBYTE *)pOldBlk, uiIndex);
				uiTempToMove = actualEntrySize( getEntrySize( (FLMBYTE *)pOldBlk, uiIndex));

				if ((pucCurEntry - uiTempToMove) != pucTempEntry)
				{
					uiIndex--;
					break;
				}
				else
				{
					pucCurEntry -= uiTempToMove;
					pucHeap -= uiTempToMove;
					uiAmtToMove += uiTempToMove;
					bteSetEntryOffset( pui16OffsetArray, uiIndex, 
							(FLMUINT16)(pucHeap - (FLMBYTE *)pBlk));
					uiIndex++;
				}
			}

			f_memcpy( pucHeap, pucCurEntry, uiAmtToMove);
			ui16BlkBytesAvail -= (FLMUINT16)uiAmtToMove;
		}
	}
	else
	{
		// Work back from the first hole.  Move entries to fill all of the
		// holes in the block.

		for( uiIndex = uiFirstHole; uiIndex < uiNumKeys; uiIndex++)
		{
			pucCurEntry = BtEntry( (FLMBYTE *)pBlk, uiIndex);
			uiAmtToMove = actualEntrySize( getEntrySize( (FLMBYTE *)pBlk, uiIndex));
			pucHeap -= uiAmtToMove;

			if( pucHeap != pucCurEntry)
			{
				// We have a hole.  We don't want to move just one entry
				// if we can avoid it.  We would like to continue searching
				// until we find either the end, or another hole.  Then we
				// can move a larger block of data instead of one entry.

				bteSetEntryOffset( pui16OffsetArray, uiIndex, 
						(FLMUINT16)(pucHeap - (FLMBYTE *)pBlk));
				uiIndex++;

				while( uiIndex < uiNumKeys)
				{
					pucTempEntry = BtEntry( (FLMBYTE *)pBlk, uiIndex);
					uiTempToMove = actualEntrySize( 
											getEntrySize( (FLMBYTE *)pBlk, uiIndex));

					if( (pucCurEntry - uiTempToMove) != pucTempEntry)
					{
						uiIndex--;
						break;
					}
					else
					{
						pucCurEntry -= uiTempToMove;
						pucHeap -= uiTempToMove;
						uiAmtToMove += uiTempToMove;
						bteSetEntryOffset( pui16OffsetArray, 
							uiIndex, (FLMUINT16)(pucHeap - (FLMBYTE *)pBlk));
						uiIndex++;
					}
				}
			}

			// Now move the range we have determined.

			f_memmove( pucHeap, pucCurEntry, uiAmtToMove);
			ui16BlkBytesAvail -= (FLMUINT16)(uiAmtToMove);
		}
	}

	// Set the available space.  If there are no keys in this block, we should
	// set the it to the calculated available space

	if( !uiNumKeys)
	{
		pBlk->stdBlkHdr.ui16BlkBytesAvail = ui16BlkBytesAvail;
	}

	flmAssert( pBlk->stdBlkHdr.ui16BlkBytesAvail == ui16BlkBytesAvail);
	pBlk->ui16HeapSize = ui16BlkBytesAvail;

	// Clean up the heap space.

#ifdef FLM_DEBUG
	f_memset( getBlockEnd( pBlk) - ui16BlkBytesAvail, 0, ui16BlkBytesAvail);
#endif

Exit:

	if( pOldSCache)
	{
		ScaReleaseCache( pOldSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc:	Method to handle the insertion, deletion and replacment of a single
		entry in a block.
		Assumption:  The find method has already been called to locate the
		insertion point, so the stack has already been setup.
****************************************************************************/
RCODE F_Btree::updateEntry(
	const FLMBYTE *		pucKey,		// In
	FLMUINT					uiKeyLen,	// In
	const FLMBYTE *		pucValue,	// In
	FLMUINT					uiLen,		// In
	F_ELM_UPD_ACTION		eAction,
	FLMBOOL					bTruncate)
{
	RCODE						rc = NE_XFLM_OK;
	const FLMBYTE *		pucRemainingValue = NULL;
	FLMUINT					uiRemainingLen = 0;
	const FLMBYTE *		pucSavKey = pucKey;
	FLMUINT					uiSavKeyLen = uiKeyLen;
	FLMUINT					uiChildBlkAddr = 0;
	FLMUINT					uiCounts = 0;
	FLMUINT					uiFlags = BTE_FLAG_FIRST_ELEMENT | BTE_FLAG_LAST_ELEMENT;
	FLMBOOL					bMoreToRemove = FALSE;
	FLMBOOL					bDone = FALSE;
	FLMUINT					uiOrigDataLen = uiLen;
	FLMBOOL					bOrigTruncate = bTruncate;

	flmAssert( m_pReplaceInfo == NULL);

	// For each level that needs modifying...
	
	while( !bDone)
	{

		switch( eAction)
		{
			case ELM_INSERT_DO:
			{
				// In this case, the uiLen parameter represents the OADataLength.
				
				uiFlags = BTE_FLAG_DATA_BLOCK |
							 BTE_FLAG_FIRST_ELEMENT |
							 BTE_FLAG_LAST_ELEMENT |
							 BTE_FLAG_OA_DATA_LEN;

				if( RC_BAD( rc = insertEntry( &pucKey, &uiKeyLen, pucValue,
					uiLen, uiFlags, &uiChildBlkAddr, &uiCounts, &pucRemainingValue,
					&uiRemainingLen, &eAction)))
				{
					goto Exit;
				}
				
				// Not needed for upper levels of the Btree.
				
				pucValue = NULL;
				uiLen = 0;
				break;
			}

			case ELM_INSERT:
			{
				// This function will return all info needed to handle the next
				// level up in the Btree (if anything), including setting up
				// the stack.  pucKey & uiKeyLen will be pointing to the key that
				// the upper level needs to insert, replace or delete.
				//
				// It will be pointing to an entry in a lower level block, so that
				// block must not be released until after we are all done.

				if( RC_BAD( rc = insertEntry( &pucKey, &uiKeyLen, pucValue,
					uiLen, uiFlags, &uiChildBlkAddr, &uiCounts, &pucRemainingValue,
					&uiRemainingLen, &eAction)))
				{
					goto Exit;
				}
				
				// Not needed for upper levels of the Btree.
				
				pucValue = NULL;
				uiLen = 0;
				break;
			}
			
			case ELM_REPLACE_DO:
			{
				// In this case, the uiLen parameter represents the OADataLength.
				
				uiFlags = BTE_FLAG_DATA_BLOCK |
							 BTE_FLAG_FIRST_ELEMENT |
							 BTE_FLAG_LAST_ELEMENT |
							 BTE_FLAG_OA_DATA_LEN;

				// Should only get here if we are able to truncate the data.
				
				flmAssert( bTruncate);

				if( RC_BAD( rc = replaceEntry( &pucKey, &uiKeyLen, pucValue,
					uiLen, uiFlags, &uiChildBlkAddr, &uiCounts,
					&pucRemainingValue, &uiRemainingLen, &eAction)))
				{
					goto Exit;
				}

				// Not needed for upper levels of the Btree.
				
				pucValue = NULL;
				uiLen = 0;
				bTruncate = TRUE;
				break;
			}
			
			case ELM_REPLACE:
			{
				if( RC_BAD( rc = replaceEntry( &pucKey, &uiKeyLen, pucValue,
					uiLen, uiFlags, &uiChildBlkAddr, &uiCounts, &pucRemainingValue,
					&uiRemainingLen, &eAction, bTruncate)))
				{
					goto Exit;
				}

				// Not needed for upper levels of the Btree.
				
				pucValue = NULL;
				uiLen = 0;
				bTruncate = TRUE;
				break;
			}
			
			case ELM_REMOVE:
			{
				if (RC_BAD( rc = removeEntry( &pucKey, &uiKeyLen, &uiChildBlkAddr,
					&uiCounts, &bMoreToRemove, &eAction)))
				{
					goto Exit;
				}

				// Not needed for upper levels of the B-Tree.
				
				pucValue = NULL;
				uiLen = 0;

				break;
			}
			
			case ELM_DONE:
			{
				if( m_pReplaceInfo)
				{
					// This info structure gets generated when the replaced entry in
					// the upper levels is the last entry in the block and we had to
					// move entries to a previous block to accommodate it.
					// We will therefore need to update the parent block with this
					// new information. We need to take care of this before we check
					// for any additional data to store.
					
					if( RC_BAD( rc = restoreReplaceInfo( &pucKey, &uiKeyLen,
						&uiChildBlkAddr, &uiCounts)))
					{
					  goto Exit;
					}
					
					bTruncate = bOrigTruncate;
					eAction = ELM_REPLACE;
				}
				else if( bMoreToRemove)
				{
					eAction = ELM_REMOVE;
					
					// We need to locate where we should remove the entry.
					
					if( RC_BAD( rc = findEntry( pucSavKey, uiSavKeyLen, XFLM_EXACT)))
					{
						goto Exit;
					}

				}
				else if( pucRemainingValue && uiRemainingLen)
				{
					eAction = ELM_INSERT;
					
					// We need to locate where we should insert the new entry.
					
					rc = findEntry( pucSavKey, uiSavKeyLen, XFLM_EXCL);

					// We could find this entry.  If we get back anything other than
					// an NE_XFLM_EOF_HIT or NE_XFLM_OK, then there is a problem.
					
					if( rc != NE_XFLM_OK && rc != NE_XFLM_EOF_HIT &&
						 rc != NE_XFLM_NOT_FOUND)
					{
						goto Exit;
					}

					pucValue = pucRemainingValue;
					uiLen = uiRemainingLen;
					pucKey = pucSavKey;
					uiKeyLen = uiSavKeyLen;

					// Make certain that the  BTE_FIRST_ELEMENT flag is NOT set if
					// the first part of the data was stored.
					
					if( uiOrigDataLen != uiLen)
					{
						uiFlags = BTE_FLAG_LAST_ELEMENT;
					}
					else
					{
						uiFlags = BTE_FLAG_FIRST_ELEMENT | BTE_FLAG_LAST_ELEMENT;
					}
				}
				else
				{
					bDone = TRUE;
				}

				break;
			}
			
			// Should never get this!
			
			case ELM_BLK_MERGE:
			{
				rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
				goto Exit;
			}
		}
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	This method will coordinate inserting an entry into a block.  If it
		cannot fit it all in, then it may have to break the entry up so that
		it spans more than one block.  It will also setup for the next level
		before returning.
****************************************************************************/
RCODE F_Btree::insertEntry(
	const FLMBYTE **		ppucKey,
	FLMUINT *				puiKeyLen,
	const FLMBYTE *		pucValue,
	FLMUINT					uiLen,
	FLMUINT					uiFlags,
	FLMUINT *				puiChildBlkAddr,
	FLMUINT *				puiCounts,
	const FLMBYTE **		ppucRemainingValue,
	FLMUINT *				puiRemainingLen,
	F_ELM_UPD_ACTION *	peAction)
{
	RCODE						rc = NE_XFLM_OK;
	const FLMBYTE *		pucDataValue = pucValue;
	FLMUINT					uiDataLen = uiLen;
	FLMUINT					uiOADataLen = 0;
	FLMUINT					uiEntrySize = 0;
	FLMBOOL					bEntriesWereMoved = FALSE;
	FLMBOOL					bHaveRoom;
	FLMBOOL					bLastEntry;
	const FLMBYTE *		pucKey = *ppucKey;
	FLMUINT					uiKeyLen = *puiKeyLen;
	FLMUINT					uiChildBlkAddr = *puiChildBlkAddr;
	FLMUINT					uiCounts = *puiCounts;
	F_CachedBlock *		pPrevSCache = NULL;
	FLMBYTE *				pucEntry;
	FLMBOOL					bDefragBlk = FALSE;
	FLMBOOL					bBlockSplit;

	if( m_pStack->uiLevel == 0)
	{
		// We are only safe to do this when we are working on level 0
		// (leaf level) of the Btree.
		
		*ppucRemainingValue = NULL;
		*puiRemainingLen = 0;
	}

	if( *peAction == ELM_INSERT_DO)
	{
		// Adjust the data entry sizes as the data passed in is the 
		// OA Data Length.
		
		uiOADataLen = uiLen;
		uiDataLen = 4;
	}

	// Process until we are done

StartOver:

	if( RC_BAD( rc = calcNewEntrySize( uiKeyLen, uiDataLen, &uiEntrySize,
			&bHaveRoom, &bDefragBlk)))
	{
		goto Exit;
	}

	// Does the entry fit into the block?
	
	if( bHaveRoom)
	{
		if( bDefragBlk)
		{
			// We will have to defragment the block before we can store the data
			
			if( RC_BAD( rc = defragmentBlock( &m_pStack->pSCache)))
			{
				goto Exit;
			}
		}
		
		if( RC_BAD( rc = storeEntry( pucKey, uiKeyLen, pucDataValue,
				uiDataLen, uiFlags, uiOADataLen, uiChildBlkAddr, uiCounts,
				uiEntrySize, &bLastEntry)))
		{
			goto Exit;
		}

		if( (bLastEntry || m_bCounts) && !isRootBlk( m_pStack->pBlkHdr))
		{
			// Are we in here because of the counts only?  If so, then we
			// can update the counts right here, no need to continue.
			
			if( !bLastEntry)
			{
				if( RC_BAD( rc = updateCounts()))
				{
					goto Exit;
				}
				
				*peAction = ELM_DONE;
			}
			else
			{
				// Ensure we are updating with the correct key.
				
				pucEntry = BtLastEntry( (FLMBYTE *)m_pStack->pBlkHdr);

				*puiKeyLen = getEntryKeyLength( pucEntry,
									m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType, ppucKey);

				*puiChildBlkAddr = m_pStack->ui32BlkAddr;

				// Do we need counts for the next level?
				
				if( m_bCounts)
				{
					*puiCounts = countKeys( (FLMBYTE *)m_pStack->pBlkHdr);
				}
				
				m_pStack++;
				*peAction = ELM_REPLACE;
			}
		}
		else
		{
			*peAction = ELM_DONE;
		}
		
		goto Exit;
	}

	// Can we move entries around at all to make some room?
	
	if( RC_BAD( rc = moveEntriesToPrevBlk(  uiEntrySize, &pPrevSCache,
			&bEntriesWereMoved)))
	{
		goto Exit;
	}

	if( bEntriesWereMoved)
	{
		// Only defragment the block if the heap size is not big enough.
		
		if( uiEntrySize > m_pStack->pBlkHdr->ui16HeapSize)
		{
			if( RC_BAD( rc = defragmentBlock( &m_pStack->pSCache)))
			{
				goto Exit;
			}
		}
		
		// Store the entry now because we know there is enough room
		
		if( RC_BAD( rc = storeEntry( pucKey, uiKeyLen, pucDataValue,
				uiDataLen, uiFlags, uiOADataLen, uiChildBlkAddr, uiCounts,
				uiEntrySize, &bLastEntry)))
		{
			goto Exit;
		}

		// Ordinarily, this would NEVER be the last element in the
		// block because we need to adjust the stack to take care of the
		// elements we just moved!  There is only one condition where we would
		// insert as the last entry in the block, and that is when this
		// insert is actually a part of a replace operation where the data
		// is too large to fit in the block.  We had to remove the entry, then
		// insert the new one and we are in the upper levels of the
		// btree. (i.e. not at the leaf).
		//
		// VISIT:  Should I assert that we are not at the leaf level 
		// if we get in here?
		
		if( bLastEntry)
		{
			// Since we just added an entry to the last position of the
			// current block.  We will need to preserve the current stack so
			// that we can finish updating the parentage later. Should only
			// happen as a result of a replace operation where the new entry
			// is larger than the existing one while in the upper levels.
			
			if( RC_BAD( rc = saveReplaceInfo( pucKey, uiKeyLen)))
			{
				goto Exit;
			}
		}

		// Need to update the counts of the parents if we are maintining
		// counts before we abandon
		
		if( m_bCounts)
		{
			if( RC_BAD( rc = updateCounts()))
			{
				goto Exit;
			}
		}

		// This method will release any blocks no longer referenced
		// in the stack.  Then pull in the previous block information into
		// the stack.
		
		if( RC_BAD( rc = moveStackToPrev( pPrevSCache)))
		{
			goto Exit;
		}

		// If we are maintaining counts, then lets return a count of the
		// current number of keys referenced below this point.
		
		if( m_bCounts)
		{
			*puiCounts = countKeys( (FLMBYTE *)m_pStack->pBlkHdr);
		}

		flmAssert( !isRootBlk( m_pStack->pBlkHdr));

		// Return the key to the last entry in the prevous block.
		// Recall that we have changed that stack now so that it
		// is referencing the changed block (pPrevSCache).
		
		pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr, m_pStack->uiCurOffset);
								  
		*puiKeyLen = getEntryKeyLength( pucEntry,
								pPrevSCache->m_pBlkHdr->ui8BlkType, ppucKey);

		// Return the new child block address
		
		*puiChildBlkAddr = m_pStack->ui32BlkAddr;

		// Set up to fixup the parentage of the previous block on return...
		
		m_pStack++;

		// Return the new action for the parent block.
		
		*peAction = ELM_REPLACE;
		goto Exit;
	}

	// Try moving to the next block...
	
	if( RC_BAD( rc = moveEntriesToNextBlk( uiEntrySize, &bEntriesWereMoved)))
	{
		goto Exit;
	}

	if( bEntriesWereMoved)
	{
		// Only defragment the block if the heap size is not big enough.
		
		if( uiEntrySize > m_pStack->pBlkHdr->ui16HeapSize)
		{
			if( RC_BAD( rc = defragmentBlock( &m_pStack->pSCache)))
			{
				goto Exit;
			}
		}

		// Store the entry now because we know there is enough room
		
		if( RC_BAD( rc = storeEntry( pucKey, uiKeyLen, pucDataValue,
				uiDataLen, uiFlags, uiOADataLen, uiChildBlkAddr, uiCounts,
				uiEntrySize, &bLastEntry)))
		{
			goto Exit;
		}

		// Return the key to the last entry in the current block.
		// Note: If bLastEntry is TRUE, we already know what the key is.
		
		if( !bLastEntry)
		{
			// Get the last key from the block.
			
			pucEntry = BtLastEntry( (FLMBYTE *)m_pStack->pBlkHdr);

			*puiKeyLen = getEntryKeyLength( pucEntry,
									m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType, ppucKey);

		}

		flmAssert( !isRootBlk( m_pStack->pBlkHdr));

		// if we are maintaining counts, then lets return a count of the
		// current number of keys referenced below this point.
		
		if( m_bCounts)
		{
			*puiCounts = countKeys( (FLMBYTE *)m_pStack->pBlkHdr);
		}

		// Return the new child block address
		
		*puiChildBlkAddr = m_pStack->ui32BlkAddr;

		// Set up to fixup the parentage of the this block on return...
		
		m_pStack++;
		*peAction = ELM_REPLACE;

		goto Exit;
	}

	// Before we incur the expense of a block split, see if we can store this
	// entry in the previous block.  If we can, we will save some space.  This
	// will only happen if we are trying to insert at the first position in
	// this block.  We would only ever get into this block of code once for
	// each level of the btree.

	if( m_pStack->uiCurOffset == 0 && 
		  m_pStack->pBlkHdr->stdBlkHdr.ui32PrevBlkInChain)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock(
				m_pDb, m_pLFile, m_pStack->pBlkHdr->stdBlkHdr.ui32PrevBlkInChain,
				NULL, &pPrevSCache)))
		{
			goto Exit;
		}

		if( RC_BAD( rc = moveStackToPrev( pPrevSCache)))
		{
			goto Exit;
		}

		// Increment so we point to one past the last entry.

		m_pStack->uiCurOffset++;
		goto StartOver;
	}

	// We will have to split the block to make room for this entry.
	
	if( RC_BAD( rc = splitBlock( *ppucKey, *puiKeyLen, pucDataValue,
			uiDataLen, uiFlags, uiOADataLen, uiChildBlkAddr, uiCounts,
			ppucRemainingValue, puiRemainingLen, &bBlockSplit)))
	{
		goto Exit;
	}

	// Return the new key value.
	
	pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr, m_pStack->uiCurOffset);

	*puiKeyLen = getEntryKeyLength( pucEntry,
						((F_BLK_HDR *)m_pStack->pBlkHdr)->ui8BlkType, ppucKey);

	// Return the child block address and the counts (if needed).
	
	*puiChildBlkAddr = m_pStack->ui32BlkAddr;

	// Return the counts if we are maintaining them
	
	if( m_bCounts)
	{
		*puiCounts = countKeys( (FLMBYTE *)m_pStack->pBlkHdr);
	}

	// The bBlockSplit boolean will only be FALSE if we were involved in a
	// ReplaceByInsert operation and the call to split resulted in an empty
	// block.  Thus we were able to store the new entry.  In such cases,
	// only the count (if any) need to be updated, not the keys.
	
	if( bBlockSplit)
	{
		*peAction = ELM_INSERT;
		m_pStack++;
	}
	else
	{
		*peAction = ELM_DONE;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Method to handle the insertion of a single entry into a block.
		Assumption:  The find method has already been called to locate the 
		insertion point, so the stack has already been setup.
****************************************************************************/
RCODE F_Btree::storeEntry(
	const FLMBYTE *		pucKey,
	FLMUINT					uiKeyLen,
	const FLMBYTE *		pucValue,
	FLMUINT					uiLen,
	FLMUINT					uiFlags,
	FLMUINT					uiOADataLen,
	FLMUINT					uiChildBlkAddr,
	FLMUINT					uiCounts,
	FLMUINT					uiEntrySize,
	FLMBOOL *				pbLastEntry)
{
	RCODE						rc = NE_XFLM_OK;
	FLMUINT					uiBlkType = m_pStack->pSCache->m_pBlkHdr->ui8BlkType;
	FLMBYTE *				pucInsertAt;
	FLMUINT16 *				pui16OffsetArray;
	FLMUINT					uiNumKeys;
	FLMUINT					uiTmp;
	F_BTREE_BLK_HDR *		pBlk;

	// Assume this is not the last entry for now.
	// We will change it later if needed.

	*pbLastEntry = FALSE;

	// We can go ahead and insert this entry as it is.  All checking has been
	// made before getting to this point.

	uiEntrySize = calcEntrySize( uiBlkType, uiFlags,
										  uiKeyLen, uiLen, uiOADataLen);

	// Log this block before making any changes to it.  Since the
	// pSCache could change, we must update the block header after the call.

	if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &m_pStack->pSCache)))
	{
		goto Exit;
	}

	pBlk = m_pStack->pBlkHdr =	(F_BTREE_BLK_HDR *)m_pStack->pSCache->m_pBlkHdr;
	m_pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pBlk, 0);
	uiNumKeys = getBlkEntryCount( (FLMBYTE *)pBlk);
	pucInsertAt = getBlockEnd( pBlk) - uiEntrySize;
	pui16OffsetArray = m_pStack->pui16OffsetArray;

	if( RC_BAD( rc = buildAndStoreEntry( uiBlkType, uiFlags, pucKey, uiKeyLen,
		pucValue, uiLen, uiOADataLen, uiChildBlkAddr, uiCounts,
		pucInsertAt, uiEntrySize, NULL)))
	{
		goto Exit;
	}

	// Now to update the offset in the offset array.  This will move all
	// entries that sort after the new entry down by one position.

	for( uiTmp = uiNumKeys; uiTmp > m_pStack->uiCurOffset; uiTmp--)
	{
		bteSetEntryOffset( pui16OffsetArray, uiTmp,
								 bteGetEntryOffset( pui16OffsetArray, uiTmp - 1));
	}

	bteSetEntryOffset( pui16OffsetArray, m_pStack->uiCurOffset,
							 (FLMUINT16)(pucInsertAt - (FLMBYTE *)pBlk));

	// Update the available space and the number of keys.
	// Account for the new offset entry too.

	m_pStack->pBlkHdr->stdBlkHdr.ui16BlkBytesAvail -= 
			(FLMUINT16)(uiEntrySize + 2);
	m_pStack->pBlkHdr->ui16HeapSize -= (FLMUINT16)(uiEntrySize + 2);
	m_pStack->pBlkHdr->ui16NumKeys++;

	// Check to see if this was the last entry

	if( m_pStack->uiCurOffset == (FLMUINT)(m_pStack->pBlkHdr->ui16NumKeys - 1))
	{
		*pbLastEntry = TRUE;
	}

	if( !m_pStack->uiLevel && (uiFlags & BTE_FLAG_FIRST_ELEMENT))
	{
		m_ui32PrimaryBlkAddr = m_pStack->ui32BlkAddr;
		m_uiCurOffset = m_pStack->uiCurOffset;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	This method will coordinate removing an entry from a block. If the
		entry spans more than one block, it will set the flag pbMoreToRemove.
		It will also setup for the next level before returning.
****************************************************************************/
RCODE F_Btree::removeEntry(
	const FLMBYTE **		ppucKey,
	FLMUINT *				puiKeyLen,
	FLMUINT *				puiChildBlkAddr,
	FLMUINT *				puiCounts,
	FLMBOOL *				pbMoreToRemove,
	F_ELM_UPD_ACTION *	peAction)
{
	RCODE				rc = NE_XFLM_OK;
	FLMBOOL			bLastEntry = FALSE;
	FLMBYTE *		pucEntry;
	FLMBOOL			bMergedWithPrev = FALSE;
	FLMBOOL			bMergedWithNext = FALSE;

	if( m_pStack->uiLevel == 0)
	{
		// We are only safe to do this when we are working on level 0
		// (leaf level) of the Btree.
		
		*pbMoreToRemove = FALSE;
	}

	// Check the current entry to see if it spans more than a single block.

	pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr,
							  m_pStack->uiCurOffset);

	// We only need to worry about data spanning more than one block if it is
	// at level zero (i.e. leaf block) and the lastElement flag is not set.

	if( (m_pStack->uiLevel == 0) && m_bData && !bteLastElementFlag( pucEntry))
	{
		*pbMoreToRemove = TRUE;
	}

	// Find out if we are looking at the last entry in the block.

	if( m_pStack->uiCurOffset == (FLMUINT)(m_pStack->pBlkHdr->ui16NumKeys - 1))
	{
		bLastEntry = TRUE;
	}

	// Now we remove the entry... Will also remove any chained Data Only blocks

	if( RC_BAD( rc = remove( TRUE)))
	{
		goto Exit;
	}

	// If the block is now empty, we will free the block.

	if( !m_pStack->pBlkHdr->ui16NumKeys)
	{
		FLMBOOL			bIsRoot;

		// Test for root block.

		bIsRoot = isRootBlk( m_pStack->pBlkHdr);

		if( RC_BAD( rc = deleteEmptyBlock()))
		{
			goto Exit;
		}

		// Need to remove the parent entry referencing the deleted block.

		if( !bIsRoot)
		{
			*peAction = ELM_REMOVE;
			m_pStack++;
		}
		else
		{
			// If we ever get here, it means we have just deleted the root block.
			// I have put in the possibility, but typically, deleting the Btree
			// is done by calling btDeleteTree.

			*peAction = ELM_DONE;
		}
	}
	else
	{
		if( ((((F_BLK_HDR *)m_pStack->pBlkHdr)->ui16BlkBytesAvail * 100) /
													m_uiBlockSize) >= BT_LOW_WATER_MARK)
		{
			// We will need to check to see if we can merge two blocks into one to
			// conserve space.

			if( RC_BAD( rc = mergeBlocks( bLastEntry, &bMergedWithPrev,
				&bMergedWithNext, peAction)))
			{
				goto Exit;
			}
		}

		// If the entry that we just removed was the last entry in the block and
		// we did not merge any blocks, we will need to prep for an update to the
		// parent with a new key.

		if( bLastEntry && !bMergedWithPrev && !bMergedWithNext)
		{
			if( m_bCounts)
			{
				*puiCounts = countKeys( (FLMBYTE *)m_pStack->pBlkHdr);
			}

			// Backup to the new "last" entry (remove() does not adjust the offset
			// in the stack).

			flmAssert( m_pStack->uiCurOffset > 0);

			m_pStack->uiCurOffset--;
			pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr,
									  m_pStack->uiCurOffset);

			*puiKeyLen = getEntryKeyLength( pucEntry,
								m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType, ppucKey);

			*puiChildBlkAddr = m_pStack->ui32BlkAddr;
			*peAction = ELM_REPLACE;
			m_pStack++;
		}
		else
		{
			// Are we tracking counts?

			if( !bMergedWithPrev && !bMergedWithNext)
			{
				if( m_bCounts)
				{
					if( RC_BAD( rc = updateCounts()))
					{
						goto Exit;
					}
				}
				
				*peAction = ELM_DONE;
			}
		}
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Method to replace an existing entry with a new one.
****************************************************************************/
RCODE F_Btree::replaceEntry(
	const FLMBYTE **		ppucKey,
	FLMUINT *				puiKeyLen,
	const FLMBYTE *		pucValue,
	FLMUINT					uiLen,
	FLMUINT					uiFlags,
	FLMUINT *				puiChildBlkAddr,
	FLMUINT *				puiCounts,
	const FLMBYTE **		ppucRemainingValue,
	FLMUINT *				puiRemainingLen,
	F_ELM_UPD_ACTION *	peAction,
	FLMBOOL					bTruncate)
{
	RCODE					rc = NE_XFLM_OK;
	const FLMBYTE *	pucDataValue = pucValue;
	FLMUINT				uiDataLen = uiLen;
	FLMUINT				uiOADataLen = 0;
	FLMBYTE *			pucEntry = NULL;
	FLMUINT32			ui32OrigDOAddr = 0;
	const FLMBYTE *	pucData = NULL;

	if( m_pStack->uiLevel == 0)
	{
		*ppucRemainingValue = NULL;
		*puiRemainingLen = 0;
	}

	if( *peAction == ELM_REPLACE_DO)
	{
		// Adjust the data entry sizes as the data passed in
		// is the OA Data Length.
		
		uiOADataLen = uiLen;
		uiDataLen = 4;
	}

	if( m_pStack->uiLevel == 0 && m_bData)
	{
		if( m_bOrigInDOBlocks)
		{
			flmAssert( bTruncate);

			pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr,
									  m_pStack->uiCurOffset);

			btGetEntryDataLength( pucEntry, &pucData, NULL, NULL);
			ui32OrigDOAddr = bteGetBlkAddr( pucData);
		}
	}

	// We only have to worry about updating the upper levels of the Btree
	// when we are doing a replacement at a non-leaf level or we are maintaining
	// counts.  Replacements at the leaf level do not require a change in the
	// parent block.  The only exception is when the old entry spanned to
	// another block, but the new one did not.  This results in removing the
	// excess part of the old entry unless we are not truncating the element.
	// Even then, we only update the parent if the excess entry was the only key
	// in the block, i.e. the block became empty as a result of the removal.
	// All of this would have been handled already by the time we return from
	// this call.

	// When bTruncate is FALSE we do not trim back the entry so we don't worry
	// about updating the parentage.
	
	if( RC_BAD( rc = replaceOldEntry( ppucKey, puiKeyLen, pucDataValue,
		uiDataLen, uiFlags, uiOADataLen, puiChildBlkAddr, puiCounts,
		ppucRemainingValue, puiRemainingLen, peAction, bTruncate)))
	{
		goto Exit;
	}

	// Do we need to free the original DO blocks since they are not
	// used in the new entry?

	if( m_bOrigInDOBlocks && !m_bDataOnlyBlock && m_pStack->uiLevel == 0)
	{
		if( RC_BAD( rc = removeDOBlocks( ui32OrigDOAddr)))
		{
			goto Exit;
		}
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Method to handle replacing a single entry in a block.
		ASSUMPTION:  The find method has already been called to locate the
		insertion point, so the stack has already been setup.
****************************************************************************/
RCODE F_Btree::replaceOldEntry(
	const FLMBYTE **		ppucKey,
	FLMUINT *				puiKeyLen,
	const FLMBYTE *		pucValue,
	FLMUINT					uiLen,
	FLMUINT					uiFlags,
	FLMUINT					uiOADataLen,
	FLMUINT *				puiChildBlkAddr,
	FLMUINT *				puiCounts,
	const FLMBYTE **		ppucRemainingValue,
	FLMUINT *				puiRemainingLen,
	F_ELM_UPD_ACTION *	peAction,
	FLMBOOL					bTruncate)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT				uiOldEntrySize;
	FLMBYTE *			pucEntry = NULL;
	FLMBYTE *			pucData = NULL;
	FLMUINT				uiEntrySize;
	FLMBOOL				bLastEntry = FALSE;
	FLMBOOL				bLastElement = TRUE;
	FLMBOOL				bHaveRoom;
	FLMBOOL				bDefragBlk;
	FLMUINT				uiDataLen = 0;
	FLMUINT				uiOldOADataLen = 0;
	FLMBOOL				bRemoveOADataAllowance = FALSE;

	uiOldEntrySize = actualEntrySize(
								getEntrySize( (FLMBYTE *)m_pStack->pBlkHdr,
												  m_pStack->uiCurOffset, &pucEntry));

	if( m_pStack->uiLevel == 0 && m_bData)
	{
		bLastElement = bteLastElementFlag( pucEntry);

		uiDataLen = btGetEntryDataLength( pucEntry, (const FLMBYTE **)&pucData, 
			&uiOldOADataLen, NULL);

		// Test to see if we need to worry about the bTruncate flag.

		if( uiDataLen == uiOldOADataLen)
		{
			if( uiLen > uiDataLen)
			{
				bTruncate = TRUE;
			}
			else if( uiLen <= uiDataLen && uiOADataLen == 0)
			{
				bRemoveOADataAllowance = TRUE;
			}
		}
		else
		{
			if( uiLen > uiOldOADataLen)
			{
				bTruncate = TRUE;
			}
		}
	}

	// bTruncate has no meaning if we have no data or we are not at the
	// leaf level.

	if( m_pStack->uiLevel != 0 || !m_bData)
	{
		bTruncate = TRUE;
	}

	// The calcNewEntrySize function will tack on 2 bytes for the offset.
	// It also adds an extra 4 bytes for the OADataLen, even though it may
	// not be needed.  We will need to be aware of this here as it may affect
	// our decision as to how we will replace the entry.

	if( RC_BAD( rc = calcNewEntrySize( *puiKeyLen, uiLen, &uiEntrySize,
			&bHaveRoom, &bDefragBlk)))
	{
		goto Exit;
	}

	if( bRemoveOADataAllowance)
	{
		uiEntrySize -= 4;
	}

	// Since this is a replace operation, we don't need to know about the offset
	// as that won't be a factor in what we are doing. 'actualEntrySize' will
	// remove those two bytyes from the size.

	uiEntrySize = actualEntrySize( uiEntrySize);
	if( uiEntrySize <= uiOldEntrySize)
	{
		if( !bTruncate)
		{
			flmAssert( uiLen <= uiDataLen);
			f_memcpy( pucData, pucValue, uiLen);

			if( m_pStack->uiCurOffset == 
					(FLMUINT)(m_pStack->pBlkHdr->ui16NumKeys - 1))
			{
				bLastEntry = TRUE;
			}
		}
		else
		{
			// We can go ahead and replace this entry as it is.  All checking
			// has been made before getting to this point.

			if( RC_BAD( rc = buildAndStoreEntry( 
				m_pStack->pSCache->m_pBlkHdr->ui8BlkType,
				uiFlags, *ppucKey, *puiKeyLen, pucValue, uiLen, uiOADataLen,
				*puiChildBlkAddr, *puiCounts, m_pucTempBlk, m_uiBlockSize,
				&uiEntrySize)))
			{
				goto Exit;
			}

			if( RC_BAD( rc = replace( m_pucTempBlk, uiEntrySize, &bLastEntry)))
			{
				goto Exit;
			}
		}

		if( !bLastElement && bTruncate)
		{
			// The element that we replaced actually spans more than one entry.
			// We will have to remove the remaining entries.

			if( RC_BAD( rc = removeRemainingEntries( *ppucKey, *puiKeyLen)))
			{
				goto Exit;
			}
		}

		if( (bLastEntry || m_bCounts) && !isRootBlk( m_pStack->pBlkHdr) &&
			  (m_pStack->uiLevel != 0))
		{
			// Are we in here because of the counts only?  If so, then make
			// sure we don't change the key in the parent.

			if( !bLastEntry)
			{
				if( RC_BAD( rc = updateCounts()))
				{
					goto Exit;
				}
				
				*peAction = ELM_DONE;
			}
			else
			{
				// Return the key to the last entry in the block.

				pucEntry = BtLastEntry( (FLMBYTE *)m_pStack->pBlkHdr);

				*puiKeyLen = getEntryKeyLength( pucEntry,
									m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType, ppucKey);
				*puiChildBlkAddr = m_pStack->ui32BlkAddr;

				// Do we need counts for the next level?

				if( m_bCounts)
				{
					*puiCounts = countKeys( (FLMBYTE *)m_pStack->pBlkHdr);
				}
				
				m_pStack++;
				*peAction = ELM_REPLACE;
			}
		}
		else
		{
			*peAction = ELM_DONE;
		}
		
		goto Exit;
	}

	// If we do not have a stack setup yet (which can happen if the replace
	// is trying to shortcut to the previously known block address and offset),
	// then at this point, we must build the stack, since it may be required
	// to adjust the upper levels of the btree.

	if( !m_bStackSetup)
	{
		if( RC_BAD( rc = findEntry( *ppucKey, *puiKeyLen, XFLM_EXACT)))
		{
			goto Exit;
		}
	}

	// The new entry will not fit into the original entry's space.
	// If we remove the entry in the block, will there be enough room
	// to put it in?

	if( bTruncate &&
		 m_pStack->pSCache->m_pBlkHdr->ui16BlkBytesAvail +
		 uiOldEntrySize >= uiEntrySize)
	{
		// First remove the current entry.  Do not delete any DO blocks chained
		// to this entry.

		if( RC_BAD( rc = remove( FALSE)))
		{
			goto Exit;
		}

		if( (m_pStack->pBlkHdr->stdBlkHdr.ui16BlkBytesAvail !=
			 m_pStack->pBlkHdr->ui16HeapSize) &&
			 ((uiEntrySize + 2) > m_pStack->pBlkHdr->ui16HeapSize))
		{
			if( RC_BAD( rc = defragmentBlock( &m_pStack->pSCache)))
			{
				goto Exit;
			}
		}

		// Now insert the new entry.

		if( RC_BAD( rc = storeEntry( *ppucKey, *puiKeyLen, pucValue, uiLen,
				uiFlags, uiOADataLen, *puiChildBlkAddr, *puiCounts, uiEntrySize,
				&bLastEntry)))
		{
			goto Exit;
		}

		// Check if the original element spanned more than one entry

		if( !bLastElement)
		{
			// The element that we replaced actually spans more than one entry.
			// We will have to remove the remaining entries.

			if( RC_BAD( rc = removeRemainingEntries( *ppucKey, *puiKeyLen)))
			{
				goto Exit;
			}
		}

		if( (bLastEntry || m_bCounts) && !isRootBlk( m_pStack->pBlkHdr) &&
			 (m_pStack->uiLevel != 0))
		{
			// Are we in here because of the counts only?

			if( !bLastEntry)
			{
				if( RC_BAD( rc = updateCounts()))
				{
					goto Exit;
				}
				
				*peAction = ELM_DONE;
			}
			else
			{
				// Set the key to the last entry in the block.

				pucEntry = BtLastEntry( (FLMBYTE *)m_pStack->pBlkHdr);

				*puiKeyLen = getEntryKeyLength( pucEntry,
									m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType, ppucKey);
				*puiChildBlkAddr = m_pStack->ui32BlkAddr;

				// Do we need counts for the next level?

				if( m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType == BT_NON_LEAF_COUNTS)
				{
					*puiCounts = countKeys( (FLMBYTE *)m_pStack->pBlkHdr);
				}
				
				m_pStack++;
				*peAction = ELM_REPLACE;
			}
		}
		else
		{
			*peAction = ELM_DONE;
		}
		
		goto Exit;
	}

	// If the original element does not span multiple entries and we still don't
	// have room for the replacement, then we will remove this entry and insert
	// the replacement.  When the insert happens, it will take care of moving
	// things around or splitting the block as needed to get it in.  If bTruncate
	// is FALSE, and the new entry is larger than the original, we can ignore it.

	if( bLastElement)
	{
		if( RC_BAD( rc = replaceByInsert( ppucKey, puiKeyLen,
			pucValue, uiLen, uiOADataLen, uiFlags, puiChildBlkAddr,
			puiCounts, ppucRemainingValue, puiRemainingLen,
			peAction)))
		{
			goto Exit;
		}

		goto Exit;
	}

	if( bTruncate)
	{
		if( RC_BAD( rc = replaceMultiples( ppucKey, puiKeyLen, pucValue,
			uiLen, uiFlags, puiChildBlkAddr, puiCounts, ppucRemainingValue,
			puiRemainingLen, peAction)))
		{
			goto Exit;
		}
	}
	else
	{
		if( RC_BAD( rc = replaceMultiNoTruncate( ppucKey, puiKeyLen,
			pucValue, uiLen, uiFlags, puiChildBlkAddr, puiCounts,
			ppucRemainingValue, puiRemainingLen, peAction)))
		{
			goto Exit;
		}
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	This method is called whenever a replacement entry will not fit in
		the block, even if we remove the existing entry.  It ASSUMES that the
		original element does not continue to another entry, either in the
		same block or in another block.
****************************************************************************/
RCODE F_Btree::replaceByInsert(
	const FLMBYTE **		ppucKey,
	FLMUINT *				puiKeyLen,
	const FLMBYTE *		pucDataValue,
	FLMUINT					uiDataLen,
	FLMUINT					uiOADataLen,
	FLMUINT					uiFlags,
	FLMUINT *				puiChildBlkAddr,
	FLMUINT *				puiCounts,
	const FLMBYTE **		ppucRemainingValue,
	FLMUINT *				puiRemainingLen,
	F_ELM_UPD_ACTION *	peAction)
{
	RCODE				rc = NE_XFLM_OK;
	FLMUINT			uiLen = uiDataLen;

	if( *peAction == ELM_REPLACE_DO)
	{
		uiLen = uiOADataLen;
		*peAction = ELM_INSERT_DO;
	}
	else
	{
		*peAction = ELM_INSERT;
	}

	// At this point, it is clear that this new entry is larger than the
	// old entry.  We will remove the old entry first.  Then we can treat
	// this whole operation as an insert rather than as a replace.

	if( RC_BAD( rc = remove( FALSE)))
	{
		goto Exit;
	}

	if( RC_BAD( rc = insertEntry( ppucKey, puiKeyLen, pucDataValue, uiLen,
		uiFlags, puiChildBlkAddr, puiCounts, ppucRemainingValue, puiRemainingLen,
		peAction)))
	{
		goto Exit;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Method to replace an entry in a block and update the available
		space.  This method expects to receive a buffer with an entry already
		prepared to be written to the block.
****************************************************************************/
RCODE F_Btree::replace(
	FLMBYTE *		pucEntry,
	FLMUINT			uiEntrySize,
	FLMBOOL *		pbLastEntry)
{
	RCODE						rc = NE_XFLM_OK;
	FLMBYTE *				pucReplaceAt;
	FLMUINT					uiNumKeys;
	FLMBYTE *				pBlk;
	FLMUINT					uiOldEntrySize;

	*pbLastEntry = FALSE;

	// Log this block before making any changes to it.  Since the
	// pSCache could change, we must update the block header after the call.

	if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &m_pStack->pSCache)))
	{
		goto Exit;
	}
	
	m_pStack->pBlkHdr = (F_BTREE_BLK_HDR *)m_pStack->pSCache->m_pBlkHdr;
	pBlk = (FLMBYTE *)m_pStack->pBlkHdr;
	m_pStack->pui16OffsetArray = BtOffsetArray( pBlk, 0);

	uiNumKeys = getBlkEntryCount( pBlk);

	uiOldEntrySize = actualEntrySize( getEntrySize( 
														pBlk, m_pStack->uiCurOffset));

	flmAssert( uiOldEntrySize >= uiEntrySize);

	pucReplaceAt = BtEntry( pBlk, m_pStack->uiCurOffset);

	// Let's go ahead and copy the entry into the block now.

	f_memcpy( pucReplaceAt, pucEntry, uiEntrySize);

#ifdef FLM_DEBUG
	// Clean up the empty space (if any)

	if( uiOldEntrySize > uiEntrySize)
	{
		pucReplaceAt += uiEntrySize;
		f_memset( pucReplaceAt, 0, uiOldEntrySize - uiEntrySize);
	}
#endif

	// Update the available space.  It may not have changed at all if the
	// two entries are the same size. The Heap size will not have changed.
	// This is because we write the entry into the same location as the
	// original.  Even though the new entry may be smaller, we start at
	// the same location, possibly leaving a hole in the block.

	m_pStack->pBlkHdr->stdBlkHdr.ui16BlkBytesAvail +=
									(FLMUINT16)(uiOldEntrySize - uiEntrySize);


	if( m_pStack->uiCurOffset == (FLMUINT)(m_pStack->pBlkHdr->ui16NumKeys - 1))
	{
		*pbLastEntry = TRUE;
	}

	// Preserve the block and offset index in case it is wanted on the way out.

	if( !m_pStack->uiLevel && bteFirstElementFlag( pucEntry))
	{
		m_ui32PrimaryBlkAddr = m_pStack->ui32BlkAddr;
		m_uiCurOffset = m_pStack->uiCurOffset;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Method to rebuild the stack so that it references the parentage of
		the parameter pSCache block.  The assumption is that we will begin at
		whatever level m_pStack is currently sitting at. Therefore, this
		method can be called for any level in the Btree.
****************************************************************************/
RCODE F_Btree::moveStackToPrev(
	F_CachedBlock *	pSCache)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT				uiBlkAddr;
	F_BTREE_BLK_HDR *	pBlkHdr;
	F_BTSK_p				pStack = m_pStack;
	F_CachedBlock *	pPrevSCache = NULL;

	if( pSCache)
	{
		if( pStack->pSCache)
		{
			// Make sure the block we passed in really is the previous
			// block in the chain.

			if( pSCache->m_uiBlkAddress !=
					pStack->pSCache->m_pBlkHdr->ui32PrevBlkInChain)
			{
				rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
				goto Exit;
			}

			// Cannot be the same block.

			if( pSCache == pStack->pSCache)
			{
				rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
				goto Exit;
			}

			// Release the current block.  We don't need to fetch
			// the new block because it was passed in to us.  If
			// we encounter this situation further up the tree,
			// we will have to fetch the block as well.

			ScaReleaseCache( pStack->pSCache, FALSE);
		}

		pStack->pSCache = pSCache;
		pBlkHdr = (F_BTREE_BLK_HDR *)pSCache->m_pBlkHdr;
		pStack->pBlkHdr = pBlkHdr;
		pStack->ui32BlkAddr = ((F_BLK_HDR *)pBlkHdr)->ui32BlkAddr;
		pStack->uiCurOffset = pBlkHdr->ui16NumKeys - 1;  // Last entry
		pStack->uiLevel =	 pBlkHdr->ui8BlkLevel;
		pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pBlkHdr, 0);

		// Now walk up the stack until done.

		pStack++;
	}

	for (;;)
	{
		// If we don't have this block in the stack, we must first get it.

		if( pStack->pSCache == NULL)
		{
			// Don't continue if we don't have this level in the stack.

			if( pStack->ui32BlkAddr == 0)
			{
				break;
			}

			if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
				pStack->ui32BlkAddr, NULL, &pStack->pSCache)))
			{
				goto Exit;
			}
			
			pStack->pBlkHdr = (F_BTREE_BLK_HDR *)pStack->pSCache->m_pBlkHdr;
		}

		// See if we need to go to the previous block.

		if( pStack->uiCurOffset == 0)
		{
			// If this is the root block and we are looking at the first
			// entry in the block, then we have a problem.

			if( !isRootBlk( pStack->pBlkHdr))
			{
				// When the stack is pointing to the first entry, this
				// means that we want the target stack to point to the previous
				// block in the chain.

				uiBlkAddr = pStack->pBlkHdr->stdBlkHdr.ui32PrevBlkInChain;
				flmAssert( uiBlkAddr);

				// Fetch the new block

				if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
					uiBlkAddr, NULL, &pPrevSCache)))
				{
					goto Exit;
				}

				// Release the old block

				ScaReleaseCache( pStack->pSCache, FALSE);

				pBlkHdr = (F_BTREE_BLK_HDR *)pPrevSCache->m_pBlkHdr;
				pStack->pSCache = pPrevSCache;
				pPrevSCache = NULL;
				pStack->pBlkHdr = pBlkHdr;
				pStack->ui32BlkAddr = ((F_BLK_HDR *)pBlkHdr)->ui32BlkAddr;
				pStack->uiCurOffset = pBlkHdr->ui16NumKeys - 1;  // Last Entry
				pStack->uiLevel = pBlkHdr->ui8BlkLevel;
				pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pBlkHdr, 0);
			}
			else
			{
				// We have no previous.

				rc = RC_SET( NE_XFLM_BOF_HIT);
				goto Exit;
			}
		}
		else
		{
			pStack->uiCurOffset--;  // Previous Entry
			break;
		}
		
		pStack++;
	}

Exit:

	if( pPrevSCache)
	{
		ScaReleaseCache( pPrevSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc:	Method to rebuild the stack so that it references the parentage of
		the parameter pSCache block. The assumption is that we will begin at
		whatever level m_pStack is currently sitting at.  Therefore, this 
		method can be called for any level in the Btree.
****************************************************************************/
RCODE F_Btree::moveStackToNext(
	F_CachedBlock *	pSCache,
	FLMBOOL				bReleaseCurrent)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT				uiBlkAddr;
	F_BTREE_BLK_HDR *	pBlkHdr;
	F_BTSK_p				pStack = m_pStack;
	F_CachedBlock *	pNextSCache = NULL;

	if( pSCache)
	{
		if( pStack->pSCache)
		{
			// Make sure the block we passed in really is the next in chain.

			if( pSCache->m_uiBlkAddress !=
					pStack->pSCache->m_pBlkHdr->ui32NextBlkInChain)
			{
				rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
				goto Exit;
			}

			// Cannot be the same block.

			if( pSCache == pStack->pSCache)
			{
				rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
				goto Exit;
			}

			// Release the current block.  We don't need to fetch
			// the new block because it was passed in to us.  If
			// we encounter this situation further up the tree,
			// we will have to fetch the block as well.

			if( bReleaseCurrent)
			{
				ScaReleaseCache( pStack->pSCache, FALSE);
			}
		}

		pStack->pSCache = pSCache;
		pBlkHdr = (F_BTREE_BLK_HDR *)pSCache->m_pBlkHdr;
		pStack->pBlkHdr = pBlkHdr;
		pStack->ui32BlkAddr = ((F_BLK_HDR *)pBlkHdr)->ui32BlkAddr;
		pStack->uiCurOffset = 0;  // First entry
		pStack->uiLevel = pBlkHdr->ui8BlkLevel;
		pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pBlkHdr, 0);

		// Now walk up the stack until done.

		pStack++;
	}

	for (;;)
	{
		// If we don't currently have this block, let's get it.

		if( pStack->pSCache == NULL)
		{
			if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
				pStack->ui32BlkAddr, NULL, &pStack->pSCache)))
			{
				goto Exit;
			}
			
			pStack->pBlkHdr = (F_BTREE_BLK_HDR *)pStack->pSCache->m_pBlkHdr;
		}

		// See if we need to go to the next block.

		if( pStack->uiCurOffset == (FLMUINT)(pStack->pBlkHdr->ui16NumKeys - 1))
		{
			// If this is the root block and we are looking at the last entry in the
			// block, then we have a problem.

			if( !isRootBlk( pStack->pBlkHdr))
			{
				// When the stack is pointing to the last entry, this
				// means that we want the target stack to point the next block in
				// the chain.

				uiBlkAddr = pStack->pBlkHdr->stdBlkHdr.ui32NextBlkInChain;
				flmAssert( uiBlkAddr);

				// Get the next block

				if( RC_BAD( rc = getNextBlock( &pStack->pSCache)))
				{
					goto Exit;
				}

				pBlkHdr = (F_BTREE_BLK_HDR *)pStack->pSCache->m_pBlkHdr;
				pStack->pBlkHdr = pBlkHdr;
				pStack->ui32BlkAddr = ((F_BLK_HDR *)pBlkHdr)->ui32BlkAddr;
				pStack->uiCurOffset = 0;   // First Entry
				pStack->uiLevel = pBlkHdr->ui8BlkLevel;
				pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pBlkHdr, 0);
			}
			else
			{
				// We should never have to attempt to get a previous block
				// on the root.

				rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
				goto Exit;
			}
		}
		else
		{
			pStack->uiCurOffset++;  // Next Entry
			break;
		}
		
		pStack++;
	}

Exit:

	if( pNextSCache)
	{
		ScaReleaseCache( pNextSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc:	Method to calculate the actual entry size of a new entry
****************************************************************************/
RCODE F_Btree::calcNewEntrySize(
	FLMUINT			uiKeyLen,
	FLMUINT			uiDataLen,
	FLMUINT *		puiEntrySize,
	FLMBOOL *		pbHaveRoom,
	FLMBOOL *		pbDefragBlk)
{
	RCODE				rc = NE_XFLM_OK;

	// Calculate the entry size.

	switch( m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType)
	{
		case BT_LEAF:
		{
			// This block type is a leaf block, No Data
			
			*puiEntrySize = BTE_LEAF_OVHD + uiKeyLen;
			break;
		}

		case BT_LEAF_DATA:
		{
			// Leaf block with data
			
			*puiEntrySize = BTE_LEAF_DATA_OVHD +
								 (uiKeyLen > ONE_BYTE_SIZE ? 2 : 1) +
								 (uiDataLen > ONE_BYTE_SIZE ? 2 : 1) +
								 uiKeyLen + uiDataLen;
			break;
		}

		case BT_NON_LEAF:
		{
			*puiEntrySize = BTE_NON_LEAF_OVHD + uiKeyLen;
			break;
		}

		case BT_NON_LEAF_COUNTS:
		{
			*puiEntrySize = BTE_NON_LEAF_COUNTS_OVHD + uiKeyLen;
			break;
		}

		default:
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
			*puiEntrySize = 0;
			goto Exit;
		}
	}

	// See if we have room in the heap first.  If not, maybe we can make
	// room by defraging the block.

	if( *puiEntrySize <= m_pStack->pBlkHdr->ui16HeapSize)
	{
		*pbDefragBlk = FALSE;
		*pbHaveRoom = TRUE;
	}
	else if( *puiEntrySize <= m_pStack->pBlkHdr->stdBlkHdr.ui16BlkBytesAvail)
	{
		// A defrag of the block is required to make room.  We will only defrag
		// if we can recover a minimum of 5% of the total block size.

		if( m_pStack->pBlkHdr->stdBlkHdr.ui16BlkBytesAvail >= m_uiDefragThreshold)
		{
			*pbHaveRoom = TRUE;
			*pbDefragBlk = TRUE;
		}
		else
		{
			*pbHaveRoom = FALSE;
			*pbDefragBlk = FALSE;
		}
	}
	else
	{
		*pbHaveRoom = FALSE;
		*pbDefragBlk = FALSE;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Function to save the replacement information that we could not store
		on the current go round.  The replace function will check for the
		presence of this structure and deal with it later.
****************************************************************************/
RCODE F_Btree::saveReplaceInfo(
	const FLMBYTE *	pucNewKey,
	FLMUINT				uiNewKeyLen)
{
	RCODE								rc = NE_XFLM_OK;
	BTREE_REPLACE_STRUCT *		pPrev;
	F_BTSK_p							pStack = m_pStack;
	const FLMBYTE *				pucParentKey;
	FLMBYTE *						pucEntry;

	if( m_uiReplaceLevels + 1 >= BH_MAX_LEVELS)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
		goto Exit;
	}

	pPrev = m_pReplaceInfo;
	m_pReplaceInfo = &m_pReplaceStruct[ m_uiReplaceLevels++];
	m_pReplaceInfo->pPrev = (void *)pPrev;

	// We should not be at the root level already!

	flmAssert( pStack->uiLevel != m_uiStackLevels - 1);

	m_pReplaceInfo->uiParentLevel = pStack->uiLevel+1;
	m_pReplaceInfo->uiNewKeyLen = uiNewKeyLen;
	m_pReplaceInfo->uiChildBlkAddr = pStack->ui32BlkAddr;
	
	if( m_bCounts)
	{
		m_pReplaceInfo->uiCounts = countKeys( (FLMBYTE *)pStack->pBlkHdr);
	}
	else
	{
		m_pReplaceInfo->uiCounts = 0;
	}

	f_memcpy( &m_pReplaceInfo->pucNewKey[0], pucNewKey, uiNewKeyLen);

	pStack++;
	pucEntry = BtEntry( (FLMBYTE *)pStack->pBlkHdr, pStack->uiCurOffset);

	m_pReplaceInfo->uiParentKeyLen = getEntryKeyLength( pucEntry,
						pStack->pBlkHdr->stdBlkHdr.ui8BlkType, &pucParentKey);

	f_memcpy( &m_pReplaceInfo->pucParentKey[0], pucParentKey, 
				 m_pReplaceInfo->uiParentKeyLen);

	m_pReplaceInfo->uiParentChildBlkAddr = bteGetBlkAddr( pucEntry);

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Method to restore the stack to a state where we  can finish updating
		the parent with the new key information.
****************************************************************************/
RCODE F_Btree::restoreReplaceInfo(
	const FLMBYTE **	ppucKey,
	FLMUINT *			puiKeyLen,
	FLMUINT *			puiChildBlkAddr,
	FLMUINT *			puiCounts)
{
	RCODE					rc = NE_XFLM_OK;
	RCODE					rcTmp = NE_XFLM_OK;
	FLMUINT				uiLoop;
	FLMBYTE *			pucEntry;
	FLMUINT				uiKeyLen;
	const FLMBYTE *	pucKey;
	FLMUINT				uiSearchLevel = m_uiSearchLevel;
	FLMUINT				uiStackLevels = m_uiStackLevels;

	// We will need to redo our stack from the top down to
	// make sure we are looking at the correct blocks.

	m_uiSearchLevel = m_uiStackLevels - m_pReplaceInfo->uiParentLevel - 1;
	rcTmp = findEntry( m_pReplaceInfo->pucParentKey,
							 m_pReplaceInfo->uiParentKeyLen, XFLM_EXACT);

	m_uiSearchLevel = uiSearchLevel;

	if ((rcTmp != NE_XFLM_OK) &&
		 (rcTmp != NE_XFLM_NOT_FOUND) &&
		 (rcTmp != NE_XFLM_EOF_HIT))
	{
		rc = RC_SET( rcTmp);
		goto Exit;
	}

	// Set the stack pointer to the parent level that we want to replace.

	m_pStack = &m_Stack[ m_pReplaceInfo->uiParentLevel];

	// There is always the possibility that the key we are searching for
	// has a duplicate key ahead of it, as a result of a continuation element.
	// We really must replace the entry we were looking at when the information
	// was stored, therefore, we will verify that we have the right entry.

	for( ;;)
	{
		pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr, m_pStack->uiCurOffset);

		uiKeyLen = getEntryKeyLength( pucEntry, 
							m_pStack->pBlkHdr->stdBlkHdr.ui8BlkType, &pucKey);

		if( uiKeyLen != m_pReplaceInfo->uiParentKeyLen)
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
			goto Exit;
		}

		if( f_memcmp( &m_pReplaceInfo->pucParentKey[0], pucKey, uiKeyLen) == 0)
		{
			if( bteGetBlkAddr( pucEntry) != m_pReplaceInfo->uiParentChildBlkAddr)
			{
				// Try moving forward to the next entry ...
				
				if( RC_BAD( rc = moveStackToNext( NULL)))
				{
					rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
					goto Exit;
				}
			}
			else
			{
				break;
			}
		}
		else
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
			goto Exit;
		}
	}

	// Now return the other important stuff

	*puiChildBlkAddr = m_pReplaceInfo->uiChildBlkAddr;
	*puiKeyLen = m_pReplaceInfo->uiNewKeyLen;
	*puiCounts = m_pReplaceInfo->uiCounts;

	for( uiLoop = 0; uiLoop < m_uiStackLevels; uiLoop++)
	{
		m_Stack[ uiLoop].uiKeyLen = m_pReplaceInfo->uiNewKeyLen;
	}

	m_uiStackLevels = uiStackLevels;

	// Point to the key

	*ppucKey = &m_pReplaceInfo->pucNewKey[ 0];

	// Free the current ReplaceInfo Buffer

	m_pReplaceInfo = (BTREE_REPLACE_STRUCT *)m_pReplaceInfo->pPrev;
	m_uiReplaceLevels--;

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Method to set the key to be returned to the caller.
****************************************************************************/
FINLINE RCODE F_Btree::setReturnKey(
	FLMBYTE *		pucEntry,
	FLMUINT			uiBlockType,
	FLMBYTE *		pucKey,
	FLMUINT *		puiKeyLen,
	FLMUINT			uiKeyBufSize)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT				uiKeyLen;
	const FLMBYTE *	pucKeyRV;

	uiKeyLen =  getEntryKeyLength( pucEntry, uiBlockType, &pucKeyRV);
	
	if( uiKeyLen == 0)
	{
		// We hit the LEM, hence the EOF error
		
		rc = RC_SET( NE_XFLM_EOF_HIT);
		goto Exit;
	}

	if( uiKeyLen <= uiKeyBufSize)
	{
		f_memcpy( pucKey, pucKeyRV, uiKeyLen);
		*puiKeyLen = uiKeyLen;
	}
	else
	{
		rc = RC_SET( NE_XFLM_CONV_DEST_OVERFLOW);
		goto Exit;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc: Method to return the data from either the BTREE block or
		the DO block.  It will update the tracking variables too.
		This method assumes that the m_pSCache has already been setup for 
		the 1st go-round.
****************************************************************************/
RCODE F_Btree::extractEntryData(
	FLMBYTE *			pucKey,
	FLMUINT				uiKeyLen,
	FLMBYTE *			pucBuffer,
	FLMUINT				uiBufSiz,
	FLMUINT *			puiDataLen)
{
	RCODE				rc = NE_XFLM_OK;
	FLMBYTE *		pucDestPtr = pucBuffer;
	FLMUINT32		ui32BlkAddr = 0;
	FLMBOOL			bNewBlock;
	FLMUINT			uiDataLen = 0;

	flmAssert( m_pSCache);

	if( puiDataLen)
	{
		*puiDataLen = 0;
	}

#ifdef FLM_DEBUG
	if( pucBuffer)
	{
		f_memset( pucBuffer, 0, uiBufSiz);
	}
#endif

	// Is there anything to read?
	
	if( m_uiOADataRemaining == 0)
	{
		rc = RC_SET( NE_XFLM_EOF_HIT);
		goto Exit;
	}

	while( m_uiOADataRemaining && (uiDataLen < uiBufSiz))
	{
		if( m_uiDataRemaining <= (uiBufSiz - uiDataLen))
		{
			// Let's take what we have left in this block first.

			if( pucDestPtr)
			{
				f_memcpy( pucDestPtr, m_pucDataPtr, m_uiDataRemaining);
				pucDestPtr += m_uiDataRemaining;
			}

			uiDataLen += m_uiDataRemaining;
			m_uiOADataRemaining -= m_uiDataRemaining;
			m_uiDataRemaining = 0;
		}
		else
		{
			// Buffer is too small to hold everything in this block.
			
			if( pucDestPtr)
			{
				f_memcpy( pucDestPtr, m_pucDataPtr, uiBufSiz - uiDataLen);
				pucDestPtr += (uiBufSiz - uiDataLen);
			}

			m_pucDataPtr += (uiBufSiz - uiDataLen);
			m_uiOADataRemaining -= (uiBufSiz - uiDataLen);
			m_uiDataRemaining -= (uiBufSiz - uiDataLen);
			uiDataLen += (uiBufSiz - uiDataLen);
		}

		// If there is still more overall data remaining, we need to get the
		// next DO block or standard block and setup to read it too.
		// i.e. More to come, but nothing left in this block.
		
		if( (m_uiOADataRemaining > 0) && (m_uiDataRemaining == 0))
		{
			if (!m_bDataOnlyBlock &&
				 (m_uiCurOffset <
				  (FLMUINT)(((F_BTREE_BLK_HDR *)m_pSCache->m_pBlkHdr)->ui16NumKeys - 1)))
			{
				m_uiCurOffset++;
				bNewBlock = FALSE;
			}
			else
			{
				// Get the next block address
				
				ui32BlkAddr = m_pSCache->m_pBlkHdr->ui32NextBlkInChain;

				// Release the current block before we get the next one.
				
				ScaReleaseCache( m_pSCache, FALSE);
				m_pSCache = NULL;

				if( ui32BlkAddr == 0)
				{
					rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
					goto Exit;
				}

				if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
					ui32BlkAddr, NULL, &m_pSCache)))
				{
					goto Exit;
				}

				updateTransInfo( m_pSCache->m_pBlkHdr->ui64TransID,
									  m_pSCache->m_ui64HighTransID);

				m_ui64LastBlkTransId = m_pSCache->m_pBlkHdr->ui64TransID;
				bNewBlock = TRUE;
			}

			// If this is a data only block, then we can get the local data size
			// from the header.

			if( m_bDataOnlyBlock)
			{
				flmAssert( m_pSCache->m_pBlkHdr->ui8BlkType == BT_DATA_ONLY);

				m_pucDataPtr = (FLMBYTE *)m_pSCache->m_pBlkHdr +
										  sizeofDOBlkHdr( m_pSCache->m_pBlkHdr);
										  
				m_uiDataRemaining = m_uiBlockSize -
										  sizeofDOBlkHdr( m_pSCache->m_pBlkHdr) -
										  m_pSCache->m_pBlkHdr->ui16BlkBytesAvail;
										  
				m_uiDataLength = m_uiDataRemaining;
				m_ui32CurBlkAddr = ui32BlkAddr;
			}
			else
			{
				F_BTREE_BLK_HDR *		pBlkHdr;
				FLMBYTE *				pucEntry;

				// In a BTREE block, we MUST ensure that the first entry is a
				// continuation of the previous entry in the previous block.
				
				pBlkHdr = (F_BTREE_BLK_HDR *)m_pSCache->m_pBlkHdr;

				if( pBlkHdr->ui16NumKeys == 0)
				{
					rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
					goto Exit;
				}

				if( bNewBlock)
				{
					m_uiCurOffset = 0;
				}

				// Point to the first entry ...
				
				pucEntry = BtEntry( (FLMBYTE *)pBlkHdr, m_uiCurOffset);

				if( !checkContinuedEntry( pucKey, uiKeyLen, NULL, pucEntry,
					pBlkHdr->stdBlkHdr.ui8BlkType))
				{
					rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
					goto Exit;
				}

				m_uiDataRemaining = btGetEntryDataLength( pucEntry,
					&m_pucDataPtr, NULL, NULL);
					
				m_uiDataLength = m_uiDataRemaining;

				if( bNewBlock)
				{
					m_ui32CurBlkAddr = ui32BlkAddr;
				}
			}
			
			// Update the offset at the begining of the current entry.
			
			m_uiOffsetAtStart = m_uiOADataLength - m_uiOADataRemaining;
		}
	}

Exit:

	if( puiDataLen)
	{
		*puiDataLen = uiDataLen;
	}

	if( m_pSCache)
	{
		// We must release the SCache block
		
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}

	return( rc);
}

/***************************************************************************
Desc:	Method to prepare the Btree state for reading.  Since several APIs do
		the same thing, this has been put into a private method.
****************************************************************************/
RCODE F_Btree::setupReadState(
	F_BLK_HDR *		pBlkHdr,
	FLMBYTE *		pucEntry)
{
	RCODE					rc = NE_XFLM_OK;
	F_CachedBlock *	pSCache = NULL;
	const FLMBYTE *	pucData;

	// Is there any data?  Check the block type.
	
	if( pBlkHdr->ui8BlkType == BT_LEAF_DATA)
	{
		// How large is the value for this entry?
		
		m_uiDataLength = btGetEntryDataLength( pucEntry, &pucData,
									&m_uiOADataLength, &m_bDataOnlyBlock);

		m_uiPrimaryDataLen = m_uiDataLength;
	}
	else
	{
		m_uiDataLength = 0;
		m_uiOADataLength = 0;
		m_bDataOnlyBlock = FALSE;
	}

  // Represents the offset at the beginning entry in the first block.  This
  // will change as we move through the blocks.
									
	m_uiOffsetAtStart = 0;
	
	// Watch the transaction id and the transaction count during streaming
	// read operations.  If either changes after an initial read, then
	// we abort the operation.
	
	m_ui64CurrTransID = m_pDb->m_ui64CurrTransID;
	m_uiBlkChangeCnt = m_pDb->m_uiBlkChangeCnt;
	m_ui64LastBlkTransId = pBlkHdr->ui64TransID;
	m_ui64PrimaryBlkTransId = pBlkHdr->ui64TransID;

	// Track the overall length progress
  
	m_uiOADataRemaining = m_uiOADataLength;
	
	// Track the local entry progress
	
	m_uiDataRemaining = m_uiDataLength;

	if( m_bDataOnlyBlock)
	{
		m_ui32DOBlkAddr = bteGetBlkAddr( pucData);
		m_ui32CurBlkAddr = m_ui32DOBlkAddr;

		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			m_ui32DOBlkAddr, NULL, &pSCache)))
		{
			goto Exit;
		}
		
		m_ui64LastBlkTransId = pSCache->m_pBlkHdr->ui64TransID;

		// Local amount of data in this block
		
		m_uiDataRemaining = m_uiBlockSize -
										sizeofDOBlkHdr((F_BLK_HDR *)pSCache->m_pBlkHdr) -
										pSCache->m_pBlkHdr->ui16BlkBytesAvail;

		// Keep the actual local data size for later.
		
		m_uiDataLength = m_uiDataRemaining;

		// Adjust for the key at the beginning of the first block.
		
		if( pSCache->m_pBlkHdr->ui32PrevBlkInChain == 0)
		{
			FLMBYTE *	pucPtr = (FLMBYTE *)pSCache->m_pBlkHdr +
										sizeofDOBlkHdr((F_BLK_HDR *)pSCache->m_pBlkHdr);
			FLMUINT16	ui16KeyLen = FB2UW( pucPtr);

			m_uiDataLength -= (ui16KeyLen + 2);
			m_uiDataRemaining -= (ui16KeyLen + 2);
		}

		// Now release the DO Block.  We will get it again when we need it.
		
		ScaReleaseCache( pSCache, FALSE);
		pSCache = NULL;
	}

Exit:

	if( pSCache)
	{
		ScaReleaseCache( pSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc:	Method to remove extra entries after a replace operation.
****************************************************************************/
RCODE F_Btree::removeRemainingEntries(
	const FLMBYTE *	pucKey,
	FLMUINT				uiKeyLen)
{
	RCODE						rc = NE_XFLM_OK;
	F_BTREE_BLK_HDR *		pBlkHdr;
	FLMBOOL					bLastElement = FALSE;
	FLMBYTE *				pucEntry;
	FLMBOOL					bFirst = TRUE;

	// We should never get to this function when in the upper levels.

	flmAssert( m_pStack->uiLevel == 0);

	// If we do not have a stack setup yet (which can happen if the replace
	// is trying to shortcut to the previously known block address and offset),
	// then at this point, we must build the stack, since it may be required
	// to adjust the upper levels of the btree.

	if( !m_bStackSetup)
	{
		if( RC_BAD( rc = findEntry( pucKey, uiKeyLen, XFLM_EXACT)))
		{
			goto Exit;
		}
	}

	while( !bLastElement)
	{
		// Begin each iteration at the leaf level.
		
		m_pStack = &m_Stack[ 0];

		// Advance the stack to the next entry.
		
		if (bFirst ||
				m_pStack->uiCurOffset >= (FLMUINT)(m_pStack->pBlkHdr->ui16NumKeys))
		{
			if( RC_BAD( rc = moveStackToNext( NULL)))
			{
				goto Exit;
			}
		}

		bFirst = FALSE;
		pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr, m_pStack->uiCurOffset);

		if( !checkContinuedEntry( pucKey, uiKeyLen, &bLastElement,
					pucEntry, getBlkType( (FLMBYTE *)m_pStack->pBlkHdr)))
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
			goto Exit;
		}

		// Remove the entry from this block.
		
		if( RC_BAD( rc = remove( FALSE)))
		{
			goto Exit;
		}
		
		pBlkHdr = (F_BTREE_BLK_HDR *)m_pStack->pSCache->m_pBlkHdr;

		// Is the block empty now?  If it is, then we will want to remove this
		// block and remove the entry in the parent that points to this block.
		
		if( pBlkHdr->ui16NumKeys == 0)
		{
			for (;;)
			{
				flmAssert( !isRootBlk( m_pStack->pBlkHdr));

				// Remove this block, then update the parent.
				
				if( RC_BAD( rc = deleteEmptyBlock()))
				{
					goto Exit;
				}

				// Now update the parent blocks
				
				m_pStack++;

				if( RC_BAD( rc = remove( FALSE)))
				{
					goto Exit;
				}

				// Update the counts if keeping counts.
				
				if( m_bCounts && !isRootBlk(pBlkHdr))
				{
					if( RC_BAD( rc = updateCounts()))
					{
						goto Exit;
					}
				}

				if( m_pStack->pBlkHdr->ui16NumKeys > 0)
				{
					break;
				}
			}

			// Rebuild the stack to the beginning after a delete block operation.
			
			if( RC_BAD( findEntry( pucKey, uiKeyLen, XFLM_EXACT)))
			{
				goto Exit;
			}
			
			bFirst = TRUE;
		}
		else
		{
			// Update the counts if keeping counts.
			
			if( m_bCounts)
			{
				if( RC_BAD( rc = updateCounts()))
				{
					goto Exit;
				}
			}
		}
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Method to delete an empty block.  The block that will be deleted is
		the current block pointed to by m_pStack.
****************************************************************************/
RCODE F_Btree::deleteEmptyBlock( void)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT32			ui32PrevBlkAddr;
	FLMUINT32			ui32NextBlkAddr;
	F_CachedBlock *	pSCache = NULL;

	// Get the previous block address so we can back everything up in the stack

	ui32PrevBlkAddr = m_pStack->pBlkHdr->stdBlkHdr.ui32PrevBlkInChain;
	ui32NextBlkAddr = m_pStack->pBlkHdr->stdBlkHdr.ui32NextBlkInChain;

	// Free the block

	rc = m_pDb->m_pDatabase->blockFree(m_pDb, m_pStack->pSCache);
	
	m_pStack->pSCache = NULL;
	m_pStack->pBlkHdr = NULL;
	
	if( RC_BAD( rc))
	{
		goto Exit;
	}

	// Update the previous block.

	if( ui32PrevBlkAddr)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			ui32PrevBlkAddr, NULL, &pSCache)))
		{
			goto Exit;
		}

		if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &pSCache)))
		{
			goto Exit;
		}

		pSCache->m_pBlkHdr->ui32NextBlkInChain = ui32NextBlkAddr;

		ScaReleaseCache( pSCache, FALSE);
		pSCache = NULL;
	}

	// Update the next block

	if( ui32NextBlkAddr)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			ui32NextBlkAddr, NULL, &pSCache)))
		{
			goto Exit;
		}

		if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &pSCache)))
		{
			goto Exit;
		}

		pSCache->m_pBlkHdr->ui32PrevBlkInChain = ui32PrevBlkAddr;

		ScaReleaseCache( pSCache, FALSE);
		pSCache = NULL;
	}

Exit:

	if( pSCache)
	{
		ScaReleaseCache( pSCache, FALSE);
	}
	
	return( rc);
}

/***************************************************************************
Desc:	Method to remove (free) all data only blocks that are linked to the
		data only block whose address is passed in (inclusive).
****************************************************************************/
RCODE F_Btree::removeDOBlocks(
	FLMUINT32		ui32BlkAddr)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT32			ui32NextBlkAddr;
	F_CachedBlock *	pSCache = NULL;

	ui32NextBlkAddr = ui32BlkAddr;

	while( ui32NextBlkAddr)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			ui32NextBlkAddr, NULL, &pSCache)))
		{
			goto Exit;
		}

		flmAssert( getBlkType( (FLMBYTE *)pSCache->m_pBlkHdr) == BT_DATA_ONLY);
		ui32NextBlkAddr = pSCache->m_pBlkHdr->ui32NextBlkInChain;

		rc = m_pDb->m_pDatabase->blockFree( m_pDb, pSCache);
		pSCache = NULL;
		
		if( RC_BAD( rc))
		{
			goto Exit;
		}
	}

Exit:

	if( pSCache)
	{
		ScaReleaseCache( pSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc:	Method used to replace entries where the original spans multiple
		elements and we are NOT to truncate it.  To do this, we will attempt
		to fill each block until we have stored everything.
****************************************************************************/
RCODE F_Btree::replaceMultiples(
	const FLMBYTE **		ppucKey,
	FLMUINT *				puiKeyLen,
	const FLMBYTE *		pucDataValue,
	FLMUINT					uiLen,
	FLMUINT,					//uiFlags,
	FLMUINT *,				//puiChildBlkAddr,
	FLMUINT *,				//puiCounts,
	const FLMBYTE **		ppucRemainingValue,
	FLMUINT *				puiRemainingLen,
	F_ELM_UPD_ACTION *	peAction)
{
	RCODE						rc = NE_XFLM_OK;
	FLMBOOL					bLastElement = FALSE;
	FLMUINT					uiRemainingData = uiLen;
	const FLMBYTE *		pucRemainingValue = pucDataValue;
	FLMBYTE *				pucEntry = NULL;
	FLMBYTE *				pucData;
	FLMUINT					uiDataLength;
	FLMUINT					uiOADataLength = uiLen;
	FLMUINT					uiOldOADataLength;
	FLMUINT					uiAmtCopied;

	// Must be at the leaf level!

	flmAssert( m_pStack->uiLevel == 0);

	while( uiRemainingData)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( 
			m_pDb, &m_pStack->pSCache)))
		{
			goto Exit;
		}

		m_pStack->pBlkHdr = (F_BTREE_BLK_HDR *)m_pStack->pSCache->m_pBlkHdr;
		m_pStack->pui16OffsetArray = BtOffsetArray( 
													(FLMBYTE *)m_pStack->pBlkHdr, 0);

		// Get a pointer to the current entry
		
		pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr, m_pStack->uiCurOffset);

		// Determine the data size for this entry
		
		uiDataLength = btGetEntryDataLength( pucEntry, (const FLMBYTE **)&pucData,
								&uiOldOADataLength, NULL);

		// Now over-write as much of the data as we can
		
		if( uiRemainingData >= uiDataLength)
		{
			f_memcpy( pucData, pucRemainingValue, uiDataLength);

			uiAmtCopied = uiDataLength;
			pucRemainingValue += uiDataLength;
			uiRemainingData -= uiDataLength;
		}
		else
		{
			f_memcpy( pucData, pucRemainingValue, uiRemainingData);
			uiAmtCopied = uiRemainingData;
			pucRemainingValue += uiRemainingData;
			uiRemainingData = 0;
		}

		// Do we need to adjust the data length?
		
		if( uiDataLength > uiAmtCopied)
		{
			FLMBYTE *	pucTmp = pucEntry;
			
			// Skip the flag
			
			pucTmp++;
			
			if( bteKeyLenFlag( pucEntry))
			{
				pucTmp += 2;
			}
			else
			{
				pucTmp++;
			}

			if( bteDataLenFlag( pucEntry))
			{
				UW2FBA( (FLMUINT16)uiAmtCopied, pucTmp);
				pucTmp += 2;
			}
			else
			{
				*pucTmp = (FLMBYTE)uiAmtCopied;
				pucTmp++;
			}

			// We need to adjust the free space in the block too.

			m_pStack->pBlkHdr->stdBlkHdr.ui16BlkBytesAvail +=
									(FLMUINT16)(uiDataLength - uiAmtCopied);


#ifdef FLM_DEBUG
			// Clear the unused portion of the block now.
	
			pucTmp = pucData + uiAmtCopied;
			f_memset( pucTmp, 0, (uiDataLength - uiAmtCopied));
#endif
		}

		// Adjust the OA Data length if needed.  We only need to worry about this
		// on the first element.  No others have it.

		if( bteFirstElementFlag( pucEntry) && uiOADataLength != uiOldOADataLength)
		{
			FLMBYTE *		pucTmp = pucEntry;

			flmAssert( bteOADataLenFlag( pucEntry));

			pucTmp++;
			
			if( bteKeyLenFlag( pucEntry))
			{
				pucTmp += 2;
			}
			else
			{
				pucTmp++;
			}

			if( bteDataLenFlag( pucEntry))
			{
				pucTmp += 2;
			}
			else
			{
				pucTmp++;
			}

			UD2FBA( (FLMUINT32)uiOADataLength, pucTmp);
		}

		// If we just updated the last member of this entry so break out.
		
		if( uiRemainingData == 0)
		{
			break;
		}

		// Was this the last element for this entry?
		
		if( bteLastElementFlag(pucEntry))
		{
			FLMBYTE *	pucTmp = pucEntry;

			// Turn off the lastElement flag on this entry.
			
			*pucTmp &= ~BTE_FLAG_LAST_ELEMENT;

			// No more to replace, the rest is going to be new data.
			
			*ppucRemainingValue = pucRemainingValue;
			*puiRemainingLen = uiRemainingData;
			break;
		}

		// Advance to the next entry, this block or the next...
		// The function expects to find the block in m_pSCache, so
		// let's put it there for now.

		if( RC_BAD( rc = moveStackToNext( NULL)))
		{
			goto Exit;
		}

		pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr,
								  m_pStack->uiCurOffset);

		// Make sure we are still looking at the same key etc.
		
		if( !checkContinuedEntry( *ppucKey, *puiKeyLen, &bLastElement,
			pucEntry, getBlkType( (FLMBYTE *)m_pStack->pBlkHdr)))
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
			goto Exit;
		}
	}

	// Are there any more entries to remove?
	
	if( !bteLastElementFlag( pucEntry) && !uiRemainingData)
	{
		*pucEntry |= BTE_FLAG_LAST_ELEMENT;
		
		if( RC_BAD( rc = removeRemainingEntries( *ppucKey, *puiKeyLen)))
		{
			goto Exit;
		}
	}

	*peAction = ELM_DONE;

Exit:

	// Only release the m_pSCache if the use count is greater than 1.  It is
	// pointed to by the stack also.
	
	if( m_pSCache && m_pSCache->m_uiUseCount > 1)
	{
		ScaReleaseCache( m_pSCache, FALSE);
	}
	
	m_pSCache = NULL;
	return( rc);
}

/***************************************************************************
Desc:	Method used to replace entries where the original spans multiple
		elements and we are not to truncate it.  To do this, we will attempt
		to fill each block until we have stored everything.
****************************************************************************/
RCODE F_Btree::replaceMultiNoTruncate(
	const FLMBYTE **		ppucKey,
	FLMUINT *				puiKeyLen,
	const FLMBYTE *		pucDataValue,
	FLMUINT					uiLen,
	FLMUINT,					//uiFlags,
	FLMUINT *,				//puiChildBlkAddr,
	FLMUINT *,				//puiCounts,
	const FLMBYTE **		ppucRemainingValue,
	FLMUINT *				puiRemainingLen,
	F_ELM_UPD_ACTION *	peAction)
{
	RCODE						rc = NE_XFLM_OK;
	FLMBOOL					bLastElement = FALSE;
	FLMUINT					uiRemainingData = uiLen;
	const FLMBYTE *		pucRemainingValue = pucDataValue;
	FLMBYTE *				pucEntry;
	FLMBYTE *				pucData;
	FLMUINT					uiDataLength;

	// Must be at the leaf level
	
	flmAssert( m_pStack->uiLevel == 0);

	while( uiRemainingData)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( 
			m_pDb, &m_pStack->pSCache)))
		{
			goto Exit;
		}

		m_pStack->pBlkHdr = (F_BTREE_BLK_HDR *)m_pStack->pSCache->m_pBlkHdr;
		m_pStack->pui16OffsetArray = BtOffsetArray( 
												(FLMBYTE *)m_pStack->pBlkHdr, 0);

		// Get a pointer to the current entry
		
		pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr, m_pStack->uiCurOffset);

		// Determine the data size for this entry
		
		uiDataLength = btGetEntryDataLength( pucEntry, 
							(const FLMBYTE **)&pucData, NULL, NULL);

		// Now over-write as much of the data as we can.
		
		if( uiRemainingData > uiDataLength)
		{
			f_memcpy( pucData, pucRemainingValue, uiDataLength);
			pucRemainingValue += uiDataLength;
			uiRemainingData -= uiDataLength;
		}
		else
		{
			f_memcpy( pucData, pucRemainingValue, uiRemainingData);
			pucRemainingValue += uiRemainingData;
			uiRemainingData = 0;
		}

		// We just updated the last member of this entry so break out.
		
		if( uiRemainingData == 0)
		{
			break;
		}

		// Was this the last element for this entry?
		
		if( bteLastElementFlag( pucEntry))
		{
			// No more to replace, the rest is going to be new data.
			
			*ppucRemainingValue = pucRemainingValue;
			*puiRemainingLen = uiRemainingData;
			break;
		}

		// Advance to the next entry, this block or the next...
		// The function expects to find the block in m_pSCache, so
		// let's put it there f or now.

		if( RC_BAD( rc = moveStackToNext( NULL)))
		{
			goto Exit;
		}

		pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr,
								  m_pStack->uiCurOffset);

		// Make sure we are still looking at the same key etc.
		
		if( !checkContinuedEntry( *ppucKey, *puiKeyLen, &bLastElement,
			pucEntry, getBlkType( (FLMBYTE *)m_pStack->pBlkHdr)))
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
			goto Exit;
		}
	}

	*peAction = ELM_DONE;

Exit:

	// Only release the m_pSCache if the use count is greater than 1.  It is
	// pointed to by the stack also.

	if( m_pSCache && m_pSCache->m_uiUseCount > 1)
	{
		ScaReleaseCache( m_pSCache, FALSE);
	}
	
	m_pSCache = NULL;
	return( rc);
}

/***************************************************************************
Desc:	Private method to retrieve the next block in the chain relative to
		the block that is passed in.  The block that is passed in is always
		released prior to getting the next block.
****************************************************************************/
FINLINE RCODE F_Btree::getNextBlock(
	F_CachedBlock **	ppSCache)
{
	RCODE				rc = NE_XFLM_OK;
	FLMUINT32		ui32BlkAddr;

	ui32BlkAddr = (*ppSCache)->m_pBlkHdr->ui32NextBlkInChain;

	ScaReleaseCache( *ppSCache, FALSE);
	*ppSCache = NULL;

	if( ui32BlkAddr == 0)
	{
		rc = RC_SET( NE_XFLM_EOF_HIT);
		goto Exit;
	}

	if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
		ui32BlkAddr, NULL, ppSCache)))
	{
		goto Exit;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Private method to retrieve the previous block in the chain relative to
		the  block that is passed in.  The block that is passed in is always
		released prior to getting the previous block.
****************************************************************************/
FINLINE RCODE F_Btree::getPrevBlock(
	F_CachedBlock **	ppSCache)
{
	RCODE			rc = NE_XFLM_OK;
	FLMUINT32	ui32BlkAddr;

	ui32BlkAddr = (*ppSCache)->m_pBlkHdr->ui32PrevBlkInChain;

	ScaReleaseCache( *ppSCache, FALSE);
	*ppSCache = NULL;

	if( ui32BlkAddr == 0)
	{
		rc = RC_SET( NE_XFLM_BOF_HIT);
		goto Exit;
	}

	if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
		ui32BlkAddr, NULL, ppSCache)))
	{
		goto Exit;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Private method to verify that the entry we are looking at in the stack
		is a continuation entry.  The key must match the key we pass in and
		the entry must be marked as a continuation, i.e. not the first
		element.
****************************************************************************/
FLMBOOL F_Btree::checkContinuedEntry(
	const FLMBYTE *	pucKey,
	FLMUINT				uiKeyLen,
	FLMBOOL *			pbLastElement,
	FLMBYTE *			pucEntry,
	FLMUINT				uiBlkType)
{
	FLMBOOL				bOk = TRUE;
	FLMUINT				uiBlkKeyLen;
	const FLMBYTE *	pucBlkKey;

	if( pbLastElement)
	{
		*pbLastElement = bteLastElementFlag( pucEntry);
	}

	uiBlkKeyLen = getEntryKeyLength( pucEntry, uiBlkType, &pucBlkKey);

	// Must be the same size key!
	
	if( uiKeyLen != uiBlkKeyLen)
	{
		bOk = FALSE;
		goto Exit;
	}

	// Must be identical!
	
	if( f_memcmp( pucKey, pucBlkKey, uiKeyLen) != 0)
	{
		bOk = FALSE;
		goto Exit;
	}
		
	// Must not be the first element!
	
	if( bteFirstElementFlag( pucEntry))
	{
		bOk = FALSE;
		goto Exit;
	}

Exit:

	return( bOk);
}

/***************************************************************************
Desc:	Private method to assend the tree, updating the counts for a
		particular block.  This method allows us to update the counts quickly
		without the need to continually loop, replacing existing keys with 
		new counts.
****************************************************************************/
RCODE F_Btree::updateCounts( void)
{
	RCODE				rc = NE_XFLM_OK;
	FLMUINT			uiLevel;

	for( uiLevel = m_pStack->uiLevel;
		  uiLevel < m_uiStackLevels - 1;
		  uiLevel++)
	{
		if( RC_BAD( rc = updateParentCounts( m_Stack[ uiLevel].pSCache,
			&m_Stack[ uiLevel + 1].pSCache, m_Stack[ uiLevel + 1].uiCurOffset)))
		{
			goto Exit;
		}
		
		m_Stack[ uiLevel + 1].pBlkHdr =
			(F_BTREE_BLK_HDR *)m_Stack[ uiLevel + 1].pSCache->m_pBlkHdr;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Private method to store part of an entry in a block.  This method will
		determine how much of the data can be stored in the block.  The amount
		that does not get stored will be returned in ppucRemainingValue and
		puiRemainingLen.
****************************************************************************/
RCODE F_Btree::storePartialEntry(
	const FLMBYTE *		pucKey,
	FLMUINT					uiKeyLen,
	const FLMBYTE *		pucValue,
	FLMUINT					uiLen,
	FLMUINT					uiFlags,
	FLMUINT					uiChildBlkAddr,
	FLMUINT 					uiCounts,
	const FLMBYTE **		ppucRemainingValue,
	FLMUINT *				puiRemainingLen,
	FLMBOOL					bNewBlock)
{
	RCODE				rc = NE_XFLM_OK;
	FLMUINT			uiNewDataLen;
	FLMUINT			uiOADataLen = 0;
	FLMUINT			uiEntrySize;
	FLMBOOL			bHaveRoom;
	FLMBOOL			bDefragBlk;
	FLMBOOL			bLastEntry;

	if( RC_BAD( rc = calcOptimalDataLength( uiKeyLen,
				uiLen, m_pStack->pBlkHdr->stdBlkHdr.ui16BlkBytesAvail,
				&uiNewDataLen)))
	{
		goto Exit;
	}

	if( uiNewDataLen < uiLen)
	{
		// Turn off the last element flag.
		
		uiFlags &= ~BTE_FLAG_LAST_ELEMENT;
		
		if( uiFlags & BTE_FLAG_FIRST_ELEMENT)
		{
			// Store the overall data length from this point forward.
			
			uiOADataLen = uiLen;
		}
	}

	if( RC_BAD( rc = calcNewEntrySize( uiKeyLen, uiNewDataLen, &uiEntrySize,
		&bHaveRoom, &bDefragBlk)))
	{
		goto Exit;
	}

	// We will defragment the block first if the avail and heap
	// are not the same size.

	if( m_pStack->pBlkHdr->ui16HeapSize !=
						m_pStack->pBlkHdr->stdBlkHdr.ui16BlkBytesAvail)
	{
		if( RC_BAD( rc = defragmentBlock( &m_pStack->pSCache)))
		{
			goto Exit;
		}
	}

	if( RC_BAD( rc = storeEntry( pucKey, uiKeyLen, pucValue, uiNewDataLen,
		uiFlags, uiOADataLen, uiChildBlkAddr, uiCounts, 
		uiEntrySize, &bLastEntry)))
	{
		goto Exit;
	}

	// If this block has a parent block, and the btree is maintaining counts
	// we will want to update the counts on the parent block.

	if( !isRootBlk( m_pStack->pBlkHdr) && m_bCounts && !bNewBlock)
	{
		if( RC_BAD( rc = updateCounts()))
		{
			goto Exit;
		}
	}

	if( uiNewDataLen < uiLen)
	{
		// Save the portion of the data that was not written.
		// It will be written later.
		
		*ppucRemainingValue = pucValue + uiNewDataLen;
		*puiRemainingLen = uiLen - uiNewDataLen;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Private meethod for checking the down links in the btree to make sure
		they are not corrupt.
****************************************************************************/
RCODE F_Btree::checkDownLinks( void)
{
	RCODE					rc = NE_XFLM_OK;
	F_CachedBlock *	pParentSCache = NULL;

	if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock(
		m_pDb, m_pLFile, m_pLFile->uiRootBlk, NULL, &pParentSCache)))
	{
		goto Exit;
	}

	if( (pParentSCache->m_pBlkHdr->ui8BlkType == BT_NON_LEAF) ||
		 (pParentSCache->m_pBlkHdr->ui8BlkType == BT_NON_LEAF_COUNTS))
	{
		if( RC_BAD( rc = verifyChildLinks( pParentSCache)))
		{
			goto Exit;
		}
	}

Exit:

	if( pParentSCache)
	{
		ScaReleaseCache( pParentSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc:	Private method (recursive) that checks the child links in the given
		blocks to ensure they are correct.
****************************************************************************/
RCODE F_Btree::verifyChildLinks(
	F_CachedBlock *		pParentSCache)
{
	RCODE						rc = NE_XFLM_OK;
	FLMUINT					uiNumKeys;
	F_CachedBlock *		pChildSCache = NULL;
	F_BTREE_BLK_HDR *		pParentBlkHdr;
	F_BTREE_BLK_HDR *		pChildBlkHdr;
	FLMUINT					uiCurOffset;
	FLMBYTE *				pucEntry;
	FLMUINT32				ui32BlkAddr;
	const FLMBYTE *		pucParentKey;
	FLMBYTE *				pucChildEntry;
	const FLMBYTE *		pucChildKey;
	FLMUINT					uiParentKeyLen;
	FLMUINT					uiChildKeyLen;

	pParentBlkHdr = (F_BTREE_BLK_HDR *)pParentSCache->m_pBlkHdr;
	uiNumKeys = pParentBlkHdr->ui16NumKeys;

	for( uiCurOffset = 0; uiCurOffset < uiNumKeys; uiCurOffset++)
	{
		pucEntry = BtEntry( (FLMBYTE *)pParentBlkHdr, uiCurOffset);
		
		// Non-leaf nodes have children.
		
		ui32BlkAddr = bteGetBlkAddr( pucEntry);
		flmAssert( ui32BlkAddr);

		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock(
			m_pDb, m_pLFile, ui32BlkAddr, NULL, &pChildSCache)))
		{
			goto Exit;
		}
		
		pChildBlkHdr = (F_BTREE_BLK_HDR *)pChildSCache->m_pBlkHdr;

		// Get key from the parent entry and compare it to the
		// last key in the child block.
		
		uiParentKeyLen = getEntryKeyLength(
			pucEntry, pParentBlkHdr->stdBlkHdr.ui8BlkType, &pucParentKey);

		// Get the last entry in the child block.
		
		pucChildEntry = BtLastEntry( (FLMBYTE *)pChildBlkHdr);

		uiChildKeyLen = getEntryKeyLength(
			pucChildEntry, pChildBlkHdr->stdBlkHdr.ui8BlkType, &pucChildKey);

		if( uiParentKeyLen != uiChildKeyLen)
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
			goto Exit;
		}

		if( f_memcmp( pucParentKey, pucChildKey, uiParentKeyLen) != 0)
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
			goto Exit;
		}

		if( (pChildBlkHdr->stdBlkHdr.ui8BlkType == BT_NON_LEAF) ||
			 (pChildBlkHdr->stdBlkHdr.ui8BlkType == BT_NON_LEAF_COUNTS))
		{
			if( RC_BAD( rc = verifyChildLinks( pChildSCache)))
			{
				goto Exit;
			}
		}
		
		ScaReleaseCache( pChildSCache, FALSE);
		pChildSCache = NULL;
	}

Exit:

	if( pChildSCache)
	{
		ScaReleaseCache( pChildSCache, FALSE);
	}
	
	return( rc);
}

/***************************************************************************
Desc:	This is a private method that computes the number of entries (keys)
		and the number of blocks between two points in the Btree.
****************************************************************************/
RCODE F_Btree::computeCounts(
	F_BTSK_p			pFromStack,
	F_BTSK_p			pUntilStack,
	FLMUINT *		puiBlockCount,
	FLMUINT *		puiKeyCount,
	FLMBOOL *		pbTotalsEstimated,
	FLMUINT			uiAvgBlkFullness)
{
	RCODE			rc = NE_XFLM_OK;
	FLMUINT		uiTotalKeys = 0;
	FLMUINT		uiTempKeyCount = 0;
	FLMUINT		uiEstKeyCount = 0;
	FLMUINT		uiTotalBlocksBetween = 0;
	FLMUINT		uiEstBlocksBetween = 0;

	uiTotalBlocksBetween = 0;
	*pbTotalsEstimated = FALSE;

	// The stack that we are looking at does not hold the blocks
	// we need. We first need to restore the blocks as needed.

	if( RC_BAD( rc = getCacheBlocks( pFromStack, pUntilStack)))
	{
		goto Exit;
	}

	// Are the from and until positions in the same block?

	if( pFromStack->ui32BlkAddr == pUntilStack->ui32BlkAddr)
	{
		rc = blockCounts( pFromStack, pFromStack->uiCurOffset,
			pUntilStack->uiCurOffset, &uiTotalKeys, NULL);
		goto Exit;
	}

	// Are we maintaining counts on this Btree?  If so, we can just
	// use the counts we have...  The blocks count may still be estimated.

	if( m_bCounts)
	{
		return( getStoredCounts( pFromStack, pUntilStack, puiBlockCount,
			puiKeyCount, pbTotalsEstimated, uiAvgBlkFullness));
	}

	// Since we are not keeping counts on this Btree, we will need to
	// count them and possibly estimate them.

	// Gather the counts in the from and until leaf blocks.

	if( RC_BAD( rc = blockCounts( pFromStack, pFromStack->uiCurOffset,
			pFromStack->pBlkHdr->ui16NumKeys - 1, &uiTotalKeys, NULL)))
	{
		goto Exit;
	}

	if( RC_BAD( rc = blockCounts( pUntilStack, 0,
			pUntilStack->uiCurOffset, &uiTempKeyCount, NULL)))
	{
		goto Exit;
	}

	uiTotalKeys += uiTempKeyCount;

	// Do the obvious check to see if the blocks are neighbors.  If they
	// are, we are done.

	if( pFromStack->pBlkHdr->stdBlkHdr.ui32NextBlkInChain ==
							pUntilStack->ui32BlkAddr)
	{
		goto Exit;
	}

	// Estimate the number of elements in the parent block.

	*pbTotalsEstimated = TRUE;

	uiEstKeyCount = getAvgKeyCount( pFromStack, pUntilStack, uiAvgBlkFullness);
	uiEstBlocksBetween = 1;

	for (;;)
	{
		FLMUINT	uiElementCount;
		FLMUINT	uiTempElementCount;
		FLMUINT	uiEstElementCount;

		// Go up a b-tree level and check out how far apart the elements are.

		pFromStack++;
		pUntilStack++;

		if( RC_BAD( rc = getCacheBlocks( pFromStack, pUntilStack)))
		{
			goto Exit;
		}

		// Share the same block?

		if( pFromStack->ui32BlkAddr == pUntilStack->ui32BlkAddr)
		{
			if( RC_BAD( rc = blockCounts( pFromStack, pFromStack->uiCurOffset,
					pUntilStack->uiCurOffset, NULL, &uiElementCount)))
			{
				goto Exit;
			}

			// Don't count the pFromStack or the pUntilStack current elements.

			uiElementCount -= 2;

			uiTotalBlocksBetween += uiEstBlocksBetween *
											(uiElementCount > 0 ? uiElementCount : 1);
			uiTotalKeys += uiEstKeyCount *
								(uiElementCount > 0 ? uiElementCount : 1);
			goto Exit;
		}

		// Gather the counts in the from and until non-leaf blocks.

		if( RC_BAD( rc = blockCounts( pFromStack, pFromStack->uiCurOffset,
				pFromStack->pBlkHdr->ui16NumKeys - 1, NULL, &uiElementCount)))
		{
			goto Exit;
		}

		// Don't count the first element.

		uiElementCount--;

		if( RC_BAD( rc = blockCounts( pUntilStack, 0,
				pUntilStack->uiCurOffset, NULL, &uiTempElementCount)))
		{
			goto Exit;
		}

		uiElementCount += (uiTempElementCount - 1);

		uiTotalBlocksBetween += uiEstBlocksBetween * uiElementCount;
		uiTotalKeys += uiEstKeyCount * uiElementCount;

		// Do the obvious check to see if the blocks are neighbors.

		if( (FLMUINT)pFromStack->pBlkHdr->stdBlkHdr.ui32NextBlkInChain ==
			 pUntilStack->ui32BlkAddr)
		{
			goto Exit;
		}

		// Recompute the estimated element count on every b-tree level
		// because the compression is better the lower in the b-tree we go.

		uiEstElementCount = getAvgKeyCount(
										pFromStack, pUntilStack, uiAvgBlkFullness);

		// Adjust the estimated key/ref count to be the counts from a complete
		// (not partial) block starting at this level going to the leaf.

		uiEstKeyCount *= uiEstElementCount;
		uiEstBlocksBetween *= uiEstElementCount;
	}

Exit:

	if( puiKeyCount)
	{
		*puiKeyCount = uiTotalKeys;
	}

	if( puiBlockCount)
	{
		*puiBlockCount = uiTotalBlocksBetween;
	}

	return( rc);
}

/***************************************************************************
Desc:	Private method to count the number of unique keys between two points.
		The count returned is inclusive of the first and last offsets.
****************************************************************************/
RCODE F_Btree::blockCounts(
	F_BTSK_p			pStack,
	FLMUINT			uiFirstOffset,
	FLMUINT			uiLastOffset,
	FLMUINT *		puiKeyCount,
	FLMUINT *		puiElementCount)
{
	RCODE				rc = NE_XFLM_OK;
	FLMUINT			uiKeyCount;
	FLMUINT			uiElementCount;
	FLMBYTE *		pucBlk;
	FLMBYTE *		pucEntry;

	// Debug checks.

	flmAssert( uiFirstOffset <= uiLastOffset);
	flmAssert( uiLastOffset <= (FLMUINT)(pStack->pBlkHdr->ui16NumKeys - 1));

	uiKeyCount = uiElementCount = 0;
	pucBlk = (FLMBYTE *)pStack->pBlkHdr;

	// Loop gathering the statistics.

	while( uiFirstOffset <= uiLastOffset)
	{
		uiElementCount++;

		if( puiKeyCount)
		{
			pucEntry = BtEntry( pucBlk, uiFirstOffset);

			// We only have to worry about first key elements when we are at the
			// leaf level and we are keeping data at that level.

			if( pStack->uiLevel == 0 && m_bData)
			{
				if( bteFirstElementFlag( pucEntry))
				{
					uiKeyCount++;
				}
			}
			else
			{
				uiKeyCount++;
			}
		}

		// Next element.

		if( uiFirstOffset == (FLMUINT)(pStack->pBlkHdr->ui16NumKeys - 1))
		{
			break;
		}
		else
		{
			uiFirstOffset++;
		}
	}

	if( puiKeyCount)
	{
		*puiKeyCount = uiKeyCount;
	}

	if( puiElementCount)
	{
		*puiElementCount = uiElementCount;
	}

	return( rc);
}

/***************************************************************************
Desc:	Similar to computeCounts, except we use the stored counts.
****************************************************************************/
RCODE F_Btree::getStoredCounts(
	F_BTSK_p			pFromStack,
	F_BTSK_p			pUntilStack,
	FLMUINT *		puiBlockCount,
	FLMUINT *		puiKeyCount,
	FLMBOOL *		pbTotalsEstimated,
	FLMUINT			uiAvgBlkFullness)
{
	RCODE				rc = NE_XFLM_OK;
	FLMUINT			uiOmittedKeys;
	FLMUINT			uiTotalKeys;
	FLMUINT			uiEstBlocksBetween;
	FLMUINT			uiTotalBlocksBetween;

	*pbTotalsEstimated = FALSE;
	*puiBlockCount = 0;
	uiTotalBlocksBetween = 0;

	// Are these blocks adjacent?

	if( pFromStack->pBlkHdr->stdBlkHdr.ui32NextBlkInChain ==
			pUntilStack->ui32BlkAddr)
	{
		*puiKeyCount = (pFromStack->pBlkHdr->ui16NumKeys - 
								pFromStack->uiCurOffset) + pUntilStack->uiCurOffset + 1;
		goto Exit;
	}

	*pbTotalsEstimated = TRUE;

	// How many keys are excluded in the From and Until blocks?

	uiOmittedKeys = countRangeOfKeys(
					pFromStack, 0, pFromStack->uiCurOffset) - 1;

	uiOmittedKeys += countRangeOfKeys(
					pUntilStack, pUntilStack->uiCurOffset,
					pUntilStack->pBlkHdr->ui16NumKeys - 1) - 1;

	uiTotalKeys = 0;
	uiEstBlocksBetween = 1;

	for( ;;)
	{
		FLMUINT	uiElementCount;
		FLMUINT	uiTempElementCount;
		FLMUINT	uiEstElementCount;

		// Go up a b-tree level and check out how far apart the elements are.

		pFromStack++;
		pUntilStack++;

		if( RC_BAD( rc = getCacheBlocks( pFromStack, pUntilStack)))
		{
			goto Exit;
		}

		// Share the same block?  We can get the actual key count now.

		if( pFromStack->ui32BlkAddr == pUntilStack->ui32BlkAddr)
		{

			if( RC_BAD( rc = blockCounts( pFromStack, pFromStack->uiCurOffset,
					pUntilStack->uiCurOffset, NULL, &uiElementCount)))
			{
				goto Exit;
			}

			// Don't count the pFromStack current element.

			uiElementCount -= 2;
			uiTotalBlocksBetween += uiEstBlocksBetween *
											(uiElementCount > 0 ? uiElementCount : 1);

			// Add one to the lasty offset to include the last entry in the count.
			
			uiTotalKeys = countRangeOfKeys(
				pFromStack, pFromStack->uiCurOffset, pUntilStack->uiCurOffset);

			*puiKeyCount = uiTotalKeys - uiOmittedKeys;
			*puiBlockCount = uiTotalBlocksBetween;
			goto Exit;
		}

		// How many to exclude from the From & Until blocks.

		if( pFromStack->uiCurOffset)
		{
			uiOmittedKeys += countRangeOfKeys(
				pFromStack, 0, pFromStack->uiCurOffset - 1);
		}

		uiOmittedKeys += countRangeOfKeys(
				pUntilStack,  pUntilStack->uiCurOffset + 1,
				pUntilStack->pBlkHdr->ui16NumKeys - 1);

		// Gather the counts in the from and until non-leaf blocks.

		if( RC_BAD( rc = blockCounts( pFromStack, pFromStack->uiCurOffset,
				pFromStack->pBlkHdr->ui16NumKeys - 1, NULL, &uiElementCount)))
		{
			goto Exit;
		}

		// Don't count the first element.

		uiElementCount--;

		if( RC_BAD( rc = blockCounts( pUntilStack, 0,
				pUntilStack->uiCurOffset, NULL, &uiTempElementCount)))
		{
			goto Exit;
		}

		uiElementCount += (uiTempElementCount - 1);
		uiTotalBlocksBetween += uiEstBlocksBetween * uiElementCount;

		// We are not going to check if these blocks are neighbors here because
		// we want to find the common parent.  That will tell us what the actual
		// counts are at the leaf level.

		// Recompute the estimated element count on every b-tree level
		// because the compression is better the lower in the b-tree we go.

		uiEstElementCount = getAvgKeyCount( 
										pFromStack, pUntilStack, uiAvgBlkFullness);
		uiEstBlocksBetween *= uiEstElementCount;
	}

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Retrieve the blocks identified in the two stack entries.  Used in
		computing counts (btComputeCounts etc.)
****************************************************************************/
RCODE F_Btree::getCacheBlocks(
	F_BTSK_p			pStack1,
	F_BTSK_p			pStack2)
{
	RCODE				rc = NE_XFLM_OK;

	// If these blocks are at the root level, we must ensure that we retrieve
	// the root block.  The root block can potentially change address, so
	// we wil reset it here to be sure.

	if( pStack1->uiLevel == m_uiRootLevel)
	{
		pStack1->ui32BlkAddr = (FLMUINT32)m_pLFile->uiRootBlk;
	}

	if( pStack2->uiLevel == m_uiRootLevel)
	{
		pStack2->ui32BlkAddr = (FLMUINT32)m_pLFile->uiRootBlk;
	}

	if( RC_BAD( m_pDb->m_pDatabase->getBlock(
		m_pDb, m_pLFile, pStack1->ui32BlkAddr, NULL, &pStack1->pSCache)))
	{
		goto Exit;
	}
	
	pStack1->pBlkHdr = (F_BTREE_BLK_HDR *)pStack1->pSCache->m_pBlkHdr;

	if( RC_BAD( m_pDb->m_pDatabase->getBlock(
		m_pDb, m_pLFile, pStack2->ui32BlkAddr, NULL, &pStack2->pSCache)))
	{
		goto Exit;
	}
	
	pStack2->pBlkHdr = (F_BTREE_BLK_HDR *)pStack2->pSCache->m_pBlkHdr;

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Method to tally the counts in a block between (inclusive) the
		uiFromOffset & uiUntilOffset parameters.
****************************************************************************/
FLMUINT F_Btree::countRangeOfKeys(
	F_BTSK_p				pFromStack,
	FLMUINT				uiFromOffset,
	FLMUINT				uiUntilOffset)
{
	FLMUINT			uiCount = 0;
	FLMBYTE *		pucBlk;
	FLMUINT			uiLoop = uiFromOffset;
	FLMBYTE *		pucEntry;
	FLMUINT			uiBlkType;

	pucBlk = (FLMBYTE *)pFromStack->pBlkHdr;
	uiBlkType = getBlkType( pucBlk);

	if( uiBlkType == BT_NON_LEAF_COUNTS)
	{
		while( uiLoop < uiUntilOffset)
		{
			pucEntry = BtEntry( pucBlk, uiLoop);
			pucEntry += 4;
			uiCount += FB2UD( pucEntry);
			uiLoop++;
		}
	}
	else
	{
		uiCount = uiUntilOffset;
	}

	return( uiCount);
}

/***************************************************************************
Desc:	Method to estimate the averge number of keys, based on the anticipated
		average block usage (passed in) and the actual block usage.
****************************************************************************/
FINLINE FLMUINT F_Btree::getAvgKeyCount(
	F_BTSK_p			pFromStack,
	F_BTSK_p			pUntilStack,
	FLMUINT			uiAvgBlkFullness)
{
	FLMUINT			uiFromUsed;
	FLMUINT			uiUntilUsed;
	FLMUINT			uiTotalUsed;
	FLMUINT			uiFromKeys;
	FLMUINT			uiUntilKeys;
	FLMUINT			uiTotalKeys;

	uiFromUsed = m_uiBlockSize -
		((F_BLK_HDR *)pFromStack->pBlkHdr)->ui16BlkBytesAvail;
		
	uiUntilUsed = m_uiBlockSize -
		((F_BLK_HDR *)pUntilStack->pBlkHdr)->ui16BlkBytesAvail;

	uiTotalUsed = uiFromUsed + uiUntilUsed;

	uiFromKeys = pFromStack->pBlkHdr->ui16NumKeys;
	uiUntilKeys = pUntilStack->pBlkHdr->ui16NumKeys;
	uiTotalKeys = uiFromKeys + uiUntilKeys;

	return( (uiAvgBlkFullness * uiTotalKeys) / uiTotalUsed);
}

/***************************************************************************
Desc:	Method to test if two blocks can be merged together to make a single
		block.  This is done only after a remove operation and is intended to
		try to consolidate space as much as possible.  If we can consolidate
		two blocks, we will do it, then update the tree.
****************************************************************************/
RCODE F_Btree::mergeBlocks(
	FLMBOOL						bLastEntry,
	FLMBOOL *					pbMergedWithPrev,
	FLMBOOL *					pbMergedWithNext,
	F_ELM_UPD_ACTION *		peAction)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT32			ui32PrevBlkAddr;
	F_CachedBlock *	pPrevSCache = NULL;
	FLMUINT32			ui32NextBlkAddr;
	F_CachedBlock *	pNextSCache = NULL;

	*pbMergedWithPrev = FALSE;
	*pbMergedWithNext = FALSE;

	// Our first check is to see if we can merge the current block with its
	// previous block.

	ui32PrevBlkAddr = m_pStack->pSCache->m_pBlkHdr->ui32PrevBlkInChain;
	if( ui32PrevBlkAddr)
	{
		// Get the block.

		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock(
			m_pDb, m_pLFile, ui32PrevBlkAddr, NULL, &pPrevSCache)))
		{
			goto Exit;
		}

		// Is there room to merge?

		if( (FLMUINT)(pPrevSCache->m_pBlkHdr->ui16BlkBytesAvail +
				m_pStack->pSCache->m_pBlkHdr->ui16BlkBytesAvail) >=
				(FLMUINT)(m_uiBlockSize - sizeofBTreeBlkHdr(
									(F_BTREE_BLK_HDR *)m_pStack->pSCache->m_pBlkHdr)))
		{
			// Looks like we can merge these two.  We will move the content
			// of the previous block into this one.

			if( RC_BAD( rc = merge( &pPrevSCache, &m_pStack->pSCache)))
			{
				goto Exit;
			}

			// Save the changed block header address

			m_pStack->pBlkHdr = (F_BTREE_BLK_HDR *)m_pStack->pSCache->m_pBlkHdr;

			// Update the counts for the current block before releasing it.

			if( m_bCounts)
			{
				if( RC_BAD( rc = updateCounts()))
				{
					goto Exit;
				}
			}

			if( bLastEntry)
			{
				// Need to save the replace information for the last entry in
				// the block before we move to the previous block.  This will
				// allow us to do the replace later.

				FLMBYTE *			pucEntry;
				const FLMBYTE *	pucKey;
				FLMUINT				uiKeyLen;

				pucEntry = BtEntry(
					(FLMBYTE *)m_pStack->pBlkHdr, m_pStack->pBlkHdr->ui16NumKeys - 1);
					
				uiKeyLen = getEntryKeyLength(
					pucEntry, getBlkType( (FLMBYTE *)m_pStack->pBlkHdr), &pucKey);

				if( RC_BAD( rc = saveReplaceInfo( pucKey, uiKeyLen)))
				{
					goto Exit;
				}
			}

			// Move the stack to the previous entry

			if( RC_BAD( rc = moveStackToPrev( pPrevSCache)))
			{
				goto Exit;
			}
			pPrevSCache = NULL;

			flmAssert( m_pStack->pBlkHdr->ui16NumKeys == 0);

			// Free the empty block.

			if( RC_BAD( rc = deleteEmptyBlock()))
			{
				goto Exit;
			}

			// Now we want to remove the parent entry for the block that was
			// freed.

			m_pStack++;
			*peAction = ELM_REMOVE;
			*pbMergedWithPrev = TRUE;
			
			goto Exit;
		}
		else
		{
			// No room here so release the block.

			ScaReleaseCache( pPrevSCache, FALSE);
			pPrevSCache = NULL;
		}
	}

	// Can we merge with the next block?

	ui32NextBlkAddr = m_pStack->pSCache->m_pBlkHdr->ui32NextBlkInChain;
	if( ui32NextBlkAddr)
	{
		// Get the block.

		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock(
			m_pDb, m_pLFile, ui32NextBlkAddr, NULL, &pNextSCache)))
		{
			goto Exit;
		}

		// Is there room to merge?

		if( (FLMUINT)(pNextSCache->m_pBlkHdr->ui16BlkBytesAvail +
				m_pStack->pSCache->m_pBlkHdr->ui16BlkBytesAvail) >=
				(FLMUINT)(m_uiBlockSize - sizeofBTreeBlkHdr(
								(F_BTREE_BLK_HDR *)m_pStack->pSCache->m_pBlkHdr)))
		{
			// Looks like we can merge these two.

			if( RC_BAD( rc = merge( &m_pStack->pSCache, &pNextSCache)))
			{
				goto Exit;
			}

			// Save the changed block header address.

			m_pStack->pBlkHdr = (F_BTREE_BLK_HDR *)m_pStack->pSCache->m_pBlkHdr;

			// Update the counts for the current block and the next block.

			if( m_bCounts)
			{
				pPrevSCache = m_pStack->pSCache;

				// Need to move the stack to the next entry.  Don't let the current
				// block get released because we still need it.

				if( RC_BAD( rc = moveStackToNext( pNextSCache, FALSE)))
				{
					goto Exit;
				}
				pNextSCache = NULL;

				if( RC_BAD( rc = updateCounts()))
				{
					goto Exit;
				}

				// Move back to the original stack again.  It's okay to release the
				// now current block.

				if( RC_BAD( rc = moveStackToPrev( pPrevSCache)))
				{
					goto Exit;
				}
				
				pPrevSCache = NULL;
			}

			flmAssert( m_pStack->pBlkHdr->ui16NumKeys == 0);

			// Free the empty block.

			if( RC_BAD( rc = deleteEmptyBlock()))
			{
				goto Exit;
			}

			// Now we want to remove the parent entry for the block that was freed.

			m_pStack++;
			*peAction = ELM_REMOVE;
			*pbMergedWithNext = TRUE;
			goto Exit;
		}
		else
		{
			// No room here so release the block.

			ScaReleaseCache( pNextSCache, FALSE);
			pNextSCache = NULL;
		}
	}

Exit:

	if( *pbMergedWithPrev || *pbMergedWithNext)
	{
		if( m_pDb->m_pDbStats != NULL)
		{
			XFLM_LFILE_STATS *		pLFileStats;

			if( (pLFileStats = m_pDb->getLFileStatPtr( m_pLFile)) != NULL)
			{
				pLFileStats->bHaveStats = TRUE;
				pLFileStats->ui64BlockCombines++;
			}
		}
	}

	if( pPrevSCache)
	{
		ScaReleaseCache( pPrevSCache, FALSE);
	}

	if( pNextSCache)
	{
		ScaReleaseCache( pNextSCache, FALSE);
	}

	return( rc);
}

/***************************************************************************
Desc:	Method to move the contents of the ppFromSCache block into the
		ppToSCache block.  Note that all merges are a move to next operation.
****************************************************************************/
RCODE F_Btree::merge(
	F_CachedBlock **	ppFromSCache,
	F_CachedBlock **	ppToSCache)
{
	RCODE					rc = NE_XFLM_OK;
	F_BTSK				tempStack;
	F_BTSK_p				pStack = NULL;
	F_CachedBlock *	pSCache;
	F_BTREE_BLK_HDR *	pBlkHdr;

	// May need to defragment the blocks first.

	pBlkHdr = (F_BTREE_BLK_HDR *)(*ppToSCache)->m_pBlkHdr;
	if( pBlkHdr->stdBlkHdr.ui16BlkBytesAvail != pBlkHdr->ui16HeapSize)
	{
		if( RC_BAD( rc = defragmentBlock( ppToSCache)))
		{
			goto Exit;
		}
	}

	// Make a temporary stack entry so we can "fool" the moveToNext
	// function into moving the entries for us.

	pSCache = *ppFromSCache;
	tempStack.pBlkHdr = (F_BTREE_BLK_HDR *)pSCache->m_pBlkHdr;
	tempStack.ui32BlkAddr = pSCache->m_pBlkHdr->ui32BlkAddr;
	tempStack.pSCache = pSCache;
	tempStack.uiCurOffset = 0;
	tempStack.uiLevel = m_pStack->uiLevel;
	tempStack.pui16OffsetArray = BtOffsetArray( (FLMBYTE *)pSCache->m_pBlkHdr, 0);

	// Save the current m_pStack.

	pStack = m_pStack;
	m_pStack = &tempStack;

	// Now do the move

	if( RC_BAD( rc = moveToNext( tempStack.pBlkHdr->ui16NumKeys - 1,
		0, ppToSCache)))
	{
		goto Exit;
	}

	// Return the changed block structure

	*ppFromSCache = tempStack.pSCache;

Exit:

	// Must always restore the stack.

	m_pStack = pStack;
	return( rc);
}

/***************************************************************************
Desc:	Method to test if the src and dst entries can be combined into one
		entry.  If they can, then they will be combined and stored in the
		m_pucTempBlk buffer.
****************************************************************************/
RCODE F_Btree::combineEntries(
	F_BTREE_BLK_HDR *		pSrcBlkHdr,
	FLMUINT					uiSrcOffset,
	F_BTREE_BLK_HDR *		pDstBlkHdr,
	FLMUINT					uiDstOffset,
	FLMBOOL *				pbEntriesCombined,
	FLMUINT *				puiEntrySize)
{
	RCODE					rc = NE_XFLM_OK;
	FLMBYTE *			pucSrcEntry;
	FLMBYTE *			pucDstEntry;
	FLMUINT				uiSrcKeyLen;
	FLMUINT				uiDstKeyLen;
	const FLMBYTE *	pucSrcKey;
	const FLMBYTE *	pucDstKey;
	FLMUINT				uiFlags = 0;
	FLMBYTE *			pucTmp;
 	FLMUINT				uiSrcOADataLen;
 	FLMUINT				uiDstOADataLen;
	const FLMBYTE *	pucSrcData;
	const FLMBYTE *	pucDstData;
	FLMUINT				uiSrcDataLen;
	FLMUINT				uiDstDataLen;
	FLMUINT				uiEntrySize;

	*pbEntriesCombined = FALSE;
	*puiEntrySize = 0;

	if( pDstBlkHdr->ui16NumKeys == 0)
	{
		goto Exit;
	}

	if( pSrcBlkHdr->ui16NumKeys == 0)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
		goto Exit;
	}

	if( getBlkType( (FLMBYTE *)pSrcBlkHdr) != BT_LEAF_DATA)
	{
		goto Exit;
	}

	pucSrcEntry = BtEntry( (FLMBYTE *)pSrcBlkHdr, uiSrcOffset);
	pucDstEntry = BtEntry( (FLMBYTE *)pDstBlkHdr, uiDstOffset);

	// Do we have the same key?

	uiSrcKeyLen = getEntryKeyLength( pucSrcEntry, BT_LEAF_DATA, &pucSrcKey);
	uiDstKeyLen = getEntryKeyLength( pucDstEntry, BT_LEAF_DATA, &pucDstKey);

	if( uiSrcKeyLen != uiDstKeyLen)
	{
		// Not the same key.
		
		goto Exit;
	}

	if( f_memcmp( pucSrcKey, pucDstKey, uiSrcKeyLen) != 0)
	{
		// Not the same key.
		
		goto Exit;
	}

	// They match, so we can combine them.

	pucTmp = &m_pucTempBlk[ 1];		// Key length position
	uiFlags = (pucDstEntry[0] & (BTE_FLAG_FIRST_ELEMENT | BTE_FLAG_LAST_ELEMENT)) |
				 (pucSrcEntry[0] & (BTE_FLAG_FIRST_ELEMENT | BTE_FLAG_LAST_ELEMENT));
	uiEntrySize = 1;

	if( uiSrcKeyLen > ONE_BYTE_SIZE)
	{
		uiFlags |= BTE_FLAG_KEY_LEN;
		UW2FBA( (FLMUINT16)uiSrcKeyLen, pucTmp);
		pucTmp += 2;
		uiEntrySize += 2;
	}
	else
	{
		*pucTmp = (FLMBYTE)uiSrcKeyLen;
		pucTmp++;
		uiEntrySize++;
	}

	uiSrcDataLen = btGetEntryDataLength(
		pucSrcEntry, &pucSrcData, &uiSrcOADataLen, NULL);

	uiDstDataLen = btGetEntryDataLength(
		pucDstEntry, &pucDstData, &uiDstOADataLen, NULL);

	if( (uiSrcDataLen + uiDstDataLen) > ONE_BYTE_SIZE)
	{
		uiFlags |= BTE_FLAG_DATA_LEN;
		UW2FBA( (FLMUINT16)(uiSrcDataLen + uiDstDataLen), pucTmp);
		pucTmp += 2;
		uiEntrySize += 2;
	}
	else
	{
		*pucTmp = (FLMBYTE)(uiSrcDataLen + uiDstDataLen);
		pucTmp++;
		uiEntrySize++;
	}

	// Verify the OA Data length

	if( (*pucSrcEntry & BTE_FLAG_OA_DATA_LEN) &&
			(uiSrcOADataLen > (uiSrcDataLen + uiDstDataLen)))
	{
		uiFlags |= BTE_FLAG_OA_DATA_LEN;
		UD2FBA( (FLMUINT32)uiSrcOADataLen, pucTmp);
		pucTmp += 4;
		uiEntrySize += 4;
	}
	else if( (*pucDstEntry & BTE_FLAG_OA_DATA_LEN) &&
			(uiDstOADataLen > (uiSrcDataLen + uiDstDataLen)))
	{
		uiFlags |= BTE_FLAG_OA_DATA_LEN;
		UD2FBA( (FLMUINT32)uiDstOADataLen, pucTmp);
		pucTmp += 4;
		uiEntrySize += 4;
	}

	f_memcpy( pucTmp, pucSrcKey, uiSrcKeyLen);
	pucTmp += uiSrcKeyLen;
	uiEntrySize += uiSrcKeyLen;

	// Need to put the entry together in the right order.  If the Src block is
	// before the Dst block, then we will put down the Src data first.

	if( pSrcBlkHdr->stdBlkHdr.ui32NextBlkInChain ==
							pDstBlkHdr->stdBlkHdr.ui32BlkAddr)
	{
		f_memcpy( pucTmp, pucSrcData, uiSrcDataLen);
		pucTmp += uiSrcDataLen;
		uiEntrySize += uiSrcDataLen;

		f_memcpy( pucTmp, pucDstData, uiDstDataLen);
		uiEntrySize += uiDstDataLen;
	}
	else
	{
		f_memcpy( pucTmp, pucDstData, uiDstDataLen);
		uiEntrySize += uiDstDataLen;
		pucTmp += uiDstDataLen;
		
		f_memcpy( pucTmp, pucSrcData, uiSrcDataLen);
		uiEntrySize += uiSrcDataLen;
	}

	m_pucTempBlk[ 0] = (FLMBYTE)uiFlags;
	*puiEntrySize = uiEntrySize;
	*pbEntriesCombined = TRUE;

Exit:

	return( rc);
}

/***************************************************************************
Desc:	Method to move a block from one location to another.
****************************************************************************/
RCODE F_Btree::btMoveBlock(
	FLMUINT32			ui32FromBlkAddr,
	FLMUINT32			ui32ToBlkAddr)
{
	RCODE						rc = NE_XFLM_OK;
	FLMUINT					uiType;

	if( !m_bOpened || m_bSetupForRead || m_bSetupForReplace ||
		  (m_bSetupForWrite))
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	flmAssert( m_uiSearchLevel >= BH_MAX_LEVELS);

	// Verify the Txn type

	if( m_pDb->m_eTransType != XFLM_UPDATE_TRANS && !m_bTempDb)
	{
		rc = RC_SET_AND_ASSERT( m_pDb->m_eTransType == XFLM_NO_TRANS
								? NE_XFLM_NO_TRANS_ACTIVE
								: NE_XFLM_ILLEGAL_TRANS_OP);
		goto Exit;
	}

	// Get the From block and retrieve the last key in the block.  Make note
	// of the level of the block.  We will need this to make sure we get the
	// right block.

	if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
		ui32FromBlkAddr, NULL, &m_pSCache)))
	{
		goto Exit;
	}

	// Find out if this is a Btree block or a DO block.

	uiType = getBlkType((FLMBYTE *)m_pSCache->m_pBlkHdr);

	if( uiType == BT_FREE)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
		goto Exit;
	}

	if( uiType == BT_DATA_ONLY)
	{
		if( RC_BAD( rc = moveDOBlock( ui32FromBlkAddr, ui32ToBlkAddr)))
		{
			goto Exit;
		}
	}
	else
	{
		if( RC_BAD( rc = moveBtreeBlock( ui32FromBlkAddr, ui32ToBlkAddr)))
		{
			goto Exit;
		}
	}

Exit:

	if( m_pSCache)
	{
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}

	return( rc);
}

/***************************************************************************
Desc:	Move a Btree block from one address to another, updating its parent.
****************************************************************************/
RCODE F_Btree::moveBtreeBlock(
	FLMUINT32			ui32FromBlkAddr,
	FLMUINT32			ui32ToBlkAddr)
{
	RCODE						rc = NE_XFLM_OK;
	F_BTREE_BLK_HDR *		pBlkHdr = NULL;
	F_BTREE_BLK_HDR *		pNewBlkHdr = NULL;
	FLMBYTE *				pucEntry;
	const FLMBYTE *		pucKeyRV = NULL;
	FLMBYTE *				pucKey = NULL;
	FLMUINT					uiBlkLevel;
	FLMBYTE *				pucSrc;
	FLMBYTE *				pucDest;
	F_CachedBlock *		pSCache = NULL;
	FLMUINT					uiKeyLen;

	// m_pSCache has already been retrieved.

	flmAssert( m_pSCache);

	pBlkHdr = (F_BTREE_BLK_HDR *)m_pSCache->m_pBlkHdr;
	uiBlkLevel = pBlkHdr->ui8BlkLevel;

	pucEntry = BtLastEntry( (FLMBYTE *)pBlkHdr);

	uiKeyLen = getEntryKeyLength( pucEntry, getBlkType((FLMBYTE *)pBlkHdr),
											&pucKeyRV);

	if( RC_BAD( rc = f_calloc( uiKeyLen, &pucKey)))
	{
		goto Exit;
	}
	
	f_memcpy( pucKey, pucKeyRV, uiKeyLen);

	// Release the block and search for the key.
	
	ScaReleaseCache( m_pSCache, FALSE);
	m_pSCache = NULL;

	if( RC_BAD( rc = findEntry( pucKey, uiKeyLen, XFLM_EXACT)))
	{
		// We must find it!
		
		RC_UNEXPECTED_ASSERT( rc);
		goto Exit;
	}

	// Verify that we found the right block.
	
	m_pStack = &m_Stack[ uiBlkLevel];

	if( ui32FromBlkAddr != m_pStack->ui32BlkAddr)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
		goto Exit;
	}

	if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &m_pStack->pSCache)))
	{
		goto Exit;
	}

	m_pStack->pBlkHdr = (F_BTREE_BLK_HDR *)m_pStack->pSCache->m_pBlkHdr;
	m_pStack->pui16OffsetArray = BtOffsetArray( (FLMBYTE *)m_pStack->pBlkHdr, 0);
	pBlkHdr = m_pStack->pBlkHdr;

	// Get the new block and verify that it is a free block.
	
	if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
		ui32ToBlkAddr, NULL, &m_pSCache)))
	{
		goto Exit;
	}

	if( getBlkType( (FLMBYTE *)m_pSCache->m_pBlkHdr) != BT_FREE)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
		goto Exit;
	}

	// Update the header of the new block to point to the prev and next
	// blocks etc ...
	
	if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &m_pSCache)))
	{
		goto Exit;
	}

	pNewBlkHdr = (F_BTREE_BLK_HDR *)m_pSCache->m_pBlkHdr;

	pNewBlkHdr->stdBlkHdr.ui32PrevBlkInChain =
													pBlkHdr->stdBlkHdr.ui32PrevBlkInChain;
	pNewBlkHdr->stdBlkHdr.ui32NextBlkInChain =
													pBlkHdr->stdBlkHdr.ui32NextBlkInChain;
	pNewBlkHdr->stdBlkHdr.ui16BlkBytesAvail =
													pBlkHdr->stdBlkHdr.ui16BlkBytesAvail;
	pNewBlkHdr->stdBlkHdr.ui8BlkType = pBlkHdr->stdBlkHdr.ui8BlkType;
	pNewBlkHdr->stdBlkHdr.ui8BlkFlags = pBlkHdr->stdBlkHdr.ui8BlkFlags;

	pNewBlkHdr->ui16LogicalFile = pBlkHdr->ui16LogicalFile;
	pNewBlkHdr->ui16NumKeys = pBlkHdr->ui16NumKeys;
	pNewBlkHdr->ui8BlkLevel = pBlkHdr->ui8BlkLevel;
	pNewBlkHdr->ui8BTreeFlags = pBlkHdr->ui8BTreeFlags;
	pNewBlkHdr->ui16HeapSize = pBlkHdr->ui16HeapSize;

	// Get the previous and next blocks and set their next and prev addresses.
	
	if( pBlkHdr->stdBlkHdr.ui32PrevBlkInChain)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			pBlkHdr->stdBlkHdr.ui32PrevBlkInChain, NULL, &pSCache)))
		{
			goto Exit;
		}
		
		if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &pSCache)))
		{
			goto Exit;
		}
		
		flmAssert( pSCache->m_pBlkHdr->ui32NextBlkInChain == ui32FromBlkAddr);
		pSCache->m_pBlkHdr->ui32NextBlkInChain = ui32ToBlkAddr;

		ScaReleaseCache( pSCache, FALSE);
		pSCache = NULL;
	}

	if( pBlkHdr->stdBlkHdr.ui32NextBlkInChain)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			pBlkHdr->stdBlkHdr.ui32NextBlkInChain, NULL, &pSCache)))
		{
			goto Exit;
		}
		
		if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( m_pDb, &pSCache)))
		{
			goto Exit;
		}
		
		flmAssert( pSCache->m_pBlkHdr->ui32PrevBlkInChain == ui32FromBlkAddr);
		pSCache->m_pBlkHdr->ui32PrevBlkInChain = ui32ToBlkAddr;

		ScaReleaseCache( pSCache, FALSE);
		pSCache = NULL;
	}

	// Copy the content of the old block into the new block.
	
	pucSrc = (FLMBYTE *)pBlkHdr + sizeofBTreeBlkHdr( pBlkHdr);
	pucDest = (FLMBYTE *)pNewBlkHdr + sizeofBTreeBlkHdr( pNewBlkHdr);

	f_memcpy( pucDest, pucSrc, m_uiBlockSize - sizeofBTreeBlkHdr( pBlkHdr));

	if( isRootBlk( pBlkHdr))
	{
		// We need to update LFile to reflect the new root block address.

		F_COLLECTION *		pCollection = NULL;
		F_Database *		pDatabase= m_pDb->m_pDatabase;

		m_pLFile->uiRootBlk = ui32ToBlkAddr;

		if( m_pLFile->eLfType == XFLM_LF_COLLECTION)
		{
			// Cannot use collections in a temporary DB.

			flmAssert( !m_bTempDb);
			
			if( RC_BAD( rc = m_pDb->m_pDict->getCollection( m_pLFile->uiLfNum,
				&pCollection)))
			{
				goto Exit;
			}
		}

		rc  = pDatabase->lFileWrite( m_pDb, pCollection, m_pLFile);
		goto Exit;
	}

	// Move up one level to the parent entry.
	
	m_pStack++;
	flmAssert( m_pStack->pSCache);

	// Log that we are making a change to the block.
	
	if( RC_BAD( rc = m_pDb->m_pDatabase->logPhysBlk( 
		m_pDb, &m_pStack->pSCache)))
	{
		goto Exit;
	}
	
	m_pStack->pBlkHdr = (F_BTREE_BLK_HDR *)m_pStack->pSCache->m_pBlkHdr;

	// Update the parent block with a new address for the new block.
	
	pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr, m_pStack->uiCurOffset);
	UD2FBA( ui32ToBlkAddr, pucEntry);

Exit:

	if( m_pSCache)
	{
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}

	if( pSCache)
	{
		ScaReleaseCache( pSCache, FALSE);
	}

	f_free( &pucKey);
	releaseBlocks( TRUE);
	return( rc);
}

/***************************************************************************
Desc:	Move a DO block from one address to another, updating its reference
		btree entry.
****************************************************************************/
RCODE F_Btree::moveDOBlock(
	FLMUINT32			ui32FromBlkAddr,
	FLMUINT32			ui32ToBlkAddr)
{
	RCODE						rc = NE_XFLM_OK;
	F_BLK_HDR *				pBlkHdr = NULL;
	F_BLK_HDR *				pNewBlkHdr = NULL;
	FLMBYTE *				pucEntry;
	FLMBYTE *				pucKey = NULL;
	FLMBYTE *				pucSrc;
	FLMBYTE *				pucDest;
	F_CachedBlock *		pSCache = NULL;
	F_CachedBlock *		pPrevSCache = NULL;
	F_CachedBlock *		pNextSCache = NULL;
	FLMUINT					uiKeyLen;
	FLMUINT					uiOADataLen;
	const FLMBYTE *		pucData;
	FLMUINT32				ui32DOBlkAddr;
	FLMUINT					uiDataLen;
	FLMBYTE					ucDataBuffer[ sizeof(FLMUINT32)];
	FLMUINT					uiBlkHdrSize;

	// m_pSCache has already been retrieved.

	flmAssert( m_pSCache);

	// Log that we are changing this block.

	if( RC_BAD( m_pDb->m_pDatabase->logPhysBlk(  m_pDb, &m_pSCache)))
	{
		goto Exit;
	}
	pBlkHdr = m_pSCache->m_pBlkHdr;

	// Get the new block and verify that it is a free block.

	if( RC_BAD( m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
		ui32ToBlkAddr, NULL, &pSCache)))
	{
		goto Exit;
	}

	if( getBlkType( (FLMBYTE *)pSCache->m_pBlkHdr) != BT_FREE)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
		goto Exit;
	}

	// Update the header of the new block to point to the prev and next
	// blocks etc..

	if( RC_BAD( m_pDb->m_pDatabase->logPhysBlk(  m_pDb, &pSCache)))
	{
		goto Exit;
	}

	pNewBlkHdr = pSCache->m_pBlkHdr;
	pNewBlkHdr->ui32PrevBlkInChain = pBlkHdr->ui32PrevBlkInChain;
	pNewBlkHdr->ui32NextBlkInChain = pBlkHdr->ui32NextBlkInChain;
	pNewBlkHdr->ui16BlkBytesAvail = pBlkHdr->ui16BlkBytesAvail;
	pNewBlkHdr->ui8BlkType = pBlkHdr->ui8BlkType;
	pNewBlkHdr->ui8BlkFlags = pBlkHdr->ui8BlkFlags;

	// Get the previous and next blocks and set their next and prev addresses.

	if( pBlkHdr->ui32PrevBlkInChain)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			pBlkHdr->ui32PrevBlkInChain, NULL, &pPrevSCache)))
		{
			goto Exit;
		}
		
		if( RC_BAD( m_pDb->m_pDatabase->logPhysBlk(  m_pDb, &pPrevSCache)))
		{
			goto Exit;
		}
		
		flmAssert( pPrevSCache->m_pBlkHdr->ui32NextBlkInChain == ui32FromBlkAddr);
		pPrevSCache->m_pBlkHdr->ui32NextBlkInChain = ui32ToBlkAddr;
		ScaReleaseCache( pPrevSCache, FALSE);
		pPrevSCache = NULL;
	}

	if( pBlkHdr->ui32NextBlkInChain)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			pBlkHdr->ui32NextBlkInChain, NULL, &pNextSCache)))
		{
			goto Exit;
		}
		
		if( RC_BAD( m_pDb->m_pDatabase->logPhysBlk(  m_pDb, &pNextSCache)))
		{
			goto Exit;
		}
		
		flmAssert( pNextSCache->m_pBlkHdr->ui32PrevBlkInChain == ui32FromBlkAddr);
		pNextSCache->m_pBlkHdr->ui32PrevBlkInChain = ui32ToBlkAddr;
		ScaReleaseCache( pNextSCache, FALSE);
		pNextSCache = NULL;
	}

	// Copy the content of the old block into the new block.

	uiBlkHdrSize = sizeofDOBlkHdr((F_BLK_HDR *)pBlkHdr);
	pucSrc = (FLMBYTE *)pBlkHdr + uiBlkHdrSize;
	pucDest = (FLMBYTE *)pNewBlkHdr + uiBlkHdrSize;
	f_memcpy( pucDest, pucSrc, m_uiBlockSize - uiBlkHdrSize);

	// Do we need to update the reference btree entry.

	if( pBlkHdr->ui32PrevBlkInChain == 0)
	{
		// Get the key from the beginning of the block.

		uiKeyLen = FB2UW( pucDest);
		pucKey = pucDest + sizeof( FLMUINT16);

		if( RC_BAD( rc = findEntry( pucKey, uiKeyLen, XFLM_EXACT)))
		{
			// We must find it!
			
			RC_UNEXPECTED_ASSERT( rc);
			goto Exit;
		}

		// Verify that we found the right block.

		pucEntry = BtEntry( (FLMBYTE *)m_pStack->pBlkHdr, m_pStack->uiCurOffset);

		if( !bteDataBlockFlag( pucEntry))
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
			goto Exit;
		}

		uiDataLen = btGetEntryDataLength( pucEntry, &pucData,
							&uiOADataLen, NULL);

		ui32DOBlkAddr = bteGetBlkAddr( pucData);

		if( ui32DOBlkAddr != ui32FromBlkAddr)
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
			goto Exit;
		}

		if( uiDataLen != sizeof( ucDataBuffer))
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
			goto Exit;
		}

		// Make the data entry with the new block address

		UD2FBA( ui32ToBlkAddr, ucDataBuffer);

		if( RC_BAD( rc = updateEntry(
			pucKey, uiKeyLen, ucDataBuffer, uiOADataLen, ELM_REPLACE_DO)))
		{
			goto Exit;
		}
	}

Exit:

	if( m_pSCache)
	{
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}

	if( pSCache)
	{
		ScaReleaseCache( pSCache, FALSE);
	}

	if( pPrevSCache)
	{
		ScaReleaseCache( pPrevSCache, FALSE);
	}

	if( pNextSCache)
	{
		ScaReleaseCache( pNextSCache, FALSE);
	}

	releaseBlocks( TRUE);
	return( rc);
}

/***************************************************************************
Desc: Method to move the read point in an entry to a particular position
		within the entry.  This method will move to a previous or a later
		position.
****************************************************************************/
RCODE F_Btree::btSetReadPosition(
	FLMBYTE *				pucKey,
	FLMUINT					uiKeyLen,
	FLMUINT					uiPosition)
{
	RCODE					rc = NE_XFLM_OK;
	FLMBYTE *			pucEntry;
	F_BLK_HDR *			pBlkHdr = NULL;
	FLMUINT32			ui32BlkAddr;
	FLMBOOL				bLastElement = FALSE;

	if( !m_bOpened || !m_bSetupForRead)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	// We cannot position to a point beyond the end of the current entry.
	
	if( uiPosition >= m_uiOADataLength)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
		goto Exit;
	}

	// If the transaction Id or the Block Change Count has changed,
	// we must re-sync ourselves.
	
	if( (m_ui64CurrTransID != m_pDb->m_ui64CurrTransID) ||
		 (m_uiBlkChangeCnt != m_pDb->m_uiBlkChangeCnt))
	{

		// Test to see if we really need to re-synch...
		
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
										m_ui32CurBlkAddr, NULL, &m_pSCache)))
		{
			goto Exit;
		}

		if( m_pSCache->m_pBlkHdr->ui64TransID != m_ui64LastBlkTransId ||
				( m_pDb->m_eTransType == XFLM_UPDATE_TRANS &&
					m_pDb->m_ui64CurrTransID == m_pSCache->m_pBlkHdr->ui64TransID))
		{
			// We must call btLocateEntry so we can re-initialize the read.
			
			if( !m_bFirstRead)
			{
				if( RC_BAD( rc = btLocateEntry(
									pucKey, uiKeyLen, &uiKeyLen, XFLM_EXACT)))
				{
					goto Exit;
				}

				// Will need a new version of this block.
				
				ScaReleaseCache( m_pSCache, FALSE);
				m_pSCache = NULL;
			}
			else
			{
				rc = RC_SET(NE_XFLM_BTREE_BAD_STATE);
				goto Exit;
			}
		}
	}

	// The easiest case to handle is when we want to position within the
	// current entry.  We should not have to worry about the data only blocks
	// because the m_uiDataLength and m_uiDataRemaining are being set correctly
	// in setupReadState (via btLocateEntry, btNextEntry, btPrevEntry,
	// btFirstEntry and btLastEntry) which is always called before this method is
	// called.

	if( (uiPosition < (m_uiOffsetAtStart + m_uiDataLength)) &&
			(uiPosition >= m_uiOffsetAtStart))
	{
		m_uiDataRemaining = m_uiDataLength - (uiPosition - m_uiOffsetAtStart);
		m_uiOADataRemaining = m_uiOADataLength - uiPosition;
		goto Exit;
	}

	// Get the current block.  It is either a DO or a Btree block.
	
	if( m_pSCache == NULL)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			m_ui32CurBlkAddr, NULL, &m_pSCache)))
		{
			goto Exit;
		}
	}

	// The next case is when the new position is in a *previous* entry, possibly
	// a previous block.
	
	while( uiPosition < m_uiOffsetAtStart)
	{
		pBlkHdr = m_pSCache->m_pBlkHdr;

		// Are we dealing with DataOnly blocks?
		
		if( m_bDataOnlyBlock)
		{
			ui32BlkAddr = pBlkHdr->ui32PrevBlkInChain;
			flmAssert( ui32BlkAddr);

			ScaReleaseCache( m_pSCache, FALSE);
			m_pSCache = NULL;

			if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
				ui32BlkAddr, NULL, &m_pSCache)))
			{
				goto Exit;
			}
			
			m_ui32CurBlkAddr = ui32BlkAddr;
			pBlkHdr = m_pSCache->m_pBlkHdr;

			m_uiDataLength = m_uiBlockSize - pBlkHdr->ui16BlkBytesAvail -
										sizeofDOBlkHdr((F_BLK_HDR *)pBlkHdr);
										
			if( !pBlkHdr->ui32PrevBlkInChain)
			{
				FLMBYTE *		pucPtr = (FLMBYTE *)pBlkHdr + sizeofDOBlkHdr( (F_BLK_HDR *)pBlkHdr);
				FLMUINT16		ui16KeyLen = FB2UW( pucPtr);

				// We need to adjust for the key in the first block.
				
				m_uiDataLength -= ui16KeyLen;
			}
			
			// Decrement by the size of the current data
			
			m_uiOffsetAtStart -= m_uiDataLength;
		}
		else
		{
			// Backup to the previous element. This may or may not get
			// another block
			
			if( RC_BAD( rc = backupToPrevElement( FALSE)))
			{
				goto Exit;
			}

			pucEntry = BtEntry( (FLMBYTE *)m_pSCache->m_pBlkHdr, m_uiCurOffset);

			// Make sure we are still looking at the same key etc.
			
			if( !checkContinuedEntry(
				pucKey, uiKeyLen, &bLastElement, pucEntry,
				getBlkType( (FLMBYTE *)m_pSCache->m_pBlkHdr)))
			{
				// Should always match at this point!
				
				rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
				goto Exit;
			}

			m_uiDataLength = btGetEntryDataLength( pucEntry, NULL, NULL, NULL);
			m_uiOffsetAtStart -= m_uiDataLength;
		}
	}

	// Did we find the block?
	
	if( (uiPosition < (m_uiOffsetAtStart + m_uiDataLength)) &&
			(uiPosition >= m_uiOffsetAtStart))
	{
		m_uiDataRemaining = m_uiDataLength - (uiPosition - m_uiOffsetAtStart);
		m_uiOADataRemaining = m_uiOADataLength - uiPosition;
		goto Exit;
	}

	// Finally, we realize that the new position is beyond the current entry.
	
	while( uiPosition >= (m_uiOffsetAtStart + m_uiDataLength))
	{
		flmAssert( m_uiDataLength + m_uiOffsetAtStart <= m_uiOADataLength);

		// Get the next entry.
		
		pBlkHdr = m_pSCache->m_pBlkHdr;

		// Are we dealing with DataOnly blocks?
		
		if( m_bDataOnlyBlock)
		{
			ui32BlkAddr = pBlkHdr->ui32NextBlkInChain;
			flmAssert( ui32BlkAddr);

			ScaReleaseCache( m_pSCache, FALSE);
			m_pSCache = NULL;

			if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
				ui32BlkAddr, NULL, &m_pSCache)))
			{
				goto Exit;
			}
			
			m_ui32CurBlkAddr = ui32BlkAddr;
			pBlkHdr = m_pSCache->m_pBlkHdr;

			// Increment by the size of the previous data.  Note that in this
			// case, we do not have to be concerned about the key in the first
			// DO block since we will never move forward to it.
			
			m_uiOffsetAtStart += m_uiDataLength;
			m_uiDataLength = m_uiBlockSize - pBlkHdr->ui16BlkBytesAvail -
														sizeofDOBlkHdr((F_BLK_HDR *)pBlkHdr);
		}
		else
		{
			// Advance to the next element. This may or may not get another block.
			// Be sure we do not advance the stack since we do not have one.
			
			if( RC_BAD( rc = advanceToNextElement( FALSE)))
			{
				goto Exit;
			}

			pucEntry = BtEntry( (FLMBYTE *)m_pSCache->m_pBlkHdr, m_uiCurOffset);

			// Make sure we are still looking at the same key etc.
			
			if( !checkContinuedEntry(
				pucKey, uiKeyLen, &bLastElement, pucEntry,
				getBlkType( (FLMBYTE *)m_pSCache->m_pBlkHdr)))
			{
				// Should always match at this point!
				
				rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
				goto Exit;
			}

			// Get the data length of the current entry.
			
			m_uiOffsetAtStart += m_uiDataLength;
			m_uiDataLength = btGetEntryDataLength( pucEntry, NULL, NULL, NULL);
		}
	}

	// Did we find the block?  If we still don't find it, then we
	// have a big problem.
	
	if( (uiPosition >= (m_uiOffsetAtStart + m_uiDataLength)) ||
			(uiPosition < m_uiOffsetAtStart))
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
		goto Exit;
	}

	m_uiDataRemaining = m_uiDataLength - (uiPosition - m_uiOffsetAtStart);
	m_uiOADataRemaining = m_uiOADataLength - uiPosition;
	updateTransInfo( m_pSCache->m_pBlkHdr->ui64TransID,
						  m_pSCache->m_ui64HighTransID);

Exit:

	if( m_pSCache)
	{
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}

	releaseBlocks( FALSE);
	return( rc);
}

/***************************************************************************
Desc:
****************************************************************************/
RCODE F_Btree::btGetReadPosition(
	FLMBYTE *			pucKey,
	FLMUINT				uiKeyLen,
	FLMUINT *			puiPosition)
{
	RCODE					rc = NE_XFLM_OK;

	if( !m_bOpened || !m_bSetupForRead)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_BAD_STATE);
		goto Exit;
	}

	flmAssert( puiPosition);

	// If the transaction ID or the block change count has changed,
	// we must re-sync ourselves.
	
	if( !m_bTempDb &&
		 ((m_ui64CurrTransID != m_pDb->m_ui64CurrTransID) ||
		  (m_uiBlkChangeCnt != m_pDb->m_uiBlkChangeCnt)))
	{
		// Test to see if we really need to re-sync ...

		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
										m_ui32CurBlkAddr, NULL, &m_pSCache)))
		{
			goto Exit;
		}

		if( m_pSCache->m_pBlkHdr->ui64TransID != m_ui64LastBlkTransId ||
				(m_pDb->m_eTransType == XFLM_UPDATE_TRANS &&
					m_pDb->m_ui64CurrTransID == m_pSCache->m_pBlkHdr->ui64TransID))
		{
			// We must call btLocateEntry so we can re-initialize the read
			
			if( !m_bFirstRead)
			{
				if( RC_BAD( rc = btLocateEntry(
									pucKey, uiKeyLen, &uiKeyLen, XFLM_EXACT)))
				{
					goto Exit;
				}

				// Will need a new version of this block.
				
				ScaReleaseCache( m_pSCache, FALSE);
				m_pSCache = NULL;
			}
			else
			{
				rc = RC_SET(NE_XFLM_BTREE_BAD_STATE);
				goto Exit;
			}
		}
	}

	*puiPosition = m_uiOffsetAtStart + (m_uiDataLength - m_uiDataRemaining);

	if( m_pSCache)
	{
		updateTransInfo( m_pSCache->m_pBlkHdr->ui64TransID,
							  m_pSCache->m_ui64HighTransID);
	}

Exit:

	if( m_pSCache)
	{
		ScaReleaseCache( m_pSCache, FALSE);
		m_pSCache = NULL;
	}

	releaseBlocks( FALSE);
	return( rc);
}

/***************************************************************************
Desc: Performs a consistancy check on the BTree
		NOTE: Must be performed inside of a read transaction!
****************************************************************************/
RCODE F_Btree::btCheck(
	BTREE_ERR_STRUCT *	pErrStruct)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT32			ui32NextBlkAddr = 0;
	FLMUINT32			ui32NextLevelBlkAddr = 0;
	FLMUINT32			ui32ChildBlkAddr = 0;
	FLMUINT32			ui32DOBlkAddr = 0;
	FLMUINT				uiNumKeys;
	const FLMBYTE *	pucPrevKey;
	FLMUINT				uiPrevKeySize;
	const FLMBYTE *	pucCurKey;
	FLMUINT				uiCurKeySize;
	F_CachedBlock *	pCurrentBlk = NULL;
	F_CachedBlock *	pPrevSCache = NULL;
	FLMBYTE *			pBlk = NULL;
	FLMBYTE *			pucEntry = NULL;
	FLMBYTE *			pucPrevEntry = NULL;
	F_CachedBlock *	pChildBlk = NULL;
	FLMUINT16 *			puiOffsetArray;
	BTREE_ERR_STRUCT	localErrStruct;
	FLMINT				iCmpResult;
	FLMUINT				uiOADataLength = 0;

	// Verify the Txn type
	
	if( m_pDb->m_eTransType == XFLM_NO_TRANS && !m_bTempDb)
	{
		rc = RC_SET_AND_ASSERT( NE_XFLM_NO_TRANS_ACTIVE);
		goto Exit;
	}

	// Initial setup...
	
	ui32NextLevelBlkAddr = (FLMUINT32)m_pLFile->uiRootBlk;
	f_memset( &localErrStruct, 0, sizeof( localErrStruct));
	localErrStruct.uiBlockSize = m_uiBlockSize;

	// While there's a next level....
	
	while( ui32NextLevelBlkAddr)
	{
		localErrStruct.uiLevels++;
		ui32NextBlkAddr = ui32NextLevelBlkAddr;

		// Update uiNextLevelBlkAddr
		
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			ui32NextBlkAddr, NULL, &pCurrentBlk)))
		{
			localErrStruct.type = SCA_GET_BLOCK_FAILED;
			f_sprintf( localErrStruct.szMsg, 
				"Failed to get block at %X", ui32NextBlkAddr);
			goto Exit;
		}
		
		pBlk = (FLMBYTE *)pCurrentBlk->m_pBlkHdr;
		puiOffsetArray = BtOffsetArray( pBlk, 0);
		
		if( (getBlkType( pBlk) == BT_LEAF) || (getBlkType( pBlk) == BT_LEAF_DATA))
		{
			ui32NextLevelBlkAddr = 0;
		}
		else
		{
			pucEntry = BtEntry( pBlk, 0);

			// The child block address is the first part of the entry
			
			ui32NextLevelBlkAddr = bteGetBlkAddr( pucEntry);
		}

		if( pPrevSCache)
		{
			ScaReleaseCache( pPrevSCache, FALSE);
			pPrevSCache = NULL;
		}

		// While there's another block on this level...
		
		while( ui32NextBlkAddr) 
		{
			// This loop assumes that pCurrentBlk and pBlk are already initialized.
			
			localErrStruct.uiBlocksChecked++;
			localErrStruct.uiAvgFreeSpace =
				(localErrStruct.uiAvgFreeSpace * (localErrStruct.uiBlocksChecked - 1) / 
					localErrStruct.uiBlocksChecked) +
				(getBlkAvailSpace(pBlk) / localErrStruct.uiBlocksChecked);
			localErrStruct.ui64FreeSpace += getBlkAvailSpace(pBlk);

			localErrStruct.LevelStats[ localErrStruct.uiLevels - 1].uiBlkCnt++;
			localErrStruct.LevelStats[ localErrStruct.uiLevels - 1].uiBytesUsed +=
										(m_uiBlockSize - getBlkAvailSpace(pBlk));

			uiNumKeys = ((F_BTREE_BLK_HDR *)pBlk)->ui16NumKeys;

			// VISIT:  Verify the block header fields
			/*
				ui32PrevBlkInChain =
				ui32BlkCRC =
				ui16BlkBytesAvail < ?
				ui8BlkLevel??
				ui8BlkIsRoot??
			*/

			// Verify that the keys are in order...
			// Make sure that we check the keys between blocks as well.
			
			if( pPrevSCache)
			{
				pucEntry = BtLastEntry( (FLMBYTE *)pPrevSCache->m_pBlkHdr);
				uiPrevKeySize = getEntryKeyLength( pucEntry, getBlkType(
							(FLMBYTE *)pPrevSCache->m_pBlkHdr), &pucPrevKey);
			}
			else
			{
				pucEntry = BtEntry( pBlk, 0);
				uiPrevKeySize = getEntryKeyLength( pucEntry, getBlkType( pBlk),
															  &pucPrevKey);
				if( getBlkType(pBlk) == BT_LEAF_DATA)
				{
					if( bteFirstElementFlag( pucEntry))
					{
						localErrStruct.LevelStats[ 
								localErrStruct.uiLevels - 1].uiFirstKeyCnt++;
					}
				}
				else
				{
					// Everything else is a first key.
					
					localErrStruct.LevelStats[
						localErrStruct.uiLevels - 1].uiFirstKeyCnt++;
				}
			}
			
			for( FLMUINT uiLoop = (pPrevSCache ? 0: 1); 
				  uiLoop < uiNumKeys; uiLoop++)
			{
				pucPrevEntry = pucEntry;
				pucEntry = BtEntry( pBlk, uiLoop);

				if( getBlkType(pBlk) == BT_LEAF_DATA)
				{
					if( bteFirstElementFlag( pucEntry))
					{
						localErrStruct.LevelStats[ 
							localErrStruct.uiLevels - 1].uiFirstKeyCnt++;
					}
				}
				else
				{
					// Everything else is a first key.
					
					localErrStruct.LevelStats[
						localErrStruct.uiLevels - 1].uiFirstKeyCnt++;
				}

				uiCurKeySize = getEntryKeyLength( pucEntry,
										getBlkType( pBlk), &pucCurKey);

				// The last key in the last block of each level is an infinity marker
				// It must have a 0 keylength and if it's a leaf node, a 0 datalength.
				
				if( (uiLoop == uiNumKeys - 1) &&
					(((F_BLK_HDR *)pBlk)->ui32NextBlkInChain == 0))
				{
					// If the key size is not 0, or we're a leaf block, and the
					// data size is not 0 ...
					
					if( (uiCurKeySize != 0) ||
						  (((getBlkType( pBlk) == BT_LEAF_DATA)) &&
							 (btGetEntryDataLength( pucEntry, NULL, NULL, NULL) > 0)))
					{
						localErrStruct.type = INFINITY_MARKER;
						localErrStruct.uiBlkAddr = ((F_BLK_HDR *)pBlk)->ui32BlkAddr;
						f_sprintf( localErrStruct.szMsg, "Invalid Infinity Marker %ul", uiLoop);
						rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
						goto Exit;
					}
				}
				else
				{
					// Do a comparison of the previous and current keys ...
					
					if( RC_BAD( rc = compareKeys( pucPrevKey, uiPrevKeySize,
						pucCurKey, uiCurKeySize, &iCmpResult)))
					{
						goto Exit;
					}
					
					if( iCmpResult > 0)
					{
						localErrStruct.type = KEY_ORDER;
						localErrStruct.uiBlkAddr = ((F_BLK_HDR *)pBlk)->ui32BlkAddr;
						f_sprintf( localErrStruct.szMsg, "Key Number %ul", uiLoop);
						rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
						goto Exit;
					}

					if( getBlkType(pBlk) == BT_LEAF_DATA)
					{
						if( iCmpResult < 0)
						{
							flmAssert( *pucEntry & BTE_FLAG_FIRST_ELEMENT);
						}
						else if( iCmpResult == 0)
						{
							flmAssert( (*pucEntry & BTE_FLAG_FIRST_ELEMENT) == 0);
							flmAssert( (*pucPrevEntry & BTE_FLAG_LAST_ELEMENT) == 0);
						}
					}
				}

				pucPrevKey = pucCurKey;
				uiPrevKeySize = uiCurKeySize;
			}

			localErrStruct.uiNumKeys += uiNumKeys;
			localErrStruct.LevelStats[ 
							localErrStruct.uiLevels - 1].uiKeyCnt += uiNumKeys;

			// If this is a leaf block, check for any pointers to data-only
			// blocks.  Verify the blocks...
			
			if( getBlkType( pBlk) == BT_LEAF || 
				 getBlkType( pBlk) == BT_LEAF_DATA)
			{
				if( getBlkType( pBlk) == BT_LEAF_DATA)
				{
					for( FLMUINT uiLoop = 0; uiLoop < uiNumKeys; uiLoop++)
					{
						pucEntry = BtEntry( pBlk, uiLoop);
						
						if( bteDataBlockFlag( pucEntry))
						{
							FLMBYTE	ucDOBlkAddr[ 4];

							if( RC_BAD( rc = btGetEntryData( pucEntry, 
								&ucDOBlkAddr[ 0], 4, NULL)))
							{
								RC_UNEXPECTED_ASSERT( rc);
								localErrStruct.type = CATASTROPHIC_FAILURE;
								localErrStruct.uiBlkAddr = ((F_BLK_HDR *)pBlk)->ui32BlkAddr;
								f_sprintf( localErrStruct.szMsg, 
										"getEntryData couldn't get the DO blk addr.");
								goto Exit;
							}

							ui32DOBlkAddr = bteGetBlkAddr( (FLMBYTE *)&ucDOBlkAddr[ 0]);

							// Verify that there is an OverallDataLength field

							if( bteOADataLenFlag( pucEntry) == 0)
							{
								localErrStruct.type = MISSING_OVERALL_DATA_LENGTH;
								localErrStruct.uiBlkAddr = ((F_BLK_HDR *)pBlk)->ui32BlkAddr;
								f_sprintf( localErrStruct.szMsg, 
									"OverallDataLength field is missing");
							}
							else
							{
								if( bteKeyLenFlag( pucEntry))
								{
									uiOADataLength = FB2UD( pucEntry + 4);
								}
								else
								{
									uiOADataLength = FB2UD( pucEntry + 3);
								}
							}

							if( RC_BAD( rc = verifyDOBlkChain( ui32DOBlkAddr,
								uiOADataLength , &localErrStruct)))
							{
								goto Exit;
							}
						}
					}
				}
			}
			else
			{
				// This is a non-leaf block, verify that blocks exist for all
				// the child block addresses

				// NOTE: Also need to somehow verify that no two elements have the
				// same child block address...				
					
				for( FLMUINT uiLoop = 0; uiLoop < uiNumKeys; uiLoop++)
				{
					pucEntry = BtEntry( pBlk, uiLoop);
					ui32ChildBlkAddr = bteGetBlkAddr( pucEntry);
					if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
						ui32ChildBlkAddr, NULL, &pChildBlk)))
					{
						localErrStruct.type = SCA_GET_BLOCK_FAILED;
						f_sprintf( localErrStruct.szMsg, "Failed to get block at %X", 
							ui32ChildBlkAddr);
						goto Exit;
					}

					ScaReleaseCache( pChildBlk, FALSE);
				}
			}

			// Release the current block and get the next one
			
			ui32NextBlkAddr = ((F_BLK_HDR *)pBlk)->ui32NextBlkInChain;
			
			if( pPrevSCache)
			{
				ScaReleaseCache( pPrevSCache, FALSE);
				pPrevSCache = NULL;
			}
			
			pPrevSCache = pCurrentBlk;
			pCurrentBlk = NULL;
			
			if( ui32NextBlkAddr)
			{
				if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
												ui32NextBlkAddr, NULL, &pCurrentBlk)))
				{
					localErrStruct.type = SCA_GET_BLOCK_FAILED;
					f_sprintf( localErrStruct.szMsg, 
						"Failed to get block at %X", ui32ChildBlkAddr);
					goto Exit;
				}
				
				pBlk = (FLMBYTE *)pCurrentBlk->m_pBlkHdr;
			}
		}
	}

	if( m_bCounts)
	{
		if( RC_BAD( rc = verifyCounts( &localErrStruct)))
		{
			goto Exit;
		}
	}

Exit:

	if( pPrevSCache)
	{
		ScaReleaseCache( pPrevSCache, FALSE);
	}

	if( pCurrentBlk)
	{
		ScaReleaseCache( pCurrentBlk, FALSE);
	}
	
	f_memcpy( pErrStruct, &localErrStruct, sizeof( localErrStruct));
	return( rc);
}

/***************************************************************************
Desc: Performs an integrity check on a chain of data-only blocks.  Should
		only be called from btCheck().  Note that unlike btCheck(),
		errStruct CANNOT be NULL here.
****************************************************************************/
RCODE F_Btree::verifyDOBlkChain(
	FLMUINT					uiDOAddr,			// Address of first block in chain
	FLMUINT					uiDataLength,		// The length of the entire entry
	BTREE_ERR_STRUCT *	errStruct)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT				uiRunningLength = 0; // A running total of the DataLength fields
														// for all of the blocks in this chain
	F_CachedBlock *	pCurrentBlk = NULL;
	FLMUINT32			ui32NextAddr = (FLMUINT32)uiDOAddr;
	FLMBYTE *			pBlk;
	FLMUINT				uiDataSize;

	while( ui32NextAddr)
	{
		errStruct->LevelStats[ errStruct->uiLevels - 1].uiDOBlkCnt++;
		
		// Get the next block
		
		if( RC_BAD( m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
			ui32NextAddr, NULL, &pCurrentBlk)))
		{
			errStruct->type = SCA_GET_BLOCK_FAILED;
			f_sprintf( errStruct->szMsg, "Failed to get block at %X", uiDOAddr);
			goto Exit;
		}
		
		pBlk = (FLMBYTE *)pCurrentBlk->m_pBlkHdr;

		// Verify that it's really a DO Block
		
		if( getBlkType( pBlk) != BT_DATA_ONLY)
		{
			rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
			errStruct->type = NOT_DATA_ONLY_BLOCK;
			goto Exit;
		}

		// Update counts info in errStruct
		
		errStruct->LevelStats[ errStruct->uiLevels - 1].uiDOBytesUsed +=
						m_uiBlockSize - pCurrentBlk->m_pBlkHdr->ui16BlkBytesAvail;
						
		// Update the data length running total
		
		uiDataSize = m_uiBlockSize - sizeofDOBlkHdr( (F_BLK_HDR *)pBlk) -
										((F_BLK_HDR *)pBlk)->ui16BlkBytesAvail;
										
		if( ((F_BLK_HDR *)pBlk)->ui32PrevBlkInChain == 0)
		{
			FLMBYTE *		pucPtr = pBlk + sizeofDOBlkHdr( (F_BLK_HDR *)pBlk);
			FLMUINT16		ui16KeyLen = FB2UW( pucPtr);
			
			uiDataSize -= (ui16KeyLen + 2);
		}
		
		uiRunningLength += uiDataSize;

		// Update ui32nextAddr
		
		ui32NextAddr = ((F_BLK_HDR *)pBlk)->ui32NextBlkInChain;

		// Release it when we no longer need it.
		
		ScaReleaseCache( pCurrentBlk, FALSE);
		pCurrentBlk = NULL;
	}

	// Check the calculated overall length vs. uiDataLength
	
	if( uiRunningLength != uiDataLength)
	{
		errStruct->type = BAD_DO_BLOCK_LENGTHS;
		rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
		goto Exit;
	}

Exit:

	if( pCurrentBlk)
	{
		ScaReleaseCache( pCurrentBlk, FALSE);
	}

	if( rc == NE_XFLM_BTREE_ERROR)
	{
		f_sprintf( errStruct->szMsg, "Corrupt DO chain starting at %X", uiDOAddr);
	}

	return( NE_XFLM_OK);
}

/***************************************************************************
Desc:	Method to check the counts in a database with counts.
****************************************************************************/
RCODE F_Btree::verifyCounts(
	BTREE_ERR_STRUCT *	pErrStruct)
{
	RCODE					rc = NE_XFLM_OK;
	FLMUINT				uiNextLevelBlkAddr;
	FLMUINT				uiNextBlkAddr;
	FLMUINT				uiChildBlkAddr;
	F_CachedBlock *	pCurrentBlk = NULL;
	F_CachedBlock *	pChildBlk = NULL;
	FLMBYTE *			pucEntry;
	FLMUINT				uiNumKeys;
	FLMUINT				uiEntryNum;
	FLMUINT				uiParentCounts;
	FLMUINT				uiChildCounts;
	FLMBYTE *			pBlk;
	FLMBOOL				bDone = FALSE;

	flmAssert( m_bCounts);

	// Repeat at each level, starting at the root.
	
	uiNextLevelBlkAddr = m_pLFile->uiRootBlk;

	while( uiNextLevelBlkAddr)
	{
		if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock(
			m_pDb, m_pLFile, uiNextLevelBlkAddr, NULL, &pCurrentBlk)))
		{
			goto Exit;
		}

		if( pCurrentBlk->m_pBlkHdr->ui8BlkType != BT_NON_LEAF_COUNTS)
		{
			ScaReleaseCache( pCurrentBlk, FALSE);
			pCurrentBlk = NULL;
			break;
		}

		pucEntry = BtEntry( (FLMBYTE *)pCurrentBlk->m_pBlkHdr, 0);
		uiNextLevelBlkAddr = bteGetBlkAddr( pucEntry);

		// For every entry in the block, and for every block on this level,
		// check that the counts match the actual counts in the corresponding
		// child block.
		
		bDone = FALSE;
		while( !bDone)
		{
			uiNumKeys = ((F_BTREE_BLK_HDR *)pCurrentBlk->m_pBlkHdr)->ui16NumKeys;
			pBlk = (FLMBYTE *)pCurrentBlk->m_pBlkHdr;

			// Now check every entry in this block.
			
			for( uiEntryNum = 0; uiEntryNum < uiNumKeys; uiEntryNum++)
			{
				pucEntry = BtEntry( pBlk, uiEntryNum);
				uiChildBlkAddr = bteGetBlkAddr( pucEntry);

				pucEntry += 4;
				uiParentCounts = FB2UD( pucEntry);

				if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock( m_pDb, m_pLFile,
											uiChildBlkAddr, NULL, &pChildBlk)))
				{
					goto Exit;
				}

				uiChildCounts = countKeys( (FLMBYTE *)pChildBlk->m_pBlkHdr);

				if( uiChildCounts != uiParentCounts)
				{
					pErrStruct->type = BAD_COUNTS;
					pErrStruct->uiBlkAddr = pChildBlk->m_pBlkHdr->ui32BlkAddr;
					f_sprintf(
						pErrStruct->szMsg,
						"Counts do not match.  Expected %d, got %d",
						uiParentCounts, uiChildCounts);
					rc = RC_SET_AND_ASSERT( NE_XFLM_BTREE_ERROR);
					goto Exit;
				}

				ScaReleaseCache( pChildBlk, FALSE);
				pChildBlk = NULL;
			}

			// Now get the next block at this level.
			
			uiNextBlkAddr = pCurrentBlk->m_pBlkHdr->ui32NextBlkInChain;
			ScaReleaseCache( pCurrentBlk, FALSE);
			pCurrentBlk = NULL;

			if( uiNextBlkAddr == 0)
			{
				bDone = TRUE;
			}
			else
			{
				if( RC_BAD( rc = m_pDb->m_pDatabase->getBlock(
					m_pDb, m_pLFile, uiNextBlkAddr, NULL, &pCurrentBlk)))
				{
					goto Exit;
				}
			}
		}
	}

Exit:

	if( pCurrentBlk)
	{
		ScaReleaseCache( pCurrentBlk, FALSE);
	}

	if( pChildBlk)
	{
		ScaReleaseCache( pChildBlk, FALSE);
	}
	
	return( rc);
}
