/*
* podbc.cxx
*
* Virteos ODBC Implementation for PWLib Library.
*
* Virteos is a Trade Mark of ISVO (Asia) Pte Ltd.
*
* Copyright (c) 2004 ISVO (Asia) Pte Ltd. All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
*
* The Original Code is derived from and used in conjunction with the
* pwlib Libaray of the OpenH323 Project (www.openh323.org/)
*
* The Initial Developer of the Original Code is ISVO (Asia) Pte Ltd.
*
*   Portions: Simple ODBC Wrapper Article www.codeproject.com
*
* Contributor(s): ______________________________________.
*
* $Revision: 28760 $
* $Author: rjongbloed $
* $Date: 2013-01-03 17:39:22 -0600 (Thu, 03 Jan 2013) $
*/

#include <ptlib.h>

#if defined(P_ODBC) && !defined(_WIN32_WCE)

#include <ptclib/podbc.h>
#include <ptclib/guid.h>


#include <odbcinst.h>
#include <sql.h> 
#include <sqlext.h>

#ifdef _MSC_VER
 #include <tchar.h>
 #pragma comment(lib,"odbc32.lib")
 #pragma comment(lib,"odbcCP32.lib")
#else

  #ifdef UNICODE
  typedef WCHAR                 TCHAR;
  typedef LPWSTR                LPTSTR;
  typedef LPCWSTR               LPCTSTR;
  // Needs a definition one day ... #define _T(x)
  #else
  typedef CHAR                  TCHAR;
  typedef LPSTR                 LPTSTR;
  typedef LPCSTR                LPCTSTR;
  #define _T(x) x
  #endif

#endif // _MSC_VER


class PODBC::Statement : public PObject
{
    PCLASSINFO(Statement, PObject);
  public:
    /**@name Constructor/Deconstructor */
    //@{
    /** Constructor PODBC (Datasources call) or thro' DSNConnection (Connection call). 
    In General this class is constructed within the PODBC::RecordSet Class.
    */
    Statement(PODBC & odbc);

    /** Deconstructor. This Class should be available for the duration of which
    a specific query/table is required and be deconstructed at the time of
    the PODBC::RecordSet deconstruction.
    */
    ~Statement();
    //@}

    /**@name Data Management */
    //@{ 
    /** IsValid Checks to ensure a Handle has been allocated and
    is effective.
    */
    __inline bool IsValid() const { return m_hStmt != SQL_NULL_HSTMT; }

    /** GetChangedRowCount retreives the number of rows updated/altered by
    UPDATE/INSERT statements.
    */
    DWORD GetChangedRowCount();

    /** Execute function is the Main function to pass SQL statements to retreive/
    add/Modify database data. It accepts generally acceptable SQL Statements.
    ie. Select * from [table-x]
    */
    bool Execute(const PString & sql);

    // Close cursor, remove all bindings.
    bool CloseCursor() { return SQL_OK(SQLCloseCursor(m_hStmt)); }
    //@}

    /**@name Data Retrieval */
    //@{  
    /** FetchRow More detailed fetching of Rows. This allows you to fetch an
    Absolute row or a row relative to the current row fetched.
    */
    bool FetchScroll(SQLSMALLINT orientation, SQLLEN offset = 0)
    { return SQL_OK(SQLFetchScroll(m_hStmt, orientation, offset)); }

    // Commit the data
    bool Commit(unsigned operation);

    /** Retreive the List of tables from the current Datasource
    The option field can be used to specify the Table Types
    ie "TABLE" for Tables or "VIEW" for preconfigured datasource
    queries. *Further investigation is required*
    */
    PStringArray TableList(const PString & options);

    /** Is the SQL Instruction OK
    If an Error is detected then GetLastError is called
    to Retrieve the SQL Error Information and Returns false
    */
    bool SQL_OK(SQLRETURN res);
    //@}

    __inline bool NumResultCols(SQLSMALLINT *columnCount) { return SQL_OK(SQLNumResultCols(m_hStmt, columnCount)); }

    __inline bool GetData(
      SQLUSMALLINT column,
      SQLSMALLINT type,
      SQLPOINTER data,
      SQLLEN size,
      SQLLEN * len
    ) { return SQL_OK(SQLGetData(m_hStmt, column, type, data, size, len)); }

    __inline bool BindCol(
      SQLUSMALLINT column,
      SQLSMALLINT type,
      SQLPOINTER data, 
      SQLLEN size,
      SQLLEN * len
      ) { return SQL_OK(SQLBindCol(m_hStmt, column, type, data, size, len)); }

    __inline bool DescribeCol(
      SQLUSMALLINT column,
      SQLCHAR *namePtr,
      SQLSMALLINT nameSize,
      SQLSMALLINT *nameLength,
      SQLSMALLINT *dataType,
      SQLULEN *columnSize,
      SQLSMALLINT *decimalDigits,
      SQLSMALLINT *nullable
    ) { return SQL_OK(SQLDescribeCol(m_hStmt, column, namePtr, nameSize, nameLength, dataType, columnSize, decimalDigits, nullable)); }

    __inline bool ColAttribute (
      SQLUSMALLINT column,
      SQLUSMALLINT field,
      SQLPOINTER attrPtr,
      SQLSMALLINT attrSize,
      SQLSMALLINT *length,
      SQLLEN * numericPtr
    ) { return SQL_OK(SQLColAttribute(m_hStmt, column, field, attrPtr, attrSize, length, numericPtr)); }


    // Member variables
    PODBC   & m_odbc;
    HSTMT     m_hStmt;
    SQLRETURN m_lastResult;

  private:
    Statement(const Statement & other) : PObject(other), m_odbc(other.m_odbc) { }
    void operator=(const Statement &) { }
};


// Must be simple struct, no virtuals!
struct PODBC::FieldExtra
{
  // Union must be first
  union {
    DATE_STRUCT      date;      /// Date Structure  
    TIME_STRUCT      time;      /// Time Structure
    TIMESTAMP_STRUCT timestamp; /// TimeStamp Structure
    char             datetime[SQL_TIMESTAMP_LEN];
  };

  SQLLEN bindLenOrInd;

  FieldExtra() { memset(this, 0, sizeof(*this)); }
};


struct PODBC::Link
{
  HENV m_hEnv; // Handle to environment
  HDBC m_hDBC; // Handle to database connection
};


