/************ Odbconn C++ Functions Source Code File (.CPP) ************/
/*  Name: ODBCONN.CPP  Version 1.9                                     */
/*                                                                     */
/*  (C) Copyright to the author Olivier BERTRAND          1998-2013    */
/*                                                                     */
/*  This file contains the ODBC connection classes functions.          */
/***********************************************************************/

/***********************************************************************/
/*  Include relevant MariaDB header file.                              */
/***********************************************************************/
#include "my_global.h"
#if defined(WIN32)
//nclude <io.h>
//nclude <fcntl.h>
#include <direct.h>                      // for getcwd
#if defined(__BORLANDC__)
#define __MFC_COMPAT__                   // To define min/max as macro
#endif
//#include <windows.h>
#else
#if defined(UNIX)
#include <errno.h>
#else
//nclude <io.h>
#endif
//nclude <fcntl.h>
#define NODW
#endif

/***********************************************************************/
/*  Required objects includes.                                         */
/***********************************************************************/
#include "global.h"
#include "plgdbsem.h"
#include "xobject.h"
//#include "kindex.h"
#include "xtable.h"
#include "tabodbc.h"
#include "odbccat.h"
#include "plgcnx.h"                       // For DB types
#include "resource.h"
#include "valblk.h"
#include "osutil.h"


#if defined(WIN32)
/***********************************************************************/
/*  For dynamic load of ODBC32.DLL                                     */
/***********************************************************************/
#pragma comment(lib, "odbc32.lib")
extern "C" HINSTANCE s_hModule;           // Saved module handle
#endif // WIN32

/***********************************************************************/
/*  Some macro's (should be defined elsewhere to be more accessible)   */
/***********************************************************************/
#if defined(_DEBUG)
#define ASSERT(f)          assert(f)
#define DEBUG_ONLY(f)      (f)
#else   // !_DEBUG
#define ASSERT(f)          ((void)0)
#define DEBUG_ONLY(f)      ((void)0)
#endif  // !_DEBUG

extern "C" int trace;

/***********************************************************************/
/*  GetSQLType: returns the SQL_TYPE corresponding to a PLG type.      */
/***********************************************************************/
static short GetSQLType(int type)
  {
  short tp = SQL_TYPE_NULL;

  switch (type) {
    case TYPE_STRING: tp = SQL_CHAR;      break;
    case TYPE_SHORT:  tp = SQL_SMALLINT;  break;
    case TYPE_INT:    tp = SQL_INTEGER;   break;
    case TYPE_DATE:   tp = SQL_TIMESTAMP; break;
    case TYPE_BIGINT: tp = SQL_BIGINT;    break;                //  (-5)
    case TYPE_DOUBLE: tp = SQL_DOUBLE;    break;
    case TYPE_TINY:   tp = SQL_TINYINT;   break;
    case TYPE_DECIM:  tp = SQL_DECIMAL;   break;
    } // endswitch type

  return tp;
  } // end of GetSQLType

/***********************************************************************/
/*  GetSQLCType: returns the SQL_C_TYPE corresponding to a PLG type.   */
/***********************************************************************/
static int GetSQLCType(int type)
  {
  int tp = SQL_TYPE_NULL;

  switch (type) {
    case TYPE_STRING: tp = SQL_C_CHAR;      break;
    case TYPE_SHORT:  tp = SQL_C_SHORT;     break;
    case TYPE_INT:    tp = SQL_C_LONG;      break;
    case TYPE_DATE:   tp = SQL_C_TIMESTAMP; break;
    case TYPE_BIGINT: tp = SQL_C_SBIGINT;   break;
    case TYPE_DOUBLE: tp = SQL_C_DOUBLE;    break;
    case TYPE_TINY :  tp = SQL_C_TINYINT;   break;
    case TYPE_DECIM:  tp = SQL_C_CHAR;      break;
    } // endswitch type

  return tp;
  } // end of GetSQLCType

/***********************************************************************/
/*  TranslateSQLType: translate a SQL Type to a PLG type.              */
/***********************************************************************/
int TranslateSQLType(int stp, int prec, int& len, char& v)
  {
  int type;

  switch (stp) {
    case SQL_VARCHAR:                       //   12
      v = 'V';
    case SQL_CHAR:                          //    1
      type = TYPE_STRING;
      break;
    case SQL_LONGVARCHAR:                   //  (-1)
      v = 'V';
      type = TYPE_STRING;
      len = min(abs(len), 256);
      break;
    case SQL_NUMERIC:                       //    2
    case SQL_DECIMAL:                       //    3
//    type = (prec || len > 20) ? TYPE_DOUBLE
//         : (len > 10) ? TYPE_BIGINT : TYPE_INT;
      type = TYPE_DECIM;
      break;
    case SQL_INTEGER:                       //    4
      type = TYPE_INT;
      break;
    case SQL_SMALLINT:                      //    5
      type = TYPE_SHORT;
      break;
    case SQL_TINYINT:                       //  (-6)
    case SQL_BIT:                           //  (-7)
      type = TYPE_TINY;
      break;
    case SQL_FLOAT:                         //    6
    case SQL_REAL:                          //    7
    case SQL_DOUBLE:                        //    8
      type = TYPE_DOUBLE;
      break;
    case SQL_DATETIME:                      //    9
//  case SQL_DATE:                          //    9
      type = TYPE_DATE;
      len = 10;
      break;
    case SQL_INTERVAL:                      //   10
//  case SQL_TIME:                          //   10
      type = TYPE_STRING;
      len = 8 + ((prec) ? (prec+1) : 0);
      break;
    case SQL_TIMESTAMP:                     //   11
      type = TYPE_DATE;
      len = 19 + ((prec) ? (prec+1) : 0);
      break;
    case SQL_BIGINT:                        //  (-5)
      type = TYPE_BIGINT;
      break;
    case SQL_UNKNOWN_TYPE:                  //    0
    case SQL_BINARY:                        //  (-2)
    case SQL_VARBINARY:                     //  (-3)
    case SQL_LONGVARBINARY:                 //  (-4)
//  case SQL_BIT:                           //  (-7)
    case SQL_GUID:                          // (-11)
    default:
      type = TYPE_ERROR;
      len = 0;
    } // endswitch type

  return type;
  } // end of TranslateSQLType

#if defined(PROMPT_OK)
/***********************************************************************/
/*  ODBCCheckConnection: Check completeness of connection string.      */
/***********************************************************************/
char *ODBCCheckConnection(PGLOBAL g, char *dsn, int cop)
  {
  char    *newdsn, dir[_MAX_PATH], buf[_MAX_PATH];
  int      rc;
  DWORD    options = ODBConn::openReadOnly;
  ODBConn *ocp = new(g) ODBConn(g, NULL);

  (void) getcwd(dir, sizeof(dir) - 1);

  switch (cop) {
    case 1: options |= ODBConn::forceOdbcDialog; break;
    case 2: options |= ODBConn::noOdbcDialog;    break;
    } // endswitch cop

  if (ocp->Open(dsn, options) < 1)
    newdsn = NULL;
  else
    newdsn = ocp->GetConnect();

  (void) getcwd(buf, sizeof(buf) - 1);

  // Some data sources change the current directory
  if (strcmp(dir, buf))
    rc = chdir(dir);

  ocp->Close();
  return newdsn;         // Return complete connection string
  } // end of ODBCCheckConnection
#endif   // PROMPT_OK

/***********************************************************************/
/*  Allocate the structure used to refer to the result set.            */
/***********************************************************************/
static CATPARM *AllocCatInfo(PGLOBAL g, CATINFO fid, char *db, 
                                        char *tab, PQRYRES qrp)
  {
  size_t   i, m, n;
  CATPARM *cap;

#if defined(_DEBUG)
  assert(qrp);
#endif

  // Save stack and allocation environment and prepare error return
  if (g->jump_level == MAX_JUMP) {
    strcpy(g->Message, MSG(TOO_MANY_JUMPS));
    return NULL;
    } // endif jump_level

  if (setjmp(g->jumper[++g->jump_level]) != 0) {
    printf("%s\n", g->Message);
    cap = NULL;
    goto fin;
    } // endif rc

  m = (size_t)qrp->Maxres;
  n = (size_t)qrp->Nbcol;
  cap = (CATPARM *)PlugSubAlloc(g, NULL, sizeof(CATPARM));
  memset(cap, 0, sizeof(CATPARM));
  cap->Id = fid;
  cap->Qrp = qrp;
  cap->DB = (PUCHAR)db;
  cap->Tab = (PUCHAR)tab;
  cap->Vlen = (SQLLEN* *)PlugSubAlloc(g, NULL, n * sizeof(SQLLEN *));

  for (i = 0; i < n; i++)
    cap->Vlen[i] = (SQLLEN *)PlugSubAlloc(g, NULL, m * sizeof(SQLLEN));

  cap->Status = (UWORD *)PlugSubAlloc(g, NULL, m * sizeof(UWORD));

 fin:
  g->jump_level--;
  return cap;
  } // end of AllocCatInfo

#if 0
/***********************************************************************/
/*  Check for nulls and reset them to Null (?) values.                 */
/***********************************************************************/
static void ResetNullValues(CATPARM *cap)
  {
  int      i, n, ncol;
  PCOLRES  crp;
  PQRYRES  qrp = cap->Qrp;

#if defined(_DEBUG)
  assert(qrp);
#endif

  ncol = qrp->Nbcol;

  for (i = 0, crp = qrp->Colresp; i < ncol && crp; i++, crp = crp->Next)
    for (n = 0; n < qrp->Nblin; n++)
      if (cap->Vlen[i][n] == SQL_NULL_DATA)
        crp->Kdata->Reset(n);

  } // end of ResetNullValues
#endif

