///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoBTreeManager.cc
// -------------------
// Cego btree index manager class implementation
//                                                         
// Design and Implementation by Bjoern Lemke               
//         
// (C)opyright 2000-2025 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: CegoBTreeManager
//
// Description: This class implements the btree algorithm to be used for table index objects.
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

// LFC INCLUDES
#include <lfcbase/Exception.h>

// CEGO INCLUDES
#include "CegoBTreeManager.h"
#include "CegoLockHandler.h"
#include "CegoTupleState.h"

// POSIX INCLUDES
#include <string.h>
#include <stdlib.h>

char __allowDuplicateNull = 0;

CegoBTreeManager::BTreeCache::BTreeCache()
{
}

CegoBTreeManager::BTreeCache::~BTreeCache()
{
    CacheEntry *pEntry = _cache.First();
    while ( pEntry )
    {
	free(pEntry->getPage()->getPagePtr());
	free(pEntry->getPage());
	pEntry = _cache.Next();
    }
    _cache.Empty();    
}

CegoBufferPage* CegoBTreeManager::BTreeCache::newCachePage(CegoBufferPage& bp, const CegoBufferPage::PageType pageType, bool copyPage)
{
    char* pagePtr = (char*)malloc(bp.getPageSize());

    CegoBufferPage* pCachePage = new CegoBufferPage(pagePtr, bp.getPageSize());

    pCachePage->initPage(pageType);
    pCachePage->setPageId(bp.getPageId());
    // pCachePage->setNextPageId(bp.getNextPageId());
    if ( copyPage )
	memcpy( pCachePage->getChunkEntry(), bp.getChunkEntry(), bp.getChunkLen());	
    
    _cache.Insert(CacheEntry(pCachePage));

    return pCachePage;
}

CegoBufferPage* CegoBTreeManager::BTreeCache::getCachePage(PageIdType pageId)
{
    CacheEntry *pEntry = _cache.Find(CacheEntry(pageId));
    CegoBufferPage *pBP = 0;
    if ( pEntry )
    {
	pBP = pEntry->getPage();
    }
    return pBP;
}
		  
CegoBufferPage* CegoBTreeManager::BTreeCache::getFirst()
{
    CacheEntry *pEntry = _cache.First();
    if ( pEntry )
    {
	return pEntry->getPage();
    }
    return 0;
}
	
CegoBufferPage* CegoBTreeManager::BTreeCache::getNext()
{
    CacheEntry *pEntry = _cache.Next();
    if ( pEntry )
    {
	return pEntry->getPage();
    }
    return 0;
}

CegoBTreeManager::CegoBTreeManager(CegoObjectManager *pObjMng, CegoBTreeObject *pBTO)
{
    _pDBMng = pObjMng->getDBMng();
    _modId = _pDBMng->getModId("CegoBTreeManager");
    _pObjMng = pObjMng;
    _pBTO = pBTO;
    _pCache = 0;
    _tabSetId = _pBTO->getTabSetId();
    _btreeName = _pBTO->getName();    
    _btreeType = _pBTO->getType();
    _btreeSchema = _pBTO->getSchema();
	    
    _keyLen = CegoBTreeValue::getKeyLen(_btreeSchema);
}

CegoBTreeManager::~CegoBTreeManager()
{
    if ( _pCache )
	delete _pCache;
}

void CegoBTreeManager::createCache()
{
    if ( _pCache )
	delete _pCache;

    _pCache = new BTreeCache();
}

void CegoBTreeManager::commit(CegoDataPointer& sysEntry)
{    
    if ( _pCache )
    {	
	CegoBufferPage* pCachePage;
	pCachePage = _pCache->getFirst();
	while ( pCachePage )
	{
	    // sync page here

	    CegoBufferPage bp;
	    _pDBMng->bufferFix(bp, _tabSetId, pCachePage->getPageId(), CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());

	    memcpy( bp.getChunkEntry(), pCachePage->getChunkEntry(), pCachePage->getChunkLen());

	    bp.setType(pCachePage->getType());
	    bp.setNextPageId(pCachePage->getNextPageId());
	    
	    _pDBMng->bufferUnfix(bp, true, _pObjMng->getLockHandler());
		
	    pCachePage = _pCache->getNext();
	}	
	delete _pCache;
	_pCache = 0;
    }

    char *p;
    unsigned len;
    CegoBufferPage rbp;
    _pObjMng->claimDataPtrUnlocked(_tabSetId, CegoBufferPool::NOSYNC, sysEntry, p, len, rbp);
    _pBTO->encode(p);
    _pObjMng->releaseDataPtrUnlocked(rbp, true);
}