static bool SQLFailed(PODBC & odbc, SQLSMALLINT type, SQLHANDLE handle, SQLRETURN result)
{
  if (result == SQL_SUCCESS)
    return false;

  if (result == SQL_NEED_DATA || result == SQL_NO_DATA)
    return true;

  bool error = result != SQL_SUCCESS_WITH_INFO;

  SQLINTEGER  nativeError;
  SQLSMALLINT msgLen, index = 1;
  char errStr[6], msg[SQL_MAX_MESSAGE_LENGTH];

  while (SQL_SUCCEEDED(SQLGetDiagRec(type,
                                     handle,
                                     index++,
                                     (unsigned char *)errStr,
                                     &nativeError,
                                     (unsigned char *)msg,
                                     sizeof(msg),
                                     &msgLen))) {
    PTRACE(error ? 2 : 4, "ODBC", errStr << ": " << msg);
    odbc.OnSQLError(nativeError, errStr, msg);
  }

  if (error && index == 2) {
    PTRACE(2, "ODBC\tSQL function failed but no error information available");
    odbc.OnSQLError(-1, PString::Empty(), "Unknown error");
  }

  return error;
}


#define new PNEW


#ifdef _MSC_VER
#pragma warning(disable:4244)
#endif


/////////////////////////////////////////////////////////////////
///PODBC

PODBC::PODBC()
  : m_link(new Link)
  , m_lastError(0)
  , m_precision(4)
  , m_timeFormat(PTime::MediumDate)
  , m_dateFormat(PTime::LongTime)
  , m_dateTimeFormat(PTime::MediumDateTime)
  , m_needChunking(false)
  , m_maxChunkSize(32768)
{
  m_link->m_hDBC = NULL;
  if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_link->m_hEnv)))
    m_lastErrorText = "Unable to allocated ODBC environment";
  else
    SQLFailed(*this, SQL_HANDLE_ENV, m_link->m_hEnv, SQLSetEnvAttr(m_link->m_hEnv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0));
}


PODBC::~PODBC()
{
  Disconnect();

  if (m_link->m_hEnv != NULL)
    SQLFreeHandle(SQL_HANDLE_ENV, m_link->m_hEnv);
}


PStringList PODBC::GetDrivers(bool withAttributes) const
{
  PStringList drivers;
  SQLCHAR description[1000], attributes[2000];
  SQLSMALLINT descLen, attrLen;

  if (m_link->m_hEnv != NULL &&
      SQL_SUCCEEDED(SQLDrivers(m_link->m_hEnv, SQL_FETCH_FIRST,
                               description, sizeof(description), &descLen,
                               attributes, sizeof(attributes), &attrLen))) {
    do {
      if (withAttributes) {
        for (PINDEX i = 0; i < attrLen-1; ++i) {
          if (attributes[i] == '\0')
            attributes[i] = '\t';
        }
        drivers.AppendString(PString((const char *)description, descLen) + '\t' +
                              PString((const char *)attributes, attrLen));
      }
      else
        drivers.AppendString(PString((const char *)description, descLen));
    } while (SQL_SUCCEEDED(SQLDrivers(m_link->m_hEnv, SQL_FETCH_NEXT,
                                      description, sizeof(description), &descLen,
                                      attributes, sizeof(attributes), &attrLen)));
  }

  return drivers;
}


PStringList PODBC::GetSources(bool system, bool withDescription) const
{
  PStringList sources;
  SQLCHAR server[1000], description[1000];
  SQLSMALLINT servLen, descLen;

  if (m_link->m_hEnv != NULL &&
      SQL_SUCCEEDED(SQLDataSources(m_link->m_hEnv, system ? SQL_FETCH_FIRST_SYSTEM : SQL_FETCH_FIRST_USER,
                                   server, sizeof(server), &servLen,
                                   description, sizeof(description), &descLen))) {
    do {
      if (withDescription)
        sources.AppendString(PString((const char *)server, servLen) + '\t' +
                             PString((const char *)description, descLen));
      else
        sources.AppendString(PString((const char *)server, servLen));
    } while (SQL_SUCCEEDED(SQLDataSources(m_link->m_hEnv, SQL_FETCH_NEXT,
                                          server, sizeof(server), &servLen,
                                          description, sizeof(description), &descLen)));
  }

  return sources;
}


bool PODBC::Connect(const PString & source)
{
  if (m_link->m_hEnv == NULL)
    return false;

  Disconnect();

  if (SQLFailed(*this, SQL_HANDLE_ENV, m_link->m_hEnv, SQLAllocHandle(SQL_HANDLE_DBC, m_link->m_hEnv, &m_link->m_hDBC)))
    return false;

  short shortResult = 0;
  SQLCHAR szOutConnectString[1024];
  static SQLINTEGER LoginTimeout = 5;

  if (SQLFailed(*this, SQL_HANDLE_DBC, m_link->m_hDBC,
                SQLSetConnectAttr(m_link->m_hDBC, SQL_LOGIN_TIMEOUT, &LoginTimeout, 0)) ||
      SQLFailed(*this, SQL_HANDLE_DBC, m_link->m_hDBC,
                 SQLDriverConnect(m_link->m_hDBC,
                                  NULL,
                                  (SQLCHAR*)source.GetPointer(),
                                  source.GetLength(),
                                  szOutConnectString,
                                  sizeof(szOutConnectString),
                                  &shortResult,
                                  SQL_DRIVER_NOPROMPT
                                  ))) {
    PTRACE(2, "ODBC\tCould not connect to \"" << source << '"');
    // Don't call Disconnect() or m_lastError is changed.
    SQLFreeHandle(SQL_HANDLE_DBC, m_link->m_hDBC);
    m_link->m_hDBC = NULL;
    return false;
  }

  PTRACE(3, "ODBC\tConnected to " << szOutConnectString);
  OnConnected();
  return true;
}


const char * PODBC::GetDriverName(DriverType type)
{
  static const char * const Names[] = {
    "DSN",
    "MySQL ODBC 3.51 Driver",
    "PostgreSQL",
    "Microsoft ODBC for Oracle",
    "Microsoft ODBC for DB2",
    "SQL Server",
    "Microsoft Access Driver (*.mdb)",
    "Microsoft Paradox Driver (*.db )",
    "Microsoft Visual Foxpro Driver",
    "Microsoft dBASE Driver (*.dbf)",
    "Microsoft Excel Driver (*.xls)",
    "Microsoft Text Driver (*.txt; *.csv)"
  };
  return type < PARRAYSIZE(Names) ? Names[type] : "";
}


