//-------------------------------------------------------------------------
// Desc:	B-tree searching.
// Tabs:	3
//
//		Copyright (c) 1990-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: fssearch.cpp 12321 2006-01-19 15:55:00 -0700 (Thu, 19 Jan 2006) dsanders $
//-------------------------------------------------------------------------

#include "flaimsys.h"

FSTATIC FLMUINT FSKeyCmp(
	BTSK *			pStack,
	FLMBYTE *		key,
	FLMUINT			uiKeyLen,
	FLMUINT			drnDomain);

/****************************************************************************
Desc:	Search the b-tree for a matching key.
****************************************************************************/
RCODE FSBtSearch(
	FDB *			pDb,
	LFILE *		pLFile,
	BTSK **		pStackRV,
	FLMBYTE *	key,
	FLMUINT		keyLen,
	FLMUINT		dinDomain)
{
	RCODE			rc = FERR_OK;
	BTSK *		pStack = *pStackRV;
	FLMBYTE *	pKeyBuf = pStack->pKeyBuf;
	FLMUINT		uiBlkAddr;
	FLMUINT		uiKeyBufSize;
	LFILE			TmpLFile;

	uiKeyBufSize = (pLFile->uiLfType == LF_INDEX) ? MAX_KEY_SIZ : DIN_KEY_SIZ;

	// Get the correct root block specified in the LFILE.

	if (RC_BAD( rc = FSGetRootBlock( pDb, &pLFile, &TmpLFile, pStack)))
	{
		if (rc == FERR_NO_ROOT_BLOCK)
		{
			flmAssert( pLFile->uiRootBlk == BT_END);
			rc = FERR_OK;
		}

		goto Exit;
	}

	// MAIN LOOP Read each block going down the b-tree. Save state
	// information in the pStack[].

	for (;;)
	{
		pStack->uiFlags = FULL_STACK;
		pStack->uiKeyBufSize = uiKeyBufSize;
		
		if (pStack->uiBlkType != BHT_NON_LEAF_DATA)
		{
			rc = FSBtScan( pStack, key, keyLen, dinDomain);
		}
		else
		{
			rc = FSBtScanNonLeafData( pStack, 
						keyLen == 1 
							? (FLMUINT) *key 
							: (FLMUINT) f_bigEndianToUINT32( key));
		}

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

		if (!pStack->uiLevel)
		{
			// Leaf level - we are done.
			
			break;
		}

		uiBlkAddr = FSChildBlkAddr( pStack);

		pStack++;
		pStack->pKeyBuf = pKeyBuf;

		if (RC_BAD( rc = FSGetBlock( pDb, pLFile, uiBlkAddr, pStack)))
		{
			goto Exit;
		}
	}

	*pStackRV = pStack;

Exit:

	return (rc);
}

