/////////////////////////////////////////////////////////////////////////////
// Name:        MyODBC classes
// Purpose:     
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     2000/07/27
// RCS-ID:      $Id: myodbc.cpp,v 1.3 2001/04/23 21:55:19 vlabella Exp $
// Copyright:   (C) PM-Soft
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////
  
#ifdef __GNUG__
    #pragma implementation "myodbc.cpp"
    #pragma interface "myodbc.h"
#endif

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include "wx/wspp/myodbc.h"

#include "sql.h"
#include "sqlext.h"

#include <time.h>

/***** Implementation for class MyDriverManager *****/

MYODBC_HENV MyDriverManager::mHEnv = NULL;

char MyDriverManager::mDateTimeFormat[100] = "%c";
char MyDriverManager::mFloatFormat[100] = "%f";

MyConnection* MyDriverManager::GetConnection( const wxString& dsn, 
                        const wxString& user, 
                        const wxString& password )
{
  CheckStarted();

  HDBC hdbc;

  int ret = SQLAllocConnect( (SQLHENV)mHEnv, &hdbc);

  if ( ret != SQL_SUCCESS ) return NULL;

  ret = SQLConnect( hdbc, (unsigned char*)(const char*)dsn, dsn.Length(), 
              (unsigned char*)(const char*)user, user.Length(), 
              (unsigned char*)(const char*)password, password.Length() );

  if ( ret != SQL_SUCCESS )
  {
    printf( "ERROR: MyDriverManager::GetConnection() - faild connecting, "
        "dsn = %s, user = %s, password = %s\n", 
        dsn.c_str(), user.c_str(), password.c_str() );

    SQLFreeConnect( hdbc );

    return NULL;
  }

  MyConnection* con =new MyConnection( (MYODBC_HDBC)hdbc );

  return con;
}

void MyDriverManager::ShutDown()
{
  if ( mHEnv != NULL ) 
  {
    SQLFreeEnv( (HENV)mHEnv );

    mHEnv = NULL;
  }
}

void MyDriverManager::SetDateTimeFormat( const wxString& fmt )
{
  strcpy( mDateTimeFormat, fmt.c_str() );
}

void MyDriverManager::SetFloatFormat( const wxString& fmt )
{
  strcpy( mFloatFormat, fmt.c_str() );
}

const char* MyDriverManager::GetDateTimeFormat()
{
  return mDateTimeFormat;
}

const char* MyDriverManager::GetFloatFormat()
{
  return mFloatFormat;
}

/*** protected methods ***/

void MyDriverManager::CheckStarted()
{
  if ( mHEnv == NULL )

    Init();
}

void MyDriverManager::Init()
{
  wxASSERT( mHEnv == NULL ); // DBG::

  HENV henv;
  int ret = SQLAllocEnv( &henv );

  mHEnv = (MYODBC_HENV)henv;

  wxASSERT_MSG( ret == SQL_SUCCESS, "FATAL ERROR: Failed allocating ODBC-environment!\n" );
}

MYODBC_HENV MyDriverManager::GetHEnv()
{
  return mHEnv;
}

/***** Implementation for class MyColumnData *****/

MyColumnData::MyColumnData()

  : mpInfo( NULL ),
    mSetValues( NULL ),
    mSetSizes( NULL ),
    mCachedValues( NULL ),
    mCachedSizes( NULL )
{}

MyColumnData::~MyColumnData()
{
  if ( mSetValues ) delete [] mSetValues;
  if ( mSetSizes  ) delete [] mSetSizes;

  if ( mCachedValues ) delete [] mCachedValues;
  if ( mCachedSizes  ) delete [] mCachedSizes;
}

/***** Implementation for class MyResultSetMetaData *****/

MyResultSetMetaData::MyResultSetMetaData()

  : mpResultSet( NULL )
{}

MyResultSetMetaData::~MyResultSetMetaData()
{
  WX_CLEAR_ARRAY( mColumns );
}

void MyResultSetMetaData::SetResultSet( MyResultSet* pSet )
{
  mpResultSet = pSet;
}

int MyResultSetMetaData::GetColumnCount()
{
  return mColumns.Count();
}

int MyResultSetMetaData::GetColumnSqlType( int col )
{
  wxASSERT( col >=1 && col <= (int)mColumns.Count() ); // DBG::

  return mColumns[col-1]->mSqlType;
}

int MyResultSetMetaData::GetColumnCType( int col )
{
  wxASSERT( col >=1 && col <= (int)mColumns.Count() ); // DBG::

  return mColumns[col-1]->mCType;
}