static PString AddDriver(PODBC::DriverType type)
{
  PStringStream strm;
  strm << "DRIVER={" << PODBC::GetDriverName(type) << "};";
  return strm;
}


static PString AddString(const char * title, const PString & value, const PString & dflt = PString::Empty())
{
  PStringStream strm;
  strm << title << '=';

  if (value.IsEmpty()) {
    if (dflt.IsEmpty())
      return PString::Empty();
    strm << dflt;
  }
  else
    strm << value;

  strm << ';';
  return strm;
}


static PString AddNumber(const char * title, unsigned value, unsigned dflt = 0)
{
  PStringStream strm;
  strm << title << '=';

  if (value == 0) {
    if (dflt == 0)
      return PString::Empty();
    strm << dflt;
  }
  else
    strm << value;

  strm << ';';
  return strm;
}


static PString AddBool(const char * title, bool value)
{
  PStringStream strm;
  strm << title << '=' << (value ? "Yes" : "No") << ';';
  return strm;
}


bool PODBC::Connect(const PString & source, const PString & username, const PString & password)
{
  return Connect(AddString("DSN", source) +
                 AddString("Uid", username) +
                 AddString("Pwd", password));
}


bool PODBC::IsConnected() const
{
  return m_link->m_hDBC != NULL;
}


void PODBC::Disconnect()
{
  if (m_link->m_hDBC != NULL) {
    SQLFailed(*this, SQL_HANDLE_DBC, m_link->m_hDBC, SQLDisconnect(m_link->m_hDBC));
    SQLFailed(*this, SQL_HANDLE_DBC, m_link->m_hDBC, SQLFreeHandle(SQL_HANDLE_DBC, m_link->m_hDBC));
    m_link->m_hDBC = NULL;
  }
}


bool PODBC::Connect_MSSQL(const PString & user,
                          const PString & pass,
                          const PString & host,
                          bool trusted,
                          MSSQLProtocols Proto)
{
  PString network;

  switch(Proto) {
  case MSSQLNamedPipes:
    network ="dbnmpntw";
    break;
  case MSSQLWinSock:
    network ="dbmssocn";
    break;
  case MSSQLIPX:
    network = "dbmsspxn";
    break;
  case MSSQLBanyan:
    network = "dbmsvinn";
    break;
  case MSSQLRPC:
    network = "dbmsrpcn";
    break;
  default:
    network = "dbmssocn";
    break;
  }

  return PODBC::Connect(AddDriver(MSSQL) +
                        AddString("Server", host) +
                        AddString("Uid", user) +
                        AddString("Pwd", pass) +
                        AddString("Network", network) +
                        AddBool("Trusted_Connection", trusted));
}


bool PODBC::Connect_DB2(const PFilePath & dbPath)
{
  return PODBC::Connect(AddDriver(IBM_DB2) +
                        AddNumber("DriverID", 277) +
                        AddString("Dbq", dbPath));
}


bool PODBC::Connect_XLS(const PFilePath & xlsPath, const PString & defDir)
{
  return PODBC::Connect(AddDriver(Excel) +
                        AddNumber("DriverId", 790) +
                        AddString("bq", xlsPath) +
                        AddString("DefaultDir", defDir));
}


bool PODBC::Connect_TXT(const PFilePath & txtPath)
{
  return PODBC::Connect(AddDriver(Ascii) +
                        AddString("Dbq", txtPath) +
                        AddString("Extensions", "asc,csv,tab,txt"));
}


bool PODBC::Connect_FOX(const PFilePath & dbPath,
                        const PString & user,
                        const PString & pass,
                        const PString & type,
                        bool exclusive)
{
  return PODBC::Connect(AddDriver(Foxpro) +
                        AddString("Uid", user) +
                        AddString("Pwd", pass) +
                        AddString("SourceDB", dbPath) +
                        AddString("SourceType", type) +
                        AddBool("Exclusive", exclusive));
}


bool PODBC::Connect_MDB(const PFilePath & mdbPath,
                        const PString & user,
                        const PString & pass,
                        bool exclusive)
{
  return PODBC::Connect(AddDriver(MSAccess) +
                        AddString("Dbq", mdbPath) +
                        AddString("Uid", user) +
                        AddString("Pwd", pass) +
                        AddBool("Exclusive", exclusive));
}


bool PODBC::Connect_PDOX(const PDirectory & dbPath, const PDirectory & defaultDir, int version)
{
  return PODBC::Connect(AddDriver(Paradox) +
                        AddNumber("DriverID", 538) +
                        "Fil=Paradox " + PString(version) + ".X;" +
                        AddString("DefaultDir", defaultDir) +
                        AddString("Dbq", dbPath) +
                        "CollatingSequence=ASCII;");
}


bool PODBC::Connect_DBASE(const PDirectory & dbPath)
{
  return PODBC::Connect(AddDriver(dBase) +
                        AddNumber("DriverID", 277) +
                        AddString("Dbq", dbPath));
}


bool PODBC::Connect_Oracle(const PString & server, const PString & user, const PString & pass)
{
  return PODBC::Connect(AddDriver(Oracle) +
                        AddString("Server", server) +
                        AddString("Uid", user) +
                        AddString("Pwd", pass));
}


static PConstString const LocalHost("localhost");

bool PODBC::Connect_mySQL(const PString & user,
                          const PString & pass,
                          const PString & host,
                          int port)
{
  return PODBC::Connect(AddDriver(mySQL) +
                        AddString("Server", host, LocalHost) +
                        AddNumber("Port", port, 3306) +
                        AddString("Uid", user) +
                        AddString("Pwd", pass));
}


bool PODBC::ConnectDB_mySQL(const PString & db,
                            const PString & user,
                            const PString & pass,
                            const PString & host,
                            int port)
{
  if (db.IsEmpty())
    return Connect_mySQL(user, pass, host, port);

  return PODBC::Connect(AddDriver(mySQL) +
                        AddString("Server", host, LocalHost) +
                        AddNumber("Port", port, 3306) +
                        AddString("Database", db) +
                        AddString("Uid", user) +
                        AddString("Pwd", pass));
}


