///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoDbThread.cc  
// ---------------                                                     
// Cego db thread class implementation
//                                                         
// Design and Implementation by Bjoern Lemke
//               
// (C)opyright 2000-2025 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: CegoDbThread
// 
// Description: This class implements the functions of a single database thread.
//              It serves a database session dispatched from the database thread pool
//              and handles subsequent requests for this session.
//              Database requests can be request from a native client but also distributed
//              requests from a different cego database node.
//  
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

// INCLUDES
#include <lfcbase/Exception.h>
#include <lfcbase/Sleeper.h>
#include <lfcbase/ThreadLock.h>
#include <lfcbase/Datetime.h>

#include "CegoXMLdef.h"
#include "CegoDefs.h"
#include "CegoDbThread.h"
#include "CegoDbThreadPool.h"

#include <string.h>
#include <stdlib.h>

static ThreadLock histLock("QUERYHIST");

extern Chain __dateTimeFormat;
extern char __quoteEscapeFlag;
extern bool __lockStatOn;

CegoDbThread::CegoDbThread() : Thread()
{
}

CegoDbThread::CegoDbThread(CegoDbThreadPool *pPool, CegoDatabaseManager *pDBMng, CegoDbHandler::ProtocolType protType) : Thread()
{
    _pPool = pPool;
    _pDBMng = pDBMng;
    _protType = protType;

    histLock.init(LCKMNG_LOCKWAITDELAY, __lockStatOn);

    _pTabMng = new CegoQueryManager(_pDBMng);
    _pPA = new CegoAction(_pTabMng, _pPool);

    _pTim = new NanoTimer();
    _pCost = new NanoTimer();
    _qid = 0;
    
    _modId = _pDBMng->getModId("CegoDbThread");
    _errorCode = 0;
}

CegoDbThread::~CegoDbThread()
{
    delete _pPA;
    delete _pTabMng;
    delete _pTim;
    delete _pCost;
}

ListT<CegoDbThread::QueryEntry> CegoDbThread::getLastQuery() const
{
    // we have to invert query list to indicate last query first
    // since getHistory is not performance critical, we do it here
    ListT<QueryEntry> history;

    P();

    if ( _queryList.Size() > 0 )
	for ( unsigned i=_queryList.Size(); i>0; i--)	    
	    history.Insert(_queryList[i-1]);
    V();
    
    return history;
}

ListT<CegoDbThread::QueryEntry> CegoDbThread::getCostQuery() const
{
    // we have to invert query list to indicate last query first
    // since getHistory is not performance critical, we do it here
    ListT<QueryEntry> costList;
    StackT<QueryEntry> costStack;
    
    P();

    QueryEntry *pEntry = _costTree.First();
    while ( pEntry )
    {
	costStack.Push(*pEntry);
	pEntry = _costTree.Next();
    }
    V();
    
    QueryEntry stackEntry;

    while ( costStack.Pop(stackEntry) )
	costList.Insert(stackEntry);
    
    return costList;
}

Chain CegoDbThread::lastAction() const
{
    return _lastAction;
}

unsigned long long CegoDbThread::allocatedSortArea() const
{
    if ( _pTabMng )
	return _pTabMng->getAllocatedSortArea();
    return 0;
}

void CegoDbThread::abortSession()
{
    _pTabMng->abort();
}

unsigned CegoDbThread::getErrorCode() const
{
    return _errorCode;
}

void CegoDbThread::addReloadRequest(unsigned tabSetId)
{
    _pPool->P(_idx);
    _loadList.Insert(tabSetId);
    _pPool->V(_idx);
}

void CegoDbThread::checkReloadRequest()
{   
    _pPool->P(_idx);
    unsigned *pTS = _loadList.First();
    if ( pTS )
    {
	unsigned ts = *pTS;
	_loadList.Remove(*pTS);
	_pPool->V(_idx);

	_pTabMng->disableAuth();

	try
	{
	    loadObjects(ts);
	}
	catch ( Exception e )
	{

	    Chain msg;
	    e.pop(msg);
	    _pDBMng->log(_modId, Logger::LOGERR, Chain("Thread ") + Chain(_idx) + Chain(" reload error : ") + msg);	   	    
	}

	_pTabMng->enableAuth();
    }
    else
    {
	_pPool->V(_idx);
    }
}