const wxString& MyResultSetMetaData::GetColumnName( int col )
{
  wxASSERT( col >=1 && col <= (int)mColumns.Count() ); // DBG::

  return mColumns[col-1]->mName;
}

/***** Implementation for class MyResultSet *****/

MyResultSet::MyResultSet( MYODBC_HSTMT hstmt )

  : mHStmt( hstmt ),
    mFetchSize( 200 ),

    mColData( NULL ),
    mColCount( 0 ),
    mCacheUsed( 0 ),
    mCacheSize( 0 ),

    mCurRow( POS_BEFORE_FIRST ),
    mRowSetFirstRow( POS_BEFORE_FIRST ),
    mRowSetSize( 0 ),

    mEOFReached( FALSE )
{
  FetchMetaData();

  SetFetchSize( mFetchSize );

  BindColumns();
}

MyResultSet::~MyResultSet()
{
  DestroyCaches();
}

void MyResultSet::SetFetchSize( int fetchSize )
{
  mFetchSize = fetchSize;

  int ret = SQLSetStmtOption( (SQLHSTMT)mHStmt, SQL_ROWSET_SIZE, (SQLUINTEGER)fetchSize );

  if ( ret != SQL_SUCCESS )
  {
    wxString msg;
    msg.Printf( "FATAL-ERROR: failed setting fetch-size to %d\n", fetchSize );

    int avoidCompilerWarning = 0;
    wxASSERT_MSG( avoidCompilerWarning, msg ); // DBG::
  }
}

int MyResultSet::GetFetchSize()
{
  return mFetchSize;
}

bool MyResultSet::BeforeFirst()
{
  int avoidCompilerWarning = 0;
  wxASSERT( avoidCompilerWarning ); // FOR NOW::

  return TRUE;
}

bool MyResultSet::First()
{
  int avoidCompilerWarning = 0;
  wxASSERT( avoidCompilerWarning ); // FOR NOW::

  return TRUE;
}

bool MyResultSet::Last()
{
  int avoidCompilerWarning = 0;
  wxASSERT( avoidCompilerWarning ); // FOR NOW::

  return TRUE;
}

bool MyResultSet::Previous()
{
  int avoidCompilerWarning = 0;
  wxASSERT( avoidCompilerWarning ); // FOR NOW::

  return TRUE;
}

bool MyResultSet::Next()
{
  if ( mCurRow == POS_AFTER_LAST )

    return FALSE;

  if ( mCurRow != POS_BEFORE_FIRST )
  {
    if ( mCurRow + 1 < mRowSetFirstRow + mRowSetSize ) // if hits rowset cache 
    {
      ++mCurRow; 

      return TRUE;
    }
  }

  if ( mEOFReached )
  {
    mCurRow = POS_AFTER_LAST;

    return FALSE;
  }

  UDWORD rowsFetched;

  int ret = SQLExtendedFetch( (SQLHSTMT)mHStmt, SQL_FETCH_NEXT, 0, &rowsFetched, NULL );

  if ( ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO )
  {
    if ( mRowSetFirstRow == POS_BEFORE_FIRST )
    
      mRowSetFirstRow = 0;
    else
      mRowSetFirstRow = mRowSetFirstRow + mRowSetSize;

    mRowSetSize = rowsFetched;

    CacheRowsetData();

    mCurRow = mRowSetFirstRow;
  }
  else
    mEOFReached = TRUE;

  if ( mRowSetSize != mFetchSize )
  
    mEOFReached = TRUE;

  return ( ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO ) && mRowSetSize != 0;
}

bool MyResultSet::IsBeforeFirst()
{
  return mCurRow == POS_BEFORE_FIRST;
}

bool MyResultSet::IsAfterLast()
{
  return mCurRow == POS_AFTER_LAST;
}

bool MyResultSet::IsEof()
{
  return mCurRow == POS_BEFORE_FIRST || 
       mCurRow == POS_AFTER_LAST   || 
       mCurRow == POS_UNKNOWN;
}

int MyResultSet::GetCurrentRowPos()
{
  return mCurRow;
}

bool MyResultSet::SetCurrentRowPos( int pos )
{
  if ( pos >= mRowSetFirstRow + mRowSetSize )

    return FALSE;

  mCurRow = pos;

  return TRUE;
}