bool PODBC::Connect_postgreSQL(const PString & db,
                               const PString & user,
                               const PString & pass,
                               const PString & host,
                               int port)
{
  return PODBC::Connect(AddDriver(postgreSQL) +
                        AddString("Server", host, LocalHost) +
                        AddNumber("Port", port, 5432) +
                        AddString("Database", db) +
                        AddString("Uid", user) +
                        AddString("Pwd", pass));
}


bool PODBC::DataSource(DriverType driver, ConnectData connectInfo)
{
  connectInfo.m_driver = driver;
  return Connect(connectInfo);
}


bool PODBC::Connect(const ConnectData & connectInfo)
{
  switch (connectInfo.m_driver) {
    case DSN :
      return Connect(connectInfo.m_database,connectInfo.m_username, connectInfo.m_password);
    case mySQL:
      return ConnectDB_mySQL(connectInfo.m_database,connectInfo.m_username,connectInfo.m_password,connectInfo.m_host,connectInfo.m_port);
    case MSSQL:
      return Connect_MSSQL(connectInfo.m_username,connectInfo.m_password,connectInfo.m_host,connectInfo.m_trusted, (MSSQLProtocols)connectInfo.m_options);
    case Oracle:
      return Connect_Oracle(connectInfo.m_username,connectInfo.m_username, connectInfo.m_password);
    case IBM_DB2:
      return Connect_DB2(connectInfo.m_database);
    case dBase:
      return Connect_DBASE(connectInfo.m_database);
    case Paradox:
      return Connect_PDOX(connectInfo.m_database,connectInfo.m_directory,connectInfo.m_options);
    case Excel:
      return Connect_XLS(connectInfo.m_database,connectInfo.m_directory);
    case Ascii:
      return Connect_TXT(connectInfo.m_database);
    case Foxpro:
      return Connect_FOX(connectInfo.m_database,connectInfo.m_username,connectInfo.m_password,"DBF",connectInfo.m_exclusive);
    case MSAccess:
      return Connect_MDB(connectInfo.m_database,connectInfo.m_username,connectInfo.m_password,connectInfo.m_exclusive);
    case postgreSQL:
      return Connect_postgreSQL(connectInfo.m_database,connectInfo.m_username,connectInfo.m_password,connectInfo.m_host,connectInfo.m_port);
    case ConnectionString :
      return Connect(connectInfo.m_database);
    default :
      return false;
  }
}


PStringArray PODBC::TableList(const PString & options)
{
  Statement data(*this);
  return data.TableList(options);
}


bool PODBC::Execute(const PString & query)
{
  if (m_link->m_hDBC == NULL)
    return false;

  Statement stmt(*this);
  return stmt.Execute(query);
}


void PODBC::SetPrecision(unsigned precision)
{
  m_precision = precision;
}


void PODBC::OnConnected()
{
  char f[2];
  SQLFailed(*this, SQL_HANDLE_DBC, m_link->m_hDBC,
            SQLGetInfo(m_link->m_hDBC, SQL_NEED_LONG_DATA_LEN, f, sizeof(f), NULL));
  m_needChunking = toupper(f[0]) == 'Y';
}


void PODBC::OnSQLError(int native, const PString & code, const PString & message)
{
  m_lastError = native;
  m_lastErrorText = code;
  if (!code.IsEmpty() && !message.IsEmpty())
    m_lastErrorText += ": ";
  m_lastErrorText += message;
}


PString PODBC::GetFieldType(DriverType driver, PVarType::BasicType type, unsigned size)
{
  switch (type) {
    case PVarType::VarBoolean :
      switch (driver) {
        case mySQL :
          return "tinyint";
        case postgreSQL :
          return "boolean";
        default :
          return "bit";
      }
    case PVarType::VarChar :
      return "char(1)";
    case PVarType::VarInt8 :
    case PVarType::VarUInt8 :
      switch (driver) {
        case MSAccess :
          return "byte";
        case postgreSQL :
          return "smallint";
        default :
          return "tinyint";
      }
    case PVarType::VarInt16 :
    case PVarType::VarUInt16 :
      switch (driver) {
        case MSAccess :
          return "integer";
        default :
          return "smallint";
      }
    case PVarType::VarInt32 :
    case PVarType::VarUInt32 :
      switch (driver) {
        case MSAccess :
          return "long";
        default :
          return "int";
      }
    case PVarType::VarInt64 :
    case PVarType::VarUInt64 :
      switch (driver) {
        case MSAccess :
          return "numeric";
        default :
          return "bigint";
      }
    case PVarType::VarFloatSingle :
      switch (driver) {
        case MSAccess :
          return "single";
        default :
          return "real";
      }
    case PVarType::VarFloatDouble :
      switch (driver) {
        case MSAccess :
          return "double";
        case postgreSQL :
          return "double precision";
        default :
          return "float(53)";
      }
    case PVarType::VarFloatExtended :
      return "numeric";
    case PVarType::VarGUID :
      switch (driver) {
        case MSAccess :
          return "guid";
        default :
          return "uniqueidentifier";
      }
    case PVarType::VarTime :
      switch (driver) {
        case postgreSQL :
          return "timestamp";
        default :
          return "datetime";
      }
    case PVarType::VarStaticBinary :
    case PVarType::VarDynamicBinary :
      switch (driver) {
        case postgreSQL :
          return "bytea";
        case MSAccess :
          if (size == 0 || size > 255)
            return "longbinary";
          // Do next case
        default :
          if (size == 0)
            return "varbinary";
          return psprintf("binary(%u)", size);
      }
    case PVarType::VarFixedString :
      if (size != 0)
        return psprintf("char(%u)", size);
      // Do next case
    default :
      switch (driver) {
        case MSAccess :
          if (size == 0 || size > 255)
            return "longtext";
          // do next case
        default :
          if (size == 0)
            return "text";
          return psprintf("varchar(%u)", size);
      }
  }
}


/////////////////////////////////////////////////////////////////////////////
// PODBC::Statement

PODBC::Statement::Statement(PODBC & odbc)
  : m_odbc(odbc)
  , m_lastResult(SQL_SUCCESS)
{
  if (SQLFailed(odbc, SQL_HANDLE_DBC, odbc.m_link->m_hDBC, SQLAllocHandle(SQL_HANDLE_STMT, odbc.m_link->m_hDBC, &m_hStmt))) {
    m_hStmt = SQL_NULL_HSTMT;
    return;
  }

  SQLSetStmtAttr(m_hStmt, SQL_ATTR_CONCURRENCY,    (SQLPOINTER) SQL_CONCUR_ROWVER, 0);
  SQLSetStmtAttr(m_hStmt, SQL_ATTR_CURSOR_TYPE,    (SQLPOINTER)SQL_CURSOR_KEYSET_DRIVEN, 0);
  SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_BIND_TYPE,  (SQLPOINTER)SQL_BIND_BY_COLUMN, 0);
  SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)1, 0);
  SQLSetStmtAttr(m_hStmt, SQL_ATTR_ROW_STATUS_PTR, NULL, 0);
}