/****************************************************************************
Desc:	Search the right-most end of the b-tree.
****************************************************************************/
RCODE FSBtSearchEnd(
	FDB *			pDb,
	LFILE *		pLFile,		// Logical file definition
	BTSK **		pStackRV,	// Stack of variables for each level
	FLMUINT		uiDrn)		// Used to position and setup for update
{
	RCODE			rc = FERR_OK;
	BTSK *		pStack = *pStackRV;
	FLMBYTE *	pKeyBuf = pStack->pKeyBuf;
	FLMBYTE		key[ DIN_KEY_SIZ + 4];
	FLMUINT		uiBlkAddr;
	LFILE			TmpLFile;

	// Get the correct root block specified in the LFILE.

	if (RC_BAD( rc = FSGetRootBlock( pDb, &pLFile, &TmpLFile, pStack)))
	{
		if (rc == FERR_NO_ROOT_BLOCK)
		{
			flmAssert( pLFile->uiRootBlk == BT_END);
			rc = FERR_OK;
		}

		goto Exit;
	}

	f_UINT32ToBigEndian( (FLMUINT32)uiDrn, key);
	
	for (;;)
	{
		pStack->uiFlags = FULL_STACK;
		pStack->uiKeyBufSize = DIN_KEY_SIZ;

		// Remove all scanning from non-leaf data blocks (both formats).

		if (pStack->uiLevel)
		{
			pStack->uiCurElm = pStack->uiBlkEnd;	// Position past last element
			FSBtPrevElm( pDb, pLFile, pStack);		// Build full key in pKeyBuf[]
		}
		else
		{
			if (pStack->uiBlkType != BHT_NON_LEAF_DATA)
			{
				rc = FSBtScan( pStack, key, DIN_KEY_SIZ, 0);
			}
			else
			{
				rc = FSBtScanNonLeafData( pStack, uiDrn);
			}

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

		if (!pStack->uiLevel)
		{
			// Leaf level - we are done.
			
			break;
		}

		uiBlkAddr = FSChildBlkAddr( pStack);
		pStack++;
		pStack->pKeyBuf = pKeyBuf;

		if (RC_BAD( rc = FSGetBlock( pDb, pLFile, uiBlkAddr, pStack)))
		{
			goto Exit;
		}
	}

	*pStackRV = pStack;

Exit:

	return (rc);
}

/****************************************************************************
Desc: Returns the root block of a passed-in LFILE
****************************************************************************/
RCODE FSGetRootBlock(
	FDB *			pDb,
	LFILE **		ppLFile,
	LFILE *		pTmpLFile,
	BTSK *		pStack)
{
	RCODE			rc = FERR_OK;
	LFILE *		pLFile = *ppLFile;
	FLMUINT		uiBlkAddr;
	FLMBOOL		bRereadLFH = FALSE;

	// Make Sure this is the correct root block in the LFILE area. If not
	// then read in the LFH structure and try again. It would be nice to
	// have a routine that reads only root blocks. DSS: Added check for
	// uiBlkAddr >= pDb->Loghdr.uiLogicalEOF because the pLFile could have
	// a root block address of an aborted update transaction where the root
	// block has not yet been fixed up by the aborting transaction (in a
	// shared environment).

	if (((uiBlkAddr = pLFile->uiRootBlk) == BT_END) ||
		 (uiBlkAddr >= pDb->LogHdr.uiLogicalEOF))
	{
		bRereadLFH = TRUE;
	}
	else if (RC_BAD( rc = FSGetBlock( pDb, pLFile, uiBlkAddr, pStack)))
	{
		if (rc == FERR_DATA_ERROR || (rc == FERR_OLD_VIEW && !pDb->uiKilledTime))
		{
			bRereadLFH = TRUE;
			pStack->uiBlkAddr = BT_END;
		}
		else
		{
			goto Exit;
		}
	}
	else
	{
		// Check for valid root block - Root Flag and Logical file number		
		
		FLMBYTE *		pBlk = pStack->pBlk;

		if (!(BH_IS_ROOT_BLK( pBlk)) ||
			 (pLFile->uiLfNum != FB2UW( &pBlk[BH_LOG_FILE_NUM])))
		{
			bRereadLFH = TRUE;
			FSReleaseBlock( pStack, FALSE);
			pStack->uiBlkAddr = BT_END;
		}
	}

	// Reread the LFH from disk if we do not have the root block

	if (bRereadLFH)
	{

		// If we are in a read transaction, copy the LFILE structure so
		// that we don't mess up a thread that may be doing an update.  

		if (flmGetDbTransType( pDb) == FLM_READ_TRANS)
		{
			f_memcpy( pTmpLFile, pLFile, sizeof(LFILE));
			pLFile = pTmpLFile;
		}

		if (RC_BAD( rc = flmLFileRead( pDb, pLFile)))
		{
			goto Exit;
		}

		// If there is no root block, return right away

		if ((uiBlkAddr = pLFile->uiRootBlk) == BT_END)
		{

			// The caller of FSGetRootBlock is expected to check for and
			// handle FERR_NO_ROOT_BLOCK. It should NEVER be returned to the
			// application. NOTE: Checking for BT_END_OF_DATA will not work
			// in every case to check for no root block because it is not
			// always initialized before calling FSGetRootBlock, so it could
			// have garbage in it if we don't end up going through this code
			// path.

			rc = RC_SET( FERR_NO_ROOT_BLOCK);
			pStack->uiCmpStatus = BT_END_OF_DATA;
			pStack->uiBlkAddr = BT_END;
			goto Exit;
		}

		if (RC_BAD( rc = FSGetBlock( pDb, pLFile, uiBlkAddr, pStack)))
		{
			goto Exit;
		}
	}

Exit:

	*ppLFile = pLFile;
	return (rc);
}

/****************************************************************************
Desc:	Scan a b-tree block for a matching key at any b-tree block level.
****************************************************************************/
RCODE FSBtScan(
	BTSK *		pStack,				// [in/out] Stack of variables for each level
	FLMBYTE *	pSearchKey, 		// The input key to search for
	FLMUINT		uiSearchKeyLen,	// Length of the key (not null terminated)
	FLMUINT		dinDomain)			// INDEXES ONLY - lower bounds of din
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pCurElm;				// Points to the current element.
	FLMBYTE *	pBlk;					// Points to the cache block.
	FLMBYTE *	pKeyBuf;				// Points to pStack->pKeyBuf (optimization).
	FLMBYTE *	pElmKey;				// Points to the key within the element.
	FLMUINT		uiRecLen = 0;		// Length of the record portion.
	FLMUINT		uiPrevKeyCnt;		// Number left end bytes compressed
	FLMUINT		uiElmKeyLen;		// Length of the current element's key portion
	FLMUINT		uiBlkType;			// B-tree block type - Leaf or non-leaf.
	FLMUINT		uiElmOvhd;			// Number bytes overhead for element.
	FLMUINT		uiBytesMatched;	// Number of bytes matched with pSearchKey

	uiBlkType = pStack->uiBlkType;
	flmAssert( uiBlkType != BHT_NON_LEAF_DATA);

	// Initialize stack variables for possibly better performance.

	pKeyBuf = pStack->pKeyBuf;
	pBlk = pStack->pBlk;
	uiElmOvhd = pStack->uiElmOvhd;
	pStack->uiCurElm = BH_OVHD;
	pStack->uiKeyLen = pStack->uiPKC = pStack->uiPrevElmPKC = 0;
	uiBytesMatched = 0;

	for (;;)
	{
		pCurElm = &pBlk[pStack->uiCurElm];
		uiElmKeyLen = BBE_GETR_KL( pCurElm);

		// Read in RAW mode - doesn't do all bit checking

		if ((uiPrevKeyCnt = (BBE_GETR_PKC( pCurElm))) > BBE_PKC_MAX)
		{
			uiElmKeyLen += (uiPrevKeyCnt & BBE_KL_HBITS) << BBE_KL_SHIFT_BITS;
			uiPrevKeyCnt &= BBE_PKC_MAX;
		}

		// Should not have a non-zero PKC if we are on the first element of
		// a block

		if (uiPrevKeyCnt && pStack->uiCurElm == BH_OVHD)
		{
			rc = RC_SET( FERR_DATA_ERROR);
			goto Exit;
		}

		// Get the record portion length when on the leaf blocks.

		if (uiBlkType == BHT_LEAF)
		{
			uiRecLen = BBE_GET_RL( pCurElm);
		}

		pStack->uiPrevElmPKC = pStack->uiPKC;

		// The zero length key is the terminating element in a right-most
		// block.

		if ((pStack->uiKeyLen = uiPrevKeyCnt + uiElmKeyLen) == 0)
		{
			pStack->uiPrevElmPKC = f_min( uiBytesMatched, BBE_PKC_MAX);
			pStack->uiPKC = 0;
			pStack->uiCmpStatus = BT_END_OF_DATA;
			goto Exit;
		}

		// Handle special case of left-end compression maxing out.

		if (uiPrevKeyCnt == BBE_PKC_MAX && BBE_PKC_MAX < uiBytesMatched)
		{
			uiBytesMatched = BBE_PKC_MAX;
		}

		// Check out this element to see if the key matches.

		if (uiPrevKeyCnt == uiBytesMatched)
		{
			pElmKey = &pCurElm[uiElmOvhd];
			for (;;)
			{

				// All bytes of the search key are matched?

				if (uiBytesMatched == uiSearchKeyLen)
				{
					pStack->uiPKC = f_min( uiBytesMatched, BBE_PKC_MAX);

					// Build pKeyBuf with the search key because it matches.
					// Current key is either equal or greater than search key.

					if (uiSearchKeyLen < pStack->uiKeyLen)
					{
						f_memcpy( &pKeyBuf[uiSearchKeyLen], pElmKey,
									pStack->uiKeyLen - uiSearchKeyLen);
						pStack->uiCmpStatus = BT_GT_KEY;
					}
					else
					{
						if (dinDomain)
						{
							FLMBYTE*		pCurRef = pCurElm;
							if ((dinDomain - 1) < 
									FSGetDomain( &pCurRef, (FLMBYTE) uiElmOvhd))
							{

								// Keep going...

								goto Next_Element;
							}
						}

						pStack->uiCmpStatus = BT_EQ_KEY;
					}

					f_memcpy( pKeyBuf, pSearchKey, uiSearchKeyLen);
					goto Exit;
				}

				// .. else matches all the bytes in the element key.

				if (uiBytesMatched == pStack->uiKeyLen)
				{
					pStack->uiPKC = f_min( uiBytesMatched, BBE_PKC_MAX);

					// Need an outer break call here - forced to do a goto.

					goto Next_Element;
				}

				// Compare the next byte in the search key and element

				if (pSearchKey[uiBytesMatched] != *pElmKey)
				{
					break;
				}

				uiBytesMatched++;
				pElmKey++;
			}

			pStack->uiPKC = f_min( uiBytesMatched, BBE_PKC_MAX);

			// Check if we are done comparing, if so build pKeyBuf[].

			if (pSearchKey[uiBytesMatched] < *pElmKey)
			{
				if (uiBytesMatched)
				{
					f_memcpy( pKeyBuf, pSearchKey, uiBytesMatched);
				}

				f_memcpy( &pKeyBuf[uiBytesMatched], pElmKey,
							pStack->uiKeyLen - uiBytesMatched);
				pStack->uiCmpStatus = BT_GT_KEY;
				goto Exit;
			}
		}
		else if (uiPrevKeyCnt < uiBytesMatched)
		{

			// Current key > search key. Set pKeyBuf and break out.

			pStack->uiPKC = uiPrevKeyCnt;
			if (uiPrevKeyCnt)
			{
				f_memcpy( pKeyBuf, pSearchKey, uiPrevKeyCnt);
			}

			f_memcpy( &pKeyBuf[uiPrevKeyCnt], &pCurElm[uiElmOvhd], uiElmKeyLen);
			pStack->uiCmpStatus = BT_GT_KEY;
			goto Exit;
		}

		// else the key is less than the search key (uiPrevKeyCnt >
		// uiBytesMatched).

Next_Element:

		// Position to the next element

		pStack->uiCurElm += uiElmKeyLen + 
									((uiBlkType == BHT_LEAF) 
											? (BBE_KEY + uiRecLen)
											: (BNE_IS_DOMAIN( pCurElm) 
												? (BNE_DOMAIN_LEN + uiElmOvhd) 
												: uiElmOvhd));

		// Most common check first.

		if (pStack->uiCurElm < pStack->uiBlkEnd)
		{
			continue;
		}

		if (pStack->uiCurElm == pStack->uiBlkEnd)
		{

			// On the equals conditition it may be OK in some very special
			// cases.

			pStack->uiCmpStatus = BT_END_OF_DATA;
			goto Exit;
		}

		// Marched off the end of the block - something is corrupt.

		rc = RC_SET( FERR_CACHE_ERROR);
		goto Exit;
	}

Exit:

	return (rc);
}

