/*!
  @file           IFR_UpdatableRowSet.cpp
  @author         TTK
  @ingroup        IFR_Fetch
  @brief          Implements class for updatable rowset processing
  @see            

\if EMIT_LICENCE

    ========== licence begin  GPL
    Copyright (c) 2001-2005 SAP AG

    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
    of the License, 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; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end



\endif
*/

#include "Interfaces/Runtime/IFR_UpdatableRowSet.h"
#include "Interfaces/Runtime/IFR_PreparedStmt.h"

IFR_UpdatableRowSet::IFR_UpdatableRowSet (IFR_ResultSet *resultSet, IFR_Bool& memory_ok)
:IFR_LOBHost(resultSet->m_Connection->allocator),
 IFR_RowSet (resultSet, memory_ok),
 m_updateStmt (NULL),
 m_insertStmt (NULL),
 m_deleteStmt (NULL)
{
  DBUG_METHOD_ENTER(IFR_UpdatableRowSet, IFR_UpdatableRowSet); 
}

IFR_UpdatableRowSet::~IFR_UpdatableRowSet()
{
  m_resultset->m_Connection->releaseStatement (m_updateStmt);
  m_resultset->m_Connection->releaseStatement (m_insertStmt);
  m_resultset->m_Connection->releaseStatement (m_deleteStmt);
  DBUG_METHOD_ENTER(IFR_UpdatableRowSet, ~IFR_UpdatableRowSet);
}

IFR_Retcode IFR_UpdatableRowSet::insertAllRows()
{
    DBUG_METHOD_ENTER(IFR_UpdatableRowSet, insertAllRows);
    DBUG_RETURN(insertRows(false));
}


IFR_Retcode IFR_UpdatableRowSet::insertOneRow()
{
    DBUG_METHOD_ENTER(IFR_UpdatableRowSet, insertOneRow);
    DBUG_RETURN(insertRows(true));
}

IFR_Retcode IFR_UpdatableRowSet::insertRows(IFR_Bool single)
{
  DBUG_METHOD_ENTER(IFR_UpdatableRowSet, insertRows);
  DBUG_PRINT(single);
  
  IFR_Retcode retcode = IFR_OK;
  IFR_Bool    prepareStmt = IFR_FALSE;

  if (!isResultSetUpdatable()) {
    retcode = IFR_NOT_OK;
    goto exit;
  }

  if (m_insertStmt == NULL) {
      m_insertStmt = m_resultset->m_Connection->createPreparedStatement ();
      prepareStmt = IFR_TRUE;
  }

  if (m_insertStmt == NULL) {
      m_resultset->error().setMemoryAllocationFailed();
      retcode = IFR_NOT_OK;
  }

  // set rowset size 
  if (retcode == IFR_OK)
    m_insertStmt->setRowArraySize (m_resultset->getRowSetSize());

  // build and prepare insert statement if necessary
  if (m_insertStmt != NULL  &&  prepareStmt == IFR_TRUE) {
      IFR_String insertCmd(m_resultset->allocator);
      IFR_Bool memory_ok=true;
    if (retcode == IFR_OK)
        retcode = buildCommand(insertCmd, "INSERT ", memory_ok);
    
    if(retcode == IFR_OK) {
        // prepare insert statement
        retcode = m_insertStmt->prepare (insertCmd.getBuffer(), insertCmd.getLength(),
                                         insertCmd.getEncoding());
    } else {
        retcode = IFR_NOT_OK;
    }
  }

  // bind rowset columns as parameter
  if (retcode == IFR_OK) 
      retcode = bindColumnsAsParameters (m_insertStmt);
  
  // execute
  if (retcode == IFR_OK) {
      if(single) {
          retcode = m_insertStmt->execute();
      } else {
          retcode = m_insertStmt->executeBatch();
      }
  }
  
  
  // @todo copy warning from m_insertStmt to this
  if (m_insertStmt->error().getErrorCode() != 0)
      m_resultset->error() = m_insertStmt->error();

 exit:;
  DBUG_RETURN (retcode);
}

/**
 * updates the row on a given position in the row array
 */