PODBC::Statement::~Statement()
{
  if (IsValid()) {
    CloseCursor();
    SQLFreeHandle(SQL_HANDLE_STMT, m_hStmt);
  }
}


DWORD PODBC::Statement::GetChangedRowCount(void)
{
  SQLLEN nRows = 0;
  return SQL_OK(SQLRowCount(m_hStmt,&nRows)) ? nRows : 0;
}


bool PODBC::Statement::Execute(const PString & sql)
{
  SQLCloseCursor(m_hStmt);
  return SQL_OK(SQLExecDirect(m_hStmt, (SQLCHAR *)sql.GetPointer(), sql.GetLength()));
}


bool PODBC::Statement::Commit(unsigned operation)
{
  SQLRETURN nRet = operation == SQL_ADD ? SQLBulkOperations(m_hStmt, SQL_ADD)
                                        : SQLSetPos(m_hStmt, 1, operation, SQL_LOCK_NO_CHANGE);

  /// Everything OK but no Long Data
  if (SQL_OK(nRet))
    return true;

  /// Error Somewhere else.
  if (nRet != SQL_NEED_DATA)
    return false;

  PINDEX chunkSize = m_odbc.GetMaxChunkSize();
  SQLPOINTER pColumn;

  /// If More Data Required
  while ((nRet = SQLParamData(m_hStmt, &pColumn)) == SQL_NEED_DATA) {
    Field & field = *(Field*)pColumn;

    const uint8_t * ptr = (const uint8_t *)field.GetPointer();
    PINDEX len = field.GetSize();

    while (len > chunkSize) {
      SQLPutData(m_hStmt, (SQLPOINTER)ptr, chunkSize);
      len -= chunkSize;
      ptr += chunkSize;
    }
    SQLPutData(m_hStmt, (SQLPOINTER)ptr, len);
  }

  return SQL_OK(nRet);
}


PStringArray PODBC::Statement::TableList(const PString & options)
{
  PStringArray list;

  SQLUSMALLINT column = 3;
  SQLRETURN result;

  if (options == "CATALOGS") {
    result = SQLTables(m_hStmt, (SQLCHAR *)SQL_ALL_CATALOGS, SQL_NTS, NULL, 0, NULL, 0, NULL, 0);
    column = 1;
  }
  else if (options *= "SCHEMAS") {
    result = SQLTables(m_hStmt, NULL, 0, (SQLCHAR *)SQL_ALL_SCHEMAS, SQL_NTS, NULL, 0, NULL, 0);
    column = 2;
  }
  else if (options *= "TYPES") {
    result = SQLTables(m_hStmt, NULL, 0, NULL, 0, NULL, 0, (SQLCHAR *)SQL_ALL_TABLE_TYPES, SQL_NTS);
    column = 4;
  }
  else if (options.Find('%') != P_MAX_INDEX)
    result = SQLTables(m_hStmt, NULL, 0, NULL, 0, (SQLCHAR *)options.GetPointer(), SQL_NTS, NULL, 0);
  else if (options.IsEmpty())
    result = SQLTables(m_hStmt, NULL, 0, NULL, 0, NULL, 0, (SQLCHAR *)"TABLE,VIEW", SQL_NTS);
  else 
    result = SQLTables(m_hStmt, NULL, 0, NULL, 0, NULL, 0, (SQLCHAR *)options.GetPointer(), SQL_NTS);

  if (SQL_OK(result)) {
    while (SQL_OK(SQLFetch(m_hStmt))) {
      char entry[1000];
      SQLLEN cb = SQL_NULL_DATA;
      if (SQL_OK(SQLGetData(m_hStmt, column, SQL_C_CHAR, entry, sizeof(entry), &cb)) && cb > 0)
        list.Append(new PCaselessString(entry));
    }
  }

  return list;
}


bool PODBC::Statement::SQL_OK(SQLRETURN result)
{
  m_lastResult = result;
  return !SQLFailed(m_odbc, SQL_HANDLE_STMT, m_hStmt, result);
}


/////////////////////////////////////////////////////////////////////////////
// PODBC::Field