/****************************************************************************
Desc:	Binary search into a non-leaf data record block.
****************************************************************************/
RCODE FSBtScanNonLeafData(
	BTSK *		pStack,
	FLMUINT		uiDrn)
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pBlk = pStack->pBlk;
	FLMUINT		uiLow = 0;
	FLMUINT		uiMid;
	FLMUINT		uiHigh = ((pStack->uiBlkEnd - BH_OVHD) >> 3) - 1;
	FLMUINT		uiTblSize = uiHigh;
	FLMUINT		uiCurDrn;

	pStack->uiCmpStatus = BT_GT_KEY;
	for (;;)
	{
		uiMid = (uiLow + uiHigh) >> 1;

		uiCurDrn = f_bigEndianToUINT32( &pBlk[ BH_OVHD + (uiMid << 3)]);
		if (uiCurDrn == 0)
		{

			// Special case - at the end of a rightmost block.

			pStack->uiCmpStatus = BT_EQ_KEY;
			break;
		}

		if (uiDrn == uiCurDrn)
		{

			// Remember a data record can span multiple blocks (same DRN).

			while (uiMid)
			{
				uiCurDrn = f_bigEndianToUINT32( 
					&pBlk[ BH_OVHD + ((uiMid - 1) << 3)]);
					
				if (uiDrn != uiCurDrn)
				{
					break;
				}

				uiMid--;
			}

			pStack->uiCmpStatus = BT_EQ_KEY;
			break;
		}

		// Down to one item if too high then position to next item.

		if (uiLow >= uiHigh)
		{
			if ((uiDrn > uiCurDrn) && uiMid < uiTblSize)
			{
				uiMid++;
			}
			break;
		}

		// If too high then try lower section

		if (uiDrn < uiCurDrn)
		{

			// First item too high?

			if (uiMid == 0)
			{
				break;
			}

			uiHigh = uiMid - 1;
		}
		else
		{
			// Try upper section because mid value is too low.			
			
			if (uiMid == uiTblSize)
			{
				uiMid++;
				pStack->uiCmpStatus = BT_END_OF_DATA;
				break;
			}

			uiLow = uiMid + 1;
		}
	}

	// Set curElm and the key buffer.

	pStack->uiCurElm = BH_OVHD + (uiMid << 3);
	f_UINT32ToBigEndian( (FLMUINT32)uiCurDrn, pStack->pKeyBuf);
	return (rc);
}

