/* 
   This file is part of libodbc++.
   
   Copyright (C) 1999 Manush Dodunekov <manush@litecom.net>
   
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
   
   This library 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
   Library General Public License for more details.
   
   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/

#include <odbc++/resultset.h>
#include <odbc++/statement.h>
#include <odbc++/resultsetmetadata.h>

#include "datahandler.h"
#include "datastream.h"
#include "driverinfo.h"
#include "dtconv.h"

using namespace odbc;
using namespace std;

// values for location_
#define BEFORE_FIRST 	(-3)
#define AFTER_LAST	(-2)
#define INSERT_ROW	(-1)
#define UNKNOWN		(0)

#define LOCATION_IS_VALID	(location_>=0)


/*
  Our current location in a result set is stored in location_. If
  location_>0, we know our position. If it's UNKNOWN we don't 
  but it's still somewhere in the resultset. If location_<0
  we're outside or on the insert row.

  We decide on which of the SQL*Fetch* functions to use in the
  following way:
  
  if (DM_IS_ODBC3) {
    if(forwardOnly) {
      SQLFetch();
    } else {
      SQLFetchScroll();
    }
  } else {
    if(forwardOnly && rowsetSize==1) {
      SQLFetch();
    } else {
      SQLExtendedFetch();
    }
  }


  For scrollable ResultSets, we add an extra row to rowset_. A call to 
  moveToInsertRow() would put the rowset position on the last row.

  When using an ODBC2 driver, a call to insertRow simply does 
  _applyPosition(SQL_ADD) (while on the insert row).

  With an ODBC3 driver, we have to re-bind all columns to point at the 
  last row with rowset size set to 1 (stupid ODBC bind offsets only work with
  row-wise binding, and we use column-wise), and then call SQLBulkOperations.
  That binding is kept in effect until moveToCurrentRow() is called, for
  the case of the application wanting to insert more rows.
  When calling moveToCurrentRow() in this case, we have two possibilities:

  1. bindPos_ = 0 -> app didn't call insertRow(), we can just set location_
  to locBeforeInsert_ and the rowset pos to rowBeforeInsert_.

  2. bindPos_ >0 -> app inserted one or more rows using SQLBulkOperations(). 
  Per ODBC3 specs, the actual rowset position is undefined, and we do the
  following:
     1. Restore the rowset size (SQL_ROW_ARRAY_SIZE)
     2. If locBeforeInsert_ was valid, use an absolute fetch to get there and
     restore the rowset size. Otherwise, try to restore it by going after-last
     or before-first. With a driver that can't report the current row position
     (eg returns 0 for SQLGetStmtAttr(SQL_ATTR_ROW_NUMBER)), this breaks and
     leaves a ResultSet before it's first row after an insert. However, I haven't
     seen an ODBC3 driver that does this yet.
*/




namespace odbc {
  // a simple utility to save a value and restore it
  // at the end of the scope
  template <class T> class ValueSaver {
  private:
    T& orig_;
    T val_;
  public:
    ValueSaver(T& v)
      :orig_(v), val_(v) {}
    ~ValueSaver() {
      orig_=val_;
    }
  };
}; 



#define CHECK_INSERT_ROW					\
do {								\
  if(location_==INSERT_ROW) {					\
    throw SQLException						\
      ("[libodbc++]: Illegal operation while on insert row");	\
  }								\
} while(false)


#define CHECK_SCROLLABLE_CURSOR						\
do {									\
  if(this->getType()==TYPE_FORWARD_ONLY) {				\
    throw SQLException							\
      ("[libodbc++]: Operation not possible on a forward-only cursor");	\
  } 									\
} while(false)


ResultSet::ResultSet(Statement* stmt, SQLHSTMT hstmt, bool ownStmt)
  :statement_(stmt), 
   hstmt_(hstmt),
   ownStatement_(ownStmt),
   currentFetchSize_(stmt->getFetchSize()),
   newFetchSize_(currentFetchSize_),
   rowset_(NULL),
   rowStatus_(NULL),
   rowsInRowset_(0),
   colsBound_(false),
   streamedColsBound_(false),
   bindPos_(0),
   location_(BEFORE_FIRST)
{
  metaData_=new ResultSetMetaData(this);

  //ODBC says GetData cannot be called on a forward-only cursor
  //with rowset size > 1
  //so, if ResultSetMetaData saw a streamed column, 
  //we 'adjust' the rowset size to 1
  if(metaData_->needsGetData_==true && 
     this->getType()==TYPE_FORWARD_ONLY) {
    currentFetchSize_=1;
  }

  newFetchSize_=currentFetchSize_;


#if ODBCVER >= 0x0300
  
  // with ODBC3, we call SQLFetchScroll, 
  // and need to set the following
  statement_->_setPointerOption
    (SQL_ATTR_ROWS_FETCHED_PTR,(SQLPOINTER)&rowsInRowset_);
  
#endif

  //this will do all the work - set the rowset size, bind and so on
  this->_applyFetchSize();
}