IFR_Retcode IFR_UpdatableRowSet::updateRow (int position)
{
  IFR_Retcode retcode     = IFR_OK;
  IFR_Int4    lPos        = position + m_resultset->m_rowsetstartrow;
  IFR_Bool    prepareStmt = IFR_FALSE;
  IFR_Bool    memory_ok=true;
  IFR_String  updateCmd(m_resultset->allocator);

  DBUG_METHOD_ENTER(IFR_UpdatableRowSet, updateRow);

  if (!isResultSetUpdatable()) {
    retcode = IFR_NOT_OK;
    goto exit;
  }

  if (m_updateStmt == NULL) {
      m_updateStmt = m_resultset->m_Connection->createPreparedStatement ();
      prepareStmt = IFR_TRUE;
  }

  if (m_updateStmt == NULL) {
      m_resultset->error().setMemoryAllocationFailed();
      DBUG_RETURN(IFR_NOT_OK);
  }
  
  // build and prepare update statement if necessary
  if (prepareStmt == IFR_TRUE) {
      retcode = buildCommand (updateCmd, "UPDATE ", memory_ok);
      if(retcode != IFR_OK) {
          DBUG_RETURN(IFR_NOT_OK);
      }
      updateCmd.append (" WHERE POS OF \"", IFR_StringEncodingAscii, IFR_NTS, memory_ok);
      updateCmd.append (m_resultset->m_Statement->getCursorName(), memory_ok);
      updateCmd.append ("\" IS ?", IFR_StringEncodingAscii, IFR_NTS, memory_ok);
      if(!memory_ok) {
          m_resultset->error().setMemoryAllocationFailed();
          DBUG_RETURN(IFR_NOT_OK);
      }
      // prepare update statement
      retcode = m_updateStmt->prepare (updateCmd.getBuffer(), updateCmd.getLength(),
                                       updateCmd.getEncoding());
  }
  // bind rowset columns as parameter
  if (retcode == IFR_OK)
      retcode = bindColumnsAsParameters (m_updateStmt);
  
  // bind position
  if (retcode == IFR_OK)
      retcode = m_updateStmt->bindParameter (m_updateStmt->getParameterMetaData()->getParameterCount(),
                                             IFR_HOSTTYPE_INT4, (char*)&lPos, NULL, 0);

  // execute
  if (retcode == IFR_OK)
      retcode = m_updateStmt->execute ();
  
  if (m_updateStmt->error().getErrorCode() != 0)
      m_resultset->error() = m_updateStmt->error();

 exit:;  
  DBUG_RETURN (retcode);
}

/**
 * deletes the the row on a given position in the row array
 */
IFR_Retcode IFR_UpdatableRowSet::deleteRow (int position)
{
  IFR_Retcode retcode = IFR_OK;
  IFR_Int4 lPos = position + m_resultset->m_rowsetstartrow;
  IFR_Bool memory_ok = true;
  DBUG_METHOD_ENTER(IFR_UpdatableRowSet, deleteRow);

  if (!isResultSetUpdatable()) {
    retcode = IFR_NOT_OK;
    goto exit;
  }

  // create delete statement if necessary
  if (m_deleteStmt == NULL) {
      const IFR_Statement *statement = m_resultset->getStatement();
      IFR_String lStr("DELETE FROM ", IFR_StringEncodingType::Ascii, m_resultset->allocator, memory_ok);
      
      lStr.append (statement->getTableName(), memory_ok);
      lStr.append (" WHERE POS OF \"", IFR_StringEncodingAscii, IFR_NTS, memory_ok);
      lStr.append (statement->getCursorName(), memory_ok);
      lStr.append ("\" IS ?", IFR_StringEncodingAscii, IFR_NTS, memory_ok);
      
      if(!memory_ok) {
          m_resultset->error().setMemoryAllocationFailed();
          retcode = IFR_NOT_OK;
      } else {
          m_deleteStmt = m_resultset->m_Connection->createPreparedStatement ();
      }
      
      if (m_deleteStmt == NULL) {
          m_resultset->error().setMemoryAllocationFailed();
          retcode = IFR_NOT_OK;
      } else {
          retcode = m_deleteStmt->prepare(lStr.getBuffer(),
                                          lStr.getLength(), 
                                          lStr.getEncoding());
          if (retcode != IFR_OK)
              DBUG_PRINT ("prepare failed");
      }
  }

  // bind position
  if (retcode == IFR_OK)
    retcode = m_deleteStmt->bindParameter (1, IFR_HOSTTYPE_INT4, (char*)&lPos, NULL, 0);

  // execute
  if (retcode == IFR_OK)
    retcode = m_deleteStmt->execute ();

  if (m_deleteStmt->error().getErrorCode() != 0)
    m_resultset->error() = m_deleteStmt->error();

 exit:;
  DBUG_RETURN (retcode);
}

IFR_Bool IFR_UpdatableRowSet::isResultSetUpdatable()
{
  
  if (m_resultset->m_concurType != IFR_Statement::UPDATABLE) {
    m_resultset->error().setRuntimeError(IFR_ERR_NO_UPDATABLE_ROWSET);
    return IFR_FALSE;
  }
  
  return IFR_TRUE;
}