wxString MyResultSet::GetString( int colIdx )
{
  if ( mCurRow == POS_BEFORE_FIRST ||
     mCurRow == POS_AFTER_LAST )

     return "OUT-OF-RANGE!";

  MyColumnData& col = mColData[ colIdx-1 ];

  void* data = GetDataPtr( colIdx );

  if ( col.mpInfo->mCType == SQL_C_SLONG )
  {
    wxString s;
    s.Printf( "%d", *(SDWORD*)data );
    return s;
  }
  else
  if ( col.mpInfo->mCType == SQL_C_SSHORT )
  {
    wxString s;
    s.Printf( "%d", *(SWORD*)data );
    return s;
  }
  else
  if ( col.mpInfo->mCType == SQL_C_STINYINT )
  {
    wxString s;
    s.Printf( "%d", *(SCHAR*)data );
    return s;
  }
  else
  if ( col.mpInfo->mCType == SQL_C_CHAR )

    return (char*)data;
  else
  if ( col.mpInfo->mCType == SQL_C_TIMESTAMP )
  {
    TIMESTAMP_STRUCT& ts = *(TIMESTAMP_STRUCT*)data;

    int delta = 1 - (int)wxDateTime::Jan;
    int mval = ts.month - delta;
    wxDateTime::Month m = (wxDateTime::Month)mval;

    wxDateTime dt( ts.day, m, ts.year, ts.hour, ts.minute, ts.second, ts.fraction/1000000 );
    return dt.Format( MyDriverManager::GetDateTimeFormat() );
  }
  else
  if ( col.mpInfo->mCType == SQL_C_DOUBLE )
  {
    wxString s;
    s.Printf( MyDriverManager::GetFloatFormat(), *(double*)data );
    return s;
  }
  else
  if ( col.mpInfo->mCType == SQL_C_FLOAT )
  {
    wxString s;
    s.Printf( MyDriverManager::GetFloatFormat(), *(float*)data );
    return s;
  }

  return "CONV-ERROR!";
}

int MyResultSet::GetInt( int colIdx )
{
  MyColumnData& col = mColData[ colIdx-1 ];

  void* data = GetDataPtr( colIdx );

  if ( col.mpInfo->mCType == SQL_C_SLONG )

    return (int)*(SDWORD*)data;
  else
  if ( col.mpInfo->mCType == SQL_C_SSHORT )

    return (int)*(SWORD*)data;
  else
  if ( col.mpInfo->mCType == SQL_C_STINYINT )

    return (int)*(SCHAR*)data;
  else
  if ( col.mpInfo->mCType == SQL_C_FLOAT )

    return (int)*(float*)data;
  else
  if ( col.mpInfo->mCType == SQL_C_DOUBLE )

    return (int)*(double*)data;

  return -1;
}

double MyResultSet::GetDouble( int colIdx )
{
  MyColumnData& col = mColData[ colIdx-1 ];

  void* data = GetDataPtr( colIdx );

  if ( col.mpInfo->mCType == SQL_C_SLONG )

    return (double)*(SDWORD*)data;
  else
  if ( col.mpInfo->mCType == SQL_C_SSHORT )

    return (double)*(SWORD*)data;
  else
  if ( col.mpInfo->mCType == SQL_C_STINYINT )

    return (double)*(SCHAR*)data;
  else
  if ( col.mpInfo->mCType == SQL_C_FLOAT )

    return (double)*(float*)data;
  else
  if ( col.mpInfo->mCType == SQL_C_DOUBLE )

    return (double)*(double*)data;

  return -1.0;
}

wxDateTime MyResultSet::GetTime( int colIdx )
{
  MyColumnData& col = mColData[ colIdx-1 ];

  void* data = GetDataPtr( colIdx );

  if ( col.mpInfo->mCType == SQL_C_TIME )
  {
    TIMESTAMP_STRUCT& ts = *(TIMESTAMP_STRUCT*)data;

    int delta = 1 - (int)wxDateTime::Jan;
    int mval = ts.month - delta;
    wxDateTime::Month m = (wxDateTime::Month)mval;

    return wxDateTime( ts.day, m, ts.year, ts.hour, ts.minute, ts.second, ts.fraction/1000000 );
  }

  return wxDateTime();
}

MyResultSetMetaData* MyResultSet::GetMetaData()
{
  return &mMetaData;
}

int MyResultSet::GetCacheSize()
{
  return mCacheSize;
}

int MyResultSet::GetCacheUsed()
{
  return mCacheUsed;
}

void MyResultSet::InsertCacheRows( int atPos, int nRows )
{
  ExpandCache( mCacheUsed + nRows );

  for( int i = 0; i != mMetaData.GetColumnCount(); ++i )
  
    DoInsertCacheRows( &mColData[i], atPos, nRows );
  
  mCacheUsed += nRows;
}