ResultSet::~ResultSet()
{
  if(colsBound_) {
    this->_unbindCols();
  }
  
  // in case some exception caused us 
  // to destruct, these can be bound
  if(streamedColsBound_) {
    this->_unbindStreamedCols();
  }

#if ODBCVER >= 0x0300

  // we don't want anything to point to us
  statement_->_setPointerOption
    (SQL_ATTR_ROWS_FETCHED_PTR,(SQLPOINTER)NULL);

  statement_->_setPointerOption
    (SQL_ATTR_ROW_STATUS_PTR,(SQLPOINTER)NULL);

#endif

  delete rowset_;
  delete[] rowStatus_;
  delete metaData_;
  statement_->_unregisterResultSet(this);

  //if we own the statement, nuke it
  if(ownStatement_) {
    delete statement_;
  }
}

//private
void ResultSet::_applyFetchSize()
{
  statement_->_setNumericOption
    (ODBC3_C(SQL_ATTR_ROW_ARRAY_SIZE,SQL_ROWSET_SIZE),
     currentFetchSize_);

  // it is possible that the driver changes the 
  // rowset size, so we do an extra check
  int driverFetchSize=statement_->_getNumericOption
    (ODBC3_C(SQL_ATTR_ROW_ARRAY_SIZE,SQL_ROWSET_SIZE));

  if(driverFetchSize!=currentFetchSize_) {
    // save these values
    currentFetchSize_=driverFetchSize;
    newFetchSize_=driverFetchSize;
  }


  if(colsBound_) {
    this->_unbindCols();
  }

  this->_resetRowset();


  if(!colsBound_) {
    this->_bindCols();
  }
}

//private
void ResultSet::_resetRowset()
{
  delete rowset_;
  delete[] rowStatus_;
  
  //Forward-only resultsets can't have insert rows
  int extraRows=(this->getType()==TYPE_FORWARD_ONLY?0:1);
  rowset_=new Rowset(currentFetchSize_+extraRows,//1 for the insert row
		     ODBC3_DC(true,false)); // possibly use ODBC3 c-types
  
  rowStatus_=new SQLUSMALLINT[currentFetchSize_+extraRows]; //same here

#if ODBCVER >= 0x0300
  // since SQLFetchScroll doesn't take this argument, we 
  // need to set it here.
  statement_->_setPointerOption
    (SQL_ATTR_ROW_STATUS_PTR,(SQLPOINTER)rowStatus_);

#endif

  //add the datahandlers
  int nc=metaData_->getColumnCount();
  for(int i=1; i<=nc; i++) {
    int realprec;
#if ODBCVER < 0x0300

    realprec=metaData_->getPrecision(i);

#else

    if(this->_getDriverInfo()->getMajorVersion()>=3) {
    
      switch(metaData_->getColumnType(i)) {
      case Types::CHAR:
      case Types::VARCHAR:
      case Types::BINARY:
      case Types::VARBINARY:
	realprec=metaData_->colLengths_[i-1];
	break;
      default:
	realprec=metaData_->getPrecision(i);
	break;
      }

    } else {
      realprec=metaData_->getPrecision(i);
    }
    
#endif

    rowset_->addColumn(metaData_->getColumnType(i),
		       realprec,
		       metaData_->getScale(i));
  }
}

//private
void ResultSet::_prepareForFetch()
{
  if(newFetchSize_!=currentFetchSize_) {
    currentFetchSize_=newFetchSize_;
    this->_applyFetchSize();
  }
}

SQLRETURN ResultSet::_applyPosition(int mode)
{
  if(this->getType() != TYPE_FORWARD_ONLY) {
    SQLRETURN r=SQLSetPos(hstmt_,
			  (SQLUSMALLINT)rowset_->getCurrentRow()+1,
			  (SQLUSMALLINT)mode,
			  SQL_LOCK_NO_CHANGE);
    
    this->_checkStmtError(hstmt_,r,"SQLSetPos failed");
    return r;
  }
  return SQL_SUCCESS;
}