void CegoBTreeManager::rollback()
{    
    if ( _pCache )
    {	
	CegoBufferPage* pCachePage;

	pCachePage = _pCache->getFirst();
	while ( pCachePage )
	{	    
	    CegoBufferPage bp;
	    _pDBMng->bufferFix(bp, _tabSetId, pCachePage->getPageId(), CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());
	    _pDBMng->bufferRelease(bp, _pObjMng->getLockHandler());
		
	    pCachePage = _pCache->getNext();
	}

	delete _pCache;
	_pCache = 0;
    }
    else
    {
	freeBTree();
    }    
}

void CegoBTreeManager::insertBTreeWithCommit(const CegoDataPointer& dp,
				   const CegoBTreeValue &iv,
				   unsigned long long tid)
{    
    // we have to keep the index object in buffer pool 
    CegoBufferPage bp;
    _pObjMng->getObjectWithFix(_tabSetId, _btreeName, _btreeType, *_pBTO, bp);
    
    CegoDataPointer sysEntry(bp.getPageId(), bp.getEntryPos());

    try 
    {
	insertBTree( dp, iv, tid);
    }
    catch ( Exception e )
    {
	_pDBMng->bufferUnfix(bp, true, _pObjMng->getLockHandler());
	throw Exception(EXLOC, Chain("Cannot insert into btree with commit"), e);
    }
    _pDBMng->bufferUnfix(bp, true, _pObjMng->getLockHandler());

    commit(sysEntry);
}

