///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoProcedure.cc  
// -----------------                                                     
// Cego procedure class implementation 
//
// Design and Implementation by Bjoern Lemke               
//               
// (C)opyright 2000-2025 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: CegoProcedure
//
// Description: Stored procedure container class
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

// cego includes
#include "CegoDefs.h"
#include "CegoProcedure.h"
#include "CegoTypeConverter.h"
#include "CegoProcException.h"
#include "CegoDatabaseFormater.h"

CegoProcedure::CegoProcedure()
{
}

CegoProcedure::CegoProcedure(const Chain& procName, CegoProcBlock *pBlock)
{
    _procName = procName;
    _pBlock = pBlock;    
    _procType = PROCEDURE;
    _returnType = NULL_TYPE;
    _returnTypeLen = 0;
    _returnTypeDim = 0;
    _pMasterBlock = 0;
    _cacheEnabled = false;
}
 
CegoProcedure::CegoProcedure(const Chain& procName, CegoProcBlock *pBlock, CegoDataType returnType, unsigned returnTypeLen, unsigned returnTypeDim)
{
    _procName = procName;
    _pBlock = pBlock;
    _returnType = returnType;
    _returnTypeLen = returnTypeLen;
    _returnTypeDim = returnTypeDim;
    _procType = FUNCTION;
    _pMasterBlock = 0;
    _cacheEnabled = false;
}

CegoProcedure::~CegoProcedure()
{
    cleanUp();
    if ( _pBlock )
       delete _pBlock;
}

void CegoProcedure::setMasterBlock(CegoProcBlock *pBlock)
{
    _pMasterBlock = pBlock;
}

CegoProcBlock* CegoProcedure::getMasterBlock() const
{
    return _pMasterBlock;
}

const Chain& CegoProcedure::getName() const
{
    return _procName;
}

CegoProcBlock* CegoProcedure::getBlock()
{
    return _pBlock;
}

ListT<CegoProcVar>& CegoProcedure::getOutParamList()
{
    return _outParamList;
}

void CegoProcedure::getArgList(ListT<CegoProcVar>& argList)
{
    CegoProcVar *pArg = _pBlock->getVarList().First();
    
    while ( pArg )
    {
	if ( pArg->getVarType() == CegoProcVar::INVAR  || pArg->getVarType() == CegoProcVar::OUTVAR )
	    argList.Insert(*pArg);
	pArg = _pBlock->getVarList().Next();
    }    
}

void CegoProcedure::execute(const ListT<CegoFieldValue>& fvl)
{    
    CegoFieldValue *pValue = fvl.First();
    CegoProcVar *pVar = _pBlock->getVarList().First();

    // for proc cache
    ListT<CegoFieldValue> infvl;

    // we just treat the in out param values at the beginning of the param list in the block
    if ( pVar )
	if ( pVar->getVarType() == CegoProcVar::BLOCKVAR )
	    pVar = 0;
    
    while ( pValue && pVar)
    {
	if ( pVar->getVarType() == CegoProcVar::INVAR )
	{
	    if ( pValue->getType() == VARCHAR_TYPE )
	    {
		if ( pVar->getLength() < pValue->getLength() - 1 )
		    throw Exception(EXLOC, Chain("Parameter length for ") + pVar->getName() + Chain(" exceeded ( max len is ") + Chain(pVar->getLength()) + Chain(")"));
	    }
	    
	    pVar->setValue(*pValue);

	    if ( _cacheEnabled && _pBlock->isStatic() )
	    {
		infvl.Insert(*pValue);
	    }
	}
	
	pValue = fvl.Next();
	pVar = _pBlock->getVarList().Next();
	
	// we just treat the in out param values at the beginning of the param list in the block
	if ( pVar )
	    if ( pVar->getVarType() == CegoProcVar::BLOCKVAR )
		pVar = 0;
    }

    if ( pVar || pValue )
    {	
	Chain msg = Chain("Mismatched parameter count for procedure ");
	throw Exception(EXLOC, msg);
    }

    if ( _cacheEnabled && _pBlock->isStatic() )
    {
	CegoFieldValue procResult;
        if ( getCacheValue(infvl, procResult) )
	{
	    // cout << "Found result .." << procResult << endl;
	    _pBlock->setRetVal(procResult);
	    return;
	}
    }
    
    CegoException excep = _pBlock->execute();

    if ( _cacheEnabled && _pBlock->isStatic() )
    {
	addCacheValue(infvl, _pBlock->getRetVal());
    }
    
    if ( excep != NONE_EXCEP && excep != RETURN_EXCEP )
    {
	Chain msg = Chain("Procedure exception : ") + _pBlock->getExceptionMsg();
	throw Exception(EXLOC, msg);
    }

    pValue = fvl.First();
    pVar = _pBlock->getVarList().First(); 
    _outParamList.Empty();

    
    while ( pValue && pVar)
    {
	if ( pVar->getVarType() == CegoProcVar::OUTVAR )
	{
	    Chain var = pValue->valAsChain();

	    if ( _pMasterBlock )
	    {
		_pMasterBlock->setValue(var, pVar->getValue());
	    }
	    
	    CegoFieldValue fv = pVar->getValue();
	    _outParamList.Insert(CegoProcVar(var, CegoProcVar::OUTVAR, fv.getType(), fv.getLength(), fv.getDim(), fv));
	}
	pValue = fvl.Next();
	pVar = _pBlock->getVarList().Next();	
    }
}

