diff options
Diffstat (limited to 'storage/connect/odbconn.cpp')
-rw-r--r-- | storage/connect/odbconn.cpp | 2654 |
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 |