void ResultSet::_bindCols()
{
  int nc=metaData_->getColumnCount();

  SQLRETURN r;

  bindPos_=rowset_->getCurrentRow();
  colsBound_=true;
  
  for(int i=1; i<=nc; i++) {
    DataHandler* dh=rowset_->getColumn(i);
    if(!dh->isStreamed_) {

      r=SQLBindCol(hstmt_,
		   (SQLUSMALLINT)i,
		   dh->cType_,
		   (SQLPOINTER)dh->data(),
		   dh->bufferSize_,
		   &dh->dataStatus_[dh->currentRow_]);
      
      this->_checkStmtError(hstmt_,r,"Error binding column");
    }
  }
}


//this we do before an update or insert
void ResultSet::_bindStreamedCols()
{
  int nc=metaData_->getColumnCount();
  unsigned int cr=rowset_->getCurrentRow();

  for(int i=1; i<=nc; i++) {
    DataHandler* dh=rowset_->getColumn(i);
    if(dh->isStreamed_) {
      streamedColsBound_=true;
      SQLRETURN r=SQLBindCol(hstmt_,
			     (SQLUSMALLINT)i,
			     dh->cType_,
			     (SQLPOINTER)i, //our column number (for SQLParamData)
			     0,
			     &dh->dataStatus_[dh->currentRow_]);
      //dh->dataStatus_ should be SQL_LEN_DATA_AT_EXEC(len) by this point
      this->_checkStmtError(hstmt_,r,"Error binding column");
    }
  }
}

void ResultSet::_unbindStreamedCols()
{
  int nc=metaData_->getColumnCount();
  streamedColsBound_=false;

  for(int i=1; i<=nc; i++) {
    DataHandler* dh=rowset_->getColumn(i);
    if(dh->isStreamed_) {
      SQLRETURN r=SQLBindCol(hstmt_,
			   (SQLUSMALLINT)i,
			   dh->cType_,
			   0, //setting this to NULL means unbind this column
			   0,
			   dh->dataStatus_);
      this->_checkStmtError(hstmt_,r,"Error unbinding column");
    }
  }
}



//private
void ResultSet::_unbindCols()
{
  //we don't perform an error check here, since
  //this is called from the destructor; 
  //egcs dislikes exceptions thrown from destructors.
  SQLFreeStmt(hstmt_,SQL_UNBIND);
  colsBound_=false;
}


//private
//all actual fetching is done here

void ResultSet::_doFetch(int fetchType, int rowNum)
{
  SQLRETURN r;
  
  bool isfo=this->getType()==TYPE_FORWARD_ONLY;
  
  // if isfo is true fetchType can only be SQL_FETCH_NEXT
  if(isfo
#if ODBCVER < 0x0300
     && currentFetchSize_==1
#endif
     ) {
    // the only way to get here
    assert(fetchType==SQL_FETCH_NEXT);
    
    // ODBC3 says SQLFetch can do rowset sizes > 1
    r=SQLFetch(hstmt_);
#if ODBCVER < 0x0300
    // this won't get updated otherwise
    rowsInRowset_=1;
#endif
    
  } else {
    
    // we have either a scrolling cursor or a
    // rowset size > 1 in ODBC2
#if ODBCVER < 0x0300
    r=SQLExtendedFetch(hstmt_,
		       (SQLUSMALLINT)fetchType,
		       (SQLINTEGER)rowNum,
		       &rowsInRowset_,
		       rowStatus_);
    
#else
    
    r=SQLFetchScroll(hstmt_,
		     (SQLUSMALLINT)fetchType,
		     (SQLINTEGER)rowNum);
    
#endif

  }
  
  
  this->_checkStmtError(hstmt_,r,"Error fetching data from datasource");
    
  rowset_->setCurrentRow(0);

  if(r==ODBC3_C(SQL_NO_DATA,SQL_NO_DATA_FOUND)) {
    rowsInRowset_=0;
    switch(fetchType) {
    case SQL_FETCH_RELATIVE: 
      if(rowNum<0) {
	location_=BEFORE_FIRST;
      } else if(rowNum>0) {
	location_=AFTER_LAST;
      }
      break;

    case SQL_FETCH_ABSOLUTE:
      if(rowNum==0) {
	location_=BEFORE_FIRST;
      } else {
	location_=AFTER_LAST;
      }
      break;

    case SQL_FETCH_NEXT:
    case SQL_FETCH_LAST:
      location_=AFTER_LAST;
      break;
      
    case SQL_FETCH_FIRST:
    case SQL_FETCH_PRIOR:
      location_=BEFORE_FIRST;
      break;

    default:
      //notreached
      assert(false);
    }
  } else {

    // for forward only cursors, we don't bother fetching the location
    // we just increment it for each fetch

    if(isfo) {
      if(location_<=0) {
	location_=1;
      } else {
	location_+=currentFetchSize_;
      }
    } else {
      SQLUINTEGER l=statement_->_getNumericOption
	(ODBC3_C(SQL_ATTR_ROW_NUMBER,SQL_ROW_NUMBER));
      
      if(l==0) {
	location_=UNKNOWN;
      } else {
	location_=(int)l;
      }
    }
  }
}

