///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoRecoveryManager.cc
// ----------------------
// Cego recovery manager class implementation
//                                                         
// Design and Implementation by Bjoern Lemke               
//
// (C)opyright 2000-2025 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: CegoRecoveryManager
// 
// Description: Transaction recovery handling
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

// LFC INCLUDES
#include <lfcbase/SetT.h>
#include <lfcbase/Datetime.h>
#include <lfcbase/Sleeper.h>
#include <lfcbase/CommandExecuter.h>

// CEGO INCLUDES
#include "CegoXMLdef.h"
#include "CegoRecoveryManager.h"
#include "CegoPredicate.h"

extern long long __recoveryTS;

CegoRecoveryManager::CegoRecoveryManager(CegoQueryManager *pGTM)
{
    _pGTM = pGTM;
    _pLogger = _pGTM->getDBMng();
    _pDBMng = _pGTM->getDBMng();
    _modId = _pDBMng->getModId("CegoRecoveryManager");
}

CegoRecoveryManager::~CegoRecoveryManager()
{
}

unsigned long long CegoRecoveryManager::startRecovery(const Chain& tableSet)
{
    
    Chain status = _pDBMng->getTableSetRunState(tableSet);
    if ( status != Chain(XML_OFFLINE_VALUE) )
    {
	throw Exception(EXLOC, Chain("Cannot start recovery of tableset in status ") + status);
    }

    unsigned tabSetId = _pDBMng->getTabSetId(tableSet);

    _pDBMng->setRecoveryMode(tabSetId, CegoDatabaseManager::ON);
    
    // we have to set tableset run state AFTER ticket restore
    _pDBMng->setTableSetRunState(tableSet, XML_RECOVERY_VALUE);
   
    _pDBMng->releaseLogFiles(tableSet, true);
	
    // during recovery, we do not write to online log files
    _pDBMng->stopLog(tabSetId);	

    _pGTM->regDataFiles(tableSet);

    _pDBMng->adjustPageOffset();
           
    _pGTM->registerObjects(tableSet);


    unsigned long long cplsn  = _pDBMng->getCommittedLSN(tableSet);	
    _pDBMng->log(_modId, Logger::NOTICE, Chain("Committed lsn = ") + Chain(cplsn));
    
    cplsn++;
        
    while ( _pDBMng->getRecoveryMode(tabSetId) != CegoDatabaseManager::REQOFF )
    {
		
	unsigned long long lsn = _pDBMng->getCommittedLSN(tableSet);
	lsn++;
	Chain archLogFile = _pDBMng->getArchiveLogName(tableSet, lsn);
		
	ListT<Chain> archIdList;
	ListT<Chain> archPathList;
	
	_pDBMng->getArchLogInfo(tabSetId, archIdList, archPathList);
	
	Chain *pArchLogPath = archPathList.First();

	bool fileFound = false;

	Chain sourceFileName;
	
	while ( pArchLogPath  && fileFound == false)
	{
	    sourceFileName = *pArchLogPath + Chain("/") + archLogFile;
	    
	    File checkFile(sourceFileName);
	    
	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Checking logfile ") + sourceFileName + Chain(" ..."));
	    
	    if ( checkFile.exists() )
	    {		    
		fileFound = true;		    
	    }
	    else
	    {
		pArchLogPath = archPathList.Next();
	    }
	}
	    
	// if we found the appropriate file, we try to recover it
	if ( fileFound )
	{

	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Recovering offline logfile ") + sourceFileName + Chain(" ..."));
	    _pDBMng->setLogFile(tabSetId, sourceFileName, true);		
	    	    
	    unsigned long long lsn = 0;
	    long long ts = 0;
	    RecoveryState rstate = recoverCurrentTransactionLog(tabSetId, 0, 0, lsn, ts);
	    
	    // set current lsn
	    _pDBMng->setCurrentLSN(tabSetId, lsn);

	    cplsn = lsn;
	    
	    // skip to next expected lsn
	    lsn++;
	    
	    if ( rstate == CegoRecoveryManager::RECOK )
	    {
		_pDBMng->log(_modId, Logger::NOTICE, Chain("Transaction recovery ok"));
	    }
	    else if ( rstate == CegoRecoveryManager::RECPITREACHED )
	    {	      
		_pDBMng->log(_modId, Logger::NOTICE, Chain("PIT reached"));
	    }
	    else
	    {
		throw Exception(EXLOC, Chain("Unknwon recovery state"));
	    }
	    
	    _pDBMng->writeCheckPoint(tableSet, false, false, _pGTM->getLockHandle());
	}
	else
	{	
	    Sleeper s;
	    s.secSleep(LOGMNG_RECOVERY_DELAY);
	}
    }

    // if recovery is finished, we still have to initialize online redo logs
    _pDBMng->initLogFiles(tableSet, true);

    
    _pDBMng->setTableSetRunState(tableSet, XML_OFFLINE_VALUE);
    _pDBMng->setRecoveryMode(tabSetId, CegoDatabaseManager::OFF);

    return cplsn;
}
    