/****************************************************************************
Desc: Read the block information and initialize all needed pStack
		elements.
****************************************************************************/
void FSBlkToStack(
	BTSK *		pStack)
{
	FLMBYTE *	pBlk = pStack->pBlk;
	FLMUINT		uiBlkType;

	pStack->uiBlkType = uiBlkType = (FLMUINT) (BH_GET_TYPE( pBlk));

	// The standard overhead is used in the pStack Compares are made to
	// determine if the element is extended.

	if (uiBlkType == BHT_LEAF)
	{
		pStack->uiElmOvhd = BBE_KEY;
	}
	else if (uiBlkType == BHT_NON_LEAF_DATA)
	{
		pStack->uiElmOvhd = BNE_DATA_OVHD;
	}
	else if (uiBlkType == BHT_NON_LEAF)
	{
		pStack->uiElmOvhd = BNE_KEY_START;
	}
	else if (uiBlkType == BHT_NON_LEAF_COUNTS)
	{
		pStack->uiElmOvhd = BNE_KEY_COUNTS_START;
	}
	else
	{
		flmAssert( 0);
		pStack->uiElmOvhd = BNE_KEY_START;
	}

	pStack->uiKeyLen = pStack->uiPKC = pStack->uiPrevElmPKC = 0;
	pStack->uiCurElm = BH_OVHD;
	pStack->uiBlkEnd = (FLMUINT) FB2UW( &pBlk[BH_ELM_END]);
	pStack->uiLevel = (FLMUINT) pBlk[BH_LEVEL];
}