void ResultSet::_handleStreams(SQLRETURN r)
{
  if(r==SQL_NEED_DATA) {
    char buf[PUTDATA_CHUNK_SIZE];

    while(r==SQL_NEED_DATA) {
      SQLPOINTER currentCol;
      r=SQLParamData(hstmt_,&currentCol);
      this->_checkStmtError(hstmt_,r,"SQLParamData failure");
      if(r==SQL_NEED_DATA) {
	DataHandler* dh=rowset_->getColumn((int)currentCol);
	
	if(dh->isNull()) {
	  //Nothing to send.
	  continue;
	}
	
	std::istream* s=dh->getStream();
	if(s==NULL || !(*s)) {
	  //hmm. if this occurs, I do something wrong
	  continue;
	}
	
	do {
	  s->read(buf,PUTDATA_CHUNK_SIZE);
	  if(s->gcount()>0) {
	    SQLPutData(hstmt_,(SQLCHAR*)buf,s->gcount());
	  }
	} while(*s);
      }
    }
  }
}




int ResultSet::getType()
{
  return statement_->getResultSetType();
}

int ResultSet::getConcurrency()
{
  return statement_->getResultSetConcurrency();
}

void ResultSet::setFetchSize(int fs)
{
  CHECK_INSERT_ROW;

  if(fs==0) {
    fs=SQL_ROWSET_SIZE_DEFAULT; //1
  }

  if(fs>0) {
    newFetchSize_=fs;
    if(fs!=currentFetchSize_) {
      //ok, we'll need to change it
      //if we are before/after the resultset, it's ok to proceed now
      if(!LOCATION_IS_VALID) {
	currentFetchSize_=fs;
	this->_applyFetchSize();
      }
    } 
  } else {
    throw SQLException
      ("[libodbc++]: Invalid fetch size "+intToString(fs));
  }
}


bool ResultSet::next()
{
  CHECK_INSERT_ROW;

  if(LOCATION_IS_VALID &&
     rowset_->getCurrentRow() < rowsInRowset_-1) {
    //we just move inside the rowset
    rowset_->setCurrentRow(rowset_->getCurrentRow()+1);
    this->_applyPosition();
    return true;
  } else if(location_==AFTER_LAST) {
    //no more to come
    return false;
  }

  //we now know for sure we have to do a fetch
  this->_prepareForFetch();
  this->_doFetch(SQL_FETCH_NEXT,0);

  if(LOCATION_IS_VALID) {
    this->_applyPosition();
    return true;
  }

  return false;
}

bool ResultSet::previous()
{
  CHECK_INSERT_ROW;

  CHECK_SCROLLABLE_CURSOR;

  
  if(LOCATION_IS_VALID &&
     rowset_->getCurrentRow()>0) {
    
    //alright, just move inside the rowset
    rowset_->setCurrentRow(rowset_->getCurrentRow()-1);
    this->_applyPosition();
    return true;

  } else if(location_==BEFORE_FIRST) {
    
    //no rows there..
    return false;

  } else {

    //if, we have, say a rowset that covers rows 10-30, and
    //we request the previous, we get rows 1-20. Then
    //we'll have to adjust to position to the
    //right row

    int oldloc=location_;

    this->_prepareForFetch();
    this->_doFetch(SQL_FETCH_PRIOR,0);

    if(LOCATION_IS_VALID) {
      if(oldloc>0 &&
	 oldloc-location_ < currentFetchSize_) {
	rowset_->setCurrentRow(oldloc-2); //1 for 1-based location, 1 for previous
      } else {
	//just the last row in the rowset
	rowset_->setCurrentRow(rowsInRowset_-1);
      }
      this->_applyPosition();
      return true;
    } else {
      return false;
    }
  }
}


