/////////////////////////////////////////////////////////////////////////////
// Name:        odbc.cpp
// Purpose:     ODBC implementation
// Author:      Julian Smart, Olaf Klein (oklein@smallo.ruhr.de),
//              Patrick Halke (patrick@zaphod.ruhr.de)
// Modified by:
// Created:     04/01/98
// RCS-ID:      $Id: odbc.cpp,v 1.11 1999/12/14 23:42:40 VS Exp $
// Copyright:   (c) Julian Smart and Markus Holzem
// Licence:     wxWindows license
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
#pragma implementation "odbc.h"
#endif

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

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#include "wx/defs.h"

// this code is old and shouldn't be used - use the new ODBC classes in db.cpp
// and dbtable.cpp instead
#define wxUSE_OLD_ODBC 0

#if wxUSE_OLD_ODBC

#ifdef __VISUALC__
    #pragma warning(disable:4706)   // assignment within conditional expression
#endif // VC++

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

#include "wx/string.h"
#include "wx/odbc.h"

#include <math.h>
#include <stdlib.h>

#if defined(__WXMSW__) && !defined(__WIN32__)
#include <print.h>
#endif

HENV wxDatabase::hEnv = 0;
int wxDatabase::refCount = 0;

IMPLEMENT_DYNAMIC_CLASS(wxDatabase, wxObject)
IMPLEMENT_DYNAMIC_CLASS(wxQueryCol, wxObject)
IMPLEMENT_DYNAMIC_CLASS(wxQueryField, wxObject)
IMPLEMENT_DYNAMIC_CLASS(wxRecordSet, wxObject)

wxDatabase::wxDatabase(void)
{
  hDBC = 0;
  username = NULL;
  password = NULL;
  datasource = NULL;
  dbname = NULL;
  connectstring = NULL;
  isOpen = FALSE;
  refCount ++;
  retcode = 0;
  username = NULL;
  password = NULL;
  err_occured = FALSE;

  memset(sqlstate, 0, sizeof sqlstate);
  memset(errmsg, 0, sizeof errmsg);
  
  if (hEnv == 0)
  {
    retcode = SQLAllocEnv(&hEnv);

    if (retcode != SQL_SUCCESS)
      hEnv = 0;
  }
}

wxDatabase::~wxDatabase(void)
{
  Close();
  DeleteRecordSets(); // Added JACS

  if (connectstring)
    delete[] connectstring;
  if (datasource)
    delete[] datasource;
  if (username)
    delete[] username;
  if (password)
    delete[] password;
  if (dbname)
    delete[] dbname;
  
  refCount --;
  if (!refCount && hEnv)
  {
    retcode = SQLFreeEnv(hEnv);
    hEnv = 0; // JACS 17/6
  }
}

void wxDatabase::ErrorSnapshot(HSTMT hstmt)
{
  SWORD len = 0; // JACS: sometimes doesn't get filled in by SQLError.
  
  err_occured = TRUE;
  SQLError(hEnv, hDBC, hstmt, (unsigned char *)sqlstate, &nat_err, (unsigned char *)errmsg, SQL_MAX_MESSAGE_LENGTH-1, &len);
  errmsg[len] = '\0';
}

bool wxDatabase::ErrorOccured(void) 
{
  return err_occured;
}

char* wxDatabase::GetErrorMessage(void)
{
  return errmsg;
}

long wxDatabase::GetErrorNumber(void) 
{
  return nat_err;
}

char* wxDatabase::GetErrorClass(void) {
  return sqlstate;
}

bool wxDatabase::Open(char *thedatasource, bool WXUNUSED(exclusive), 
  bool WXUNUSED(readOnly), char *username, char *password)
{
  err_occured = FALSE;
  
  if (isOpen)
    return FALSE;
  
  SetUsername(username);
  SetPassword(password);
  SetDataSource(thedatasource);
  
  if (!hEnv)
    return FALSE;

  retcode = SQLAllocConnect(hEnv, &hDBC);
  if (retcode != SQL_SUCCESS) {
    hDBC = 0;
    return FALSE;
  }
  
  retcode = SQLConnect(hDBC, (UCHAR FAR*)thedatasource, strlen(thedatasource), (UCHAR FAR*)username, strlen(username),
		       (UCHAR FAR*)password, strlen(password));
  
  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO) {
    ErrorSnapshot();
    return FALSE;
  }

  isOpen = TRUE;
  
  return TRUE;
}

bool wxDatabase::Close(void)
{
  // JACS: make sure the record set statements are all released.
  ResetRecordSets();
  
  err_occured = FALSE;
  if (hDBC != 0)
  {
    retcode = SQLDisconnect(hDBC);

    if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO) {
      ErrorSnapshot();      
      return FALSE;
    }

    retcode = SQLFreeConnect(hDBC);

    hDBC = 0;
    isOpen = FALSE;
    
    return TRUE;
  }
  
  return FALSE;
}

// Database attributes
char *wxDatabase::GetDatabaseName(void)
{
  err_occured = FALSE;
  if (hDBC == 0)
    return NULL;
    
  char nameBuf[400];
  int nameSize = 0;

  retcode = SQLGetInfo(hDBC, SQL_DATABASE_NAME, (unsigned char*)nameBuf, sizeof(nameBuf), (short *)&nameSize);

  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
    return NULL;

  delete[] dbname;
  dbname = NULL;
  
  if (nameSize > 0)
  {
    dbname = copystring(nameBuf);
    return dbname;
  }
  else
    return NULL;
}
  
bool wxDatabase::CanUpdate(void)
{
  return FALSE;
}

bool wxDatabase::CanTransact(void)
{
  return FALSE;
}

