1 /***********************************************************************/
2 /*  Name: ODBCONN.CPP  Version 2.4                                     */
3 /*                                                                     */
4 /*  (C) Copyright to the author Olivier BERTRAND          1998-2021    */
5 /*                                                                     */
6 /*  This file contains the ODBC connection classes functions.          */
7 /***********************************************************************/
8 
9 /***********************************************************************/
10 /*  Include relevant MariaDB header file.                              */
11 /***********************************************************************/
12 #include <my_global.h>
13 #include <m_string.h>
14 #if defined(_WIN32)
15 //nclude <io.h>
16 //nclude <fcntl.h>
17 #include <direct.h>                      // for getcwd
18 #if defined(__BORLANDC__)
19 #define __MFC_COMPAT__                   // To define min/max as macro
20 #endif
21 //#include <windows.h>
22 #else
23 #if defined(UNIX)
24 #include <errno.h>
25 #else
26 //nclude <io.h>
27 #endif
28 //nclude <fcntl.h>
29 #define NODW
30 #endif
31 
32 /***********************************************************************/
33 /*  Required objects includes.                                         */
34 /***********************************************************************/
35 #include "global.h"
36 #include "plgdbsem.h"
37 #include "xobject.h"
38 #include "xtable.h"
39 #include "tabext.h"
40 #include "odbccat.h"
41 #include "tabodbc.h"
42 #include "plgcnx.h"                       // For DB types
43 #include "resource.h"
44 #include "valblk.h"
45 #include "osutil.h"
46 
47 
48 #if defined(_WIN32)
49 /***********************************************************************/
50 /*  For dynamic load of ODBC32.DLL                                     */
51 /***********************************************************************/
52 #pragma comment(lib, "odbc32.lib")
53 extern "C" HINSTANCE s_hModule;           // Saved module handle
54 #endif // _WIN32
55 
56 TYPCONV GetTypeConv();
57 int GetConvSize();
58 void OdbcClose(PGLOBAL g, PFBLOCK fp);
59 
60 /***********************************************************************/
61 /*  Some macro's (should be defined elsewhere to be more accessible)   */
62 /***********************************************************************/
63 #if defined(_DEBUG)
64 #define ASSERT(f)          assert(f)
65 #define DEBUG_ONLY(f)      (f)
66 #else   // !_DEBUG
67 #define ASSERT(f)          ((void)0)
68 #define DEBUG_ONLY(f)      ((void)0)
69 #endif  // !_DEBUG
70 
71 /***********************************************************************/
72 /*  GetSQLType: returns the SQL_TYPE corresponding to a PLG type.      */
73 /***********************************************************************/
GetSQLType(int type)74 static short GetSQLType(int type)
75   {
76   short tp = SQL_TYPE_NULL;
77 
78   switch (type) {
79     case TYPE_STRING: tp = SQL_CHAR;      break;
80     case TYPE_SHORT:  tp = SQL_SMALLINT;  break;
81     case TYPE_INT:    tp = SQL_INTEGER;   break;
82     case TYPE_DATE:   tp = SQL_TIMESTAMP; break;
83     case TYPE_BIGINT: tp = SQL_BIGINT;    break;                //  (-5)
84     case TYPE_DOUBLE: tp = SQL_DOUBLE;    break;
85     case TYPE_TINY:   tp = SQL_TINYINT;   break;
86     case TYPE_DECIM:  tp = SQL_DECIMAL;   break;
87     } // endswitch type
88 
89   return tp;
90   } // end of GetSQLType
91 
92 /***********************************************************************/
93 /*  GetSQLCType: returns the SQL_C_TYPE corresponding to a PLG type.   */
94 /***********************************************************************/
GetSQLCType(int type)95 static int GetSQLCType(int type)
96   {
97   int tp = SQL_TYPE_NULL;
98 
99   switch (type) {
100     case TYPE_STRING: tp = SQL_C_CHAR;      break;
101     case TYPE_SHORT:  tp = SQL_C_SHORT;     break;
102     case TYPE_INT:    tp = SQL_C_LONG;      break;
103     case TYPE_DATE:   tp = SQL_C_TIMESTAMP; break;
104     case TYPE_BIGINT: tp = SQL_C_SBIGINT;   break;
105     case TYPE_DOUBLE: tp = SQL_C_DOUBLE;    break;
106     case TYPE_TINY :  tp = SQL_C_TINYINT;   break;
107 //#if (ODBCVER >= 0x0300)
108 //    case TYPE_DECIM:  tp = SQL_C_NUMERIC;   break;  (CRASH!!!)
109 //#else
110     case TYPE_DECIM:  tp = SQL_C_CHAR;      break;
111 //#endif
112 
113     } // endswitch type
114 
115   return tp;
116   } // end of GetSQLCType
117 
118 /***********************************************************************/
119 /*  TranslateSQLType: translate a SQL Type to a PLG type.              */
120 /***********************************************************************/
TranslateSQLType(int stp,int prec,int & len,char & v,bool & w)121 int TranslateSQLType(int stp, int prec, int& len, char& v, bool& w)
122   {
123   int type;
124 
125   switch (stp) {
126     case SQL_WVARCHAR:                      //  (-9)
127       w = true;
128     case SQL_VARCHAR:                       //   12
129       v = 'V';
130       type = TYPE_STRING;
131       break;
132     case SQL_WCHAR:                         //  (-8)
133       w = true;
134     case SQL_CHAR:                          //    1
135       type = TYPE_STRING;
136       break;
137     case SQL_WLONGVARCHAR:                  // (-10)
138       w = true;
139     case SQL_LONGVARCHAR:                   //  (-1)
140 			if (GetTypeConv() == TPC_YES || GetTypeConv() == TPC_FORCE) {
141 				v = 'V';
142 				type = TYPE_STRING;
143 				len = (len) ? MY_MIN(abs(len), GetConvSize()) : GetConvSize();
144 			} else
145 				type = TYPE_ERROR;
146 
147       break;
148     case SQL_NUMERIC:                       //    2
149     case SQL_DECIMAL:                       //    3
150 //    type = (prec || len > 20) ? TYPE_DOUBLE
151 //         : (len > 10) ? TYPE_BIGINT : TYPE_INT;
152       type = TYPE_DECIM;
153       break;
154     case SQL_INTEGER:                       //    4
155       type = TYPE_INT;
156       break;
157     case SQL_SMALLINT:                      //    5
158       type = TYPE_SHORT;
159       break;
160     case SQL_TINYINT:                       //  (-6)
161     case SQL_BIT:                           //  (-7)
162       type = TYPE_TINY;
163       break;
164     case SQL_FLOAT:                         //    6
165     case SQL_REAL:                          //    7
166     case SQL_DOUBLE:                        //    8
167       type = TYPE_DOUBLE;
168       break;
169     case SQL_DATETIME:                      //    9
170       type = TYPE_DATE;
171       len = 19;
172       break;
173     case SQL_TYPE_DATE:                     //   91
174       type = TYPE_DATE;
175       len = 10;
176       v = 'D';
177       break;
178     case SQL_INTERVAL:                      //   10
179     case SQL_TYPE_TIME:                     //   92
180       type = TYPE_STRING;
181       len = 8 + ((prec) ? (prec+1) : 0);
182       v = 'T';
183       break;
184     case SQL_TIMESTAMP:                     //   11
185     case SQL_TYPE_TIMESTAMP:                //   93
186       type = TYPE_DATE;
187       len = 19 + ((prec) ? (prec+1) : 0);
188       v = 'S';
189       break;
190     case SQL_BIGINT:                        //  (-5)
191       type = TYPE_BIGINT;
192       break;
193     case SQL_BINARY:                        //  (-2)
194     case SQL_VARBINARY:                     //  (-3)
195     case SQL_LONGVARBINARY:                 //  (-4)
196 			if (GetTypeConv() == TPC_FORCE) {
197 				v = 'V';
198 				type = TYPE_STRING;
199 				len = (len) ? MY_MIN(abs(len), GetConvSize()) : GetConvSize();
200 			}	else
201 				type = TYPE_ERROR;
202 
203 			break;
204 		case SQL_GUID:                          // (-11)
205 			type = TYPE_STRING;
206 			len = 36;
207 			break;
208 		case SQL_UNKNOWN_TYPE:                  //    0
209 		default:
210       type = TYPE_ERROR;
211       len = 0;
212     } // endswitch type
213 
214   return type;
215   } // end of TranslateSQLType
216 
217 #if defined(PROMPT_OK)
218 /***********************************************************************/
219 /*  ODBCCheckConnection: Check completeness of connection string.      */
220 /***********************************************************************/
ODBCCheckConnection(PGLOBAL g,char * dsn,int cop)221 char *ODBCCheckConnection(PGLOBAL g, char *dsn, int cop)
222   {
223   char    *newdsn, dir[_MAX_PATH], buf[_MAX_PATH];
224   int      rc;
225   DWORD    options = ODBConn::openReadOnly;
226   ODBConn *ocp = new(g) ODBConn(g, NULL);
227 
228   (void) getcwd(dir, sizeof(dir) - 1);
229 
230   switch (cop) {
231     case 1: options |= ODBConn::forceOdbcDialog; break;
232     case 2: options |= ODBConn::noOdbcDialog;    break;
233     } // endswitch cop
234 
235   if (ocp->Open(dsn, options) < 1)
236     newdsn = NULL;
237   else
238     newdsn = ocp->GetConnect();
239 
240   (void) getcwd(buf, sizeof(buf) - 1);
241 
242   // Some data sources change the current directory
243   if (strcmp(dir, buf))
244     rc = chdir(dir);
245 
246   ocp->Close();
247   return newdsn;         // Return complete connection string
248   } // end of ODBCCheckConnection
249 #endif   // PROMPT_OK
250 
251 /***********************************************************************/
252 /*  Allocate the structure used to refer to the result set.            */
253 /***********************************************************************/
AllocCatInfo(PGLOBAL g,CATINFO fid,PCSZ db,PCSZ tab,PQRYRES qrp)254 static CATPARM *AllocCatInfo(PGLOBAL g, CATINFO fid, PCSZ db,
255 	                                      PCSZ tab, PQRYRES qrp)
256 {
257 	size_t   i, m, n;
258 	CATPARM *cap;
259 
260 #if defined(_DEBUG)
261 	assert(qrp);
262 #endif
263 
264 	try {
265 		m = (size_t)qrp->Maxres;
266 		n = (size_t)qrp->Nbcol;
267 		cap = (CATPARM *)PlugSubAlloc(g, NULL, sizeof(CATPARM));
268 		memset(cap, 0, sizeof(CATPARM));
269 		cap->Id = fid;
270 		cap->Qrp = qrp;
271 		cap->DB = db;
272 		cap->Tab = tab;
273 		cap->Vlen = (SQLLEN* *)PlugSubAlloc(g, NULL, n * sizeof(SQLLEN *));
274 
275 		for (i = 0; i < n; i++)
276 			cap->Vlen[i] = (SQLLEN *)PlugSubAlloc(g, NULL, m * sizeof(SQLLEN));
277 
278 		cap->Status = (UWORD *)PlugSubAlloc(g, NULL, m * sizeof(UWORD));
279 
280 	} catch (int n) {
281 		htrc("Exeption %d: %s\n", n, g->Message);
282 		cap = NULL;
283 	} catch (const char *msg) {
284 		htrc(g->Message, msg);
285 		printf("%s\n", g->Message);
286 		cap = NULL;
287 	} // end catch
288 
289 	return cap;
290 } // end of AllocCatInfo
291 
292 #if 0
293 /***********************************************************************/
294 /*  Check for nulls and reset them to Null (?) values.                 */
295 /***********************************************************************/
296 static void ResetNullValues(CATPARM *cap)
297   {
298   int      i, n, ncol;
299   PCOLRES  crp;
300   PQRYRES  qrp = cap->Qrp;
301 
302 #if defined(_DEBUG)
303   assert(qrp);
304 #endif
305 
306   ncol = qrp->Nbcol;
307 
308   for (i = 0, crp = qrp->Colresp; i < ncol && crp; i++, crp = crp->Next)
309     for (n = 0; n < qrp->Nblin; n++)
310       if (cap->Vlen[i][n] == SQL_NULL_DATA)
311         crp->Kdata->Reset(n);
312 
313   } // end of ResetNullValues
314 #endif
315 
316 /***********************************************************************/
317 /*  Close an ODBC table after a thrown error (called by PlugCloseFile) */
318 /***********************************************************************/
OdbcClose(PGLOBAL g,PFBLOCK fp)319 void OdbcClose(PGLOBAL g, PFBLOCK fp) {
320 	((ODBConn*)fp->File)->Close();
321 }	// end of OdbcClose
322 
323 /***********************************************************************/
324 /*  ODBCColumns: constructs the result blocks containing all columns   */
325 /*  of an ODBC table that will be retrieved by GetData commands.       */
326 /***********************************************************************/
ODBCColumns(PGLOBAL g,PCSZ dsn,PCSZ db,PCSZ table,PCSZ colpat,int maxres,bool info,POPARM sop)327 PQRYRES ODBCColumns(PGLOBAL g, PCSZ dsn, PCSZ db, PCSZ table,
328 	                  PCSZ colpat, int maxres, bool info, POPARM sop)
329   {
330   int  buftyp[] = {TYPE_STRING, TYPE_STRING, TYPE_STRING, TYPE_STRING,
331                    TYPE_SHORT,  TYPE_STRING, TYPE_INT,    TYPE_INT,
332                    TYPE_SHORT,  TYPE_SHORT,  TYPE_SHORT,  TYPE_STRING};
333   XFLD fldtyp[] = {FLD_CAT,   FLD_SCHEM,    FLD_TABNAME, FLD_NAME,
334                    FLD_TYPE,  FLD_TYPENAME, FLD_PREC,    FLD_LENGTH,
335                    FLD_SCALE, FLD_RADIX,    FLD_NULL,    FLD_REM};
336   unsigned int length[] = {0, 0, 0, 0, 6, 0, 10, 10, 6, 6, 6, 0};
337 	bool     b[] = {true,true,false,false,false,false,false,false,true,true,false,true};
338   int      i, n, ncol = 12;
339 	PCOLRES  crp;
340 	PQRYRES  qrp;
341   CATPARM *cap;
342   ODBConn *ocp = NULL;
343 
344   /************************************************************************/
345   /*  Do an evaluation of the result size.                                */
346   /************************************************************************/
347   if (!info) {
348     ocp = new(g) ODBConn(g, NULL);
349 
350     if (ocp->Open(dsn, sop, 10) < 1)  // openReadOnly + noODBCdialog
351       return NULL;
352 
353     if (table && !strchr(table, '%')) {
354       // We fix a MySQL limit because some data sources return 32767
355       n = ocp->GetMaxValue(SQL_MAX_COLUMNS_IN_TABLE);
356       maxres = (n) ? MY_MIN(n, 4096) : 4096;
357     } else if (!maxres)
358       maxres = 20000;
359 
360 //  n = ocp->GetMaxValue(SQL_MAX_CATALOG_NAME_LEN);
361 //  length[0] = (n) ? (n + 1) : 0;
362 //  n = ocp->GetMaxValue(SQL_MAX_SCHEMA_NAME_LEN);
363 //  length[1] = (n) ? (n + 1) : 0;
364 //  n = ocp->GetMaxValue(SQL_MAX_TABLE_NAME_LEN);
365 //  length[2] = (n) ? (n + 1) : 0;
366     n = ocp->GetMaxValue(SQL_MAX_COLUMN_NAME_LEN);
367     length[3] = (n) ? (n + 1) : 128;
368   } else {                 // Info table
369     maxres = 0;
370     length[0] = 128;
371     length[1] = 128;
372     length[2] = 128;
373     length[3] = 128;
374     length[5] = 30;
375     length[11] = 255;
376   } // endif ocp
377 
378   if (trace(1))
379     htrc("ODBCColumns: max=%d len=%d,%d,%d,%d\n",
380          maxres, length[0], length[1], length[2], length[3]);
381 
382   /************************************************************************/
383   /*  Allocate the structures used to refer to the result set.            */
384   /************************************************************************/
385   qrp = PlgAllocResult(g, ncol, maxres, IDS_COLUMNS,
386                           buftyp, fldtyp, length, false, true);
387 
388 	for (i = 0, crp = qrp->Colresp; crp; i++, crp = crp->Next)
389 		if (b[i])
390 			crp->Kdata->SetNullable(true);
391 
392   if (info || !qrp)                      // Info table
393     return qrp;
394 
395   if (trace(1))
396     htrc("Getting col results ncol=%d\n", qrp->Nbcol);
397 
398   if (!(cap = AllocCatInfo(g, CAT_COL, db, table, qrp)))
399     return NULL;
400 
401   cap->Pat = colpat;
402 
403   /************************************************************************/
404   /*  Now get the results into blocks.                                    */
405   /************************************************************************/
406   if ((n = ocp->GetCatInfo(cap)) >= 0) {
407     qrp->Nblin = n;
408 //  ResetNullValues(cap);
409 
410     if (trace(1))
411       htrc("Columns: NBCOL=%d NBLIN=%d\n", qrp->Nbcol, qrp->Nblin);
412 
413   } else
414     qrp = NULL;
415 
416   /* Cleanup */
417   ocp->Close();
418 
419   /************************************************************************/
420   /*  Return the result pointer for use by GetData routines.              */
421   /************************************************************************/
422   return qrp;
423   } // end of ODBCColumns
424 
425 /**************************************************************************/
426 /*  ODBCSrcCols: constructs the result blocks containing the              */
427 /*  description of all the columns of a Srcdef option.                    */
428 /**************************************************************************/
ODBCSrcCols(PGLOBAL g,char * dsn,char * src,POPARM sop)429 PQRYRES ODBCSrcCols(PGLOBAL g, char *dsn, char *src, POPARM sop)
430   {
431 	char    *sqry;
432   ODBConn *ocp = new(g) ODBConn(g, NULL);
433 
434   if (ocp->Open(dsn, sop, 10) < 1)   // openReadOnly + noOdbcDialog
435     return NULL;
436 
437 	if (strstr(src, "%s")) {
438 		// Place holder for an eventual where clause
439 		sqry = (char*)PlugSubAlloc(g, NULL, strlen(src) + 3);
440 		sprintf(sqry, src, "1=1", "1=1");			 // dummy where clause
441 	} else
442 		sqry = src;
443 
444   return ocp->GetMetaData(g, dsn, sqry);
445   } // end of ODBCSrcCols
446 
447 #if 0
448 /**************************************************************************/
449 /* MyODBCCols: returns column info as required by ha_connect::pre_create. */
450 /**************************************************************************/
451 PQRYRES MyODBCCols(PGLOBAL g, char *dsn, char *tab, bool info)
452   {
453 //  int      i, type, len, prec;
454   bool     w = false;
455 //  PCOLRES  crp, crpt, crpl, crpp;
456   PQRYRES  qrp;
457   ODBConn *ocp;
458 
459   /**********************************************************************/
460   /*  Open the connection with the ODBC data source.                    */
461   /**********************************************************************/
462   if (!info) {
463     ocp = new(g) ODBConn(g, NULL);
464 
465     if (ocp->Open(dsn, 2) < 1)        // 2 is openReadOnly
466       return NULL;
467 
468   } else
469     ocp = NULL;
470 
471   /**********************************************************************/
472   /*  Get the information about the ODBC table columns.                 */
473   /**********************************************************************/
474   if ((qrp = ODBCColumns(g, ocp, dsn, NULL, tab, 0, NULL)) && ocp)
475     dsn = ocp->GetConnect();        // Complete connect string
476 
477   /************************************************************************/
478   /*  Close the local connection.                                         */
479   /************************************************************************/
480   if (ocp)
481     ocp->Close();
482 
483   if (!qrp)
484     return NULL;             // Error in ODBCColumns
485 
486   /************************************************************************/
487   /*  Keep only the info used by ha_connect::pre_create.                  */
488   /************************************************************************/
489   qrp->Colresp = qrp->Colresp->Next->Next;  // Skip Schema and Table names
490 
491   crpt = qrp->Colresp->Next;                // SQL type
492   crpl = crpt->Next->Next;                  // Length
493   crpp = crpl->Next->Next;                  // Decimals
494 
495   for (int i = 0; i < qrp->Nblin; i++) {
496     // Types must be PLG types, not SQL types
497     type = crpt->Kdata->GetIntValue(i);
498     len  = crpl->Kdata->GetIntValue(i);
499     prec = crpp->Kdata->GetIntValue(i);
500     type = TranslateSQLType(type, prec, len, w);
501     crpt->Kdata->SetValue(type, i);
502 
503     // Some data sources do not count prec in length
504     if (type == TYPE_DOUBLE)
505       len += (prec + 2);                    // To be safe
506 
507     // Could have been changed for blobs or numeric
508     crpl->Kdata->SetValue(len, i);
509     } // endfor i
510 
511   crpp->Next = crpp->Next->Next->Next;      // Should be Remark
512 
513   // Renumber crp's for flag comparison
514   for (i = 0, crp = qrp->Colresp; crp; crp = crp->Next)
515     crp->Ncol = ++i;
516 
517   qrp->Nbcol = i;             // Should be 7; was 11, skipped 4
518   return qrp;
519   } // end of MyODBCCols
520 #endif // 0
521 
522 /*************************************************************************/
523 /*  ODBCDrivers: constructs the result blocks containing all ODBC        */
524 /*  drivers available on the local host.                                 */
525 /*  Called with info=true to have result column names.                   */
526 /*************************************************************************/
ODBCDrivers(PGLOBAL g,int maxres,bool info)527 PQRYRES ODBCDrivers(PGLOBAL g, int maxres, bool info)
528   {
529   int      buftyp[] = {TYPE_STRING, TYPE_STRING};
530   XFLD     fldtyp[] = {FLD_NAME, FLD_REM};
531   unsigned int length[] = {128, 256};
532 	bool     b[] = {false, true};
533 	int      i, ncol = 2;
534 	PCOLRES  crp;
535 	PQRYRES  qrp;
536   ODBConn *ocp = NULL;
537 
538   /************************************************************************/
539   /*  Do an evaluation of the result size.                                */
540   /************************************************************************/
541   if (!info) {
542     ocp = new(g) ODBConn(g, NULL);
543 
544     if (!maxres)
545       maxres = 256;         // Estimated max number of drivers
546 
547   } else
548     maxres = 0;
549 
550   if (trace(1))
551     htrc("ODBCDrivers: max=%d len=%d\n", maxres, length[0]);
552 
553   /************************************************************************/
554   /*  Allocate the structures used to refer to the result set.            */
555   /************************************************************************/
556   qrp = PlgAllocResult(g, ncol, maxres, IDS_DRIVER,
557                           buftyp, fldtyp, length, false, true);
558 
559 	for (i = 0, crp = qrp->Colresp; crp; i++, crp = crp->Next)
560 		if (b[i])
561 			crp->Kdata->SetNullable(true);
562 
563 	/************************************************************************/
564   /*  Now get the results into blocks.                                    */
565   /************************************************************************/
566   if (!info && qrp && ocp->GetDrivers(qrp))
567     qrp = NULL;
568 
569   /************************************************************************/
570   /*  Return the result pointer for use by GetData routines.              */
571   /************************************************************************/
572   return qrp;
573   } // end of ODBCDrivers
574 
575 /*************************************************************************/
576 /*  ODBCDataSources: constructs the result blocks containing all ODBC    */
577 /*  data sources available on the local host.                            */
578 /*  Called with info=true to have result column names.                   */
579 /*************************************************************************/
ODBCDataSources(PGLOBAL g,int maxres,bool info)580 PQRYRES ODBCDataSources(PGLOBAL g, int maxres, bool info)
581   {
582   int      buftyp[] = {TYPE_STRING, TYPE_STRING};
583   XFLD     fldtyp[] = {FLD_NAME, FLD_REM};
584   unsigned int length[] = {0, 256};
585 	bool     b[] = {false, true};
586 	int      i, n = 0, ncol = 2;
587 	PCOLRES  crp;
588 	PQRYRES  qrp;
589   ODBConn *ocp = NULL;
590 
591   /************************************************************************/
592   /*  Do an evaluation of the result size.                                */
593   /************************************************************************/
594   if (!info) {
595     ocp = new(g) ODBConn(g, NULL);
596     n = ocp->GetMaxValue(SQL_MAX_DSN_LENGTH);
597     length[0] = (n) ? (n + 1) : 256;
598 
599     if (!maxres)
600       maxres = 512;         // Estimated max number of data sources
601 
602   } else {
603     length[0] = 256;
604     maxres = 0;
605   } // endif info
606 
607   if (trace(1))
608     htrc("ODBCDataSources: max=%d len=%d\n", maxres, length[0]);
609 
610   /************************************************************************/
611   /*  Allocate the structures used to refer to the result set.            */
612   /************************************************************************/
613   qrp = PlgAllocResult(g, ncol, maxres, IDS_DSRC,
614                           buftyp, fldtyp, length, false, true);
615 
616 	for (i = 0, crp = qrp->Colresp; crp; i++, crp = crp->Next)
617 		if (b[i])
618 			crp->Kdata->SetNullable(true);
619 
620 	/************************************************************************/
621   /*  Now get the results into blocks.                                    */
622   /************************************************************************/
623   if (!info && qrp && ocp->GetDataSources(qrp))
624     qrp = NULL;
625 
626   /************************************************************************/
627   /*  Return the result pointer for use by GetData routines.              */
628   /************************************************************************/
629   return qrp;
630   } // end of ODBCDataSources
631 
632 /**************************************************************************/
633 /*  ODBCTables: constructs the result blocks containing all tables in     */
634 /*  an ODBC database that will be retrieved by GetData commands.          */
635 /**************************************************************************/
ODBCTables(PGLOBAL g,PCSZ dsn,PCSZ db,PCSZ tabpat,PCSZ tabtyp,int maxres,bool info,POPARM sop)636 PQRYRES ODBCTables(PGLOBAL g, PCSZ dsn, PCSZ db, PCSZ tabpat, PCSZ tabtyp,
637 	                 int maxres, bool info, POPARM sop)
638   {
639   int      buftyp[] = {TYPE_STRING, TYPE_STRING, TYPE_STRING,
640                        TYPE_STRING, TYPE_STRING};
641   XFLD     fldtyp[] = {FLD_CAT,  FLD_SCHEM, FLD_NAME,
642                        FLD_TYPE, FLD_REM};
643   unsigned int length[] = {0, 0, 0, 16, 0};
644 	bool     b[] ={ true, true, false, false, true };
645 	int      i, n, ncol = 5;
646 	PCOLRES  crp;
647 	PQRYRES  qrp;
648   CATPARM *cap;
649   ODBConn *ocp = NULL;
650 
651   /************************************************************************/
652   /*  Do an evaluation of the result size.                                */
653   /************************************************************************/
654   if (!info) {
655     /**********************************************************************/
656     /*  Open the connection with the ODBC data source.                    */
657     /**********************************************************************/
658     ocp = new(g) ODBConn(g, NULL);
659 
660     if (ocp->Open(dsn, sop, 2) < 1)        // 2 is openReadOnly
661       return NULL;
662 
663     if (!maxres)
664       maxres = 10000;                 // This is completely arbitrary
665 
666 //  n = ocp->GetMaxValue(SQL_MAX_CATALOG_NAME_LEN);
667 //  length[0] = (n) ? (n + 1) : 0;
668 //  n = ocp->GetMaxValue(SQL_MAX_SCHEMA_NAME_LEN);
669 //  length[1] = (n) ? (n + 1) : 0;
670     n = ocp->GetMaxValue(SQL_MAX_TABLE_NAME_LEN);
671     length[2] = (n) ? (n + 1) : 128;
672   } else {
673     maxres = 0;
674     length[0] = 128;
675     length[1] = 128;
676     length[2] = 128;
677     length[4] = 255;
678   } // endif info
679 
680   if (trace(1))
681     htrc("ODBCTables: max=%d len=%d,%d\n", maxres, length[0], length[1]);
682 
683   /************************************************************************/
684   /*  Allocate the structures used to refer to the result set.            */
685   /************************************************************************/
686   qrp = PlgAllocResult(g, ncol, maxres, IDS_TABLES, buftyp,
687                                         fldtyp, length, false, true);
688 
689 	for (i = 0, crp = qrp->Colresp; crp; i++, crp = crp->Next)
690 		if (b[i])
691 			crp->Kdata->SetNullable(true);
692 
693 	if (info || !qrp)
694     return qrp;
695 
696   if (!(cap = AllocCatInfo(g, CAT_TAB, db, tabpat, qrp)))
697     return NULL;
698 
699 	cap->Pat = tabtyp;
700 
701   if (trace(1))
702     htrc("Getting table results ncol=%d\n", cap->Qrp->Nbcol);
703 
704   /************************************************************************/
705   /*  Now get the results into blocks.                                    */
706   /************************************************************************/
707   if ((n = ocp->GetCatInfo(cap)) >= 0) {
708     qrp->Nblin = n;
709 //  ResetNullValues(cap);
710 
711     if (trace(1))
712       htrc("Tables: NBCOL=%d NBLIN=%d\n", qrp->Nbcol, qrp->Nblin);
713 
714   } else
715     qrp = NULL;
716 
717   /************************************************************************/
718   /*  Close any local connection.                                         */
719   /************************************************************************/
720   ocp->Close();
721 
722   /************************************************************************/
723   /*  Return the result pointer for use by GetData routines.              */
724   /************************************************************************/
725   return qrp;
726   } // end of ODBCTables
727 
728 #if 0                           // Currently not used by CONNECT
729 /**************************************************************************/
730 /*  PrimaryKeys: constructs the result blocks containing all the          */
731 /*  ODBC catalog information concerning primary keys.                     */
732 /**************************************************************************/
733 PQRYRES ODBCPrimaryKeys(PGLOBAL g, ODBConn *op, char *dsn, char *table)
734   {
735   static int buftyp[] = {TYPE_STRING, TYPE_STRING, TYPE_STRING,
736                          TYPE_STRING, TYPE_SHORT,  TYPE_STRING};
737   static unsigned int length[] = {0, 0, 0, 0, 6, 128};
738   int      n, ncol = 5;
739   int     maxres;
740   PQRYRES  qrp;
741   CATPARM *cap;
742   ODBConn *ocp = op;
743 
744   if (!op) {
745     /**********************************************************************/
746     /*  Open the connection with the ODBC data source.                    */
747     /**********************************************************************/
748     ocp = new(g) ODBConn(g, NULL);
749 
750     if (ocp->Open(dsn, 2) < 1)        // 2 is openReadOnly
751       return NULL;
752 
753     } // endif op
754 
755   /************************************************************************/
756   /*  Do an evaluation of the result size.                                */
757   /************************************************************************/
758   n = ocp->GetMaxValue(SQL_MAX_COLUMNS_IN_TABLE);
759   maxres = (n) ? (int)n : 250;
760   n = ocp->GetMaxValue(SQL_MAX_CATALOG_NAME_LEN);
761   length[0] = (n) ? (n + 1) : 128;
762   n = ocp->GetMaxValue(SQL_MAX_SCHEMA_NAME_LEN);
763   length[1] = (n) ? (n + 1) : 128;
764   n = ocp->GetMaxValue(SQL_MAX_TABLE_NAME_LEN);
765   length[2] = (n) ? (n + 1) : 128;
766   n = ocp->GetMaxValue(SQL_MAX_COLUMN_NAME_LEN);
767   length[3] = (n) ? (n + 1) : 128;
768 
769   if (trace(1))
770     htrc("ODBCPrimaryKeys: max=%d len=%d,%d,%d\n",
771          maxres, length[0], length[1], length[2]);
772 
773   /************************************************************************/
774   /*  Allocate the structure used to refer to the result set.             */
775   /************************************************************************/
776   qrp = PlgAllocResult(g, ncol, maxres, IDS_PKEY,
777                           buftyp, NULL, length, false, true);
778 
779   if (trace(1))
780     htrc("Getting pkey results ncol=%d\n", qrp->Nbcol);
781 
782   cap = AllocCatInfo(g, CAT_KEY, NULL, table, qrp);
783 
784   /************************************************************************/
785   /*  Now get the results into blocks.                                    */
786   /************************************************************************/
787   if ((n = ocp->GetCatInfo(cap)) >= 0) {
788     qrp->Nblin = n;
789 //  ResetNullValues(cap);
790 
791     if (trace(1))
792       htrc("PrimaryKeys: NBCOL=%d NBLIN=%d\n", qrp->Nbcol, qrp->Nblin);
793 
794   } else
795     qrp = NULL;
796 
797   /************************************************************************/
798   /*  Close any local connection.                                         */
799   /************************************************************************/
800   if (!op)
801     ocp->Close();
802 
803   /************************************************************************/
804   /*  Return the result pointer for use by GetData routines.              */
805   /************************************************************************/
806   return qrp;
807   } // end of ODBCPrimaryKeys
808 
809 /**************************************************************************/
810 /*  Statistics: constructs the result blocks containing statistics        */
811 /*  about one or several tables to be retrieved by GetData commands.      */
812 /**************************************************************************/
813 PQRYRES ODBCStatistics(PGLOBAL g, ODBConn *op, char *dsn, char *pat,
814                                                int un, int acc)
815   {
816   static int buftyp[] = {TYPE_STRING,
817                          TYPE_STRING, TYPE_STRING, TYPE_SHORT, TYPE_STRING,
818                          TYPE_STRING, TYPE_SHORT,  TYPE_SHORT, TYPE_STRING,
819                          TYPE_STRING, TYPE_INT,   TYPE_INT,  TYPE_STRING};
820   static unsigned int length[] = {0, 0, 0 ,6 ,0 ,0 ,6 ,6 ,0 ,2 ,10 ,10 ,128};
821   int      n, ncol = 13;
822   int     maxres;
823   PQRYRES  qrp;
824   CATPARM *cap;
825   ODBConn *ocp = op;
826 
827   if (!op) {
828     /**********************************************************************/
829     /*  Open the connection with the ODBC data source.                    */
830     /**********************************************************************/
831     ocp = new(g) ODBConn(g, NULL);
832 
833     if (ocp->Open(dsn, 2) < 1)        // 2 is openReadOnly
834       return NULL;
835 
836     } // endif op
837 
838   /************************************************************************/
839   /*  Do an evaluation of the result size.                                */
840   /************************************************************************/
841   n = 1 + ocp->GetMaxValue(SQL_MAX_COLUMNS_IN_INDEX);
842   maxres = (n) ? (int)n : 32;
843   n = ocp->GetMaxValue(SQL_MAX_SCHEMA_NAME_LEN);
844   length[1] = (n) ? (n + 1) : 128;
845   n = ocp->GetMaxValue(SQL_MAX_TABLE_NAME_LEN);
846   length[2] = length[5] = (n) ? (n + 1) : 128;
847   n = ocp->GetMaxValue(SQL_MAX_CATALOG_NAME_LEN);
848   length[0] = length[4] = (n) ? (n + 1) : length[2];
849   n = ocp->GetMaxValue(SQL_MAX_COLUMN_NAME_LEN);
850   length[7] = (n) ? (n + 1) : 128;
851 
852   if (trace(1))
853     htrc("SemStatistics: max=%d pat=%s\n", maxres, SVP(pat));
854 
855   /************************************************************************/
856   /*  Allocate the structure used to refer to the result set.             */
857   /************************************************************************/
858   qrp = PlgAllocResult(g, ncol, maxres, IDS_STAT,
859                           buftyp, NULL, length, false, true);
860 
861   if (trace(1))
862     htrc("Getting stat results ncol=%d\n", qrp->Nbcol);
863 
864   cap = AllocCatInfo(g, CAT_STAT, NULL, pat, qrp);
865   cap->Unique = (un < 0) ? SQL_INDEX_UNIQUE : (UWORD)un;
866   cap->Accuracy = (acc < 0) ? SQL_QUICK : (UWORD)acc;
867 
868   /************************************************************************/
869   /*  Now get the results into blocks.                                    */
870   /************************************************************************/
871   if ((n = ocp->GetCatInfo(cap)) >= 0) {
872     qrp->Nblin = n;
873 //  ResetNullValues(cap);
874 
875     if (trace(1))
876       htrc("Statistics: NBCOL=%d NBLIN=%d\n", qrp->Nbcol, qrp->Nblin);
877 
878   } else
879     qrp = NULL;
880 
881   /************************************************************************/
882   /*  Close any local connection.                                         */
883   /************************************************************************/
884   if (!op)
885     ocp->Close();
886 
887   /************************************************************************/
888   /*  Return the result pointer for use by GetData routines.              */
889   /************************************************************************/
890   return qrp;
891   } // end of Statistics
892 #endif // 0
893 
894 /***********************************************************************/
895 /*  Implementation of DBX class.                                       */
896 /***********************************************************************/
DBX(RETCODE rc,PCSZ msg)897 DBX::DBX(RETCODE rc, PCSZ msg)
898   {
899   m_RC = rc;
900   m_Msg = msg;
901 
902   for (int i = 0; i < MAX_NUM_OF_MSG; i++)
903     m_ErrMsg[i] = NULL;
904 
905   } // end of DBX constructor
906 
907 /***********************************************************************/
908 /*  This function is called by ThrowDBX.                               */
909 /***********************************************************************/
BuildErrorMessage(ODBConn * pdb,HSTMT hstmt)910 bool DBX::BuildErrorMessage(ODBConn* pdb, HSTMT hstmt)
911   {
912   if (pdb) {
913     SWORD   len;
914     RETCODE rc;
915     UCHAR   msg[SQL_MAX_MESSAGE_LENGTH + 1];
916     UCHAR   state[SQL_SQLSTATE_SIZE + 1];
917     SDWORD  native;
918     PGLOBAL g = pdb->m_G;
919 
920     rc = SQLError(pdb->m_henv, pdb->m_hdbc, hstmt, state,
921                   &native, msg, SQL_MAX_MESSAGE_LENGTH - 1, &len);
922 
923     if (rc == SQL_NO_DATA_FOUND)
924       return false;
925     else if (rc != SQL_INVALID_HANDLE) {
926     // Skip non-errors
927       for (int i = 0; i < MAX_NUM_OF_MSG
928               && (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO)
929               && strcmp((char*)state, "00000"); i++) {
930         m_ErrMsg[i] = (PSZ)PlugDup(g, (char*)msg);
931 
932         if (trace(1))
933           htrc("%s: %s, Native=%d\n", state, msg, native);
934 
935         rc = SQLError(pdb->m_henv, pdb->m_hdbc, hstmt, state,
936                       &native, msg, SQL_MAX_MESSAGE_LENGTH - 1, &len);
937 
938         } // endfor i
939 
940       return true;
941     } else {
942       snprintf((char*)msg, SQL_MAX_MESSAGE_LENGTH + 1, "%s: %s", m_Msg,
943                MSG(BAD_HANDLE_VAL));
944       m_ErrMsg[0] = (PSZ)PlugDup(g, (char*)msg);
945 
946       if (trace(1))
947         htrc("%s: rc=%hd\n", SVP(m_ErrMsg[0]), m_RC);
948 
949       return true;
950     } // endif rc
951 
952   } else
953     m_ErrMsg[0] = "No connexion address provided";
954 
955   if (trace(1))
956     htrc("%s: rc=%hd (%s)\n", SVP(m_Msg), m_RC, SVP(m_ErrMsg[0]));
957 
958   return true;
959   } // end of BuildErrorMessage
960 
GetErrorMessage(int i)961 const char *DBX::GetErrorMessage(int i)
962   {
963     if (i < 0 || i >= MAX_NUM_OF_MSG)
964       return "No ODBC error";
965     else if (m_ErrMsg[i])
966       return m_ErrMsg[i];
967     else
968       return (m_Msg) ? m_Msg : "Unknown error";
969 
970   } // end of GetErrorMessage
971 
972 /***********************************************************************/
973 /*  ODBConn construction/destruction.                                  */
974 /***********************************************************************/
ODBConn(PGLOBAL g,TDBODBC * tdbp)975 ODBConn::ODBConn(PGLOBAL g, TDBODBC *tdbp)
976   {
977   m_G = g;
978   m_Tdb = tdbp;
979   m_henv = SQL_NULL_HENV;
980   m_hdbc = SQL_NULL_HDBC;
981 //m_Recset = NULL
982   m_hstmt = SQL_NULL_HSTMT;
983   m_LoginTimeout = DEFAULT_LOGIN_TIMEOUT;
984   m_QueryTimeout = DEFAULT_QUERY_TIMEOUT;
985   m_UpdateOptions = 0;
986   m_RowsetSize = (DWORD)((tdbp) ? tdbp->Rows : 10);
987   m_Catver = (tdbp) ? tdbp->Catver : 0;
988   m_Rows = 0;
989   m_Fetch = 0;
990 	m_Fp = NULL;
991   m_Connect = NULL;
992   m_User = NULL;
993   m_Pwd = NULL;
994   m_Updatable = true;
995   m_Transact = false;
996   m_Scrollable = (tdbp) ? tdbp->Scrollable : false;
997   m_Full = false;
998   m_UseCnc = false;
999   m_IDQuoteChar[0] = '"';
1000   m_IDQuoteChar[1] = 0;
1001 //*m_ErrMsg = '\0';
1002   } // end of ODBConn
1003 
1004 //ODBConn::~ODBConn()
1005 //  {
1006 //if (Connected())
1007 //  EndCom();
1008 
1009 //  } // end of ~ODBConn
1010 
1011 /***********************************************************************/
1012 /*  Screen for errors.                                                 */
1013 /***********************************************************************/
Check(RETCODE rc)1014 bool ODBConn::Check(RETCODE rc)
1015   {
1016   switch (rc) {
1017     case SQL_SUCCESS_WITH_INFO:
1018       if (trace(1)) {
1019         DBX x(rc);
1020 
1021         if (x.BuildErrorMessage(this, m_hstmt))
1022           htrc("ODBC Success With Info, hstmt=%p %s\n",
1023             m_hstmt, x.GetErrorMessage(0));
1024 
1025         } // endif trace
1026 
1027       // Fall through
1028     case SQL_SUCCESS:
1029     case SQL_NO_DATA_FOUND:
1030       return true;
1031     } // endswitch rc
1032 
1033   return false;
1034   } // end of Check
1035 
1036 /***********************************************************************/
1037 /*  DB exception throw routines.                                       */
1038 /***********************************************************************/
ThrowDBX(RETCODE rc,PCSZ msg,HSTMT hstmt)1039 void ODBConn::ThrowDBX(RETCODE rc, PCSZ msg, HSTMT hstmt)
1040   {
1041   DBX* xp = new(m_G) DBX(rc, msg);
1042 
1043   // Don't throw if no error
1044   if (xp->BuildErrorMessage(this, hstmt))
1045     throw xp;
1046 
1047   } // end of ThrowDBX
1048 
ThrowDBX(PCSZ msg)1049 void ODBConn::ThrowDBX(PCSZ msg)
1050   {
1051   DBX* xp = new(m_G) DBX(0, "Error");
1052 
1053   xp->m_ErrMsg[0] = msg;
1054   throw xp;
1055   } // end of ThrowDBX
1056 
1057 /***********************************************************************/
1058 /*  Utility routine.                                                   */
1059 /***********************************************************************/
GetStringInfo(ushort infotype)1060 PSZ ODBConn::GetStringInfo(ushort infotype)
1061   {
1062 //ASSERT(m_hdbc != SQL_NULL_HDBC);
1063   char   *p, buffer[MAX_STRING_INFO];
1064   SWORD   result;
1065   RETCODE rc;
1066 
1067   rc = SQLGetInfo(m_hdbc, infotype, buffer, sizeof(buffer), &result);
1068 
1069   if (!Check(rc)) {
1070     ThrowDBX(rc, "SQLGetInfo");  // Temporary
1071 //  *buffer = '\0';
1072     } // endif rc
1073 
1074   p = PlugDup(m_G, buffer);
1075   return p;
1076   } // end of GetStringInfo
1077 
1078 /***********************************************************************/
1079 /*  Utility routine.                                                   */
1080 /***********************************************************************/
GetMaxValue(ushort infotype)1081 int ODBConn::GetMaxValue(ushort infotype)
1082   {
1083 //ASSERT(m_hdbc != SQL_NULL_HDBC);
1084   ushort  maxval;
1085   RETCODE rc;
1086 
1087   rc = SQLGetInfo(m_hdbc, infotype, &maxval, 0, NULL);
1088 
1089   if (!Check(rc))
1090     maxval = 0;
1091 
1092   return (int)maxval;
1093   } // end of GetMaxValue
1094 
1095 /***********************************************************************/
1096 /*  Utility routines.                                                  */
1097 /***********************************************************************/
OnSetOptions(HSTMT hstmt)1098 void ODBConn::OnSetOptions(HSTMT hstmt)
1099   {
1100   RETCODE rc;
1101   ASSERT(m_hdbc != SQL_NULL_HDBC);
1102 
1103   if ((signed)m_QueryTimeout != -1) {
1104     // Attempt to set query timeout.  Ignore failure
1105     rc = SQLSetStmtOption(hstmt, SQL_QUERY_TIMEOUT, m_QueryTimeout);
1106 
1107     if (!Check(rc))
1108       // don't attempt it again
1109       m_QueryTimeout = (DWORD)-1;
1110 
1111     } // endif m_QueryTimeout
1112 
1113   if (m_RowsetSize > 0) {
1114     // Attempt to set rowset size.
1115     // In case of failure reset it to 0 to use Fetch.
1116     rc = SQLSetStmtOption(hstmt, SQL_ROWSET_SIZE, m_RowsetSize);
1117 
1118     if (!Check(rc))
1119       // don't attempt it again
1120       m_RowsetSize = 0;
1121 
1122     } // endif m_RowsetSize
1123 
1124   } // end of OnSetOptions
1125 
1126 /***********************************************************************/
1127 /*  Open: connect to a data source.                                    */
1128 /***********************************************************************/
Open(PCSZ ConnectString,POPARM sop,DWORD options)1129 int ODBConn::Open(PCSZ ConnectString, POPARM sop, DWORD options)
1130   {
1131   PGLOBAL& g = m_G;
1132 //ASSERT_VALID(this);
1133 //ASSERT(ConnectString == NULL || AfxIsValidString(ConnectString));
1134   ASSERT(!(options & noOdbcDialog && options & forceOdbcDialog));
1135 
1136   m_Updatable = !(options & openReadOnly);
1137   m_Connect = ConnectString;
1138   m_User = sop->User;
1139   m_Pwd = sop->Pwd;
1140   m_LoginTimeout = sop->Cto;
1141   m_QueryTimeout = sop->Qto;
1142   m_UseCnc = sop->UseCnc;
1143 
1144   // Allocate the HDBC and make connection
1145   try {
1146     /*PSZ ver;*/
1147 
1148     AllocConnect(options);
1149     /*ver = GetStringInfo(SQL_ODBC_VER);*/
1150 
1151     if (!m_UseCnc) {
1152       if (DriverConnect(options)) {
1153         strcpy(g->Message, MSG(CONNECT_CANCEL));
1154         return 0;
1155         } // endif
1156 
1157     } else           // Connect using SQLConnect
1158       Connect();
1159 
1160 		/*********************************************************************/
1161 		/*  Link a Fblock. This make possible to automatically close it      */
1162 		/*  in case of error (throw).                                        */
1163 		/*********************************************************************/
1164 		PDBUSER dbuserp = (PDBUSER)g->Activityp->Aptr;
1165 
1166 		m_Fp = (PFBLOCK)PlugSubAlloc(g, NULL, sizeof(FBLOCK));
1167 		m_Fp->Type = TYPE_FB_ODBC;
1168 		m_Fp->Fname = NULL;
1169 		m_Fp->Next = dbuserp->Openlist;
1170 		dbuserp->Openlist = m_Fp;
1171 		m_Fp->Count = 1;
1172 		m_Fp->Length = 0;
1173 		m_Fp->Memory = NULL;
1174 		m_Fp->Mode = MODE_ANY;
1175 		m_Fp->File = this;
1176 		m_Fp->Handle = 0;
1177 
1178 		/*ver = GetStringInfo(SQL_DRIVER_ODBC_VER);*/
1179     // Verify support for required functionality and cache info
1180 //  VerifyConnect();         Deprecated
1181     GetConnectInfo();
1182   } catch(DBX *xp) {
1183     sprintf(g->Message, "%s: %s", xp->m_Msg, xp->GetErrorMessage(0));
1184     Close();
1185 //  Free();
1186     return -1;
1187   } // end try-catch
1188 
1189   return 1;
1190   } // end of Open
1191 
1192 /***********************************************************************/
1193 /*  Allocate an henv (first time called) and hdbc.                     */
1194 /***********************************************************************/
AllocConnect(DWORD Options)1195 void ODBConn::AllocConnect(DWORD Options)
1196   {
1197   if (m_hdbc != SQL_NULL_HDBC)
1198     return;
1199 
1200   RETCODE rc;
1201 //AfxLockGlobals(CRIT_ODBC);
1202 
1203   // Need to allocate an environment for first connection
1204   if (m_henv == SQL_NULL_HENV) {
1205 //  ASSERT(m_nAlloc == 0);
1206 
1207     rc = SQLAllocEnv(&m_henv);
1208 
1209     if (!Check(rc)) {
1210 //    AfxUnlockGlobals(CRIT_ODBC);
1211       ThrowDBX(rc, "SQLAllocEnv");  // Fatal
1212       } // endif rc
1213 
1214     } // endif m_henv
1215 
1216   // Do the real thing, allocating connection data
1217   rc = SQLAllocConnect(m_henv, &m_hdbc);
1218 
1219   if (!Check(rc)) {
1220 //  AfxUnlockGlobals(CRIT_ODBC);
1221     ThrowDBX(rc, "SQLAllocConnect");  // Fatal
1222     } // endif rc
1223 
1224 //m_nAlloc++;                          // allocated at last
1225 //AfxUnlockGlobals(CRIT_ODBC);
1226 
1227 #if defined(_DEBUG)
1228   if (Options & traceSQL) {
1229     SQLSetConnectOption(m_hdbc, SQL_OPT_TRACEFILE, (SQLULEN)"xodbc.out");
1230     SQLSetConnectOption(m_hdbc, SQL_OPT_TRACE, 1);
1231     } // endif
1232 #endif // _DEBUG
1233 
1234   if ((signed)m_LoginTimeout >= 0) {
1235     rc = SQLSetConnectOption(m_hdbc, SQL_LOGIN_TIMEOUT, m_LoginTimeout);
1236 
1237     if (trace(1) && rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
1238       htrc("Warning: Failure setting login timeout\n");
1239 
1240     } // endif Timeout
1241 
1242   if (!m_Updatable) {
1243     rc = SQLSetConnectOption(m_hdbc, SQL_ACCESS_MODE, SQL_MODE_READ_ONLY);
1244 
1245     if (trace(1) && rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
1246       htrc("Warning: Failure setting read only access mode\n");
1247 
1248     } // endif
1249 
1250   // Turn on cursor lib support
1251   if (Options & useCursorLib)
1252     rc = SQLSetConnectOption(m_hdbc, SQL_ODBC_CURSORS, SQL_CUR_USE_DRIVER);
1253 
1254   return;
1255   } // end of AllocConnect
1256 
1257 /***********************************************************************/
1258 /*  Connect to data source using SQLConnect.                           */
1259 /***********************************************************************/
Connect(void)1260 void ODBConn::Connect(void)
1261   {
1262   SQLRETURN   rc;
1263   SQLSMALLINT ul = (m_User ? SQL_NTS : 0);
1264   SQLSMALLINT pl = (m_Pwd ? SQL_NTS : 0);
1265 
1266   rc = SQLConnect(m_hdbc, (SQLCHAR*)m_Connect, SQL_NTS,
1267                           (SQLCHAR*)m_User, ul, (SQLCHAR*)m_Pwd, pl);
1268 
1269   if (!Check(rc))
1270     ThrowDBX(rc, "SQLConnect");
1271 
1272   } // end of Connect
1273 
1274 /***********************************************************************/
1275 /*  Connect to data source using SQLDriverConnect.                     */
1276 /***********************************************************************/
DriverConnect(DWORD Options)1277 bool ODBConn::DriverConnect(DWORD Options)
1278   {
1279   RETCODE rc;
1280   SWORD   nResult;
1281   PUCHAR  ConnOut = (PUCHAR)PlugSubAlloc(m_G, NULL, MAX_CONNECT_LEN);
1282   UWORD   wConnectOption = SQL_DRIVER_COMPLETE;
1283 #if defined(_WIN32)
1284   HWND    hWndTop = GetForegroundWindow();
1285   HWND    hWnd = GetParent(hWndTop);
1286 
1287   if (hWnd == NULL)
1288     hWnd = GetDesktopWindow();
1289 #else   // !_WIN32
1290   HWND    hWnd = (HWND)1;
1291 #endif  // !_WIN32
1292 
1293     wConnectOption = SQL_DRIVER_NOPROMPT;
1294 //else if (Options & forceOdbcDialog)
1295 //  wConnectOption = SQL_DRIVER_PROMPT;
1296 
1297   rc = SQLDriverConnect(m_hdbc, hWnd, (PUCHAR)m_Connect,
1298                         SQL_NTS, ConnOut, MAX_CONNECT_LEN,
1299                         &nResult, wConnectOption);
1300 
1301 #if defined(_WIN32)
1302   if (hWndTop)
1303     EnableWindow(hWndTop, true);
1304 #endif   // _WIN32
1305 
1306   // If user hit 'Cancel'
1307   if (rc == SQL_NO_DATA_FOUND) {
1308     Close();
1309 //  Free();
1310     return true;
1311     } // endif rc
1312 
1313   if (!Check(rc))
1314     ThrowDBX(rc, "SQLDriverConnect");
1315 
1316   // Save connect string returned from ODBC
1317   m_Connect = (PSZ)ConnOut;
1318 
1319   // All done
1320   return false;
1321   } // end of DriverConnect
1322 
VerifyConnect()1323 void ODBConn::VerifyConnect()
1324   {
1325 #if defined(NEWMSG) || defined(XMSG)
1326   PGLOBAL& g = m_G;
1327 #endif   // NEWMSG  ||         XMSG
1328   RETCODE  rc;
1329   SWORD    result;
1330   SWORD    conformance;
1331 
1332   rc = SQLGetInfo(m_hdbc, SQL_ODBC_API_CONFORMANCE,
1333                   &conformance, sizeof(conformance), &result);
1334 
1335   if (!Check(rc))
1336     ThrowDBX(rc, "SQLGetInfo");
1337 
1338   if (conformance < SQL_OAC_LEVEL1)
1339     ThrowDBX(MSG(API_CONF_ERROR));
1340 
1341   rc = SQLGetInfo(m_hdbc, SQL_ODBC_SQL_CONFORMANCE,
1342                   &conformance, sizeof(conformance), &result);
1343 
1344   if (!Check(rc))
1345     ThrowDBX(rc, "SQLGetInfo");
1346 
1347   if (conformance < SQL_OSC_MINIMUM)
1348     ThrowDBX(MSG(SQL_CONF_ERROR));
1349 
1350   } // end of VerifyConnect
1351 
GetConnectInfo()1352 void ODBConn::GetConnectInfo()
1353   {
1354   RETCODE rc;
1355   SWORD   nResult;
1356 #if 0                   // Update not implemented yet
1357   UDWORD  DrvPosOp;
1358 
1359   // Reset the database update options
1360   m_UpdateOptions = 0;
1361 
1362   // Check for SQLSetPos support
1363   rc = SQLGetInfo(m_hdbc, SQL_POS_OPERATIONS,
1364                   &DrvPosOp, sizeof(DrvPosOp), &nResult);
1365 
1366   if (Check(rc) &&
1367       (DrvPosOp & SQL_POS_UPDATE) &&
1368       (DrvPosOp & SQL_POS_DELETE) &&
1369       (DrvPosOp & SQL_POS_ADD))
1370      m_UpdateOptions = SQL_SETPOSUPDATES;
1371 
1372   // Check for positioned update SQL support
1373   UDWORD PosStatements;
1374 
1375   rc = SQLGetInfo(m_hdbc, SQL_POSITIONED_STATEMENTS,
1376                         &PosStatements, sizeof(PosStatements),
1377                         &nResult);
1378 
1379   if (Check(rc) &&
1380       (PosStatements & SQL_PS_POSITIONED_DELETE) &&
1381       (PosStatements & SQL_PS_POSITIONED_UPDATE))
1382     m_UpdateOptions |= SQL_POSITIONEDSQL;
1383 
1384   if (m_Updatable) {
1385     // Make sure data source is Updatable
1386     char ReadOnly[10];
1387 
1388     rc = SQLGetInfo(m_hdbc, SQL_DATA_SOURCE_READ_ONLY,
1389                     ReadOnly, sizeof(ReadOnly), &nResult);
1390 
1391     if (Check(rc) && nResult == 1)
1392       m_Updatable = !!strcmp(ReadOnly, "Y");
1393     else
1394       m_Updatable = false;
1395 
1396     if (trace(1))
1397       htrc("Warning: data source is readonly\n");
1398 
1399   } else // Make data source is !Updatable
1400     rc = SQLSetConnectOption(m_hdbc, SQL_ACCESS_MODE,
1401                                      SQL_MODE_READ_ONLY);
1402 #endif   // 0
1403 
1404   // Get the quote char to use when constructing SQL
1405   rc = SQLGetInfo(m_hdbc, SQL_IDENTIFIER_QUOTE_CHAR,
1406                   m_IDQuoteChar, sizeof(m_IDQuoteChar), &nResult);
1407 
1408   if (trace(1))
1409     htrc("DBMS: %s, Version: %s, rc=%d\n",
1410          GetStringInfo(SQL_DBMS_NAME), GetStringInfo(SQL_DBMS_VER), rc);
1411 
1412   } // end of GetConnectInfo
1413 
1414 /***********************************************************************/
1415 /*  Allocate record set and execute an SQL query.                      */
1416 /***********************************************************************/
ExecDirectSQL(char * sql,ODBCCOL * tocols)1417 int ODBConn::ExecDirectSQL(char *sql, ODBCCOL *tocols)
1418   {
1419   PGLOBAL& g = m_G;
1420   void    *buffer;
1421   bool     b;
1422   UWORD    n, k;
1423   SWORD    len, tp, ncol = 0;
1424   ODBCCOL *colp;
1425   RETCODE  rc;
1426   HSTMT    hstmt;
1427 
1428   try {
1429     b = false;
1430 
1431     if (m_hstmt) {
1432       // This is a Requery
1433       rc = SQLFreeStmt(m_hstmt, SQL_CLOSE);
1434 
1435       if (!Check(rc))
1436         ThrowDBX(rc, "SQLFreeStmt", m_hstmt);
1437 
1438       m_hstmt = NULL;
1439       } // endif m_hstmt
1440 
1441     rc = SQLAllocStmt(m_hdbc, &hstmt);
1442 
1443     if (!Check(rc))
1444       ThrowDBX(rc, "SQLAllocStmt");
1445 
1446     if (m_Scrollable) {
1447       rc = SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_SCROLLABLE,
1448                           (void*)SQL_SCROLLABLE, 0);
1449 
1450       if (!Check(rc))
1451         ThrowDBX(rc, "Scrollable", hstmt);
1452 
1453       } // endif m_Scrollable
1454 
1455     OnSetOptions(hstmt);
1456     b = true;
1457 
1458     if (trace(1))
1459       htrc("ExecDirect hstmt=%p %.256s\n", hstmt, sql);
1460 
1461     if (m_Tdb->Srcdef) {
1462       // Be sure this is a query returning a result set
1463       do {
1464         rc = SQLPrepare(hstmt, (PUCHAR)sql, SQL_NTS);
1465         } while (rc == SQL_STILL_EXECUTING);
1466 
1467       if (!Check(rc))
1468         ThrowDBX(rc, "SQLPrepare", hstmt);
1469 
1470       if (!Check(rc = SQLNumResultCols(hstmt, &ncol)))
1471         ThrowDBX(rc, "SQLNumResultCols", hstmt);
1472 
1473       if (ncol == 0) {
1474         strcpy(g->Message, "This Srcdef does not return a result set");
1475         return -1;
1476         } // endif ncol
1477 
1478       // Ok, now we can proceed
1479       do {
1480         rc = SQLExecute(hstmt);
1481         } while (rc == SQL_STILL_EXECUTING);
1482 
1483       if (!Check(rc))
1484         ThrowDBX(rc, "SQLExecute", hstmt);
1485 
1486     } else {
1487       do {
1488         rc = SQLExecDirect(hstmt, (PUCHAR)sql, SQL_NTS);
1489       } while (rc == SQL_STILL_EXECUTING);
1490 
1491       if (!Check(rc))
1492         ThrowDBX(rc, "SQLExecDirect", hstmt);
1493 
1494       do {
1495         rc = SQLNumResultCols(hstmt, &ncol);
1496       } while (rc == SQL_STILL_EXECUTING);
1497 
1498       k = 0;    // used for column number
1499     } // endif Srcdef
1500 
1501     for (n = 0, colp = tocols; colp; colp = (PODBCCOL)colp->GetNext())
1502       if (!colp->IsSpecial())
1503         n++;
1504 
1505     // n can be 0 for query such as Select count(*) from table
1506     if (n && n > (UWORD)ncol)
1507       ThrowDBX(MSG(COL_NUM_MISM));
1508 
1509     // Now bind the column buffers
1510     for (colp = tocols; colp; colp = (PODBCCOL)colp->GetNext())
1511       if (!colp->IsSpecial()) {
1512         buffer = colp->GetBuffer(m_RowsetSize);
1513         len = colp->GetBuflen();
1514         tp = GetSQLCType(colp->GetResultType());
1515 
1516         if (tp == SQL_TYPE_NULL) {
1517           sprintf(m_G->Message, MSG(INV_COLUMN_TYPE),
1518                   colp->GetResultType(), SVP(colp->GetName()));
1519           ThrowDBX(m_G->Message);
1520         } // endif tp
1521 
1522         if (m_Tdb->Srcdef)
1523           k = colp->GetIndex();
1524         else
1525           k++;
1526 
1527         if (trace(1))
1528           htrc("Binding col=%u type=%d buf=%p len=%d slen=%p\n",
1529                   k, tp, buffer, len, colp->GetStrLen());
1530 
1531         rc = SQLBindCol(hstmt, k, tp, buffer, len, colp->GetStrLen());
1532 
1533         if (!Check(rc))
1534           ThrowDBX(rc, "SQLBindCol", hstmt);
1535 
1536       } // endif colp
1537 
1538   } catch(DBX *x) {
1539     if (trace(1))
1540       for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
1541         htrc(x->m_ErrMsg[i]);
1542 
1543     sprintf(m_G->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
1544 
1545     if (b)
1546       SQLCancel(hstmt);
1547 
1548     rc = SQLFreeStmt(hstmt, SQL_DROP);
1549     m_hstmt = NULL;
1550     return -1;
1551   } // end try/catch
1552 
1553   m_hstmt = hstmt;
1554   return (int)m_RowsetSize;   // May have been reset in OnSetOptions
1555   } // end of ExecDirectSQL
1556 
1557 /***********************************************************************/
1558 /*  Get the number of lines of the result set.                         */
1559 /***********************************************************************/
GetResultSize(char * sql,ODBCCOL * colp)1560 int ODBConn::GetResultSize(char *sql, ODBCCOL *colp)
1561   {
1562   int    n = 0;
1563   RETCODE rc;
1564 
1565   if (ExecDirectSQL(sql, colp) < 0)
1566     return -1;
1567 
1568   try {
1569     for (n = 0; ; n++) {
1570       do {
1571         rc = SQLFetch(m_hstmt);
1572         } while (rc == SQL_STILL_EXECUTING);
1573 
1574       if (!Check(rc))
1575         ThrowDBX(rc, "SQLFetch", m_hstmt);
1576 
1577       if (rc == SQL_NO_DATA_FOUND)
1578         break;
1579 
1580       } // endfor n
1581 
1582   } catch(DBX *x) {
1583     strcpy(m_G->Message, x->GetErrorMessage(0));
1584 
1585     if (trace(1))
1586       for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
1587         htrc(x->m_ErrMsg[i]);
1588 
1589     SQLCancel(m_hstmt);
1590     n = -2;
1591   } // end try/catch
1592 
1593   rc = SQLFreeStmt(m_hstmt, SQL_DROP);
1594   m_hstmt = NULL;
1595 
1596   if (n != 1)
1597     return -3;
1598   else
1599     return colp->GetIntValue();
1600 
1601   } // end of GetResultSize
1602 
1603 /***********************************************************************/
1604 /*  Fetch next row.                                                    */
1605 /***********************************************************************/
Fetch(int pos)1606 int ODBConn::Fetch(int pos)
1607   {
1608   ASSERT(m_hstmt);
1609   int      irc;
1610   SQLULEN  crow;
1611   RETCODE  rc;
1612   PGLOBAL& g = m_G;
1613 
1614   try {
1615 //  do {
1616     if (pos) {
1617       rc = SQLExtendedFetch(m_hstmt, SQL_FETCH_ABSOLUTE, pos, &crow, NULL);
1618     } else if (m_RowsetSize) {
1619       rc = SQLExtendedFetch(m_hstmt, SQL_FETCH_NEXT, 1, &crow, NULL);
1620     } else {
1621       rc = SQLFetch(m_hstmt);
1622       crow = 1;
1623     } // endif m_RowsetSize
1624 //    } while (rc == SQL_STILL_EXECUTING);
1625 
1626     if (trace(2))
1627       htrc("Fetch: hstmt=%p RowseSize=%d rc=%d\n",
1628                      m_hstmt, m_RowsetSize, rc);
1629 
1630     if (!Check(rc))
1631       ThrowDBX(rc, "Fetching", m_hstmt);
1632 
1633     if (rc == SQL_NO_DATA_FOUND) {
1634       m_Full = (m_Fetch == 1);
1635       irc = 0;
1636     } else
1637       irc = (int)crow;
1638 
1639     m_Fetch++;
1640     m_Rows += irc;
1641   } catch(DBX *x) {
1642     if (trace(1))
1643       for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
1644         htrc(x->m_ErrMsg[i]);
1645 
1646     sprintf(g->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
1647     irc = -1;
1648   } // end try/catch
1649 
1650   return irc;
1651   } // end of Fetch
1652 
1653 /***********************************************************************/
1654 /*  Prepare an SQL statement for insert.                               */
1655 /***********************************************************************/
PrepareSQL(char * sql)1656 int ODBConn::PrepareSQL(char *sql)
1657   {
1658   PGLOBAL& g = m_G;
1659   bool     b;
1660   UINT     txn = 0;
1661   SWORD    nparm;
1662   RETCODE  rc;
1663   HSTMT    hstmt;
1664 
1665   if (m_Tdb->GetMode() != MODE_READ) {
1666     // Does the data source support transactions
1667     rc = SQLGetInfo(m_hdbc, SQL_TXN_CAPABLE, &txn, 0, NULL);
1668 
1669     if (Check(rc) && txn != SQL_TC_NONE) try {
1670       rc = SQLSetConnectAttr(m_hdbc, SQL_ATTR_AUTOCOMMIT,
1671                                      SQL_AUTOCOMMIT_OFF, SQL_IS_UINTEGER);
1672 
1673       if (!Check(rc))
1674         ThrowDBX(SQL_INVALID_HANDLE, "SQLSetConnectAttr");
1675 
1676       m_Transact = true;
1677     } catch(DBX *x) {
1678       if (trace(1))
1679         for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
1680           htrc(x->m_ErrMsg[i]);
1681 
1682     sprintf(g->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
1683     } // end try/catch
1684 
1685     } // endif Mode
1686 
1687   try {
1688     b = false;
1689 
1690     if (m_hstmt) {
1691       SQLFreeStmt(m_hstmt, SQL_CLOSE);
1692 
1693       hstmt = m_hstmt;
1694       m_hstmt = NULL;
1695 
1696       if (m_Tdb->GetAmType() != TYPE_AM_XDBC)
1697         ThrowDBX(MSG(SEQUENCE_ERROR));
1698 
1699     } // endif m_hstmt
1700 
1701     rc = SQLAllocStmt(m_hdbc, &hstmt);
1702 
1703     if (!Check(rc))
1704       ThrowDBX(SQL_INVALID_HANDLE, "SQLAllocStmt");
1705 
1706     OnSetOptions(hstmt);
1707     b = true;
1708 
1709     if (trace(1))
1710       htrc("Prepare hstmt=%p %.64s\n", hstmt, sql);
1711 
1712     do {
1713       rc = SQLPrepare(hstmt, (PUCHAR)sql, SQL_NTS);
1714       } while (rc == SQL_STILL_EXECUTING);
1715 
1716     if (!Check(rc))
1717       ThrowDBX(rc, "SQLPrepare", hstmt);
1718 
1719     do {
1720       rc = SQLNumParams(hstmt, &nparm);
1721       } while (rc == SQL_STILL_EXECUTING);
1722 
1723   } catch(DBX *x) {
1724     if (trace(1))
1725       for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
1726         htrc(x->m_ErrMsg[i]);
1727 
1728     sprintf(g->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
1729 
1730     if (b)
1731       SQLCancel(hstmt);
1732 
1733     rc = SQLFreeStmt(hstmt, SQL_DROP);
1734     m_hstmt = NULL;
1735 
1736     if (m_Transact) {
1737       rc = SQLEndTran(SQL_HANDLE_DBC, m_hdbc, SQL_ROLLBACK);
1738       m_Transact = false;
1739       } // endif m_Transact
1740 
1741     return -1;
1742   } // end try/catch
1743 
1744   m_hstmt = hstmt;
1745   return (int)nparm;
1746   } // end of PrepareSQL
1747 
1748 /***********************************************************************/
1749 /*  Execute a prepared statement.                                      */
1750 /***********************************************************************/
ExecuteSQL(void)1751 int ODBConn::ExecuteSQL(void)
1752   {
1753   PGLOBAL& g = m_G;
1754   SWORD    ncol = 0;
1755   RETCODE  rc;
1756   SQLLEN   afrw = -1;
1757 
1758   try {
1759     do {
1760       rc = SQLExecute(m_hstmt);
1761       } while (rc == SQL_STILL_EXECUTING);
1762 
1763     if (!Check(rc))
1764       ThrowDBX(rc, "SQLExecute", m_hstmt);
1765 
1766     if (!Check(rc = SQLNumResultCols(m_hstmt, &ncol)))
1767       ThrowDBX(rc, "SQLNumResultCols", m_hstmt);
1768 
1769     if (ncol) {
1770       // This should never happen while inserting
1771       strcpy(g->Message, "Logical error while inserting");
1772     } else {
1773       // Insert, Update or Delete statement
1774       if (!Check(rc = SQLRowCount(m_hstmt, &afrw)))
1775         ThrowDBX(rc, "SQLRowCount", m_hstmt);
1776 
1777     } // endif ncol
1778 
1779   } catch(DBX *x) {
1780     sprintf(m_G->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
1781     SQLCancel(m_hstmt);
1782     rc = SQLFreeStmt(m_hstmt, SQL_DROP);
1783     m_hstmt = NULL;
1784 
1785     if (m_Transact) {
1786       rc = SQLEndTran(SQL_HANDLE_DBC, m_hdbc, SQL_ROLLBACK);
1787       m_Transact = false;
1788       } // endif m_Transact
1789 
1790     afrw = -1;
1791   } // end try/catch
1792 
1793   return (int)afrw;
1794   } // end of ExecuteSQL
1795 
1796 /***********************************************************************/
1797 /*  Bind a parameter for inserting.                                    */
1798 /***********************************************************************/
BindParam(ODBCCOL * colp)1799 bool ODBConn::BindParam(ODBCCOL *colp)
1800   {
1801   void        *buf;
1802   int          buftype = colp->GetResultType();
1803   SQLUSMALLINT n = colp->GetRank();
1804 	SQLSMALLINT  ct, sqlt, dec, nul __attribute__((unused));
1805   SQLULEN      colsize;
1806   SQLLEN       len;
1807   SQLLEN      *strlen = colp->GetStrLen();
1808   SQLRETURN    rc;
1809 
1810 #if 0
1811   try {
1812 		// This function is often not or badly implemented by data sources
1813     rc = SQLDescribeParam(m_hstmt, n, &sqlt, &colsize, &dec, &nul);
1814 
1815     if (!Check(rc))
1816       ThrowDBX(rc, "SQLDescribeParam", m_hstmt);
1817 
1818   } catch(DBX *x) {
1819     sprintf(m_G->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
1820 #endif // 0
1821     colsize = colp->GetPrecision();
1822     sqlt = GetSQLType(buftype);
1823 		dec = IsTypeNum(buftype) ? colp->GetScale() : 0;
1824 		nul = colp->IsNullable() ? SQL_NULLABLE : SQL_NO_NULLS;
1825 //} // end try/catch
1826 
1827   buf = colp->GetBuffer(0);
1828   len = IsTypeChar(buftype) ? colp->GetBuflen() : 0;
1829   ct = GetSQLCType(buftype);
1830   *strlen = IsTypeChar(buftype) ? SQL_NTS : 0;
1831 
1832   try {
1833     rc = SQLBindParameter(m_hstmt, n, SQL_PARAM_INPUT, ct, sqlt,
1834                           colsize, dec, buf, len, strlen);
1835 
1836     if (!Check(rc))
1837       ThrowDBX(rc, "SQLBindParameter", m_hstmt);
1838 
1839   } catch(DBX *x) {
1840     strcpy(m_G->Message, x->GetErrorMessage(0));
1841     SQLCancel(m_hstmt);
1842     rc = SQLFreeStmt(m_hstmt, SQL_DROP);
1843     m_hstmt = NULL;
1844     return true;
1845   } // end try/catch
1846 
1847   return false;
1848   } // end of BindParam
1849 
1850 /***********************************************************************/
1851 /*  Execute an SQL command.                                            */
1852 /***********************************************************************/
1853 bool ODBConn::ExecSQLcommand(char *sql)
1854   {
1855   char     cmd[16];
1856   bool     b, rcd = false;
1857   UINT     txn = 0;
1858   PGLOBAL& g = m_G;
1859   SWORD    ncol = 0;
1860   SQLLEN   afrw;
1861   RETCODE  rc;
1862   HSTMT    hstmt;
1863 
1864   try {
1865     b = FALSE;
1866 
1867     // Check whether we should use transaction
1868     if (sscanf(sql, " %15s ", cmd) == 1) {
1869       if (!stricmp(cmd, "INSERT") || !stricmp(cmd, "UPDATE") ||
1870           !stricmp(cmd, "DELETE") || !stricmp(cmd, "REPLACE")) {
1871         // Does the data source support transactions
1872         rc = SQLGetInfo(m_hdbc, SQL_TXN_CAPABLE, &txn, 0, NULL);
1873 
1874         if (Check(rc) && txn != SQL_TC_NONE) {
1875           rc = SQLSetConnectAttr(m_hdbc, SQL_ATTR_AUTOCOMMIT,
1876                                  SQL_AUTOCOMMIT_OFF, SQL_IS_UINTEGER);
1877 
1878           if (!Check(rc))
1879             ThrowDBX(SQL_INVALID_HANDLE, "SQLSetConnectAttr");
1880 
1881           m_Transact = TRUE;
1882           } // endif txn
1883 
1884         } // endif cmd
1885 
1886       } // endif sql
1887 
1888     // Allocate the statement handle
1889     rc = SQLAllocStmt(m_hdbc, &hstmt);
1890 
1891     if (!Check(rc))
1892       ThrowDBX(SQL_INVALID_HANDLE, "SQLAllocStmt");
1893 
1894     OnSetOptions(hstmt);
1895     b = true;
1896 
1897     if (trace(1))
1898       htrc("ExecSQLcommand hstmt=%p %.64s\n", hstmt, sql);
1899 
1900     // Proceed with command execution
1901     do {
1902       rc = SQLExecDirect(hstmt, (PUCHAR)sql, SQL_NTS);
1903       } while (rc == SQL_STILL_EXECUTING);
1904 
1905     if (!Check(rc))
1906       ThrowDBX(rc, "SQLExecDirect", hstmt);
1907 
1908     // Check whether this is a query returning a result set
1909     if (!Check(rc = SQLNumResultCols(hstmt, &ncol)))
1910       ThrowDBX(rc, "SQLNumResultCols", hstmt);
1911 
1912     if (!ncol) {
1913       if (!Check(SQLRowCount(hstmt, &afrw)))
1914         ThrowDBX(rc, "SQLRowCount", hstmt);
1915 
1916       m_Tdb->AftRows = (int)afrw;
1917       strcpy(g->Message, "Affected rows");
1918     } else {
1919       m_Tdb->AftRows = (int)ncol;
1920       strcpy(g->Message, "Result set column number");
1921     } // endif ncol
1922 
1923   } catch(DBX *x) {
1924 		if (trace(1))
1925 			for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
1926 				htrc(x->m_ErrMsg[i]);
1927 
1928     sprintf(g->Message, "Remote %s: %s", x->m_Msg, x->GetErrorMessage(0));
1929 
1930     if (b)
1931       SQLCancel(hstmt);
1932 
1933     m_Tdb->AftRows = -1;
1934     rcd = true;
1935   } // end try/catch
1936 
1937   if (!Check(rc = SQLFreeStmt(hstmt, SQL_CLOSE)))
1938     sprintf(g->Message, "SQLFreeStmt: rc=%d", rc);
1939 
1940   if (m_Transact) {
1941     // Terminate the transaction
1942     if (!Check(rc = SQLEndTran(SQL_HANDLE_DBC, m_hdbc,
1943                        (rcd) ? SQL_ROLLBACK : SQL_COMMIT)))
1944       sprintf(g->Message, "SQLEndTran: rc=%d", rc);
1945 
1946     if (!Check(rc = SQLSetConnectAttr(m_hdbc, SQL_ATTR_AUTOCOMMIT,
1947                (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_UINTEGER)))
1948       sprintf(g->Message, "SQLSetConnectAttr: rc=%d", rc);
1949 
1950     m_Transact = false;
1951     } // endif m_Transact
1952 
1953   return rcd;
1954   } // end of ExecSQLcommand
1955 
1956 /**************************************************************************/
1957 /*  GetMetaData: constructs the result blocks containing the              */
1958 /*  description of all the columns of an SQL command.                     */
1959 /**************************************************************************/
1960 PQRYRES ODBConn::GetMetaData(PGLOBAL g, PCSZ dsn, PCSZ src)
1961   {
1962   static int  buftyp[] = {TYPE_STRING, TYPE_SHORT, TYPE_INT,
1963                           TYPE_SHORT,  TYPE_SHORT};
1964   static XFLD fldtyp[] = {FLD_NAME,  FLD_TYPE, FLD_PREC,
1965                           FLD_SCALE, FLD_NULL};
1966   static unsigned int length[] = {0, 6, 10, 6, 6};
1967   unsigned char cn[60];
1968   int     qcol = 5;
1969   short   nl, type, prec, nul, cns = (short)sizeof(cn);
1970   PQRYRES qrp = NULL;
1971   PCOLRES crp;
1972   USHORT  i;
1973   SQLULEN n;
1974   SWORD   ncol;
1975   RETCODE rc;
1976   HSTMT   hstmt;
1977 
1978   try {
1979     rc = SQLAllocStmt(m_hdbc, &hstmt);
1980 
1981     if (!Check(rc))
1982       ThrowDBX(SQL_INVALID_HANDLE, "SQLAllocStmt");
1983 
1984     OnSetOptions(hstmt);
1985 
1986     do {
1987       rc = SQLPrepare(hstmt, (PUCHAR)src, SQL_NTS);
1988 //    rc = SQLExecDirect(hstmt, (PUCHAR)src, SQL_NTS);
1989       } while (rc == SQL_STILL_EXECUTING);
1990 
1991     if (!Check(rc))
1992       ThrowDBX(rc, "SQLExecDirect", hstmt);
1993 
1994     do {
1995       rc = SQLNumResultCols(hstmt, &ncol);
1996       } while (rc == SQL_STILL_EXECUTING);
1997 
1998     if (!Check(rc))
1999       ThrowDBX(rc, "SQLNumResultCols", hstmt);
2000 
2001     if (ncol) for (i = 1; i <= ncol; i++) {
2002       do {
2003         rc = SQLDescribeCol(hstmt, i, NULL, 0, &nl, NULL, NULL, NULL, NULL);
2004         } while (rc == SQL_STILL_EXECUTING);
2005 
2006       if (!Check(rc))
2007         ThrowDBX(rc, "SQLDescribeCol", hstmt);
2008 
2009       length[0] = MY_MAX(length[0], (UINT)nl);
2010       } // endfor i
2011 
2012   } catch(DBX *x) {
2013     sprintf(g->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
2014     goto err;
2015   } // end try/catch
2016 
2017   if (!ncol) {
2018     strcpy(g->Message, "Invalid Srcdef");
2019     goto err;
2020     } // endif ncol
2021 
2022   /************************************************************************/
2023   /*  Allocate the structures used to refer to the result set.            */
2024   /************************************************************************/
2025   if (!(qrp = PlgAllocResult(g, qcol, ncol, IDS_COLUMNS + 3,
2026                              buftyp, fldtyp, length, false, true)))
2027     return NULL;
2028 
2029   // Some columns must be renamed
2030   for (i = 0, crp = qrp->Colresp; crp; crp = crp->Next)
2031     switch (++i) {
2032       case 3: crp->Name = "Precision"; break;
2033       case 4: crp->Name = "Scale";     break;
2034       case 5: crp->Name = "Nullable";  break;
2035       } // endswitch i
2036 
2037   /************************************************************************/
2038   /*  Now get the results into blocks.                                    */
2039   /************************************************************************/
2040   try {
2041     for (i = 0; i < ncol; i++) {
2042       do {
2043         rc = SQLDescribeCol(hstmt, i+1, cn, cns, &nl, &type, &n, &prec, &nul);
2044         } while (rc == SQL_STILL_EXECUTING);
2045 
2046       if (!Check(rc))
2047         ThrowDBX(rc, "SQLDescribeCol", hstmt);
2048       else
2049         qrp->Nblin++;
2050 
2051       crp = qrp->Colresp;                    // Column_Name
2052       crp->Kdata->SetValue((char*)cn, i);
2053       crp = crp->Next;                       // Data_Type
2054       crp->Kdata->SetValue(type, i);
2055       crp = crp->Next;                       // Precision (length)
2056       crp->Kdata->SetValue((int)n, i);
2057       crp = crp->Next;                       // Scale
2058       crp->Kdata->SetValue(prec, i);
2059       crp = crp->Next;                       // Nullable
2060       crp->Kdata->SetValue(nul, i);
2061       } // endfor i
2062 
2063   } catch(DBX *x) {
2064     sprintf(g->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
2065     qrp = NULL;
2066   } // end try/catch
2067 
2068   /* Cleanup */
2069  err:
2070   SQLCancel(hstmt);
2071   rc = SQLFreeStmt(hstmt, SQL_DROP);
2072   Close();
2073 
2074   /************************************************************************/
2075   /*  Return the result pointer for use by GetData routines.              */
2076   /************************************************************************/
2077   return qrp;
2078   } // end of GetMetaData
2079 
2080 /***********************************************************************/
2081 /*  Get the list of Data Sources and set it in qrp.                    */
2082 /***********************************************************************/
2083 bool ODBConn::GetDataSources(PQRYRES qrp)
2084   {
2085   bool    rv = false;
2086   UCHAR  *dsn, *des;
2087   UWORD   dir = SQL_FETCH_FIRST;
2088   SWORD   n1, n2, p1, p2;
2089   PCOLRES crp1 = qrp->Colresp, crp2 = qrp->Colresp->Next;
2090   RETCODE rc;
2091 
2092   n1 = crp1->Clen;
2093   n2 = crp2->Clen;
2094 
2095   try {
2096     rc = SQLAllocEnv(&m_henv);
2097 
2098     if (!Check(rc))
2099       ThrowDBX(rc, "SQLAllocEnv"); // Fatal
2100 
2101     for (int i = 0; i < qrp->Maxres; i++) {
2102       dsn = (UCHAR*)crp1->Kdata->GetValPtr(i);
2103       des = (UCHAR*)crp2->Kdata->GetValPtr(i);
2104       rc = SQLDataSources(m_henv, dir, dsn, n1, &p1, des, n2, &p2);
2105 
2106       if (rc == SQL_NO_DATA_FOUND)
2107         break;
2108       else if (!Check(rc))
2109         ThrowDBX(rc, "SQLDataSources");
2110 
2111       qrp->Nblin++;
2112       dir = SQL_FETCH_NEXT;
2113       } // endfor i
2114 
2115   } catch(DBX *x) {
2116     sprintf(m_G->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
2117     rv = true;
2118   } // end try/catch
2119 
2120   Close();
2121   return rv;
2122   } // end of GetDataSources
2123 
2124 /***********************************************************************/
2125 /*  Get the list of Drivers and set it in qrp.                         */
2126 /***********************************************************************/
2127 bool ODBConn::GetDrivers(PQRYRES qrp)
2128   {
2129   int     i, n;
2130   bool    rv = false;
2131   UCHAR  *des, *att;
2132   UWORD   dir = SQL_FETCH_FIRST;
2133   SWORD   n1, n2, p1, p2;
2134   PCOLRES crp1 = qrp->Colresp, crp2 = qrp->Colresp->Next;
2135   RETCODE rc;
2136 
2137   n1 = crp1->Clen;
2138   n2 = crp2->Clen;
2139 
2140   try {
2141     rc = SQLAllocEnv(&m_henv);
2142 
2143     if (!Check(rc))
2144       ThrowDBX(rc, "SQLAllocEnv"); // Fatal
2145 
2146     for (n = 0; n < qrp->Maxres; n++) {
2147       des = (UCHAR*)crp1->Kdata->GetValPtr(n);
2148       att = (UCHAR*)crp2->Kdata->GetValPtr(n);
2149       rc = SQLDrivers(m_henv, dir, des, n1, &p1, att, n2, &p2);
2150 
2151       if (rc == SQL_NO_DATA_FOUND)
2152         break;
2153       else if (!Check(rc))
2154         ThrowDBX(rc, "SQLDrivers");
2155 
2156 
2157       // The attributes being separated by '\0', set them to ';'
2158       for (i = 0; i < p2; i++)
2159         if (!att[i])
2160           att[i] = ';';
2161 
2162       qrp->Nblin++;
2163       dir = SQL_FETCH_NEXT;
2164       } // endfor n
2165 
2166   } catch(DBX *x) {
2167     sprintf(m_G->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
2168     rv = true;
2169   } // end try/catch
2170 
2171   Close();
2172   return rv;
2173   } // end of GetDrivers
2174 
2175 /***********************************************************************/
2176 /*  A helper class to split an optionally qualified table name into    */
2177 /*  components.                                                        */
2178 /*  These formats are understood:                                      */
2179 /*    "CatalogName.SchemaName.TableName"                               */
2180 /*    "SchemaName.TableName"                                           */
2181 /*    "TableName"                                                      */
2182 /***********************************************************************/
2183 class SQLQualifiedName
2184 {
2185   static const uint max_parts= 3;          // Catalog.Schema.Table
2186   MYSQL_LEX_STRING m_part[max_parts];
2187   char m_buf[512];
2188 
2189   void lex_string_set(MYSQL_LEX_STRING *S, char *str, size_t length)
2190   {
2191     S->str= str;
2192     S->length= length;
2193   } // end of lex_string_set
2194 
2195   void lex_string_shorten_down(MYSQL_LEX_STRING *S, size_t offs)
2196   {
2197     DBUG_ASSERT(offs <= S->length);
2198     S->str+= offs;
2199     S->length-= offs;
2200   } // end of lex_string_shorten_down
2201 
2202   /*********************************************************************/
2203   /*  Find the rightmost '.' delimiter and return the length           */
2204   /*  of the qualifier, including the rightmost '.' delimier.          */
2205   /*  For example, for the string {"a.b.c",5} it will return 4,        */
2206   /*  which is the length of the qualifier "a.b."                      */
2207   /*********************************************************************/
2208   size_t lex_string_find_qualifier(MYSQL_LEX_STRING *S)
2209   {
2210     size_t i;
2211     for (i= S->length; i > 0; i--)
2212     {
2213       if (S->str[i - 1] == '.')
2214       {
2215         S->str[i - 1]= '\0';
2216         return i;
2217       }
2218     }
2219     return 0;
2220   } // end of lex_string_find_qualifier
2221 
2222 public:
2223   /*********************************************************************/
2224   /*  Initialize to the given optionally qualified name.               */
2225   /*  NULL pointer in "name" is supported.                             */
2226   /*  name qualifier has precedence over schema.                       */
2227   /*********************************************************************/
2228   SQLQualifiedName(CATPARM *cap)
2229   {
2230     const char *name = (const char *)cap->Tab;
2231     char       *db = (char *)cap->DB;
2232     size_t      len, i;
2233 
2234     // Initialize the parts
2235     for (i = 0 ; i < max_parts; i++)
2236       lex_string_set(&m_part[i], NULL, 0);
2237 
2238     if (name) {
2239       // Initialize the first (rightmost) part
2240       lex_string_set(&m_part[0], m_buf,
2241                      strmake(m_buf, name, sizeof(m_buf) - 1) - m_buf);
2242 
2243       // Initialize the other parts, if exist.
2244       for (i= 1; i < max_parts; i++) {
2245         if (!(len= lex_string_find_qualifier(&m_part[i - 1])))
2246           break;
2247 
2248         lex_string_set(&m_part[i], m_part[i - 1].str, len - 1);
2249         lex_string_shorten_down(&m_part[i - 1], len);
2250         } // endfor i
2251 
2252       } // endif name
2253 
2254     // If it was not specified, set schema as the passed db name
2255     if (db && !m_part[1].length)
2256       lex_string_set(&m_part[1], db, strlen(db));
2257 
2258   } // end of SQLQualifiedName
2259 
2260   SQLCHAR *ptr(uint i)
2261   {
2262     DBUG_ASSERT(i < max_parts);
2263     return (SQLCHAR *) (m_part[i].length ? m_part[i].str : NULL);
2264   } // end of ptr
2265 
2266   SQLSMALLINT length(uint i)
2267   {
2268     DBUG_ASSERT(i < max_parts);
2269     return (SQLSMALLINT)m_part[i].length;
2270   } // end of length
2271 
2272 }; // end of class SQLQualifiedName
2273 
2274 /***********************************************************************/
2275 /*  Allocate recset and call SQLTables, SQLColumns or SQLPrimaryKeys.  */
2276 /***********************************************************************/
2277 int ODBConn::GetCatInfo(CATPARM *cap)
2278   {
2279   PGLOBAL& g = m_G;
2280   void    *buffer;
2281   int      i, irc;
2282   bool     b;
2283   PCSZ     fnc = "Unknown";
2284   UWORD    n = 0;
2285   SWORD    ncol, len, tp;
2286   SQLULEN  crow = 0;
2287   PQRYRES  qrp = cap->Qrp;
2288   PCOLRES  crp;
2289   RETCODE  rc = 0;
2290   HSTMT    hstmt = NULL;
2291   SQLLEN  *vl, *vlen = NULL;
2292   PVAL    *pval = NULL;
2293   char*   *pbuf = NULL;
2294 
2295   try {
2296     b = false;
2297 
2298     if (!m_hstmt) {
2299       rc = SQLAllocStmt(m_hdbc, &hstmt);
2300 
2301       if (!Check(rc))
2302         ThrowDBX(SQL_INVALID_HANDLE, "SQLAllocStmt");
2303 
2304     } else
2305       ThrowDBX(MSG(SEQUENCE_ERROR));
2306 
2307     b = true;
2308 
2309     // Currently m_Catver should be always 0 here
2310     assert(!m_Catver);     // This may be temporary
2311 
2312     if (qrp->Maxres > 0)
2313       m_RowsetSize = 1;
2314     else
2315       ThrowDBX("0-sized result");
2316 
2317     SQLQualifiedName name(cap);
2318 
2319     // Now do call the proper ODBC API
2320     switch (cap->Id) {
2321       case CAT_TAB:
2322         fnc = "SQLTables";
2323         rc = SQLTables(hstmt, name.ptr(2), name.length(2),
2324                               name.ptr(1), name.length(1),
2325                               name.ptr(0), name.length(0),
2326 					                    (SQLCHAR *)cap->Pat,
2327 					                    cap->Pat ? SQL_NTS : 0);
2328         break;
2329       case CAT_COL:
2330         fnc = "SQLColumns";
2331         rc = SQLColumns(hstmt, name.ptr(2), name.length(2),
2332                                name.ptr(1), name.length(1),
2333                                name.ptr(0), name.length(0),
2334 															 (SQLCHAR *)cap->Pat,
2335 					                     cap->Pat ? SQL_NTS : 0);
2336         break;
2337       case CAT_KEY:
2338         fnc = "SQLPrimaryKeys";
2339         rc = SQLPrimaryKeys(hstmt, name.ptr(2), name.length(2),
2340                                    name.ptr(1), name.length(1),
2341                                    name.ptr(0), name.length(0));
2342         break;
2343       case CAT_STAT:
2344         fnc = "SQLStatistics";
2345         rc = SQLStatistics(hstmt, name.ptr(2), name.length(2),
2346                                   name.ptr(1), name.length(1),
2347                                   name.ptr(0), name.length(0),
2348                                   cap->Unique, cap->Accuracy);
2349         break;
2350       case CAT_SPC:
2351         ThrowDBX("SQLSpecialColumns not available yet");
2352       default:
2353         ThrowDBX("Invalid SQL function id");
2354       } // endswitch infotype
2355 
2356     if (!Check(rc))
2357       ThrowDBX(rc, fnc, hstmt);
2358 
2359 		// Some data source do not implement SQLNumResultCols
2360     if (Check(SQLNumResultCols(hstmt, &ncol)))
2361       // n because we no more ignore the first column
2362       if ((n = (UWORD)qrp->Nbcol) > (UWORD)ncol)
2363         ThrowDBX(MSG(COL_NUM_MISM));
2364 
2365     // Unconditional to handle STRBLK's
2366     pval = (PVAL *)PlugSubAlloc(g, NULL, n * sizeof(PVAL));
2367     vlen = (SQLLEN *)PlugSubAlloc(g, NULL, n * sizeof(SQLLEN));
2368     pbuf = (char**)PlugSubAlloc(g, NULL, n * sizeof(char*));
2369 
2370     // Now bind the column buffers
2371     for (n = 0, crp = qrp->Colresp; crp; crp = crp->Next) {
2372       if ((tp = GetSQLCType(crp->Type)) == SQL_TYPE_NULL) {
2373         sprintf(g->Message, MSG(INV_COLUMN_TYPE), crp->Type, crp->Name);
2374         ThrowDBX(g->Message);
2375         } // endif tp
2376 
2377       if (!(len = GetTypeSize(crp->Type, crp->Length))) {
2378         len = 255;           // for STRBLK's
2379         ((STRBLK*)crp->Kdata)->SetSorted(true);
2380         } // endif len
2381 
2382       pval[n] = AllocateValue(g, crp->Type, len);
2383 			pval[n]->SetNullable(true);
2384 
2385       if (crp->Type == TYPE_STRING) {
2386         pbuf[n] = (char*)PlugSubAlloc(g, NULL, len);
2387         buffer = pbuf[n];
2388       } else
2389         buffer = pval[n]->GetTo_Val();
2390 
2391       vl = vlen + n;
2392 
2393       // n + 1 because column numbers begin with 1
2394       rc = SQLBindCol(hstmt, n + 1, tp, buffer, len, vl);
2395 
2396       if (!Check(rc))
2397         ThrowDBX(rc, "SQLBindCol", hstmt);
2398 
2399       n++;
2400       } // endfor crp
2401 
2402     fnc = "SQLFetch";
2403 
2404     // Now fetch the result
2405     // Extended fetch cannot be used because of STRBLK's
2406     for (i = 0; i < qrp->Maxres; i++) {
2407       if ((rc = SQLFetch(hstmt)) == SQL_NO_DATA_FOUND)
2408         break;
2409       else if (rc != SQL_SUCCESS) {
2410         if (trace(2) || (trace(1) && rc != SQL_SUCCESS_WITH_INFO)) {
2411           UCHAR   msg[SQL_MAX_MESSAGE_LENGTH + 1];
2412           UCHAR   state[SQL_SQLSTATE_SIZE + 1];
2413           RETCODE erc;
2414           SDWORD  native;
2415 
2416           htrc("SQLFetch: row %d rc=%d\n", i+1, rc);
2417           erc = SQLError(m_henv, m_hdbc, hstmt, state, &native, msg,
2418                          SQL_MAX_MESSAGE_LENGTH - 1, &len);
2419 
2420           if (rc != SQL_INVALID_HANDLE)
2421             // Skip non-errors
2422             for (n = 0; n < MAX_NUM_OF_MSG
2423                  && (erc == SQL_SUCCESS || erc == SQL_SUCCESS_WITH_INFO)
2424                  && strcmp((char*)state, "00000"); n++) {
2425               htrc("%s: %s, Native=%d\n", state, msg, native);
2426               erc = SQLError(m_henv, m_hdbc, hstmt, state, &native,
2427                              msg, SQL_MAX_MESSAGE_LENGTH - 1, &len);
2428               } // endfor n
2429 
2430           } // endif trace
2431 
2432         if (rc != SQL_SUCCESS_WITH_INFO)
2433           qrp->BadLines++;
2434 
2435         } // endif rc
2436 
2437       for (n = 0, crp = qrp->Colresp; crp; n++, crp = crp->Next) {
2438 				if (vlen[n] == SQL_NO_TOTAL)
2439 					ThrowDBX("Unexpected SQL_NO_TOTAL returned from SQLFetch");
2440 				else if (vlen[n] == SQL_NULL_DATA)
2441           pval[n]->SetNull(true);
2442         else if (crp->Type == TYPE_STRING/* && vlen[n] != SQL_NULL_DATA*/)
2443           pval[n]->SetValue_char(pbuf[n], (int)vlen[n]);
2444         else
2445           pval[n]->SetNull(false);
2446 
2447         crp->Kdata->SetValue(pval[n], i);
2448         cap->Vlen[n][i] = vlen[n];
2449         } // endfor crp
2450 
2451       } // endfor i
2452 
2453 #if 0
2454     if ((crow = i) && (rc == SQL_NO_DATA || rc == SQL_SUCCESS_WITH_INFO))
2455       rc = SQL_SUCCESS;
2456 
2457     if (rc == SQL_NO_DATA_FOUND) {
2458       if (cap->Pat)
2459         sprintf(g->Message, MSG(NO_TABCOL_DATA), cap->Tab, cap->Pat);
2460       else
2461         sprintf(g->Message, MSG(NO_TAB_DATA), cap->Tab);
2462 
2463       ThrowDBX(g->Message);
2464     } else if (rc == SQL_SUCCESS) {
2465       if ((rc = SQLFetch(hstmt)) != SQL_NO_DATA_FOUND)
2466         qrp->Truncated = true;
2467 
2468     } else
2469       ThrowDBX(rc, fnc, hstmt);
2470 #endif // 0
2471 
2472     if (!rc || rc == SQL_NO_DATA || rc == SQL_SUCCESS_WITH_INFO) {
2473       if ((rc = SQLFetch(hstmt)) != SQL_NO_DATA_FOUND)
2474         qrp->Truncated = true;
2475 
2476       crow = i;
2477     } else
2478       ThrowDBX(rc, fnc, hstmt);
2479 
2480     irc = (int)crow;
2481   } catch(DBX *x) {
2482     if (trace(1))
2483       for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
2484         htrc(x->m_ErrMsg[i]);
2485 
2486     sprintf(g->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
2487     irc = -1;
2488   } // end try/catch
2489 
2490   if (b)
2491     SQLCancel(hstmt);
2492 
2493   // All this (hstmt vs> m_hstmt) to be revisited
2494   if (hstmt)
2495     rc = SQLFreeStmt(hstmt, SQL_DROP);
2496 
2497   return irc;
2498   } // end of GetCatInfo
2499 
2500 /***********************************************************************/
2501 /*  Allocate a CONNECT result structure from the ODBC result.          */
2502 /***********************************************************************/
2503 PQRYRES ODBConn::AllocateResult(PGLOBAL g)
2504   {
2505   bool         uns;
2506   PODBCCOL     colp;
2507   PCOLRES     *pcrp, crp;
2508   PQRYRES      qrp;
2509 
2510   if (!m_Rows) {
2511     strcpy(g->Message, "Void result");
2512     return NULL;
2513     } // endif m_Res
2514 
2515   /*********************************************************************/
2516   /*  Allocate the result storage for future retrieval.                */
2517   /*********************************************************************/
2518   qrp = (PQRYRES)PlugSubAlloc(g, NULL, sizeof(QRYRES));
2519   pcrp = &qrp->Colresp;
2520   qrp->Continued = FALSE;
2521   qrp->Truncated = FALSE;
2522   qrp->Info = FALSE;
2523   qrp->Suball = TRUE;
2524   qrp->BadLines = 0;
2525   qrp->Maxsize = m_Rows;
2526   qrp->Maxres = m_Rows;
2527   qrp->Nbcol = 0;
2528   qrp->Nblin = 0;
2529   qrp->Cursor = 0;
2530 
2531   for (colp = (PODBCCOL)m_Tdb->Columns; colp;
2532        colp = (PODBCCOL)colp->GetNext())
2533     if (!colp->IsSpecial()) {
2534       *pcrp = (PCOLRES)PlugSubAlloc(g, NULL, sizeof(COLRES));
2535       crp = *pcrp;
2536       pcrp = &crp->Next;
2537       memset(crp, 0, sizeof(COLRES));
2538       crp->Ncol = ++qrp->Nbcol;
2539       crp->Name = colp->GetName();
2540       crp->Type = colp->GetResultType();
2541       crp->Prec = colp->GetScale();
2542       crp->Length = colp->GetLength();
2543       crp->Clen = colp->GetBuflen();
2544       uns = colp->IsUnsigned();
2545 
2546       if (!(crp->Kdata = AllocValBlock(g, NULL, crp->Type, m_Rows,
2547                                      crp->Clen, 0, FALSE, TRUE, uns))) {
2548         sprintf(g->Message, MSG(INV_RESULT_TYPE),
2549                           GetFormatType(crp->Type));
2550         return NULL;
2551         } // endif Kdata
2552 
2553       if (!colp->IsNullable())
2554         crp->Nulls = NULL;
2555       else {
2556         crp->Nulls = (char*)PlugSubAlloc(g, NULL, m_Rows);
2557         memset(crp->Nulls, ' ', m_Rows);
2558       } // endelse Nullable
2559 
2560       colp->SetCrp(crp);
2561       } // endif colp
2562 
2563   *pcrp = NULL;
2564 //qrp->Nblin = n;
2565   return qrp;
2566   } // end of AllocateResult
2567 
2568 /***********************************************************************/
2569 /*  Restart from beginning of result set                               */
2570 /***********************************************************************/
2571 int ODBConn::Rewind(char *sql, ODBCCOL *tocols)
2572   {
2573   int rc, rbuf = -1;
2574 
2575   if (!m_hstmt)
2576     rbuf = -1;
2577   else if (m_Full)
2578     rbuf = m_Rows;           // No need to "rewind"
2579   else if (m_Scrollable) {
2580     SQLULEN  crow;
2581 
2582     try {
2583       rc = SQLExtendedFetch(m_hstmt, SQL_FETCH_FIRST, 1, &crow, NULL);
2584 
2585       if (!Check(rc))
2586         ThrowDBX(rc, "SQLExtendedFetch", m_hstmt);
2587 
2588       rbuf = (int)crow;
2589     } catch(DBX *x) {
2590       sprintf(m_G->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
2591       rbuf = -1;
2592     } // end try/catch
2593 
2594   } else if (ExecDirectSQL(sql, tocols) >= 0)
2595     rbuf = 0;
2596 
2597   return rbuf;
2598   } // end of Rewind
2599 
2600 /***********************************************************************/
2601 /*  Disconnect connection                                              */
2602 /***********************************************************************/
2603 void ODBConn::Close()
2604   {
2605   RETCODE rc;
2606 
2607   if (m_hstmt) {
2608     // Is required for multiple tables
2609     rc = SQLFreeStmt(m_hstmt, SQL_DROP);
2610     m_hstmt = NULL;
2611     } // endif m_hstmt
2612 
2613   if (m_hdbc != SQL_NULL_HDBC) {
2614     if (m_Transact) {
2615       rc = SQLEndTran(SQL_HANDLE_DBC, m_hdbc, SQL_COMMIT);
2616       m_Transact = false;
2617       } // endif m_Transact
2618 
2619     rc = SQLDisconnect(m_hdbc);
2620 
2621     if (trace(1) && rc != SQL_SUCCESS)
2622       htrc("Error: SQLDisconnect rc=%d\n", rc);
2623 
2624     rc = SQLFreeConnect(m_hdbc);
2625 
2626     if (trace(1) && rc != SQL_SUCCESS)
2627       htrc("Error: SQLFreeConnect rc=%d\n", rc);
2628 
2629     m_hdbc = SQL_NULL_HDBC;
2630     } // endif m_hdbc
2631 
2632   if (m_henv != SQL_NULL_HENV) {
2633     rc = SQLFreeEnv(m_henv);
2634 
2635     if (trace(1) && rc != SQL_SUCCESS)   // Nothing we can do
2636       htrc("Error: SQLFreeEnv failure ignored in Close\n");
2637 
2638     m_henv = SQL_NULL_HENV;
2639     } // endif m_henv
2640 
2641 	if (m_Fp)
2642 		m_Fp->Count = 0;
2643 
2644   } // end of Close
2645