bool ResultSet::first()
{
  CHECK_INSERT_ROW;
  CHECK_SCROLLABLE_CURSOR;

  // don't trust any positions, just fetch the first rowset
  this->_prepareForFetch();
  this->_doFetch(SQL_FETCH_FIRST,0);

  if(LOCATION_IS_VALID) {
    this->_applyPosition();
    return true;
  }

  return false;
}


bool ResultSet::last()
{
  CHECK_INSERT_ROW;
  CHECK_SCROLLABLE_CURSOR;

  this->_prepareForFetch();
  this->_doFetch(SQL_FETCH_LAST,0);
  
  if(LOCATION_IS_VALID) {
    rowset_->setCurrentRow(rowsInRowset_-1);
    this->_applyPosition();
    return true;
  } else {
    return false;
  }
}

bool ResultSet::relative(int rows)
{
  CHECK_INSERT_ROW;
  CHECK_SCROLLABLE_CURSOR;

  if(!LOCATION_IS_VALID) {
    throw SQLException
      ("[libodbc++]: ResultSet::relative(): no current row");
  }

  if(rows==0) {
    return true;
  }

  if(rows>0 && rowset_->getCurrentRow() < rowsInRowset_-rows) {
    rowset_->setCurrentRow(rowset_->getCurrentRow()+rows);
  } else if(rows<0 && rowset_->getCurrentRow() >= -rows) {
    rowset_->setCurrentRow(rowset_->getCurrentRow()+rows);
  } else {
    
    //since SQL_FETCH_RELATIVE is relative to the start of the rowset
    //we'll need to adjust
    rows-=rowset_->getCurrentRow();

    this->_prepareForFetch();
    this->_doFetch(SQL_FETCH_RELATIVE,rows);
  }

  if(LOCATION_IS_VALID) {
    this->_applyPosition();
    return true;
  } else {
    return false;
  }
}


bool ResultSet::absolute(int row)
{
  CHECK_INSERT_ROW;
  CHECK_SCROLLABLE_CURSOR;

  this->_prepareForFetch();

  this->_doFetch(SQL_FETCH_ABSOLUTE,row);
  
  if(LOCATION_IS_VALID) {
    this->_applyPosition();
    return true;
  }

  return false;
}

void ResultSet::beforeFirst()
{
  CHECK_INSERT_ROW;
  CHECK_SCROLLABLE_CURSOR;

  if(location_!=BEFORE_FIRST) {
    this->absolute(0);
  }
}

void ResultSet::afterLast()
{
  CHECK_INSERT_ROW;
  CHECK_SCROLLABLE_CURSOR;

  if(location_!=AFTER_LAST) {
    this->_prepareForFetch();
    
    this->_doFetch(SQL_FETCH_LAST,0); //now on last rowset
    this->_doFetch(SQL_FETCH_NEXT,0); //now after last rowset
  }
}



void ResultSet::moveToInsertRow()
{
  CHECK_SCROLLABLE_CURSOR;

  if(location_!=INSERT_ROW) {
    rowBeforeInsert_=rowset_->getCurrentRow();
    locBeforeInsert_=location_;

    rowset_->setCurrentRow(currentFetchSize_);

    
    location_=INSERT_ROW;
  }
}

void ResultSet::moveToCurrentRow()
{
  CHECK_SCROLLABLE_CURSOR;

  if(location_==INSERT_ROW) {
    
    if(bindPos_>0) {
      assert(this->_getDriverInfo()->getMajorVersion()>2);
      // this occurs after we have inserted rows
      // with an ODBC3 driver
      // restore our fetch size and
      // rebind with the real positions
      statement_->_setNumericOption
	(ODBC3_C(SQL_ATTR_ROW_ARRAY_SIZE,SQL_ROWSET_SIZE),
	 currentFetchSize_);

      rowset_->setCurrentRow(0);
      this->_bindCols();

      // this mirrors rowset_->getCurrentRow()
      assert(bindPos_==0);

      if(locBeforeInsert_>0) {
	// we know our remembered position
	this->_doFetch(SQL_FETCH_ABSOLUTE, locBeforeInsert_);
      } else {

	switch(locBeforeInsert_) {
	case AFTER_LAST:
	  this->_doFetch(SQL_FETCH_ABSOLUTE,-1);
	  this->_doFetch(SQL_FETCH_NEXT,0);
	  break;

	case BEFORE_FIRST:
	case UNKNOWN: // inconsistent
	default:
	  this->_doFetch(SQL_FETCH_ABSOLUTE,0);
	}
      }

    } else {
      
      // we are in the right rowset already
      location_=locBeforeInsert_;

    }

    if(LOCATION_IS_VALID) {
      rowset_->setCurrentRow(rowBeforeInsert_);
      this->_applyPosition();
    } else {
      rowset_->setCurrentRow(0);
    }
  } 
}