void CegoBTreeManager::insertBTree(const CegoDataPointer& dp,
				   const CegoBTreeValue &iv,
				   unsigned long long tid)
{   
    StackT<CegoBufferPage*> parentPageStack;
    StackT<CegoBufferPage*> fixedPageStack;

    unsigned long long lockId = 0;
    
    try
    {	
	PageIdType pageId = _pBTO->getDataPageId();	    
	
	if ( pageId == 0 )
	{
	    throw Exception(EXLOC, Chain("Btree object is not valid"));
	}
    	
	if ( _pCache == 0 )
	    lockId = _pObjMng->getLockHandler()->lockData(CegoObject::BTREE, pageId, CegoLockHandler::WRITE);
	
	bool isInserted = false;
	// bool isFirst = true;
	
	while ( ! isInserted )
	{
	    CegoBufferPage* pBP;

	    pBP = getPage(pageId);
	    	    
	    if ( pBP->getType() == CegoBufferPage::BTREE_NODE )
	    {	
		CegoBTreeNode traceNode;
		traceNode.setPtr(pBP->getChunkEntry(), pBP->getChunkLen());
		traceNode.setSchema(CegoBTreeNode::NODE, &_btreeSchema, _keyLen);
		traceNode.setPageId(pBP->getPageId());
		
		traceNode.getChildPage(iv, pageId, false); 

		parentPageStack.Push(pBP);
	    }
	    else if ( pBP->getType() == CegoBufferPage::BTREE_LEAF )
	    {			
		fixedPageStack.Push(pBP);
		
		CegoBTreeNode leafLeft;
		leafLeft.setPtr(pBP->getChunkEntry(), pBP->getChunkLen());
		leafLeft.setPageId(pBP->getPageId());
		leafLeft.setSchema(CegoBTreeNode::LEAF, &_btreeSchema, _keyLen);
		leafLeft.setNextPageId(pBP->getNextPageId());

		bool hasDuplicate = checkDuplicate(iv, leafLeft, tid);
		
		if ( hasDuplicate && ( _btreeType == CegoObject::PBTREE || ( _btreeType == CegoObject::UBTREE && ! ( iv.isNull(_btreeSchema) && __allowDuplicateNull))))
		{
		    Chain msg = Chain("Duplicate key ") + iv.toChain(&_btreeSchema) + Chain(" on unique btree ") + _btreeName;
		    throw Exception(EXLOC, msg);			
		}	    
	       
		if ( leafLeft.addValue(iv, dp) == false )
		{
		    CegoBTreeNode leafRight;
		    CegoBufferPage* pNewPage = allocPage(CegoBufferPage::BTREE_LEAF);
		    
		    fixedPageStack.Push(pNewPage);
		    
		    leafRight.setPtr(pNewPage->getChunkEntry(), pNewPage->getChunkLen());
		    leafRight.initNode();
		    leafRight.setPageId(pNewPage->getPageId());
		    leafRight.setSchema(CegoBTreeNode::LEAF, &_btreeSchema, _keyLen);

		    leafLeft.split(leafRight);

		    // setting up leaf pointer chain
		    pNewPage->setNextPageId(leafLeft.getNextPageId());
		    
		    leafLeft.setNextPageId(pNewPage->getPageId());

		    CegoBufferPage* pModPage;

		    pModPage = getPage(leafLeft.getPageId());
				
		    pModPage->setNextPageId(pNewPage->getPageId());

		    putPage(pModPage);
		    		    
		    CegoBTreeValue ri = leafRight.getMin();
		    
		    if ( iv.isHigher(ri, &_btreeSchema) )
		    {
			leafRight.addValue(iv, dp);
		    }
		    else
		    {
			leafLeft.addValue(iv, dp);
		    }
		    
		    CegoBTreeValue propValue = leafLeft.getMax();		    
		    CegoBTreeNode propRight = leafRight;
		    CegoBTreeNode propLeft = leafLeft;

		    // cout << "Propagating value " << propValue.toChain(&_btreeSchema) << endl;
		    bool isPropagated = false;
		    while ( ! isPropagated )
		    {
			// propagate newLeaf to parent node
			CegoBufferPage* pPropPage;
			
			if ( parentPageStack.Pop(pPropPage) )
			{
			    fixedPageStack.Push(pPropPage);
			    
			    CegoBTreeNode nodeLeft;
			    nodeLeft.setPtr(pPropPage->getChunkEntry(), pPropPage->getChunkLen());
			    nodeLeft.setPageId(pPropPage->getPageId());
			    nodeLeft.setNextPageId(pPropPage->getNextPageId());
			    nodeLeft.setSchema(CegoBTreeNode::NODE, &_btreeSchema, _keyLen);
			    
			    if ( nodeLeft.propagate(propValue, propLeft, propRight) == false )
			    {								
				propLeft = nodeLeft;
				
				// since we still have to add the right node, save it
				CegoBTreeNode addNode = propRight;

				// create new 
				CegoBufferPage* pNewPage;

				pNewPage = allocPage(CegoBufferPage::BTREE_NODE);
				
				fixedPageStack.Push(pNewPage);				
				propRight.setPtr(pNewPage->getChunkEntry(), pNewPage->getChunkLen());
				propRight.initNode();				
				propRight.setPageId(pNewPage->getPageId());
				propRight.setSchema(CegoBTreeNode::NODE, &_btreeSchema, _keyLen);

				propLeft.split(propRight);
				
				// Setting up follow up node page

				CegoBufferPage* pModPage;
				pModPage = getPage(propLeft.getPageId());

				pModPage->setNextPageId(propRight.getPageId());

				propLeft.setNextPageId(propRight.getPageId());

				putPage(pModPage);
												
				// add new node
				CegoBTreeValue iv1 = addNode.getMin();
				CegoBTreeValue iv2 = propRight.getMin();
				
				if ( iv1.isHigher(iv2, &_btreeSchema) )
				{
				    propRight.addNode(iv1, addNode);
				}
				else
				{
				    propLeft.addNode(iv1, addNode);
				}

				// setup prop value to max left
				propValue = propLeft.getMax();				
			    }
			    else
			    {			   
				isPropagated = true;
			    }
			    
			    // Setting up follow up node page
			    			    
			    if ( pPropPage->getNextPageId() )
			    {
				CegoBufferPage* pNodePageA;				
				CegoBufferPage* pNodePageB;
				CegoBTreeNode nodeA;				
				CegoBTreeNode nodeB;				
				PageIdType nodePageId;

				pNodePageA = getPage(pPropPage->getPageId());
				
				nodeA.setPtr(pNodePageA->getChunkEntry(), pNodePageA->getChunkLen());
				nodeA.setSchema(CegoBTreeNode::NODE, &_btreeSchema, _keyLen);
								
				pNodePageB = getPage(pPropPage->getNextPageId());
				
				nodeB.setPtr(pNodePageB->getChunkEntry(), pNodePageB->getChunkLen());
				nodeB.setSchema(CegoBTreeNode::NODE, &_btreeSchema, _keyLen);
				
				nodeA.getLastChildPointer(nodePageId);
				nodeB.setFirstChildPointer(nodePageId);

				putPage(pNodePageA);
				putPage(pNodePageB);	
			    }			    
			}
			else // root node reached, adding new root
			{			    			    
			    CegoBufferPage* pRootPage;

			    pRootPage = allocPage(CegoBufferPage::BTREE_NODE);

			    fixedPageStack.Push(pRootPage);				
			    
			    CegoBTreeNode rootNode;
			    rootNode.setPtr(pRootPage->getChunkEntry(), pRootPage->getChunkLen());
			    rootNode.initNode();			
			    rootNode.setPageId(pRootPage->getPageId());
			    rootNode.setSchema(CegoBTreeNode::NODE, &_btreeSchema, _keyLen);
			    
			    rootNode.propagate(propValue, propLeft, propRight);
			    
			    _pBTO->setDataPageId(pRootPage->getPageId());
			    
			    isPropagated=true;
			}
		    }		
		}
		else
		{
		    fixedPageStack.Pop(pBP);    
		    if ( _pCache == 0 )
		    {			
			_pDBMng->bufferUnfix(*pBP, true, _pObjMng->getLockHandler());
			delete pBP;
		    }
		}
		if ( hasDuplicate == false )
		{
		    _pBTO->increaseRelevance();
		}
		isInserted = true;	    
	    }
	    // isFirst = false;
	}
    }
    catch ( Exception e )
    {
	if ( _pCache == 0 )
	{
	    CegoBufferPage* pBP;
	    while ( parentPageStack.Pop(pBP) )
	    {
		_pDBMng->bufferUnfix(*pBP, true, _pObjMng->getLockHandler());
		delete pBP;
	    }	    
	    while ( fixedPageStack.Pop(pBP) )
	    {
		_pDBMng->bufferUnfix(*pBP, true, _pObjMng->getLockHandler());	    
		delete pBP;
	    }
	    if ( lockId  ) 
		_pObjMng->getLockHandler()->unlockData(CegoObject::BTREE, lockId);
	}
	throw Exception(EXLOC, Chain("Cannot insert into btree"), e);
    }

    if ( _pCache == 0 )
    {
	// unfix node pages on stack
	CegoBufferPage* pBP;
	while ( parentPageStack.Pop(pBP) )
	{
	    _pDBMng->bufferUnfix(*pBP, true, _pObjMng->getLockHandler());
	    delete pBP;
	}	
	while ( fixedPageStack.Pop(pBP) )
	{
	    _pDBMng->bufferUnfix(*pBP, true, _pObjMng->getLockHandler());	
	    delete pBP;
	}	
	
	_pObjMng->getLockHandler()->unlockData(CegoObject::BTREE, lockId);
    }
}