unsigned long long CegoRecoveryManager::recoverTableSet(const Chain& tableSet, long long pit, unsigned long long targetlsn, CegoAdminHandler* pAH)
{   
    _pLogger->log(_modId, Logger::NOTICE, Chain("Recovering tableset ") + tableSet + Chain(" ..."));

    char *pShell = getenv(CGEXESHELLVARNAME);
    if ( pShell == NULL )
    {
	_shellCmd = Chain(CGSTDEXESHELL);
    }
    else
    {
	_shellCmd = Chain(pShell);
    }
    
    unsigned tabSetId = _pDBMng->getTabSetId(tableSet);

    try
    {	

	// we first have to check, if file recovery is needed 
	bool needFileRecovery=false;
	
	Chain status = _pDBMng->getTableSetRunState(tableSet);
	if ( status == Chain(XML_INCOMPLETE_VALUE) )
	{
	    needFileRecovery=true;
	}
	
	// now we can set state to recovery, this is needed to release log files
	_pDBMng->setTableSetRunState(tableSet, XML_RECOVERY_VALUE);
	
	if ( pAH ) 
	    pAH->sendInfo(Chain("Releasing log files ...\n")); 
	_pDBMng->releaseLogFiles(tableSet, true);
	

	// during recovery, we do not write to online log files
	if ( pAH ) 
            pAH->sendInfo(Chain("Stopping log ...\n")); 
	_pDBMng->stopLog(tabSetId);	


	// register data files, this is also needed for data file recovery
	
	if ( pAH ) 
            pAH->sendInfo(Chain("Registering datafiles ...\n")); 
	_pGTM->regDataFiles(tableSet);

	_pDBMng->adjustPageOffset();


	// retrieve committed lsn and skip forward to next lsn to start recovery
	
	unsigned long long cplsn  = _pDBMng->getCommittedLSN(tableSet);	
	_pDBMng->log(_modId, Logger::NOTICE, Chain("Committed lsn = ") + Chain(cplsn));
	
	cplsn++;

	// check, if file recovery is required
	// during file recovery, saved file bitmap and page information is restored to the datafiles
	if ( needFileRecovery )
	{
	    
	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Starting datafile recovery for tableset ")
			 + tableSet
			 + Chain(" from lsn=") + Chain(cplsn));
	    unsigned long long lsn = dataFileRecovery(tableSet, tabSetId, cplsn, pAH);
	    
	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Datafile recovery for tableset ") + tableSet + Chain(" finished with lsn ") + Chain(lsn));    

	}
	
	// now we can register objects. this must be done after file recovery,
	// since during registration, pages are loaded into bufferpool
	// file recovery restores bitmap and page information  on file level ( via CegoFileHandler::writePage
	// without reloading it to bufferpool
	
	if ( pAH ) 
            pAH->sendInfo(Chain("Registering objects ...\n")); 
	_pGTM->registerObjects(tableSet);

      	
	if ( pAH ) 
	    pAH->sendInfo(Chain("Committed lsn is ") + Chain(cplsn) + Chain("\n"));
	
	if ( pit == 0 && targetlsn == 0 )
	{
	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Starting up-to-crash transaction recovery for tableset ") + tableSet + Chain(" ..."));
	    if ( pAH ) 
		pAH->sendInfo(Chain("Starting up-to-crash recovery ...\n"));
	}
	else
	{
	    Chain targetPoint;
	    if ( pit > 0 )
	    {
		Datetime d(pit);
		targetPoint = Chain("PIT=") + d.asChain();
	    }
	    else
	    {
		targetPoint = Chain("LSN=") + Chain(targetlsn);
	    }
	    
	    _pDBMng->log(_modId, Logger::NOTICE, 
			 Chain("Starting point-in-time transaction recovery to ") + targetPoint + Chain(" for tableset ") + tableSet + Chain(" ..."));
	    if ( pAH ) 
		pAH->sendInfo(Chain("Starting point-in-time recovery ...\n"));
	}

	// during transaction recovery phase, we must ignore invalid btrees/index objects
	// since these objects could be invalidated but Log data integrity and consistency is ensured 
	_pGTM->setIgnoreInvalid(true);
	unsigned long long lsn = transactionRecovery(tableSet, tabSetId, cplsn, pit, targetlsn, pAH);
	_pGTM->setIgnoreInvalid(false);
	
	// correct invalidated index an btrees
	_pGTM->correctTableSet(tabSetId, false);
	
	if ( pAH ) 
	    pAH->sendInfo(Chain("Init logfiles ...\n"));
	_pDBMng->initLogFiles(tableSet, true);
	
	_pDBMng->setActiveLogFile(tableSet);
	
	if ( pAH ) 
	    pAH->sendInfo(Chain("Starting log ...\n"));
	_pDBMng->startLog(tabSetId);
        
	CegoLogRecord logRec;    
	CegoLogRecord lr;
	lr.setAction(CegoLogRecord::LOGREC_SYNC);
	
	_pDBMng->logIt(tabSetId, lr, _pGTM->getLockHandle());
	
	_pDBMng->setTableSetRunState(tableSet, XML_ONLINE_VALUE);    
	
	_pDBMng->setRecoveryMode(tabSetId, CegoDatabaseManager::OFF );

	lsn = _pDBMng->getCurrentLSN(tabSetId);
	
	_pDBMng->setCommittedLSN(tabSetId, lsn);
		
	_pGTM->registerObjects(tableSet);

	if ( pit != 0 || targetlsn != 0 )
	{
	    // with a point-in-time recovery, we need new online backup
	    _pDBMng->nextTableSetBackupBranch(tableSet);
	}
	
	if ( pAH ) 
	    pAH->sendInfo(Chain("Writing final checkpoint ...\n"));
	_pDBMng->writeCheckPoint(tableSet, true, false, _pGTM->getLockHandle());

	_pDBMng->log(_modId, Logger::NOTICE, Chain("Recovery for tableset ") + tableSet + Chain(" finished"));
	
	return lsn;	
    }
    catch ( Exception e )
    {
	Chain msg = e.getBaseMsg();

	_pDBMng->setTableSetRunState(tableSet, XML_OFFLINE_VALUE);
	_pDBMng->setRecoveryMode(tabSetId, CegoDatabaseManager::OFF );
	_pDBMng->releaseFiles(tabSetId);
	
	throw Exception(EXLOC, Chain("Recovery failed : ") + msg);
    }
}