bool wxDatabase::InWaitForDataSource(void)
{
  return FALSE;
}

void wxDatabase::SetLoginTimeout(long WXUNUSED(seconds))
{
}

void wxDatabase::SetQueryTimeout(long WXUNUSED(seconds))
{
}

void wxDatabase::SetSynchronousMode(bool WXUNUSED(synchronous))
{
}

// Database operations
bool wxDatabase::BeginTrans(void)
{
  return FALSE;
}

bool wxDatabase::CommitTrans(void)
{
  return FALSE;
}

bool wxDatabase::RollbackTrans(void)
{
  return FALSE;
}

void wxDatabase::Cancel(void)
{
}

// Overridables
void wxDatabase::OnSetOptions(wxRecordSet *WXUNUSED(recordSet))
{
}

void wxDatabase::OnWaitForDataSource(bool WXUNUSED(stillExecuting))
{
}

void wxDatabase::SetPassword(char *s)
{
  if (password)
    delete[] password;
  if (s)
  {
    password = copystring(s);
  }
  else
    password = NULL;
}

void wxDatabase::SetUsername(char *s)
{
  delete[] username;
  
  if (s)
    username = copystring(s);
  else
    username = NULL;
}

void wxDatabase::SetDataSource(char *s)
{
  delete[] datasource;
  
  if (s)
    datasource = copystring(s);
  else
    datasource = NULL;
}

/*
 * Added by JACS
 */

void wxDatabase::DeleteRecordSets(void)
{
  wxNode *node = recordSets.First();
  while (node)
  {
    wxNode *next = node->Next();
    wxRecordSet *rec = (wxRecordSet *)node->Data();
    delete rec;
    // The node is implicitly deleted by ~wxRecordSet
    node = next;
  }
}

void wxDatabase::ResetRecordSets(void)
{
  wxNode *node = recordSets.First();
  while (node)
  {
    wxRecordSet *rec = (wxRecordSet *)node->Data();
    rec->ReleaseHandle();

    node = node->Next();
  }
}

bool wxDatabase::GetInfo(long infoType, long *buf)
{
  short sz = 0;
  retcode = SQLGetInfo(hDBC, (UWORD)infoType, (unsigned char*)buf, sizeof(buf), &sz);
  
  if (retcode != SQL_ERROR)
    return TRUE;
  else
    return FALSE;
}

bool wxDatabase::GetInfo(long infoType, char *buf, int bufSize)
{
  if (bufSize == -1)
    bufSize = sizeof(buf);
    
  short sz = 0;
  retcode = SQLGetInfo(hDBC, (UWORD)infoType, (unsigned char*)buf, bufSize, &sz);
  
  if (retcode != SQL_ERROR)
    return TRUE;
  else
    return FALSE;
}

wxString wxDatabase::GetODBCVersionString(bool implementation)
{
  char buf[50];
  if (!implementation)
  {
#ifdef SQL_SPEC_MAJOR
    sprintf(buf, "%d%d.%d", (int)(SQL_SPEC_MAJOR/10), (int)(SQL_SPEC_MAJOR - (((int)(SQL_SPEC_MAJOR/10))*10)),
             SQL_SPEC_MINOR);
    return wxString(buf);
#else
    return wxString("00.00");
#endif
  }
  
  bool noDBC = FALSE;
  if (hDBC == 0)
  {
    noDBC = TRUE;
    retcode = SQLAllocConnect(hEnv, &hDBC);
    if (retcode != SQL_SUCCESS)
    {
      hDBC = 0;
      return wxString("00.00");
    }
  }

  int bufSize = sizeof(buf);
    
  short sz = 0;
  retcode = SQLGetInfo(hDBC, (UWORD)SQL_ODBC_VER, (unsigned char*)buf, bufSize, &sz);
  
  if (hDBC != 0 && noDBC)
  {
    retcode = SQLFreeConnect(hDBC);
    hDBC = 0;
  }

  if (retcode == SQL_ERROR)
    return wxString("");
  else
    return wxString(buf);
}

float wxDatabase::GetODBCVersionFloat(bool implementation)
{
  if (!implementation)
  {
#ifdef SQL_SPEC_MAJOR
    return (float)(SQL_SPEC_MAJOR + (SQL_SPEC_MINOR/100.0));
#else
    return 0.0;
#endif
  }

  bool noDBC = FALSE;
  if (hDBC == 0)
  {
    noDBC = TRUE;
    retcode = SQLAllocConnect(hEnv, &hDBC);
    if (retcode != SQL_SUCCESS)
    {
      hDBC = 0;
      return (float)0.0;
    }
  }

  char buf[50];
  int bufSize = sizeof(buf);
 
  short sz = 0;
  retcode = SQLGetInfo(hDBC, (UWORD)SQL_ODBC_VER, (unsigned char*)buf, bufSize, &sz);

  if (hDBC != 0 && noDBC)
  {
    retcode = SQLFreeConnect(hDBC);
    hDBC = 0;
  }

  if (retcode == SQL_ERROR)
    return 0.0;
  else
    return (float)atof(buf);
}

/*
 * wxRecordSet
 */
 
wxRecordSet::wxRecordSet(wxDatabase *db, int typ, int opt):
    cols(wxKEY_STRING)
{
  parentdb = db;
  hStmt = 0;
  nFields = 0;
  nParams = 0;
  recordFilter = NULL;
  sortString = NULL;
  retcode = 0;
  cursor = 0;
  tablename = NULL;
  nCols = 0;
  nRecords = 0;
  
  type = typ;
  options = opt;

  // Added JACS
  if (parentdb)
    parentdb->GetRecordSets().Append(this);
}