/***********************************************************************/
/*  ODBCColumns: constructs the result blocks containing all columns   */
/*  of an ODBC table that will be retrieved by GetData commands.       */
/***********************************************************************/
PQRYRES ODBCColumns(PGLOBAL g, char *dsn, char *db, char *table,
                               char *colpat, int maxres, bool info)
  {
  int  buftyp[] = {TYPE_STRING, TYPE_STRING, TYPE_STRING, TYPE_STRING,
                   TYPE_SHORT,  TYPE_STRING, TYPE_INT,    TYPE_INT,
                   TYPE_SHORT,  TYPE_SHORT,  TYPE_SHORT,  TYPE_STRING};
  XFLD fldtyp[] = {FLD_CAT,   FLD_SCHEM,    FLD_TABNAME, FLD_NAME,
                   FLD_TYPE,  FLD_TYPENAME, FLD_PREC,    FLD_LENGTH,
                   FLD_SCALE, FLD_RADIX,    FLD_NULL,    FLD_REM};
  unsigned int length[] = {0, 0, 0, 0, 6, 0, 10, 10, 6, 6, 6, 0};
  int      n, ncol = 12;
  PQRYRES  qrp;
  CATPARM *cap;
  ODBConn *ocp = NULL;

  /************************************************************************/
  /*  Do an evaluation of the result size.                                */
  /************************************************************************/
  if (!info) {
    ocp = new(g) ODBConn(g, NULL);

    if (ocp->Open(dsn, 10) < 1)  // openReadOnly + noODBCdialog
      return NULL;

    if (table && !strchr(table, '%')) {
      // We fix a MySQL limit because some data sources return 32767
      n = ocp->GetMaxValue(SQL_MAX_COLUMNS_IN_TABLE);
      maxres = (n) ? min(n, 4096) : 4096;
    } else if (!maxres)
      maxres = 20000;

//  n = ocp->GetMaxValue(SQL_MAX_CATALOG_NAME_LEN);
//  length[0] = (n) ? (n + 1) : 0;
//  n = ocp->GetMaxValue(SQL_MAX_SCHEMA_NAME_LEN);
//  length[1] = (n) ? (n + 1) : 0;
//  n = ocp->GetMaxValue(SQL_MAX_TABLE_NAME_LEN);
//  length[2] = (n) ? (n + 1) : 0;
    n = ocp->GetMaxValue(SQL_MAX_COLUMN_NAME_LEN);
    length[3] = (n) ? (n + 1) : 128;
  } else {                 // Info table
    maxres = 0;
    length[0] = 128;
    length[1] = 128;
    length[2] = 128;
    length[3] = 128;
    length[5] = 30;
    length[11] = 255;
  } // endif ocp

  if (trace)
    htrc("ODBCColumns: max=%d len=%d,%d,%d\n",
         maxres, length[0], length[1], length[2], length[3]);

  /************************************************************************/
  /*  Allocate the structures used to refer to the result set.            */
  /************************************************************************/
  qrp = PlgAllocResult(g, ncol, maxres, IDS_COLUMNS,
                          buftyp, fldtyp, length, false, true);

  if (info || !qrp)                      // Info table
    return qrp;

  if (trace)
    htrc("Getting col results ncol=%d\n", qrp->Nbcol);

  if (!(cap = AllocCatInfo(g, CAT_COL, db, table, qrp)))
    return NULL;

  cap->Pat = (PUCHAR)colpat;

  /************************************************************************/
  /*  Now get the results into blocks.                                    */
  /************************************************************************/
  if ((n = ocp->GetCatInfo(cap)) >= 0) {
    qrp->Nblin = n;
//  ResetNullValues(cap);

    if (trace)
      htrc("Columns: NBCOL=%d NBLIN=%d\n", qrp->Nbcol, qrp->Nblin);

  } else
    qrp = NULL;

  /* Cleanup */
  ocp->Close();

  /************************************************************************/
  /*  Return the result pointer for use by GetData routines.              */
  /************************************************************************/
  return qrp;
  } // end of ODBCColumns

/**************************************************************************/
/*  ODBCSrcCols: constructs the result blocks containing the              */
/*  description of all the columns of a Srcdef option.                    */
/**************************************************************************/
PQRYRES ODBCSrcCols(PGLOBAL g, char *dsn, char *src)
  {
  ODBConn *ocp = new(g) ODBConn(g, NULL);

  return ocp->GetMetaData(g, dsn, src);
  } // end of ODBCSrcCols

#if 0
/**************************************************************************/
/* MyODBCCols: returns column info as required by ha_connect::pre_create. */
/**************************************************************************/
PQRYRES MyODBCCols(PGLOBAL g, char *dsn, char *tab, bool info)
  {
//  int      i, type, len, prec;
//  PCOLRES  crp, crpt, crpl, crpp;
  PQRYRES  qrp;
  ODBConn *ocp;

  /**********************************************************************/
  /*  Open the connection with the ODBC data source.                    */
  /**********************************************************************/
  if (!info) {
    ocp = new(g) ODBConn(g, NULL);

    if (ocp->Open(dsn, 2) < 1)        // 2 is openReadOnly
      return NULL;

  } else
    ocp = NULL;

  /**********************************************************************/
  /*  Get the information about the ODBC table columns.                 */
  /**********************************************************************/
  if ((qrp = ODBCColumns(g, ocp, dsn, NULL, tab, 0, NULL)) && ocp)
    dsn = ocp->GetConnect();        // Complete connect string

  /************************************************************************/
  /*  Close the local connection.                                         */
  /************************************************************************/
  if (ocp)
    ocp->Close();

  if (!qrp)
    return NULL;             // Error in ODBCColumns

  /************************************************************************/
  /*  Keep only the info used by ha_connect::pre_create.                  */
  /************************************************************************/
  qrp->Colresp = qrp->Colresp->Next->Next;  // Skip Schema and Table names

  crpt = qrp->Colresp->Next;                // SQL type
  crpl = crpt->Next->Next;                  // Length
  crpp = crpl->Next->Next;                  // Decimals

  for (int i = 0; i < qrp->Nblin; i++) {
    // Types must be PLG types, not SQL types
    type = crpt->Kdata->GetIntValue(i);
    len  = crpl->Kdata->GetIntValue(i);
    prec = crpp->Kdata->GetIntValue(i);
    type = TranslateSQLType(type, prec, len);
    crpt->Kdata->SetValue(type, i);

    // Some data sources do not count prec in length
    if (type == TYPE_DOUBLE)
      len += (prec + 2);                    // To be safe

    // Could have been changed for blobs or numeric
    crpl->Kdata->SetValue(len, i);          
    } // endfor i

  crpp->Next = crpp->Next->Next->Next;      // Should be Remark

  // Renumber crp's for flag comparison
  for (i = 0, crp = qrp->Colresp; crp; crp = crp->Next)
    crp->Ncol = ++i;

  qrp->Nbcol = i;             // Should be 7; was 11, skipped 4
  return qrp;
  } // end of MyODBCCols
#endif // 0

/*************************************************************************/
/*  ODBCDrivers: constructs the result blocks containing all ODBC        */
/*  drivers available on the local host.                                 */
/*  Called with info=true to have result column names.                   */
/*************************************************************************/
PQRYRES ODBCDrivers(PGLOBAL g, int maxres, bool info)
  {
  int      buftyp[] = {TYPE_STRING, TYPE_STRING};
  XFLD     fldtyp[] = {FLD_NAME, FLD_REM};
  unsigned int length[] = {128, 256};
  int      ncol = 2;
  PQRYRES  qrp;
  ODBConn *ocp = NULL;

  /************************************************************************/
  /*  Do an evaluation of the result size.                                */
  /************************************************************************/
  if (!info) {
    ocp = new(g) ODBConn(g, NULL);

    if (!maxres)
      maxres = 256;         // Estimated max number of drivers

  } else
    maxres = 0;

  if (trace)
    htrc("ODBCDrivers: max=%d len=%d\n", maxres, length[0]);

  /************************************************************************/
  /*  Allocate the structures used to refer to the result set.            */
  /************************************************************************/
  qrp = PlgAllocResult(g, ncol, maxres, IDS_DRIVER, 
                          buftyp, fldtyp, length, false, true);

  /************************************************************************/
  /*  Now get the results into blocks.                                    */
  /************************************************************************/
  if (!info && qrp && ocp->GetDrivers(qrp))
    qrp = NULL;

  /************************************************************************/
  /*  Return the result pointer for use by GetData routines.              */
  /************************************************************************/
  return qrp;
  } // end of ODBCDrivers

/*************************************************************************/
/*  ODBCDataSources: constructs the result blocks containing all ODBC    */
/*  data sources available on the local host.                            */
/*  Called with info=true to have result column names.                   */
/*************************************************************************/
PQRYRES ODBCDataSources(PGLOBAL g, int maxres, bool info)
  {
  int      buftyp[] = {TYPE_STRING, TYPE_STRING};
  XFLD     fldtyp[] = {FLD_NAME, FLD_REM};
  unsigned int length[] = {0, 256};
  int      n = 0, ncol = 2;
  PQRYRES  qrp;
  ODBConn *ocp = NULL;

  /************************************************************************/
  /*  Do an evaluation of the result size.                                */
  /************************************************************************/
  if (!info) {
    ocp = new(g) ODBConn(g, NULL);
    n = ocp->GetMaxValue(SQL_MAX_DSN_LENGTH);
    length[0] = (n) ? (n + 1) : 256;

    if (!maxres)
      maxres = 512;         // Estimated max number of data sources

  } else {
    length[0] = 256;
    maxres = 0;
  } // endif info

  if (trace)
    htrc("ODBCDataSources: max=%d len=%d\n", maxres, length[0]);

  /************************************************************************/
  /*  Allocate the structures used to refer to the result set.            */
  /************************************************************************/
  qrp = PlgAllocResult(g, ncol, maxres, IDS_DSRC, 
                          buftyp, fldtyp, length, false, true);

  /************************************************************************/
  /*  Now get the results into blocks.                                    */
  /************************************************************************/
  if (!info && qrp && ocp->GetDataSources(qrp))
    qrp = NULL;

  /************************************************************************/
  /*  Return the result pointer for use by GetData routines.              */
  /************************************************************************/
  return qrp;
  } // end of ODBCDataSources