unsigned long long CegoRecoveryManager::dataFileRecovery(const Chain& tableSet, unsigned tabSetId, unsigned long long cplsn, CegoAdminHandler* pAH)
{

    bool endOfBackup = false;   
    unsigned long long lsn = cplsn;

    while ( endOfBackup == false )
    {	
	Chain archLogFileName = _pDBMng->getArchiveLogName(tableSet, lsn);
	
	ListT<Chain> archIdList;
	ListT<Chain> archPathList;
	
	_pDBMng->getArchLogInfo(tabSetId, archIdList, archPathList);
	
	Chain *pArchLogPath = archPathList.First();
	Chain sourceFileName;	
	bool fileFound = false;

	while ( pArchLogPath  && fileFound == false )
	{			
	    sourceFileName = *pArchLogPath + Chain("/") + archLogFileName;
	    
	    File checkFile(sourceFileName);

	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Checking file ") + sourceFileName);
	    
	    if ( checkFile.exists() )
	    {		    
		fileFound = true;		
	    }
	    else
	    {
		pArchLogPath = archPathList.Next();
	    }
	}

	if ( fileFound )
	{
	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Recovering offline logfile ") + sourceFileName + Chain(" ..."));
	    _pDBMng->setLogFile(tabSetId, sourceFileName, true);

	    if ( pAH ) 
		pAH->sendInfo(Chain("Datafile recovery with ") + sourceFileName + Chain("\n")); 
	    // recover offline logs
	    lsn = recoverCurrentDataFileLog(tabSetId, endOfBackup);

	    // set current lsn, not really needed for datafile recovery
	    _pDBMng->setCurrentLSN(tabSetId, lsn);
	    // skip to next lsn
	    lsn++;
	    
	}
	else
	{
	    if  ( restoreLogFile(tableSet, lsn) == false)
		endOfBackup = true;
	}
    }

    return lsn;
}

unsigned long long CegoRecoveryManager::transactionRecovery(const Chain& tableSet, unsigned tabSetId, unsigned long long cplsn, long long pit, unsigned long long targetlsn, CegoAdminHandler* pAH)
{
    bool endOfRecovery = false;    
    Chain sourceFileName;
    long long ts = 0;
    unsigned long long lsn = cplsn;
    RecoveryState rstate = CegoRecoveryManager::RECOK;

    _isCrashRecovery = false;
    
    while ( rstate != CegoRecoveryManager::RECPITREACHED
	    && endOfRecovery == false)
    {	
	bool fileFound = false;   
	
	while ( fileFound == false && endOfRecovery == false ) 
	{	    
	    Chain archLogFileName = _pDBMng->getArchiveLogName(tableSet, lsn);
	    
	    ListT<Chain> archIdList;
	    ListT<Chain> archPathList;
	    
	    _pDBMng->getArchLogInfo(tabSetId, archIdList, archPathList);
	    
	    Chain *pArchLogPath = archPathList.First();
	    
	    while ( pArchLogPath  && fileFound == false)
	    {
		sourceFileName = *pArchLogPath + Chain("/") + archLogFileName;
		
		File checkFile(sourceFileName);
		
		_pDBMng->log(_modId, Logger::NOTICE, Chain("Checking logfile ") + sourceFileName + Chain(" ..."));
		
		if ( checkFile.exists() )
		{		    
		    fileFound = true;		    
		}
		else
		{
		    pArchLogPath = archPathList.Next();
		}
	    }
	    
	    if ( fileFound == false )
	    {
		if  ( restoreLogFile(tableSet, lsn) == false)
		    endOfRecovery = true;		
	    }
	    if ( fileFound == false )
	    {
		Sleeper s;
		s.secSleep(LOGMNG_RECOVERY_DELAY);
	    }
	}
	
	// if we found the appropriate file, we try to recover it
	if ( fileFound )
	{
	    bool isRecovered = false;
	    while ( ! isRecovered ) 
	    {		
		_pDBMng->log(_modId, Logger::NOTICE, Chain("Recovering offline logfile ") + sourceFileName + Chain(" ..."));
		_pDBMng->setLogFile(tabSetId, sourceFileName, true);		
		
		if ( pAH ) 
		    pAH->sendInfo(Chain("Transaction recovery with ") + sourceFileName + Chain("\n"));
		
		rstate =  recoverCurrentTransactionLog(tabSetId, pit, targetlsn, lsn, ts);
		
		// set current lsn
		_pDBMng->setCurrentLSN(tabSetId, lsn);
		// skip to next expected lsn
		lsn++;
		
		if ( rstate == CegoRecoveryManager::RECOK )
		{
		    _pDBMng->log(_modId, Logger::NOTICE, Chain("Transaction recovery ok"));		   
		    isRecovered=true;
		}
		else if ( rstate == CegoRecoveryManager::RECPITREACHED )
		{	      
		    _pDBMng->log(_modId, Logger::NOTICE, Chain("PIT reached"));
		    isRecovered=true;
		}
		else
		{
		    throw Exception(EXLOC, Chain("Unknwon recovery state"));
		}
	    }
	}
    }
    
    _pDBMng->log(_modId, Logger::NOTICE, Chain("Finished recovery loop ..."));
    
    _pDBMng->writeCheckPoint(tableSet, false, false, _pGTM->getLockHandle());
    
    _pDBMng->log(_modId, Logger::NOTICE, Chain("Transaction recovery finished with lsn ") + Chain(lsn));
        
    return lsn;
	
}