/* 
   The checkDuplicate method checks subsequent btree page for a duplicate entry
   until an entry is found which is higher than the current
*/

bool CegoBTreeManager::checkDuplicate(const CegoBTreeValue &iv, const CegoBTreeNode &leaf, unsigned long long tid)
{
    bool foundHigher=false;  
    bool hasDuplicate = false;
    
    if ( leaf.valueExists(iv, _pObjMng, _tabSetId, tid) )
    {
	hasDuplicate=true;
    }
    else
    {
	if ( leaf.numEntries() > 0 )
	    foundHigher = leaf.getMax().isHigher(iv, &_btreeSchema);
    }
        
    PageIdType nextPageId = leaf.getNextPageId();

    while ( hasDuplicate == false && foundHigher == false && nextPageId != 0 )
    {
	// now we still have to check the next following page for duplicates
	
	CegoBufferPage nbp;
	_pDBMng->bufferFix(nbp, _tabSetId, nextPageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());
	
	CegoBTreeNode leafNext;
	leafNext.setPtr(nbp.getChunkEntry(), nbp.getChunkLen());
	leafNext.setPageId(nbp.getPageId());
	leafNext.setSchema(CegoBTreeNode::LEAF, &_btreeSchema, _keyLen);
	
	if ( leafNext.valueExists(iv, _pObjMng, _tabSetId, tid) )
	{
	    hasDuplicate=true;
	}
	else
	{
	    if ( leafNext.numEntries() > 0 )
		// if an entry exists in the followup node, it must be higher
		// because equal value does not exist
		// foundHigher = leafNext.getMax().isHigher(iv, &_btreeSchema);
		foundHigher = true; 
	}
	
	nextPageId = leafNext.getNextPageId();
	
	_pDBMng->bufferUnfix(nbp, false, _pObjMng->getLockHandler());
    }

    return hasDuplicate;
}

void CegoBTreeManager::deleteBTree(const CegoBTreeValue &iv,
				   const CegoDataPointer& dp,
    				   unsigned long long tid)
{    
    PageIdType pageId = _pBTO->getDataPageId();
    
    if ( pageId == 0 )
    {
	throw Exception(EXLOC, Chain("Btree object is not valid"));
    }

    CegoBufferPage rootBP;
    _pDBMng->bufferFix(rootBP, _tabSetId, pageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());
        
    // we have to keep the index object in buffer pool 
    CegoBufferPage bp;
    _pObjMng->getObjectWithFix(_tabSetId, _btreeName, _btreeType, *_pBTO, bp);
    
    CegoDataPointer sysEntry(bp.getPageId(), bp.getEntryPos());

    try 
    {
	deleteBTree(sysEntry, iv, dp, tid);
    }
    catch ( Exception e )
    {
	_pDBMng->bufferUnfix(bp, true, _pObjMng->getLockHandler());
	throw Exception(EXLOC, Chain("Cannot delete from btree"), e);
    }
    _pDBMng->bufferUnfix(bp, true, _pObjMng->getLockHandler());
    _pDBMng->bufferUnfix(rootBP, true, _pObjMng->getLockHandler());    
}