PODBC::Field::Field(Row & row, PINDEX column)
  : m_row(row)
  , m_column(column)
  , m_odbcType(UINT_MAX)
  , m_scale(UINT_MAX)
  , m_isNullable(false)
  , m_isReadOnly(false)
  , m_isAutoIncrement(false)
  , m_decimals(UINT_MAX)
  , m_extra(new FieldExtra)
{
  Statement & statement = *m_row.m_recordSet.m_statement;

  SQLULEN suColSize = 0;
  SWORD swType = 0;
  {
    SWORD swNameLen = 0, swScale = 0, swNull = 0;
    SQLCHAR nameBuf[256];
    if (!statement.DescribeCol(m_column,        // ColumnNumber
                               nameBuf,         // ColumnName
                               sizeof(nameBuf), // BufferLength
                               &swNameLen,      // NameLengthPtr
                               &swType,         // DataTypePtr
                               &suColSize,      // ColumnSizePtr
                               &swScale,        // DecimalDigitsPtr
                               &swNull))        // NullablePtr
      return;

    m_name = PString((const char *)nameBuf, swNameLen);
    m_odbcType = swType;
    m_scale = swScale;
    m_isNullable = swNull == SQL_NULLABLE;

    SQLLEN attr = SQL_ATTR_READONLY;
    statement.ColAttribute(m_column, SQL_DESC_UPDATABLE, NULL, 0, NULL, &attr);
    m_isReadOnly = attr == SQL_ATTR_READONLY;

    attr = SQL_FALSE;
    statement.ColAttribute(m_column, SQL_DESC_AUTO_UNIQUE_VALUE, NULL, 0, NULL, &attr);
    m_isAutoIncrement = attr == SQL_TRUE;

    attr = 0;
    statement.ColAttribute(m_column, SQL_DESC_PRECISION, NULL, 0, NULL, &attr);
    m_decimals = attr;
  }

  switch (swType) {
    case SQL_BIT :
      PVarType::SetType(VarBoolean);
      m_odbcType = SQL_C_BIT;
      break;

    case SQL_TINYINT :
      PVarType::SetType(VarInt8);
      m_odbcType = SQL_C_STINYINT;
      break;

    case SQL_SMALLINT :
      PVarType::SetType(VarInt16);
      m_odbcType = SQL_C_SSHORT;
      break;

    case SQL_INTEGER :
      PVarType::SetType(VarInt32);
      m_odbcType = SQL_C_LONG;
      break;

    case SQL_BIGINT :
      PVarType::SetType(VarInt64);
      m_odbcType = SQL_C_SBIGINT;
      break;

    case SQL_NUMERIC :
    case SQL_DECIMAL :
    case SQL_FLOAT :
    case SQL_REAL :
    case SQL_DOUBLE :
      PVarType::SetType(VarFloatDouble);
      m_odbcType = SQL_C_DOUBLE;
      break;

    case SQL_GUID:
      PVarType::SetType(VarGUID);
      m_odbcType = SQL_C_GUID;
      break;

    case SQL_DATETIME:
    case SQL_TYPE_DATE:
    case SQL_TYPE_TIME:
    case SQL_TYPE_TIMESTAMP:
      switch (m_odbcType) {
        case SQL_DATETIME:
        case SQL_TYPE_TIMESTAMP:
          PVarType::SetType(VarTime, statement.m_odbc.GetDateTimeFormat());
          m_odbcType = SQL_C_TYPE_TIMESTAMP;
          break;

        case SQL_TYPE_DATE:
          PVarType::SetType(VarTime, statement.m_odbc.GetDateFormat());
          m_odbcType = SQL_C_TYPE_DATE;
          break;

        case SQL_TYPE_TIME:
          PVarType::SetType(VarTime, statement.m_odbc.GetTimeFormat());
          m_odbcType = SQL_C_TYPE_TIME;
          break;
      }
      if (!m_isReadOnly)
        statement.BindCol(m_column, m_odbcType, m_extra, sizeof(*m_extra), &m_extra->bindLenOrInd);
      return;

    case SQL_CHAR :
    case SQL_VARCHAR :
      PVarType::SetType(suColSize <= 1 ? VarChar : VarFixedString, suColSize+1);
      m_odbcType = SQL_C_CHAR;
      break;

    case SQL_BINARY :
    case SQL_VARBINARY :
      PVarType::SetType(VarDynamicBinary, suColSize);
      m_odbcType = SQL_C_BINARY;
      break;

    case SQL_LONGVARCHAR :
    case SQL_LONGVARBINARY :
      m_odbcType = m_odbcType == SQL_LONGVARCHAR ? SQL_C_CHAR : SQL_C_BINARY;

      if (m_isReadOnly || statement.m_odbc.GetMaxChunkSize() == P_MAX_INDEX) {
        PVarType::SetType(m_odbcType == SQL_C_CHAR ? VarDynamicString : VarDynamicBinary, 256);
        m_extra->bindLenOrInd = 0;
      }
      else {
        PVarType::SetType(m_odbcType == SQL_C_CHAR ? VarDynamicString : VarDynamicBinary, statement.m_odbc.GetMaxChunkSize());
        m_extra->bindLenOrInd = SQL_DATA_AT_EXEC;
      }

      if (!m_isReadOnly)
        statement.BindCol(m_column, m_odbcType, (SQLPOINTER)GetPointer(), GetSize(), &m_extra->bindLenOrInd);
      return;

    default :
      PTRACE(2, "ODBC\tUnknown/unsupported column data type " << swType << " for " << m_name);
      m_odbcType = swType;
      // Do next case, assume a char field
  }

  m_extra->bindLenOrInd = GetSize();

  if (!m_isReadOnly)
    statement.BindCol(m_column, m_odbcType, (SQLPOINTER)GetPointer(), m_extra->bindLenOrInd, &m_extra->bindLenOrInd);
}


PODBC::Field::~Field()
{
  if (!m_isReadOnly)
    m_row.m_recordSet.m_statement->BindCol(m_column, m_odbcType, NULL, 0, NULL);
  delete m_extra;
}


void PODBC::Field::InternalCopy(const PVarType & other)
{
  if (m_type == other.GetType()) {
    PVarType::InternalCopy(other);
    return;
  }

  switch (m_type) {
    case VarBoolean :
      m_.boolean = other.AsBoolean();
      break;
    case VarInt8 :
      m_.int8 = other.AsInteger();
      break;
    case VarInt16 :
      m_.int16 = other.AsInteger();
      break;
    case VarInt32 :
      m_.int32 = other.AsInteger();
      break;
    case VarInt64 :
      m_.int64 = other.AsInteger64();
      break;
    case VarUInt8 :
      m_.uint8 = other.AsUnsigned();
      break;
    case VarUInt16 :
      m_.uint16 = other.AsUnsigned();
      break;
    case VarUInt32 :
      m_.uint32 = other.AsUnsigned();
      break;
    case VarUInt64 :
      m_.uint64 = other.AsUnsigned64();
      break;
    case VarFloatSingle :
      m_.floatSingle = other.AsFloat();
      break;
    case VarFloatDouble :
      m_.floatDouble = other.AsFloat();
      break;
    case VarFloatExtended :
      m_.floatExtended = other.AsFloat();
      break;
    default :
      SetValue(other.AsString());
      return;
  }

  OnValueChanged();
}


bool PODBC::Field::SetType(BasicType, PINDEX)
{
  return false;
}


void PODBC::Field::SetDefaultValues()
{
  if (IsReadOnly() || IsAutoIncrement())
    return;

  if (IsNullable()) {
    SetValue(PString::Empty());
    SetNULL();
  }
  else
    SetValue("0");
}