void* CegoDbThread::job(void* arg)
{
    try
    {
	_idx = *(unsigned long long*)arg;
	
	_pTabMng->setPoolSyncInfo(_pPool, _idx);
	_pTabMng->setThreadId(getTid());
	_pPool->setTid(_idx, getTid());
	_pPool->setThreadState(_idx, CegoDbThreadPool::READY);

	unsigned queueDelay = _pDBMng->getQueueDelay();

	while ( ! _pPool->isTerminated() )
	{
	    try
	    {
		_pTim->reset();
		_pTim->start();

		
		NetHandler* pRequest = _pPool->nextRequest();

		if ( pRequest ) 
		{
#ifdef CGDEBUG		    
		    _pDBMng->log(_modId, Logger::DEBUG, Chain("Thread ") + Chain(_idx) + Chain(" : Serving service request"));
#endif
		    _pPool->setState(_idx, CegoDbThreadPool::CONNECTED);
		    _pPool->incNumRequest(_idx);
		    
		    _pDBMng->increaseActiveDbThread();
		    
		    CegoDbHandler *pSH = 0;
		    try
		    {	       	
			pSH = new CegoDbHandler(pRequest, _protType, _pDBMng); 
			serveSession(pSH);
			delete pSH;
		    }
		    catch ( Exception e)
		    {
			if ( pSH )
			{
			    delete pSH;
			}
			Chain msg;
			Chain module;
			unsigned line;
			
			Chain exep;
			while ( e.pop(module, line, msg) )
			{
			    exep += Chain("\n\t") + module + Chain("(") +  Chain(line) + Chain(") : ") + msg;
			}
			
			_pDBMng->log(_modId, Logger::LOGERR, Chain("Thread ") + Chain(_idx) + Chain(" : DB session aborted : ") + exep);
		    }
		    
		    // in any case, rollback any open transactions
		    _pTabMng->rollbackTransaction(_pPA->getTableSet());
		    
		    _pDBMng->decreaseActiveDbThread();		    
#ifdef CGDEBUG
		    _pDBMng->log(_modId, Logger::DEBUG, Chain("Thread ") + Chain(_idx) + Chain(" : Service request finished"));
#endif
		    _pPool->setState(_idx, CegoDbThreadPool::READY);
		    
		    if ( _pTabMng->isAborted() )
		    {
			_pDBMng->log(_modId, Logger::NOTICE,  Chain("Thread ") + Chain(_idx) + Chain(" : Abort catched, proceed with session"));
			_pTabMng->proceed();
		    }

		    // reset append and autocommit mode
		    _pTabMng->setAppend(false);
		    _pTabMng->setAutoCommit(true);
		   
		    delete pRequest;
		}
		else
		{
		    Sleeper s;
		    s.microSleep(queueDelay);
		}

		_pTim->stop();
		_pPool->addThreadIdle(_idx, _pTim->getSum());

		checkReloadRequest();
	    }
	    catch ( Exception e)
	    {
		Chain msg;
		Chain module;
		unsigned line;	
		Chain exep;
		
		while ( e.pop(module, line, msg) )
		{
		    exep += Chain("\n\t") + module + Chain("(") +  Chain(line) + Chain(") : ") + msg;
		}
		
		_pDBMng->log(_modId, Logger::LOGERR, Chain("Thread ") + Chain(_idx) + Chain(" :") + exep);
	    }
	}
    }
    catch ( Exception e )
    {
	Chain msg;
	e.pop(msg);	
	_pDBMng->log(_modId, Logger::LOGERR, Chain("Thread ") + Chain(_idx) + Chain(" : ") + msg);
	_errorCode = 1;
    }
    return 0;
}