/****************************************************************************
Desc:	Scan to a specific element (pStack->uiCurElm) in a b-tree block.
****************************************************************************/
RCODE FSBtScanTo(
	BTSK *		pStack,
	FLMBYTE *	pSearchKey,
	FLMUINT		uiSearchKeyLen,
	FLMUINT		dinDomain)
{
	RCODE				rc = FERR_OK;
	FLMBYTE *		pCurElm;
	FLMBYTE *		pBlk;
	FLMBYTE *		pKeyBuf = pStack->pKeyBuf;
	FLMBYTE *		pPrevElm;
	FLMUINT			uiPrevKeyCnt = 0;
	FLMUINT			uiElmKeyLen = 0;
	FLMUINT			uiTargetCurElm = pStack->uiCurElm;
	FLMUINT			uiElmOvhd;
	FLMUINT			uiKeyBufLen;

	FSBlkToStack( pStack);
	pBlk = pStack->pBlk;
	uiElmOvhd = pStack->uiElmOvhd;

	if (uiTargetCurElm > pStack->uiBlkEnd)
	{
		uiTargetCurElm = pStack->uiBlkEnd;
	}

	// The code is easy for non-leaf data blocks.

	if (pStack->uiBlkType == BHT_NON_LEAF_DATA)
	{

		// Target may be any byte offset in the block.

		while (pStack->uiCurElm < uiTargetCurElm)
		{
			pStack->uiCurElm += BNE_DATA_OVHD;
		}

		if (uiTargetCurElm < pStack->uiBlkEnd)
		{
			flmCopyDrnKey( pKeyBuf, &pBlk[pStack->uiCurElm]);
			pStack->uiCmpStatus = BT_EQ_KEY;
		}
		else
		{
			pStack->uiCmpStatus = BT_END_OF_DATA;
		}

		goto Exit;
	}

	// Note: There is no way pPrevElm can be accessed and point to NULL
	// unless the block is corrupt and starts with a PKC value.

	pCurElm = NULL;
	uiKeyBufLen = 0;
	while (pStack->uiCurElm < uiTargetCurElm)
	{
		pPrevElm = pCurElm;
		pCurElm = &pBlk[pStack->uiCurElm];
		uiPrevKeyCnt = BBE_GET_PKC( pCurElm);
		uiElmKeyLen = BBE_GET_KL( pCurElm);
		if ((pStack->uiKeyLen = uiPrevKeyCnt + uiElmKeyLen) > pStack->uiKeyBufSize)
		{
			rc = RC_SET( FERR_CACHE_ERROR);
			goto Exit;
		}

		// Copy the minimum number of bytes from the previous element.

		if (uiPrevKeyCnt > uiKeyBufLen)
		{
			FLMUINT		uiCopyLength = uiPrevKeyCnt - uiKeyBufLen;
			FLMBYTE *	pSrcPtr = &pPrevElm[uiElmOvhd];

			flmAssert( pCurElm != NULL);

			while (uiCopyLength--)
			{
				pKeyBuf[uiKeyBufLen++] = *pSrcPtr++;
			}
		}
		else
		{
			uiKeyBufLen = uiPrevKeyCnt;
		}

		// Position to the next element

		if (pStack->uiBlkType == BHT_LEAF)
		{
			pStack->uiCurElm += (FLMUINT) (BBE_LEN( pCurElm));
			if (pStack->uiCurElm + BBE_LEM_LEN >= pStack->uiBlkEnd)
			{
				f_memcpy( &pKeyBuf[uiKeyBufLen], &pCurElm[uiElmOvhd], uiElmKeyLen);

				if (uiSearchKeyLen && (pStack->uiCurElm < pStack->uiBlkEnd))
				{

					// This is a rare and unsure case where caller needs to
					// have pStack->uiPrevElmPKC set correctly.

					FSKeyCmp( pStack, pSearchKey, uiSearchKeyLen, dinDomain);
				}

				goto Hit_End;
			}
		}
		else
		{
			pStack->uiCurElm += (FLMUINT) (BNE_LEN( pStack, pCurElm));
			
			if (pStack->uiCurElm >= pStack->uiBlkEnd)
			{

				// Make sure that pKeyBuf has the last element's key.

				f_memcpy( &pKeyBuf[uiKeyBufLen], &pCurElm[uiElmOvhd], uiElmKeyLen);

Hit_End:

				pStack->uiKeyLen = 0;
				pStack->uiPrevElmPKC = pStack->uiPKC;
				pStack->uiPKC = 0;
				pStack->uiCmpStatus = BT_END_OF_DATA;
				goto Exit;
			}
		}
	}

	// Check to see if the scan hit where you wanted, if so setup stack &
	// pKeyBuf.

	if (pStack->uiCurElm == uiTargetCurElm)
	{

		// BE CAREFUL. Names with "target" point to this element. All other
		// references include pCurElm point to the previous element.

		FLMBYTE *	pTargetCurElm = CURRENT_ELM( pStack);
		FLMUINT		uiTargetPrevKeyCnt = BBE_GET_PKC( pTargetCurElm);
		FLMUINT		uiTargetElmKeyLen = BBE_GET_KL( pTargetCurElm);

		// Compare the current key so that prevPKC and PKC are set.

		pStack->uiCmpStatus = BT_EQ_KEY;
		if (pCurElm)
		{
			if (uiSearchKeyLen)
			{

				// Copy the entire key into keyBuf to compare

				f_memcpy( &pKeyBuf[uiPrevKeyCnt], &pCurElm[uiElmOvhd], uiElmKeyLen);
				pStack->uiCmpStatus = FSKeyCmp( pStack, pSearchKey, uiSearchKeyLen,
														 dinDomain);
			}
			else if (uiTargetPrevKeyCnt > uiKeyBufLen)
			{

				// Copy what is necessary. uiPrevKeyCnt is equal to
				// uiKeyBufLen.

				FLMUINT		uiCopyLength = uiTargetPrevKeyCnt - uiKeyBufLen;
				FLMBYTE *	pSrcPtr = &pCurElm[uiElmOvhd];

				while (uiCopyLength--)
				{
					pKeyBuf[uiKeyBufLen++] = *pSrcPtr++;
				}
			}
		}

		if ((pStack->uiKeyLen = uiTargetPrevKeyCnt + uiTargetElmKeyLen) > 
				pStack->uiKeyBufSize)
		{
			rc = RC_SET( FERR_CACHE_ERROR);
			goto Exit;
		}

		if (uiTargetElmKeyLen)
		{
			f_memcpy( &pKeyBuf[uiTargetPrevKeyCnt], &pTargetCurElm[uiElmOvhd],
						uiTargetElmKeyLen);

			if (uiSearchKeyLen)
			{
				pStack->uiCmpStatus = FSKeyCmp( pStack, pSearchKey, uiSearchKeyLen,
														 dinDomain);
			}
		}
		else
		{

			// This will be hit on a condition where we want to insert "ABCD
			// (10)" into ABCD (15) ABCD (5) between the two keys. (10) is
			// the DIN value. Because the keys are equal we don't have to
			// call compare again. The uiPKC is the uiPrevKeyCnt value.

			pStack->uiPrevElmPKC = pStack->uiPKC;
			pStack->uiPKC = uiTargetPrevKeyCnt;
		}
	}
	else
	{

		// Copy the remaining bytes of the current key into the buffer.

		if (pCurElm)
		{
			f_memcpy( &pKeyBuf[uiPrevKeyCnt], &pCurElm[uiElmOvhd], uiElmKeyLen);
		}

		pStack->uiCmpStatus = BT_GT_KEY;
	}

Exit:

	return (rc);
}