void PODBC::Field::OnGetValue()
{
  Statement & statement = *m_row.m_recordSet.m_statement;

  if (m_isReadOnly && m_type == VarTime)
    statement.GetData(m_column, m_odbcType, m_extra, sizeof(*m_extra), &m_extra->bindLenOrInd);

  switch (m_odbcType) {
    case SQL_DATETIME :
      if (m_extra->bindLenOrInd == SQL_NULL_DATA)
        m_.time.seconds = -1;
      else
        m_.time.seconds = PTime(m_extra->datetime).GetTimeInSeconds();
      break;

    case SQL_C_TYPE_DATE:
      if (m_extra->bindLenOrInd == SQL_NULL_DATA)
        m_.time.seconds = -1;
      else
        m_.time.seconds = PTime(0, 0, 0, m_extra->date.day, m_extra->date.month, m_extra->date.year).GetTimeInSeconds();
      break;

    case SQL_C_TYPE_TIME:
      if (m_extra->bindLenOrInd == SQL_NULL_DATA)
        m_.time.seconds = -1;
      else
        m_.time.seconds = PTime(m_extra->time.second, m_extra->time.minute, m_extra->time.hour, 0, 0, 0).GetTimeInSeconds();
      break;

    case SQL_C_TYPE_TIMESTAMP:
      if (m_extra->bindLenOrInd == SQL_NULL_DATA)
        m_.time.seconds = -1;
      else
        m_.time.seconds = PTime(m_extra->timestamp.second, m_extra->timestamp.minute, m_extra->timestamp.hour,
                                m_extra->timestamp.day, m_extra->timestamp.month, m_extra->timestamp.year).GetTimeInSeconds();
      break;

    default :
      if (m_extra->bindLenOrInd != 0) {
        if (m_isReadOnly)
          statement.GetData(m_column, m_odbcType, (SQLPOINTER)GetPointer(), GetSize(), &m_extra->bindLenOrInd);
      }
      else {
        size_t nullTerminatedString = m_odbcType == SQL_C_CHAR ? 1 : 0;
        size_t total = 0;
        SQLINTEGER chunk = m_.dynamic.size;
        SQLLEN lenOrInd = 0;
        while (statement.GetData(m_column, m_odbcType, m_.dynamic.data+total, chunk, &lenOrInd)) {
          total += chunk - nullTerminatedString;

          if (lenOrInd == SQL_NO_TOTAL)
            m_.dynamic.Realloc(m_.dynamic.size+1000);
          else if (lenOrInd > chunk)
            m_.dynamic.Realloc(lenOrInd + nullTerminatedString);
          else
            break;

          chunk = m_.dynamic.size - total;
        }
      }
  }
}


void PODBC::Field::OnValueChanged()
{
  Statement & statement = *m_row.m_recordSet.m_statement;

  switch (m_type) {
    case VarFixedString :
    case VarStaticString :
    case VarDynamicString :
      m_extra->bindLenOrInd = strlen(m_.dynamic.data); // Allow for trailing '\0'

      // Bind again as pointer might have changed
      if (!m_isReadOnly)
        statement.BindCol(m_column, m_odbcType, (SQLPOINTER)GetPointer(), GetSize(), &m_extra->bindLenOrInd);
      break;

    case VarDynamicBinary :
      m_extra->bindLenOrInd = GetSize();

      // Bind again as pointer might have changed
      if (!m_isReadOnly)
        statement.BindCol(m_column, m_odbcType, (SQLPOINTER)GetPointer(), GetSize(), &m_extra->bindLenOrInd);
      break;

    case VarTime:
      if (m_.time.seconds <= 0)
        SetNULL();
      else {
        PTime time(m_.time.seconds);
        switch (m_odbcType) {
          case SQL_DATETIME :
            strcpy(m_extra->datetime, time.AsString("YYYY-MM-dd hh:mm:ss"));
            m_extra->bindLenOrInd = sizeof(m_extra->datetime);
            break;

          case SQL_C_TYPE_DATE:
            m_extra->date.day = time.GetDay();
            m_extra->date.month = time.GetMonth();
            m_extra->date.year = time.GetYear();
            m_extra->bindLenOrInd = sizeof(m_extra->date);
            break;

          case SQL_C_TYPE_TIME:
            m_extra->time.second = time.GetSecond();
            m_extra->time.minute = time.GetMinute();
            m_extra->time.hour = time.GetHour();
            m_extra->bindLenOrInd = sizeof(m_extra->time);
            break;

          case SQL_C_TYPE_TIMESTAMP:
            m_extra->timestamp.fraction = time.GetMicrosecond()*1000;
            m_extra->timestamp.second = time.GetSecond();
            m_extra->timestamp.minute = time.GetMinute();
            m_extra->timestamp.hour = time.GetHour();
            m_extra->timestamp.day = time.GetDay();
            m_extra->timestamp.month = time.GetMonth();
            m_extra->timestamp.year = time.GetYear();
            m_extra->bindLenOrInd = sizeof(m_extra->timestamp);
            break;
        }
      }
      break;

    default :
      m_extra->bindLenOrInd = GetSize();
  }
}


void PODBC::Field::SetNULL()
{
  m_extra->bindLenOrInd = SQL_NULL_DATA;
}


bool PODBC::Field::IsNULL() const
{
  return m_extra->bindLenOrInd == SQL_NULL_DATA;
}


bool PODBC::Field::Post()
{
  return m_row.m_recordSet.Commit();
}


///////////////////////////////////////////////////////////////////
// PODBC::Row

PODBC::Row::Row(RecordSet & table)
  : m_recordSet(table)
  , m_rowIndex(UndefinedRowIndex)
{
}


PODBC::Field & PODBC::Row::Column(PINDEX col) const
{
  /// Column = 0 return blank field
  return m_fields[PAssert(col > 0 && col <= m_fields.GetSize(), PInvalidParameter) ? col-1 : 0];
}


PODBC::Field & PODBC::Row::Column(const PString & name) const
{
  return Column(ColumnByName(name));
}


PStringArray PODBC::Row::ColumnNames() const
{
  PStringArray names;
  for (PINDEX i = 0; i < m_fields.GetSize(); ++i)
    names.AppendString(m_fields[i].GetName());
  return names;
}


PINDEX PODBC::Row::Rows()
{
  return m_recordSet.Rows();
}


void PODBC::Row::SetNewRow()
{
  m_rowIndex = UndefinedRowIndex;
  for (PINDEX i = 0; i < m_fields.GetSize(); i++)
    m_fields[i].SetDefaultValues();
}


bool PODBC::Row::MoveTo(RowIndex row)
{
  if (row == UndefinedRowIndex)
    return false;

  if (m_rowIndex == row)
    return true;

  if (!m_recordSet.m_statement->FetchScroll(SQL_FETCH_ABSOLUTE, row))
    return false;

  m_rowIndex = row;
  return true;
}