void MyResultSet::RemoveCacheRows( int atPos, int nRows )
{
  for( int i = 0; i != mMetaData.GetColumnCount(); ++i )
  
    DoRemoveCacheRows( &mColData[i], atPos, nRows );

  mCacheUsed -= nRows;
}

int MyResultSet::FindInsertionPos( int colNo, const wxString& value, bool isAscending )
{
  MyColumnData& data = mColData[colNo];

  // find out if inserted row goes somewhere inside the
  // cached row-set

  SDWORD slongValue=0;
  SWORD  sshortValue;
  double doubleValue;
  float  floatValue;
  char   charValue[500];
  TIMESTAMP_STRUCT tsValue;

  wxASSERT( value.Length() < sizeof(charValue ) ); // DBG::

  {
    tsValue.year     = 0; // shuts up warning in VC++ Warn 4
    tsValue.month    = 0; // shuts up warning in VC++ Warn 4
    tsValue.day      = 0; // shuts up warning in VC++ Warn 4
    tsValue.hour     = 0; // shuts up warning in VC++ Warn 4
    tsValue.minute   = 0; // shuts up warning
    tsValue.fraction = 0; // shuts up warning in VC++ Warn 4
    int type = data.mpInfo->mCType;

    switch( type )
    {
      case SQL_C_SLONG   : slongValue = atoi( value ); break;
      case SQL_C_SSHORT : sshortValue = atoi( value ); break;
      case SQL_C_FLOAT  : sscanf( value.c_str(), MyDriverManager::GetFloatFormat(), &floatValue ); break;
      case SQL_C_DOUBLE : sscanf( value.c_str(), MyDriverManager::GetFloatFormat(), &doubleValue ); break;
      case SQL_C_CHAR   : strcpy( charValue, (const char*)value ); break;
      case SQL_C_TIMESTAMP : 
        {
          wxDateTime dt;
          dt.ParseFormat( value, MyDriverManager::GetDateTimeFormat() ); 
          tsValue.fraction = dt.GetMillisecond() * 1000000;
          tsValue.second = dt.GetSecond();
          tsValue.hour = dt.GetHour();
          tsValue.day = dt.GetDay();
          tsValue.month = dt.GetMonth(); // FIXEM:: ..
          tsValue.year = dt.GetYear();
          break;
        }

      default: break;
    }
  }


  for( int pos = 0; pos != mCacheUsed; ++pos )
  {
    bool hits = FALSE;
    // FIXME:: non-lexicografical compares for number/date columns!

    void* bytes = (void*)(data.mCachedValues + pos * data.mpInfo->mSize);

    switch( data.mpInfo->mCType )
    {
      case SQL_C_SLONG   : 
        {
          if ( isAscending ) hits = slongValue < *(SDWORD*)bytes;
                  else hits = slongValue > *(SDWORD*)bytes;
          break;
        }
      case SQL_C_SSHORT : 
        {
          if ( isAscending ) hits = slongValue < *(SDWORD*)bytes;
                  else hits = slongValue > *(SDWORD*)bytes;
          break;
        }


      case SQL_C_FLOAT  : 
        {
          if ( isAscending ) hits = slongValue < *(SDWORD*)bytes;
                  else hits = slongValue > *(SDWORD*)bytes;
          break;
        }

      case SQL_C_DOUBLE : 
        {
          if ( isAscending ) hits = slongValue < *(SDWORD*)bytes;
                  else hits = slongValue > *(SDWORD*)bytes;
          break;
        }

      case SQL_C_CHAR   : 
        {
          if ( isAscending ) hits = strcmpi( charValue, (char*)bytes ) < 0;
                  else hits = strcmpi( charValue, (char*)bytes ) > 0;
          break;
        }

      case SQL_C_TIMESTAMP : 
        {
          TIMESTAMP_STRUCT& ts = *(TIMESTAMP_STRUCT*)bytes;

          // FOR NOW:: assuming that boolean-evaluation compiled as not-compleat

          bool isLess = ts.year     < tsValue.year   ||
                  ts.month    < tsValue.month  ||
                  ts.day      < tsValue.day    ||
                  ts.hour     < tsValue.hour   ||
                  ts.minute   < tsValue.minute ||
                  ts.fraction < tsValue.fraction;

          if ( isAscending ) hits = isLess;
                  else hits = !isLess;
          break;
        }


      default : break;
    }

    if ( hits )

      return pos;
  }

  return -1;
}