void CegoBTreeManager::deleteBTree(const CegoDataPointer& sysEntry, 
				   const CegoBTreeValue &iv,
				   const CegoDataPointer& dp,
				   unsigned long long tid)
{
    PageIdType pageId = _pBTO->getDataPageId();
        
    if ( pageId == 0 )
    {
	throw Exception(EXLOC, Chain("Btree object is not valid"));
    }
    
    bool isDeleted = false;
    StackT<CegoBufferPage> parentPageStack;
    unsigned long long lockId = 0;
    
    try
    {
	lockId = _pObjMng->getLockHandler()->lockData(CegoObject::BTREE, pageId, CegoLockHandler::WRITE);
	
	while ( ! isDeleted )
	{	    
	    CegoBufferPage bp;
	    
	    _pDBMng->bufferFix(bp, _tabSetId, pageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());
	    
	    if ( bp.getType() == CegoBufferPage::BTREE_NODE )
	    {	
		CegoBTreeNode traceNode;
		traceNode.setPtr(bp.getChunkEntry(), bp.getChunkLen());	    
		traceNode.setSchema(CegoBTreeNode::NODE, &_btreeSchema, _keyLen);
		traceNode.setPageId(bp.getPageId());
		
		// traceNode.printNode();
		
		traceNode.getChildPage(iv, pageId, true);
		
		parentPageStack.Push(bp);
	    }
	    else if ( bp.getType() == CegoBufferPage::BTREE_LEAF )
	    {	
		CegoBTreeNode leaf;
		leaf.setPtr(bp.getChunkEntry(), bp.getChunkLen());
		leaf.setPageId(bp.getPageId());
		leaf.setSchema(CegoBTreeNode::LEAF, &_btreeSchema, _keyLen);
		leaf.setNextPageId(bp.getNextPageId());

		PageIdType leafPrevPageId = 0;		
		PageIdType leafPageId = bp.getPageId();
		PageIdType leafNextPageId = bp.getNextPageId();
		
		while ( isDeleted == false )
		{
		    isDeleted = leaf.deleteValue(iv, dp);
		    if ( isDeleted == false )
		    {
			if ( leafNextPageId != 0 )
			{
			    _pDBMng->bufferUnfix(bp, false, _pObjMng->getLockHandler());
			    _pDBMng->bufferFix(bp, _tabSetId, leafNextPageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());
			    leaf.setPtr(bp.getChunkEntry(), bp.getChunkLen());

			    leafPrevPageId = leafPageId;			    
			    leafPageId = bp.getPageId();
			    leafNextPageId = bp.getNextPageId();			    
			}
			else
			{
			    Chain val = iv.toChain(&_btreeSchema);
			    Chain dpstr = dp.toChain();
			    _pDBMng->bufferUnfix(bp, true, _pObjMng->getLockHandler());
			    throw Exception(EXLOC, Chain("Entry ") + val + dpstr + Chain(" not found in btree"));
			}
		    }
		}
		
		_pDBMng->bufferUnfix(bp, true, _pObjMng->getLockHandler());
		
		// check for duplicates
		
		bool hasDuplicate=false;
		
		CegoBufferPage cbp;
		_pDBMng->bufferFix(cbp, _tabSetId, leafPageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());
		leaf.setPtr(cbp.getChunkEntry(), cbp.getChunkLen());
		
		if ( leaf.valueExists(iv, _pObjMng, _tabSetId, tid) )
		{
		    hasDuplicate=true;
		}
		
		if ( hasDuplicate == false && leafPrevPageId != 0 )
		{
		    // now we still have to check the next following page for duplicates
		    CegoBufferPage pbp;
		    _pDBMng->bufferFix(pbp, _tabSetId, leafPrevPageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());
		    leaf.setPtr(pbp.getChunkEntry(), pbp.getChunkLen());
		    if ( leaf.valueExists(iv, _pObjMng, _tabSetId, tid) )
		    {
			hasDuplicate=true;
		    }		    
		    _pDBMng->bufferUnfix(pbp, false, _pObjMng->getLockHandler());
		}
		
		if ( hasDuplicate == false && leafNextPageId != 0 )
		{
		    // now we still have to check the next following page for duplicates
		    CegoBufferPage nbp;
		    _pDBMng->bufferFix(nbp, _tabSetId, leafNextPageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());
		    leaf.setPtr(nbp.getChunkEntry(), nbp.getChunkLen());
		    if ( leaf.valueExists(iv, _pObjMng, _tabSetId, tid) )
		    {
			hasDuplicate=true;
		    }		    
		    _pDBMng->bufferUnfix(nbp, false, _pObjMng->getLockHandler());
		}
	
		_pDBMng->bufferUnfix(cbp, false, _pObjMng->getLockHandler());
		
		if ( hasDuplicate == false )
		{		    
		    _pBTO->decreaseRelevance();
		    char *p;
		    unsigned len;
		    CegoBufferPage bp;
		    _pObjMng->claimDataPtrUnlocked(_tabSetId, CegoBufferPool::NOSYNC, sysEntry, p, len, bp);
		    _pBTO->encode(p);
		    _pObjMng->releaseDataPtrUnlocked(bp, true);		    
		}
	    }
	}
    }
    catch ( Exception e ) 
    {
	CegoBufferPage bp;
	while ( parentPageStack.Pop(bp) )
	{
	    _pDBMng->bufferUnfix(bp, true, _pObjMng->getLockHandler());
	}
	if ( lockId )
	    _pObjMng->getLockHandler()->unlockData(CegoObject::BTREE, lockId);	
	throw Exception(EXLOC, Chain("Cannot delete from btree"), e);
    }

    // unfix node pages on stack
    CegoBufferPage bp;
    while ( parentPageStack.Pop(bp) )
    {
	_pDBMng->bufferUnfix(bp, true, _pObjMng->getLockHandler());
    }

    _pObjMng->getLockHandler()->unlockData(CegoObject::BTREE, lockId);
}

