///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoQuery.cc
// ------------
// Cego query implementation         
//     
// Design and Implementation by Bjoern Lemke
//     
// (C)opyright 2000-2008 Bjoern Lemke
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or (at your option)
// any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; see the file COPYING.  If not, write to
// the Free Software Foundation, 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.
//
// IMPLEMENTATION MODULE
//
// Class: CegoQuery
// 
// Description: 
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

#include "CegoQueryException.h"
#include <lfc/BigDecimal.h>
#include <lfc/Datetime.h>

#include "CegoOutput.h"
#include "CegoQuery.h"


CegoQuery::CegoQuery(CegoDistManager* pGTM, const Chain& tableSet, CegoQuery::TransactionOp to)
{
    if ( to == CegoQuery::START )
	_mode = START_QUERY;
    else if ( to == CegoQuery::COMMIT )
	_mode = COMMIT_QUERY;
    else if ( to == CegoQuery::ROLLBACK )
	_mode = ROLLBACK_QUERY;
    
    _pGTM = pGTM;
    _pLogger = _pGTM->getDBMng();
    _tableSet = tableSet;
    _pPred = 0;
}


CegoQuery::CegoQuery(CegoDistManager* pGTM, const Chain& tableName, const Chain& tableSet, const ListT<CegoField>& schema, const ListT<ListT<CegoExpr*> >& exprListArray)
{
    _mode = INSERT_QUERY;
    _pGTM = pGTM;
    _pLogger = _pGTM->getDBMng();
    _tableName = tableName;
    _tableSet = tableSet;
    _schema = schema;
    _pSelect = 0;
    _exprListArray = exprListArray;
    _pPred = 0;
}


CegoQuery::CegoQuery(CegoDistManager* pGTM, const Chain& tableName, const Chain& tableSet,  const ListT<CegoField>& schema, CegoSelect *pSelect)
{
    _mode = INSERTBYSELECT_QUERY;
    _pGTM = pGTM;
    _pLogger = _pGTM->getDBMng();
    _tableName = tableName;
    _tableSet = tableSet;
    _schema = schema;
    _pSelect = pSelect;
    _pPred = 0;
}


CegoQuery::CegoQuery(CegoDistManager* pGTM, const Chain& tableName,  const Chain& tableAlias, const Chain& tableSet, CegoPredDesc* pPred)
{
    _mode = DELETE_QUERY;
    _pGTM = pGTM;
    _pLogger = _pGTM->getDBMng();
    _tableName = tableName;
    _tableAlias = tableAlias;
    _tableSet = tableSet;
    _pPred = pPred;
    _pSelect = 0;    
}

CegoQuery::CegoQuery(CegoDistManager* pGTM, const Chain& tableName, const Chain& tableAlias, const Chain& tableSet, CegoPredDesc* pPred, ListT<CegoField>& updSchema, const ListT<CegoExpr*>& updList, const ListT<CegoReturnVar*> retVarList)
{
    _mode = UPDATE_QUERY;
    _pGTM = pGTM;
    _pLogger = _pGTM->getDBMng();
    _tableName = tableName;
    _tableAlias = tableAlias;
    _tableSet = tableSet;
    _pPred = pPred;
    _schema = updSchema;
    _pSelect = 0;
    _updList = updList;
    _retVarList = retVarList;
}

CegoQuery::CegoQuery(CegoDistManager* pGTM, const Chain& tableName,  const Chain& tableSet, const ListT<CegoAlterDesc>& alterList)
{
    _mode = ALTER_QUERY;
    _pGTM = pGTM;
    _pLogger = _pGTM->getDBMng();
    _tableName = tableName;
    _tableSet = tableSet;
    _alterList = alterList;
    _pPred = 0;
    _pSelect = 0;
}

CegoQuery::CegoQuery(CegoDistManager* pGTM,   const Chain& tableSet, const Chain& objName, CegoObject::ObjectType objType, const Chain& newObjName )
{
    _mode = RENAME_QUERY;
    _pGTM = pGTM;
    _pLogger = _pGTM->getDBMng();
    _tableSet = tableSet;
    _objName = objName;
    _objType = objType;
    _newObjName = newObjName;
    _pPred = 0;
    _pSelect = 0;
}