void MyResultSet::SetCacheRowValue( int row, int col, const wxString& value )
{
  MyColumnData& column = mColData[col];

  void* data = (void*)(column.mCachedValues + row * column.mpInfo->mSize);

  switch( column.mpInfo->mCType )
  {
    case SQL_C_SLONG : 
    {
      *(SDWORD*)data = atoi( value );
      column.mCachedSizes[ row ] = sizeof(SDWORD);
      break;
    }
    case SQL_C_DOUBLE :
    {
      sscanf( value.c_str(), "%f", (double*)data );
      column.mCachedSizes[ row ] = sizeof(double);
      break;
    }
    case SQL_C_FLOAT :
    {
      sscanf( value.c_str(), "%f", (float*)data );
      column.mCachedSizes[ row ] = sizeof(float);
      break;
    }
    case SQL_C_SSHORT :
    {
      *(SWORD*)data = atoi( value );
      column.mCachedSizes[ row ] = sizeof(SWORD);
      break;
    }
    case SQL_C_STINYINT :
    {
      *(SCHAR*)data = atoi( value );
      column.mCachedSizes[ row ] = sizeof(SCHAR);
      break;
    }
    case SQL_C_CHAR :
    {
      if ( (int)value.Length() >= column.mpInfo->mSize + 1 )
      {
        int sz = column.mpInfo->mSize;
        memcpy( (char*)data, value.c_str(), sz );
        *((char*)data + column.mpInfo->mSize - 1 ) = '\0';
        column.mCachedSizes[ row ] = (unsigned long)sz;
      }
      else
      {
        int sz = value.Length() + 1;
        memcpy( (char*)data, value.c_str(), sz );
        column.mCachedSizes[ row ] = (unsigned long)sz;
      }

      break;
    }
    case SQL_C_TIMESTAMP :
    {
      /*
      TIMESTAMP_STRUCT& ts = *(TIMESTAMP_STRUCT*)data;

      int delta = 1 - (int)wxDateTime::Jan;
      int mval = ts.month - delta;
      wxDateTime::Month m = (wxDateTime::Month)mval;

      return wxDateTime( ts.day, m, ts.year, ts.hour, ts.minute, ts.second, ts.fraction/1000000 );
      */

      wxDateTime dt;
      dt.ParseFormat( value, MyDriverManager::GetDateTimeFormat() );

      TIMESTAMP_STRUCT& ts = *(TIMESTAMP_STRUCT*)data;

      ts.fraction = dt.GetMillisecond() * 1000000;
      ts.second   = dt.GetSecond();
      ts.minute   = dt.GetMinute();
      ts.hour     = dt.GetHour();
      ts.year     = dt.GetYear();
      ts.month    = dt.GetMonth(); // FIXME::
      ts.day      = dt.GetDay();

      column.mCachedSizes[ row ] = sizeof(TIMESTAMP_STRUCT);

      break;
    }

    default: break;
  }
}

/*** protected methods ***/

void MyResultSet::ExpandCache( int newSize )
{
  if ( mCacheSize >= newSize )

    return;

  int n = 0, s = 2;
  while( s < newSize )
  {
    s *= 2; 
    ++n;
  }

  newSize = wxMax( 64, s );

  for( int col = 0; col != mColCount; ++col )
  {
    MyColumnData& colData = mColData[col];

    char* oldValues = colData.mCachedValues;
    unsigned long* oldSizes  = colData.mCachedSizes;

    colData.mCachedValues = new char[ newSize * colData.mpInfo->mSize ];
    colData.mCachedSizes  = new unsigned long[ newSize ];

    if ( oldValues )
    {
      memcpy( colData.mCachedValues, oldValues, mCacheSize * colData.mpInfo->mSize );
      memcpy( colData.mCachedSizes , oldSizes,  mCacheSize * sizeof(unsigned long) );

      delete [] oldValues;
      delete [] oldSizes;
    }
  }

  mCacheSize = newSize;
}

void MyResultSet::ReallocRowSetSpace()
{
  for( int col = 0; col != mColCount; ++col )
  {
    MyColumnData& colData = mColData[col];

    if ( colData.mSetValues ) delete [] colData.mSetValues;
    if ( colData.mSetSizes  ) delete [] colData.mSetSizes;

    colData.mSetValues = new char[ mFetchSize * colData.mpInfo->mSize ];
    colData.mSetSizes  = new unsigned long[ mFetchSize ];
  }
}