/**************************************************************************/
/*  ODBCTables: constructs the result blocks containing all tables in     */
/*  an ODBC database that will be retrieved by GetData commands.          */
/**************************************************************************/
PQRYRES ODBCTables(PGLOBAL g, char *dsn, char *db, char *tabpat,
                              int maxres, bool info)
  {
  int      buftyp[] = {TYPE_STRING, TYPE_STRING, TYPE_STRING,
                       TYPE_STRING, TYPE_STRING};
  XFLD     fldtyp[] = {FLD_CAT,  FLD_SCHEM, FLD_NAME,
                       FLD_TYPE, FLD_REM};
  unsigned int length[] = {0, 0, 0, 16, 0};
  int      n, ncol = 5;
  PQRYRES  qrp;
  CATPARM *cap;
  ODBConn *ocp = NULL;

  /************************************************************************/
  /*  Do an evaluation of the result size.                                */
  /************************************************************************/
  if (!info) {
    /**********************************************************************/
    /*  Open the connection with the ODBC data source.                    */
    /**********************************************************************/
    ocp = new(g) ODBConn(g, NULL);

    if (ocp->Open(dsn, 2) < 1)        // 2 is openReadOnly
      return NULL;

    if (!maxres)
      maxres = 10000;                 // This is completely arbitrary

//  n = ocp->GetMaxValue(SQL_MAX_CATALOG_NAME_LEN);
//  length[0] = (n) ? (n + 1) : 0;
//  n = ocp->GetMaxValue(SQL_MAX_SCHEMA_NAME_LEN);
//  length[1] = (n) ? (n + 1) : 0;
    n = ocp->GetMaxValue(SQL_MAX_TABLE_NAME_LEN);
    length[2] = (n) ? (n + 1) : 128;
  } else {
    maxres = 0;
    length[0] = 128;
    length[1] = 128;
    length[2] = 128;
    length[4] = 255;
  } // endif info

  if (trace)
    htrc("ODBCTables: max=%d len=%d,%d\n", maxres, length[0], length[1]);

  /************************************************************************/
  /*  Allocate the structures used to refer to the result set.            */
  /************************************************************************/
  qrp = PlgAllocResult(g, ncol, maxres, IDS_TABLES, buftyp,
                                        fldtyp, length, false, true);

  if (info || !qrp)
    return qrp;

  if (!(cap = AllocCatInfo(g, CAT_TAB, db, tabpat, qrp)))
    return NULL;

//cap->Pat = (PUCHAR)tabtyp;

  if (trace)
    htrc("Getting table results ncol=%d\n", cap->Qrp->Nbcol);

  /************************************************************************/
  /*  Now get the results into blocks.                                    */
  /************************************************************************/
  if ((n = ocp->GetCatInfo(cap)) >= 0) {
    qrp->Nblin = n;
//  ResetNullValues(cap);

    if (trace)
      htrc("Tables: NBCOL=%d NBLIN=%d\n", qrp->Nbcol, qrp->Nblin);

  } else
    qrp = NULL;

  /************************************************************************/
  /*  Close any local connection.                                         */
  /************************************************************************/
  ocp->Close();

  /************************************************************************/
  /*  Return the result pointer for use by GetData routines.              */
  /************************************************************************/
  return qrp;
  } // end of ODBCTables

#if 0                           // Currently not used by CONNECT
/**************************************************************************/
/*  PrimaryKeys: constructs the result blocks containing all the          */
/*  ODBC catalog information concerning primary keys.                     */
/**************************************************************************/
PQRYRES ODBCPrimaryKeys(PGLOBAL g, ODBConn *op, char *dsn, char *table)
  {
  static int buftyp[] = {TYPE_STRING, TYPE_STRING, TYPE_STRING,
                         TYPE_STRING, TYPE_SHORT,  TYPE_STRING};
  static unsigned int length[] = {0, 0, 0, 0, 6, 128};
  int      n, ncol = 5;
  int     maxres;
  PQRYRES  qrp;
  CATPARM *cap;
  ODBConn *ocp = op;

  if (!op) {
    /**********************************************************************/
    /*  Open the connection with the ODBC data source.                    */
    /**********************************************************************/
    ocp = new(g) ODBConn(g, NULL);

    if (ocp->Open(dsn, 2) < 1)        // 2 is openReadOnly
      return NULL;

    } // endif op

  /************************************************************************/
  /*  Do an evaluation of the result size.                                */
  /************************************************************************/
  n = ocp->GetMaxValue(SQL_MAX_COLUMNS_IN_TABLE);
  maxres = (n) ? (int)n : 250;
  n = ocp->GetMaxValue(SQL_MAX_CATALOG_NAME_LEN);
  length[0] = (n) ? (n + 1) : 128;
  n = ocp->GetMaxValue(SQL_MAX_SCHEMA_NAME_LEN);
  length[1] = (n) ? (n + 1) : 128;
  n = ocp->GetMaxValue(SQL_MAX_TABLE_NAME_LEN);
  length[2] = (n) ? (n + 1) : 128;
  n = ocp->GetMaxValue(SQL_MAX_COLUMN_NAME_LEN);
  length[3] = (n) ? (n + 1) : 128;

  if (trace)
    htrc("ODBCPrimaryKeys: max=%d len=%d,%d,%d\n",
         maxres, length[0], length[1], length[2]);

  /************************************************************************/
  /*  Allocate the structure used to refer to the result set.             */
  /************************************************************************/
  qrp = PlgAllocResult(g, ncol, maxres, IDS_PKEY,
                          buftyp, NULL, length, false, true);

  if (trace)
    htrc("Getting pkey results ncol=%d\n", qrp->Nbcol);

  cap = AllocCatInfo(g, CAT_KEY, NULL, table, qrp);

  /************************************************************************/
  /*  Now get the results into blocks.                                    */
  /************************************************************************/
  if ((n = ocp->GetCatInfo(cap)) >= 0) {
    qrp->Nblin = n;
//  ResetNullValues(cap);

    if (trace)
      htrc("PrimaryKeys: NBCOL=%d NBLIN=%d\n", qrp->Nbcol, qrp->Nblin);

  } else
    qrp = NULL;

  /************************************************************************/
  /*  Close any local connection.                                         */
  /************************************************************************/
  if (!op)
    ocp->Close();

  /************************************************************************/
  /*  Return the result pointer for use by GetData routines.              */
  /************************************************************************/
  return qrp;
  } // end of ODBCPrimaryKeys

/**************************************************************************/
/*  Statistics: constructs the result blocks containing statistics        */
/*  about one or several tables to be retrieved by GetData commands.      */
/**************************************************************************/
PQRYRES ODBCStatistics(PGLOBAL g, ODBConn *op, char *dsn, char *pat,
                                               int un, int acc)
  {
  static int buftyp[] = {TYPE_STRING,
                         TYPE_STRING, TYPE_STRING, TYPE_SHORT, TYPE_STRING,
                         TYPE_STRING, TYPE_SHORT,  TYPE_SHORT, TYPE_STRING,
                         TYPE_STRING, TYPE_INT,   TYPE_INT,  TYPE_STRING};
  static unsigned int length[] = {0, 0, 0 ,6 ,0 ,0 ,6 ,6 ,0 ,2 ,10 ,10 ,128};
  int      n, ncol = 13;
  int     maxres;
  PQRYRES  qrp;
  CATPARM *cap;
  ODBConn *ocp = op;

  if (!op) {
    /**********************************************************************/
    /*  Open the connection with the ODBC data source.                    */
    /**********************************************************************/
    ocp = new(g) ODBConn(g, NULL);

    if (ocp->Open(dsn, 2) < 1)        // 2 is openReadOnly
      return NULL;

    } // endif op

  /************************************************************************/
  /*  Do an evaluation of the result size.                                */
  /************************************************************************/
  n = 1 + ocp->GetMaxValue(SQL_MAX_COLUMNS_IN_INDEX);
  maxres = (n) ? (int)n : 32;
  n = ocp->GetMaxValue(SQL_MAX_SCHEMA_NAME_LEN);
  length[1] = (n) ? (n + 1) : 128;
  n = ocp->GetMaxValue(SQL_MAX_TABLE_NAME_LEN);
  length[2] = length[5] = (n) ? (n + 1) : 128;
  n = ocp->GetMaxValue(SQL_MAX_CATALOG_NAME_LEN);
  length[0] = length[4] = (n) ? (n + 1) : length[2];
  n = ocp->GetMaxValue(SQL_MAX_COLUMN_NAME_LEN);
  length[7] = (n) ? (n + 1) : 128;

  if (trace)
    htrc("SemStatistics: max=%d pat=%s\n", maxres, SVP(pat));

  /************************************************************************/
  /*  Allocate the structure used to refer to the result set.             */
  /************************************************************************/
  qrp = PlgAllocResult(g, ncol, maxres, IDS_STAT,
                          buftyp, NULL, length, false, true);

  if (trace)
    htrc("Getting stat results ncol=%d\n", qrp->Nbcol);

  cap = AllocCatInfo(g, CAT_STAT, NULL, pat, qrp);
  cap->Unique = (un < 0) ? SQL_INDEX_UNIQUE : (UWORD)un;
  cap->Accuracy = (acc < 0) ? SQL_QUICK : (UWORD)acc;

  /************************************************************************/
  /*  Now get the results into blocks.                                    */
  /************************************************************************/
  if ((n = ocp->GetCatInfo(cap)) >= 0) {
    qrp->Nblin = n;
//  ResetNullValues(cap);

    if (trace)
      htrc("Statistics: NBCOL=%d NBLIN=%d\n", qrp->Nbcol, qrp->Nblin);

  } else
    qrp = NULL;

  /************************************************************************/
  /*  Close any local connection.                                         */
  /************************************************************************/
  if (!op)
    ocp->Close();

  /************************************************************************/
  /*  Return the result pointer for use by GetData routines.              */
  /************************************************************************/
  return qrp;
  } // end of Statistics
#endif // 0

/***********************************************************************/
/*  Implementation of DBX class.                                       */
/***********************************************************************/
DBX::DBX(RETCODE rc, PSZ msg)
  {
  m_RC = rc;
  m_Msg = msg;

  for (int i = 0; i < MAX_NUM_OF_MSG; i++)
    m_ErrMsg[i] = NULL;

  } // end of DBX constructor