CegoQuery::~CegoQuery()
{

    CegoExpr **pExpr = _updList.First();
    while ( pExpr )
    {
	delete *pExpr;
	pExpr = _updList.Next();
    }

    ListT<CegoExpr*> *pExprList = _exprListArray.First();
    while ( pExprList )
    {
	CegoExpr **pExpr = pExprList->First();
	while ( pExpr )
	{
	    delete *pExpr;
	    pExpr = pExprList->Next();
	}
	pExprList = _exprListArray.Next();
    }

    switch ( _mode )
    {
    case DELETE_QUERY:
    case UPDATE_QUERY:
    {
	if ( _pPred )
	    delete _pPred;
	break;
    }
    case INSERTBYSELECT_QUERY:
    {
	delete _pSelect;
    }
    default:
	break;
    }
}

void CegoQuery::setBlock(CegoProcBlock *pBlock)
{

    CegoExpr** pExpr = _updList.First();
    while ( pExpr )
    {
	(*pExpr)->setBlock(pBlock);
	pExpr = _updList.Next();
    }

    ListT<CegoExpr*> *pExprList = _exprListArray.First();
    while ( pExprList )
    {
	CegoExpr **pExpr = pExprList->First();
	while ( pExpr )
	{
	    (*pExpr)->setBlock(pBlock);
	    pExpr = pExprList->Next();
	}
	pExprList = _exprListArray.Next();
    }

    if ( _pPred )
    {
	_pPred->setBlock(pBlock);
    }
    return ;
}