CegoBufferPage* CegoBTreeManager::allocPage(CegoBufferPage::PageType type)
{    
    bool doAppend = true;
    
    if ( _pCache )
    {
	// for performance improvments, we use a low level allocation on file handler layer
	// we can do since, since the page is not needed in buffercache, just a valid pageid has to be allocated
		
	PageIdType pageId;	
	unsigned* fbm;
	unsigned fbmSize=0;
	CegoFileHandler::FileType ft = CegoFileHandler::DATAFILE;
	bool doAppend = true;

	_pDBMng->allocatePage(_tabSetId, ft, pageId, _pObjMng->getLockHandler(), fbm, fbmSize, doAppend);

	CegoBufferPage bp;
	bp.setPageId(pageId);
	bp.setPageSize(_pDBMng->getPageSize());
	
	CegoBufferPage* pCachePage = _pCache->newCachePage(bp, type, false);
	return pCachePage;
    }
    else
    {
	CegoBufferPage* pBP = new CegoBufferPage();
	_pObjMng->getNewFilePage(*pBP, _tabSetId, CegoObject::BTREE, true, doAppend);		    
	pBP->initPage(type);     
	return pBP;
    }
}

CegoBufferPage* CegoBTreeManager::getPage(PageIdType pageId)
{
    CegoBufferPage *pBP = 0;
    if ( _pCache )
    {
	pBP = _pCache->getCachePage(pageId);
	if ( pBP == 0 )
	{
	    CegoBufferPage poolPage;
	    _pDBMng->bufferFix(poolPage, _tabSetId, pageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());
	    pBP = _pCache->newCachePage(poolPage, poolPage.getType(), true);	    
	    _pDBMng->bufferUnfix(poolPage, true, _pObjMng->getLockHandler());
	}
    }
    else
    {	
	pBP = new CegoBufferPage();
	_pDBMng->bufferFix(*pBP, _tabSetId, pageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());
    }
    return pBP;
}

void CegoBTreeManager::putPage(CegoBufferPage* pBP)
{
    // we just have to do something in uncached mode
    if ( _pCache == 0 )
    {
	_pDBMng->bufferUnfix(*pBP, true, _pObjMng->getLockHandler());
	delete pBP;
    }
}

bool CegoBTreeManager::verifyBTree(CegoAdminHandler* pAH)
{
    PageIdType pageId = _pBTO->getDataPageId();
        
    if ( pageId == 0 )
    {
	return false;
    }
    _verifyCount=0;
    
    bool result = verifyNode(pageId, pAH);

    pAH->sendInfo(Chain("... ")
		  + Chain(_verifyCount)
		  + Chain(" ")
		  + _btreeName
		  + Chain(" nodes")
		  + Chain("\n"));

    return result;
}

bool CegoBTreeManager::verifyNode(PageIdType pageId, CegoAdminHandler* pAH)
{

    _verifyCount++;

    if ( _verifyCount % 1000 == 0 )
	pAH->sendInfo(Chain("... ")
		      + Chain(_verifyCount)
		      + Chain(" ")
		      + _btreeName		      
		      + Chain(" nodes")
		      + Chain("\r"));
    
    CegoBufferPage bp;
    
    _pDBMng->bufferFix(bp, _tabSetId, pageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());

    unsigned numError = 0;
    
    CegoBTreeNode traceNode;
    traceNode.setPtr(bp.getChunkEntry(), bp.getChunkLen());
    traceNode.setPageId(pageId);
    traceNode.setNextPageId(bp.getNextPageId());

    if ( bp.getType() == CegoBufferPage::BTREE_NODE )
    {
	traceNode.setSchema(CegoBTreeNode::NODE, &_btreeSchema, _keyLen);
	
	if ( traceNode.verify() == false )
	    numError++;
	
	PageIdType childPid;
	
	traceNode.reset();
	bool nodeValid = true;
	while ( traceNode.nextChildPointer(childPid) && nodeValid)
	{	    
	    if ( (nodeValid = verifyNode(childPid, pAH)) == false )
		numError++;
	}
    }
    else
    {
	traceNode.setSchema(CegoBTreeNode::LEAF, &_btreeSchema, _keyLen);
	if ( traceNode.verifyLeafFull(_tabSetId, _pObjMng) == false )
	    numError++;
    }
    
    _pDBMng->bufferUnfix(bp, false, _pObjMng->getLockHandler());
    return numError == 0;
}

