#include <windows.h>
#include <stdio.h>
#include <sqltypes.h>
#include <sql.h>
#include <sqlext.h>
#include <odbcinst.h>
#include "cdefs.h"
#include "mem.h"

cint MaxNullString = 254;
extern object_Undefined;
int MinBuffer = 16384;
int MaxBuffer = 32768;
 
/* Utility Functions */

cint Success(SQLRETURN retcode) {
  return (retcode == SQL_SUCCESS) | (retcode == SQL_SUCCESS_WITH_INFO);
}

cstring Unspace(cstring temp) {
  while (temp[strlen(temp)-1] == ' ') { 
    temp[strlen(temp)-1] = '\0';
  }

  return temp;
}

/* Blob Functions */

typedef struct tagBlobRec {
  void* GHandle;
  SDWORD GSize;
} BlobRec;

void* Lock(void* AHandle) {

  void* APtr;

  //Lock Global Memory
  APtr = GlobalLock(AHandle);
  return APtr;  //NULL = error
}

void Unlock(void* AHandle) {
  //Unlock Global Memory
  GlobalUnlock(AHandle);
}

long GetSize(void* AHandle) {
  //Get Global Memory Size
  return GlobalSize(AHandle);
}

void* SetSize(void* AHandle,
             long ASize) {

  if (ASize > 0) {
    //ReAllocate Global Memory
    if (ASize < MinBuffer) {
      ASize = MinBuffer;
    }
    AHandle = GlobalReAlloc(AHandle, ASize, 0);
    return AHandle;  //0 = error
  }
}

void* Create(long ASize) {

  void* AHandle;

  //Allocate Global Memory
  if (ASize < MinBuffer) {
    ASize = MinBuffer;
  }
  AHandle = GlobalAlloc(GMEM_MOVEABLE, ASize);
  return AHandle;  //0 = error
}

void Destroy(void* AHandle) {

  if (GetSize(AHandle) > 0) {
    //Free Global Memory
    GlobalFree(AHandle);
  }
}

SQLRETURN FetchBlob(HSTMT hstmt,
                    SQLUSMALLINT icol,
                    SQLSMALLINT ColType,
                    SQLINTEGER* ColSize,
                    void** blobhandle) {

  BlobRec* temp;
  long GSize, BSize, TotalSize, GCursor;
  void *OffsetPtr;

  void *FHandle, *FPtr;
  SQLRETURN retcode;
  SQLINTEGER pcbValue;
  
  //Allocate Global Memory
  GSize = MinBuffer;
  FHandle = Create(GSize);
  FPtr = Lock(FHandle);
  BSize = GSize-1;

  //Determine Total Size
  if (ColType == SQL_CHAR) {
    TotalSize = BSize+1;
  } 
  else {
    TotalSize = BSize;
  }

  retcode = SQLGetData(hstmt, icol, ColType, FPtr, TotalSize, &pcbValue);
  *ColSize = pcbValue;
  if (!Success(retcode)) {
    goto ErrHandler;  //error
  }

  if (*ColSize != SQL_NULL_DATA) {
  
    //Resize Global Memory
    if (*ColSize > GSize) {
      GSize = *ColSize+1;
      Unlock(FHandle);
      FHandle = SetSize(FHandle, GSize);
      FPtr = Lock(FHandle);
    }

    //Retrieve Blob
    if (retcode == SQL_SUCCESS_WITH_INFO) {
      GCursor = BSize;
      
      while (retcode == SQL_SUCCESS_WITH_INFO) {
        if ((*ColSize - GCursor) > MaxBuffer) {
          BSize = MaxBuffer;
        }
        else {
          BSize = *ColSize - GCursor;  //or: pcbValue - BSize
        } 
   
        //Determine Total Size
        if (ColType == SQL_CHAR) {
          TotalSize = BSize+1;
        }
        else {
          TotalSize = BSize;
        }

        OffsetPtr = (void*)((int)FPtr + GCursor);
        retcode = SQLGetData(hstmt, icol, ColType, OffsetPtr, TotalSize, &pcbValue);
        GCursor += BSize;
      }
    }
  }

ErrHandler:
  Unlock(FHandle);

  temp = mem_malloc(sizeof(BlobRec));
  temp->GHandle = FHandle;
  temp->GSize = *ColSize;

  *blobhandle = temp;
  return SQL_SUCCESS;
}