void CegoDbThread::loadObjects(unsigned tabSetId)
{
    Chain tableSet = _pDBMng->getTabSetName(tabSetId);

    _pPA->setTableSet(tableSet);
    
    ListT<Chain> procList;
    _pTabMng->getObjectList(tabSetId, CegoObject::PROCEDURE, procList);
    
    Chain *pProcName = procList.First();
    while ( pProcName )
    {	
#ifdef CGDEBUG
	_pDBMng->log(_modId, Logger::DEBUG, Chain("Thread ") + Chain(_idx) + Chain(" : Compiling procedure ") + *pProcName);
#endif
	_pTabMng->reloadProcedure(tabSetId, *pProcName);

#ifdef CGDEBUG
	_pDBMng->log(_modId, Logger::DEBUG, Chain("Thread ") + Chain(_idx) + Chain(" : Procedure ") + *pProcName + Chain(" added"));	
#endif

	pProcName = procList.Next();
    }
    
    ListT<Chain> viewList;
    _pTabMng->getObjectList(tabSetId, CegoObject::VIEW, viewList);
    
    Chain *pViewName = viewList.First();
    while ( pViewName )
    {		
#ifdef CGDEBUG
	_pDBMng->log(_modId, Logger::DEBUG, Chain("Thread ") + Chain(_idx) + Chain(" : Compiling view ") + *pViewName);
#endif

	_pTabMng->reloadView(tabSetId, *pViewName);
		
#ifdef CGDEBUG
	_pDBMng->log(_modId, Logger::DEBUG, Chain("Thread ") + Chain(_idx) + Chain(" : View ") + *pViewName + Chain(" added"));	
#endif
	pViewName = viewList.Next();
    }

    ListT<Chain> triggerList;
    _pTabMng->getObjectList(tabSetId, CegoObject::TRIGGER, triggerList);
    
    Chain *pTriggerName = triggerList.First();
    while ( pTriggerName )
    {
#ifdef CGDEBUG
	_pDBMng->log(_modId, Logger::DEBUG, Chain("Thread ") + Chain(_idx) + Chain(" : Compiling trigger ") + *pTriggerName);
#endif

	_pTabMng->reloadTrigger(tabSetId, *pTriggerName);

#ifdef CGDEBUG
	_pDBMng->log(_modId, Logger::DEBUG, Chain("Thread ") + Chain(_idx) + Chain(" : Trigger ") + *pTriggerName + Chain(" added"));	
#endif

	pTriggerName = triggerList.Next();
    }
}

void CegoDbThread::unloadObjects(unsigned tabSetId)
{
    try
    {	
	_pTabMng->getTransactionManager()->release(tabSetId);
	_pTabMng->removeAllComp(tabSetId);
    }
    catch ( Exception e )
    {
	throw Exception(EXLOC, Chain("Cannot unload objects"), e);
    }
}

void CegoDbThread::invalidateObject(unsigned tabSetId, const Chain& objName, CegoObject::ObjectType type)
{
    if ( type == CegoObject::VIEW )
    {
	_pTabMng->removeCompView(tabSetId, objName);
    }
    else if ( type == CegoObject::PROCEDURE )
    {
	_pTabMng->removeCompProcedure(tabSetId, objName);
    }
    else if ( type == CegoObject::TRIGGER )
    {
	_pTabMng->removeCompTrigger(tabSetId, objName);
    }    
    else
    {
	throw Exception(EXLOC, "Unknown object type for invalidation");	    
    }    
}