void ResultSet::insertRow()
{
  CHECK_SCROLLABLE_CURSOR;

  if(location_==INSERT_ROW) {

    // first, decide whether we use SQLBulkOperations
    // or SQLSetPos
#if ODBCVER >= 0x0300
    if(this->_getDriverInfo()->getMajorVersion()==3) {

      if(bindPos_==0) {
	// this is the first time we call insertRow()
	// while on the insert row
	statement_->_setNumericOption
	  (ODBC3_C(SQL_ATTR_ROW_ARRAY_SIZE,SQL_ROWSET_SIZE),1);
	// this would bind only the last row of our rowset
	this->_bindCols();

	assert(bindPos_>0);
      }

      this->_bindStreamedCols();

      SQLRETURN r=SQLBulkOperations(hstmt_,SQL_ADD);
      
      try {
	this->_checkStmtError(hstmt_,r,"SQLBulkOperations failed");
      } catch(...) {
	this->_unbindStreamedCols();
	throw;
      }

      this->_handleStreams(r);
    
      this->_unbindStreamedCols();
    
      rowset_->afterUpdate();
      return;
    }
#else

    // we are using SQLSetPos(SQL_ADD) to insert a row
    SQLRETURN r;
    this->_bindStreamedCols();

    try {
      r=this->_applyPosition(SQL_ADD);
    } catch(...) {
      this->_unbindStreamedCols();
      throw;
    }

    this->_handleStreams(r);
    this->_unbindStreamedCols();
    rowset_->afterUpdate();

#endif


  } else {
    throw SQLException
      ("[libodbc++]: Not on insert row");
  }
}

void ResultSet::updateRow()
{
  CHECK_SCROLLABLE_CURSOR;

  CHECK_INSERT_ROW;

  if(!LOCATION_IS_VALID) {
    throw SQLException
      ("[libodbc++]: No current row");
  }

  this->_bindStreamedCols();
  // With ODBC3, the operation below will change our
  // rowsInRowset_, so we save it
  SQLRETURN r;
  {
#if ODBCVER >= 0x0300
    ValueSaver<SQLUINTEGER> _rows(rowsInRowset_);
#endif
    
    r=this->_applyPosition(SQL_UPDATE);
  }

  this->_handleStreams(r);

  
  this->_unbindStreamedCols();
  
  rowset_->afterUpdate();
}

void ResultSet::refreshRow()
{
  CHECK_SCROLLABLE_CURSOR;

  CHECK_INSERT_ROW;

  if(!LOCATION_IS_VALID) {
    throw SQLException
      ("[libodbc++]: No current row");
  }
  this->_applyPosition(SQL_REFRESH);
}


void ResultSet::deleteRow()
{
  CHECK_SCROLLABLE_CURSOR;

  CHECK_INSERT_ROW;
  if(!LOCATION_IS_VALID) {
    throw SQLException
      ("[libodbc++]: No current row");
  }

  {
#if ODBCVER >= 0x0300
    ValueSaver<SQLUINTEGER> _rows(rowsInRowset_);
#endif
    
    this->_applyPosition(SQL_DELETE);
  }
}


void ResultSet::cancelRowUpdates()
{
  CHECK_SCROLLABLE_CURSOR;

  if(LOCATION_IS_VALID) {
    // do a refresh
    this->refreshRow();
  } else if(location_==INSERT_ROW) {
    // this will set all values to NULL
    rowset_->afterUpdate();
  } else {
    throw SQLException
      ("[libodbc++]: No current row");
  }
}


int ResultSet::findColumn(const string& colName)
{
  for(int i=1; i<=metaData_->getColumnCount(); i++) {
    if(
#if !defined(WIN32)
       strcasecmp
#else
       _stricmp
#endif
       (colName.c_str(),metaData_->getColumnName(i).c_str())==0) {
      return i;
    }
  }
  
  throw SQLException
    ("[libodbc++]: Column "+colName+" not found in result set");

  //shut up MSVC
  return 0;
}