Chain CegoQuery::execute(CegoProcBlock *pBlock)
{

    _affCount = 0;

    switch ( _mode )
    {
    case START_QUERY:
    {
	try
	{
	    _pGTM->startDistTransaction(_tableSet);
	}
	catch ( Exception e )
	{
	    Chain msg;
	    e.pop(msg);
	    throw CegoQueryException(COREOP_EXCEP, EXLOC, msg);    
	}
	return Chain("Transaction started");

    }
    case COMMIT_QUERY:
    {
	long opCount = 0;
	try
	{
	    opCount = _pGTM->endDistTransaction(_tableSet);
	}
	catch ( Exception e )
	{
	    Chain msg;
	    e.pop(msg);
	    throw CegoQueryException(COREOP_EXCEP, EXLOC, msg);    
	}
	Chain msg = Chain(opCount) + Chain(" operations committed");
	return msg;
    }
    case ROLLBACK_QUERY:
    {
	long opCount = 0;
	try
	{
	    opCount = _pGTM->rollbackDistTransaction(_tableSet);
	}
	catch ( Exception e )
	{
	    Chain msg;
	    e.pop(msg);
	    throw CegoQueryException(COREOP_EXCEP, EXLOC, msg);    
	}
	Chain msg = Chain(opCount) + Chain(" operations rollbacked");
	return msg;
    }	
    case INSERT_QUERY:
    {

	if ( _pGTM->getAutoCommit() == false )
	{
	    if ( _pGTM->getDistTid(_tableSet) == 0 )
	    {
		_pGTM->startDistTransaction(_tableSet);
	    }
	}

	CegoTableObject oe;

	try
	{
	    _pGTM->getDistObject(_tableSet, _tableName, CegoObject::TABLE, oe);

	}
	catch ( Exception e )
	{
	    Chain msg;
	    e.pop(msg);
	    throw CegoQueryException(INVALID_OBJECT_EXCEP, EXLOC, msg);    
	}
	
	if (  _schema.isEmpty() )
	{   
	    _schema = oe.getSchema();
	}
	else
	{

	    /* complete missing attributes in schema */
	    ListT<CegoField> checkSchema = oe.getSchema();
	    
	    CegoField *pF = checkSchema.First();
	    while ( pF )
	    {
		CegoField *pCF = _schema.Find(*pF);
		if ( pCF == 0  )
		{
		    if ( pF->getValue().getValue() )
		    {
			_schema.Insert( *pF );
			

			ListT<CegoExpr*> *pExprList = _exprListArray.First();
			while ( pExprList )
			{			    
			    CegoFieldValue fv = pF->getValue();
			    CegoExpr* pExpr = new CegoExpr( new CegoTerm( new CegoFactor( fv )));
			    pExprList->Insert(pExpr);
			    pExprList = _exprListArray.Next();
			}
			    
		    }
		    else if ( pF->isNullable() )
		    {
			_schema.Insert( *pF );

			ListT<CegoExpr*> *pExprList = _exprListArray.First();
			while ( pExprList )
			{
			    CegoFieldValue fv;
			    CegoExpr* pExpr = new CegoExpr( new CegoTerm( new CegoFactor( fv )));
			    pExprList->Insert(pExpr);
			    pExprList = _exprListArray.Next();
			}			
		    }
		    else
		    {
			throw CegoQueryException(INVALID_ATTRIBUTE_EXCEP, EXLOC, Chain("Attribute ") + pF->getAttrName() + " not nullable");    
		    }
		}
		else
		{
		    pCF->setType(pF->getType());
		    pCF->setLength(pF->getLength());				     
		}
		pF = checkSchema.Next();
	    }
	    pF = _schema.First();
	    while ( pF )
	    {
		CegoField *pUF = oe.getSchema().Find(*pF);
		if ( pUF )
		{
		    pF->setValue(pUF->getValue());
		    pF->setNullable(pUF->isNullable());
		}
		else
		{
		    throw CegoQueryException(INVALID_ATTRIBUTE_EXCEP, EXLOC, Chain("Unknown attribute ") + pF->getAttrName() );
		}
		
		pF = _schema.Next();
	    }
	}
	

	ListT<CegoExpr*> *pExprList = _exprListArray.First();
	while ( pExprList )
	{	   
	    CegoField* pFV = _schema.First();
	    CegoExpr** pExpr = pExprList->First();
	    
	    int pos=1;
	    while ( pFV && pExpr)
	    {	    
		
		(*pExpr)->setBlock(pBlock);
		CegoFieldValue fv = (*pExpr)->evalFieldValue();
		
		CegoQueryHelper qh;
		qh.prepareFieldValue(pFV, fv, pos);
		
		pFV->setValue(fv); 
		pFV = _schema.Next();
		pExpr = pExprList->Next();	    
		pos++;
	    }
	    if ( pFV || pExpr )
	    {
		throw CegoQueryException(INVALID_ATTRIBUTE_EXCEP, EXLOC, Chain("Mismatched argument count for value list"));
	    }	    

	    try 
	    {
		_pGTM->insertDistDataTable(oe, _schema);
		_affCount++;
	    }
	    catch ( Exception e )
	    {
		Chain msg;
		e.pop(msg);
		throw CegoQueryException(COREOP_EXCEP, EXLOC, msg);
	    }

	    pExprList = _exprListArray.Next();
	}

	return Chain(_affCount) + Chain(" tuples inserted");

    }
    case INSERTBYSELECT_QUERY:
    {

	if ( _pGTM->getAutoCommit() == false )
	{
	    if ( _pGTM->getDistTid(_tableSet) == 0 )
	    {
		_pGTM->startDistTransaction(_tableSet);
	    }
	}
 
	CegoTableObject oe;

	try
	{
	    _pGTM->getDistObject(_tableSet, _tableName, CegoObject::TABLE, oe);

	}
	catch ( Exception e )
	{
	    Chain msg;
	    e.pop(msg);
	    throw CegoQueryException(INVALID_OBJECT_EXCEP, EXLOC, msg);    
	}
	
	try {

	    if (  _schema.isEmpty() )
	    {   
		_schema = oe.getSchema();
	    }
	    else
	    {
		
		/* complete missing attributes in schema */
		ListT<CegoField> checkSchema = oe.getSchema();

		CegoField *pF = checkSchema.First();
		while ( pF )
		{
		    CegoField *pCF = _schema.Find(*pF);
		    if ( pCF == 0  )
		    {
			if ( pF->getValue().getValue() )
			{
			    _schema.Insert( *pF );			    
			}
			else if ( pF->isNullable() )
			{
			    _schema.Insert( *pF );				
			}
			else
			{
			    throw CegoQueryException(INVALID_ATTRIBUTE_EXCEP, EXLOC, Chain("Attribute ") + pF->getAttrName() + " not nullable");    
			}
		    }
		    else
		    {
			pCF->setType(pF->getType());
			pCF->setLength(pF->getLength());				     
		    }
		    pF = checkSchema.Next();
		    
		}
		
		pF = _schema.First();
		while ( pF )
		{
		    CegoField *pUF = oe.getSchema().Find(*pF);
		    if ( pUF )
		    {
			pF->setValue(pUF->getValue());
			pF->setNullable(pUF->isNullable());
		    }
		    else
		    {
			throw CegoQueryException(INVALID_ATTRIBUTE_EXCEP, EXLOC, Chain("Unknown attribute ") + pF->getAttrName() );
		    }
		    
		    pF = _schema.Next();
		}
	    }
	    
	    
	    int tabSetId = _pGTM->getDBMng()->getTabSetId(_tableSet);    
	    
	    _pSelect->setProcBlock(pBlock);
	    _pSelect->setTabSetId(tabSetId);
	    _pSelect->prepare();
	    // _pSelect->getSchema(schema);
	    
	    ListT<CegoField> fl;
	    bool moreTuple = _pSelect->nextTuple(fl);
	    
	    while ( moreTuple )
	    {
		ListT<CegoField> insertSchema = _schema;
		CegoField* pFV = insertSchema.First();
		CegoField* pSF = fl.First();
		
		int pos=1;
		while ( pFV && pSF)
		{
		    
		    CegoQueryHelper qh;
		    CegoFieldValue fv = pSF->getValue();

		    qh.prepareFieldValue(pFV, fv, pos);
		    
		    pFV->setValue(fv); 
		    pFV = insertSchema.Next();
		    pSF = fl.Next();	    
		    pos++;
		}

		_pGTM->insertDistDataTable(oe, insertSchema);
		_affCount++;
		
		moreTuple = _pSelect->nextTuple(fl);
	    }
	    _pSelect->cleanUp();
	}
	catch ( Exception e )
	{
	    _pSelect->cleanUp();
	    Chain msg;
	    e.pop(msg);
	    throw CegoQueryException(COREOP_EXCEP, EXLOC, msg);
	}

	return Chain(_affCount) + Chain(" tuples inserted");

    }
    case DELETE_QUERY:
    {

	if ( _pGTM->getAutoCommit() == false )
	{
	    if ( _pGTM->getDistTid(_tableSet) == 0 )
	    {
		_pGTM->startDistTransaction(_tableSet);
	    }
	}
	
	CegoTableObject oe;
	
	try 
	{	    
	    _pGTM->getDistObject(_tableSet, _tableName, CegoObject::TABLE, oe);

	}
	catch ( Exception e )
	{
	    Chain msg;
	    e.pop(msg);
	    throw CegoQueryException(INVALID_OBJECT_EXCEP, EXLOC, msg);
	}

	try 
	{
	    oe.setTabAlias(_tableAlias);
	    _affCount = _pGTM->deleteDistDataTable(oe, _pPred, pBlock);
	}
	catch ( Exception e )
	{
	    Chain msg;
	    e.pop(msg);
	    throw CegoQueryException(COREOP_EXCEP, EXLOC, msg);    
	}

	return Chain(_affCount) + Chain(" tuples deleted");

    }
    case UPDATE_QUERY:
    {

	if ( _pGTM->getAutoCommit() == false )
	{
	    if ( _pGTM->getDistTid(_tableSet) == 0 )
	    {
		_pGTM->startDistTransaction(_tableSet);
	    }
	}

	CegoTableObject oe;

	try 
	{
	    _pGTM->getDistObject(_tableSet, _tableName, CegoObject::TABLE, oe);
	}
	catch ( Exception e )
	{
	    Chain msg;
	    e.pop(msg);
	    throw CegoQueryException(INVALID_OBJECT_EXCEP, EXLOC, msg);    
	}
	
	CegoField *pF = _schema.First();
	while ( pF )
	{
	    CegoField *pUF = oe.getSchema().Find(*pF);
	    if ( pUF )
	    {		
		pF->setLength(pUF->getLength());
		pF->setType(pUF->getType());
		pF->setNullable(pUF->isNullable());		
		pF->setValue(pUF->getValue());
	    }
	    else
	    {
		throw CegoQueryException(INVALID_ATTRIBUTE_EXCEP, EXLOC, Chain("Unknown attribute ") + pF->getAttrName() );
	    }
	    
	    pF = _schema.Next();
	}
	
	try 
	{
	    oe.setTabAlias(_tableAlias);
	   _affCount =  _pGTM->updateDistDataTable(oe, _pPred, _schema, _updList,  pBlock);

	}
	catch ( Exception e )
	{

	    Chain msg;
	    e.pop(msg);
	    throw CegoQueryException(COREOP_EXCEP, EXLOC, msg);	    
	}
	
	CegoReturnVar **pRV = _retVarList.First();
	while ( pRV ) 
	{	    
	    if ( pBlock )
	    {
		
		CegoExpr *pExpr = (*pRV)->getExpr();

		pExpr->setFieldListArray(&_schema, 1);
		CegoFieldValue fv = pExpr->evalFieldValue();

		pBlock->setValue((*pRV)->getVarName(), fv); 
	    }
	    pRV = _retVarList.Next();
	}

	return Chain(_affCount) + Chain(" tuples updated");

    }
    case ALTER_QUERY:
    {
	if ( _pGTM->getAutoCommit() == false )
	{
	    if ( _pGTM->getDistTid(_tableSet) == 0 )
	    {
		_pGTM->startDistTransaction(_tableSet);
	    }
	}
 
	CegoTableObject oe;

	try
	{

	    _pGTM->getDistObject(_tableSet, _tableName, CegoObject::TABLE, oe);

	}
	catch ( Exception e )
	{
	    Chain msg;
	    e.pop(msg);
	    throw CegoQueryException(INVALID_OBJECT_EXCEP, EXLOC, msg);    
	}

	try 
	{
	
	    _pGTM->alterDistDataTable(oe, _alterList);
	    _affCount++;

	}
	catch ( Exception e )
	{
	    Chain msg;
	    e.pop(msg);
	    throw CegoQueryException(COREOP_EXCEP, EXLOC, msg);	    
	}
	    
	return Chain("Table altered");	

    }
    case RENAME_QUERY:
    {


	if ( _pGTM->getAutoCommit() == false )
	{
	    if ( _pGTM->getDistTid(_tableSet) == 0 )
	    {
		_pGTM->startDistTransaction(_tableSet);
	    }
	}
	
	try 
	{

	    _pGTM->renameDistObject(_tableSet, _objName, _objType, _newObjName);
	    _affCount++;

	}
	catch ( Exception e )
	{
	    Chain msg;
	    e.pop(msg);
	    throw CegoQueryException(COREOP_EXCEP, EXLOC, msg);   
	}
	return Chain("Object renamed");	

    }
    }
}