unsigned long long CegoRecoveryManager::recoverCurrentDataFileLog(unsigned tabSetId, bool& endOfBackup)
{
    unsigned long long lsn=0;

    _pDBMng->seekToStart(tabSetId);


    CegoLockHandler* pLockHandle = _pGTM->getLockHandle();

    // now recover to last lsn in log
    
    endOfBackup = false;
    CegoLogRecord logRec;
    while ( _pDBMng->logRead(tabSetId, logRec) && ! endOfBackup )
    {	
	
	if ( logRec.getLSN() > 0 )
	    lsn=logRec.getLSN();
	
	switch ( logRec.getAction() ) 
	{		
	case CegoLogRecord::LOGREC_BUPAGE:
	{
#ifdef CGDEBUG	
	    _pDBMng->log(_modId, Logger::DEBUG, Chain("Writing page (") + Chain(logRec.getFileId()) + Chain(",") + Chain(logRec.getPageId()) + Chain(")"));
#endif
	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Restoring page (") + Chain(logRec.getFileId()) + Chain(",") + Chain(logRec.getPageId()) + Chain(")"));
	    
	    _pDBMng->writePage(logRec.getPageId(), 0, logRec.getData(), pLockHandle ); 
	    break;
	}	
	case CegoLogRecord::LOGREC_BUFBM:
	{
#ifdef CGDEBUG	
	    _pDBMng->log(_modId, Logger::DEBUG, Chain("Writing fbm for fileId ") + Chain(logRec.getFileId()));
#endif
	    _pDBMng->writeFBM(logRec.getFileId(), (unsigned*)logRec.getData(), pLockHandle ); 
	    break;
	}
	case CegoLogRecord::LOGREC_BUFIN:
	{
	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Detected end of backup"));
	    endOfBackup=true;
	    break;
	}
	case CegoLogRecord::LOGREC_CREATE:
	case CegoLogRecord::LOGREC_DROP:
	case CegoLogRecord::LOGREC_ALTER:
	case CegoLogRecord::LOGREC_RENAME:
	case CegoLogRecord::LOGREC_INSERT:
	case CegoLogRecord::LOGREC_DELETE:
	case CegoLogRecord::LOGREC_UPDATE: 
	case CegoLogRecord::LOGREC_BEGIN: 
	case CegoLogRecord::LOGREC_COMMIT: 
	case CegoLogRecord::LOGREC_ABORT: 
	case CegoLogRecord::LOGREC_SYNC: 		     
	case CegoLogRecord::LOGREC_ADDCOUNTER:
	case CegoLogRecord::LOGREC_DELCOUNTER:
	case CegoLogRecord::LOGREC_TRUNCATE:		
	    break;
	}
	
	if ( logRec.getData() )
	    free ( logRec.getData() ) ;	
    }
    return lsn;
}

void CegoRecoveryManager::prepareForCrashRecovery(unsigned tabSetId, bool correctDirect)
{
    _isCrashRecovery = true;
    _correctDirect = correctDirect;
    _invalidatedList.Empty();
    
    _pGTM->getTransactionManager()->getCrashAffectedTables(tabSetId, _invalidatedList);
    
    Chain *pTableName = _invalidatedList.First();
    while ( pTableName )
    {
	_pDBMng->log(_modId, Logger::NOTICE, Chain("Invalidating index objects for crash affected table  ") + *pTableName + Chain(" ..."));
	_pGTM->invalidateIndexForTable(tabSetId, *pTableName);
	pTableName = _invalidatedList.Next();
    }
    _pDBMng->log(_modId, Logger::NOTICE, Chain("Index invalidation done"));
}