void CegoProcedure::cleanUp()
{
    _pMasterBlock = 0;
    _pBlock->cleanUp();
}

CegoDataType CegoProcedure::getReturnType() const
{
    return _returnType;
}

unsigned CegoProcedure::getReturnTypeLen() const
{
    return _returnTypeLen;
}

unsigned CegoProcedure::getReturnTypeDim() const
{
    return _returnTypeDim;
}

CegoFieldValue CegoProcedure::getRetVal() const
{
    // still we have to cast for type or dim adjustments
    if ( _pBlock->getRetVal().castTo(_returnType, _returnTypeDim) == false )
    {
	Chain msg = Chain("Cannot cast to type ")  + CegoTypeConverter::getTypeString(_returnType);
	throw Exception(EXLOC, msg);	
    }	

    return _pBlock->getRetVal();
}

CegoProcedure::ProcType CegoProcedure::getProcType() const
{
    return _procType;
}

CegoProcedure& CegoProcedure::operator = ( const CegoProcedure& p)
{
    _procName = p._procName;
    _pBlock = p._pBlock;
    _pMasterBlock = p._pMasterBlock;
    _procType = p._procType;
    _returnType = p._returnType;
    _returnTypeLen = p._returnTypeLen;
    _returnTypeDim = p._returnTypeDim;
    _cacheEnabled = p._cacheEnabled;
    return (*this);
}

bool CegoProcedure::operator == ( const CegoProcedure& p) const
{
    if ( _procName == p._procName )
	return true;
    return false;
}