wxRecordSet::~wxRecordSet(void)
{
  ReleaseHandle();
  
  // JACS
  if (parentdb)
    parentdb->GetRecordSets().DeleteObject(this);
    
  if (recordFilter)
    delete[] recordFilter;
  if (sortString)
    delete[] sortString;
  if (tablename)
    delete[] tablename;
}

// If SQL is non-NULL, table and columns can be NULL.
bool wxRecordSet::BeginQuery(int WXUNUSED(openType), char *WXUNUSED(sql), int WXUNUSED(options))
{
  // Needs to construct an appropriate SQL statement. By default
  // (i.e. if table and columns are provided) then
  // SELECT <columns> FROM <table>
  // will be used.
  if (!parentdb)
    return FALSE;
  if (!parentdb->GetHDBC())
    return FALSE;
    
  if (hStmt)
  {
    retcode = SQLFreeStmt(hStmt, SQL_DROP);
    if (retcode == SQL_ERROR)
    {
      parentdb->ErrorSnapshot(hStmt);
      return FALSE;
    }
    hStmt = 0;
  }

  retcode = SQLAllocStmt(parentdb->GetHDBC(), &hStmt);
  if (retcode != SQL_SUCCESS)
    return FALSE;

  return TRUE;
}

bool wxRecordSet::Query(char *columns, char *table, char *filter)
{
  // Needs to construct an appropriate SQL statement. By default
  // (i.e. if table and columns are provided) then
  // SELECT <columns> FROM <table>
  // will be used.

  char* thetable = table ? table : tablename;
  
  if (!thetable)
    return FALSE;
  if (!columns)
    return FALSE;
    
  wxString query;

  query += "SELECT ";
  query += columns;
  query += " FROM ";
  query += thetable;

  if (filter)  {
    query += " WHERE ";
    query += filter;
  }
  retcode = SQLPrepare(hStmt, (UCHAR FAR *)query.GetData(), strlen(query.GetData()));
  if (retcode != SQL_SUCCESS) {
    parentdb->ErrorSnapshot(hStmt);
    return FALSE;
  }

  retcode = SQLExecute(hStmt);

  if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO) {
    parentdb->ErrorSnapshot(hStmt);
    return FALSE;
  }

  return TRUE;
}

bool wxRecordSet::EndQuery(void)
{
  return TRUE;
}

void wxRecordSet::FillVars(int recnum) {
  wxNode* node = cols.First();
  
  do {
    ((wxQueryCol*)node->Data())->FillVar(recnum);
  } while ((node = node->Next()));
}

bool wxRecordSet::GetResultSet(void)
{
//  long trash = SQL_NULL_DATA; // value added by JACS
  long trash;
               // contains the number of bytes transferred by SQLFetch()
               // who needs this ?
  wxNode *currow, *fetch, *curcol;
  
  retcode = SQLNumResultCols(hStmt, &nCols);

  if (!nCols)
    return TRUE;
  
  // delete old data first
  cols.DeleteContents(TRUE);
  cols.Clear();
  fetchbuf.DeleteContents(TRUE);
  fetchbuf.Clear();
  
  nRecords = 0;
  cursor = 0;

  int i;
  for (i=0; i<nCols; i++) {
    char name[512];
    short type, scale, nullable, namelen;
    unsigned long len;
    
    retcode = SQLDescribeCol(hStmt, i+1, (unsigned char *)name, 511, &namelen, &type, &len, &scale, &nullable);
    if (SQL_SUCCESS != retcode && SQL_SUCCESS_WITH_INFO != retcode)  {
      parentdb->ErrorSnapshot(hStmt);
      return FALSE;
    }

    wxQueryCol *col1 = new wxQueryCol;
    curcol = cols.Append(name, col1);
    col1->SetName(name);
    col1->SetType(type);
    col1->SetNullable((nullable != 0));

    wxQueryField *field1 = new wxQueryField;
    fetch = fetchbuf.Append(field1);
    field1->SetType(type);
    field1->SetSize(len);
    
    SQLBindCol(hStmt, i+1, SQL_C_BINARY, (unsigned char*)field1->GetData(), field1->GetSize(), &trash);
  }
  
  switch (type) {
    case wxOPEN_TYPE_SNAPSHOT:
    // get it all !
    // After we've done an SQLFetch, copy the data in the fetch buffer into
    // new fields, for each column.
    while (SQL_SUCCESS == (retcode = SQLFetch(hStmt)) || SQL_SUCCESS_WITH_INFO == retcode) {
      nRecords++;
      
      curcol = cols.First();
      fetch = fetchbuf.First();
      for (i=0; i<nCols; i++) {

        wxQueryField *fetchField = (wxQueryField *)fetch->Data();
        wxQueryCol *col = (wxQueryCol *)curcol->Data();
        wxQueryField *field = new wxQueryField;

	currow = col->fields.Append(field);
	
	field->SetType(fetchField->GetType());
	field->SetData(fetchField->GetData(), fetchField->GetSize());
        curcol = curcol->Next();
        fetchField->ClearData(); // Runs ok if this commented out and SetData commented out
	fetch = fetch->Next();
      }
    }
    // while loop should only be left, when no more data was found;
    // otherwise it seems, that there was an error
    if (SQL_NO_DATA_FOUND != retcode) {
      parentdb->ErrorSnapshot(hStmt);
      return FALSE;
    }
    break;
    case wxOPEN_TYPE_DYNASET:
    // get first record only
    if (SQL_SUCCESS == (retcode = SQLFetch(hStmt)) || retcode == SQL_SUCCESS_WITH_INFO) {
      nRecords = 1;  // TO DO! # of records in the ODBC result set should be set here.
      
      curcol = cols.First();
      fetch = fetchbuf.First();
      for (i=0; i<nCols; i++) {
	currow = ((wxQueryCol*)curcol->Data())->fields.Append(new wxQueryField);
	
	((wxQueryField*)currow->Data())->SetType(((wxQueryField*)fetch->Data())->GetType());
	((wxQueryField*)currow->Data())->SetData(((wxQueryField*)fetch->Data())->GetData(), ((wxQueryField*)fetch->Data())->GetSize());
        curcol = curcol->Next();
        ((wxQueryField*)fetch->Data())->ClearData();
	fetch = fetch->Next();
      }
    }
    if (SQL_NO_DATA_FOUND != retcode) {
      parentdb->ErrorSnapshot(hStmt);
      return FALSE;
    }
    break;
    default:
    return FALSE;
  }

  FillVars(0);
  
  return TRUE;
}