void MyResultSet::CacheRowsetData()
{
  if ( mCacheUsed >= mRowSetFirstRow + mRowSetSize )

    return;

  ExpandCache( mRowSetFirstRow + mRowSetSize );

  for( int col = 0; col != mColCount; ++col )
  {
    MyColumnData& colData = mColData[col];

    memcpy( colData.mCachedValues + mRowSetFirstRow * colData.mpInfo->mSize, 
        colData.mSetValues, mRowSetSize * colData.mpInfo->mSize );

    memcpy( (void*)(colData.mCachedSizes + mRowSetFirstRow),
          colData.mSetSizes, mRowSetSize * sizeof(unsigned long) );
  }

  mCacheUsed = wxMax( mCacheUsed, mRowSetFirstRow + mRowSetSize );
}

void MyResultSet::BindColumns()
{
  ReallocRowSetSpace();

  for( int col = 0; col != mColCount; ++col )
  {
    MyColumnData& colData = mColData[col];

    int ret = SQLBindCol( (SQLHSTMT)mHStmt, col + 1, 
                colData.mpInfo->mCType, 
                (SQLPOINTER)colData.mSetValues, 
                colData.mpInfo->mSize,
                (SQLINTEGER*)colData.mSetSizes
              );

    wxASSERT_MSG( ret == SQL_SUCCESS, "FATAL-ERROR: failed binding columns\n" ); // DBG::
  }
}


struct MyTypeMapEntry
{
  int sqlType;
  int cType;
  int size;
  char strSqlType[20];
  char strCType[20];
};

MyTypeMapEntry _gSqlTypeMap[] = { 

  { SQL_BIGINT    , SQL_C_SLONG   , sizeof(SDWORD), "SQL_BIGINT",     "SQL_C_SLONG" },
  { SQL_BINARY    , SQL_C_CHAR    , sizeof(char)  , "SQL_BINARY",     "SQL_C_CHAR" },
  { SQL_BIT     , SQL_C_CHAR    , sizeof(char)  , "SQL_BIT",      "SQL_C_CHAR" },
  { SQL_CHAR      , SQL_C_CHAR    , sizeof(char)  , "SQL_CHAR",     "SQL_C_CHAR" },
  { SQL_DOUBLE    , SQL_C_DOUBLE    , sizeof(double), "SQL_DOUBLE",     "SQL_C_DOUBLE" },
  { SQL_FLOAT     , SQL_C_FLOAT   , sizeof(float) , "SQL_FLOAT",      "SQL_C_FLOAT" },
  { SQL_INTEGER   , SQL_C_SLONG   , sizeof(long)  , "SQL_INTEGER",    "SQL_C_SLONG" },
  { SQL_LONGVARBINARY , SQL_C_CHAR    , sizeof(char)  , "SQL_LONGVARBINARY",  "SQL_C_CHAR" },
  { SQL_LONGVARCHAR , SQL_C_CHAR    , sizeof(char)  , "SQL_LONGVARCHAR",  "SQL_C_CHAR" },
  { SQL_NUMERIC   , SQL_C_SLONG   , sizeof(SDWORD), "SQL_NUMERIC",    "SQL_C_SLONG" },
  { SQL_DECIMAL   , SQL_C_CHAR    , sizeof(SDWORD), "SQL_DECIMAL",    "SQL_C_SLONG" },
  { SQL_REAL      , SQL_C_FLOAT   , sizeof(float) , "SQL_REAL",     "SQL_C_FLOAT" },
  { SQL_SMALLINT    , SQL_C_SSHORT    , sizeof(SWORD) , "SQL_SMALLINT",   "SQL_C_SSHORT" },
  { SQL_TINYINT   , SQL_C_STINYINT  , sizeof(SCHAR) , "SQL_TINYINT",    "SQL_C_STINYINT" },
  { SQL_VARBINARY   , SQL_C_CHAR    , sizeof(char)  , "SQL_VARBINARY",    "SQL_C_CHAR" },
  { SQL_VARCHAR   , SQL_C_CHAR    , sizeof(char)  , "SQL_VARCHAR",    "SQL_C_CHAR" },

  { SQL_DATE      , SQL_C_TIMESTAMP , sizeof(TIMESTAMP_STRUCT), "SQL_DATE",   "SQL_C_TIMESTAMP" },
  { SQL_TIME      , SQL_C_TIMESTAMP , sizeof(TIMESTAMP_STRUCT), "SQL_TIME",   "SQL_C_TIMESTAMP" },
  { SQL_TIMESTAMP   , SQL_C_TIMESTAMP , sizeof(TIMESTAMP_STRUCT), "SQL_TIMESTAMP","SQL_C_TIMESTAMP" },
};