int ResultSet::getRow()
{
  if(location_>0 && rowsInRowset_>0) {
    return location_+rowset_->getCurrentRow();
  } else if(location_==INSERT_ROW && locBeforeInsert_>0) {
    return locBeforeInsert_+rowBeforeInsert_;
  }

  return 0;
}


string ResultSet::getCursorName()
{
  char buf[256];
  SQLSMALLINT t;
  SQLRETURN r=SQLGetCursorName(hstmt_,
			       (SQLCHAR*)buf,
			       255,
			       &t);

  this->_checkStmtError(hstmt_,r,"Error fetching cursor name");

  buf[255]=0;
  return string(buf);
}


bool ResultSet::rowDeleted()
{
  return (rowStatus_[rowset_->getCurrentRow()-bindPos_]==SQL_ROW_DELETED);
}

bool ResultSet::rowInserted()
{
  return (rowStatus_[rowset_->getCurrentRow()-bindPos_]==SQL_ROW_ADDED);
}

bool ResultSet::rowUpdated()
{
  return (rowStatus_[rowset_->getCurrentRow()-bindPos_]==SQL_ROW_UPDATED);
}


bool ResultSet::isAfterLast()
{
  return location_==AFTER_LAST;
}

bool ResultSet::isBeforeFirst()
{
  return location_==BEFORE_FIRST;
}

bool ResultSet::isFirst()
{
  CHECK_INSERT_ROW;

  if(!LOCATION_IS_VALID) {
    return false;
  }

  if(location_!=UNKNOWN) {
    return (location_==1 && rowset_->getCurrentRow()==0);
  }

  // to do more, we need scrolling
  CHECK_SCROLLABLE_CURSOR;

  // we have to do a fetch
  // as with isLast(), fall down to rowset size=1
  int oldfs=currentFetchSize_;
  newFetchSize_=1;
  int oldpos=rowset_->getCurrentRow();

  this->_prepareForFetch();
  this->_doFetch(SQL_FETCH_PREV,0);

  bool havePrev=LOCATION_IS_VALID;

  newFetchSize_=oldfs;
  this->_prepareForFetch();
  this->_doFetch(SQL_FETCH_NEXT,0);
  rowset_->setCurrentRow(oldpos);
  this->_applyPosition();

  return !havePrev;
}


bool ResultSet::isLast()
{
  CHECK_INSERT_ROW;

  if(!LOCATION_IS_VALID ||
     rowset_->getCurrentRow() < rowsInRowset_-1) {
    //we know for sure
    return false;
  }

  //nothing we can do if we aren't scrolling
  CHECK_SCROLLABLE_CURSOR;
  
  //here, we fall down to a rowset size of 1 in order to
  //be able to come back to exactly the same position

  int oldfs=currentFetchSize_;
  int oldpos=rowset_->getCurrentRow();
  newFetchSize_=1;

  this->_prepareForFetch();
  this->_doFetch(SQL_FETCH_NEXT,0);

  bool haveMore=LOCATION_IS_VALID;

  newFetchSize_=oldfs;

  this->_prepareForFetch();

  // return to our position
  this->_doFetch(SQL_FETCH_PREV,0);
  rowset_->setCurrentRow(oldpos);
  this->_applyPosition();
  
  return !haveMore;
}



#define CHECK_COL(x)					\
do {							\
  if(x<1 || x>metaData_->getColumnCount()) {		\
    throw SQLException					\
      ("Column index out of range");			\
  }							\
} while(false)


// this allows the position to be on the insert row
#define CHECK_LOCATION						\
do {								\
  if(!LOCATION_IS_VALID && location_!=INSERT_ROW) {		\
    throw SQLException						\
      ("[libodbc++]: No current row");				\
  }								\
} while(false)


#define IMPLEMENT_GET(RETTYPE,FUNCSUFFIX)			\
RETTYPE ResultSet::get##FUNCSUFFIX(int idx)			\
{								\
  CHECK_COL(idx);						\
  CHECK_LOCATION;                                               \
  DataHandler* dh=rowset_->getColumn(idx);			\
  lastWasNull_=dh->isNull();					\
  return dh->get##FUNCSUFFIX();					\
}								\
								\
RETTYPE ResultSet::get##FUNCSUFFIX(const string& colName)	\
{								\
  return this->get##FUNCSUFFIX(this->findColumn(colName));	\
}