bool wxRecordSet::ExecuteSQL(char *sql)
{
  if (hStmt)
  {
    retcode = SQLFreeStmt(hStmt, SQL_DROP);
    if (retcode == SQL_ERROR)
    {
      parentdb->ErrorSnapshot(hStmt);
      return FALSE;
    }
    hStmt = NULL;
  }
  
  retcode = SQLAllocStmt(parentdb->GetHDBC(), &hStmt);
  
  if (SQL_SUCCESS != retcode) {
    parentdb->ErrorSnapshot(hStmt);
    return FALSE;
  }

  retcode = SQLExecDirect(hStmt, (UCHAR FAR*)sql, SQL_NTS);
  
  if (SQL_SUCCESS != retcode && SQL_SUCCESS_WITH_INFO != retcode)  {
    parentdb->ErrorSnapshot(hStmt);
    return FALSE;
  }

  return GetResultSet();
}

bool wxRecordSet::GetDataSources(void) {

  char *dsname = "Name", *dsdesc = "Description";
  char namebuf[64];
  char descbuf[512];
  short namelen, desclen;
  
  cursor = 0;
  
  // delete old data first
  cols.DeleteContents(TRUE);
  cols.Clear();
  nRecords = 0;

  // JACS This is a snapshot, not a dynaset.
  type = wxOPEN_TYPE_SNAPSHOT;
  
  wxNode *namecol, *desccol;
  
  namecol = cols.Append(dsname, new wxQueryCol);
  ((wxQueryCol*)namecol->Data())->SetName(dsname);
  ((wxQueryCol*)namecol->Data())->SetType(SQL_CHAR);
  desccol = cols.Append(dsdesc, new wxQueryCol);
  ((wxQueryCol*)desccol->Data())->SetName(dsdesc);
  ((wxQueryCol*)desccol->Data())->SetType(SQL_CHAR);
  
  retcode = SQLDataSources(parentdb->GetHENV(), SQL_FETCH_FIRST, (unsigned char *)namebuf, 63, &namelen, (unsigned char *)descbuf, 511, &desclen);
  while (SQL_SUCCESS == retcode || SQL_SUCCESS_WITH_INFO == retcode) {
    nRecords++;
    ((wxQueryCol*)namecol->Data())->AppendField(namebuf, namelen);
    ((wxQueryCol*)desccol->Data())->AppendField(descbuf, desclen);
    retcode = SQLDataSources(parentdb->GetHENV(), SQL_FETCH_NEXT, (unsigned char *)namebuf, 63, &namelen, (unsigned char *)descbuf, 511, &desclen);
  }

  if (SQL_SUCCESS != retcode && SQL_SUCCESS_WITH_INFO != retcode && SQL_NO_DATA_FOUND != retcode)  {
    parentdb->ErrorSnapshot();
    return FALSE;
  }

  cursor = 0;
  
  return TRUE;
}

// Attributes
void wxRecordSet::SetTableName(char* name) {
  delete[] tablename;
  tablename = NULL;
  
  if (name)
    tablename = copystring(name);
}

bool wxRecordSet::GetTables(void)
{
  if (hStmt)
  {
    retcode = SQLFreeStmt(hStmt, SQL_DROP);
    if (retcode == SQL_ERROR)
    {
      parentdb->ErrorSnapshot(hStmt);
      return FALSE;
    }
      
    hStmt = NULL;
  }
  
  retcode = SQLAllocStmt(parentdb->GetHDBC(), &hStmt);
  
  if (SQL_SUCCESS != retcode) {
    parentdb->ErrorSnapshot();
    return FALSE;
  }

  retcode = SQLTables(hStmt, NULL, 0, NULL, 0, NULL, 0, NULL, 0);
  
  if (SQL_SUCCESS != retcode && SQL_SUCCESS_WITH_INFO != retcode)  {
    parentdb->ErrorSnapshot(hStmt);
    return FALSE;
  }

  return GetResultSet();
}

bool wxRecordSet::GetColumns(char* table)
{
  char* name=NULL;
//  char* wildcard = "%";

  name = table ? table : tablename;
  
  if (!name)
    return FALSE;

  if (hStmt)
  {
    retcode = SQLFreeStmt(hStmt, SQL_DROP);
    if (retcode == SQL_ERROR)
    {
      parentdb->ErrorSnapshot(hStmt);
      return FALSE;
    }
    hStmt = NULL;
  }
  
  retcode = SQLAllocStmt(parentdb->GetHDBC(), &hStmt);
  
  if (SQL_SUCCESS != retcode) {
    parentdb->ErrorSnapshot();
    return FALSE;
  }

  //retcode = SQLColumns(hstmt, (unsigned char*)parentdb->GetDataSource(), strlen(parentdb->GetDataSource()), wildcard, 1, name, strlen(name), wildcard, 1);
  retcode = SQLColumns(hStmt, NULL, 0, NULL, 0, (unsigned char *)name, strlen(name), NULL, 0);
  
  if (SQL_SUCCESS != retcode && SQL_SUCCESS_WITH_INFO != retcode)  {
    parentdb->ErrorSnapshot(hStmt);
    return FALSE;
  }

  return GetResultSet();
}