bool PODBC::Row::Move(int offset)
{
  switch (offset) {
    case 0 :
      return true;
    case 1 :
      return Next();
    case -1 :
      return Previous();
  }

  if (!m_recordSet.m_statement->FetchScroll(SQL_FETCH_RELATIVE, offset))
    return false;

  if (m_rowIndex != UndefinedRowIndex)
    m_rowIndex += offset;
  return true;
}


bool PODBC::Row::First()
{
  if (!m_recordSet.m_statement->FetchScroll(SQL_FETCH_FIRST))
    return false;

  m_rowIndex = 1;
  return true;
}


bool PODBC::Row::Next()
{
  if (!m_recordSet.m_statement->FetchScroll(SQL_FETCH_NEXT)) {
    if (m_recordSet.m_statement->m_lastResult == SQL_NO_DATA && m_recordSet.m_totalRows == UndefinedRowIndex)
      m_recordSet.m_totalRows = m_rowIndex;
    m_rowIndex = UndefinedRowIndex;
    return false;
  }

  if (m_rowIndex != UndefinedRowIndex)
    ++m_rowIndex;
  return true;
}


bool PODBC::Row::Previous()
{
  if (!m_recordSet.m_statement->FetchScroll(SQL_FETCH_PRIOR))
    return false;

  if (m_rowIndex != UndefinedRowIndex)
    --m_rowIndex;
  return true;
}


bool PODBC::Row::Last()
{
  if (!m_recordSet.m_statement->FetchScroll(SQL_FETCH_LAST))
    return false;

  m_rowIndex = m_recordSet.Rows(false);
  return true;
}


bool PODBC::Row::Commit()
{
  if (m_rowIndex != 0)
    return m_recordSet.m_statement->Commit(SQL_UPDATE);

  if (!m_recordSet.m_statement->Commit(SQL_ADD))
    return false;

  if (m_recordSet.m_totalRows != UndefinedRowIndex)
    ++m_recordSet.m_totalRows;

  return true;
}


bool PODBC::Row::Delete(RowIndex row)
{
  return m_recordSet.DeleteRow(row);
}


PINDEX PODBC::Row::ColumnByName(const PCaselessString & columnName) const
{
  PINDEX nCols = Columns();
  for(PINDEX i = 1; i <= nCols; ++i) {
    if (columnName == ColumnName(i))
      return i;
  }
  return 0;
}


/////////////////////////////////////////////////////////////////////////////
// PODBC::Table

PODBC::RecordSet::RecordSet(PODBC & odbc, const PString & query)
  : m_statement(new Statement(odbc))
  , m_totalRows(UndefinedRowIndex)
  , P_DISABLE_MSVC_WARNINGS(4355, m_cursor(*this))
{
  Query(query);
}


PODBC::RecordSet::RecordSet(PODBC * odbc, const PString & query)
  : m_statement(new Statement(*odbc))
  , m_totalRows(UndefinedRowIndex)
  , P_DISABLE_MSVC_WARNINGS(4355, m_cursor(*this))
{
  Query(query);
}


PODBC::RecordSet::RecordSet(const RecordSet & other)
  : PObject(other)
  , m_statement(NULL)
  , P_DISABLE_MSVC_WARNINGS(4355, m_cursor(*this))
{
}


PODBC::RecordSet::~RecordSet()
{
  m_cursor.m_fields.RemoveAll(); // Delete fields before m_statement
  delete m_statement;
}


bool PODBC::RecordSet::Query(const PString & query)
{
  m_statement->CloseCursor();
  m_cursor.m_rowIndex = 0;
  m_cursor.m_fields.RemoveAll();

  if (query.IsEmpty())
    return false;

  PCaselessString trimmed = query.Trim();
  if (trimmed.NumCompare("SELECT") != EqualTo &&
      m_statement->m_odbc.TableList().GetStringsIndex(trimmed) != P_MAX_INDEX)
    return Select(trimmed);

  if (!m_statement->Execute(query))
    return false;

  // See if succeeded
  SQLSMALLINT numColumns = 0;
  if(!m_statement->NumResultCols(&numColumns))
    return false;

  // Go to the First Row
  m_cursor.First();

  // Get initial values and structure
  for (SQLSMALLINT i = 1; i <= numColumns; i++)
    m_cursor.m_fields.Append(new Field(m_cursor, i));

  return true;
}


bool PODBC::RecordSet::Select(const PString & table,
                              const PString & whereClause,
                              const PString & fields,
                              const PString & orderedBy,
                              bool descending)
{
  PStringStream query;
  query << "SELECT ";

  if (fields.IsEmpty())
    query << '*';
  else
    query << fields;

  query << " FROM ";
  if (table.FindOneOf(" .") == P_MAX_INDEX)
    query << table;
  else
    query << '[' << table << ']';

  if (!whereClause.IsEmpty())
    query << " WHERE (" << whereClause << ')';

  if (!orderedBy.IsEmpty()) {
    query << " ORDERED BY ";
    if (orderedBy.FindOneOf(" .") == P_MAX_INDEX)
      query << orderedBy;
    else
      query << '[' << orderedBy << ']';
    if (descending)
      query << " DESC";
  }

  query << ';';
  return Query(query);
}


PODBC::RowIndex PODBC::RecordSet::Rows(bool forceCount)
{
  if (m_totalRows == UndefinedRowIndex && forceCount) {
    RowIndex save = m_cursor.m_rowIndex;
    if (m_cursor.First()) {
      do {
      } while (m_cursor.Next());
    }
    m_cursor.MoveTo(save);
  }
  return m_totalRows;
}


PODBC::Row & PODBC::RecordSet::NewRow()
{
  m_cursor.SetNewRow();
  return m_cursor;
}


bool PODBC::RecordSet::DeleteRow(RowIndex row)
{
  if (row != 0)
    m_cursor.Navigate(row);

  if (!m_statement->Commit(SQL_DELETE))
    return false;

  if (m_totalRows != UndefinedRowIndex)
    --m_totalRows;

  return true;
}


PODBC::Row & PODBC::RecordSet::operator[](PINDEX row)
{
  m_cursor.Navigate(row);
  return m_cursor;
}


PODBC::Field & PODBC::RecordSet::operator()(RowIndex row, PINDEX col)
{
  m_cursor.Navigate(row);
  return m_cursor.Column(col);
}


#endif // P_ODBC

