summaryrefslogtreecommitdiffstats
path: root/storage/connect/odbconn.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 18:00:34 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 18:00:34 +0000
commit3f619478f796eddbba6e39502fe941b285dd97b1 (patch)
treee2c7b5777f728320e5b5542b6213fd3591ba51e2 /storage/connect/odbconn.cpp
parentInitial commit. (diff)
downloadmariadb-3f619478f796eddbba6e39502fe941b285dd97b1.tar.xz
mariadb-3f619478f796eddbba6e39502fe941b285dd97b1.zip
Adding upstream version 1:10.11.6.upstream/1%10.11.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'storage/connect/odbconn.cpp')
-rw-r--r--storage/connect/odbconn.cpp2654
1 files changed, 2654 insertions, 0 deletions
diff --git a/storage/connect/odbconn.cpp b/storage/connect/odbconn.cpp
new file mode 100644
index 00000000..520b82d5
--- /dev/null
+++ b/storage/connect/odbconn.cpp
@@ -0,0 +1,2654 @@
+/***********************************************************************/
+/* Name: ODBCONN.CPP Version 2.4 */
+/* */
+/* (C) Copyright to the author Olivier BERTRAND 1998-2021 */
+/* */
+/* This file contains the ODBC connection classes functions. */
+/***********************************************************************/
+
+/***********************************************************************/
+/* Include relevant MariaDB header file. */
+/***********************************************************************/
+#include <my_global.h>
+#include <m_string.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 "xtable.h"
+#include "tabext.h"
+#include "odbccat.h"
+#include "tabodbc.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
+
+TYPCONV GetTypeConv();
+int GetConvSize();
+void OdbcClose(PGLOBAL g, PFBLOCK fp);
+
+/***********************************************************************/
+/* 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
+
+/***********************************************************************/
+/* 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;
+//#if (ODBCVER >= 0x0300)
+// case TYPE_DECIM: tp = SQL_C_NUMERIC; break; (CRASH!!!)
+//#else
+ case TYPE_DECIM: tp = SQL_C_CHAR; break;
+//#endif
+
+ } // 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, bool& w)
+ {
+ int type;
+
+ switch (stp) {
+ case SQL_WVARCHAR: // (-9)
+ w = true;
+ /* fall through */
+ case SQL_VARCHAR: // 12
+ v = 'V';
+ type = TYPE_STRING;
+ break;
+ case SQL_WCHAR: // (-8)
+ w = true;
+ /* fall through */
+ case SQL_CHAR: // 1
+ type = TYPE_STRING;
+ break;
+ case SQL_WLONGVARCHAR: // (-10)
+ w = true;
+ /* fall through */
+ case SQL_LONGVARCHAR: // (-1)
+ if (GetTypeConv() == TPC_YES || GetTypeConv() == TPC_FORCE) {
+ v = 'V';
+ type = TYPE_STRING;
+ len = (len) ? MY_MIN(abs(len), GetConvSize()) : GetConvSize();
+ } else
+ type = TYPE_ERROR;
+
+ 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
+ type = TYPE_DATE;
+ len = 19;
+ break;
+ case SQL_TYPE_DATE: // 91
+ type = TYPE_DATE;
+ len = 10;
+ v = 'D';
+ break;
+ case SQL_INTERVAL: // 10
+ case SQL_TYPE_TIME: // 92
+ type = TYPE_STRING;
+ len = 8 + ((prec) ? (prec+1) : 0);
+ v = 'T';
+ break;
+ case SQL_TIMESTAMP: // 11
+ case SQL_TYPE_TIMESTAMP: // 93
+ type = TYPE_DATE;
+ len = 19 + ((prec) ? (prec+1) : 0);
+ v = 'S';
+ break;
+ case SQL_BIGINT: // (-5)
+ type = TYPE_BIGINT;
+ break;
+ case SQL_BINARY: // (-2)
+ case SQL_VARBINARY: // (-3)
+ case SQL_LONGVARBINARY: // (-4)
+ if (GetTypeConv() == TPC_FORCE) {
+ v = 'V';
+ type = TYPE_STRING;
+ len = (len) ? MY_MIN(abs(len), GetConvSize()) : GetConvSize();
+ } else
+ type = TYPE_ERROR;
+
+ break;
+ case SQL_GUID: // (-11)
+ type = TYPE_STRING;
+ len = 36;
+ break;
+ case SQL_UNKNOWN_TYPE: // 0
+ 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, PCSZ db,
+ PCSZ tab, PQRYRES qrp)
+{
+ size_t i, m, n;
+ CATPARM *cap;
+
+#if defined(_DEBUG)
+ assert(qrp);
+#endif
+
+ try {
+ 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 = db;
+ cap->Tab = 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));
+
+ } catch (int n) {
+ htrc("Exeption %d: %s\n", n, g->Message);
+ cap = NULL;
+ } catch (const char *msg) {
+ htrc(g->Message, msg);
+ printf("%s\n", g->Message);
+ cap = NULL;
+ } // end catch
+
+ 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
+
+/***********************************************************************/
+/* Close an ODBC table after a thrown error (called by PlugCloseFile) */
+/***********************************************************************/
+void OdbcClose(PGLOBAL g, PFBLOCK fp) {
+ ((ODBConn*)fp->File)->Close();
+} // end of OdbcClose
+
+/***********************************************************************/
+/* ODBCColumns: constructs the result blocks containing all columns */
+/* of an ODBC table that will be retrieved by GetData commands. */
+/***********************************************************************/
+PQRYRES ODBCColumns(PGLOBAL g, PCSZ dsn, PCSZ db, PCSZ table,
+ PCSZ colpat, int maxres, bool info, POPARM sop)
+ {
+ 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};
+ bool b[] = {true,true,false,false,false,false,false,false,true,true,false,true};
+ int i, n, ncol = 12;
+ PCOLRES crp;
+ 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, sop, 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) ? MY_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(1))
+ htrc("ODBCColumns: max=%d len=%d,%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);
+
+ for (i = 0, crp = qrp->Colresp; crp; i++, crp = crp->Next)
+ if (b[i])
+ crp->Kdata->SetNullable(true);
+
+ if (info || !qrp) // Info table
+ return qrp;
+
+ if (trace(1))
+ htrc("Getting col results ncol=%d\n", qrp->Nbcol);
+
+ if (!(cap = AllocCatInfo(g, CAT_COL, db, table, qrp)))
+ return NULL;
+
+ cap->Pat = colpat;
+
+ /************************************************************************/
+ /* Now get the results into blocks. */
+ /************************************************************************/
+ if ((n = ocp->GetCatInfo(cap)) >= 0) {
+ qrp->Nblin = n;
+// ResetNullValues(cap);
+
+ if (trace(1))
+ 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, POPARM sop)
+ {
+ char *sqry;
+ ODBConn *ocp = new(g) ODBConn(g, NULL);
+
+ if (ocp->Open(dsn, sop, 10) < 1) // openReadOnly + noOdbcDialog
+ return NULL;
+
+ if (strstr(src, "%s")) {
+ // Place holder for an eventual where clause
+ sqry = (char*)PlugSubAlloc(g, NULL, strlen(src) + 3);
+ sprintf(sqry, src, "1=1", "1=1"); // dummy where clause
+ } else
+ sqry = src;
+
+ return ocp->GetMetaData(g, dsn, sqry);
+ } // 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;
+ bool w = false;
+// 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, w);
+ 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};
+ bool b[] = {false, true};
+ int i, ncol = 2;
+ PCOLRES crp;
+ 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(1))
+ 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);
+
+ for (i = 0, crp = qrp->Colresp; crp; i++, crp = crp->Next)
+ if (b[i])
+ crp->Kdata->SetNullable(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};
+ bool b[] = {false, true};
+ int i, n = 0, ncol = 2;
+ PCOLRES crp;
+ 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(1))
+ 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);
+
+ for (i = 0, crp = qrp->Colresp; crp; i++, crp = crp->Next)
+ if (b[i])
+ crp->Kdata->SetNullable(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, PCSZ dsn, PCSZ db, PCSZ tabpat, PCSZ tabtyp,
+ int maxres, bool info, POPARM sop)
+ {
+ 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};
+ bool b[] ={ true, true, false, false, true };
+ int i, n, ncol = 5;
+ PCOLRES crp;
+ 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, sop, 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(1))
+ 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);
+
+ for (i = 0, crp = qrp->Colresp; crp; i++, crp = crp->Next)
+ if (b[i])
+ crp->Kdata->SetNullable(true);
+
+ if (info || !qrp)
+ return qrp;
+
+ if (!(cap = AllocCatInfo(g, CAT_TAB, db, tabpat, qrp)))
+ return NULL;
+
+ cap->Pat = tabtyp;
+
+ if (trace(1))
+ 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(1))
+ 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(1))
+ 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(1))
+ 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(1))
+ 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(1))
+ 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(1))
+ 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(1))
+ 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, PCSZ 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. */
+/***********************************************************************/
+bool 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_NO_DATA_FOUND)
+ return false;
+ else 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)PlugDup(g, (char*)msg);
+
+ if (trace(1))
+ 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 true;
+ } else {
+ snprintf((char*)msg, SQL_MAX_MESSAGE_LENGTH + 1, "%s: %s", m_Msg,
+ MSG(BAD_HANDLE_VAL));
+ m_ErrMsg[0] = (PSZ)PlugDup(g, (char*)msg);
+
+ if (trace(1))
+ htrc("%s: rc=%hd\n", SVP(m_ErrMsg[0]), m_RC);
+
+ return true;
+ } // endif rc
+
+ } else
+ m_ErrMsg[0] = "No connexion address provided";
+
+ if (trace(1))
+ htrc("%s: rc=%hd (%s)\n", SVP(m_Msg), m_RC, SVP(m_ErrMsg[0]));
+
+ return true;
+ } // 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_Rows = 0;
+ m_Fetch = 0;
+ m_Fp = NULL;
+ m_Connect = NULL;
+ m_User = NULL;
+ m_Pwd = NULL;
+ m_Updatable = true;
+ m_Transact = false;
+ m_Scrollable = (tdbp) ? tdbp->Scrollable : false;
+ m_Full = false;
+ m_UseCnc = false;
+ m_IDQuoteChar[0] = '"';
+ if (tdbp)
+ {
+ if (tdbp->Quoted && tdbp->Quote)
+ m_IDQuoteChar[0] = *tdbp->Quote;
+ }
+ 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(1)) {
+ DBX x(rc);
+
+ if (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, PCSZ msg, HSTMT hstmt)
+ {
+ DBX* xp = new(m_G) DBX(rc, msg);
+
+ // Don't throw if no error
+ if (xp->BuildErrorMessage(this, hstmt))
+ throw xp;
+
+ } // end of ThrowDBX
+
+void ODBConn::ThrowDBX(PCSZ msg)
+ {
+ DBX* xp = new(m_G) DBX(0, "Error");
+
+ 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 = PlugDup(m_G, 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(PCSZ ConnectString, POPARM sop, 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;
+ m_User = sop->User;
+ m_Pwd = sop->Pwd;
+ m_LoginTimeout = sop->Cto;
+ m_QueryTimeout = sop->Qto;
+ m_UseCnc = sop->UseCnc;
+
+ // Allocate the HDBC and make connection
+ try {
+ /*PSZ ver;*/
+
+ AllocConnect(options);
+ /*ver = GetStringInfo(SQL_ODBC_VER);*/
+
+ if (!m_UseCnc) {
+ if (DriverConnect(options)) {
+ snprintf(g->Message, sizeof(g->Message), MSG(CONNECT_CANCEL));
+ return 0;
+ } // endif
+
+ } else // Connect using SQLConnect
+ Connect();
+
+ /*********************************************************************/
+ /* Link a Fblock. This make possible to automatically close it */
+ /* in case of error (throw). */
+ /*********************************************************************/
+ PDBUSER dbuserp = (PDBUSER)g->Activityp->Aptr;
+
+ m_Fp = (PFBLOCK)PlugSubAlloc(g, NULL, sizeof(FBLOCK));
+ m_Fp->Type = TYPE_FB_ODBC;
+ m_Fp->Fname = NULL;
+ m_Fp->Next = dbuserp->Openlist;
+ dbuserp->Openlist = m_Fp;
+ m_Fp->Count = 1;
+ m_Fp->Length = 0;
+ m_Fp->Memory = NULL;
+ m_Fp->Mode = MODE_ANY;
+ m_Fp->File = this;
+ m_Fp->Handle = 0;
+
+ /*ver = GetStringInfo(SQL_DRIVER_ODBC_VER);*/
+ // Verify support for required functionality and cache info
+// VerifyConnect(); Deprecated
+ GetConnectInfo();
+ // Still we want to use the set QChar
+ } catch(DBX *xp) {
+ snprintf(g->Message, sizeof(g->Message), "%s: %s", xp->m_Msg, xp->GetErrorMessage(0));
+ Close();
+// Free();
+ return -1;
+ } // end try-catch
+
+ 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, (SQLULEN)"xodbc.out");
+ SQLSetConnectOption(m_hdbc, SQL_OPT_TRACE, 1);
+ } // endif
+#endif // _DEBUG
+
+ if ((signed)m_LoginTimeout >= 0) {
+ rc = SQLSetConnectOption(m_hdbc, SQL_LOGIN_TIMEOUT, m_LoginTimeout);
+
+ if (trace(1) && rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
+ htrc("Warning: Failure setting login timeout\n");
+
+ } // endif Timeout
+
+ if (!m_Updatable) {
+ rc = SQLSetConnectOption(m_hdbc, SQL_ACCESS_MODE, SQL_MODE_READ_ONLY);
+
+ if (trace(1) && 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_DRIVER);
+
+ return;
+ } // end of AllocConnect
+
+/***********************************************************************/
+/* Connect to data source using SQLConnect. */
+/***********************************************************************/
+void ODBConn::Connect(void)
+ {
+ SQLRETURN rc;
+ SQLSMALLINT ul = (m_User ? SQL_NTS : 0);
+ SQLSMALLINT pl = (m_Pwd ? SQL_NTS : 0);
+
+ rc = SQLConnect(m_hdbc, (SQLCHAR*)m_Connect, SQL_NTS,
+ (SQLCHAR*)m_User, ul, (SQLCHAR*)m_Pwd, pl);
+
+ if (!Check(rc))
+ ThrowDBX(rc, "SQLConnect");
+
+ } // end of Connect
+
+/***********************************************************************/
+/* Connect to data source using SQLDriverConnect. */
+/***********************************************************************/
+bool ODBConn::DriverConnect(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
+
+ 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 DriverConnect
+
+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(1))
+ 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(1))
+ 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, k;
+ SWORD len, tp, ncol = 0;
+ ODBCCOL *colp;
+ RETCODE rc;
+ HSTMT hstmt;
+
+ try {
+ b = false;
+
+ if (m_hstmt) {
+ // This is a Requery
+ rc = SQLFreeStmt(m_hstmt, SQL_CLOSE);
+
+ if (!Check(rc))
+ ThrowDBX(rc, "SQLFreeStmt", m_hstmt);
+
+ m_hstmt = NULL;
+ } // endif m_hstmt
+
+ rc = SQLAllocStmt(m_hdbc, &hstmt);
+
+ if (!Check(rc))
+ ThrowDBX(rc, "SQLAllocStmt");
+
+ if (m_Scrollable) {
+ rc = SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_SCROLLABLE,
+ (void*)SQL_SCROLLABLE, 0);
+
+ if (!Check(rc))
+ ThrowDBX(rc, "Scrollable", hstmt);
+
+ } // endif m_Scrollable
+
+ OnSetOptions(hstmt);
+ b = true;
+
+ if (trace(1))
+ htrc("ExecDirect hstmt=%p %.256s\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) {
+ snprintf(g->Message, sizeof(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);
+
+ k = 0; // used for column number
+ } // 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 (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) {
+ snprintf(m_G->Message, sizeof(m_G->Message), MSG(INV_COLUMN_TYPE),
+ colp->GetResultType(), SVP(colp->GetName()));
+ ThrowDBX(m_G->Message);
+ } // endif tp
+
+ if (m_Tdb->Srcdef)
+ k = colp->GetIndex();
+ else
+ k++;
+
+ if (trace(1))
+ htrc("Binding col=%u type=%d buf=%p len=%d slen=%p\n",
+ k, tp, buffer, len, colp->GetStrLen());
+
+ rc = SQLBindCol(hstmt, k, tp, buffer, len, colp->GetStrLen());
+
+ if (!Check(rc))
+ ThrowDBX(rc, "SQLBindCol", hstmt);
+
+ } // endif colp
+
+ } catch(DBX *x) {
+ if (trace(1))
+ for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
+ htrc(x->m_ErrMsg[i]);
+
+ snprintf(m_G->Message, sizeof(m_G->Message), "%s: %s", x->m_Msg, 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(1))
+ 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(int pos)
+ {
+ ASSERT(m_hstmt);
+ int irc;
+ SQLULEN crow;
+ RETCODE rc;
+ PGLOBAL& g = m_G;
+
+ try {
+// do {
+ if (pos) {
+ rc = SQLExtendedFetch(m_hstmt, SQL_FETCH_ABSOLUTE, pos, &crow, NULL);
+ } else 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(2))
+ htrc("Fetch: hstmt=%p RowseSize=%d rc=%d\n",
+ m_hstmt, m_RowsetSize, rc);
+
+ if (!Check(rc))
+ ThrowDBX(rc, "Fetching", m_hstmt);
+
+ if (rc == SQL_NO_DATA_FOUND) {
+ m_Full = (m_Fetch == 1);
+ irc = 0;
+ } else
+ irc = (int)crow;
+
+ m_Fetch++;
+ m_Rows += irc;
+ } catch(DBX *x) {
+ if (trace(1))
+ for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
+ htrc(x->m_ErrMsg[i]);
+
+ snprintf(g->Message, sizeof(g->Message), "%s: %s", x->m_Msg, 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(1))
+ for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
+ htrc(x->m_ErrMsg[i]);
+
+ snprintf(g->Message, sizeof(g->Message), "%s: %s", x->m_Msg, x->GetErrorMessage(0));
+ } // end try/catch
+
+ } // endif Mode
+
+ try {
+ b = false;
+
+ if (m_hstmt) {
+ 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(1))
+ 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(1))
+ for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
+ htrc(x->m_ErrMsg[i]);
+
+ snprintf(g->Message, sizeof(g->Message), "%s: %s", x->m_Msg, 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
+ snprintf(g->Message, sizeof(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) {
+ snprintf(m_G->Message, sizeof(m_G->Message), "%s: %s", x->m_Msg, 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 __attribute__((unused));
+ SQLULEN colsize;
+ SQLLEN len;
+ SQLLEN *strlen = colp->GetStrLen();
+ SQLRETURN rc;
+
+#if 0
+ try {
+ // This function is often not or badly implemented by data sources
+ rc = SQLDescribeParam(m_hstmt, n, &sqlt, &colsize, &dec, &nul);
+
+ if (!Check(rc))
+ ThrowDBX(rc, "SQLDescribeParam", m_hstmt);
+
+ } catch(DBX *x) {
+ snprintf(m_G->Message, sizeof(m_G->Message), "%s: %s", x->m_Msg, x->GetErrorMessage(0));
+#endif // 0
+ colsize = colp->GetPrecision();
+ sqlt = GetSQLType(buftype);
+ dec = IsTypeNum(buftype) ? colp->GetScale() : 0;
+ nul = colp->IsNullable() ? SQL_NULLABLE : SQL_NO_NULLS;
+//} // 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(1))
+ 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;
+ snprintf(g->Message, sizeof(g->Message), "Affected rows");
+ } else {
+ m_Tdb->AftRows = (int)ncol;
+ snprintf(g->Message, sizeof(g->Message), "Result set column number");
+ } // endif ncol
+
+ } catch(DBX *x) {
+ if (trace(1))
+ for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
+ htrc(x->m_ErrMsg[i]);
+
+ snprintf(g->Message, sizeof(g->Message), "Remote %s: %s", x->m_Msg, x->GetErrorMessage(0));
+
+ if (b)
+ SQLCancel(hstmt);
+
+ m_Tdb->AftRows = -1;
+ rcd = true;
+ } // end try/catch
+
+ if (!Check(rc = SQLFreeStmt(hstmt, SQL_CLOSE)))
+ snprintf(g->Message, sizeof(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)))
+ snprintf(g->Message, sizeof(g->Message), "SQLEndTran: rc=%d", rc);
+
+ if (!Check(rc = SQLSetConnectAttr(m_hdbc, SQL_ATTR_AUTOCOMMIT,
+ (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_UINTEGER)))
+ snprintf(g->Message, sizeof(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, PCSZ dsn, PCSZ 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;
+
+ 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] = MY_MAX(length[0], (UINT)nl);
+ } // endfor i
+
+ } catch(DBX *x) {
+ snprintf(g->Message, sizeof(g->Message), "%s: %s", x->m_Msg, x->GetErrorMessage(0));
+ goto err;
+ } // end try/catch
+
+ if (!ncol) {
+ snprintf(g->Message, sizeof(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) {
+ snprintf(g->Message, sizeof(g->Message), "%s: %s", x->m_Msg, 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) {
+ snprintf(m_G->Message, sizeof(m_G->Message), "%s: %s", x->m_Msg, 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) {
+ snprintf(m_G->Message, sizeof(m_G->Message), "%s: %s", x->m_Msg, 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;
+ } // end 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
+
+ SQLSMALLINT length(uint i)
+ {
+ DBUG_ASSERT(i < max_parts);
+ return (SQLSMALLINT)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;
+ PCSZ fnc = "Unknown";
+ UWORD n = 0;
+ SWORD ncol, len, tp;
+ SQLULEN crow = 0;
+ PQRYRES qrp = cap->Qrp;
+ PCOLRES crp;
+ RETCODE rc = 0;
+ HSTMT hstmt = NULL;
+ SQLLEN *vl, *vlen = NULL;
+ PVAL *pval = NULL;
+ char* *pbuf = 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:
+ fnc = "SQLTables";
+ rc = SQLTables(hstmt, name.ptr(2), name.length(2),
+ name.ptr(1), name.length(1),
+ name.ptr(0), name.length(0),
+ (SQLCHAR *)cap->Pat,
+ cap->Pat ? SQL_NTS : 0);
+ break;
+ case CAT_COL:
+ fnc = "SQLColumns";
+ rc = SQLColumns(hstmt, name.ptr(2), name.length(2),
+ name.ptr(1), name.length(1),
+ name.ptr(0), name.length(0),
+ (SQLCHAR *)cap->Pat,
+ cap->Pat ? SQL_NTS : 0);
+ 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");
+ break;
+ default:
+ ThrowDBX("Invalid SQL function id");
+ } // endswitch infotype
+
+ if (!Check(rc))
+ ThrowDBX(rc, fnc, hstmt);
+
+ // Some data source do not implement SQLNumResultCols
+ if (Check(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));
+ pbuf = (char**)PlugSubAlloc(g, NULL, n * sizeof(char*));
+
+ // Now bind the column buffers
+ for (n = 0, crp = qrp->Colresp; crp; crp = crp->Next) {
+ if ((tp = GetSQLCType(crp->Type)) == SQL_TYPE_NULL) {
+ snprintf(g->Message, sizeof(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);
+ pval[n]->SetNullable(true);
+
+ if (crp->Type == TYPE_STRING) {
+ pbuf[n] = (char*)PlugSubAlloc(g, NULL, len);
+ buffer = pbuf[n];
+ } else
+ 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(2) || (trace(1) && 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) {
+ if (vlen[n] == SQL_NO_TOTAL)
+ ThrowDBX("Unexpected SQL_NO_TOTAL returned from SQLFetch");
+ else if (vlen[n] == SQL_NULL_DATA)
+ pval[n]->SetNull(true);
+ else if (crp->Type == TYPE_STRING/* && vlen[n] != SQL_NULL_DATA*/)
+ pval[n]->SetValue_char(pbuf[n], (int)vlen[n]);
+ else
+ pval[n]->SetNull(false);
+
+ crp->Kdata->SetValue(pval[n], i);
+ cap->Vlen[n][i] = vlen[n];
+ } // endfor crp
+
+ } // endfor i
+
+#if 0
+ if ((crow = i) && (rc == SQL_NO_DATA || rc == SQL_SUCCESS_WITH_INFO))
+ rc = SQL_SUCCESS;
+
+ if (rc == SQL_NO_DATA_FOUND) {
+ if (cap->Pat)
+ snprintf(g->Message, sizeof(g->Message), MSG(NO_TABCOL_DATA), cap->Tab, cap->Pat);
+ else
+ snprintf(g->Message, sizeof(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);
+#endif // 0
+
+ if (!rc || rc == SQL_NO_DATA || rc == SQL_SUCCESS_WITH_INFO) {
+ if ((rc = SQLFetch(hstmt)) != SQL_NO_DATA_FOUND)
+ qrp->Truncated = true;
+
+ crow = i;
+ } else
+ ThrowDBX(rc, fnc, hstmt);
+
+ irc = (int)crow;
+ } catch(DBX *x) {
+ if (trace(1))
+ for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
+ htrc(x->m_ErrMsg[i]);
+
+ snprintf(g->Message, sizeof(g->Message), "%s: %s", x->m_Msg, 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
+
+/***********************************************************************/
+/* Allocate a CONNECT result structure from the ODBC result. */
+/***********************************************************************/
+PQRYRES ODBConn::AllocateResult(PGLOBAL g)
+ {
+ bool uns;
+ PODBCCOL colp;
+ PCOLRES *pcrp, crp;
+ PQRYRES qrp;
+
+ if (!m_Rows) {
+ snprintf(g->Message, sizeof(g->Message), "Void result");
+ return NULL;
+ } // endif m_Res
+
+ /*********************************************************************/
+ /* Allocate the result storage for future retrieval. */
+ /*********************************************************************/
+ qrp = (PQRYRES)PlugSubAlloc(g, NULL, sizeof(QRYRES));
+ pcrp = &qrp->Colresp;
+ qrp->Continued = FALSE;
+ qrp->Truncated = FALSE;
+ qrp->Info = FALSE;
+ qrp->Suball = TRUE;
+ qrp->BadLines = 0;
+ qrp->Maxsize = m_Rows;
+ qrp->Maxres = m_Rows;
+ qrp->Nbcol = 0;
+ qrp->Nblin = 0;
+ qrp->Cursor = 0;
+
+ for (colp = (PODBCCOL)m_Tdb->Columns; colp;
+ colp = (PODBCCOL)colp->GetNext())
+ if (!colp->IsSpecial()) {
+ *pcrp = (PCOLRES)PlugSubAlloc(g, NULL, sizeof(COLRES));
+ crp = *pcrp;
+ pcrp = &crp->Next;
+ memset(crp, 0, sizeof(COLRES));
+ crp->Ncol = ++qrp->Nbcol;
+ crp->Name = colp->GetName();
+ crp->Type = colp->GetResultType();
+ crp->Prec = colp->GetScale();
+ crp->Length = colp->GetLength();
+ crp->Clen = colp->GetBuflen();
+ uns = colp->IsUnsigned();
+
+ if (!(crp->Kdata = AllocValBlock(g, NULL, crp->Type, m_Rows,
+ crp->Clen, 0, FALSE, TRUE, uns))) {
+ snprintf(g->Message, sizeof(g->Message), MSG(INV_RESULT_TYPE),
+ GetFormatType(crp->Type));
+ return NULL;
+ } // endif Kdata
+
+ if (!colp->IsNullable())
+ crp->Nulls = NULL;
+ else {
+ crp->Nulls = (char*)PlugSubAlloc(g, NULL, m_Rows);
+ memset(crp->Nulls, ' ', m_Rows);
+ } // endelse Nullable
+
+ colp->SetCrp(crp);
+ } // endif colp
+
+ *pcrp = NULL;
+//qrp->Nblin = n;
+ return qrp;
+ } // end of AllocateResult
+
+/***********************************************************************/
+/* Restart from beginning of result set */
+/***********************************************************************/
+int ODBConn::Rewind(char *sql, ODBCCOL *tocols)
+ {
+ int rc, rbuf = -1;
+
+ if (!m_hstmt)
+ rbuf = 0;
+ else if (m_Full)
+ rbuf = m_Rows; // No need to "rewind"
+ else if (m_Scrollable) {
+ SQLULEN crow;
+
+ try {
+ rc = SQLExtendedFetch(m_hstmt, SQL_FETCH_FIRST, 1, &crow, NULL);
+
+ if (!Check(rc))
+ ThrowDBX(rc, "SQLExtendedFetch", m_hstmt);
+
+ rbuf = (int)crow;
+ } catch(DBX *x) {
+ snprintf(m_G->Message, sizeof(m_G->Message), "%s: %s", x->m_Msg, x->GetErrorMessage(0));
+ rbuf = -1;
+ } // end try/catch
+
+ } else if (ExecDirectSQL(sql, tocols) >= 0)
+ rbuf = 0;
+
+ return rbuf;
+ } // end of Rewind
+
+/***********************************************************************/
+/* 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(1) && rc != SQL_SUCCESS)
+ htrc("Error: SQLDisconnect rc=%d\n", rc);
+
+ rc = SQLFreeConnect(m_hdbc);
+
+ if (trace(1) && 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(1) && rc != SQL_SUCCESS) // Nothing we can do
+ htrc("Error: SQLFreeEnv failure ignored in Close\n");
+
+ m_henv = SQL_NULL_HENV;
+ } // endif m_henv
+
+ if (m_Fp)
+ m_Fp->Count = 0;
+
+ } // end of Close