git-svn-id: https://svn.code.sf.net/p/flaim/code/trunk@1012 0109f412-320b-0410-ab79-c3e0c5ffbbe6
11673 lines
277 KiB
C++
11673 lines
277 KiB
C++
//------------------------------------------------------------------------------
|
|
// Desc: This class handles all of operations on a given B-Tree.
|
|
// Tabs: 3
|
|
//
|
|
// Copyright (c) 2002-2007 Novell, Inc. All Rights Reserved.
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation; version 2.1
|
|
// of the License.
|
|
//
|
|
// This library 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
|
|
// Library Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; 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$
|
|
//------------------------------------------------------------------------------
|
|
|
|
#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_SFLM_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_SFLM_BTREE_BAD_STATE);
|
|
goto Exit;
|
|
}
|
|
|
|
// Verify that we are in an update transaction.
|
|
if (pDb->m_eTransType != SFLM_UPDATE_TRANS && !pDb->m_pDatabase->m_bTempDb)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( pDb->m_eTransType == SFLM_NO_TRANS
|
|
? NE_SFLM_NO_TRANS_ACTIVE
|
|
: NE_SFLM_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->uiEncDefNum)
|
|
{
|
|
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_SFLM_OK;
|
|
F_Database * pDatabase= pDb->m_pDatabase;
|
|
|
|
if ( m_bOpened)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_BTREE_BAD_STATE);
|
|
goto Exit;
|
|
}
|
|
|
|
if (pDb->m_eTransType == SFLM_NO_TRANS && !pDatabase->m_bTempDb)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_NO_TRANS_ACTIVE);
|
|
goto Exit;
|
|
}
|
|
|
|
|
|
if( !pLFile->uiRootBlk)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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 == SFLM_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 == SFLM_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_SFLM_OK;
|
|
FLMUINT uiNumLevels;
|
|
FLMUINT puiBlkAddrs[ BH_MAX_LEVELS];
|
|
FLMUINT uiLoop;
|
|
|
|
flmAssert( m_bOpened);
|
|
|
|
// Verify the transaction type
|
|
|
|
if (m_pDb->m_eTransType != SFLM_UPDATE_TRANS && !m_bTempDb)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( m_pDb->m_eTransType == SFLM_NO_TRANS
|
|
? NE_SFLM_NO_TRANS_ACTIVE
|
|
: NE_SFLM_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_SFLM_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() != SFLM_UPDATE_TRANS)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( pDb->getTransType() == SFLM_NO_TRANS
|
|
? NE_SFLM_NO_TRANS_ACTIVE
|
|
: NE_SFLM_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_SFLM_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 != SFLM_UPDATE_TRANS && !m_bTempDb)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( m_pDb->m_eTransType == SFLM_NO_TRANS
|
|
? NE_SFLM_NO_TRANS_ACTIVE
|
|
: NE_SFLM_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 == SFLM_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_SFLM_OK;
|
|
F_BLK_HDR * pBlkHdr;
|
|
FLMBYTE pucDOAddr[ 4];
|
|
|
|
if ( !m_bOpened || m_bSetupForRead || m_bSetupForReplace ||
|
|
(m_bSetupForWrite && bFirst))
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFLM_ILLEGAL_OP);
|
|
goto Exit;
|
|
}
|
|
|
|
// Verify the transaction type
|
|
|
|
if( m_pDb->m_eTransType != SFLM_UPDATE_TRANS && !m_bTempDb)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( m_pDb->m_eTransType == SFLM_NO_TRANS
|
|
? NE_SFLM_NO_TRANS_ACTIVE
|
|
: NE_SFLM_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, FLM_EXACT);
|
|
|
|
// Should not find this entry. If we get back anything other than
|
|
// an NE_SFLM_NOT_FOUND, then there is a problem.
|
|
|
|
if( rc != NE_SFLM_NOT_FOUND)
|
|
{
|
|
if( RC_OK( rc))
|
|
{
|
|
rc = RC_SET( NE_SFLM_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->uiEncDefNum)
|
|
{
|
|
((F_ENC_DO_BLK_HDR *)pBlkHdr)->ui32EncDefNum = (FLMUINT32)m_pLFile->uiEncDefNum;
|
|
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_SFLM_OK;
|
|
FLMBYTE * pucValue = NULL;
|
|
FLMUINT uiLen = 0;
|
|
|
|
if ( !m_bOpened)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_BTREE_BAD_STATE);
|
|
goto Exit;
|
|
}
|
|
|
|
// Verify the Txn type
|
|
if (m_pDb->m_eTransType != SFLM_UPDATE_TRANS && !m_bTempDb)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( m_pDb->m_eTransType == SFLM_NO_TRANS
|
|
? NE_SFLM_NO_TRANS_ACTIVE
|
|
: NE_SFLM_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,
|
|
FLM_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_SFLM_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_SFLM_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_SFLM_ILLEGAL_OP);
|
|
goto Exit;
|
|
}
|
|
|
|
// Verify the Txn type
|
|
if (m_pDb->m_eTransType != SFLM_UPDATE_TRANS && !m_bTempDb)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( m_pDb->m_eTransType == SFLM_NO_TRANS
|
|
? NE_SFLM_NO_TRANS_ACTIVE
|
|
: NE_SFLM_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, FLM_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->uiEncDefNum)
|
|
{
|
|
((F_ENC_DO_BLK_HDR *)pBlkHdr)->ui32EncDefNum = (FLMUINT32)m_pLFile->uiEncDefNum;
|
|
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_SFLM_OK;
|
|
FLMBYTE * pucEntry = NULL;
|
|
|
|
flmAssert( pucKey && uiKeyBufSize && puiKeyLen);
|
|
|
|
if (!m_bOpened || m_bSetupForWrite || m_bSetupForReplace)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_BTREE_BAD_STATE);
|
|
goto Exit;
|
|
}
|
|
|
|
m_bSetupForRead = FALSE;
|
|
|
|
// Verify the Txn type
|
|
|
|
if (m_pDb->m_eTransType == SFLM_NO_TRANS && !m_bTempDb)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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 FLM_FIRST, FLM_LAST, FLM_EXCL and possibly FLM_INCL,
|
|
// we will pass back the key we actually found.
|
|
|
|
if( uiMatch != FLM_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_SFLM_OK;
|
|
FLMBYTE * pucEntry;
|
|
|
|
if( !m_bOpened || !m_bSetupForRead ||
|
|
m_bSetupForWrite || m_bSetupForReplace)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_BTREE_BAD_STATE);
|
|
goto Exit;
|
|
}
|
|
|
|
if( puiDataLen)
|
|
{
|
|
*puiDataLen = 0;
|
|
}
|
|
|
|
// Is there anything there to get?
|
|
|
|
if( m_uiOADataRemaining == 0)
|
|
{
|
|
rc = RC_SET( NE_SFLM_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 == SFLM_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, FLM_EXACT)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Will need a new version of this block.
|
|
|
|
ScaReleaseCache( m_pSCache, FALSE);
|
|
m_pSCache = NULL;
|
|
}
|
|
else
|
|
{
|
|
rc = RC_SET(NE_SFLM_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_SFLM_OK;
|
|
FLMBYTE * pucEntry = NULL;
|
|
FLMBOOL bAdvanced = FALSE;
|
|
|
|
if( !m_bOpened || !m_bSetupForRead)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_BTREE_BAD_STATE);
|
|
goto Exit;
|
|
}
|
|
|
|
// Verify the transaction type
|
|
|
|
if( m_pDb->m_eTransType == SFLM_NO_TRANS && !m_bTempDb)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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 == SFLM_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 FLM_EXCL will result in our being positioned at
|
|
// the next entry.
|
|
|
|
if( RC_BAD( rc = btLocateEntry( pucKey, uiKeyBufSize, puiKeyLen,
|
|
FLM_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 FLM_FIRST, FLM_LAST, FLM_EXCL and possibly FLM_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_SFLM_OK;
|
|
FLMBYTE * pucEntry = NULL;
|
|
|
|
if( !m_bOpened || !m_bSetupForRead)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_BTREE_BAD_STATE);
|
|
goto Exit;
|
|
}
|
|
|
|
// Verify the transaction type
|
|
|
|
if( m_pDb->m_eTransType == SFLM_NO_TRANS && !m_bTempDb)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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 == SFLM_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 FLM_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,
|
|
FLM_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 FLM_FIRST, FLM_LAST, FLM_EXCL and possibly FLM_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_SFLM_OK;
|
|
|
|
m_Stack[ 0].pucKeyBuf = pucKey;
|
|
|
|
if( RC_BAD( rc = btLocateEntry( pucKey, uiKeyBufSize, puiKeyLen,
|
|
FLM_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_SFLM_OK;
|
|
|
|
m_Stack[ 0].pucKeyBuf = pucKey;
|
|
|
|
if( RC_BAD( rc = btLocateEntry( pucKey, uiKeyBufSize, puiKeyLen,
|
|
FLM_LAST, NULL, puiDataLength, pui32BlkAddr, puiOffsetIndex)))
|
|
{
|
|
if( rc == NE_SFLM_BOF_HIT)
|
|
{
|
|
rc = RC_SET( NE_SFLM_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_SFLM_OK;
|
|
FLMBYTE * pucEntry = NULL;
|
|
|
|
flmAssert( pucKey && uiKeyBufSize && puiKeyLen);
|
|
|
|
m_bSetupForRead = FALSE;
|
|
|
|
if( !m_bOpened || !m_bCounts)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_BTREE_BAD_STATE);
|
|
goto Exit;
|
|
}
|
|
|
|
// Verify the transaction type
|
|
|
|
if( m_pDb->m_eTransType == SFLM_NO_TRANS && !m_bTempDb)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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 FLM_FIRST, FLM_LAST, FLM_EXCL and
|
|
// possibly FLM_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_SFLM_OK;
|
|
FLMUINT uiKeyBufSize = uiKeyLen;
|
|
FLMUINT uiLocalKeyLen = uiKeyLen;
|
|
|
|
if( !m_bOpened || !m_bSetupForRead || !m_bCounts)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_BTREE_BAD_STATE);
|
|
goto Exit;
|
|
}
|
|
|
|
// Verify the transaction type
|
|
|
|
if( m_pDb->m_eTransType == SFLM_NO_TRANS && !m_bTempDb)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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 == SFLM_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,
|
|
FLM_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_SFLM_OK;
|
|
F_CachedBlock * pSCache = NULL;
|
|
|
|
if( !m_bSetupForRead)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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 == SFLM_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,
|
|
FLM_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,
|
|
FLMUINT64 * pui64BlkCount,
|
|
FLMUINT64 * pui64KeyCount,
|
|
FLMBOOL * pbTotalsEstimated,
|
|
FLMUINT uiAvgBlkFullness)
|
|
{
|
|
RCODE rc = NE_SFLM_OK;
|
|
|
|
if( !m_bSetupForRead || !pUntilBtree->m_bSetupForRead)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFLM_FAILURE);
|
|
goto Exit;
|
|
}
|
|
|
|
rc = computeCounts( m_pStack, pUntilBtree->m_pStack, pui64BlkCount,
|
|
pui64KeyCount, 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_SFLM_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)
|
|
{
|
|
SFLM_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_SFLM_OK;
|
|
F_CachedBlock * pNewSCache = NULL;
|
|
FLMBYTE * pSrcBlk;
|
|
FLMBYTE * pDstBlk;
|
|
F_BTREE_BLK_HDR * pBlkHdr;
|
|
FLMUINT uiCounts = 0;
|
|
FLMBYTE * pucEntry;
|
|
FLMBYTE * pucNull = NULL;
|
|
FLMBYTE ucBuffer[ SFLM_MAX_KEY_SIZE + BTE_NLC_KEY_START];
|
|
FLMUINT uiMaxNLKey = SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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->uiEncDefNum)
|
|
{
|
|
((F_ENC_DO_BLK_HDR *)pBlkHdr)->ui32EncDefNum = (FLMUINT32)m_pLFile->uiEncDefNum;
|
|
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_SFLM_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->uiEncDefNum)
|
|
{
|
|
setBlockEncrypted( pBlkHdr);
|
|
((F_ENC_DO_BLK_HDR *)pBlkHdr)->ui32EncDefNum = (FLMUINT32)m_pLFile->uiEncDefNum;
|
|
}
|
|
|
|
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_SFLM_OK;
|
|
FLMBYTE * pucTemp = pucBuffer;
|
|
|
|
if( puiEntrySize)
|
|
{
|
|
*puiEntrySize = calcEntrySize( uiBlkType, uiFlags,
|
|
uiKeyLen, uiDataLen, uiOADataLen);
|
|
|
|
if( !(*puiEntrySize) || *puiEntrySize > uiBufferSize)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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 * 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_SFLM_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 * 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_SFLM_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_SFLM_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 * 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_SFLM_EOF_HIT will be returned.
|
|
****************************************************************************/
|
|
RCODE F_Btree::advanceToNextElement(
|
|
FLMBOOL bAdvanceStack)
|
|
{
|
|
RCODE rc = NE_SFLM_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_SFLM_BOF_HIT will be returned.
|
|
****************************************************************************/
|
|
RCODE F_Btree::backupToPrevElement(
|
|
FLMBOOL bBackupStack)
|
|
{
|
|
RCODE rc = NE_SFLM_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_SFLM_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_SFLM_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_SFLM_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 == FLM_FIRST || uiMatch == FLM_LAST)
|
|
{
|
|
uiKeyLen = 0;
|
|
}
|
|
|
|
if( uiKeyLen > SFLM_MAX_KEY_SIZE)
|
|
{
|
|
rc = RC_SET( NE_SFLM_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_SFLM_NOT_FOUND) && (rc != NE_SFLM_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 == FLM_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_SFLM_NOT_FOUND) || (rc == NE_SFLM_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 FLM_FIRST & FLM_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_SFLM_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_SFLM_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_SFLM_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 != SFLM_LF_INDEX)
|
|
{
|
|
rc = RC_SET( NE_SFLM_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_SFLM_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 FLM_FIRST & FLM_LAST
|
|
values will ignore the key altogether and just return the first or last
|
|
key respectively. The FLM_INCL value will return the key if found or the
|
|
first key following if not found. The FLM_EXACT will return an
|
|
NE_SFLM_NOT_FOUND if the key cannot be found. FLM_EXCL will return
|
|
the first key following the target key.
|
|
****************************************************************************/
|
|
RCODE F_Btree::scanBlock(
|
|
F_BTSK * pStack,
|
|
FLMUINT uiMatch)
|
|
{
|
|
RCODE rc = NE_SFLM_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_SFLM_BOF_HIT);
|
|
goto Exit;
|
|
}
|
|
|
|
uiTop = 0;
|
|
uiBottom = (FLMUINT)(pStack->pBlkHdr->ui16NumKeys - 1);
|
|
|
|
if( uiMatch == FLM_FIRST)
|
|
{
|
|
pStack->uiCurOffset = uiTop;
|
|
goto Exit;
|
|
}
|
|
|
|
if( uiMatch == FLM_LAST || pStack->uiKeyLen == 0)
|
|
{
|
|
pStack->uiCurOffset = uiBottom;
|
|
goto Exit;
|
|
}
|
|
|
|
flmAssert( uiMatch == FLM_INCL || uiMatch == FLM_EXCL ||
|
|
uiMatch == FLM_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 == FLM_EXACT)
|
|
{
|
|
rc = RC_SET( NE_SFLM_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 != FLM_INCL)
|
|
{
|
|
rc = RC_SET( NE_SFLM_NOT_FOUND);
|
|
}
|
|
|
|
uiMid = uiBottom;
|
|
goto VerifyPosition;
|
|
}
|
|
|
|
ResultGreater2:
|
|
|
|
for( ;;)
|
|
{
|
|
|
|
if( uiTop == uiBottom)
|
|
{
|
|
// We're done - didn't find it.
|
|
|
|
if( uiMatch == FLM_EXACT)
|
|
{
|
|
rc = RC_SET( NE_SFLM_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 != FLM_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 == FLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_NOT_FOUND) || (rc == NE_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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, FLM_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, FLM_EXCL);
|
|
|
|
// We could find this entry. If we get back anything other than
|
|
// an NE_SFLM_EOF_HIT or NE_SFLM_OK, then there is a problem.
|
|
|
|
if( rc != NE_SFLM_OK && rc != NE_SFLM_EOF_HIT &&
|
|
rc != NE_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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, FLM_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_SFLM_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_SFLM_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_SFLM_OK;
|
|
FLMUINT uiBlkAddr;
|
|
F_BTREE_BLK_HDR * pBlkHdr;
|
|
F_BTSK * 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_SFLM_BTREE_ERROR);
|
|
goto Exit;
|
|
}
|
|
|
|
// Cannot be the same block.
|
|
|
|
if( pSCache == pStack->pSCache)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFLM_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_SFLM_OK;
|
|
FLMUINT uiBlkAddr;
|
|
F_BTREE_BLK_HDR * pBlkHdr;
|
|
F_BTSK * 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_SFLM_BTREE_ERROR);
|
|
goto Exit;
|
|
}
|
|
|
|
// Cannot be the same block.
|
|
|
|
if( pSCache == pStack->pSCache)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_OK;
|
|
BTREE_REPLACE_STRUCT * pPrev;
|
|
F_BTSK * pStack = m_pStack;
|
|
const FLMBYTE * pucParentKey;
|
|
FLMBYTE * pucEntry;
|
|
|
|
if( m_uiReplaceLevels + 1 >= BH_MAX_LEVELS)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFLM_OK;
|
|
RCODE rcTmp = NE_SFLM_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, FLM_EXACT);
|
|
|
|
m_uiSearchLevel = uiSearchLevel;
|
|
|
|
if ((rcTmp != NE_SFLM_OK) &&
|
|
(rcTmp != NE_SFLM_NOT_FOUND) &&
|
|
(rcTmp != NE_SFLM_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_SFLM_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_SFLM_BTREE_ERROR);
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFLM_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_SFLM_EOF_HIT);
|
|
goto Exit;
|
|
}
|
|
|
|
if( uiKeyLen <= uiKeyBufSize)
|
|
{
|
|
f_memcpy( pucKey, pucKeyRV, uiKeyLen);
|
|
*puiKeyLen = uiKeyLen;
|
|
}
|
|
else
|
|
{
|
|
rc = RC_SET( NE_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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, FLM_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_SFLM_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, FLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_OK;
|
|
FLMUINT32 ui32BlkAddr;
|
|
|
|
ui32BlkAddr = (*ppSCache)->m_pBlkHdr->ui32NextBlkInChain;
|
|
|
|
ScaReleaseCache( *ppSCache, FALSE);
|
|
*ppSCache = NULL;
|
|
|
|
if( ui32BlkAddr == 0)
|
|
{
|
|
rc = RC_SET( NE_SFLM_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_SFLM_OK;
|
|
FLMUINT32 ui32BlkAddr;
|
|
|
|
ui32BlkAddr = (*ppSCache)->m_pBlkHdr->ui32PrevBlkInChain;
|
|
|
|
ScaReleaseCache( *ppSCache, FALSE);
|
|
*ppSCache = NULL;
|
|
|
|
if( ui32BlkAddr == 0)
|
|
{
|
|
rc = RC_SET( NE_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_BTREE_ERROR);
|
|
goto Exit;
|
|
}
|
|
|
|
if( f_memcmp( pucParentKey, pucChildKey, uiParentKeyLen) != 0)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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 * pFromStack,
|
|
F_BTSK * pUntilStack,
|
|
FLMUINT64 * pui64BlockCount,
|
|
FLMUINT64 * pui64KeyCount,
|
|
FLMBOOL * pbTotalsEstimated,
|
|
FLMUINT uiAvgBlkFullness)
|
|
{
|
|
RCODE rc = NE_SFLM_OK;
|
|
FLMUINT64 ui64TotalKeys = 0;
|
|
FLMUINT64 ui64EstKeyCount = 0;
|
|
FLMUINT64 ui64TotalBlocksBetween = 0;
|
|
FLMUINT64 ui64EstBlocksBetween = 0;
|
|
FLMUINT uiBlkKeyCount;
|
|
|
|
ui64TotalBlocksBetween = 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, &uiBlkKeyCount, NULL);
|
|
ui64TotalKeys = (FLMUINT64)uiBlkKeyCount;
|
|
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, pui64BlockCount,
|
|
pui64KeyCount, 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, &uiBlkKeyCount, NULL)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
ui64TotalKeys += (FLMUINT64)uiBlkKeyCount;
|
|
|
|
if( RC_BAD( rc = blockCounts( pUntilStack, 0,
|
|
pUntilStack->uiCurOffset, &uiBlkKeyCount, NULL)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
ui64TotalKeys += (FLMUINT64)uiBlkKeyCount;
|
|
|
|
// 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;
|
|
|
|
ui64EstKeyCount = (FLMUINT64)getAvgKeyCount( pFromStack, pUntilStack, uiAvgBlkFullness);
|
|
ui64EstBlocksBetween = 1;
|
|
|
|
for (;;)
|
|
{
|
|
FLMUINT uiBlkElementCount;
|
|
FLMUINT uiTempBlkElementCount;
|
|
FLMUINT64 ui64EstElementCount;
|
|
|
|
// 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, &uiBlkElementCount)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Don't count the pFromStack or the pUntilStack current elements.
|
|
|
|
uiBlkElementCount -= 2;
|
|
|
|
ui64TotalBlocksBetween += ui64EstBlocksBetween *
|
|
(FLMUINT64)(uiBlkElementCount > 0 ? uiBlkElementCount : 1);
|
|
ui64TotalKeys += ui64EstKeyCount *
|
|
(FLMUINT64)(uiBlkElementCount > 0 ? uiBlkElementCount : 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, &uiBlkElementCount)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Don't count the first element.
|
|
|
|
uiBlkElementCount--;
|
|
|
|
if( RC_BAD( rc = blockCounts( pUntilStack, 0,
|
|
pUntilStack->uiCurOffset, NULL, &uiTempBlkElementCount)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
uiBlkElementCount += (uiTempBlkElementCount - 1);
|
|
|
|
ui64TotalBlocksBetween += ui64EstBlocksBetween * (FLMUINT64)uiBlkElementCount;
|
|
ui64TotalKeys += ui64EstKeyCount * (FLMUINT64)uiBlkElementCount;
|
|
|
|
// 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.
|
|
|
|
ui64EstElementCount = (FLMUINT64)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.
|
|
|
|
ui64EstKeyCount *= ui64EstElementCount;
|
|
ui64EstBlocksBetween *= ui64EstElementCount;
|
|
}
|
|
|
|
Exit:
|
|
|
|
if( pui64KeyCount)
|
|
{
|
|
*pui64KeyCount = ui64TotalKeys;
|
|
}
|
|
|
|
if( pui64BlockCount)
|
|
{
|
|
*pui64BlockCount = ui64TotalBlocksBetween;
|
|
}
|
|
|
|
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 * pStack,
|
|
FLMUINT uiFirstOffset,
|
|
FLMUINT uiLastOffset,
|
|
FLMUINT * puiKeyCount,
|
|
FLMUINT * puiElementCount)
|
|
{
|
|
RCODE rc = NE_SFLM_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 * pFromStack,
|
|
F_BTSK * pUntilStack,
|
|
FLMUINT64 * pui64BlockCount,
|
|
FLMUINT64 * pui64KeyCount,
|
|
FLMBOOL * pbTotalsEstimated,
|
|
FLMUINT uiAvgBlkFullness)
|
|
{
|
|
RCODE rc = NE_SFLM_OK;
|
|
FLMUINT64 ui64OmittedKeys;
|
|
FLMUINT64 ui64TotalKeys;
|
|
FLMUINT64 ui64EstBlocksBetween;
|
|
FLMUINT64 ui64TotalBlocksBetween;
|
|
|
|
*pbTotalsEstimated = FALSE;
|
|
*pui64BlockCount = 0;
|
|
ui64TotalBlocksBetween = 0;
|
|
|
|
// Are these blocks adjacent?
|
|
|
|
if( pFromStack->pBlkHdr->stdBlkHdr.ui32NextBlkInChain ==
|
|
pUntilStack->ui32BlkAddr)
|
|
{
|
|
*pui64KeyCount = (pFromStack->pBlkHdr->ui16NumKeys -
|
|
pFromStack->uiCurOffset) + pUntilStack->uiCurOffset + 1;
|
|
goto Exit;
|
|
}
|
|
|
|
*pbTotalsEstimated = TRUE;
|
|
|
|
// How many keys are excluded in the From and Until blocks?
|
|
|
|
ui64OmittedKeys = (FLMUINT64)countRangeOfKeys(
|
|
pFromStack, 0, pFromStack->uiCurOffset) - 1;
|
|
|
|
ui64OmittedKeys += (FLMUINT64)countRangeOfKeys(
|
|
pUntilStack, pUntilStack->uiCurOffset,
|
|
pUntilStack->pBlkHdr->ui16NumKeys - 1) - 1;
|
|
|
|
ui64TotalKeys = 0;
|
|
ui64EstBlocksBetween = 1;
|
|
|
|
for( ;;)
|
|
{
|
|
FLMUINT uiBlkElementCount;
|
|
FLMUINT uiBlkTempElementCount;
|
|
FLMUINT64 ui64EstElementCount;
|
|
|
|
// 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, &uiBlkElementCount)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Don't count the pFromStack current element.
|
|
|
|
uiBlkElementCount -= 2;
|
|
ui64TotalBlocksBetween += ui64EstBlocksBetween *
|
|
(FLMUINT64)(uiBlkElementCount > 0 ? uiBlkElementCount : 1);
|
|
|
|
// Add one to the last offset to include the last entry in the count.
|
|
|
|
ui64TotalKeys = (FLMUINT64)countRangeOfKeys(
|
|
pFromStack, pFromStack->uiCurOffset, pUntilStack->uiCurOffset);
|
|
|
|
*pui64KeyCount = ui64TotalKeys - ui64OmittedKeys;
|
|
*pui64BlockCount = ui64TotalBlocksBetween;
|
|
goto Exit;
|
|
}
|
|
|
|
// How many to exclude from the From & Until blocks.
|
|
|
|
if( pFromStack->uiCurOffset)
|
|
{
|
|
ui64OmittedKeys += (FLMUINT64)countRangeOfKeys(
|
|
pFromStack, 0, pFromStack->uiCurOffset - 1);
|
|
}
|
|
|
|
ui64OmittedKeys += (FLMUINT64)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, &uiBlkElementCount)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Don't count the first element.
|
|
|
|
uiBlkElementCount--;
|
|
|
|
if( RC_BAD( rc = blockCounts( pUntilStack, 0,
|
|
pUntilStack->uiCurOffset, NULL, &uiBlkTempElementCount)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
uiBlkElementCount += (uiBlkTempElementCount - 1);
|
|
ui64TotalBlocksBetween += ui64EstBlocksBetween * (FLMUINT64)uiBlkElementCount;
|
|
|
|
// 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.
|
|
|
|
ui64EstElementCount = (FLMUINT64)getAvgKeyCount(
|
|
pFromStack, pUntilStack, uiAvgBlkFullness);
|
|
ui64EstBlocksBetween *= ui64EstElementCount;
|
|
}
|
|
|
|
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 * pStack1,
|
|
F_BTSK * pStack2)
|
|
{
|
|
RCODE rc = NE_SFLM_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 * 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 average number of keys, based on the anticipated
|
|
average block usage (passed in) and the actual block usage.
|
|
****************************************************************************/
|
|
FINLINE FLMUINT F_Btree::getAvgKeyCount(
|
|
F_BTSK * pFromStack,
|
|
F_BTSK * 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_SFLM_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)
|
|
{
|
|
SFLM_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_SFLM_OK;
|
|
F_BTSK tempStack;
|
|
F_BTSK * 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_SFLM_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_SFLM_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_SFLM_OK;
|
|
FLMUINT uiType;
|
|
|
|
if( !m_bOpened || m_bSetupForRead || m_bSetupForReplace ||
|
|
(m_bSetupForWrite))
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_BTREE_BAD_STATE);
|
|
goto Exit;
|
|
}
|
|
|
|
flmAssert( m_uiSearchLevel >= BH_MAX_LEVELS);
|
|
|
|
// Verify the Txn type
|
|
|
|
if( m_pDb->m_eTransType != SFLM_UPDATE_TRANS && !m_bTempDb)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( m_pDb->m_eTransType == SFLM_NO_TRANS
|
|
? NE_SFLM_NO_TRANS_ACTIVE
|
|
: NE_SFLM_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_SFLM_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_SFLM_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, FLM_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_SFLM_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_SFLM_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))
|
|
{
|
|
m_pLFile->uiRootBlk = ui32ToBlkAddr;
|
|
rc = m_pDb->m_pDatabase->lFileWrite( m_pDb, 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_SFLM_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_SFLM_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, FLM_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_SFLM_BTREE_ERROR);
|
|
goto Exit;
|
|
}
|
|
|
|
uiDataLen = btGetEntryDataLength( pucEntry, &pucData,
|
|
&uiOADataLen, NULL);
|
|
|
|
ui32DOBlkAddr = bteGetBlkAddr( pucData);
|
|
|
|
if( ui32DOBlkAddr != ui32FromBlkAddr)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_BTREE_ERROR);
|
|
goto Exit;
|
|
}
|
|
|
|
if( uiDataLen != sizeof( ucDataBuffer))
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFLM_OK;
|
|
FLMBYTE * pucEntry;
|
|
F_BLK_HDR * pBlkHdr = NULL;
|
|
FLMUINT32 ui32BlkAddr;
|
|
FLMBOOL bLastElement = FALSE;
|
|
|
|
if( !m_bOpened || !m_bSetupForRead)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFLM_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 == SFLM_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, FLM_EXACT)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Will need a new version of this block.
|
|
|
|
ScaReleaseCache( m_pSCache, FALSE);
|
|
m_pSCache = NULL;
|
|
}
|
|
else
|
|
{
|
|
rc = RC_SET(NE_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_OK;
|
|
|
|
if( !m_bOpened || !m_bSetupForRead)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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 == SFLM_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, FLM_EXACT)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Will need a new version of this block.
|
|
|
|
ScaReleaseCache( m_pSCache, FALSE);
|
|
m_pSCache = NULL;
|
|
}
|
|
else
|
|
{
|
|
rc = RC_SET(NE_SFLM_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_SFLM_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 == SFLM_NO_TRANS && !m_bTempDb)
|
|
{
|
|
rc = RC_SET_AND_ASSERT( NE_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_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_SFLM_BTREE_ERROR);
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
if( pCurrentBlk)
|
|
{
|
|
ScaReleaseCache( pCurrentBlk, FALSE);
|
|
}
|
|
|
|
if( rc == NE_SFLM_BTREE_ERROR)
|
|
{
|
|
f_sprintf( errStruct->szMsg, "Corrupt DO chain starting at %X", uiDOAddr);
|
|
}
|
|
|
|
return( NE_SFLM_OK);
|
|
}
|
|
|
|
/***************************************************************************
|
|
Desc: Method to check the counts in a database with counts.
|
|
****************************************************************************/
|
|
RCODE F_Btree::verifyCounts(
|
|
BTREE_ERR_STRUCT * pErrStruct)
|
|
{
|
|
RCODE rc = NE_SFLM_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_SFLM_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);
|
|
}
|