void CegoBTreeManager::dumpBTree(Chain& chainDump)
{
    PageIdType pageId = _pBTO->getDataPageId();

    // TODO 
    chainDump = Chain("Root Page is ") + Chain(pageId) + Chain("\n");
    
    if ( pageId == 0 )
    {
	throw Exception(EXLOC, Chain("Btree object is not valid"));
    }

    dumpNode(chainDump, 0, pageId);
}

void CegoBTreeManager::dumpNode(Chain& chainDump, unsigned level, PageIdType pageId)
{
    CegoBufferPage bp;
    
    _pDBMng->bufferFix(bp, _tabSetId, pageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());

    CegoBTreeNode traceNode;
    traceNode.setPtr(bp.getChunkEntry(), bp.getChunkLen());	    

    traceNode.setPageId(pageId);
    traceNode.setNextPageId(bp.getNextPageId());

    // cout << "PagePtr = " << (long long)bp.getPagePtr() << endl;

    chainDump += Chain("Node Page is ") + Chain(pageId) + Chain("\n");
    // cout << "Page = " << pageId << endl;
    
    if ( bp.getType() == CegoBufferPage::BTREE_NODE )
    {
	traceNode.setSchema(CegoBTreeNode::NODE, &_btreeSchema, _keyLen);
	traceNode.dump(chainDump, level);
	
	PageIdType childPid;
	
	traceNode.reset();
	while ( traceNode.nextChildPointer(childPid) )
	{
	    dumpNode(chainDump, level + 3, childPid);  
	}
    }
    else
    {
	traceNode.setSchema(CegoBTreeNode::LEAF, &_btreeSchema, _keyLen);
	traceNode.dump(chainDump, level);
    }
   
    _pDBMng->bufferUnfix(bp, false, _pObjMng->getLockHandler());
}

unsigned CegoBTreeManager::freeBTree()
{
    PageIdType firstLeafPageId = 0;
    bool isFirst = true;

    PageIdType pageId = _pBTO->getDataPageId();

    unsigned pageCount;

    if ( pageId == 0 )
    {
	pageCount = 0;
    }
    else
    {   
	pageCount = freeNodePages(pageId, firstLeafPageId, isFirst);
	pageCount += freeLeafPages(firstLeafPageId);
    }

    return pageCount;
}

unsigned CegoBTreeManager::freeNodePages(PageIdType pageId, PageIdType& firstLeafPageId, bool& isFirst)
{
    unsigned pageCount = 0;

    CegoBufferPage bp;    
    _pDBMng->bufferFix(bp, _tabSetId, pageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());

    if ( bp.getType() == CegoBufferPage::BTREE_NODE )
    {
	CegoBTreeNode traceNode;
	traceNode.setPtr(bp.getChunkEntry(), bp.getChunkLen());	    
	traceNode.setSchema(CegoBTreeNode::NODE, &_btreeSchema, _keyLen);
	traceNode.setPageId(pageId);

	PageIdType childPid;
	
	traceNode.reset();

	// if the node is not on the left edge, we ignore the first entry.
	// the child nodes is already referenced by the adjacent left node on the same level.
       
	if ( isFirst == false )
	    traceNode.nextChildPointer(childPid);
	
	while ( traceNode.nextChildPointer(childPid) )
	{	    
	    pageCount += freeNodePages(childPid, firstLeafPageId, isFirst);
	}

	pageCount++;
	_pDBMng->bufferRelease(bp, _pObjMng->getLockHandler());
    }
    else
    {
	if ( isFirst )
	{
	    firstLeafPageId = pageId;
	    isFirst=false;
	}
	_pDBMng->bufferUnfix(bp, true, _pObjMng->getLockHandler());
    }
    
    return pageCount;
}

unsigned CegoBTreeManager::freeLeafPages(PageIdType pageId)
{
    unsigned pageCount = 0;

    while ( pageId )
    {
	CegoBufferPage bp;    
	_pDBMng->bufferFix(bp, _tabSetId, pageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());
	pageId=bp.getNextPageId();
	pageCount++;
       	
	_pDBMng->bufferRelease(bp, _pObjMng->getLockHandler());
    }
    return pageCount;
}