int _gSqlTypeMapSize = 19;

void MyResultSet::FetchMetaData()
{
  DestroyCaches();

  SQLINTEGER colCount = 0;

  int ret = SQLColAttributes( (SQLHSTMT)mHStmt, 0, SQL_COLUMN_COUNT, 
                NULL, 0, NULL, &colCount );

  wxASSERT_MSG( ret == SQL_SUCCESS, "FATAL-ERROR: SQLColAttributes() failed!\n" ); // DBG::

  mColCount = colCount;

  WX_CLEAR_ARRAY( mMetaData.mColumns );
  mMetaData.mColumns.Clear();
  mMetaData.mpResultSet = this;

  mColData = new MyColumnData[ mColCount ];

  for( int col = 0; col != mColCount; ++col )
  {
    MyColumnInfo* pInfo = new MyColumnInfo();

    unsigned char name[256];
    SQLSMALLINT nameLen, sqlType, scale, nullable;
    SQLUINTEGER precision;

    int ret = SQLDescribeCol( (SQLHSTMT)mHStmt, col + 1, name, sizeof(name), 
                  &nameLen, &sqlType, &precision, &scale, &nullable );

    wxASSERT_MSG( ret == SQL_SUCCESS, "FATAL-ERROR: SQLDescribeCol() failed!\n" ); // DBG::

    pInfo->mName    = name;
    pInfo->mSqlType   = (int)sqlType;
    pInfo->mIsNulable = nullable == SQL_NULLABLE;
    pInfo->mScale     = (int)scale;
    pInfo->mPrecision = (int)precision;

    mColData[col].mpInfo = pInfo;

    for( int i = 0; i != _gSqlTypeMapSize; ++i )
    {
      MyTypeMapEntry& entry = _gSqlTypeMap[i];

      if ( entry.sqlType == (int)sqlType )
      {
        pInfo->mCType = entry.cType;
        pInfo->mSize  = entry.size;

        strcpy( pInfo->mStrCType, entry.strCType );
        strcpy( pInfo->mStrSqlType, entry.strSqlType );
        break;
      }
    }

    if ( sqlType == SQL_VARCHAR     ||
       sqlType == SQL_CHAR      ||
       sqlType == SQL_LONGVARCHAR   ||
       sqlType == SQL_LONGVARBINARY ||
       sqlType == SQL_BINARY
       )
       pInfo->mSize = wxMin( precision + 1, // + terminating \0

                   200 ); // FIXME:: how to handle var-size strings efficiently?

    mMetaData.mColumns.Add( pInfo );
  }
}

void MyResultSet::DestroyCaches()
{
  if ( mColData )
  {
    delete [] mColData;

    mCacheUsed = 0;
    mCacheSize = 0;
  }
}

void* MyResultSet::GetDataPtr( int colIdx )
{
  MyColumnData& col = mColData[ colIdx-1 ];

  return (void*)(col.mCachedValues + mCurRow * col.mpInfo->mSize);
}

void MyResultSet::DoInsertCacheRows( MyColumnData* pCol, int atPos, int nRows )
{
  /*
  // OLD STUFF::
  int sz = pCol->mpInfo->mSize;
  int at = sz * atPos;
  int cnt = mCacheSize - atPos;

  memmove( pCol->mCachedValues + at + sz * nRows, pCol->mCachedValues + at, cnt * sz );

  sz = sizeof( unsigned long );
  at = sz * atPos;

  memmove( pCol->mCachedSizes + at + sz * nRows, pCol->mCachedSizes + at, cnt * sz );
  */

  int sz = pCol->mpInfo->mSize;
  int at = sz * atPos;
  int cnt = mCacheUsed - atPos;

  memmove( pCol->mCachedValues + at + sz * nRows, pCol->mCachedValues + at, cnt * sz );

  memset( pCol->mCachedValues + at, 0, nRows * sz );

  sz = sizeof( unsigned long );
  at = sz * atPos;

  memmove( (char*)(pCol->mCachedSizes + atPos + nRows), (char*)(pCol->mCachedSizes + atPos), cnt * sz );

  memset( (char*)(pCol->mCachedSizes + atPos), 0, nRows * sz );
}