// It is derived from previous GetColumns
bool wxRecordSet::GetPrimaryKeys(char* table)
{
  char* name=NULL;
//  char* wildcard = "%";

  name = table ? table : tablename;
  
  if (!name)
    return FALSE;

  if (hStmt)
  {
    retcode = SQLFreeStmt(hStmt, SQL_DROP);
    if (retcode == SQL_ERROR)
    {
      parentdb->ErrorSnapshot(hStmt);
      return FALSE;
    }
    hStmt = NULL;
  }
  
  retcode = SQLAllocStmt(parentdb->GetHDBC(), &hStmt);
  
  if (SQL_SUCCESS != retcode) {
    parentdb->ErrorSnapshot();
    return FALSE;
  }

  retcode = SQLPrimaryKeys(hStmt, NULL, 0, NULL, 0, (unsigned char *)name, SQL_NTS);  

  if (SQL_SUCCESS != retcode && SQL_SUCCESS_WITH_INFO != retcode)  {
    parentdb->ErrorSnapshot(hStmt);
    return FALSE;
  }

  return GetResultSet();
}

bool wxRecordSet::GetForeignKeys(char* PkTableName, char * FkTableName)
{
  char* Pkname=NULL;
  char* Fkname=NULL;
//  char* wildcard = "%";

// Try to disable situation: both PkTableName and FkTableName are NULL 
//   set Pkname from tablename
  if( !PkTableName && !FkTableName ) {
     Pkname = PkTableName ? PkTableName : tablename;
     Fkname = FkTableName ;
     if (!Pkname )
       return FALSE;
  } else {
     Pkname = PkTableName ;
     Fkname = FkTableName ;
  }

  if (hStmt)
  {
    retcode = SQLFreeStmt(hStmt, SQL_DROP);
    if (retcode == SQL_ERROR)
    {
      parentdb->ErrorSnapshot(hStmt);
      return FALSE;
    }
    hStmt = NULL;
  }
  
  retcode = SQLAllocStmt(parentdb->GetHDBC(), &hStmt);
  
  if (SQL_SUCCESS != retcode) {
    parentdb->ErrorSnapshot();
    return FALSE;
  }

  retcode = SQLForeignKeys(hStmt, NULL, 0, NULL, 0, (unsigned char *)Pkname,
    (Pkname ? SQL_NTS : 0), NULL, 0, NULL, 0, (unsigned char *)Fkname ,(Fkname ?SQL_NTS : 0) );  

  if (SQL_SUCCESS != retcode && SQL_SUCCESS_WITH_INFO != retcode)  {
    parentdb->ErrorSnapshot(hStmt);
    return FALSE;
  }

  return GetResultSet();
}

long wxRecordSet::GetNumberRecords(void) 
{
  return nRecords;
}

long wxRecordSet::GetNumberCols(void)
{
  return nCols;
}

char* wxRecordSet::GetColName(int col)
{
  wxNode* node = cols.Nth(col);
    
  if (!node)
    return NULL;
    
  return ((wxQueryCol*)node->Data())->GetName();
}

short wxRecordSet::GetColType(int col)
{
  wxNode* node = cols.Nth(col);
    
  if (!node)
    return SQL_TYPE_NULL;
    
  return ((wxQueryCol*)node->Data())->GetType();
}

short wxRecordSet::GetColType(const char *col)
{
  wxNode* node = cols.Find(col);
    
  if (!node)
    return SQL_TYPE_NULL;
    
  return ((wxQueryCol*)node->Data())->GetType();
}

bool wxRecordSet::GetFieldData(int col, int type, void* data)
{
  wxNode* node = cols.Nth(col);

  if (!node)
    return FALSE;
  
  if (((wxQueryCol*)node->Data())->GetType() != type)
    return FALSE;

  void* src = ((wxQueryCol*)node->Data())->GetData(cursor);

  if (!src)
    return FALSE;
  
  memcpy(data, src, ((wxQueryCol*)node->Data())->GetSize(cursor));
  
  return TRUE;
}

bool wxRecordSet::GetFieldData(const char* name, int type, void *data)
{
  wxNode* node = cols.Find(name);

  if (!node)
    return FALSE;
  
  if (((wxQueryCol*)node->Data())->GetType() != type)
    return FALSE;

  void* src = ((wxQueryCol*)node->Data())->GetData(cursor);

  if (!src)
    return FALSE;
  
  memcpy(data, src, ((wxQueryCol*)node->Data())->GetSize(cursor));
  
  return TRUE;
}

void* wxRecordSet::GetFieldDataPtr(int col, int type)
{
  wxNode* node = cols.Nth(col);

  if (!node)
    return NULL;
  
  if (((wxQueryCol*)node->Data())->GetType() != type)
    return NULL;

  return ((wxQueryCol*)node->Data())->GetData(cursor);
}

void* wxRecordSet::GetFieldDataPtr(const char* name, int type)
{
  wxNode* node = cols.Find(name);

  if (!node)
    return NULL;
  
  if (((wxQueryCol*)node->Data())->GetType() != type)
    return NULL;

  return ((wxQueryCol*)node->Data())->GetData(cursor);
}

void* wxRecordSet::BindVar(int col, void* var, long size) {
  wxNode* node = cols.Nth(col);
  
  if (!node)
    return NULL;
  
  return ((wxQueryCol*)node->Data())->BindVar(var, size);
}