long CegoQuery::getAffectedCount() const
{
    return _affCount;
}

Chain CegoQuery::toChain(const Chain& indent) const
{
    Chain s;
    switch ( _mode )
    {
    case START_QUERY:
    {
	s = indent + Chain("start transaction");
	break;
    }
    case COMMIT_QUERY:
    {
	s = indent + Chain("commit");
	break;
    }
    case ROLLBACK_QUERY:
    {
	s = indent + Chain("rollback");
	break;
    }
    case INSERT_QUERY:
    {

	
	s = indent + Chain("insert into ") + _tableName;

	if ( _schema.Size() > 0 )
	{
	    CegoField* pFV = _schema.First();
	    
	    s += " ( ";	
	    
	    while ( pFV )
	    {
		s += pFV->getAttrName();
		pFV = _schema.Next();
		if ( pFV  )
		    s += Chain(", ");
	    }
	    
	    s += Chain(" )"); 
	}
	s += Chain("\n") + indent + Chain(" values ");

	ListT<CegoExpr*> *pExprList = _exprListArray.First();
	while ( pExprList )
	{			    

	    s += Chain("("); 
	
	    CegoExpr **pExpr = pExprList->First();
	    while ( pExpr )
	    {
		s += (*pExpr)->toChain();
		pExpr = pExprList->Next();
		if ( pExpr )
		    s += Chain(", ");
	    }
	    s += Chain(")");
	    pExprList = _exprListArray.Next();
	    if ( pExprList )
		s += Chain(", ");		
	}
	break;
    }
    case INSERTBYSELECT_QUERY:
    {

	s = indent + Chain("insert into ") + _tableName;

	if ( _schema.Size() > 0 )
	{
	    CegoField* pFV = _schema.First();
	    
	    s += Chain(" ( ");	
	    
	    while ( pFV )
	    {
		s += pFV->getAttrName();
		pFV = _schema.Next();
		if ( pFV  )
		    s += Chain(", ");
	    }
	    
	    s += Chain(" )"); 
	}
	s += Chain("\n") + indent;
	s += _pSelect->toChain(indent);

	break;
    }
    case DELETE_QUERY:
    {
	s = indent + Chain("delete from ") + _tableName;

	if ( _tableName != _tableAlias )
	    s += Chain(" ") + _tableAlias;

	if (_pPred)
	{
	    s += Chain(" where ");
	    s += _pPred->toChain();
	}
	break;
    }
    case UPDATE_QUERY:
    {

	s = indent + Chain("update ") + _tableName;

	if ( _tableName != _tableAlias )
	    s += Chain(" ") + _tableAlias;
	
	s += Chain(" set ");

	CegoField* pFV = _schema.First();
	CegoExpr** pExpr = _updList.First();
		    
	while ( pFV && pExpr)
	{
	    (*pExpr)->toChain();
	    
	    s += pFV->getAttrName() + Chain("=") + (*pExpr)->toChain();
	    pFV = _schema.Next();
	    pExpr = _updList.Next();
	    if ( pFV && pExpr )
		s += Chain(", ");
	}
	
	if (_pPred)
	{
	    s += Chain(" where ");
	    s += _pPred->toChain();
	}

	
	if ( _retVarList.isEmpty() == false )
	{ 
	    s += Chain(" return ");
	    CegoReturnVar **pRV = _retVarList.First();
	    while ( pRV ) 
	    {
		s += (*pRV)->toChain();
		pRV = _retVarList.Next();
		if ( pRV ) 
		    s += Chain(",");
	    }
	}   

	break;
    }
    case ALTER_QUERY:
    {
	s = indent + Chain("alter ") + _tableName + Chain(" ");
	CegoAlterDesc *pAD = _alterList.First();
	while ( pAD ) 
	{
	    s += pAD->toChain();
	    pAD = _alterList.Next();
	    if ( pAD ) 
		s += ",";
	}
	break;
    }
    case RENAME_QUERY:
    {
	s = indent + Chain("rename ");
	switch ( _objType )
	{
	case CegoObject::TABLE:
	{
	    s += Chain("table ");
	    break;
	}
	case CegoObject::INDEX:
	{
	    s += Chain("index ");
	    break;
	}
	case CegoObject::FKEY:
	{
	    s += Chain("key ");
	    break;
	}
	case CegoObject::PROCEDURE:
	{
	    s += Chain("procedure ");
	    break;
	}
	case CegoObject::VIEW:
	{
	    s += Chain("view ");
	    break;
	}
	default:
	    break;
	}
	s += _objName + Chain(" to ") + _newObjName;

	break;
    }
    }

    return s;
    
}