/****************************************************************************
Desc: Standard key compare routine for a key and a b-tree element
****************************************************************************/
FSTATIC FLMUINT FSKeyCmp(
	BTSK *			pStack,
	FLMBYTE *		key,
	FLMUINT			uiKeyLen,
	FLMUINT			dinDomain)
{
	FLMBYTE *		pCurElm;
	FLMBYTE *		pKeyBuf;							// Current element's key
	FLMUINT			uiCmp;							// Return value
	FLMUINT			uiCompareLen;					// Length to compare
	FLMUINT			uiOrigCompareLen;				// Original compare length
	FLMUINT			uiCurElmKeyLen;				// Current element's length
	FLMUINT			uiPKCTemp;

	// Get again the current element's key length & compute compare length

	uiCurElmKeyLen = pStack->uiKeyLen;
	uiOrigCompareLen = uiCompareLen = f_min( uiKeyLen, uiCurElmKeyLen);
	pKeyBuf = pStack->pKeyBuf;					// Point to the local key buffer
	pStack->uiPrevElmPKC = pStack->uiPKC;	// Change previous element
	pStack->uiPKC = 0;

	while (uiCompareLen--)
	{
		if (*key++ == *pKeyBuf++)
		{
			continue;
		}

		uiPKCTemp = uiOrigCompareLen - (uiCompareLen + 1);
		pStack->uiPKC = (uiPKCTemp > BBE_PKC_MAX) ? BBE_PKC_MAX : uiPKCTemp;

		// Not equal so return

		return ((*(--key) < *(--pKeyBuf)) ? BT_GT_KEY : BT_LT_KEY);
	}

	// Set the prev key count value

	pStack->uiPKC = (uiOrigCompareLen <= BBE_PKC_MAX) 
									? uiOrigCompareLen 
									: BBE_PKC_MAX;

	// Set return status, If equal then compare the dinDomain if needed.

	uiCmp = uiKeyLen > uiCurElmKeyLen 
					? BT_LT_KEY 
					: (uiKeyLen < uiCurElmKeyLen 
							? BT_GT_KEY 
							: BT_EQ_KEY);

	if ((uiCmp == BT_EQ_KEY) && dinDomain)
	{
		pCurElm = CURRENT_ELM( pStack);
		if ((dinDomain - 1) < FSGetDomain( &pCurElm, (FLMBYTE) pStack->uiElmOvhd))
		{
			uiCmp = BT_LT_KEY;
		}
	}

	return (uiCmp);
}