void* wxRecordSet::BindVar(const char* name, void* var, long size) {
  wxNode* node = cols.Find(name);
  
  if (!node)
    return NULL;
  
  return ((wxQueryCol*)node->Data())->BindVar(var, size);
}

void wxRecordSet::SetType(int typ) {
  type = typ;
}

int wxRecordSet::GetType(void) {
  return type;
}

void wxRecordSet::SetOptions(int opts) {
  options = opts;
}

int wxRecordSet::GetOptions(void) {
  return options;
}

bool wxRecordSet::CanAppend(void)
{
  return FALSE;
}

bool wxRecordSet::CanRestart(void)
{
  return FALSE;
}

bool wxRecordSet::CanScroll(void)
{
  return FALSE;
}

bool wxRecordSet::CanTransact(void)
{
  return FALSE;
}

bool wxRecordSet::CanUpdate(void)
{
  return FALSE;
}

long wxRecordSet::GetCurrentRecord(void)
{
  return -1L;
}

bool wxRecordSet::RecordCountFinal(void)
{
  return FALSE;
}

char* wxRecordSet::GetTableName(void)
{
  return tablename;
}

char *wxRecordSet::GetSQL(void)
{
  return NULL;
}

bool wxRecordSet::IsOpen(void)
{
  return parentdb->IsOpen();
}

bool wxRecordSet::IsBOF(void)
{
  return cursor < 0;
}

bool wxRecordSet::IsEOF(void)
{
  return cursor >= nRecords;
}

bool wxRecordSet::IsDeleted(void)
{
  return FALSE;
}

// Update operations
void wxRecordSet::AddNew(void)
{
}

bool wxRecordSet::Delete(void)
{
  return FALSE;
}

void wxRecordSet::Edit(void)
{
}

bool wxRecordSet::Update(void)
{
  return FALSE;
}

// Record navigation
bool wxRecordSet::Move(long rows)
{
  if (!nRecords) {
    cursor = -1;
    return FALSE;
  }
  
  switch (type) {
    case wxOPEN_TYPE_SNAPSHOT:
    cursor += (int)rows;
    if (cursor < 0) {
      cursor = -1;
      return FALSE;
    }
    if (cursor > nRecords-1) {
      cursor = nRecords;
      return FALSE;
    }
    return TRUE;
      
    case wxOPEN_TYPE_DYNASET:
    return FALSE;
    default:
    return FALSE;
  }
}

bool wxRecordSet::GoTo(long row)
{
  if (!nRecords) {
    cursor = -1;
    return FALSE;
  }
  
  switch (type) {
    case wxOPEN_TYPE_SNAPSHOT:
    cursor = (int)row;
    if (cursor < 0) {
      cursor = -1;
      return FALSE;
    }
    if (cursor > nRecords-1) {
      cursor = nRecords;
      return FALSE;
    }
    return TRUE;
      
    case wxOPEN_TYPE_DYNASET:
    return FALSE;
    default:
    return FALSE;
  }
}

bool wxRecordSet::MoveFirst(void)
{
  if (!nRecords) {
    cursor = -1;
    return FALSE;
  }
  
  switch (type) {
    case wxOPEN_TYPE_SNAPSHOT:
    cursor = 0;
    return TRUE;
    
    case wxOPEN_TYPE_DYNASET:
    return FALSE;
    default:
    return FALSE;
  }
}

bool wxRecordSet::MoveLast(void)
{
  if (!nRecords) {
    cursor = -1;
    return FALSE;
  }
  
  switch (type) {
    case wxOPEN_TYPE_SNAPSHOT:
    cursor = nRecords-1;
    return TRUE;
    
    case wxOPEN_TYPE_DYNASET:
    return FALSE;
    default:
    return FALSE;
  }
}

bool wxRecordSet::MoveNext(void)
{
  if (!nRecords) {
    cursor = -1;
    return FALSE;
  }
  
  switch (type) {
    case wxOPEN_TYPE_SNAPSHOT:
    cursor++;
    if (cursor >= nRecords) {
      cursor = nRecords;
      return FALSE;
    }
    return TRUE;
    
    case wxOPEN_TYPE_DYNASET:
    return FALSE;
    default:
    return FALSE;
  }
}

bool wxRecordSet::MovePrev(void)
{
  if (!nRecords) {
    cursor = -1;
    return FALSE;
  }
  
  switch (type) {
    case wxOPEN_TYPE_SNAPSHOT:
    cursor--;
    if (cursor < 0) {
      cursor = 0;
      return FALSE;
    }
    return TRUE;

    case wxOPEN_TYPE_DYNASET:
    return FALSE;
    default:
    return FALSE;
  }
}

// Others
void wxRecordSet::Cancel(void)
{
}

bool wxRecordSet::IsFieldDirty(int col)
{
  wxNode* node = cols.Nth(col);
    
  if (!node)
    return FALSE;
    
  return ((wxQueryCol*)node->Data())->IsFieldDirty(cursor);
}

bool wxRecordSet::IsFieldDirty(const char* name)
{
  wxNode* node = cols.Find(name);
    
  if (!node)
    return FALSE;
    
  return ((wxQueryCol*)node->Data())->IsFieldDirty(cursor);
}

bool wxRecordSet::IsFieldNull(int col)
{
  wxNode* node = cols.Nth(col);
    
  if (!node)
    return TRUE;
    
  return NULL != ((wxQueryCol*)node->Data())->GetData(cursor);
}

bool wxRecordSet::IsFieldNull(const char* name)
{
  wxNode* node = cols.Find(name);
    
  if (!node)
    return TRUE;
    
  return NULL != ((wxQueryCol*)node->Data())->GetData(cursor);
}