cint odbc_String_as_Blob(cstring blobstring) {
  cptr result = mem_malloc(2*4);
  BlobRec* iblobhandle;
  cstring iblobstring;
  void* GPtr;

  iblobstring = (cstring)object_as_string(blobstring);
  iblobhandle = mem_malloc(sizeof(BlobRec));
  iblobhandle->GHandle = Create(strlen(iblobstring)+1);
  iblobhandle->GSize = strlen(iblobstring)+1;

  GPtr = Lock(iblobhandle->GHandle);
  //blob <- string
  strcpy(GPtr, iblobstring);
  Unlock(iblobhandle->GHandle);

  result[0] = object_integer_from(iblobhandle);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_Blob_as_String(cint blobhandle) {
  cptr result = mem_malloc(2*4);
  BlobRec* iblobhandle;
  cstring iblobstring;
  void* GPtr;

  iblobhandle = (BlobRec*)object_as_int(blobhandle);
  iblobstring = mem_malloc(iblobhandle->GSize);

  GPtr = Lock(iblobhandle->GHandle);
  //string <- blob
  strcpy(iblobstring, GPtr);
  Unlock(iblobhandle->GHandle);
  
  result[0] = object_string_from(iblobstring);
  result[1] = 0;

  return object_list_from(result);
}

/* Connecting to a Data Source */

cint odbc_SQLAllocEnv() {
  cptr result = mem_malloc(3*4);
  SQLRETURN retcode;
  HENV henv;

  retcode = SQLAllocEnv(&henv);

  result[0] = object_integer_from((cint)retcode);
  result[1] = object_integer_from((cint)henv);
  result[2] = 0;

  return object_list_from(result);
}

cint odbc_SQLAllocConnect(cint henv) {
  cptr result = mem_malloc(3*4);
  SQLRETURN retcode;
  HENV ihenv;
  HDBC hdbc;

  ihenv = (HENV)object_as_int(henv);
  retcode = SQLAllocConnect(ihenv, &hdbc);

  result[0] = object_integer_from((cint)retcode);
  result[1] = object_integer_from((cint)hdbc);
  result[2] = 0;

  return object_list_from(result);
}

cint odbc_SQLConnect(cint hdbc,
                     cstring szDSN,
                     cstring szUID,
                     cstring szAuthStr) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HDBC ihdbc;
  cstring iszDSN;
  SWORD cbDSN;
  cstring iszUID;
  SWORD cbUID;
  cstring iszAuthStr;
  SWORD cbAuthStr;
  
  ihdbc = (HDBC)object_as_int(hdbc);
  iszDSN = (cstring)object_as_string(szDSN);
  cbDSN = strlen(iszDSN);
  iszUID = (cstring)object_as_string(szUID);
  cbUID = strlen(iszUID);
  iszAuthStr = (cstring)object_as_string(szAuthStr);
  cbAuthStr = strlen(iszAuthStr);
  retcode = SQLConnect(ihdbc, iszDSN, cbDSN, iszUID, cbUID, iszAuthStr, cbAuthStr);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

/* Setting and Retrieving Driver Options */

cint odbc_SQLSetConnectOption(cint hdbc,
                              cint fOption,
                              cint vParam) {
  cptr result = mem_malloc(2*4);  
  SQLRETURN retcode;
  HDBC ihdbc;
  UWORD ifOption;
  UDWORD ivParam;

  ihdbc = (HDBC)object_as_int(hdbc);
  ifOption = object_as_int(fOption);
  ivParam = object_as_int(vParam);
  retcode = SQLSetConnectOption(ihdbc, ifOption, ivParam);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_SQLSetStmtOption(cint hstmt,
                           cint fOption,
                           cint vParam) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HSTMT ihstmt;
  UWORD ifOption;
  UDWORD ivParam;

  ihstmt = (HSTMT)object_as_int(hstmt);
  ifOption = object_as_int(fOption);
  ivParam = object_as_int(vParam);

  retcode = SQLSetStmtOption(ihstmt, ifOption, ivParam);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

/* Preparing SQL Requests */

cint odbc_SQLAllocStmt(cint hdbc) {
  cptr result = mem_malloc(3*4);
  SQLRETURN retcode;
  HDBC ihdbc;
  HSTMT hstmt;

  ihdbc = (HDBC)object_as_int(hdbc);
  retcode = SQLAllocStmt(ihdbc, &hstmt);
  
  result[0] = object_integer_from((cint)retcode);
  result[1] = object_integer_from((cint)hstmt);
  result[2] = 0;

  return object_list_from(result);
}

typedef struct tagParamRec {
  UWORD ipar;
  SWORD fCType;
  PTR rgbValue;
  SDWORD cbValue;
  
  cptr Next;
} ParamRec;

typedef struct tagColRec {
  SWORD fCType;
  PTR rgbValue;
  SDWORD cbValue;

  cptr Next;
} ColRec;

SQLRETURN FreeParams(HSTMT hstmt,
                     ParamRec* plist) {
  SQLRETURN retcode;
  ParamRec* temp;

  while (plist != NULL) {
    temp = (ParamRec*)plist->Next;
    free(plist->rgbValue);
    free(plist);
    plist = temp;
	}

  retcode = SQLFreeStmt(hstmt, SQL_RESET_PARAMS);

  return retcode;
}

SQLRETURN FreeCols(HSTMT hstmt,
                   ColRec* clist) {
  SQLRETURN retcode;
  ColRec* temp;

  while (clist != NULL) {
    temp = (ColRec*)clist->Next;
    free(clist->rgbValue);
    free(clist);
    clist = temp;
	}

  retcode = SQLFreeStmt(hstmt, SQL_UNBIND);

  return retcode;
}

cint odbc_SQLAllocStorage() {
  cptr result = mem_malloc(4*4);
  ParamRec* paramptr;
  ColRec* colptr;

  paramptr = mem_malloc(sizeof(ParamRec));
  paramptr->rgbValue = NULL;
  paramptr->Next = NULL;
	
  colptr = mem_malloc(sizeof(ColRec));
  colptr->rgbValue = NULL;
  colptr->Next = NULL;
	
  result[0] = object_integer_from(SQL_SUCCESS);
  result[1] = object_integer_from((cint)paramptr);
  result[2] = object_integer_from((cint)colptr);
  result[3] = 0;

  return object_list_from(result);
}

cint odbc_SQLFreeStorage(cint hstmt,
                         cint plist,
                         cint clist) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HSTMT ihstmt;
  ParamRec* iplist;
  ColRec* iclist;

  ihstmt = (HSTMT)object_as_int(hstmt);
  iplist = (ParamRec*)object_as_int(plist);
  iclist = (ColRec*)object_as_int(clist);
  retcode = FreeParams(ihstmt, iplist);
  if (Success(retcode)) {
    retcode = FreeCols(ihstmt, iclist);
  }

  result[0] = object_integer_from(SQL_SUCCESS);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_SQLPrepare(cint hstmt,
                     cstring szSqlStr) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HSTMT ihstmt;
  cstring iszSqlStr;
  SDWORD cbSqlStr;

  ihstmt = (HSTMT)object_as_int(hstmt);
  iszSqlStr = (cstring)object_as_string(szSqlStr);
  cbSqlStr = strlen(iszSqlStr);
  retcode = SQLPrepare(ihstmt, iszSqlStr, cbSqlStr);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

void InsertParam(ParamRec* plist,
                 UWORD ipar,
                 SWORD fCType,
                 PTR rgbValue,
                 SDWORD cbValue,
                 SDWORD* *pcbValue) {
  ParamRec* temp;

  if (plist->rgbValue == NULL) {
    plist->ipar = ipar;
    plist->fCType = fCType;
    plist->rgbValue = rgbValue;
    plist->cbValue = cbValue;

    *pcbValue = &plist->cbValue;
  }
  else {
    while (plist->Next != NULL) {
      plist = (ParamRec*)plist->Next;
    }

    temp = mem_malloc(sizeof(ParamRec));
    temp->ipar = ipar;
    temp->fCType = fCType;
    temp->rgbValue = rgbValue;
    temp->cbValue = cbValue;
    temp->Next = NULL;

    *pcbValue = &temp->cbValue;
    plist->Next = (cptr)temp;
  }
}

cint odbc_SQLBindParameter(cint hstmt,
                           cint plist,
                           cint paramnum,
                           cint paramtype) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  ParamRec* iplist;
  HSTMT ihstmt;
  UWORD ipar;
  SWORD fParamType;
  SWORD fCType;
  SWORD fSqlType;
  UDWORD cbColDef;
  SWORD ibScale;
  PTR rgbValue;
  SDWORD cbValueMax;
  SDWORD cbValue;
  SDWORD* pcbValue;

  iplist = (ParamRec*)object_as_int(plist);
  ihstmt = (HSTMT)object_as_int(hstmt);
  ipar = (UWORD)object_as_int(paramnum);
  fParamType = SQL_PARAM_INPUT;
  fSqlType = (SWORD)object_as_int(paramtype);
  cbColDef = 0;
  ibScale = 0;
  cbValueMax = 0;
  cbValue = 0;

  switch (fSqlType) {
    case SQL_CHAR:
    case SQL_VARCHAR:
      fCType = SQL_C_CHAR;
      cbColDef = MaxNullString;
      rgbValue = mem_malloc(MaxNullString+1);
      cbValueMax = MaxNullString+1;
      cbValue = SQL_NTS;
      break;
    case SQL_LONGVARCHAR:
      fCType = SQL_C_CHAR;
      rgbValue = NULL;
      break;
    case SQL_BINARY:
    case SQL_VARBINARY:
      fCType = SQL_C_BINARY;
      cbColDef = MaxNullString+1;
      rgbValue = mem_malloc(MaxNullString+1);
      cbValueMax = MaxNullString+1;
      cbValue = 0;
      break;
    case SQL_LONGVARBINARY:
      fCType = SQL_C_BINARY;
      rgbValue = NULL;
      break;
    case SQL_REAL:
    case SQL_DOUBLE:
    case SQL_FLOAT:
    case SQL_DECIMAL:
    case SQL_NUMERIC:
      fCType = SQL_C_FLOAT;
      rgbValue = mem_malloc(sizeof(cfloat));
      break;
    case SQL_BIT:
    case SQL_TINYINT:
    case SQL_SMALLINT:
    case SQL_INTEGER:
      fCType = SQL_C_SLONG;
      rgbValue = mem_malloc(sizeof(cint));
      break;
    case SQL_DATE:
    case SQL_TIME:
    case SQL_TIMESTAMP:
      fCType = SQL_C_TIMESTAMP;
      rgbValue = mem_malloc(sizeof(TIMESTAMP_STRUCT));
      break;
    default:
      fCType = 0;
      rgbValue = NULL;
      break;
  }

  InsertParam(iplist, ipar, fCType, rgbValue, cbValue, &pcbValue);

  retcode = SQLBindParameter(ihstmt, ipar, fParamType, fCType, fSqlType, cbColDef, ibScale, rgbValue, cbValueMax, pcbValue);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_SQLBindBlob(cint hstmt,
                      cint plist,
                      cint paramnum,
                      cint paramtype,
                      cint blobhandle) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  ParamRec* iplist;
  HSTMT ihstmt;
  UWORD ipar;
  SWORD fParamType;
  SWORD fCType;
  SWORD fSqlType;
  UDWORD cbColDef;
  SWORD ibScale;
  PTR rgbValue;
  SDWORD cbValueMax;
  SDWORD cbValue;
  SDWORD* pcbValue;
  BlobRec* temp;

  iplist = (ParamRec*)object_as_int(plist);
  ihstmt = (HSTMT)object_as_int(hstmt);
  ipar = (UWORD)object_as_int(paramnum);
  fParamType = SQL_PARAM_INPUT;
  fSqlType = (SWORD)object_as_int(paramtype);
  temp = (BlobRec*)object_as_int(blobhandle);
  cbColDef = 0;
  ibScale = 0;
  cbValueMax = 0;
  cbValue = 0;

  switch (fSqlType) {
    case SQL_LONGVARCHAR:
      fCType = SQL_C_CHAR;
      rgbValue = temp;
      cbValue = SQL_LEN_DATA_AT_EXEC_OFFSET-temp->GSize;
      break;
    case SQL_LONGVARBINARY:
      fCType = SQL_C_BINARY;
      rgbValue = temp;
      cbValue = SQL_LEN_DATA_AT_EXEC_OFFSET-temp->GSize;
      break;
    default:
      fCType = 0;
      rgbValue = NULL;
      break;
  }

  InsertParam(iplist, ipar, fCType, rgbValue, cbValue, &pcbValue);

  retcode = SQLBindParameter(ihstmt, ipar, fParamType, fCType, fSqlType, cbColDef, ibScale, rgbValue, cbValueMax, pcbValue);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_SQLAssignParamInt(cint hstmt,
                            cint plist,
                            cint paramnum,
                            cint paramvalue) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  ParamRec* iplist;
  HSTMT ihstmt;
  UWORD ipar;
  cint iparval;
			
  iplist = (ParamRec*)object_as_int(plist);
  ihstmt = (HSTMT)object_as_int(hstmt);
  ipar = (UWORD)object_as_int(paramnum);

  while ((iplist != NULL) & (iplist->ipar != ipar)) {
    iplist = (ParamRec*)iplist->Next;
  }

  if (iplist != NULL) {
    if (iplist->rgbValue != NULL) {
      if (paramvalue == object_Undefined) {
        iplist->cbValue = SQL_NULL_DATA;
      }
      else {
        iparval = (cint)object_as_int(paramvalue);  
        *(cint*)iplist->rgbValue = iparval;
        iplist->cbValue = sizeof(cint);
      }
    }
    retcode = SQL_SUCCESS;
  }
  else {
    retcode = SQL_ERROR;
  }

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_SQLAssignParamStr(cint hstmt,
                            cint plist,
                            cint paramnum,
                            cstring paramvalue) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  ParamRec* iplist;
  HSTMT ihstmt;
  UWORD ipar;
  cstring iparval;
	
  iplist = (ParamRec*)object_as_int(plist);
  ihstmt = (HSTMT)object_as_int(hstmt);
  ipar = (UWORD)object_as_int(paramnum);
  
  while ((iplist != NULL) & (iplist->ipar != ipar)) {
    iplist = (ParamRec*)iplist->Next;
  }

  if (iplist != NULL) {
    if (iplist->rgbValue != NULL) {
      if ((cint)paramvalue == object_Undefined) {
        iplist->cbValue = SQL_NULL_DATA;
      }
      else {
        iparval = (cstring)object_as_string(paramvalue);
        strcpy(iplist->rgbValue, iparval);
        iplist->cbValue = SQL_NTS;
      }
    }
    retcode = SQL_SUCCESS;
  }
  else {
    retcode = SQL_ERROR;
  }

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_SQLAssignParamNum(cint hstmt,
                            cint plist,
                            cint paramnum,
                            cint paramvalue) {
  union {cint i; cfloat f;} r;

  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  ParamRec* iplist;
  HSTMT ihstmt;
  UWORD ipar;
  cfloat iparval;
			
  iplist = (ParamRec*)object_as_int(plist);
  ihstmt = (HSTMT)object_as_int(hstmt);
  ipar = (UWORD)object_as_int(paramnum);

  while ((iplist != NULL) & (iplist->ipar != ipar)) {
    iplist = (ParamRec*)iplist->Next;
  }

  if (iplist != NULL) {
    if (iplist->rgbValue != NULL) {
      if (paramvalue == object_Undefined) {
        iplist->cbValue = SQL_NULL_DATA;
      }
      else {
        r.i = (cint)object_as_int(paramvalue);
        iparval = r.f;
        *(cfloat*)iplist->rgbValue = iparval;
        iplist->cbValue = sizeof(cfloat);
      }
    }
    retcode = SQL_SUCCESS;
  }
  else {
    retcode = SQL_ERROR;
  }

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_SQLAssignParamDT(cint hstmt,
                           cint plist,
                           cint paramnum,
                           cint paramvalue) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  ParamRec* iplist;
  HSTMT ihstmt;
  UWORD ipar;
  TIMESTAMP_STRUCT dt;
	
  iplist = (ParamRec*)object_as_int(plist);
  ihstmt = (HSTMT)object_as_int(hstmt);
  ipar = (UWORD)object_as_int(paramnum);

  while ((iplist != NULL) & (iplist->ipar != ipar)) {
    iplist = (ParamRec*)iplist->Next;
  }

  if (iplist != NULL) {
    if (iplist->rgbValue != NULL) {
      if (paramvalue == object_Undefined) {
        iplist->cbValue = SQL_NULL_DATA;
      }
      else {
        dt.year = object_as_int((cint)((cptr)object_as_array(paramvalue))[0]);
        dt.month = object_as_int((cint)((cptr)object_as_array(paramvalue))[1]);
        dt.day = object_as_int((cint)((cptr)object_as_array(paramvalue))[2]);
        dt.hour = object_as_int((cint)((cptr)object_as_array(paramvalue))[3]);
        dt.minute = object_as_int((cint)((cptr)object_as_array(paramvalue))[4]);
        dt.second = object_as_int((cint)((cptr)object_as_array(paramvalue))[5]);
        dt.fraction = object_as_int((cint)((cptr)object_as_array(paramvalue))[6]);

        ((TIMESTAMP_STRUCT*)iplist->rgbValue)->year = dt.year;
        ((TIMESTAMP_STRUCT*)iplist->rgbValue)->month = dt.month;
        ((TIMESTAMP_STRUCT*)iplist->rgbValue)->day = dt.day;
        ((TIMESTAMP_STRUCT*)iplist->rgbValue)->hour = dt.hour;
        ((TIMESTAMP_STRUCT*)iplist->rgbValue)->minute = dt.minute;
        ((TIMESTAMP_STRUCT*)iplist->rgbValue)->second = dt.second;
        ((TIMESTAMP_STRUCT*)iplist->rgbValue)->fraction = dt.fraction;

        iplist->cbValue = sizeof(TIMESTAMP_STRUCT);
      }
    }
    retcode = SQL_SUCCESS;
  }
  else {
    retcode = SQL_ERROR;
  }

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

/* Submitting Requests */

cint odbc_SQLExecute(cint hstmt) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HSTMT ihstmt;
  BlobRec* blobhandle;
  void* FPtr;
  long PSize, PCursor, BSize;
  void *OffsetPtr;

  ihstmt = (HSTMT)object_as_int(hstmt);
  retcode = SQLFreeStmt(ihstmt, SQL_CLOSE);
  retcode = SQLExecute(ihstmt);

  if (retcode == SQL_NEED_DATA) {

    retcode = SQLParamData(ihstmt, &blobhandle);
    if ((retcode != SQL_NEED_DATA) & (!Success(retcode))) {
      goto ErrHandler;
    }

    while (retcode == SQL_NEED_DATA) {
      //Retrieve Blob Information
	    FPtr = Lock(blobhandle->GHandle);
      PSize = blobhandle->GSize;
      
      //Write Blob
      PCursor = 0;
	    retcode = SQL_SUCCESS;
      while ((PCursor < PSize) & (Success(retcode))) {
        if ((PSize - PCursor) > MaxBuffer) {
          BSize = MaxBuffer;
        }
        else {
          BSize = PSize - PCursor;
        }
        OffsetPtr = (void*)((int)FPtr + PCursor);
        retcode = SQLPutData(ihstmt, OffsetPtr, BSize);
        PCursor += BSize;
      }
      if (!Success(retcode)) {
        goto ErrHandler;
      }

      Unlock(blobhandle->GHandle);

      retcode = SQLParamData(ihstmt, &blobhandle);
      if ((retcode != SQL_NEED_DATA) & (!Success(retcode))) {
        goto ErrHandler;
      }
    }
  }

ErrHandler:
  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

/* Retrieving Results and Information about Results */

cint odbc_SQLError(cint henv,
                   cint hdbc,
                   cint hstmt) {
  cptr result = mem_malloc(3*4);
  SQLRETURN retcode;
  HENV ihenv;
  HDBC ihdbc;
  HSTMT ihstmt;
  cstring szSqlState;
  SDWORD pfNativeError;
  cstring szErrorMsg;
  SWORD cbErrorMsgMax;
  WORD cbErrorMsg;

  ihenv = (HENV)object_as_int(henv);
  ihdbc = (HDBC)object_as_int(hdbc);
  ihstmt = (HSTMT)object_as_int(hstmt);
  szSqlState = mem_malloc(MaxNullString+1);
  szErrorMsg = mem_malloc(MaxNullString+1);
  cbErrorMsgMax = (SWORD)MaxNullString;
  retcode = SQLError(ihenv, ihdbc, ihstmt, szSqlState, &pfNativeError, szErrorMsg, cbErrorMsgMax, &cbErrorMsg);

  result[0] = object_integer_from((cint)retcode);
  result[1] = object_string_from(szErrorMsg);
  result[2] = 0;

  return object_list_from(result);
}

/* Obtaining Information */

cint odbc_SQLColumns(cint hstmt,
                     cstring szTableName) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HSTMT ihstmt;
  cstring szCatalogName;
  SWORD cbCatalogName;
  cstring szSchemaName;
  SWORD cbSchemaName;
  cstring iszTableName;
  SWORD cbTableName;
  cstring szColumnName;
  SWORD cbColumnName;

  ihstmt = (HSTMT)object_as_int(hstmt);
  szCatalogName = NULL;
  cbCatalogName = 0;
  szSchemaName = NULL;
  cbSchemaName = 0;
  iszTableName = (cstring)object_as_string(szTableName);
  cbTableName = strlen(iszTableName);
  szColumnName = NULL;
  cbColumnName = 0;
  retcode = SQLColumns(ihstmt, szCatalogName, cbCatalogName, szSchemaName, cbSchemaName, iszTableName, cbTableName, szColumnName, cbColumnName);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_SQLForeignKeys(cint hstmt,
                         cstring szTableName) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HSTMT ihstmt;
  cstring szCatalogName;
  SWORD cbCatalogName;
  cstring szSchemaName;
  SWORD cbSchemaName;
  cstring iszTableName;
  SWORD cbTableName;
  cstring szFKCatalogName;
  SWORD cbFKCatalogName;
  cstring szFKSchemaName;
  SWORD cbFKSchemaName;
  cstring szFKTableName;
  SWORD cbFKTableName;

  ihstmt = (HSTMT)object_as_int(hstmt);
  szCatalogName = NULL;
  cbCatalogName = 0;
  szSchemaName = NULL;
  cbSchemaName = 0;
  iszTableName = (cstring)object_as_string(szTableName);
  cbTableName = strlen(iszTableName);
  szFKCatalogName = NULL;
  cbFKCatalogName = 0;
  szFKSchemaName = NULL;
  cbFKSchemaName = 0;
  szFKTableName = NULL;
  cbFKTableName = 0;
  retcode = SQLForeignKeys(ihstmt, szCatalogName, cbCatalogName, szSchemaName, cbSchemaName, iszTableName, cbTableName,
    szFKCatalogName, cbFKCatalogName, szFKSchemaName, cbFKSchemaName, szFKTableName, cbFKTableName);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_SQLPrimaryKeys(cint hstmt,
                         cstring szTableName) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HSTMT ihstmt;
  cstring szCatalogName;
  SWORD cbCatalogName;
  cstring szSchemaName;
  SWORD cbSchemaName;
  cstring iszTableName;
  SWORD cbTableName;

  ihstmt = (HSTMT)object_as_int(hstmt);
  szCatalogName = NULL;
  cbCatalogName = 0;
  szSchemaName = NULL;
  cbSchemaName = 0;
  iszTableName = (cstring)object_as_string(szTableName);
  cbTableName = strlen(iszTableName);
  retcode = SQLPrimaryKeys(ihstmt, szCatalogName, cbCatalogName, szSchemaName, cbSchemaName, iszTableName, cbTableName);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_SQLStatistics(cint hstmt,
                        cstring szTableName) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HSTMT ihstmt;
  cstring szCatalogName;
  SWORD cbCatalogName;
  cstring szSchemaName;
  SWORD cbSchemaName;
  cstring iszTableName;
  SWORD cbTableName;

  ihstmt = (HSTMT)object_as_int(hstmt);
  szCatalogName = NULL;
  cbCatalogName = 0;
  szSchemaName = NULL;
  cbSchemaName = 0;
  iszTableName = (cstring)object_as_string(szTableName);
  cbTableName = strlen(iszTableName);
  retcode = SQLStatistics(ihstmt, szCatalogName, cbCatalogName, szSchemaName, cbSchemaName, iszTableName, cbTableName, SQL_INDEX_ALL, SQL_ENSURE);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_SQLTables(cint hstmt) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HSTMT ihstmt;
  cstring szCatalogName;
  SWORD cbCatalogName;
  cstring szSchemaName;
  SWORD cbSchemaName;
  cstring szTableName;
  SWORD cbTableName;
  cstring szTableType;
  SWORD cbTableType;

  ihstmt = (HSTMT)object_as_int(hstmt);
  szCatalogName = NULL;
  cbCatalogName = 0;
  szSchemaName = NULL;
  cbSchemaName = 0;
  szTableName = mem_malloc(MaxNullString+1);
  strcpy(szTableName, "%");
  cbTableName = strlen(szTableName);
  szTableType = mem_malloc(MaxNullString+1);
  strcpy(szTableType, "TABLE");
  cbTableType = strlen(szTableType);
  retcode = SQLTables(ihstmt, szCatalogName, cbCatalogName, szSchemaName, cbSchemaName, szTableName, cbTableName, szTableType, cbTableType);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

/* Terminating a Statement */

cint odbc_SQLFreeStmt(cint hstmt,
                      cint fOption) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HSTMT ihstmt;
  UWORD ifOption;

  ihstmt = (HSTMT)object_as_int(hstmt);
  ifOption = object_as_int(fOption);
  retcode = SQLFreeStmt(ihstmt, ifOption);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_SQLTransact(cint henv,
                      cint hdbc,
                      cint fType) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HENV ihenv;
  HDBC ihdbc;
  UWORD ifType;

  ihenv = (HENV)object_as_int(henv);
  ihdbc = (HDBC)object_as_int(hdbc);
  ifType = object_as_int(fType);
  retcode = SQLTransact(ihenv, ihdbc, ifType);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

/* Terminating a Connection */

cint odbc_SQLDisconnect(cint hdbc) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HDBC ihdbc;

  ihdbc = (HDBC)object_as_int(hdbc);
  retcode = SQLDisconnect(ihdbc);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_SQLFreeConnect(cint hdbc) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HDBC ihdbc;

  ihdbc = (HDBC)object_as_int(hdbc);
  retcode = SQLFreeConnect(ihdbc);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

cint odbc_SQLFreeEnv(cint henv) {
  cptr result = mem_malloc(2*4);
  SQLRETURN retcode;
  HENV ihenv;

  ihenv = (HENV)object_as_int(henv);
  retcode = SQLFreeEnv(ihenv);

  result[0] = object_integer_from((cint)retcode);
  result[1] = 0;

  return object_list_from(result);
}

void InsertCol(ColRec* clist,
               SWORD fCType,
               PTR rgbValue,
               SDWORD* *pcbValue) {
  ColRec* temp;

  if (clist->rgbValue == NULL) {
    clist->fCType = fCType;
    clist->rgbValue = rgbValue;
    clist->cbValue = 0;

    *pcbValue = &clist->cbValue;
  }
  else {
    while (clist->Next != NULL) {
      clist = (ColRec*)clist->Next;
    }

    temp = mem_malloc(sizeof(ColRec));
    temp->fCType = fCType;
    temp->rgbValue = rgbValue;
    temp->cbValue = 0;
    temp->Next = NULL;

    *pcbValue = &temp->cbValue;
    clist->Next = (cptr)temp;
  }
}

SQLRETURN BindCols(HSTMT hstmt,
                   ColRec* clist,
                   SWORD* pccol) {
  SQLRETURN retcode;
  UWORD icol;
  UWORD fDescType;
  SDWORD fDesc;
  SWORD cbDescMax;
  SWORD cbDesc;
  SWORD fCType;
  PTR rgbValue;
  SDWORD cbValueMax;
  SDWORD* pcbValue;

  retcode = SQLNumResultCols(hstmt, pccol);
  if (!Success(retcode)) {
    return retcode;
  }

  if (clist->rgbValue != NULL) {
    return SQL_SUCCESS;
  }
	
  fDescType = SQL_COLUMN_TYPE;
  cbDescMax = 0;

  for (icol=1; icol<=*pccol; icol++) {
	
    retcode = SQLColAttributes(hstmt, icol, fDescType, &fDesc, cbDescMax, &cbDesc, &fDesc);
    if (!Success(retcode)) {
	    return retcode;
    }

    switch (fDesc) {
      case SQL_CHAR:
      case SQL_VARCHAR:
        fCType = SQL_C_CHAR;
        rgbValue = mem_malloc(MaxNullString+1);
        cbValueMax = MaxNullString+1;
        break;
      case SQL_LONGVARCHAR:
        fCType = SQL_C_CHAR;
        rgbValue = NULL;
        break;
      case SQL_BINARY:
      case SQL_VARBINARY:
        fCType = SQL_C_BINARY;
        rgbValue = mem_malloc(MaxNullString+1);
        cbValueMax = MaxNullString+1;
        break;
      case SQL_LONGVARBINARY:
        fCType = SQL_C_BINARY;
        rgbValue = NULL;
        break;
      case SQL_REAL:
      case SQL_DOUBLE:
      case SQL_FLOAT:
      case SQL_DECIMAL:
      case SQL_NUMERIC:
        fCType = SQL_C_FLOAT;
        rgbValue = mem_malloc(sizeof(cfloat));
        cbValueMax = 0;
        break;
      case SQL_BIT:
      case SQL_TINYINT:
      case SQL_SMALLINT:
      case SQL_INTEGER:
        fCType = SQL_C_SLONG;
        rgbValue = mem_malloc(sizeof(cint));
        cbValueMax = 0;
        break;
      case SQL_DATE:
        fCType = SQL_C_DATE;
        rgbValue = mem_malloc(sizeof(DATE_STRUCT));
        cbValueMax = 0;
        break;
      case SQL_TIME:
        fCType = SQL_C_TIME;
        rgbValue = mem_malloc(sizeof(TIME_STRUCT));
        cbValueMax = 0;
        break;
      case SQL_TIMESTAMP:
        fCType = SQL_C_TIMESTAMP;
        rgbValue = mem_malloc(sizeof(TIMESTAMP_STRUCT));
        cbValueMax = 0;
        break;
      default:
        fCType = 0;
        rgbValue = NULL;
        cbValueMax = 0;
        break; 
    }

    InsertCol(clist, fCType, rgbValue, &pcbValue);

    if (rgbValue != NULL) {
      retcode = SQLBindCol(hstmt, icol, fCType, rgbValue, cbValueMax, pcbValue);
      if (!Success(retcode)) {
        return retcode;
      }
    }
  }

  return retcode;
}

/*cint odbc_SQLColSize(cint clist,
					 cint col) {
  cptr result = mem_malloc(3*4);
  SQLRETURN retcode;
  ColRec* iclist;
  UWORD icol, i;
  
  iclist = (ColRec*)object_as_int(clist);
  icol = object_as_int(col);

  for (i=1; i<=icol; i++) {
    iclist = (ColRec*)iclist->Next;
  }

  result[1] = iclist->cbValue;

  retcode = SQL_SUCCESS;
  result[0] = object_integer_from((cint)retcode);
  result[2] = 0;

  return object_list_from(result);
}*/

cint odbc_SQLFetchNext(cint hstmt,
                       cint clist) {
  union {cint i; cfloat f;} r;

  cptr result = mem_malloc(3*4);
  cptr row;
  SQLRETURN retcode;
  ColRec* iclist;
  HSTMT ihstmt;
  SWORD ccol;
  UWORD icol;
  cstring temps;
  cptr tempdt;
  void* blobhandle;

  result[1] = 0;
  iclist = (ColRec*)object_as_int(clist);
  ihstmt = (HSTMT)object_as_int(hstmt);

  retcode = BindCols(ihstmt, iclist, &ccol);
  if (!Success(retcode)) {
    goto ErrHandler;
  }

  retcode = SQLFetch(ihstmt);
  if (!Success(retcode)) {
    goto ErrHandler;
  }

  row = mem_malloc((ccol+1)*4);
  for (icol=1; icol<=ccol; icol++) {

    if (iclist->rgbValue == NULL) {
      retcode = FetchBlob(ihstmt, icol, iclist->fCType, &iclist->cbValue, &blobhandle);
      if (!Success(retcode)) {
        goto ErrHandler;
      }
      if (iclist->cbValue == SQL_NULL_DATA) {
	    row[icol-1] = object_Undefined;	
      }
	  else {
        row[icol-1] = object_integer_from((cint)blobhandle);
	  }
    }
	else if (iclist->cbValue == SQL_NULL_DATA) {
	  row[icol-1] = object_Undefined;	
    }
    else {

      switch (iclist->fCType) {
        case SQL_C_CHAR:
          temps = mem_malloc(MaxNullString+1);
          strcpy(temps, Unspace((cstring)iclist->rgbValue));
          row[icol-1] = object_string_from(temps);
          break;
        case SQL_C_BINARY:
          row[icol-1] = object_Undefined;
          break;
        case SQL_C_FLOAT:
          r.f = *(cfloat*)iclist->rgbValue;
          row[icol-1] = object_float_from(r.i);
          break;
        case SQL_C_SLONG:
          row[icol-1] = object_integer_from(*(cint*)iclist->rgbValue);
          break;
        case SQL_C_DATE:
          tempdt = mem_malloc(8*4);
          tempdt[0] = object_integer_from((cint)((DATE_STRUCT*)iclist->rgbValue)->year);
          tempdt[1] = object_integer_from((cint)((DATE_STRUCT*)iclist->rgbValue)->month);
          tempdt[2] = object_integer_from((cint)((DATE_STRUCT*)iclist->rgbValue)->day);
          tempdt[3] = object_integer_from(0);
          tempdt[4] = object_integer_from(0);
          tempdt[5] = object_integer_from(0);
          tempdt[6] = object_integer_from(0);
          tempdt[7] = 0;
          row[icol-1] = object_list_from(tempdt);
          break;
        case SQL_C_TIME:
          tempdt = mem_malloc(8*4);
          tempdt[0] = object_integer_from(1);
          tempdt[1] = object_integer_from(1);
          tempdt[2] = object_integer_from(1);
          tempdt[3] = object_integer_from((cint)((TIME_STRUCT*)iclist->rgbValue)->hour);
          tempdt[4] = object_integer_from((cint)((TIME_STRUCT*)iclist->rgbValue)->minute);
          tempdt[5] = object_integer_from((cint)((TIME_STRUCT*)iclist->rgbValue)->second);
          tempdt[6] = object_integer_from(0);
          tempdt[7] = 0;
          row[icol-1] = object_list_from(tempdt);
          break;
        case SQL_C_TIMESTAMP:
          tempdt = mem_malloc(8*4);
          tempdt[0] = object_integer_from((cint)((TIMESTAMP_STRUCT*)iclist->rgbValue)->year);
          tempdt[1] = object_integer_from((cint)((TIMESTAMP_STRUCT*)iclist->rgbValue)->month);
          tempdt[2] = object_integer_from((cint)((TIMESTAMP_STRUCT*)iclist->rgbValue)->day);
          tempdt[3] = object_integer_from((cint)((TIMESTAMP_STRUCT*)iclist->rgbValue)->hour);
          tempdt[4] = object_integer_from((cint)((TIMESTAMP_STRUCT*)iclist->rgbValue)->minute);
          tempdt[5] = object_integer_from((cint)((TIMESTAMP_STRUCT*)iclist->rgbValue)->second);
          tempdt[6] = object_integer_from((cint)((TIMESTAMP_STRUCT*)iclist->rgbValue)->fraction);
          tempdt[7] = 0;
          row[icol-1] = object_list_from(tempdt);
          break;
        default:
          row[icol-1] = object_Undefined;
          break;
      }

    }

    iclist = (ColRec*)iclist->Next;
  }

  row[ccol] = 0;
  result[1] = object_list_from(row);

ErrHandler:
  result[0] = object_integer_from((cint)retcode);
  result[2] = 0;

  return object_list_from(result);
}