void CegoDbThread::serveSession(CegoDbHandler *pSH)
{       
#ifdef CGDEBUG
    _pDBMng->log(_modId, Logger::DEBUG, Chain("Thread ") + Chain(_idx) + Chain(" : Serving session"));
#endif

    if ( pSH->acceptSession() )
    {
	bool isTraceOn;
	Chain msg;
	if ( _pDBMng->checkUser(pSH->getUser(), pSH->getPassword(), msg, isTraceOn) == false )
	{
	    pSH->sendError(msg);
	    return;
	}
	else
	{
	    if ( _pDBMng->tableSetExists(pSH->getTableSet()) == false )
	    {
		Chain msg = Chain("Unknown tableset ") + pSH->getTableSet();
		pSH->sendError(msg);
		return;	    
	    }

	    _pTabMng->setActiveUser(pSH->getTableSet(), pSH->getUser(), pSH->getPassword());

	    msg = Chain("Access granted");
	    pSH->sendSessionConfirm(msg, _idx, XML_DBPRODNAME_VALUE, XML_DBPRODVERSION_VALUE, __dateTimeFormat, __quoteEscapeFlag);

#ifdef CGDEBUG
	    _pDBMng->log(_modId, Logger::DEBUG, Chain("Thread ") + Chain(_idx) + Chain(" : Accepted session for tableset ") + pSH->getTableSet());
#endif
	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Thread ") + Chain(_idx) + Chain(" : Accepted session for user=<") + pSH->getUser() + Chain("> and tableset <") + pSH->getTableSet() + Chain(">"));	    
	}	
	
	_pPA->setTableSet(pSH->getTableSet());

	_pTabMng->setAppend(_pDBMng->getAppendMode(pSH->getTableSet()));

	// setup query history configuration for each session
	_numLast = _pDBMng->getNumQueryLast();
	_numCost = _pDBMng->getNumQueryCost();
	
	bool isTerminated = false;

	while ( isTerminated == false && _pPool->isTerminated() == false )
	{	    
	    CegoDbHandler::RequestType reqType;
	    reqType = pSH->acceptRequest();

	    _pTim->stop();
	    _pPool->addThreadIdle(_idx, _pTim->getSum());
	    _pTim->reset();
	    _pTim->start();
	    
	    if ( reqType != CegoDbHandler::REQTIMEOUT )
	    {
		if ( isTraceOn )
		{
		    _pDBMng->incUserQuery(pSH->getUser());
		}

		_pPool->incNumQueryRequest(_idx);	
		_pPool->setState(_idx, CegoDbThreadPool::BUSY);
		
		_pTim->stop();
		_pPool->addThreadIdle(_idx, _pTim->getSum());
		
		isTerminated = serveRequest(pSH, reqType);
		
		_pTim->reset();
		_pTim->start();
		
		_pPool->setState(_idx, CegoDbThreadPool::CONNECTED);
	    }
	    else
	    {		
#ifdef CGDEBUG
		_pDBMng->log(_modId, Logger::DEBUG, Chain("Thread ") + Chain(_idx) + Chain(" : Client request timeout occured, waiting ..."));
#endif
	    }
	    checkReloadRequest();
	}
    }
}