bool wxRecordSet::IsColNullable(int col)
{
  wxNode* node = cols.Nth(col);
    
  if (!node)
    return FALSE;
    
  return ((wxQueryCol*)node->Data())->IsNullable();
}

bool wxRecordSet::IsColNullable(const char* name)
{
  wxNode* node = cols.Find(name);
    
  if (!node)
    return FALSE;
    
  return ((wxQueryCol*)node->Data())->IsNullable();
}

bool wxRecordSet::Requery(void)
{
  return FALSE;
}

void wxRecordSet::SetFieldDirty(int col, bool dirty)
{
  wxNode* node = cols.Nth(col);
    
  if (!node)
    return;
    
  ((wxQueryCol*)node->Data())->SetFieldDirty(cursor, dirty);
}

void wxRecordSet::SetFieldDirty(const char* name, bool dirty)
{
  wxNode* node = cols.Find(name);
    
  if (!node)
    return;
    
  ((wxQueryCol*)node->Data())->SetFieldDirty(cursor, dirty);
}

void wxRecordSet::SetFieldNull(void *WXUNUSED(p), bool WXUNUSED(isNull))
{
}
   
// Overridables
char *wxRecordSet::GetDefaultConnect(void)
{
  return NULL;
}

char *wxRecordSet::GetDefaultSQL(void)
{
  return NULL;
}

void wxRecordSet::SetDefaultSQL(char *s)
{
  delete[] defaultSQL;
  
  if (s)
    defaultSQL = copystring(s);
  else
    defaultSQL = NULL;
}

// Build SQL query from column specification
bool wxRecordSet::ConstructDefaultSQL(void)
{
//  if (queryCols.Number() == 0)
    return FALSE;
}

bool wxRecordSet::ReleaseHandle(void)
{
  if (hStmt)
  {
    retcode = SQLFreeStmt(hStmt, SQL_DROP);
    if (retcode == SQL_ERROR)
    {
      if (parentdb)
        parentdb->ErrorSnapshot(hStmt);
      return FALSE;
    }
    hStmt = 0;
  }
  return TRUE;
}

wxQueryCol::wxQueryCol(void) {
//  __type = wxTYPE_QUERYCOL;
  name = NULL;
  type = SQL_TYPE_NULL;
  nullable = FALSE;
  var = NULL;
  varsize = 0;
}

wxQueryCol::~wxQueryCol(void) {
  // delete all data
  fields.DeleteContents(TRUE);
  fields.Clear();

  if (name)
    delete[] name;
}

void wxQueryCol::SetName(char* n) {
  name = new char[strlen(n)+1];
  strcpy(name, n);
}

bool wxQueryCol::SetData(int row, void* buf, long len) {
  wxNode* node = fields.Nth(row);
  
  if (!node)
    return FALSE;
  
  return ((wxQueryField*)node->Data())->SetData(buf, len);
}

void wxQueryCol::SetFieldDirty(int row, bool dirty) {
  wxNode* node = fields.Nth(row);
  
  if (!node)
    return;
  
  ((wxQueryField*)node->Data())->SetDirty(dirty);
}

void wxQueryCol::AppendField(void* buf, long len) {
  wxNode* node = fields.Append(new wxQueryField);
  ((wxQueryField*)node->Data())->SetType(type);
  ((wxQueryField*)node->Data())->SetData(buf, len);
}

void wxQueryCol::SetType(short t) {
  type = t;
}

void* wxQueryCol::BindVar(void* v, long s) {
  void* oldvar = var;
  
  var = v;
  varsize = s;
  
  return oldvar;
}

void wxQueryCol::FillVar(int recnum) {
  if (!var)
    return;
  
  wxNode* node = fields.Nth(recnum);
  
  if (!node)
    return;

  long actsize = ((wxQueryField*)node->Data())->GetSize();
  if (actsize > varsize)
    actsize = varsize;

  memcpy(var, ((wxQueryField*)node->Data())->GetData(), actsize);
}

void wxQueryCol::SetNullable(bool n) {
  nullable = n;
}
  
char* wxQueryCol::GetName(void) {
  return name;
}

short wxQueryCol::GetType(void)  {
  return type;
}

bool wxQueryCol::IsNullable(void) {
  return nullable;
}


bool wxQueryCol::IsFieldDirty(int row) {
  wxNode* node = fields.Nth(row);
  
  if (!node)
    return FALSE;
  
  return ((wxQueryField*)node->Data())->IsDirty();
}

void* wxQueryCol::GetData(int row) {
  wxNode* node = fields.Nth(row);
  
  if (!node)
    return NULL;
  
  return ((wxQueryField*)node->Data())->GetData();
}

long wxQueryCol::GetSize(int row) {
  wxNode* node = fields.Nth(row);
  
  if (!node)
    return 0;
  
  return ((wxQueryField*)node->Data())->GetSize();
}

wxQueryField::wxQueryField(void) {
//  __type = wxTYPE_QUERYROW;
  data = NULL;
  type = SQL_TYPE_NULL;
  size = 0;
  dirty = FALSE;
}

wxQueryField::~wxQueryField(void) {
  switch (type)
  {
    case SQL_NUMERIC:
    case SQL_DECIMAL:
    case SQL_CHAR:
    case SQL_VARCHAR:
      if (data) // JACS
        delete[] (char*)data;
      break;
    case SQL_INTEGER:
      if (data) // JACS
        delete (long*)data;
      break;
    case SQL_SMALLINT:
      if (data)
        delete (short*)data;
      break;
    case SQL_FLOAT:
    case SQL_DOUBLE:
      if (data)
        delete (double*)data;
      break;
    case SQL_REAL:
      if (data)
        delete (float*)data;
      break;
    case SQL_TIME:
      if (data)
        delete (TIME_STRUCT *)data;
      break;
    case SQL_DATE:
      if (data)
        delete (DATE_STRUCT *)data;
      break;
    case SQL_TIMESTAMP:
      if (data)
        delete (TIMESTAMP_STRUCT *)data;
      break;
  }
}