IFR_Retcode IFR_UpdatableRowSet::buildCommand (IFR_String& command,
                                               const char* cmdprefix,
                                               IFR_Bool& memory_ok)
{
    DBUG_METHOD_ENTER(IFR_UpdatableRowSet, buildCommand);
    if(!memory_ok) {
        
        DBUG_RETURN(IFR_NOT_OK);
    }
    IFR_ResultSetMetaData *resultMetaData = m_resultset->getResultSetMetaData();
    const IFR_Statement *statement = m_resultset->getStatement();
    if(resultMetaData==0 || statement==0) {
        m_resultset->error().setRuntimeError(IFR_ERR_ROWSET_NO_METADATA);
        DBUG_RETURN(IFR_NOT_OK);
    }
    IFR_StringEncoding encoding=statement->getTableName().getEncoding();
    command.clear(encoding);
    command.append(cmdprefix, IFR_StringEncodingAscii, IFR_NTS, memory_ok);
    command.append(statement->getTableName(), memory_ok);
    command.append(" SET ", IFR_StringEncodingAscii, IFR_NTS, memory_ok);
    if(!memory_ok) {
        m_resultset->error().setMemoryAllocationFailed();
        DBUG_RETURN(IFR_NOT_OK);
    }
    IFR_Int2 columnCount=resultMetaData->getColumnCount();
    IFR_Length columnNameLength=0;
    IFR_Length columnNameSize=256;
    char buf[256];
    char *columnName=(char *)buf;
    for(IFR_Int2 i=1; i<=columnCount; ++i) {
        if(i != 1) {
            command.append(",\"", IFR_StringEncodingAscii, IFR_NTS, memory_ok);
        } else {
            command.append("\"", IFR_StringEncodingAscii, IFR_NTS, memory_ok);
        } 
        if(!memory_ok) {
            if(columnName != (char *)buf) {
                IFR_ALLOCATOR(m_resultset->allocator).Deallocate(columnName);
            }
            m_resultset->error().setMemoryAllocationFailed();
            DBUG_RETURN(IFR_NOT_OK);
        }
        IFR_Retcode rc;
        while((rc = resultMetaData->getColumnName(i, columnName, encoding, 
                                                  columnNameSize-2, &columnNameLength)) 
              == IFR_DATA_TRUNC) {
            if(columnName != (char *)buf) {
                IFR_ALLOCATOR(m_resultset->allocator).Deallocate(columnName);
            }
            columnName=(char*) IFR_ALLOCATOR(m_resultset->allocator).Allocate(columnNameLength + 2);
            if(columnName == 0) {
                m_resultset->error().setMemoryAllocationFailed();
                memory_ok = false;
                DBUG_RETURN(IFR_NOT_OK);
            }
        }
        if(rc != IFR_OK) {
            if(columnName != (char *)buf) {
                IFR_ALLOCATOR(m_resultset->allocator).Deallocate(columnName);
            }
            m_resultset->error().setRuntimeError(IFR_ERR_ROWSET_WRONG_COLUMN_ENCODING_I, (IFR_Int4)i);
            DBUG_RETURN(IFR_NOT_OK);
        }
        command.append(columnName, encoding, IFR_NTS, memory_ok);
        command.append("\"=? ", IFR_StringEncodingAscii, IFR_NTS, memory_ok);
        if(!memory_ok) {
            if(columnName != (char *)buf) {
                IFR_ALLOCATOR(m_resultset->allocator).Deallocate(columnName);
            }
            m_resultset->error().setMemoryAllocationFailed();
            DBUG_RETURN(IFR_NOT_OK);
        }
    }
    DBUG_RETURN (IFR_OK);
}

IFR_Retcode IFR_UpdatableRowSet::bindColumnsAsParameters (IFR_PreparedStmt *pStmt)
{
  IFR_Retcode retcode = IFR_OK;

  const IFR_ParameterMetaData *parameterMetaData = pStmt->getParameterMetaData();
  const IFRUtil_Vector<IFR_Parameter> *paramVector = m_resultset->getParamVector ();

  DBUG_METHOD_ENTER(IFR_UpdatableRowSet, bindColumnsAsParameters);

  if (paramVector == NULL  ||  parameterMetaData == NULL) {
    DBUG_PRINT ("could not get parameter vector or parameter meta data");
    retcode = IFR_NOT_OK;
  } else {

    const IFR_Int2 paramCount  = parameterMetaData->getParameterCount();

    if (paramCount < 0  ||  paramVector->GetSize() > (IFR_size_t) paramCount)
      retcode = IFR_NOT_OK;
    else {
      if (!paramVector->IsEmpty()) {
        int i = 1;
        for (IFR_Parameter *p=(IFR_Parameter *)paramVector->Begin();
             p != paramVector->End();
             p++, i++)
          //      for (int i=1; i<=m_ParamVector->GetSize(); i++) {
          pStmt->bindParameter (i, p->getHostType(), (char*)p->data(), p->getLengthIndicator(),
                                p->getBytesLength());
      }
    }
  }

  DBUG_RETURN (retcode);
}