Chain CegoProcedure::toChain(unsigned defTabSetId) const
{
    Chain s;
    s = Chain("procedure ") + _procName + Chain("(");
    CegoProcVar *pVar = _pBlock->getVarList().First();
    while ( pVar )
    {
	if ( pVar->getVarType() == CegoProcVar::BLOCKVAR )
	    pVar = 0;
	else
	{
	    s += pVar->toChain();
	    pVar = _pBlock->getVarList().Next();
	    	    
	    if ( pVar )
	    {
		if ( pVar->getVarType() != CegoProcVar::BLOCKVAR )
		s += Chain(",\n     ");
	    }
	}
    } 

    s += Chain(")");
    if ( _procType == FUNCTION )
    {
	s += Chain(" return");
	switch ( _returnType )
	{
	case INT_TYPE:
	    s += Chain(" int");
	    break;
	case LONG_TYPE:
	    s += Chain(" long");
	    break;
	case VARCHAR_TYPE:
	    s += Chain(" string(");
	    s += Chain(_returnTypeLen);
	    s += Chain(")");
	    break;
	case BOOL_TYPE:
	    s += Chain(" bool");
	    break;
	case DATETIME_TYPE:
	    s += Chain(" datetime");
	    break;
	case BIGINT_TYPE:
	    s += Chain(" bigint(");
	    s += Chain(_returnTypeLen);
	    s += Chain(")");
	    break;
	case FLOAT_TYPE:
	    s += Chain(" float");
	    break;
	case DOUBLE_TYPE:
	    s += Chain(" double");
	    break;
	case DECIMAL_TYPE:
	    s += Chain(" decimal(");
	    s += Chain(_returnTypeLen) + Chain(",") + Chain(_returnTypeDim);
	    s += Chain(")");
	    break;
	case SMALLINT_TYPE:
	    s += Chain(" smallint");
	    break;
	case TINYINT_TYPE:
	    s += Chain(" tinyint");
	    break;	  
	case NULL_TYPE:
	    s += Chain(" null");
	    break;
	case BLOB_TYPE:
	    throw Exception(EXLOC, Chain("Blob not supported as return type"));
	case CLOB_TYPE:
	    throw Exception(EXLOC, Chain("Clob not supported as return type"));
	case PAGEID_TYPE:
	    throw Exception(EXLOC, Chain("Return type not supported"));
	}
    }

    s += Chain("\n");
    s += Chain("begin\n");

    Chain indent = Chain(DEFAULTINDENT);

    s += _pBlock->toChain(defTabSetId, indent);
    
    s += Chain("end");
    return s;
}

Chain CegoProcedure::dbFormat(CegoDatabaseFormater *pForm) const
{
    return pForm->formatProcedure(_procName, _procType, _returnType, _returnTypeLen, _returnTypeDim, _pBlock);
}

bool CegoProcedure::getCacheValue(const ListT<CegoFieldValue>& fvl, CegoFieldValue& fv)
{
    ProcCacheValue *pPCV = _cacheSet.Find( ProcCacheValue(fvl));
    if ( pPCV )
    {
	fv = pPCV->getRetVal();
	return true;
    }
	
    return false;
}

void CegoProcedure::addCacheValue(const ListT<CegoFieldValue>& fvl, const CegoFieldValue& retVal)
{
    if ( _cacheSet.Size() > PROCQUERYCACHE_MAXNUM )
    {
	ProcCacheValue *pPCV = _cacheSet.First();
	ProcCacheValue *pLast = 0;
	while ( pPCV )
	{
	    pLast = pPCV;
	    pPCV = _cacheSet.Next();
	}
	if ( pLast )
	{
	    _cacheSet.Remove(*pLast);
	}
    }
    _cacheSet.Insert( ProcCacheValue(fvl, retVal) );
}	

void CegoProcedure::enableProcCache(bool isEnabled)
{
    _cacheEnabled = isEnabled;
}
    
CegoProcedure::ProcCacheValue::ProcCacheValue(const ListT<CegoFieldValue>& fvl, const CegoFieldValue& retVal)
{
    CegoFieldValue *pFV = fvl.First();
    while ( pFV )
    {
	_paramHash += pFV->valAsChain();
	pFV = fvl.Next();
	if ( pFV )
	    _paramHash += Chain("#");
    }
    _retVal = retVal;
}

CegoProcedure::ProcCacheValue::ProcCacheValue(const ListT<CegoFieldValue>& fvl)
{
    CegoFieldValue *pFV = fvl.First();
    while ( pFV )
    {
	_paramHash += pFV->valAsChain();
	pFV = fvl.Next();
	if ( pFV )
	    _paramHash += Chain("#");
    }
}

CegoProcedure::ProcCacheValue::ProcCacheValue()
{
}

CegoProcedure::ProcCacheValue::~ProcCacheValue()
{
}

CegoProcedure::ProcCacheValue& CegoProcedure::ProcCacheValue::operator = (const CegoProcedure::ProcCacheValue& pcv)
{
    _paramHash = pcv._paramHash;
    _retVal = pcv._retVal;
    return (*this);
}	

bool CegoProcedure::ProcCacheValue::operator == (const CegoProcedure::ProcCacheValue& pcv) const
{
    return _paramHash == pcv._paramHash;
}

CegoFieldValue& CegoProcedure::ProcCacheValue::getRetVal()
{
    return _retVal;
}