void MyResultSet::DoRemoveCacheRows( MyColumnData* pCol, int atPos, int nRows )
{
  /*
  // OLD STUFF::
  int sz = pCol->mpInfo->mSize;
  int at = sz * atPos;
  int cnt = mCacheUsed - ( atPos + nRows );

  memmove( pCol->mCachedValues + at, pCol->mCachedValues + at + sz * nRows, cnt * sz );

  sz = sizeof( unsigned long );
  at = sz * atPos;

  memmove( pCol->mCachedSizes + at,  pCol->mCachedSizes  + at + sz * nRows, cnt * sz );
  */

  int sz = pCol->mpInfo->mSize;
  int at = sz * atPos;
  int cnt = mCacheUsed - ( atPos + nRows );

  memmove( pCol->mCachedValues + at, pCol->mCachedValues + at + sz * nRows, cnt * sz );

  sz = sizeof( unsigned long );
  at = sz * atPos;

  memmove( pCol->mCachedSizes + atPos,  pCol->mCachedSizes  + atPos + nRows, cnt );
}

/***** Implementation for class MyConnection *****/

MyConnection::MyConnection( MYODBC_HDBC hdbc )
  
  : mHdbc( hdbc )
{}

MyStatement* MyConnection::CreateStatement( MyResultSet::RESULT_SET_TYPE rsType )
{
  SQLHSTMT hstmt;

  int ret = SQLAllocStmt( (SQLHDBC)mHdbc, &hstmt );

  if ( ret != SQL_SUCCESS ) 
  {
    printf( "WARNING: MyConnection::CreateStatement failed!\n" );

    return NULL;
  }

  return new MyStatement( rsType, (MYODBC_HSTMT)hstmt );
}

bool MyConnection::ExecuteDirectSQL( const wxString sqlStmtStr )
{
  MyStatement* pStmt = CreateStatement();

  if ( !pStmt ) return FALSE;

  bool ok = pStmt->ExecuteDirectSQL( sqlStmtStr );

  if ( !ok )
  {
    delete pStmt;
    return FALSE;
  }

  ok = Commit();

  delete pStmt;

  return ok;
}

bool MyConnection::Commit()
{
  int ret = SQLTransact( (SQLHENV)MyDriverManager::GetHEnv(), (SQLHDBC)mHdbc, SQL_COMMIT );

  bool ok = ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO;

  if ( !ok )

    printf( "WARNING: MyConnection::Commit() failed!\n" );

  return ok;
}

bool MyConnection::RollBack()
{
  int ret = SQLTransact( (SQLHENV)MyDriverManager::GetHEnv(), (SQLHDBC)mHdbc, SQL_ROLLBACK );

  bool ok = ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO;

  if ( !ok )

    printf( "WARNING: MyConnection::RollBack() failed!\n" );

  return ok;
}

/***** Implementation for class MyStatement *****/

MyStatement::MyStatement( MyResultSet::RESULT_SET_TYPE rsType, MYODBC_HSTMT hstmt )

  : mHStmt( hstmt )
{
  if ( rsType == MyResultSet::RS_TYPE_SCROLL_SENSITIVE )
  {
    int ret = SQLSetStmtOption( (SQLHSTMT)hstmt, SQL_CURSOR_TYPE, SQL_CURSOR_DYNAMIC );

    wxASSERT_MSG( ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO,
            "FATAL-ERROR: SQLSetStmtOption failed!" ); // DBG::
  }
}

MyStatement::~MyStatement()
{
  SQLFreeStmt( (SQLHSTMT)mHStmt, SQL_CLOSE );
}

MyResultSet* MyStatement::ExecuteQuery( const wxString& queryStr )
{
  int ret = SQLFreeStmt((SQLHSTMT)mHStmt, SQL_CLOSE);

  ret = SQLExecDirect( (SQLHSTMT)mHStmt, 
             (SQLCHAR*)queryStr.c_str(), queryStr.Length() );

  if ( ret != SQL_SUCCESS )
  {
    printf( "WARNING:: SQLExecDirect() failed executing statement:\n%s\n", queryStr.c_str() );

    return NULL;
  }

  MyResultSet* set = new MyResultSet( mHStmt );

  return set;
}

bool MyStatement::ExecuteDirectSQL( const wxString& queryStr, int* pRowsAffected )
{
  if ( pRowsAffected )

    *pRowsAffected = 0;

  int ret = SQLFreeStmt((SQLHSTMT)mHStmt, SQL_CLOSE);

  ret = SQLExecDirect( (SQLHSTMT)mHStmt, 
             (SQLCHAR*)queryStr.c_str(), queryStr.Length() );

  if ( ret != SQL_SUCCESS )
  {
    printf( "WARNING:: SQLExecDirect() failed executing statement\n%s\n", queryStr.c_str() );

    return NULL;
  }

  return TRUE;
}