unsigned CegoBTreeManager::traceBTree()
{
    PageIdType firstLeafPageId = 0;
    bool isFirst = true;

    PageIdType pageId = _pBTO->getDataPageId();

    unsigned pageCount;

    if ( pageId == 0 )
    {
	pageCount = 0;
    }
    else
    {
	pageCount = traceNodePages(pageId, firstLeafPageId, isFirst);
	pageCount += traceLeafPages(firstLeafPageId);
    }
    return pageCount;
}

unsigned CegoBTreeManager::traceNodePages(PageIdType pageId, PageIdType& firstLeafPageId, bool& isFirst)
{
    unsigned pageCount = 0;

    CegoBufferPage bp;    
    _pDBMng->bufferFix(bp, _tabSetId, pageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());

    if ( bp.getType() == CegoBufferPage::BTREE_NODE )
    {
	CegoBTreeNode traceNode;
	traceNode.setPtr(bp.getChunkEntry(), bp.getChunkLen());	    
	traceNode.setSchema(CegoBTreeNode::NODE, &_btreeSchema, _keyLen);
	traceNode.setPageId(pageId);

	PageIdType childPid;
	
	traceNode.reset();

	// if the node is not on the left edge, we ignore the first entry.
	// the child nodes is already referenced by the adjacent left node on the same level.
       
	if ( isFirst == false )
	    traceNode.nextChildPointer(childPid);
	
	while ( traceNode.nextChildPointer(childPid) )
	{	    
	    pageCount += traceNodePages(childPid, firstLeafPageId, isFirst);
	}

	pageCount++;

	_pDBMng->commitPageEntry(pageId);
	
	_pDBMng->bufferUnfix(bp, false, _pObjMng->getLockHandler());
    }
    else
    {
	if ( isFirst )
	{
	    firstLeafPageId = pageId;
	    isFirst=false;
	}
	_pDBMng->bufferUnfix(bp, true, _pObjMng->getLockHandler());
    }
    
    return pageCount;
}

unsigned CegoBTreeManager::traceLeafPages(PageIdType pageId)
{
    unsigned pageCount = 0;

    while ( pageId )
    {
	CegoBufferPage bp;    
	_pDBMng->bufferFix(bp, _tabSetId, pageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());

	_pDBMng->commitPageEntry(pageId);
	
	pageId=bp.getNextPageId();
	pageCount++;
		
	_pDBMng->bufferUnfix(bp, false,  _pObjMng->getLockHandler());
    }
    return pageCount;
}

unsigned CegoBTreeManager::getNumPages()
{    
    PageIdType firstLeafPageId = 0;
    bool isFirst = true;

    PageIdType pageId = _pBTO->getDataPageId();
    
    unsigned pageCount = 0;

    if ( pageId == 0 )
    {
	pageCount = 0;
    }
    else
    {
	unsigned numNodePage = countNodePages(pageId, firstLeafPageId, isFirst);
	unsigned numLeafPage = countLeafPages(firstLeafPageId);
	pageCount = numNodePage + numLeafPage;
    }
    return pageCount;
}

unsigned CegoBTreeManager::countNodePages(PageIdType pageId, PageIdType& firstLeafPageId, bool& isFirst)
{
    unsigned pageCount = 0;

    CegoBufferPage bp;
   
    _pDBMng->bufferFix(bp, _tabSetId, pageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());

    if ( bp.getType() == CegoBufferPage::BTREE_NODE )
    {
	CegoBTreeNode traceNode;
	traceNode.setPtr(bp.getChunkEntry(), bp.getChunkLen());	    
	traceNode.setSchema(CegoBTreeNode::NODE, &_btreeSchema, _keyLen);
	traceNode.setPageId(pageId);

	PageIdType childPid;
	
	traceNode.reset();
	if ( isFirst == false )
	    traceNode.nextChildPointer(childPid);
	
	while ( traceNode.nextChildPointer(childPid) )
	{
	    pageCount += countNodePages(childPid, firstLeafPageId, isFirst);
	}
	pageCount++;
    }
    else
    {
	if ( isFirst )
	{
	    firstLeafPageId = pageId;
	    isFirst=false;
	}	
    }
   
    _pDBMng->bufferUnfix(bp, false, _pObjMng->getLockHandler());
    return pageCount;
}

unsigned CegoBTreeManager::countLeafPages(PageIdType pageId)
{
    unsigned pageCount = 0;

    while ( pageId )
    {	
	CegoBufferPage bp;    
	_pDBMng->bufferFix(bp, _tabSetId, pageId, CegoBufferPool::NOSYNC, _pObjMng->getLockHandler());
	pageId=bp.getNextPageId();
	pageCount++;
       	
	_pDBMng->bufferUnfix(bp, false, _pObjMng->getLockHandler());
    }
    return pageCount;
}