/***********************************************************************/
/*  This function is called by ThrowDBX.                               */
/***********************************************************************/
void DBX::BuildErrorMessage(ODBConn* pdb, HSTMT hstmt)
  {
  if (pdb) {
    SWORD   len;
    RETCODE rc;
    UCHAR   msg[SQL_MAX_MESSAGE_LENGTH + 1];
    UCHAR   state[SQL_SQLSTATE_SIZE + 1];
    SDWORD  native;
    PGLOBAL g = pdb->m_G;

    rc = SQLError(pdb->m_henv, pdb->m_hdbc, hstmt, state,
                  &native, msg, SQL_MAX_MESSAGE_LENGTH - 1, &len);

    if (rc != SQL_INVALID_HANDLE) {
    // Skip non-errors
      for (int i = 0; i < MAX_NUM_OF_MSG
              && (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO)
              && strcmp((char*)state, "00000"); i++) {
        m_ErrMsg[i] = (PSZ)PlugSubAlloc(g, NULL, strlen((char*)msg) + 1);
        strcpy(m_ErrMsg[i], (char*)msg);

        if (trace)
          htrc("%s: %s, Native=%d\n", state, msg, native);

        rc = SQLError(pdb->m_henv, pdb->m_hdbc, hstmt, state,
                      &native, msg, SQL_MAX_MESSAGE_LENGTH - 1, &len);

        } // endfor i

      return;
    } else {
      snprintf((char*)msg, SQL_MAX_MESSAGE_LENGTH + 1, "%s: %s", m_Msg,
               MSG(BAD_HANDLE_VAL));
      m_ErrMsg[0] = (PSZ)PlugSubAlloc(g, NULL, strlen((char*)msg) + 1);
      strcpy(m_ErrMsg[0], (char*)msg);

      if (trace)
        htrc("%s: rc=%hd\n", SVP(m_ErrMsg[0]), m_RC); 

      return;
    } // endif rc

  } else
    m_ErrMsg[0] = "No connexion address provided";

  if (trace)
    htrc("%s: rc=%hd (%s)\n", SVP(m_Msg), m_RC, SVP(m_ErrMsg[0])); 

  } // end of BuildErrorMessage

const char *DBX::GetErrorMessage(int i)
  {
    if (i < 0 || i >= MAX_NUM_OF_MSG)
      return "No ODBC error";
    else if (m_ErrMsg[i])
      return m_ErrMsg[i];
    else
      return (m_Msg) ? m_Msg : "Unknown error";

  } // end of GetErrorMessage

/***********************************************************************/
/*  ODBConn construction/destruction.                                  */
/***********************************************************************/
ODBConn::ODBConn(PGLOBAL g, TDBODBC *tdbp)
  {
  m_G = g;
  m_Tdb = tdbp;
  m_henv = SQL_NULL_HENV;
  m_hdbc = SQL_NULL_HDBC;
//m_Recset = NULL
  m_hstmt = SQL_NULL_HSTMT;
  m_LoginTimeout = DEFAULT_LOGIN_TIMEOUT;
  m_QueryTimeout = DEFAULT_QUERY_TIMEOUT;
  m_UpdateOptions = 0;
  m_RowsetSize = (DWORD)((tdbp) ? tdbp->Rows : 10);
  m_Catver = (tdbp) ? tdbp->Catver : 0;
  m_Connect = NULL;
  m_Updatable = true;
  m_Transact = false;
  m_IDQuoteChar[0] = '"';
  m_IDQuoteChar[1] = 0;
//*m_ErrMsg = '\0';
  } // end of ODBConn

//ODBConn::~ODBConn()
//  {
//if (Connected())
//  EndCom();

//  } // end of ~ODBConn

/***********************************************************************/
/*  Screen for errors.                                                 */
/***********************************************************************/
bool ODBConn::Check(RETCODE rc)
  {
  switch (rc) {
    case SQL_SUCCESS_WITH_INFO:
      if (trace) {
        DBX x(rc);

        x.BuildErrorMessage(this, m_hstmt);
        htrc("ODBC Success With Info, hstmt=%p %s\n",
          m_hstmt, x.GetErrorMessage(0));
        } // endif trace

      // Fall through
    case SQL_SUCCESS:
    case SQL_NO_DATA_FOUND:
      return true;
    } // endswitch rc

  return false;
  } // end of Check

/***********************************************************************/
/*  DB exception throw routines.                                       */
/***********************************************************************/
void ODBConn::ThrowDBX(RETCODE rc, PSZ msg, HSTMT hstmt)
  {
  DBX* xp = new(m_G) DBX(rc, msg);

  xp->BuildErrorMessage(this, hstmt);
  throw xp;
  } // end of ThrowDBX

void ODBConn::ThrowDBX(PSZ msg)
  {
  DBX* xp = new(m_G) DBX(0, msg);

  xp->m_ErrMsg[0] = msg;
  throw xp;
  } // end of ThrowDBX

/***********************************************************************/
/*  Utility routine.                                                   */
/***********************************************************************/
PSZ ODBConn::GetStringInfo(ushort infotype)
  {
//ASSERT(m_hdbc != SQL_NULL_HDBC);
  char   *p, buffer[MAX_STRING_INFO];
  SWORD   result;
  RETCODE rc;

  rc = SQLGetInfo(m_hdbc, infotype, buffer, sizeof(buffer), &result);

  if (!Check(rc)) {
    ThrowDBX(rc, "SQLGetInfo");  // Temporary
//  *buffer = '\0';
    } // endif rc

  p = (char *)PlugSubAlloc(m_G, NULL, strlen(buffer) + 1);
  strcpy(p, buffer);
  return p;
  } // end of GetStringInfo

/***********************************************************************/
/*  Utility routine.                                                   */
/***********************************************************************/
int ODBConn::GetMaxValue(ushort infotype)
  {
//ASSERT(m_hdbc != SQL_NULL_HDBC);
  ushort  maxval;
  RETCODE rc;

  rc = SQLGetInfo(m_hdbc, infotype, &maxval, 0, NULL);

  if (!Check(rc))
    maxval = 0;

  return (int)maxval;
  } // end of GetMaxValue

/***********************************************************************/
/*  Utility routines.                                                  */
/***********************************************************************/
void ODBConn::OnSetOptions(HSTMT hstmt)
  {
  RETCODE rc;
  ASSERT(m_hdbc != SQL_NULL_HDBC);

  if ((signed)m_QueryTimeout != -1) {
    // Attempt to set query timeout.  Ignore failure
    rc = SQLSetStmtOption(hstmt, SQL_QUERY_TIMEOUT, m_QueryTimeout);

    if (!Check(rc))
      // don't attempt it again
      m_QueryTimeout = (DWORD)-1;

    } // endif m_QueryTimeout

  if (m_RowsetSize > 0) {
    // Attempt to set rowset size.
    // In case of failure reset it to 0 to use Fetch.
    rc = SQLSetStmtOption(hstmt, SQL_ROWSET_SIZE, m_RowsetSize);

    if (!Check(rc))
      // don't attempt it again
      m_RowsetSize = 0;

    } // endif m_RowsetSize

  } // end of OnSetOptions

/***********************************************************************/
/*  Open: connect to a data source.                                    */
/***********************************************************************/
int ODBConn::Open(PSZ ConnectString, DWORD options)
  {
  PGLOBAL& g = m_G;
//ASSERT_VALID(this);
//ASSERT(ConnectString == NULL || AfxIsValidString(ConnectString));
  ASSERT(!(options & noOdbcDialog && options & forceOdbcDialog));

  m_Updatable = !(options & openReadOnly);
  m_Connect = ConnectString;

  // Allocate the HDBC and make connection
  try {
    /*PSZ ver;*/

    AllocConnect(options);
    /*ver = GetStringInfo(SQL_ODBC_VER);*/

    if (Connect(options)) {
      strcpy(g->Message, MSG(CONNECT_CANCEL));
      return 0;
      } // endif

    /*ver = GetStringInfo(SQL_DRIVER_ODBC_VER);*/
  } catch(DBX *xp) {
//    strcpy(g->Message, xp->m_ErrMsg[0]);
    strcpy(g->Message, xp->GetErrorMessage(0));
    Close();
//  Free();
    return -1;
  } // end try-catch

  // Verify support for required functionality and cache info
  VerifyConnect();
  GetConnectInfo();
  return 1;
  } // end of Open