#define IMPLEMENT_UPDATE(TYPE,FUNCSUFFIX)				\
void ResultSet::update##FUNCSUFFIX(int idx, TYPE val)			\
{									\
  CHECK_COL(idx);							\
  CHECK_LOCATION;                                                       \
  DataHandler* dh=rowset_->getColumn(idx);				\
  dh->set##FUNCSUFFIX(val);						\
}									\
									\
void ResultSet::update##FUNCSUFFIX(const string& colName, TYPE val)	\
{									\
  this->update##FUNCSUFFIX(this->findColumn(colName),val);		\
}

#define IMPLEMENT_BOTH(TYPE,FUNCSUFFIX)		\
IMPLEMENT_GET(TYPE,FUNCSUFFIX);			\
IMPLEMENT_UPDATE(TYPE,FUNCSUFFIX)


IMPLEMENT_BOTH(double,Double);
IMPLEMENT_BOTH(bool,Boolean);
IMPLEMENT_BOTH(signed char,Byte);
IMPLEMENT_BOTH(float,Float);
IMPLEMENT_BOTH(int,Int);
IMPLEMENT_BOTH(Long,Long);
IMPLEMENT_BOTH(short,Short);

//darn, this didn't fit in
IMPLEMENT_GET(Bytes*,Bytes);
IMPLEMENT_UPDATE(const Bytes&, Bytes);
IMPLEMENT_GET(string,String);
IMPLEMENT_UPDATE(const string&, String);
IMPLEMENT_GET(Date,Date);
IMPLEMENT_UPDATE(const Date&, Date);
IMPLEMENT_GET(Time,Time);
IMPLEMENT_UPDATE(const Time&, Time);
IMPLEMENT_GET(Timestamp,Timestamp);
IMPLEMENT_UPDATE(const Timestamp&, Timestamp);





//nor did this
void ResultSet::updateNull(int idx)
{
  CHECK_COL(idx);
  CHECK_LOCATION;
  rowset_->getColumn(idx)->setNull();
}

void ResultSet::updateNull(const string& colName)
{
  this->updateNull(this->findColumn(colName));
}


//or this
std::istream* ResultSet::getAsciiStream(int idx)
{
  CHECK_COL(idx);
  CHECK_LOCATION;
  
  // we can't get the stream of the insert row
  CHECK_INSERT_ROW;

  //if the stream is not created yet, we create it
  DataHandler* dh=rowset_->getColumn(idx);
  std::istream* s=dh->getStream();
  if(s==NULL) {
    s=new DataStream(this,hstmt_,idx,SQL_C_CHAR,
		     dh->dataStatus_[dh->currentRow_]);
    dh->setStream(s);
  }
  lastWasNull_=dh->isNull();
  return s;
}

std::istream* ResultSet::getAsciiStream(const string& colName)
{
  return this->getAsciiStream(this->findColumn(colName));
}


std::istream* ResultSet::getBinaryStream(int idx)
{
  CHECK_COL(idx);
  CHECK_LOCATION;
  CHECK_INSERT_ROW;

  DataHandler* dh=rowset_->getColumn(idx);
  std::istream* s=dh->getStream();
  if(s==NULL) {
    s=new DataStream(this,hstmt_,idx,SQL_C_BINARY,
		     dh->dataStatus_[dh->currentRow_]);
    dh->setStream(s);
  }
  lastWasNull_=dh->isNull();
  return s;
}

std::istream* ResultSet::getBinaryStream(const string& colName)
{
  return this->getBinaryStream(this->findColumn(colName));
}



void ResultSet::updateAsciiStream(int idx, std::istream* s, int len)
{
  CHECK_COL(idx);
  CHECK_LOCATION;
  DataHandler* dh=rowset_->getColumn(idx);
  dh->setStream(s,len);
}

void ResultSet::updateAsciiStream(const string& colName, std::istream* s, int len)
{
  this->updateAsciiStream(findColumn(colName),s,len);
}


void ResultSet::updateBinaryStream(int idx, std::istream* s, int len)
{
  CHECK_COL(idx);
  CHECK_LOCATION;
  DataHandler* dh=rowset_->getColumn(idx);
  dh->setStream(s,len);
}

void ResultSet::updateBinaryStream(const string& colName, std::istream* s, int len)
{
  this->updateBinaryStream(findColumn(colName),s,len);
}