bool CegoDbThread::serveRequest(CegoDbHandler *pSH, CegoDbHandler::RequestType reqType)
{

    Chain tsState = _pDBMng->getTableSetRunState(pSH->getTableSet());

    if ( tsState != Chain(XML_ONLINE_VALUE)
	 && tsState != Chain(XML_BACKUP_VALUE)
	 && tsState != Chain(XML_CHECKPOINT_VALUE) )	
    {
	Chain msg = Chain("Tableset ") + pSH->getTableSet() + Chain(" not online ( State is ") + tsState + Chain(" )");
	pSH->sendError(msg);
	return false;
    }
        
    bool isTerminated=false;   	       

    // at the beginning of the request, in any case we reset abort status
    _pTabMng->proceed();

    _pCost->reset();
    _pCost->start();
    
    try
    {	
	switch ( reqType ) 
	{	    
	case CegoDbHandler::DBPRODINFO:
	{
	    pSH->sendProdInfo();
	    break;
	}
	case CegoDbHandler::QUERYABORT:
	{
	    unsigned long long idx;

	    idx = pSH->getTid();
	    _pPool->abortThread(idx);
	    pSH->sendResponse(Chain("Query aborted"));
	    break;
	}
	case CegoDbHandler::QUERY:
	{  
	    _lastAction = pSH->getQueryArg(); 
				
#ifdef CGDEBUG
	    _pDBMng->log(_modId, Logger::DEBUG, Chain("Thread ") + Chain(_idx) + Chain(" : Query Arg = <<<") + _lastAction + Chain(">>>"));
#endif
	    
	    _pPA->cleanUp();
	    _pPA->setCommandChain( _lastAction );
	    _pPA->setDbHandle(pSH);
	    
	    _pPA->parse();

	    _pPA->execute();

#ifdef CGDEBUG
	    _pDBMng->log(_modId, Logger::DEBUG, Chain("Thread ") + Chain(_idx) + Chain(" : Query request done"));
#endif   
	    break;
	}
	case CegoDbHandler::PUTBLOB:
	{
	    _lastAction = Chain("put blob");

	    Chain tableSet;
	    unsigned long long blobSize;
	    
	    pSH->getPutBlobArg(tableSet, blobSize);

	    unsigned tabSetId = _pDBMng->getTabSetId(tableSet);

	    NetHandler *pN = pSH->getNetHandler();

	    PageIdType pageId = 0;

	    CegoBufferPage bp;

	    try
	    {
		_pTabMng->getNewFilePage(bp, tabSetId, CegoObject::TABLE);
	    }
	    catch ( Exception e )
	    {
		Chain msg;
		e.pop(msg);
		pSH->sendError(msg);
		break;
	    }

	    bp.initPage(CegoBufferPage::BLOB);
	    
	    pageId = bp.getPageId();
		
	    try
	    {
#ifdef CGDEBUG
		_pDBMng->log(_modId, Logger::DEBUG, Chain(" Thread ") + Chain(_idx) + Chain(" : Sending blob info [") + Chain(pageId) + Chain("]"));
#endif
		pSH->sendBlobInfo(pageId);
		
		unsigned freeInPage = bp.getChunkLen();
		char* pagePtr = bp.getChunkEntry();

		unsigned long long blobRef = 0;
		memcpy(pagePtr, &blobRef, sizeof(unsigned long long));
		pagePtr += sizeof(unsigned long long);
		freeInPage -= sizeof(unsigned long long);
	       
		memcpy(pagePtr, &blobSize, sizeof(unsigned long long));
		pagePtr += sizeof(unsigned long long);
		freeInPage -= sizeof(unsigned long long);
		
		char *bufPtr = 0; 
		unsigned availFromBuf = 0;
		
		unsigned long long recvSize = 0;

		while ( recvSize < blobSize )
		{		    
		    try
		    {
			bool ackRequired = false;

			if ( availFromBuf == 0 )		    
			{
			    if ( pN->waitMsg(NETMNG_WAITMSG_TIMEOUT) == false )
				throw Exception(EXLOC, "Timeout exceeded for blob chunk");
			    pN->readMsg();
			    ackRequired = true;
			    bufPtr = pN->getMsg();
			    availFromBuf = pN->getMsgSize();
			}    
			
			if ( freeInPage == 0 )
			{
			    CegoBufferPage nextPage;
			    _pTabMng->getNewFilePage(nextPage, tabSetId, CegoObject::TABLE);
			    nextPage.initPage(CegoBufferPage::BLOB);
			    
			    bp.setNextPageId(nextPage.getPageId());
			    
			    _pDBMng->bufferUnfix(bp, true, _pTabMng->getLockHandler());
			    
			    bp = nextPage;
			    
			    freeInPage = bp.getChunkLen();
			    pagePtr = bp.getChunkEntry();
			}

			if ( ackRequired )
			{
			    pN->sendAck();
			    ackRequired = false;
			}		    
		    }
		    catch ( Exception e )
		    {			
			pN->sendNack();			    
			throw Exception(EXLOC, Chain("Cannot put blob"), e);
		    }
		    
		    if ( freeInPage >= availFromBuf )
		    {
			memcpy(pagePtr, bufPtr, availFromBuf);
			recvSize += availFromBuf;
			pagePtr += availFromBuf;
			freeInPage -= availFromBuf;
			availFromBuf = 0;
		    }
		    else if ( freeInPage < availFromBuf )
		    {
			memcpy(pagePtr, bufPtr, freeInPage);
			recvSize += freeInPage;
			bufPtr += freeInPage;
			availFromBuf -= freeInPage;
			freeInPage = 0;
		    }		    
		}
		_pDBMng->bufferUnfix(bp, true, _pTabMng->getLockHandler());
	    }
	    catch ( Exception e )
	    {
		if ( bp.isFixed() )
		    _pDBMng->bufferUnfix(bp, true, _pTabMng->getLockHandler());
		if ( pageId )
		{
		    _pTabMng->decreaseBlobRef(tabSetId, pageId);
		}
	    }
	    
#ifdef CGDEBUG
	    _pDBMng->log(_modId, Logger::DEBUG, Chain(" Thread ") + Chain(_idx) + Chain(" : Blob transfer finished"));
#endif

	    break;
	}
	case CegoDbHandler::GETBLOB:
	{
	    _lastAction = Chain("get blob");

	    Chain tableSet;

	    PageIdType pageId;
	    
	    pSH->getGetBlobArg(tableSet, pageId);

	    unsigned tabSetId = _pDBMng->getTabSetId(tableSet);

	    NetHandler *pN = pSH->getNetHandler();	    	

	    CegoBufferPage bp;
	    try
	    {
		_pDBMng->bufferFix(bp, tabSetId, pageId, CegoBufferPool::SYNC,
				   _pTabMng->getLockHandler());
	    	
		unsigned long long blobSize;
		memcpy (&blobSize, bp.getChunkEntry() + sizeof(unsigned long long), sizeof(unsigned long long));
		
		pSH->sendBlobSize(blobSize);
		
		unsigned long long sentByte = 0;
		
		while ( bp.isFixed() )
		{
		    pN->recvAck();
		    
		    unsigned chunkSize;

		    // first page with written clob size, so we have to sub the size bytes
		    if ( sentByte == 0 )
		    {
			// take the full page chunk ?
			if (  bp.getChunkLen() - 2 * sizeof(unsigned long long ) < blobSize  )
			{
			    chunkSize = bp.getChunkLen() - 2 * sizeof(unsigned long long);
			}
			else // just take the needed chunk
			{
			    chunkSize = blobSize;
			}
		    }
		    else // just take the needed
		    {
			// take the full page chunk ?
			if (  (unsigned long long)bp.getChunkLen() < blobSize - sentByte  )
			{
			    chunkSize = bp.getChunkLen();
			}
			else // just take the needed chunk
			{
			    chunkSize = blobSize - sentByte;
			}
		    }

		    if ( sentByte == 0 )
		    {
			pN->setMsg(bp.getChunkEntry() + 2 * sizeof(unsigned long long), chunkSize );
		    }
		    else
		    {
			pN->setMsg(bp.getChunkEntry(), chunkSize);
		    }
		    sentByte += chunkSize;

		    pN->writeMsg();
		    
		    pageId = bp.getNextPageId();
		    
		    _pDBMng->bufferUnfix(bp, false, _pTabMng->getLockHandler());
		    
		    if ( pageId )			
		    {
			_pDBMng->bufferFix(bp, tabSetId, pageId, CegoBufferPool::SYNC,
					   _pTabMng->getLockHandler());
		    }		    
		}
	    }
	    catch ( Exception e )
	    {
		if ( bp.isFixed() )
		    _pDBMng->bufferUnfix(bp, true, _pTabMng->getLockHandler());
	    }
	    break;	    
	}
	case CegoDbHandler::DELBLOB:
	{
	    _lastAction = Chain("del blob");

	    Chain tableSet;

	    PageIdType pageId;
	    
	    pSH->getGetBlobArg(tableSet, pageId);

	    unsigned tabSetId = _pDBMng->getTabSetId(tableSet);

	    _pTabMng->decreaseBlobRef(tabSetId, pageId);

	    pSH->sendResponse(Chain("Blob deleted"));
	    break;	    
	}
	case CegoDbHandler::PUTCLOB:
	{
	    _lastAction = Chain("put clob");

	    Chain tableSet;
	    unsigned long long clobSize;
	    
	    pSH->getPutClobArg(tableSet, clobSize);

	    unsigned tabSetId = _pDBMng->getTabSetId(tableSet);

	    NetHandler *pN = pSH->getNetHandler();

	    PageIdType pageId = 0;

	    CegoBufferPage bp;

	    try
	    {
		_pTabMng->getNewFilePage(bp, tabSetId, CegoObject::TABLE);
	    }
	    catch ( Exception e )
	    {
		Chain msg;
		e.pop(msg);
		pSH->sendError(msg);
		break;
	    }

	    bp.initPage(CegoBufferPage::CLOB);
	    
	    pageId = bp.getPageId();
		
	    try
	    {
#ifdef CGDEBUG
		_pDBMng->log(_modId, Logger::DEBUG, Chain(" Thread ") + Chain(_idx) + Chain(" : Sending clob info [") + Chain(pageId) + Chain("]"));
#endif
		pSH->sendClobInfo(pageId);
		
		unsigned freeInPage = bp.getChunkLen();
		char* pagePtr = bp.getChunkEntry();

		unsigned long long clobRef = 0;
		memcpy(pagePtr, &clobRef, sizeof(unsigned long long));
		pagePtr += sizeof(unsigned long long);
		freeInPage -= sizeof(unsigned long long);
		
		memcpy(pagePtr, &clobSize, sizeof(unsigned long long));
		pagePtr += sizeof(unsigned long long);
		freeInPage -= sizeof(unsigned long long);
		
		char *bufPtr = 0; 
		unsigned availFromBuf = 0;
		
		unsigned long long recvSize = 0;

		while ( recvSize < clobSize )
		{	
		    try
		    {
			bool ackRequired = false;

			if ( availFromBuf == 0 )		    
			{
			    if ( pN->waitMsg(NETMNG_WAITMSG_TIMEOUT) == false )
				throw Exception(EXLOC, "Timeout exceeded for blob chunk");
			    pN->readMsg();
			    ackRequired = true;
			    bufPtr = pN->getMsg();
			    availFromBuf = pN->getMsgSize();
			}    
			
			if ( freeInPage == 0 )
			{
			    CegoBufferPage nextPage;
			    _pTabMng->getNewFilePage(nextPage, tabSetId, CegoObject::TABLE);
			    nextPage.initPage(CegoBufferPage::CLOB);
			    
			    bp.setNextPageId(nextPage.getPageId());
			    
			    _pDBMng->bufferUnfix(bp, true, _pTabMng->getLockHandler());
			    
			    bp = nextPage;
			    
			    freeInPage = bp.getChunkLen();
			    pagePtr = bp.getChunkEntry();
			}

			if ( ackRequired )
			{
			    pN->sendAck();
			    ackRequired = false;
			}		    
		    }
		    catch ( Exception e )
		    {			
			pN->sendNack();
			throw Exception(EXLOC, Chain("Cannot put clob"), e);
		    }
		    
		    if ( freeInPage >= availFromBuf )
		    {
			memcpy(pagePtr, bufPtr, availFromBuf);
			recvSize += availFromBuf;
			pagePtr += availFromBuf;
			freeInPage -= availFromBuf;
			availFromBuf = 0;
		    }
		    else if ( freeInPage < availFromBuf )
		    {
			memcpy(pagePtr, bufPtr, freeInPage);
			recvSize += freeInPage;
			bufPtr += freeInPage;
			availFromBuf -= freeInPage;
			freeInPage = 0;
		    }		    
		}
		_pDBMng->bufferUnfix(bp, true, _pTabMng->getLockHandler());
	    }
	    catch ( Exception e )
	    {
		if ( bp.isFixed() )
		    _pDBMng->bufferUnfix(bp, true, _pTabMng->getLockHandler());
		if ( pageId )
		{
		    _pTabMng->decreaseClobRef(tabSetId, pageId);
		}
	    }
#ifdef CGDEBUG
	    _pDBMng->log(_modId, Logger::DEBUG, Chain(" Thread ") + Chain(_idx) + Chain(" : Clob transfer finished"));
#endif
	    break;
	}
	case CegoDbHandler::GETCLOB:
	{
	    _lastAction = Chain("get clob");

	    Chain tableSet;
	    
	    PageIdType pageId;
	    
	    pSH->getGetClobArg(tableSet, pageId);

	    unsigned tabSetId = _pDBMng->getTabSetId(tableSet);

	    NetHandler *pN = pSH->getNetHandler();	    	

	    CegoBufferPage bp;
	    try
	    {
		_pDBMng->bufferFix(bp, tabSetId, pageId, CegoBufferPool::SYNC,
				   _pTabMng->getLockHandler());
	
		unsigned long long clobSize;
		memcpy (&clobSize, bp.getChunkEntry() + sizeof(unsigned long long), sizeof(unsigned long long));
		
		pSH->sendClobSize(clobSize);
		
		unsigned long long sentByte = 0;
		
		while ( bp.isFixed() )
		{
		    pN->recvAck();
		    
		    unsigned chunkSize;

		    // first page with written clob size, so we have to sub the size bytes
		    if ( sentByte == 0 )
		    {
			// take the full page chunk ?
			if (  bp.getChunkLen() - 2 * sizeof(unsigned long long ) < clobSize  )
			{
			    chunkSize = bp.getChunkLen() - 2 * sizeof(unsigned long long);
			}
			else // just take the needed chunk
			{
			    chunkSize = clobSize;
			}
		    }
		    else // just take the needed
		    {
			// take the full page chunk ?
			if (  (unsigned long long)bp.getChunkLen() < clobSize - sentByte  )
			{
			    chunkSize = bp.getChunkLen();
			}
			else // just take the needed chunk
			{
			    chunkSize = clobSize - sentByte;
			}
		    }
		    
		    if ( sentByte == 0 )
		    {
			pN->setMsg(bp.getChunkEntry() + 2 * sizeof(unsigned long long), chunkSize );
		    }
		    else
		    {
			pN->setMsg(bp.getChunkEntry(), chunkSize);
		    }
		    sentByte += chunkSize;

		    pN->writeMsg();
		    
		    pageId = bp.getNextPageId();
		    
		    _pDBMng->bufferUnfix(bp, false, _pTabMng->getLockHandler());
		    
		    if ( pageId )			
		    {
			_pDBMng->bufferFix(bp, tabSetId, pageId, CegoBufferPool::SYNC,
					   _pTabMng->getLockHandler());
		    }		    
		}
	    }
	    catch ( Exception e )
	    {
		if ( bp.isFixed() )
		    _pDBMng->bufferUnfix(bp, true, _pTabMng->getLockHandler());
	    }
	    break;   
	}
	case CegoDbHandler::DELCLOB:
	{
	    _lastAction = Chain("del clob");

	    Chain tableSet;
	    PageIdType pageId;
	    pSH->getGetClobArg(tableSet, pageId);
	    unsigned tabSetId = _pDBMng->getTabSetId(tableSet);
	    _pTabMng->decreaseClobRef(tabSetId, pageId);
	    pSH->sendResponse(Chain("Clob deleted"));
	    
	    break;   
	}
	case CegoDbHandler::SESSION_CLOSE:
	{
	    _lastAction = Chain("close session");
	    
	    isTerminated=true;
	    pSH->sendResponse(Chain("Session Closed"));
	    break;
	}
	case CegoDbHandler::REQTIMEOUT:
	case CegoDbHandler::UNKNOWN:
	{
	    break;
	}
	}
    }
    catch ( Exception e)
    {	
	Chain msg;
	Chain module;
	unsigned line;
	
	Chain exep;
	while ( e.pop(module, line, msg) )
	{
	    exep += Chain("\n\t") + module + Chain("(") +  Chain(line) + Chain(") : ") + msg;
	}
		
	_pDBMng->log(_modId, Logger::LOGERR, Chain("Thread ") + Chain(_idx) + Chain(" : DB request failed :") + exep);
	
	if ( _pTabMng->isAborted() )
	{
	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Thread ") + Chain(_idx) + Chain(" : Abort catched, proceed with session"));
	    _pTabMng->proceed();
	}
	
	pSH->sendError(e.getBaseMsg());
    }

    _pCost->stop();

    Datetime ts;
    
    addHistory(ts.asLong(), _lastAction, _pCost->getSum() / 1000000 );
    
    return isTerminated;
}

void CegoDbThread::addHistory(unsigned long long ts, const Chain& queryString, unsigned long long cost)
{
    if ( _numLast > 0 || _numCost > 0 )
    {
	P();
	if ( _numLast > 0 )
	{
	    QueryEntry *pFirst = _queryList.First();
	    if ( pFirst && _queryList.Size() >= _numLast )
		_queryList.Remove(*pFirst);
	    _queryList.Insert( QueryEntry(_idx, _qid, ts, queryString, cost));
	}
	
	if ( _numCost > 0 )
	{
	    QueryEntry costEntry(_idx, _qid, ts, queryString, cost);
	    costEntry.setCostOrder();
	    _costTree.Insert(costEntry);
	    QueryEntry *pFirst = _costTree.First();
	    if ( pFirst && (unsigned)_costTree.Size() >= _numCost )
		_costTree.Remove(*pFirst);
	}
	
	_qid++;
    
	V();
    }
}

// private methods

void CegoDbThread::P() const
{    
    histLock.writeLock(QH_LOCKTIMEOUT);
}

void CegoDbThread::V() const
{
    histLock.unlock();
}