/***********************************************************************/
/*  Allocate an henv (first time called) and hdbc.                     */
/***********************************************************************/
void ODBConn::AllocConnect(DWORD Options)
  {
  if (m_hdbc != SQL_NULL_HDBC)
    return;

  RETCODE rc;
//AfxLockGlobals(CRIT_ODBC);

  // Need to allocate an environment for first connection
  if (m_henv == SQL_NULL_HENV) {
//  ASSERT(m_nAlloc == 0);

    rc = SQLAllocEnv(&m_henv);

    if (!Check(rc)) {
//    AfxUnlockGlobals(CRIT_ODBC);
      ThrowDBX(rc, "SQLAllocEnv");  // Fatal
      } // endif rc

    } // endif m_henv

  // Do the real thing, allocating connection data
  rc = SQLAllocConnect(m_henv, &m_hdbc);

  if (!Check(rc)) {
//  AfxUnlockGlobals(CRIT_ODBC);
    ThrowDBX(rc, "SQLAllocConnect");  // Fatal
    } // endif rc

//m_nAlloc++;                          // allocated at last
//AfxUnlockGlobals(CRIT_ODBC);

#if defined(_DEBUG)
  if (Options & traceSQL) {
    SQLSetConnectOption(m_hdbc, SQL_OPT_TRACEFILE, (DWORD)"xodbc.out");
    SQLSetConnectOption(m_hdbc, SQL_OPT_TRACE, 1);
    } // endif
#endif // _DEBUG

  rc = SQLSetConnectOption(m_hdbc, SQL_LOGIN_TIMEOUT, m_LoginTimeout);

  if (trace && rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
    htrc("Warning: Failure setting login timeout\n");

  if (!m_Updatable) {
    rc = SQLSetConnectOption(m_hdbc, SQL_ACCESS_MODE, SQL_MODE_READ_ONLY);
    
    if (trace && rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
      htrc("Warning: Failure setting read only access mode\n");

    } // endif

  // Turn on cursor lib support
  if (Options & useCursorLib)
    rc = SQLSetConnectOption(m_hdbc, SQL_ODBC_CURSORS, SQL_CUR_USE_ODBC);

  return;
  } // end of AllocConnect

/***********************************************************************/
/*  Connect to data source using SQLDriverConnect.                     */
/***********************************************************************/
bool ODBConn::Connect(DWORD Options)
  {
  RETCODE rc;
  SWORD   nResult;
  PUCHAR  ConnOut = (PUCHAR)PlugSubAlloc(m_G, NULL, MAX_CONNECT_LEN);
  UWORD   wConnectOption = SQL_DRIVER_COMPLETE;
#if defined(WIN32)
  HWND    hWndTop = GetForegroundWindow();
  HWND    hWnd = GetParent(hWndTop);

  if (hWnd == NULL)
    hWnd = GetDesktopWindow();
#else   // !WIN32
  HWND    hWnd = (HWND)1;
#endif  // !WIN32
  PGLOBAL& g = m_G;
  PDBUSER dup = PlgGetUser(g);

//if (Options & noOdbcDialog || dup->Remote)
    wConnectOption = SQL_DRIVER_NOPROMPT;
//else if (Options & forceOdbcDialog)
//  wConnectOption = SQL_DRIVER_PROMPT;

  rc = SQLDriverConnect(m_hdbc, hWnd, (PUCHAR)m_Connect,
                        SQL_NTS, ConnOut, MAX_CONNECT_LEN,
                        &nResult, wConnectOption);

#if defined(WIN32)
  if (hWndTop)
    EnableWindow(hWndTop, true);
#endif   // WIN32

  // If user hit 'Cancel'
  if (rc == SQL_NO_DATA_FOUND) {
    Close();
//  Free();
    return true;
    } // endif rc

  if (!Check(rc))
    ThrowDBX(rc, "SQLDriverConnect");

  // Save connect string returned from ODBC
  m_Connect = (PSZ)ConnOut;

  // All done
  return false;
  } // end of Connect

void ODBConn::VerifyConnect()
  {
#if defined(NEWMSG) || defined(XMSG)
  PGLOBAL& g = m_G;
#endif   // NEWMSG  ||         XMSG
  RETCODE  rc;
  SWORD    result;
  SWORD    conformance;

  rc = SQLGetInfo(m_hdbc, SQL_ODBC_API_CONFORMANCE,
                  &conformance, sizeof(conformance), &result);

  if (!Check(rc))
    ThrowDBX(rc, "SQLGetInfo");

  if (conformance < SQL_OAC_LEVEL1)
    ThrowDBX(MSG(API_CONF_ERROR));

  rc = SQLGetInfo(m_hdbc, SQL_ODBC_SQL_CONFORMANCE,
                  &conformance, sizeof(conformance), &result);

  if (!Check(rc))
    ThrowDBX(rc, "SQLGetInfo");

  if (conformance < SQL_OSC_MINIMUM)
    ThrowDBX(MSG(SQL_CONF_ERROR));

  } // end of VerifyConnect

void ODBConn::GetConnectInfo()
  {
  RETCODE rc;
  SWORD   nResult;
#if 0                   // Update not implemented yet
  UDWORD  DrvPosOp;

  // Reset the database update options
  m_UpdateOptions = 0;

  // Check for SQLSetPos support
  rc = SQLGetInfo(m_hdbc, SQL_POS_OPERATIONS,
                  &DrvPosOp, sizeof(DrvPosOp), &nResult);

  if (Check(rc) &&
      (DrvPosOp & SQL_POS_UPDATE) &&
      (DrvPosOp & SQL_POS_DELETE) &&
      (DrvPosOp & SQL_POS_ADD))
     m_UpdateOptions = SQL_SETPOSUPDATES;

  // Check for positioned update SQL support
  UDWORD PosStatements;

  rc = SQLGetInfo(m_hdbc, SQL_POSITIONED_STATEMENTS,
                        &PosStatements, sizeof(PosStatements),
                        &nResult);

  if (Check(rc) &&
      (PosStatements & SQL_PS_POSITIONED_DELETE) &&
      (PosStatements & SQL_PS_POSITIONED_UPDATE))
    m_UpdateOptions |= SQL_POSITIONEDSQL;

  if (m_Updatable) {
    // Make sure data source is Updatable
    char ReadOnly[10];

    rc = SQLGetInfo(m_hdbc, SQL_DATA_SOURCE_READ_ONLY,
                    ReadOnly, sizeof(ReadOnly), &nResult);

    if (Check(rc) && nResult == 1)
      m_Updatable = !!strcmp(ReadOnly, "Y");
    else
      m_Updatable = false;

    if (trace)
      htrc("Warning: data source is readonly\n");

  } else // Make data source is !Updatable
    rc = SQLSetConnectOption(m_hdbc, SQL_ACCESS_MODE,
                                     SQL_MODE_READ_ONLY);
#endif   // 0

  // Get the quote char to use when constructing SQL
  rc = SQLGetInfo(m_hdbc, SQL_IDENTIFIER_QUOTE_CHAR,
                  m_IDQuoteChar, sizeof(m_IDQuoteChar), &nResult);

  if (trace)
    htrc("DBMS: %s, Version: %s, rc=%d\n",
         GetStringInfo(SQL_DBMS_NAME), GetStringInfo(SQL_DBMS_VER), rc);

  } // end of GetConnectInfo

/***********************************************************************/
/*  Allocate record set and execute an SQL query.                      */
/***********************************************************************/
int ODBConn::ExecDirectSQL(char *sql, ODBCCOL *tocols)
  {
  PGLOBAL& g = m_G;
  void    *buffer;
  bool     b;
  UWORD    n;
  SWORD    len, tp, ncol = 0;
  ODBCCOL *colp;
  RETCODE  rc;
  HSTMT    hstmt;

  try {
    b = false;

    if (m_hstmt) {
//    All this did not seems to make sense and was been commented out
//    if (IsOpen())
//      Close(SQL_CLOSE);

      rc = SQLFreeStmt(m_hstmt, SQL_CLOSE);

      if (trace && !Check(rc))
        htrc("Error: SQLFreeStmt rc=%d\n", rc);

      hstmt = m_hstmt;
      m_hstmt = NULL;
      ThrowDBX(MSG(SEQUENCE_ERROR));
    } else {
      rc = SQLAllocStmt(m_hdbc, &hstmt);

      if (!Check(rc))
        ThrowDBX(SQL_INVALID_HANDLE, "SQLAllocStmt");

    } // endif hstmt

    OnSetOptions(hstmt);
    b = true;

    if (trace)
      htrc("ExecDirect hstmt=%p %.64s\n", hstmt, sql);

    if (m_Tdb->Srcdef) {
      // Be sure this is a query returning a result set
      do {
        rc = SQLPrepare(hstmt, (PUCHAR)sql, SQL_NTS);
        } while (rc == SQL_STILL_EXECUTING);

      if (!Check(rc))
        ThrowDBX(rc, "SQLPrepare", hstmt);

      if (!Check(rc = SQLNumResultCols(hstmt, &ncol)))
        ThrowDBX(rc, "SQLNumResultCols", hstmt);

      if (ncol == 0) {
        strcpy(g->Message, "This Srcdef does not return a result set");
        return -1;
        } // endif ncol

      // Ok, now we can proceed
      do {
        rc = SQLExecute(hstmt);
        } while (rc == SQL_STILL_EXECUTING);

      if (!Check(rc))
        ThrowDBX(rc, "SQLExecute", hstmt);

    } else {
      do {
        rc = SQLExecDirect(hstmt, (PUCHAR)sql, SQL_NTS);
        } while (rc == SQL_STILL_EXECUTING);

      if (!Check(rc))
        ThrowDBX(rc, "SQLExecDirect", hstmt);

      do {
        rc = SQLNumResultCols(hstmt, &ncol);
        } while (rc == SQL_STILL_EXECUTING);

    } // endif Srcdef

    for (n = 0, colp = tocols; colp; colp = (PODBCCOL)colp->GetNext())
      if (!colp->IsSpecial())
      n++;

    // n can be 0 for query such as Select count(*) from table
    if (n && n != (UWORD)ncol)
      ThrowDBX(MSG(COL_NUM_MISM));

    // Now bind the column buffers
    for (n = 1, colp = tocols; colp; colp = (PODBCCOL)colp->GetNext())
      if (!colp->IsSpecial()) {
        buffer = colp->GetBuffer(m_RowsetSize);
        len = colp->GetBuflen();
        tp = GetSQLCType(colp->GetResultType());

        if (tp == SQL_TYPE_NULL) {
          sprintf(m_G->Message, MSG(INV_COLUMN_TYPE),
                  colp->GetResultType(), SVP(colp->GetName()));
          ThrowDBX(m_G->Message);
          } // endif tp

        if (trace)
          htrc("Binding col=%u type=%d buf=%p len=%d slen=%p\n",
                  n, tp, buffer, len, colp->GetStrLen());

        rc = SQLBindCol(hstmt, n, tp, buffer, len, colp->GetStrLen());

        if (!Check(rc))
          ThrowDBX(rc, "SQLBindCol", hstmt);

        n++;
        } // endif pcol

  } catch(DBX *x) {
    if (trace)
      for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
        htrc(x->m_ErrMsg[i]);

    strcpy(m_G->Message, x->GetErrorMessage(0));

    if (b)
      SQLCancel(hstmt);

    rc = SQLFreeStmt(hstmt, SQL_DROP);
    m_hstmt = NULL;
    return -1;
  } // end try/catch

  m_hstmt = hstmt;
  return (int)m_RowsetSize;   // May have been reset in OnSetOptions
  } // end of ExecDirectSQL

/***********************************************************************/
/*  Get the number of lines of the result set.                         */
/***********************************************************************/
int ODBConn::GetResultSize(char *sql, ODBCCOL *colp)
  {
  int    n = 0;
  RETCODE rc;

  if (ExecDirectSQL(sql, colp) < 0)
    return -1;

  try {
    for (n = 0; ; n++) {
      do {
        rc = SQLFetch(m_hstmt);
        } while (rc == SQL_STILL_EXECUTING);

      if (!Check(rc))
        ThrowDBX(rc, "SQLFetch", m_hstmt);

      if (rc == SQL_NO_DATA_FOUND)
        break;

      } // endfor n

  } catch(DBX *x) {
    strcpy(m_G->Message, x->GetErrorMessage(0));
    
    if (trace)
      for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
        htrc(x->m_ErrMsg[i]);

    SQLCancel(m_hstmt);
    n = -2;
  } // end try/catch

  rc = SQLFreeStmt(m_hstmt, SQL_DROP);
  m_hstmt = NULL;

  if (n != 1)
    return -3;
  else
    return colp->GetIntValue();

  } // end of GetResultSize

/***********************************************************************/
/*  Fetch next row.                                                    */
/***********************************************************************/
int ODBConn::Fetch()
  {
  ASSERT(m_hstmt);
  int      irc;
  SQLULEN  crow;
  RETCODE  rc;
  PGLOBAL& g = m_G;

  try {
//  do {
    if (m_RowsetSize) {
      rc = SQLExtendedFetch(m_hstmt, SQL_FETCH_NEXT, 1, &crow, NULL);
    } else {
      rc = SQLFetch(m_hstmt);
      crow = 1;
    } // endif m_RowsetSize
//    } while (rc == SQL_STILL_EXECUTING);

    if (trace > 1)
      htrc("Fetch: hstmt=%p RowseSize=%d rc=%d\n",
                     m_hstmt, m_RowsetSize, rc);

    if (!Check(rc))
      ThrowDBX(rc, "Fetch", m_hstmt);

    irc = (rc == SQL_NO_DATA_FOUND) ? 0 : (int)crow;
  } catch(DBX *x) {
    if (trace)
      for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
        htrc(x->m_ErrMsg[i]);

    strcpy(g->Message, x->GetErrorMessage(0));
    irc = -1;
  } // end try/catch

  return irc;
  } // end of Fetch

/***********************************************************************/
/*  Prepare an SQL statement for insert.                               */
/***********************************************************************/
int ODBConn::PrepareSQL(char *sql)
  {
  PGLOBAL& g = m_G;
  bool     b;
  UINT     txn = 0;
  SWORD    nparm;
  RETCODE  rc;
  HSTMT    hstmt;

  if (m_Tdb->GetMode() != MODE_READ) {
    // Does the data source support transactions
    rc = SQLGetInfo(m_hdbc, SQL_TXN_CAPABLE, &txn, 0, NULL);

    if (Check(rc) && txn != SQL_TC_NONE) try {
      rc = SQLSetConnectAttr(m_hdbc, SQL_ATTR_AUTOCOMMIT,
                                     SQL_AUTOCOMMIT_OFF, SQL_IS_UINTEGER);
      
      if (!Check(rc))
        ThrowDBX(SQL_INVALID_HANDLE, "SQLSetConnectAttr");

      m_Transact = true;
    } catch(DBX *x) {
      if (trace)
        for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
          htrc(x->m_ErrMsg[i]);

      strcpy(g->Message, x->GetErrorMessage(0));
    } // end try/catch

    } // endif Mode

  try {
    b = false;

    if (m_hstmt) {
      RETCODE rc = SQLFreeStmt(m_hstmt, SQL_CLOSE);

      hstmt = m_hstmt;
      m_hstmt = NULL;

      if (m_Tdb->GetAmType() != TYPE_AM_XDBC)
        ThrowDBX(MSG(SEQUENCE_ERROR));

      } // endif m_hstmt

    rc = SQLAllocStmt(m_hdbc, &hstmt);

    if (!Check(rc))
      ThrowDBX(SQL_INVALID_HANDLE, "SQLAllocStmt");

    OnSetOptions(hstmt);
    b = true;

    if (trace)
      htrc("Prepare hstmt=%p %.64s\n", hstmt, sql);

    do {
      rc = SQLPrepare(hstmt, (PUCHAR)sql, SQL_NTS);
      } while (rc == SQL_STILL_EXECUTING);

    if (!Check(rc))
      ThrowDBX(rc, "SQLPrepare", hstmt);

    do {
      rc = SQLNumParams(hstmt, &nparm);
      } while (rc == SQL_STILL_EXECUTING);

  } catch(DBX *x) {
    if (trace)
      for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
        htrc(x->m_ErrMsg[i]);

    strcpy(g->Message, x->GetErrorMessage(0));

    if (b)
      SQLCancel(hstmt);

    rc = SQLFreeStmt(hstmt, SQL_DROP);
    m_hstmt = NULL;

    if (m_Transact) {
      rc = SQLEndTran(SQL_HANDLE_DBC, m_hdbc, SQL_ROLLBACK);
      m_Transact = false;
      } // endif m_Transact

    return -1;
  } // end try/catch

  m_hstmt = hstmt;
  return (int)nparm;
  } // end of PrepareSQL

/***********************************************************************/
/*  Execute a prepared statement.                                      */
/***********************************************************************/
int ODBConn::ExecuteSQL(void)
  {
  PGLOBAL& g = m_G;
  SWORD    ncol = 0;
  RETCODE  rc;
  SQLLEN   afrw = -1;

  try {
    do {
      rc = SQLExecute(m_hstmt);
      } while (rc == SQL_STILL_EXECUTING);

    if (!Check(rc))
      ThrowDBX(rc, "SQLExecute", m_hstmt);

    if (!Check(rc = SQLNumResultCols(m_hstmt, &ncol)))
      ThrowDBX(rc, "SQLNumResultCols", m_hstmt);

    if (ncol) {
      // This should never happen while inserting
      strcpy(g->Message, "Logical error while inserting");
    } else {
      // Insert, Update or Delete statement
      if (!Check(rc = SQLRowCount(m_hstmt, &afrw)))
        ThrowDBX(rc, "SQLRowCount", m_hstmt);

    } // endif ncol

  } catch(DBX *x) {
    strcpy(m_G->Message, x->GetErrorMessage(0));
    SQLCancel(m_hstmt);
    rc = SQLFreeStmt(m_hstmt, SQL_DROP);
    m_hstmt = NULL;

    if (m_Transact) {
      rc = SQLEndTran(SQL_HANDLE_DBC, m_hdbc, SQL_ROLLBACK);
      m_Transact = false;
      } // endif m_Transact

    afrw = -1;
  } // end try/catch

  return (int)afrw;
  } // end of ExecuteSQL

/***********************************************************************/
/*  Bind a parameter for inserting.                                    */
/***********************************************************************/
bool ODBConn::BindParam(ODBCCOL *colp)
  {
  void        *buf;
  int          buftype = colp->GetResultType();
  SQLUSMALLINT n = colp->GetRank();
  SQLSMALLINT  ct, sqlt, dec, nul;
  SQLULEN      colsize;
  SQLLEN       len;
  SQLLEN      *strlen = colp->GetStrLen();
  SQLRETURN    rc;

  try {
    rc = SQLDescribeParam(m_hstmt, n, &sqlt, &colsize, &dec, &nul);

    if (!Check(rc))
      ThrowDBX(rc, "SQLDescribeParam", m_hstmt);

  } catch(DBX *x) {
    strcpy(m_G->Message, x->GetErrorMessage(0));
    colsize = colp->GetPrecision();
    sqlt = GetSQLType(buftype);
  } // end try/catch

  buf = colp->GetBuffer(0);
  len = IsTypeChar(buftype) ? colp->GetBuflen() : 0;
  ct = GetSQLCType(buftype);
  *strlen = IsTypeChar(buftype) ? SQL_NTS : 0;

  try {
    rc = SQLBindParameter(m_hstmt, n, SQL_PARAM_INPUT, ct, sqlt,
                          colsize, dec, buf, len, strlen);

    if (!Check(rc))
      ThrowDBX(rc, "SQLBindParameter", m_hstmt);

  } catch(DBX *x) {
    strcpy(m_G->Message, x->GetErrorMessage(0));
    SQLCancel(m_hstmt);
    rc = SQLFreeStmt(m_hstmt, SQL_DROP);
    m_hstmt = NULL;
    return true;
  } // end try/catch

  return false;
  } // end of BindParam

/***********************************************************************/
/*  Execute an SQL command.                                            */
/***********************************************************************/
bool ODBConn::ExecSQLcommand(char *sql)
  {
  char     cmd[16];
  bool     b, rcd = false;
  UINT     txn = 0;
  PGLOBAL& g = m_G;
  SWORD    ncol = 0;
  SQLLEN   afrw;
  RETCODE  rc;
  HSTMT    hstmt;

  try {
    b = FALSE;

    // Check whether we should use transaction
    if (sscanf(sql, " %15s ", cmd) == 1) {
      if (!stricmp(cmd, "INSERT") || !stricmp(cmd, "UPDATE") ||
          !stricmp(cmd, "DELETE") || !stricmp(cmd, "REPLACE")) {
        // Does the data source support transactions
        rc = SQLGetInfo(m_hdbc, SQL_TXN_CAPABLE, &txn, 0, NULL);
    
        if (Check(rc) && txn != SQL_TC_NONE) {
          rc = SQLSetConnectAttr(m_hdbc, SQL_ATTR_AUTOCOMMIT,
                                 SQL_AUTOCOMMIT_OFF, SQL_IS_UINTEGER);
        
          if (!Check(rc))
            ThrowDBX(SQL_INVALID_HANDLE, "SQLSetConnectAttr");
    
          m_Transact = TRUE;
          } // endif txn

        } // endif cmd

      } // endif sql

    // Allocate the statement handle
    rc = SQLAllocStmt(m_hdbc, &hstmt);

    if (!Check(rc))
      ThrowDBX(SQL_INVALID_HANDLE, "SQLAllocStmt");

    OnSetOptions(hstmt);
    b = true;

    if (trace)
      htrc("ExecSQLcommand hstmt=%p %.64s\n", hstmt, sql);

    // Proceed with command execution
    do {
      rc = SQLExecDirect(hstmt, (PUCHAR)sql, SQL_NTS);
      } while (rc == SQL_STILL_EXECUTING);

    if (!Check(rc))
      ThrowDBX(rc, "SQLExecDirect", hstmt);

    // Check whether this is a query returning a result set
    if (!Check(rc = SQLNumResultCols(hstmt, &ncol)))
      ThrowDBX(rc, "SQLNumResultCols", hstmt);

    if (!ncol) {
      if (!Check(SQLRowCount(hstmt, &afrw)))
        ThrowDBX(rc, "SQLRowCount", hstmt);

      m_Tdb->AftRows = (int)afrw;
      strcpy(g->Message, "Affected rows");
    } else {
      m_Tdb->AftRows = (int)ncol;
      strcpy(g->Message, "Result set column number");
    } // endif ncol

  } catch(DBX *x) {
		if (trace)
			for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
				htrc(x->m_ErrMsg[i]);

    sprintf(g->Message, "Remote: %s", x->GetErrorMessage(0));

    if (b)
      SQLCancel(hstmt);

    m_Tdb->AftRows = -1;
    rcd = true;
  } // end try/catch

  if (!Check(rc = SQLFreeStmt(hstmt, SQL_CLOSE)))
    sprintf(g->Message, "SQLFreeStmt: rc=%d", rc);

  if (m_Transact) {
    // Terminate the transaction
    if (!Check(rc = SQLEndTran(SQL_HANDLE_DBC, m_hdbc, 
                       (rcd) ? SQL_ROLLBACK : SQL_COMMIT)))
      sprintf(g->Message, "SQLEndTran: rc=%d", rc);

    if (!Check(rc = SQLSetConnectAttr(m_hdbc, SQL_ATTR_AUTOCOMMIT,
               (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_UINTEGER)))
      sprintf(g->Message, "SQLSetConnectAttr: rc=%d", rc);

    m_Transact = false;
    } // endif m_Transact

  return rcd;
  } // end of ExecSQLcommand

/**************************************************************************/
/*  GetMetaData: constructs the result blocks containing the              */
/*  description of all the columns of an SQL command.                     */
/**************************************************************************/
PQRYRES ODBConn::GetMetaData(PGLOBAL g, char *dsn, char *src)
  {
  static int  buftyp[] = {TYPE_STRING, TYPE_SHORT, TYPE_INT,
                          TYPE_SHORT,  TYPE_SHORT};
  static XFLD fldtyp[] = {FLD_NAME,  FLD_TYPE, FLD_PREC,
                          FLD_SCALE, FLD_NULL};
  static unsigned int length[] = {0, 6, 10, 6, 6};
  unsigned char cn[60];
  int     qcol = 5;
  short   nl, type, prec, nul, cns = (short)sizeof(cn);
  PQRYRES qrp = NULL;
  PCOLRES crp;
  USHORT  i;
  SQLULEN n;
  SWORD   ncol;
  RETCODE rc;
  HSTMT   hstmt;

  if (Open(dsn, 10) < 1)   // openReadOnly + noOdbcDialog
    return NULL;

  try {
    rc = SQLAllocStmt(m_hdbc, &hstmt);

    if (!Check(rc))
      ThrowDBX(SQL_INVALID_HANDLE, "SQLAllocStmt");

    OnSetOptions(hstmt);

    do {
      rc = SQLPrepare(hstmt, (PUCHAR)src, SQL_NTS);
//    rc = SQLExecDirect(hstmt, (PUCHAR)src, SQL_NTS);
      } while (rc == SQL_STILL_EXECUTING);

    if (!Check(rc))
      ThrowDBX(rc, "SQLExecDirect", hstmt);

    do {
      rc = SQLNumResultCols(hstmt, &ncol);
      } while (rc == SQL_STILL_EXECUTING);

    if (!Check(rc))
      ThrowDBX(rc, "SQLNumResultCols", hstmt);

    if (ncol) for (i = 1; i <= ncol; i++) {
      do {
        rc = SQLDescribeCol(hstmt, i, NULL, 0, &nl, NULL, NULL, NULL, NULL); 
        } while (rc == SQL_STILL_EXECUTING);

      if (!Check(rc))
        ThrowDBX(rc, "SQLDescribeCol", hstmt);

      length[0] = max(length[0], (UINT)nl);
      } // endfor i

  } catch(DBX *x) {
    strcpy(g->Message, x->GetErrorMessage(0));
    goto err;
  } // end try/catch

  if (!ncol) {
    strcpy(g->Message, "Invalid Srcdef");
    goto err;
    } // endif ncol

  /************************************************************************/
  /*  Allocate the structures used to refer to the result set.            */
  /************************************************************************/
  if (!(qrp = PlgAllocResult(g, qcol, ncol, IDS_COLUMNS + 3,
                             buftyp, fldtyp, length, false, true)))
    return NULL;

  // Some columns must be renamed
  for (i = 0, crp = qrp->Colresp; crp; crp = crp->Next)
    switch (++i) {
      case 3: crp->Name = "Precision"; break;
      case 4: crp->Name = "Scale";     break;
      case 5: crp->Name = "Nullable";  break;
      } // endswitch i

  /************************************************************************/
  /*  Now get the results into blocks.                                    */
  /************************************************************************/
  try {
    for (i = 0; i < ncol; i++) {
      do {
        rc = SQLDescribeCol(hstmt, i+1, cn, cns, &nl, &type, &n, &prec, &nul); 
        } while (rc == SQL_STILL_EXECUTING);

      if (!Check(rc))
        ThrowDBX(rc, "SQLDescribeCol", hstmt);
      else
        qrp->Nblin++;

      crp = qrp->Colresp;                    // Column_Name
      crp->Kdata->SetValue((char*)cn, i);
      crp = crp->Next;                       // Data_Type
      crp->Kdata->SetValue(type, i);
      crp = crp->Next;                       // Precision (length)
      crp->Kdata->SetValue((int)n, i);
      crp = crp->Next;                       // Scale
      crp->Kdata->SetValue(prec, i);
      crp = crp->Next;                       // Nullable
      crp->Kdata->SetValue(nul, i);
      } // endfor i

  } catch(DBX *x) {
    strcpy(g->Message, x->GetErrorMessage(0));
    qrp = NULL;
  } // end try/catch

  /* Cleanup */
 err:
  SQLCancel(hstmt);
  rc = SQLFreeStmt(hstmt, SQL_DROP);
  Close();

  /************************************************************************/
  /*  Return the result pointer for use by GetData routines.              */
  /************************************************************************/
  return qrp;
  } // end of GetMetaData

/***********************************************************************/
/*  Get the list of Data Sources and set it in qrp.                    */
/***********************************************************************/
bool ODBConn::GetDataSources(PQRYRES qrp)
  {
  bool    rv = false;
  UCHAR  *dsn, *des;
  UWORD   dir = SQL_FETCH_FIRST;
  SWORD   n1, n2, p1, p2;
  PCOLRES crp1 = qrp->Colresp, crp2 = qrp->Colresp->Next;
  RETCODE rc;

  n1 = crp1->Clen;
  n2 = crp2->Clen;

  try {
    rc = SQLAllocEnv(&m_henv);

    if (!Check(rc))
      ThrowDBX(rc, "SQLAllocEnv"); // Fatal

    for (int i = 0; i < qrp->Maxres; i++) {
      dsn = (UCHAR*)crp1->Kdata->GetValPtr(i);
      des = (UCHAR*)crp2->Kdata->GetValPtr(i);
      rc = SQLDataSources(m_henv, dir, dsn, n1, &p1, des, n2, &p2);

      if (rc == SQL_NO_DATA_FOUND)
        break;
      else if (!Check(rc))
        ThrowDBX(rc, "SQLDataSources");

      qrp->Nblin++;
      dir = SQL_FETCH_NEXT;
      } // endfor i

  } catch(DBX *x) {
    strcpy(m_G->Message, x->GetErrorMessage(0));
    rv = true;
  } // end try/catch

  Close();
  return rv;
  } // end of GetDataSources

/***********************************************************************/
/*  Get the list of Drivers and set it in qrp.                         */
/***********************************************************************/
bool ODBConn::GetDrivers(PQRYRES qrp)
  {
  int     i, n;
  bool    rv = false;
  UCHAR  *des, *att;
  UWORD   dir = SQL_FETCH_FIRST;
  SWORD   n1, n2, p1, p2;
  PCOLRES crp1 = qrp->Colresp, crp2 = qrp->Colresp->Next;
  RETCODE rc;

  n1 = crp1->Clen;
  n2 = crp2->Clen;

  try {
    rc = SQLAllocEnv(&m_henv);

    if (!Check(rc))
      ThrowDBX(rc, "SQLAllocEnv"); // Fatal

    for (n = 0; n < qrp->Maxres; n++) {
      des = (UCHAR*)crp1->Kdata->GetValPtr(n);
      att = (UCHAR*)crp2->Kdata->GetValPtr(n);
      rc = SQLDrivers(m_henv, dir, des, n1, &p1, att, n2, &p2);

      if (rc == SQL_NO_DATA_FOUND)
        break;
      else if (!Check(rc))
        ThrowDBX(rc, "SQLDrivers");


      // The attributes being separated by '\0', set them to ';'
      for (i = 0; i < p2; i++)
        if (!att[i])
          att[i] = ';';

      qrp->Nblin++;
      dir = SQL_FETCH_NEXT;
      } // endfor n

  } catch(DBX *x) {
    strcpy(m_G->Message, x->GetErrorMessage(0));
    rv = true;
  } // end try/catch

  Close();
  return rv;
  } // end of GetDrivers


/***********************************************************************/
/*  A helper class to split an optionally qualified table name into    */
/*  components.                                                        */
/*  These formats are understood:                                      */
/*    "CatalogName.SchemaName.TableName"                               */
/*    "SchemaName.TableName"                                           */
/*    "TableName"                                                      */
/***********************************************************************/
class SQLQualifiedName
{
  static const uint max_parts= 3;          // Catalog.Schema.Table
  MYSQL_LEX_STRING m_part[max_parts];
  char m_buf[512];

  void lex_string_set(MYSQL_LEX_STRING *S, char *str, size_t length)
  {
    S->str= str;
    S->length= length;
  } // eend of lex_string_set

  void lex_string_shorten_down(MYSQL_LEX_STRING *S, size_t offs)
  {
    DBUG_ASSERT(offs <= S->length);
    S->str+= offs;
    S->length-= offs;
  } // end of lex_string_shorten_down

  /*********************************************************************/
  /*  Find the rightmost '.' delimiter and return the length           */
  /*  of the qualifier, including the rightmost '.' delimier.          */
  /*  For example, for the string {"a.b.c",5} it will return 4,        */
  /*  which is the length of the qualifier "a.b."                      */
  /*********************************************************************/
  size_t lex_string_find_qualifier(MYSQL_LEX_STRING *S)
  {
    size_t i;
    for (i= S->length; i > 0; i--)
    {
      if (S->str[i - 1] == '.')
      {
        S->str[i - 1]= '\0';
        return i;
      }
    }
    return 0;
  } // end of lex_string_find_qualifier

public:
  /*********************************************************************/
  /*  Initialize to the given optionally qualified name.               */
  /*  NULL pointer in "name" is supported.                             */
  /*  name qualifier has precedence over schema.                       */
  /*********************************************************************/
  SQLQualifiedName(CATPARM *cap)
  {
    const char *name = (const char *)cap->Tab;
    char       *db = (char *)cap->DB;
    size_t      len, i;

    // Initialize the parts
    for (i = 0 ; i < max_parts; i++)
      lex_string_set(&m_part[i], NULL, 0);

    if (name) {
      // Initialize the first (rightmost) part
      lex_string_set(&m_part[0], m_buf,
                     strmake(m_buf, name, sizeof(m_buf) - 1) - m_buf);
  
      // Initialize the other parts, if exist. 
      for (i= 1; i < max_parts; i++) {
        if (!(len= lex_string_find_qualifier(&m_part[i - 1])))
          break;
  
        lex_string_set(&m_part[i], m_part[i - 1].str, len - 1);
        lex_string_shorten_down(&m_part[i - 1], len);
        } // endfor i

      } // endif name

    // If it was not specified, set schema as the passed db name
    if (db && !m_part[1].length)
      lex_string_set(&m_part[1], db, strlen(db));

  } // end of SQLQualifiedName

  SQLCHAR *ptr(uint i)
  {
    DBUG_ASSERT(i < max_parts);
    return (SQLCHAR *) (m_part[i].length ? m_part[i].str : NULL);
  } // end of ptr

  size_t length(uint i)
  {
    DBUG_ASSERT(i < max_parts);
    return m_part[i].length;
  } // end of length

}; // end of class SQLQualifiedName

/***********************************************************************/
/*  Allocate recset and call SQLTables, SQLColumns or SQLPrimaryKeys.  */
/***********************************************************************/
int ODBConn::GetCatInfo(CATPARM *cap)
  {
  PGLOBAL& g = m_G;
  void    *buffer;
  int      i, irc;
  bool     b;
  PSZ      fnc = "Unknown";
  UWORD    n;
  SWORD    ncol, len, tp;
  SQLULEN  crow;
  PQRYRES  qrp = cap->Qrp;
  PCOLRES  crp;
  RETCODE  rc = 0;
  HSTMT    hstmt = NULL;
  SQLLEN  *vl, *vlen = NULL;
  PVAL    *pval = NULL;

  try {
    b = false;

    if (!m_hstmt) {
      rc = SQLAllocStmt(m_hdbc, &hstmt);

      if (!Check(rc))
        ThrowDBX(SQL_INVALID_HANDLE, "SQLAllocStmt");

    } else
      ThrowDBX(MSG(SEQUENCE_ERROR));

    b = true;

    // Currently m_Catver should be always 0 here
    assert(!m_Catver);     // This may be temporary

    if (qrp->Maxres > 0)
      m_RowsetSize = 1;
    else
      ThrowDBX("0-sized result");

    SQLQualifiedName name(cap);

    // Now do call the proper ODBC API
    switch (cap->Id) {
      case CAT_TAB:
//      rc = SQLSetStmtAttr(hstmt, SQL_ATTR_METADATA_ID,
//                                (SQLPOINTER)false, 0);
        fnc = "SQLTables";
        rc = SQLTables(hstmt, name.ptr(2), name.length(2),
                              name.ptr(1), name.length(1),
                              name.ptr(0), name.length(0),
                              cap->Pat, SQL_NTS);
        break;
      case CAT_COL:
//      rc = SQLSetStmtAttr(hstmt, SQL_ATTR_METADATA_ID,
//                                (SQLPOINTER)true, 0);
        fnc = "SQLColumns";
        rc = SQLColumns(hstmt, name.ptr(2), name.length(2),
                               name.ptr(1), name.length(1),
                               name.ptr(0), name.length(0),
                               cap->Pat, SQL_NTS);
        break;
      case CAT_KEY:
        fnc = "SQLPrimaryKeys";
        rc = SQLPrimaryKeys(hstmt, name.ptr(2), name.length(2),
                                   name.ptr(1), name.length(1),
                                   name.ptr(0), name.length(0));
        break;
      case CAT_STAT:
        fnc = "SQLStatistics";
        rc = SQLStatistics(hstmt, name.ptr(2), name.length(2),
                                  name.ptr(1), name.length(1),
                                  name.ptr(0), name.length(0),
                                  cap->Unique, cap->Accuracy);
        break;
      case CAT_SPC:
        ThrowDBX("SQLSpecialColumns not available yet");
      default:
        ThrowDBX("Invalid SQL function id");
      } // endswitch infotype

    if (!Check(rc))
      ThrowDBX(rc, fnc, hstmt);

    rc = SQLNumResultCols(hstmt, &ncol);

    // n because we no more ignore the first column
    if ((n = (UWORD)qrp->Nbcol) > (UWORD)ncol)
      ThrowDBX(MSG(COL_NUM_MISM));

    // Unconditional to handle STRBLK's
    pval = (PVAL *)PlugSubAlloc(g, NULL, n * sizeof(PVAL));
    vlen = (SQLLEN *)PlugSubAlloc(g, NULL, n * sizeof(SQLLEN));

    // Now bind the column buffers
    for (n = 0, crp = qrp->Colresp; crp; crp = crp->Next) {
      if ((tp = GetSQLCType(crp->Type)) == SQL_TYPE_NULL) {
        sprintf(g->Message, MSG(INV_COLUMN_TYPE), crp->Type, crp->Name);
        ThrowDBX(g->Message);
        } // endif tp

      if (!(len = GetTypeSize(crp->Type, crp->Length))) {
        len = 255;           // for STRBLK's
        ((STRBLK*)crp->Kdata)->SetSorted(true);
        } // endif len

      pval[n] = AllocateValue(g, crp->Type, len);
      buffer = pval[n]->GetTo_Val();
      vl = vlen + n;

      // n + 1 because column numbers begin with 1
      rc = SQLBindCol(hstmt, n + 1, tp, buffer, len, vl);

      if (!Check(rc))
        ThrowDBX(rc, "SQLBindCol", hstmt);

      n++;
      } // endfor crp

    fnc = "SQLFetch";

    // Now fetch the result
    // Extended fetch cannot be used because of STRBLK's
    for (i = 0; i < qrp->Maxres; i++) {
      if ((rc = SQLFetch(hstmt)) == SQL_NO_DATA_FOUND)
        break;
      else if (rc != SQL_SUCCESS) {
        if (trace > 1 || (trace && rc != SQL_SUCCESS_WITH_INFO)) {
          UCHAR   msg[SQL_MAX_MESSAGE_LENGTH + 1];
          UCHAR   state[SQL_SQLSTATE_SIZE + 1];
          RETCODE erc;
          SDWORD  native;

          htrc("SQLFetch: row %d rc=%d\n", i+1, rc);
          erc = SQLError(m_henv, m_hdbc, hstmt, state, &native, msg,
                         SQL_MAX_MESSAGE_LENGTH - 1, &len);

          if (rc != SQL_INVALID_HANDLE)
            // Skip non-errors
            for (n = 0; n < MAX_NUM_OF_MSG
                 && (erc == SQL_SUCCESS || erc == SQL_SUCCESS_WITH_INFO)
                 && strcmp((char*)state, "00000"); n++) {
              htrc("%s: %s, Native=%d\n", state, msg, native);
              erc = SQLError(m_henv, m_hdbc, hstmt, state, &native,
                             msg, SQL_MAX_MESSAGE_LENGTH - 1, &len);
              } // endfor n

          } // endif trace

        if (rc != SQL_SUCCESS_WITH_INFO)
          qrp->BadLines++;

        } // endif rc

      for (n = 0, crp = qrp->Colresp; crp; n++, crp = crp->Next) {
        pval[n]->SetNull(vlen[n] == SQL_NULL_DATA);
        crp->Kdata->SetValue(pval[n], i);
        cap->Vlen[n][i] = vlen[n];
        } // endfor crp

      } // endfor i

    if ((crow = i) && (rc == SQL_NO_DATA || rc == SQL_SUCCESS_WITH_INFO))
      rc = SQL_SUCCESS;

    if (rc == SQL_NO_DATA_FOUND) {
      if (cap->Pat)
        sprintf(g->Message, MSG(NO_TABCOL_DATA), cap->Tab, cap->Pat);
      else
        sprintf(g->Message, MSG(NO_TAB_DATA), cap->Tab);

      ThrowDBX(g->Message);
    } else if (rc == SQL_SUCCESS) {
      if ((rc = SQLFetch(hstmt)) != SQL_NO_DATA_FOUND)
        qrp->Truncated = true; 

    } else
      ThrowDBX(rc, fnc, hstmt);

    irc = (int)crow;
  } catch(DBX *x) {
    if (trace)
      for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
        htrc(x->m_ErrMsg[i]);

    strcpy(g->Message, x->GetErrorMessage(0));
    irc = -1;
  } // end try/catch

  if (b)
    SQLCancel(hstmt);

  // All this (hstmt vs> m_hstmt) to be revisited
  if (hstmt)
    rc = SQLFreeStmt(hstmt, SQL_DROP);

  return irc;
  } // end of GetCatInfo

/***********************************************************************/
/*  Disconnect connection                                              */
/***********************************************************************/
void ODBConn::Close()
  {
  RETCODE rc;

  if (m_hstmt) {
    // Is required for multiple tables
    rc = SQLFreeStmt(m_hstmt, SQL_DROP);
    m_hstmt = NULL;
    } // endif m_hstmt

  if (m_hdbc != SQL_NULL_HDBC) {
    if (m_Transact) {
      rc = SQLEndTran(SQL_HANDLE_DBC, m_hdbc, SQL_COMMIT);
      m_Transact = false;
      } // endif m_Transact

    rc = SQLDisconnect(m_hdbc);

    if (trace && rc != SQL_SUCCESS)
      htrc("Error: SQLDisconnect rc=%d\n", rc);

    rc = SQLFreeConnect(m_hdbc);

    if (trace && rc != SQL_SUCCESS)
      htrc("Error: SQLFreeConnect rc=%d\n", rc);

    m_hdbc = SQL_NULL_HDBC;
    } // endif m_hdbc

  if (m_henv != SQL_NULL_HENV) {
    rc = SQLFreeEnv(m_henv);

    if (trace && rc != SQL_SUCCESS)   // Nothing we can do
      htrc("Error: SQLFreeEnv failure ignored in Close\n");
          
    m_henv = SQL_NULL_HENV;
    } // endif m_henv

  } // end of Close