CegoRecoveryManager::RecoveryState CegoRecoveryManager::recoverCurrentTransactionLog(unsigned tabSetId, long long pit, unsigned long long targetlsn, unsigned long long& lsn, long long& ts)
{

    /*
    _invalidatedList.Empty();
    
    _pGTM->getTransactionManager()->getCrashAffectedTables(tabSetId, _invalidatedList);
    
    Chain *pTableName = _invalidatedList.First();
    while ( pTableName )
    {
	_pDBMng->log(_modId, Logger::NOTICE, Chain("Invalidating index objects for table  ") + *pTableName + Chain(" ..."));
	_pGTM->invalidateIndexForTable(tabSetId, *pTableName);	
	pTableName = _invalidatedList.Next();
    }
    _pDBMng->log(_modId, Logger::NOTICE, Chain("Index invalidation done"));
    */
    
    // initially, we set the logfile to incomplete
    RecoveryState rstate = CegoRecoveryManager::RECOK;
    
    _pDBMng->seekToStart(tabSetId);
    _pDBMng->log(_modId, Logger::NOTICE, Chain("Recovering logfile ..."));
    
    // now recover to last lsn in log

    try
    {
	
	CegoLogRecord logRec;
	while ( _pDBMng->logRead(tabSetId, logRec) && rstate != CegoRecoveryManager::RECPITREACHED )
	{
	    // point-in-time recovery 
	    if ( ( pit > 0 && logRec.getTS() > pit ) || ( targetlsn > 0 && logRec.getLSN() > targetlsn ))
	    {
		Datetime d(logRec.getTS());
		_pDBMng->log(_modId, Logger::NOTICE, Chain("Required point in time reached, Ignoring logentry  ") + Chain(logRec.getLSN()) 
			     + Chain(" ( ") + d.asChain() + Chain(" ) and higher"));
		
		rstate = CegoRecoveryManager::RECPITREACHED;	    
	    }
	    else if ( logRec.getTS() <  ts )
	    {
		_pDBMng->log(_modId, Logger::NOTICE, Chain("Old log detected, ignoring logentry ") + Chain(logRec.getLSN()) + Chain(" and higher"));
		
		rstate = CegoRecoveryManager::RECPITREACHED;
	    }
	    else
	    {	    
		lsn=logRec.getLSN();
		ts=logRec.getTS();
		unsigned long long tid=logRec.getTID();
		unsigned long long tastep=logRec.getTAStep();
		
		__recoveryTS = ts;
		
		// _pDBMng->log(_modId, Logger::NOTICE, Chain("Recovering lsn ") + Chain(logRec.getLSN()) + Chain(" ..."));
		
		// here comes the recovery
		
		switch ( logRec.getAction() ) 
		{		    
		case CegoLogRecord::LOGREC_CREATE:
		{
		    switch ( logRec.getObjType() )
		    {	
		    case CegoObject::TABLE:
		    case CegoObject::SYSTEM:
		    {
			CegoTableObject to;
			to.decode(logRec.getData());
			_pGTM->createDataTable(tabSetId, logRec.getObjName(), logRec.getObjType(), to.getSchema());
			_pDBMng->addObject(tabSetId, logRec.getObjName(), CegoObject::TABLE);
			break;
		    }
		    case CegoObject::AVLTREE:
		    case CegoObject::PAVLTREE:
		    case CegoObject::UAVLTREE:
		    {
			CegoTableObject to;
			to.decode(logRec.getData());
			bool isCached=false;
			
			if ( _pGTM->objectExists(tabSetId, logRec.getObjName(), logRec.getObjType() ) )
			{
			    _pDBMng->log(_modId, Logger::NOTICE, Chain("Dropping suspect avl index ") + logRec.getObjName());
			    _pGTM->dropIndex(tabSetId, logRec.getObjName());
			}
			
			_pGTM->createIndexTable(tabSetId, logRec.getObjName(), to.getTabName(), to.getSchema(), logRec.getObjType(), isCached);
			_pDBMng->addObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    }
		    case CegoObject::BTREE:
		    case CegoObject::PBTREE:
		    case CegoObject::UBTREE:
		    {
			CegoBTreeObject bo;
			bo.decode(logRec.getData());
			bool isCached=false;
			
			// it may happen that the the btree objects already exists
			// in this case, we drop any rebuild
			
			if ( _pGTM->objectExists(tabSetId, logRec.getObjName(), logRec.getObjType() ) )
			{
			    _pDBMng->log(_modId, Logger::NOTICE, Chain("Dropping suspect btree ") + logRec.getObjName());
			    _pGTM->dropBTree(tabSetId, logRec.getObjName());
			}
			_pGTM->createIndexTable(tabSetId, logRec.getObjName(), bo.getTabName(), bo.getSchema(), logRec.getObjType(), isCached);
			_pDBMng->addObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    }
		    case CegoObject::VIEW:
		    {
			CegoViewObject vo;
			vo.decode(logRec.getData());
			_pGTM->createViewObject(vo);
			_pDBMng->addObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;		    
		    }
		    case CegoObject::PROCEDURE:
		    {
			CegoProcObject po;
			po.decode(logRec.getData());
			_pGTM->createProcObject(po);
			_pDBMng->addObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;		    
		    }
		    case CegoObject::FKEY:
		    {
			CegoKeyObject ko;
			ko.decode(logRec.getData());
			_pGTM->createKeyObject(ko);
			_pDBMng->addObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    }
		    case CegoObject::CHECK:
		    {
			CegoCheckObject co;
			co.decode(logRec.getData());
			_pGTM->createCheckObject(co);
			_pDBMng->addObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    }
		    case CegoObject::TRIGGER:
		    {
			CegoTriggerObject to;
			to.decode(logRec.getData());
			_pGTM->createTriggerObject(to);
			_pDBMng->addObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    }
		    case CegoObject::ALIAS:
		    {
			CegoAliasObject ao;
			ao.decode(logRec.getData());
			_pGTM->createAliasObject(ao);
			_pDBMng->addObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    }
		    case CegoObject::RBSEG:
		    case CegoObject::JOIN:
		    case CegoObject::UNDEFINED:
			// N/A
			break;
		    }
		    break;
		}
		case CegoLogRecord::LOGREC_DROP:
		{		  
		    switch ( logRec.getObjType() )
		    {
		    case CegoObject::TABLE:
			_pGTM->dropTable(tabSetId, logRec.getObjName());
			_pDBMng->removeObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    case CegoObject::AVLTREE:
		    case CegoObject::UAVLTREE:
		    case CegoObject::PAVLTREE:
			_pGTM->dropIndex(tabSetId, logRec.getObjName());
			_pDBMng->removeObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    case CegoObject::BTREE:
		    case CegoObject::UBTREE:
		    case CegoObject::PBTREE:
			_pGTM->dropBTree(tabSetId, logRec.getObjName());
			_pDBMng->removeObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    case CegoObject::VIEW:
			_pGTM->dropView(tabSetId, logRec.getObjName());
			_pDBMng->removeObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    case CegoObject::PROCEDURE:
			_pGTM->dropProcedure(tabSetId, logRec.getObjName());
			_pDBMng->removeObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    case CegoObject::FKEY:
			_pGTM->dropFKey(tabSetId, logRec.getObjName());
			_pDBMng->removeObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    case CegoObject::CHECK:
			_pGTM->dropCheck(tabSetId, logRec.getObjName());
			_pDBMng->removeObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    case CegoObject::TRIGGER:
			_pGTM->dropTrigger(tabSetId, logRec.getObjName());
			_pDBMng->removeObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    case CegoObject::ALIAS:
			_pGTM->dropAlias(tabSetId, logRec.getObjName());
			_pDBMng->removeObject(tabSetId, logRec.getObjName(), logRec.getObjType());
			break;
		    case CegoObject::SYSTEM:
		    case CegoObject::RBSEG:
		    case CegoObject::JOIN:
		    case CegoObject::UNDEFINED:
			break;
			// ignore
			// throw Exception(EXLOC, "Invalid object type for drop");
		    }		    		   
		    break;
		}
		case CegoLogRecord::LOGREC_TRUNCATE:
		{
		    _pGTM->truncateTable(tabSetId, logRec.getObjName());
		    break;
		}
		case CegoLogRecord::LOGREC_ALTER:
		{
		    CegoTableObject aoe;
		    aoe.decode(logRec.getData());
		    _pGTM->alterTableObject(tabSetId, logRec.getObjName(), logRec.getObjType(), aoe);
		    break;
		}
		case CegoLogRecord::LOGREC_RENAME:
		{
		    Chain newObjName(logRec.getData(), logRec.getDataLen());
		    _pGTM->renameObject(tabSetId, logRec.getObjName(), logRec.getObjType(), newObjName );
		    _pDBMng->removeObject(tabSetId, logRec.getObjName(), logRec.getObjType());
		    _pDBMng->addObject(tabSetId, newObjName, logRec.getObjType());
		    break;   
		}
		case CegoLogRecord::LOGREC_BEGIN:
		{
		    // tid is already setup by log record information
		    // _pGTM->beginTransaction(tabSetId);
		    break;
		}
		case CegoLogRecord::LOGREC_COMMIT:
		{
		    _pGTM->setTID(tabSetId, tid);	    
		    _pGTM->commitTableTransaction(tabSetId, false);
		    break;
		}
		case CegoLogRecord::LOGREC_ABORT:
		{
		    _pGTM->setTID(tabSetId, tid);
		    _pGTM->rollbackTableTransaction(tabSetId, false);
		    break;
		}
		case CegoLogRecord::LOGREC_INSERT:
		{
		    CegoTableObject oe;
		    _pGTM->getObject(tabSetId, logRec.getObjName(), CegoObject::TABLE, oe);

		    if ( _isCrashRecovery )
		    {
			if ( _invalidatedList.Find(logRec.getObjName()) == 0 )
			{
			    _pDBMng->log(_modId, Logger::NOTICE,
					 Chain("Invalidating index objects for table ")
					 + logRec.getObjName()
					 + Chain(" by insert"));
			    _pGTM->invalidateIndexForTable(tabSetId, logRec.getObjName());
			    _invalidatedList.Insert(logRec.getObjName());
			    if ( _correctDirect )
			    {
				// doSync = false => we are in recovery mode, no sync needed
				// doForce = true => we force to recreate
				_pDBMng->log(_modId, Logger::NOTICE, Chain("Direct correction for table ") + logRec.getObjName());
				_pGTM->correctIndexForTable(tabSetId, logRec.getObjName(), false, true);
			    }
			}
		    }
		    
		    ListT<CegoField> fvl = oe.getSchema();		    
		    ListT<CegoBlob> blobList;
		    ListT<CegoClob> clobList;
		    
#ifdef CGDEBUG	
		    _pDBMng->log(_modId, Logger::DEBUG, Chain("Decoding log record of len ") + Chain(logRec.getDataLen()));
#endif
		    
		    unsigned long long tid;
		    unsigned long long tastep;
		    CegoTupleState ts;
		    
		    unsigned toff = CegoQueryHelper::decodeTupleHeader(tid, tastep, ts, logRec.getData());
		    
		    char* tp =  logRec.getData() + toff;
		    unsigned tlen = logRec.getDataLen() - toff;
		    
		    CegoQueryHelper::decodeFVL(fvl, blobList, clobList, tp, tlen);
		    
#ifdef CGDEBUG	
		    _pDBMng->log(_modId, Logger::DEBUG, Chain("Log record decoded"));
#endif
		    
		    CegoBlob *pBlob = blobList.First();
		    CegoField *pF = fvl.First();
		    while ( pBlob && pF )
		    {	    	    
			PageIdType pageId;
			
			_pGTM->putBlobData(tabSetId, pBlob->getBufPtr(), pBlob->getSize(), pageId);
			
			while ( pF->getValue().getType() != BLOB_TYPE && pF)
			    pF = fvl.Next();
			
			if ( pF )
			{
			    Chain blobRef = Chain("[") + Chain(pageId) + Chain("]");
			    CegoFieldValue fv(BLOB_TYPE, blobRef);
			    pF->setValue(fv);
			}
			else
			{
			    throw Exception(EXLOC, "Cannot get blob reference");
			}
			
			pBlob->release();
			pBlob = blobList.Next();
			pF = fvl.Next();
		    }
		    
		    CegoClob *pClob = clobList.First();
		    pF = fvl.First();
		    while ( pClob && pF )
		    {	    	   
			PageIdType pageId;
			
			_pGTM->putClobData(tabSetId, pClob->getBufPtr(), pClob->getSize(), pageId);
			
			while ( pF->getValue().getType() != CLOB_TYPE && pF)
			    pF = fvl.Next();
			
			if ( pF )
			{
			    Chain clobRef = Chain("[") + Chain(pageId) + Chain("]");
			    CegoFieldValue fv(CLOB_TYPE, clobRef);
			    pF->setValue(fv);
			}
			else
			{
			    throw Exception(EXLOC, "Cannot get clob reference");
			}
			
			pClob->release();
			pClob = clobList.Next();
			pF = fvl.Next();
		    }
		    
		    bool doLogging = true;
		    bool flushLog = true;
		    
		    CegoDataPointer dp;
		    
		    _pGTM->setTID(tabSetId, tid);
		    _pGTM->setTAStep(tabSetId, tastep);
		    
		    _pGTM->insertDataTable(oe, fvl, dp, doLogging, flushLog);
		    
		    break;
		}
		case CegoLogRecord::LOGREC_DELETE:
		{		  
		    CegoPredicate *pPred = 0;
		    Chain tableAlias;
		    
		    CegoQueryHelper::decodeDelRec(tableAlias, pPred, logRec.getData(), logRec.getDataLen());

		    
		    CegoTableObject oe;
		    _pGTM->getObject(tabSetId, logRec.getObjName(), CegoObject::TABLE, oe);

		    if ( _isCrashRecovery )
		    {
			if ( _invalidatedList.Find(logRec.getObjName()) == 0 )
			{
			    _pDBMng->log(_modId, Logger::NOTICE,
					 Chain("Invalidating index objects for table ")
					 + logRec.getObjName()
					 + Chain(" by delete"));
			    _pGTM->invalidateIndexForTable(tabSetId, logRec.getObjName());
			    _invalidatedList.Insert(logRec.getObjName());
			    if ( _correctDirect )
			    {
				_pDBMng->log(_modId, Logger::NOTICE, Chain("Direct correction for table ") + logRec.getObjName());
				// doSync = false => we are in recovery mode, no sync needed
				// doForce = true => we force to recreate
				_pGTM->correctIndexForTable(tabSetId, logRec.getObjName(), false, true);
			    }
			}
		    }

		    if ( pPred )
		    {
			pPred->setTabSetId(tabSetId);
			pPred->setQueryManager(_pGTM);
		    }
		    
		    _pGTM->setTID(tabSetId, tid);
		    _pGTM->setTAStep(tabSetId, tastep);		    
		    _pGTM->deleteDataTable(oe, pPred, 0, false);
		    
		    break;
		}
		case CegoLogRecord::LOGREC_UPDATE:
		{
		    ListT<CegoField> updList;
		    ListT<CegoExpr*> exprList;
		    
		    CegoPredicate *pPred = 0;		    
		    Chain tableAlias;
		    bool returnOnFirst;
		    
		    CegoQueryHelper::decodeUpdRec(tableAlias, pPred, updList, exprList, returnOnFirst,
						  logRec.getData(), logRec.getDataLen(), _pGTM, tabSetId);
		    

		    if ( _isCrashRecovery )
		    {
			if ( _invalidatedList.Find(logRec.getObjName()) == 0 )
			{
			    _pDBMng->log(_modId, Logger::NOTICE,
					 Chain("Invalidating index objects for table ")
				     + logRec.getObjName()
					 + Chain(" by update"));
			    
			    _pDBMng->log(_modId, Logger::NOTICE, Chain("Invalidating index objects for table ") + logRec.getObjName());
			    _pGTM->invalidateIndexForTable(tabSetId, logRec.getObjName());
			    _invalidatedList.Insert(logRec.getObjName());
			    if ( _correctDirect )
			    {
				_pDBMng->log(_modId, Logger::NOTICE, Chain("Direct correction for table ") + logRec.getObjName());
				// doSync = false => we are in recovery mode, no sync needed
				// doForce = true => we force to recreate
				_pGTM->correctIndexForTable(tabSetId, logRec.getObjName(), false, true);
			    }
			}
		    }
		    
		    _pGTM->setTID(tabSetId, tid);
		    _pGTM->setTAStep(tabSetId, tastep);
		    
		    ListT<CegoField> returnList;

		    if ( pPred )
		    {
			pPred->setTabSetId(tabSetId);
			pPred->setQueryManager(_pGTM);
		    }
		    
		    CegoExpr** pExpr =  exprList.First();
		    while ( pExpr )
		    {
			(*pExpr)->setTabSetId(tabSetId);
			(*pExpr)->setQueryManager(_pGTM);
			pExpr =  exprList.Next();
		    }
		    
		    _pGTM->updateDataTable(tabSetId, logRec.getObjName(),
					   tableAlias, 
					   pPred, 
					   updList, 
					   exprList,
					   returnOnFirst,
					   returnList);
		    
		    break;
		}
		case CegoLogRecord::LOGREC_ADDCOUNTER:
		{
		    // counters are not completely transaction safe since during some checkpoint of any tableset
		    // counters could be already written down to XML configuration file
		    // For recovery, we set counters with force = true
		    
		    Chain counterName ( logRec.getData() );
		    long initValue = 0;
		    _pDBMng->addCounter(tabSetId, counterName, initValue, true);
		    break;
		}
		case CegoLogRecord::LOGREC_DELCOUNTER:
		{
		    Chain counterName ( logRec.getData() );
		    _pDBMng->removeCounter(tabSetId, counterName);
		    break;
		}
		case CegoLogRecord::LOGREC_SYNC:		    
		case CegoLogRecord::LOGREC_BUPAGE:
		case CegoLogRecord::LOGREC_BUFBM:
		case CegoLogRecord::LOGREC_BUFIN:
		{
		    // BUPAGE, BUFMB and BUFIN already handled in data file recovery
		    // for SYNC nothing to do
		    break;
		}
		}
	    }
	    
	    if ( logRec.getData() )
		free ( logRec.getData() ) ;
	}
	
	_pDBMng->log(_modId, Logger::NOTICE, Chain("Current logfile recovery finished with lsn ") + Chain(lsn));
	
	__recoveryTS = 0;
	
	return rstate;
	
    }
    catch ( Exception e)
    {
	__recoveryTS = 0;
	throw Exception(EXLOC, Chain("Cannot recover current transaction log "), e);
    }    
}