/****************************************************************************
Desc:	Go to the next element within the block
****************************************************************************/
RCODE FSBlkNextElm(
	BTSK *		pStack)
{
	RCODE			rc = FERR_BT_END_OF_DATA;
	FLMBYTE *	elmPtr;
	FLMUINT		uiElmSize;

	elmPtr = &pStack->pBlk[pStack->uiCurElm];

	if (pStack->uiBlkType == BHT_LEAF)
	{
		uiElmSize = BBE_LEN( elmPtr);
		if (pStack->uiCurElm + BBE_LEM_LEN < pStack->uiBlkEnd)
		{
			if ((pStack->uiCurElm += uiElmSize) + BBE_LEM_LEN < pStack->uiBlkEnd)
			{
				rc = FERR_OK;
			}
		}
	}
	else
	{
		if (pStack->uiBlkType == BHT_NON_LEAF_DATA)
		{
			uiElmSize = BNE_DATA_OVHD;
		}
		else
		{
			uiElmSize = (FLMUINT) BNE_LEN( pStack, elmPtr);
		}

		if (pStack->uiCurElm < pStack->uiBlkEnd)
		{

			// Check if this is not the last element within the block

			if ((pStack->uiCurElm += uiElmSize) < pStack->uiBlkEnd)
			{
				rc = FERR_OK;
			}
		}
	}

	return (rc);
}

/****************************************************************************
Desc: Go to the next element in the logical b-tree while building the key
****************************************************************************/
RCODE FSBtNextElm(
	FDB *		pDb,
	LFILE *	pLFile,
	BTSK *	pStack)
{
	RCODE 	rc = FERR_OK;

	if (pStack->uiCurElm < BH_OVHD)
	{
		pStack->uiCurElm = BH_OVHD;
	}
	else if ((rc = FSBlkNextElm( pStack)) == FERR_BT_END_OF_DATA)
	{
		FLMBYTE *	pBlk = BLK_ELM_ADDR( pStack, BH_NEXT_BLK);
		FLMUINT		blkNum = FB2UD( pBlk);
		
		if (blkNum != BT_END)
		{

			// Current element was last element in the block - go to next
			// block

			if (RC_OK( rc = FSGetBlock( pDb, pLFile, blkNum, pStack)))
			{

				// Set blk end and adjust parent block to next element

				pBlk = pStack->pBlk;
				pStack->uiBlkEnd = (FLMUINT) FB2UW( &pBlk[BH_ELM_END]);
				pStack->uiCurElm = BH_OVHD;
				pStack->uiPKC = 0;
				pStack->uiPrevElmPKC = 0;

				if (pStack->uiFlags & FULL_STACK)
				{
					rc = FSAdjustStack( pDb, pLFile, pStack, TRUE);
				}
			}
		}
	}

	if (RC_OK( rc))
	{
		FLMBYTE *	pCurElm = CURRENT_ELM( pStack);
		FLMUINT		uiKeyLen;

		if (pStack->uiBlkType == BHT_NON_LEAF_DATA)
		{
			flmCopyDrnKey( pStack->pKeyBuf, pCurElm);
			goto Exit;
		}

		// Copy key to the stack->pKeyBuf & check for end key

		if ((uiKeyLen = BBE_GET_KL( pCurElm)) != 0)
		{
			FLMUINT	uiPKC = (FLMUINT) (BBE_GET_PKC( pCurElm));

			if (uiKeyLen + uiPKC <= pStack->uiKeyBufSize)
			{
				pStack->uiKeyLen = (uiKeyLen + uiPKC);
				f_memcpy( &pStack->pKeyBuf[ uiPKC], &pCurElm[ pStack->uiElmOvhd],
							uiKeyLen);
			}
			else
			{
				rc = RC_SET( FERR_CACHE_ERROR);
				goto Exit;
			}
		}
	}

Exit:

	return (rc);
}