bool wxQueryField::AllocData(void) {
  switch (type)
  {
    case SQL_NUMERIC:
    case SQL_DECIMAL:
    case SQL_CHAR:
    case SQL_VARCHAR:
    {
      if (data) // JACS
        delete[] (char*)data;
      if ((data = new char[size+1]))
      {
        char *str = (char *)data;
        int i;
        for (i = 0; i < size; i++)
          str[i] = 0;
//      memset(data, 0, size+1);
      }
      break;
    }
    case SQL_INTEGER:
    {
      if (data) // JACS
        delete (long*)data;
      if ((data = new long))
        *(long*)data = 0L;
      break;
    }
    case SQL_SMALLINT:
    {
      if (data)
        delete (short*)data;
      if ((data = new short))
        *(short*)data = 0;
      break;
    }
    case SQL_FLOAT:
    case SQL_DOUBLE:
    {
      if (data)
        delete (double*)data;
      if ((data = new double))
        *(double*)data = 0;
      break;
    }
    case SQL_REAL:
    {
      if (data)
        delete (float*)data;
      if ((data = new float))
        *(float*)data = (float)0;
      break;
    }
    case SQL_TIME:
    {
      if (data)
        delete (TIME_STRUCT *)data;
      data = new TIME_STRUCT;
      memset(data, 0, sizeof(TIME_STRUCT));
      break;
    }
    case SQL_DATE:
    {
      if (data)
        delete (DATE_STRUCT *)data;
      data = new DATE_STRUCT;
      memset(data, 0, sizeof(DATE_STRUCT));
      break;
    }
    case SQL_TIMESTAMP:
    {
      if (data)
        delete (TIMESTAMP_STRUCT *)data;
      data = new TIMESTAMP_STRUCT;
      memset(data, 0, sizeof(TIMESTAMP_STRUCT));
      break;
    }
    default:
      return FALSE;
  }
  
  return TRUE;
}

bool wxQueryField::SetData(void* d, long s) {
  size = s;
  if (AllocData() && d)
  {
//    memcpy(data, d, s);
    switch (type)
    {
      case SQL_NUMERIC:
      case SQL_DECIMAL:
      case SQL_CHAR:
      case SQL_VARCHAR:
      {
        char *str = (char *)data;
        int i;
        for (i = 0; i < size; i++)
          str[i] = 0;
          
        strncpy(str, (char *)d, (int)size);
        str[size] = 0;
        break;
      }
      case SQL_INTEGER:
      {
        *(long*)data = *((long *)d);
        break;
      }
      case SQL_SMALLINT:
      {
        *(short*)data = *((short*)d);
        break;
      }
      case SQL_FLOAT:
      case SQL_DOUBLE:
      {
        *(double*)data = *((double*)d);
        break;
      }
      case SQL_REAL:
      {
        *(float*)data = *((float*)d);
        break;
      }
      case SQL_TIME:
      {
        *(TIME_STRUCT *)data = *((TIME_STRUCT*)d);
        break;
      }
      case SQL_TIMESTAMP:
      {
        *(TIMESTAMP_STRUCT *)data = *((TIMESTAMP_STRUCT*)d);
        break;
      }
      case SQL_DATE:
      {
        *(DATE_STRUCT *)data = *((DATE_STRUCT*)d);
        break;
      }
      default:
        return FALSE;
    }
    return TRUE;
  }
  return FALSE;
}

void wxQueryField::ClearData(void) {
  if (data)
  {
  //    memset(data, 0, size);
    switch (type)
    {
      case SQL_NUMERIC:
      case SQL_DECIMAL:
      case SQL_CHAR:
      case SQL_VARCHAR:
      {
        char *str = (char *)data;
        int i;
        for (i = 0; i < size; i++)
          str[i] = 0;
        break;
      }
      case SQL_INTEGER:
      {
        *(long*)data = 0L;
        break;
      }
      case SQL_SMALLINT:
      {
        *(short*)data = 0;
        break;
      }
      case SQL_FLOAT:
      case SQL_DOUBLE:
      {
        *(double*)data = (double)0.0;
        break;
      }
      case SQL_REAL:
      {
        *(float*)data = (float)0.0;
        break;
      }
      case SQL_TIME:
      {
        memset(data, 0, sizeof(TIME_STRUCT));
        break;
      }
      case SQL_DATE:
      {
        memset(data, 0, sizeof(DATE_STRUCT));
        break;
      }
      case SQL_TIMESTAMP:
      {
        memset(data, 0, sizeof(TIMESTAMP_STRUCT));
        break;
      }
      default:
        return;
    }
  }
}

void wxQueryField::SetDirty(bool d) {
  dirty = d;
}

void wxQueryField::SetType(short t) {
  type = t;
}

void wxQueryField::SetSize(long s) {
  size = s;
  AllocData();
}

void* wxQueryField::GetData(void) {
  return data;
}

short wxQueryField::GetType(void)  {
  return type;
}

long wxQueryField::GetSize(void) {
  return size;
}

bool wxQueryField::IsDirty(void) {
  return dirty;
}

#ifdef __VISUALC__
    #pragma warning(default:4706)   // assignment within conditional expression
#endif // VC++

#endif // wxUSE_OLD_ODBC