bool CegoRecoveryManager::restoreLogFile(const Chain& tableSet, unsigned long lsn)
{    
    bool fileProvided=false;
    Chain archRestoreProg = _pDBMng->getArchRestoreProg();
    
    if ( archRestoreProg != Chain("NONE") )
    {
	Chain archLogFileName = _pDBMng->getArchiveLogName(tableSet, lsn);
		
	ListT<Chain> archIdList;
	ListT<Chain> archPathList;
	
	unsigned tabSetId = _pDBMng->getTabSetId(tableSet);    
	_pDBMng->getArchLogInfo(tabSetId, archIdList, archPathList);

	_pDBMng->log(_modId, Logger::NOTICE, Chain("Waiting for logfile ") + archLogFileName + Chain(" ..."));
	
	Chain *pArchLogPath = archPathList.First();
	Chain pathString;
	while ( pArchLogPath )
	{
	    pathString += *pArchLogPath;
	    pArchLogPath = archPathList.Next();
	    if ( pArchLogPath )
		pathString += Chain(":");
	}
	
	CommandExecuter cmdExe(_shellCmd);
	
	unsigned archRestoreTimeout = _pDBMng->getArchRestoreTimeout();
	
	Chain restoreCmd = archRestoreProg + Chain(" -m recover -t ") + tableSet + Chain(" -f ") + archLogFileName + Chain(" -p ") + pathString;
	
	_pDBMng->log(_modId, Logger::NOTICE, Chain("Triggering external log manager with <") + Chain(restoreCmd) + Chain(">"));
	int retCode = cmdExe.execute(restoreCmd, archRestoreTimeout); 
	_pDBMng->log(_modId, Logger::NOTICE, Chain("External log manager returned : <") + Chain(retCode) + Chain(">"));
	if ( retCode == 0 )
	{
	    // file could be provided
	    fileProvided=true;
	}
	else if ( retCode == 1 )
	{
	    fileProvided = false;
	}
	else
	{
	    Chain msg("External log manager failed");
	    throw Exception(EXLOC, msg);			    
	}		     
    }	      
    else
    {
	fileProvided = false;
    }

    // we don't have to check for online redo logs
    // if recovery starts, the current online redo logs are copied to archive location
    // so they are already available
    
    return fileProvided;
}