/****************************************************************************
Desc: Adjust a full stack
****************************************************************************/
RCODE FSAdjustStack(
	FDB *		pDb,
	LFILE *	pLFile,
	BTSK *	pStack,
	FLMBOOL	bMovedNext)
{
	RCODE 	rc = FERR_OK;

	pStack->uiFlags = FULL_STACK;

	// Pop the stack and go to the next element This is a recursive call
	// back to FSBtNextElm() or FSBtPrevElm() Watch out, this will not work
	// if the concurrency model changes to a b-tree locking method like
	// other products use.
	//
	// Pop the pStack going to the parents block
	
	pStack--;

	// It is very rare that block will need to be read.  Maybe some sort of
	// split case.  The block should have already have been read.

	if (RC_OK( rc = FSGetBlock( pDb, pLFile, pStack->uiBlkAddr, pStack)))
	{
		rc = bMovedNext 
					? FSBtNextElm( pDb, pLFile, pStack)
					: FSBtPrevElm( pDb, pLFile, pStack);
	}

	// Push the pStack and unpin the current block

	pStack++;
	return (rc);
}

/****************************************************************************
Desc: Read in a block from the cache and set most stack elements.
****************************************************************************/
RCODE FSGetBlock(
	FDB *		pDb,
	LFILE *	pLFile,
	FLMUINT	uiBlkAddr,
	BTSK *	pStack)
{
	RCODE			rc = FERR_OK;
	FLMBYTE *	pBlk;

	// Release whatever block might be there first. If no block is there
	// (pStack->pSCache == NULL), FSReleaseBlock does nothing. Stacks are
	// ALWAYS initialized to set pSCache to NULL, so this is OK to call
	// even if stack has never been used to read a block yet.

	if (pStack->pSCache)
	{

		// If we already have the block we want, keep it!

		if (pStack->pSCache->uiBlkAddress != uiBlkAddr)
		{
			FSReleaseBlock( pStack, FALSE);
		}
	}

	if (!pStack->pSCache)
	{
		flmAssert( !pStack->pBlk);

		if (RC_BAD( rc = ScaGetBlock( pDb, pLFile, BHT_LEAF, uiBlkAddr, NULL,
					  &pStack->pSCache)))
		{
			goto Exit;
		}
	}

	pStack->pBlk = pBlk = pStack->pSCache->pucBlk;
	if (pStack->uiBlkAddr != uiBlkAddr)
	{
		FLMUINT	uiBlkType;

		pStack->uiBlkAddr = uiBlkAddr;

		// Set other pStack elements.

		pStack->uiBlkType = uiBlkType = (FLMUINT) (BH_GET_TYPE( pBlk));

		// The standard overhead is used in the stack Compares are made to
		// determine if the element is extended.

		if (uiBlkType == BHT_LEAF)
		{
			pStack->uiElmOvhd = BBE_KEY;
		}
		else if (uiBlkType == BHT_NON_LEAF_DATA)
		{
			pStack->uiElmOvhd = BNE_DATA_OVHD;
		}
		else if (uiBlkType == BHT_NON_LEAF)
		{
			pStack->uiElmOvhd = BNE_KEY_START;
		}
		else if (uiBlkType == BHT_NON_LEAF_COUNTS)
		{
			pStack->uiElmOvhd = BNE_KEY_COUNTS_START;
		}
		else
		{
			rc = RC_SET( FERR_DATA_ERROR);
			FSReleaseBlock( pStack, FALSE);
			goto Exit;
		}

		pStack->uiKeyLen = pStack->uiPKC = pStack->uiPrevElmPKC = 0;
		pStack->uiLevel = (FLMUINT) pBlk[BH_LEVEL];
		pStack->uiCurElm = BH_OVHD;
	}

	pStack->uiBlkEnd = (FLMUINT) FB2UW( &pBlk[BH_ELM_END]);

Exit:

	return (rc);
}

/****************************************************************************
Desc: Release all of the cache associated with a stack.
****************************************************************************/
void FSReleaseStackCache(
	BTSK *		pStack,
	FLMUINT		uiNumLevels,
	FLMBOOL		bMutexAlreadyLocked)
{
	FLMBOOL		bMutexLocked = FALSE;

	while (uiNumLevels)
	{
		if (pStack->pSCache)
		{
			if (!bMutexLocked && !bMutexAlreadyLocked)
			{
				f_mutexLock( gv_FlmSysData.hShareMutex);
				bMutexLocked = TRUE;
			}

			ScaReleaseCache( pStack->pSCache, TRUE);
			pStack->pSCache = NULL;
			pStack->pBlk = NULL;
		}

		uiNumLevels--;
		pStack++;
	}

	if (bMutexLocked)
	{
		f_mutexUnlock( gv_FlmSysData.hShareMutex);
	}
}
