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   PGLOBAL& g = m_G;
1293   PDBUSER dup = PlgGetUser(g);
1294 
1295 //if (Options & noOdbcDialog || dup->Remote)
1296     wConnectOption = SQL_DRIVER_NOPROMPT;
1297 //else if (Options & forceOdbcDialog)
1298 //  wConnectOption = SQL_DRIVER_PROMPT;
1299 
1300   rc = SQLDriverConnect(m_hdbc, hWnd, (PUCHAR)m_Connect,
1301                         SQL_NTS, ConnOut, MAX_CONNECT_LEN,
1302                         &nResult, wConnectOption);
1303 
1304 #if defined(_WIN32)
1305   if (hWndTop)
1306     EnableWindow(hWndTop, true);
1307 #endif   // _WIN32
1308 
1309   // If user hit 'Cancel'
1310   if (rc == SQL_NO_DATA_FOUND) {
1311     Close();
1312 //  Free();
1313     return true;
1314     } // endif rc
1315 
1316   if (!Check(rc))
1317     ThrowDBX(rc, "SQLDriverConnect");
1318 
1319   // Save connect string returned from ODBC
1320   m_Connect = (PSZ)ConnOut;
1321 
1322   // All done
1323   return false;
1324   } // end of DriverConnect
1325 
VerifyConnect()1326 void ODBConn::VerifyConnect()
1327   {
1328 #if defined(NEWMSG) || defined(XMSG)
1329   PGLOBAL& g = m_G;
1330 #endif   // NEWMSG  ||         XMSG
1331   RETCODE  rc;
1332   SWORD    result;
1333   SWORD    conformance;
1334 
1335   rc = SQLGetInfo(m_hdbc, SQL_ODBC_API_CONFORMANCE,
1336                   &conformance, sizeof(conformance), &result);
1337 
1338   if (!Check(rc))
1339     ThrowDBX(rc, "SQLGetInfo");
1340 
1341   if (conformance < SQL_OAC_LEVEL1)
1342     ThrowDBX(MSG(API_CONF_ERROR));
1343 
1344   rc = SQLGetInfo(m_hdbc, SQL_ODBC_SQL_CONFORMANCE,
1345                   &conformance, sizeof(conformance), &result);
1346 
1347   if (!Check(rc))
1348     ThrowDBX(rc, "SQLGetInfo");
1349 
1350   if (conformance < SQL_OSC_MINIMUM)
1351     ThrowDBX(MSG(SQL_CONF_ERROR));
1352 
1353   } // end of VerifyConnect
1354 
GetConnectInfo()1355 void ODBConn::GetConnectInfo()
1356   {
1357   RETCODE rc;
1358   SWORD   nResult;
1359 #if 0                   // Update not implemented yet
1360   UDWORD  DrvPosOp;
1361 
1362   // Reset the database update options
1363   m_UpdateOptions = 0;
1364 
1365   // Check for SQLSetPos support
1366   rc = SQLGetInfo(m_hdbc, SQL_POS_OPERATIONS,
1367                   &DrvPosOp, sizeof(DrvPosOp), &nResult);
1368 
1369   if (Check(rc) &&
1370       (DrvPosOp & SQL_POS_UPDATE) &&
1371       (DrvPosOp & SQL_POS_DELETE) &&
1372       (DrvPosOp & SQL_POS_ADD))
1373      m_UpdateOptions = SQL_SETPOSUPDATES;
1374 
1375   // Check for positioned update SQL support
1376   UDWORD PosStatements;
1377 
1378   rc = SQLGetInfo(m_hdbc, SQL_POSITIONED_STATEMENTS,
1379                         &PosStatements, sizeof(PosStatements),
1380                         &nResult);
1381 
1382   if (Check(rc) &&
1383       (PosStatements & SQL_PS_POSITIONED_DELETE) &&
1384       (PosStatements & SQL_PS_POSITIONED_UPDATE))
1385     m_UpdateOptions |= SQL_POSITIONEDSQL;
1386 
1387   if (m_Updatable) {
1388     // Make sure data source is Updatable
1389     char ReadOnly[10];
1390 
1391     rc = SQLGetInfo(m_hdbc, SQL_DATA_SOURCE_READ_ONLY,
1392                     ReadOnly, sizeof(ReadOnly), &nResult);
1393 
1394     if (Check(rc) && nResult == 1)
1395       m_Updatable = !!strcmp(ReadOnly, "Y");
1396     else
1397       m_Updatable = false;
1398 
1399     if (trace(1))
1400       htrc("Warning: data source is readonly\n");
1401 
1402   } else // Make data source is !Updatable
1403     rc = SQLSetConnectOption(m_hdbc, SQL_ACCESS_MODE,
1404                                      SQL_MODE_READ_ONLY);
1405 #endif   // 0
1406 
1407   // Get the quote char to use when constructing SQL
1408   rc = SQLGetInfo(m_hdbc, SQL_IDENTIFIER_QUOTE_CHAR,
1409                   m_IDQuoteChar, sizeof(m_IDQuoteChar), &nResult);
1410 
1411   if (trace(1))
1412     htrc("DBMS: %s, Version: %s, rc=%d\n",
1413          GetStringInfo(SQL_DBMS_NAME), GetStringInfo(SQL_DBMS_VER), rc);
1414 
1415   } // end of GetConnectInfo
1416 
1417 /***********************************************************************/
1418 /*  Allocate record set and execute an SQL query.                      */
1419 /***********************************************************************/
ExecDirectSQL(char * sql,ODBCCOL * tocols)1420 int ODBConn::ExecDirectSQL(char *sql, ODBCCOL *tocols)
1421   {
1422   PGLOBAL& g = m_G;
1423   void    *buffer;
1424   bool     b;
1425   UWORD    n, k;
1426   SWORD    len, tp, ncol = 0;
1427   ODBCCOL *colp;
1428   RETCODE  rc;
1429   HSTMT    hstmt;
1430 
1431   try {
1432     b = false;
1433 
1434     if (m_hstmt) {
1435       // This is a Requery
1436       rc = SQLFreeStmt(m_hstmt, SQL_CLOSE);
1437 
1438       if (!Check(rc))
1439         ThrowDBX(rc, "SQLFreeStmt", m_hstmt);
1440 
1441       m_hstmt = NULL;
1442       } // endif m_hstmt
1443 
1444     rc = SQLAllocStmt(m_hdbc, &hstmt);
1445 
1446     if (!Check(rc))
1447       ThrowDBX(rc, "SQLAllocStmt");
1448 
1449     if (m_Scrollable) {
1450       rc = SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_SCROLLABLE,
1451                           (void*)SQL_SCROLLABLE, 0);
1452 
1453       if (!Check(rc))
1454         ThrowDBX(rc, "Scrollable", hstmt);
1455 
1456       } // endif m_Scrollable
1457 
1458     OnSetOptions(hstmt);
1459     b = true;
1460 
1461     if (trace(1))
1462       htrc("ExecDirect hstmt=%p %.256s\n", hstmt, sql);
1463 
1464     if (m_Tdb->Srcdef) {
1465       // Be sure this is a query returning a result set
1466       do {
1467         rc = SQLPrepare(hstmt, (PUCHAR)sql, SQL_NTS);
1468         } while (rc == SQL_STILL_EXECUTING);
1469 
1470       if (!Check(rc))
1471         ThrowDBX(rc, "SQLPrepare", hstmt);
1472 
1473       if (!Check(rc = SQLNumResultCols(hstmt, &ncol)))
1474         ThrowDBX(rc, "SQLNumResultCols", hstmt);
1475 
1476       if (ncol == 0) {
1477         strcpy(g->Message, "This Srcdef does not return a result set");
1478         return -1;
1479         } // endif ncol
1480 
1481       // Ok, now we can proceed
1482       do {
1483         rc = SQLExecute(hstmt);
1484         } while (rc == SQL_STILL_EXECUTING);
1485 
1486       if (!Check(rc))
1487         ThrowDBX(rc, "SQLExecute", hstmt);
1488 
1489     } else {
1490       do {
1491         rc = SQLExecDirect(hstmt, (PUCHAR)sql, SQL_NTS);
1492       } while (rc == SQL_STILL_EXECUTING);
1493 
1494       if (!Check(rc))
1495         ThrowDBX(rc, "SQLExecDirect", hstmt);
1496 
1497       do {
1498         rc = SQLNumResultCols(hstmt, &ncol);
1499       } while (rc == SQL_STILL_EXECUTING);
1500 
1501       k = 0;    // used for column number
1502     } // endif Srcdef
1503 
1504     for (n = 0, colp = tocols; colp; colp = (PODBCCOL)colp->GetNext())
1505       if (!colp->IsSpecial())
1506         n++;
1507 
1508     // n can be 0 for query such as Select count(*) from table
1509     if (n && n > (UWORD)ncol)
1510       ThrowDBX(MSG(COL_NUM_MISM));
1511 
1512     // Now bind the column buffers
1513     for (colp = tocols; colp; colp = (PODBCCOL)colp->GetNext())
1514       if (!colp->IsSpecial()) {
1515         buffer = colp->GetBuffer(m_RowsetSize);
1516         len = colp->GetBuflen();
1517         tp = GetSQLCType(colp->GetResultType());
1518 
1519         if (tp == SQL_TYPE_NULL) {
1520           sprintf(m_G->Message, MSG(INV_COLUMN_TYPE),
1521                   colp->GetResultType(), SVP(colp->GetName()));
1522           ThrowDBX(m_G->Message);
1523         } // endif tp
1524 
1525         if (m_Tdb->Srcdef)
1526           k = colp->GetIndex();
1527         else
1528           k++;
1529 
1530         if (trace(1))
1531           htrc("Binding col=%u type=%d buf=%p len=%d slen=%p\n",
1532                   k, tp, buffer, len, colp->GetStrLen());
1533 
1534         rc = SQLBindCol(hstmt, k, tp, buffer, len, colp->GetStrLen());
1535 
1536         if (!Check(rc))
1537           ThrowDBX(rc, "SQLBindCol", hstmt);
1538 
1539       } // endif colp
1540 
1541   } catch(DBX *x) {
1542     if (trace(1))
1543       for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
1544         htrc(x->m_ErrMsg[i]);
1545 
1546     sprintf(m_G->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
1547 
1548     if (b)
1549       SQLCancel(hstmt);
1550 
1551     rc = SQLFreeStmt(hstmt, SQL_DROP);
1552     m_hstmt = NULL;
1553     return -1;
1554   } // end try/catch
1555 
1556   m_hstmt = hstmt;
1557   return (int)m_RowsetSize;   // May have been reset in OnSetOptions
1558   } // end of ExecDirectSQL
1559 
1560 /***********************************************************************/
1561 /*  Get the number of lines of the result set.                         */
1562 /***********************************************************************/
GetResultSize(char * sql,ODBCCOL * colp)1563 int ODBConn::GetResultSize(char *sql, ODBCCOL *colp)
1564   {
1565   int    n = 0;
1566   RETCODE rc;
1567 
1568   if (ExecDirectSQL(sql, colp) < 0)
1569     return -1;
1570 
1571   try {
1572     for (n = 0; ; n++) {
1573       do {
1574         rc = SQLFetch(m_hstmt);
1575         } while (rc == SQL_STILL_EXECUTING);
1576 
1577       if (!Check(rc))
1578         ThrowDBX(rc, "SQLFetch", m_hstmt);
1579 
1580       if (rc == SQL_NO_DATA_FOUND)
1581         break;
1582 
1583       } // endfor n
1584 
1585   } catch(DBX *x) {
1586     strcpy(m_G->Message, x->GetErrorMessage(0));
1587 
1588     if (trace(1))
1589       for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
1590         htrc(x->m_ErrMsg[i]);
1591 
1592     SQLCancel(m_hstmt);
1593     n = -2;
1594   } // end try/catch
1595 
1596   rc = SQLFreeStmt(m_hstmt, SQL_DROP);
1597   m_hstmt = NULL;
1598 
1599   if (n != 1)
1600     return -3;
1601   else
1602     return colp->GetIntValue();
1603 
1604   } // end of GetResultSize
1605 
1606 /***********************************************************************/
1607 /*  Fetch next row.                                                    */
1608 /***********************************************************************/
Fetch(int pos)1609 int ODBConn::Fetch(int pos)
1610   {
1611   ASSERT(m_hstmt);
1612   int      irc;
1613   SQLULEN  crow;
1614   RETCODE  rc;
1615   PGLOBAL& g = m_G;
1616 
1617   try {
1618 //  do {
1619     if (pos) {
1620       rc = SQLExtendedFetch(m_hstmt, SQL_FETCH_ABSOLUTE, pos, &crow, NULL);
1621     } else if (m_RowsetSize) {
1622       rc = SQLExtendedFetch(m_hstmt, SQL_FETCH_NEXT, 1, &crow, NULL);
1623     } else {
1624       rc = SQLFetch(m_hstmt);
1625       crow = 1;
1626     } // endif m_RowsetSize
1627 //    } while (rc == SQL_STILL_EXECUTING);
1628 
1629     if (trace(2))
1630       htrc("Fetch: hstmt=%p RowseSize=%d rc=%d\n",
1631                      m_hstmt, m_RowsetSize, rc);
1632 
1633     if (!Check(rc))
1634       ThrowDBX(rc, "Fetching", m_hstmt);
1635 
1636     if (rc == SQL_NO_DATA_FOUND) {
1637       m_Full = (m_Fetch == 1);
1638       irc = 0;
1639     } else
1640       irc = (int)crow;
1641 
1642     m_Fetch++;
1643     m_Rows += irc;
1644   } catch(DBX *x) {
1645     if (trace(1))
1646       for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
1647         htrc(x->m_ErrMsg[i]);
1648 
1649     sprintf(g->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
1650     irc = -1;
1651   } // end try/catch
1652 
1653   return irc;
1654   } // end of Fetch
1655 
1656 /***********************************************************************/
1657 /*  Prepare an SQL statement for insert.                               */
1658 /***********************************************************************/
PrepareSQL(char * sql)1659 int ODBConn::PrepareSQL(char *sql)
1660   {
1661   PGLOBAL& g = m_G;
1662   bool     b;
1663   UINT     txn = 0;
1664   SWORD    nparm;
1665   RETCODE  rc;
1666   HSTMT    hstmt;
1667 
1668   if (m_Tdb->GetMode() != MODE_READ) {
1669     // Does the data source support transactions
1670     rc = SQLGetInfo(m_hdbc, SQL_TXN_CAPABLE, &txn, 0, NULL);
1671 
1672     if (Check(rc) && txn != SQL_TC_NONE) try {
1673       rc = SQLSetConnectAttr(m_hdbc, SQL_ATTR_AUTOCOMMIT,
1674                                      SQL_AUTOCOMMIT_OFF, SQL_IS_UINTEGER);
1675 
1676       if (!Check(rc))
1677         ThrowDBX(SQL_INVALID_HANDLE, "SQLSetConnectAttr");
1678 
1679       m_Transact = true;
1680     } catch(DBX *x) {
1681       if (trace(1))
1682         for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
1683           htrc(x->m_ErrMsg[i]);
1684 
1685     sprintf(g->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
1686     } // end try/catch
1687 
1688     } // endif Mode
1689 
1690   try {
1691     b = false;
1692 
1693     if (m_hstmt) {
1694       RETCODE rc = SQLFreeStmt(m_hstmt, SQL_CLOSE);
1695 
1696       hstmt = m_hstmt;
1697       m_hstmt = NULL;
1698 
1699       if (m_Tdb->GetAmType() != TYPE_AM_XDBC)
1700         ThrowDBX(MSG(SEQUENCE_ERROR));
1701 
1702       } // endif m_hstmt
1703 
1704     rc = SQLAllocStmt(m_hdbc, &hstmt);
1705 
1706     if (!Check(rc))
1707       ThrowDBX(SQL_INVALID_HANDLE, "SQLAllocStmt");
1708 
1709     OnSetOptions(hstmt);
1710     b = true;
1711 
1712     if (trace(1))
1713       htrc("Prepare hstmt=%p %.64s\n", hstmt, sql);
1714 
1715     do {
1716       rc = SQLPrepare(hstmt, (PUCHAR)sql, SQL_NTS);
1717       } while (rc == SQL_STILL_EXECUTING);
1718 
1719     if (!Check(rc))
1720       ThrowDBX(rc, "SQLPrepare", hstmt);
1721 
1722     do {
1723       rc = SQLNumParams(hstmt, &nparm);
1724       } while (rc == SQL_STILL_EXECUTING);
1725 
1726   } catch(DBX *x) {
1727     if (trace(1))
1728       for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
1729         htrc(x->m_ErrMsg[i]);
1730 
1731     sprintf(g->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
1732 
1733     if (b)
1734       SQLCancel(hstmt);
1735 
1736     rc = SQLFreeStmt(hstmt, SQL_DROP);
1737     m_hstmt = NULL;
1738 
1739     if (m_Transact) {
1740       rc = SQLEndTran(SQL_HANDLE_DBC, m_hdbc, SQL_ROLLBACK);
1741       m_Transact = false;
1742       } // endif m_Transact
1743 
1744     return -1;
1745   } // end try/catch
1746 
1747   m_hstmt = hstmt;
1748   return (int)nparm;
1749   } // end of PrepareSQL
1750 
1751 /***********************************************************************/
1752 /*  Execute a prepared statement.                                      */
1753 /***********************************************************************/
ExecuteSQL(void)1754 int ODBConn::ExecuteSQL(void)
1755   {
1756   PGLOBAL& g = m_G;
1757   SWORD    ncol = 0;
1758   RETCODE  rc;
1759   SQLLEN   afrw = -1;
1760 
1761   try {
1762     do {
1763       rc = SQLExecute(m_hstmt);
1764       } while (rc == SQL_STILL_EXECUTING);
1765 
1766     if (!Check(rc))
1767       ThrowDBX(rc, "SQLExecute", m_hstmt);
1768 
1769     if (!Check(rc = SQLNumResultCols(m_hstmt, &ncol)))
1770       ThrowDBX(rc, "SQLNumResultCols", m_hstmt);
1771 
1772     if (ncol) {
1773       // This should never happen while inserting
1774       strcpy(g->Message, "Logical error while inserting");
1775     } else {
1776       // Insert, Update or Delete statement
1777       if (!Check(rc = SQLRowCount(m_hstmt, &afrw)))
1778         ThrowDBX(rc, "SQLRowCount", m_hstmt);
1779 
1780     } // endif ncol
1781 
1782   } catch(DBX *x) {
1783     sprintf(m_G->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
1784     SQLCancel(m_hstmt);
1785     rc = SQLFreeStmt(m_hstmt, SQL_DROP);
1786     m_hstmt = NULL;
1787 
1788     if (m_Transact) {
1789       rc = SQLEndTran(SQL_HANDLE_DBC, m_hdbc, SQL_ROLLBACK);
1790       m_Transact = false;
1791       } // endif m_Transact
1792 
1793     afrw = -1;
1794   } // end try/catch
1795 
1796   return (int)afrw;
1797   } // end of ExecuteSQL
1798 
1799 /***********************************************************************/
1800 /*  Bind a parameter for inserting.                                    */
1801 /***********************************************************************/
BindParam(ODBCCOL * colp)1802 bool ODBConn::BindParam(ODBCCOL *colp)
1803   {
1804   void        *buf;
1805   int          buftype = colp->GetResultType();
1806   SQLUSMALLINT n = colp->GetRank();
1807 	SQLSMALLINT  ct, sqlt, dec, nul __attribute__((unused));
1808   SQLULEN      colsize;
1809   SQLLEN       len;
1810   SQLLEN      *strlen = colp->GetStrLen();
1811   SQLRETURN    rc;
1812 
1813 #if 0
1814   try {
1815 		// This function is often not or badly implemented by data sources
1816     rc = SQLDescribeParam(m_hstmt, n, &sqlt, &colsize, &dec, &nul);
1817 
1818     if (!Check(rc))
1819       ThrowDBX(rc, "SQLDescribeParam", m_hstmt);
1820 
1821   } catch(DBX *x) {
1822     sprintf(m_G->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
1823 #endif // 0
1824     colsize = colp->GetPrecision();
1825     sqlt = GetSQLType(buftype);
1826 		dec = IsTypeNum(buftype) ? colp->GetScale() : 0;
1827 		nul = colp->IsNullable() ? SQL_NULLABLE : SQL_NO_NULLS;
1828 //} // end try/catch
1829 
1830   buf = colp->GetBuffer(0);
1831   len = IsTypeChar(buftype) ? colp->GetBuflen() : 0;
1832   ct = GetSQLCType(buftype);
1833   *strlen = IsTypeChar(buftype) ? SQL_NTS : 0;
1834 
1835   try {
1836     rc = SQLBindParameter(m_hstmt, n, SQL_PARAM_INPUT, ct, sqlt,
1837                           colsize, dec, buf, len, strlen);
1838 
1839     if (!Check(rc))
1840       ThrowDBX(rc, "SQLBindParameter", m_hstmt);
1841 
1842   } catch(DBX *x) {
1843     strcpy(m_G->Message, x->GetErrorMessage(0));
1844     SQLCancel(m_hstmt);
1845     rc = SQLFreeStmt(m_hstmt, SQL_DROP);
1846     m_hstmt = NULL;
1847     return true;
1848   } // end try/catch
1849 
1850   return false;
1851   } // end of BindParam
1852 
1853 /***********************************************************************/
1854 /*  Execute an SQL command.                                            */
1855 /***********************************************************************/
1856 bool ODBConn::ExecSQLcommand(char *sql)
1857   {
1858   char     cmd[16];
1859   bool     b, rcd = false;
1860   UINT     txn = 0;
1861   PGLOBAL& g = m_G;
1862   SWORD    ncol = 0;
1863   SQLLEN   afrw;
1864   RETCODE  rc;
1865   HSTMT    hstmt;
1866 
1867   try {
1868     b = FALSE;
1869 
1870     // Check whether we should use transaction
1871     if (sscanf(sql, " %15s ", cmd) == 1) {
1872       if (!stricmp(cmd, "INSERT") || !stricmp(cmd, "UPDATE") ||
1873           !stricmp(cmd, "DELETE") || !stricmp(cmd, "REPLACE")) {
1874         // Does the data source support transactions
1875         rc = SQLGetInfo(m_hdbc, SQL_TXN_CAPABLE, &txn, 0, NULL);
1876 
1877         if (Check(rc) && txn != SQL_TC_NONE) {
1878           rc = SQLSetConnectAttr(m_hdbc, SQL_ATTR_AUTOCOMMIT,
1879                                  SQL_AUTOCOMMIT_OFF, SQL_IS_UINTEGER);
1880 
1881           if (!Check(rc))
1882             ThrowDBX(SQL_INVALID_HANDLE, "SQLSetConnectAttr");
1883 
1884           m_Transact = TRUE;
1885           } // endif txn
1886 
1887         } // endif cmd
1888 
1889       } // endif sql
1890 
1891     // Allocate the statement handle
1892     rc = SQLAllocStmt(m_hdbc, &hstmt);
1893 
1894     if (!Check(rc))
1895       ThrowDBX(SQL_INVALID_HANDLE, "SQLAllocStmt");
1896 
1897     OnSetOptions(hstmt);
1898     b = true;
1899 
1900     if (trace(1))
1901       htrc("ExecSQLcommand hstmt=%p %.64s\n", hstmt, sql);
1902 
1903     // Proceed with command execution
1904     do {
1905       rc = SQLExecDirect(hstmt, (PUCHAR)sql, SQL_NTS);
1906       } while (rc == SQL_STILL_EXECUTING);
1907 
1908     if (!Check(rc))
1909       ThrowDBX(rc, "SQLExecDirect", hstmt);
1910 
1911     // Check whether this is a query returning a result set
1912     if (!Check(rc = SQLNumResultCols(hstmt, &ncol)))
1913       ThrowDBX(rc, "SQLNumResultCols", hstmt);
1914 
1915     if (!ncol) {
1916       if (!Check(SQLRowCount(hstmt, &afrw)))
1917         ThrowDBX(rc, "SQLRowCount", hstmt);
1918 
1919       m_Tdb->AftRows = (int)afrw;
1920       strcpy(g->Message, "Affected rows");
1921     } else {
1922       m_Tdb->AftRows = (int)ncol;
1923       strcpy(g->Message, "Result set column number");
1924     } // endif ncol
1925 
1926   } catch(DBX *x) {
1927 		if (trace(1))
1928 			for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
1929 				htrc(x->m_ErrMsg[i]);
1930 
1931     sprintf(g->Message, "Remote %s: %s", x->m_Msg, x->GetErrorMessage(0));
1932 
1933     if (b)
1934       SQLCancel(hstmt);
1935 
1936     m_Tdb->AftRows = -1;
1937     rcd = true;
1938   } // end try/catch
1939 
1940   if (!Check(rc = SQLFreeStmt(hstmt, SQL_CLOSE)))
1941     sprintf(g->Message, "SQLFreeStmt: rc=%d", rc);
1942 
1943   if (m_Transact) {
1944     // Terminate the transaction
1945     if (!Check(rc = SQLEndTran(SQL_HANDLE_DBC, m_hdbc,
1946                        (rcd) ? SQL_ROLLBACK : SQL_COMMIT)))
1947       sprintf(g->Message, "SQLEndTran: rc=%d", rc);
1948 
1949     if (!Check(rc = SQLSetConnectAttr(m_hdbc, SQL_ATTR_AUTOCOMMIT,
1950                (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_UINTEGER)))
1951       sprintf(g->Message, "SQLSetConnectAttr: rc=%d", rc);
1952 
1953     m_Transact = false;
1954     } // endif m_Transact
1955 
1956   return rcd;
1957   } // end of ExecSQLcommand
1958 
1959 /**************************************************************************/
1960 /*  GetMetaData: constructs the result blocks containing the              */
1961 /*  description of all the columns of an SQL command.                     */
1962 /**************************************************************************/
1963 PQRYRES ODBConn::GetMetaData(PGLOBAL g, PCSZ dsn, PCSZ src)
1964   {
1965   static int  buftyp[] = {TYPE_STRING, TYPE_SHORT, TYPE_INT,
1966                           TYPE_SHORT,  TYPE_SHORT};
1967   static XFLD fldtyp[] = {FLD_NAME,  FLD_TYPE, FLD_PREC,
1968                           FLD_SCALE, FLD_NULL};
1969   static unsigned int length[] = {0, 6, 10, 6, 6};
1970   unsigned char cn[60];
1971   int     qcol = 5;
1972   short   nl, type, prec, nul, cns = (short)sizeof(cn);
1973   PQRYRES qrp = NULL;
1974   PCOLRES crp;
1975   USHORT  i;
1976   SQLULEN n;
1977   SWORD   ncol;
1978   RETCODE rc;
1979   HSTMT   hstmt;
1980 
1981   try {
1982     rc = SQLAllocStmt(m_hdbc, &hstmt);
1983 
1984     if (!Check(rc))
1985       ThrowDBX(SQL_INVALID_HANDLE, "SQLAllocStmt");
1986 
1987     OnSetOptions(hstmt);
1988 
1989     do {
1990       rc = SQLPrepare(hstmt, (PUCHAR)src, SQL_NTS);
1991 //    rc = SQLExecDirect(hstmt, (PUCHAR)src, SQL_NTS);
1992       } while (rc == SQL_STILL_EXECUTING);
1993 
1994     if (!Check(rc))
1995       ThrowDBX(rc, "SQLExecDirect", hstmt);
1996 
1997     do {
1998       rc = SQLNumResultCols(hstmt, &ncol);
1999       } while (rc == SQL_STILL_EXECUTING);
2000 
2001     if (!Check(rc))
2002       ThrowDBX(rc, "SQLNumResultCols", hstmt);
2003 
2004     if (ncol) for (i = 1; i <= ncol; i++) {
2005       do {
2006         rc = SQLDescribeCol(hstmt, i, NULL, 0, &nl, NULL, NULL, NULL, NULL);
2007         } while (rc == SQL_STILL_EXECUTING);
2008 
2009       if (!Check(rc))
2010         ThrowDBX(rc, "SQLDescribeCol", hstmt);
2011 
2012       length[0] = MY_MAX(length[0], (UINT)nl);
2013       } // endfor i
2014 
2015   } catch(DBX *x) {
2016     sprintf(g->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
2017     goto err;
2018   } // end try/catch
2019 
2020   if (!ncol) {
2021     strcpy(g->Message, "Invalid Srcdef");
2022     goto err;
2023     } // endif ncol
2024 
2025   /************************************************************************/
2026   /*  Allocate the structures used to refer to the result set.            */
2027   /************************************************************************/
2028   if (!(qrp = PlgAllocResult(g, qcol, ncol, IDS_COLUMNS + 3,
2029                              buftyp, fldtyp, length, false, true)))
2030     return NULL;
2031 
2032   // Some columns must be renamed
2033   for (i = 0, crp = qrp->Colresp; crp; crp = crp->Next)
2034     switch (++i) {
2035       case 3: crp->Name = "Precision"; break;
2036       case 4: crp->Name = "Scale";     break;
2037       case 5: crp->Name = "Nullable";  break;
2038       } // endswitch i
2039 
2040   /************************************************************************/
2041   /*  Now get the results into blocks.                                    */
2042   /************************************************************************/
2043   try {
2044     for (i = 0; i < ncol; i++) {
2045       do {
2046         rc = SQLDescribeCol(hstmt, i+1, cn, cns, &nl, &type, &n, &prec, &nul);
2047         } while (rc == SQL_STILL_EXECUTING);
2048 
2049       if (!Check(rc))
2050         ThrowDBX(rc, "SQLDescribeCol", hstmt);
2051       else
2052         qrp->Nblin++;
2053 
2054       crp = qrp->Colresp;                    // Column_Name
2055       crp->Kdata->SetValue((char*)cn, i);
2056       crp = crp->Next;                       // Data_Type
2057       crp->Kdata->SetValue(type, i);
2058       crp = crp->Next;                       // Precision (length)
2059       crp->Kdata->SetValue((int)n, i);
2060       crp = crp->Next;                       // Scale
2061       crp->Kdata->SetValue(prec, i);
2062       crp = crp->Next;                       // Nullable
2063       crp->Kdata->SetValue(nul, i);
2064       } // endfor i
2065 
2066   } catch(DBX *x) {
2067     sprintf(g->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
2068     qrp = NULL;
2069   } // end try/catch
2070 
2071   /* Cleanup */
2072  err:
2073   SQLCancel(hstmt);
2074   rc = SQLFreeStmt(hstmt, SQL_DROP);
2075   Close();
2076 
2077   /************************************************************************/
2078   /*  Return the result pointer for use by GetData routines.              */
2079   /************************************************************************/
2080   return qrp;
2081   } // end of GetMetaData
2082 
2083 /***********************************************************************/
2084 /*  Get the list of Data Sources and set it in qrp.                    */
2085 /***********************************************************************/
2086 bool ODBConn::GetDataSources(PQRYRES qrp)
2087   {
2088   bool    rv = false;
2089   UCHAR  *dsn, *des;
2090   UWORD   dir = SQL_FETCH_FIRST;
2091   SWORD   n1, n2, p1, p2;
2092   PCOLRES crp1 = qrp->Colresp, crp2 = qrp->Colresp->Next;
2093   RETCODE rc;
2094 
2095   n1 = crp1->Clen;
2096   n2 = crp2->Clen;
2097 
2098   try {
2099     rc = SQLAllocEnv(&m_henv);
2100 
2101     if (!Check(rc))
2102       ThrowDBX(rc, "SQLAllocEnv"); // Fatal
2103 
2104     for (int i = 0; i < qrp->Maxres; i++) {
2105       dsn = (UCHAR*)crp1->Kdata->GetValPtr(i);
2106       des = (UCHAR*)crp2->Kdata->GetValPtr(i);
2107       rc = SQLDataSources(m_henv, dir, dsn, n1, &p1, des, n2, &p2);
2108 
2109       if (rc == SQL_NO_DATA_FOUND)
2110         break;
2111       else if (!Check(rc))
2112         ThrowDBX(rc, "SQLDataSources");
2113 
2114       qrp->Nblin++;
2115       dir = SQL_FETCH_NEXT;
2116       } // endfor i
2117 
2118   } catch(DBX *x) {
2119     sprintf(m_G->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
2120     rv = true;
2121   } // end try/catch
2122 
2123   Close();
2124   return rv;
2125   } // end of GetDataSources
2126 
2127 /***********************************************************************/
2128 /*  Get the list of Drivers and set it in qrp.                         */
2129 /***********************************************************************/
2130 bool ODBConn::GetDrivers(PQRYRES qrp)
2131   {
2132   int     i, n;
2133   bool    rv = false;
2134   UCHAR  *des, *att;
2135   UWORD   dir = SQL_FETCH_FIRST;
2136   SWORD   n1, n2, p1, p2;
2137   PCOLRES crp1 = qrp->Colresp, crp2 = qrp->Colresp->Next;
2138   RETCODE rc;
2139 
2140   n1 = crp1->Clen;
2141   n2 = crp2->Clen;
2142 
2143   try {
2144     rc = SQLAllocEnv(&m_henv);
2145 
2146     if (!Check(rc))
2147       ThrowDBX(rc, "SQLAllocEnv"); // Fatal
2148 
2149     for (n = 0; n < qrp->Maxres; n++) {
2150       des = (UCHAR*)crp1->Kdata->GetValPtr(n);
2151       att = (UCHAR*)crp2->Kdata->GetValPtr(n);
2152       rc = SQLDrivers(m_henv, dir, des, n1, &p1, att, n2, &p2);
2153 
2154       if (rc == SQL_NO_DATA_FOUND)
2155         break;
2156       else if (!Check(rc))
2157         ThrowDBX(rc, "SQLDrivers");
2158 
2159 
2160       // The attributes being separated by '\0', set them to ';'
2161       for (i = 0; i < p2; i++)
2162         if (!att[i])
2163           att[i] = ';';
2164 
2165       qrp->Nblin++;
2166       dir = SQL_FETCH_NEXT;
2167       } // endfor n
2168 
2169   } catch(DBX *x) {
2170     sprintf(m_G->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
2171     rv = true;
2172   } // end try/catch
2173 
2174   Close();
2175   return rv;
2176   } // end of GetDrivers
2177 
2178 /***********************************************************************/
2179 /*  A helper class to split an optionally qualified table name into    */
2180 /*  components.                                                        */
2181 /*  These formats are understood:                                      */
2182 /*    "CatalogName.SchemaName.TableName"                               */
2183 /*    "SchemaName.TableName"                                           */
2184 /*    "TableName"                                                      */
2185 /***********************************************************************/
2186 class SQLQualifiedName
2187 {
2188   static const uint max_parts= 3;          // Catalog.Schema.Table
2189   MYSQL_LEX_STRING m_part[max_parts];
2190   char m_buf[512];
2191 
2192   void lex_string_set(MYSQL_LEX_STRING *S, char *str, size_t length)
2193   {
2194     S->str= str;
2195     S->length= length;
2196   } // end of lex_string_set
2197 
2198   void lex_string_shorten_down(MYSQL_LEX_STRING *S, size_t offs)
2199   {
2200     DBUG_ASSERT(offs <= S->length);
2201     S->str+= offs;
2202     S->length-= offs;
2203   } // end of lex_string_shorten_down
2204 
2205   /*********************************************************************/
2206   /*  Find the rightmost '.' delimiter and return the length           */
2207   /*  of the qualifier, including the rightmost '.' delimier.          */
2208   /*  For example, for the string {"a.b.c",5} it will return 4,        */
2209   /*  which is the length of the qualifier "a.b."                      */
2210   /*********************************************************************/
2211   size_t lex_string_find_qualifier(MYSQL_LEX_STRING *S)
2212   {
2213     size_t i;
2214     for (i= S->length; i > 0; i--)
2215     {
2216       if (S->str[i - 1] == '.')
2217       {
2218         S->str[i - 1]= '\0';
2219         return i;
2220       }
2221     }
2222     return 0;
2223   } // end of lex_string_find_qualifier
2224 
2225 public:
2226   /*********************************************************************/
2227   /*  Initialize to the given optionally qualified name.               */
2228   /*  NULL pointer in "name" is supported.                             */
2229   /*  name qualifier has precedence over schema.                       */
2230   /*********************************************************************/
2231   SQLQualifiedName(CATPARM *cap)
2232   {
2233     const char *name = (const char *)cap->Tab;
2234     char       *db = (char *)cap->DB;
2235     size_t      len, i;
2236 
2237     // Initialize the parts
2238     for (i = 0 ; i < max_parts; i++)
2239       lex_string_set(&m_part[i], NULL, 0);
2240 
2241     if (name) {
2242       // Initialize the first (rightmost) part
2243       lex_string_set(&m_part[0], m_buf,
2244                      strmake(m_buf, name, sizeof(m_buf) - 1) - m_buf);
2245 
2246       // Initialize the other parts, if exist.
2247       for (i= 1; i < max_parts; i++) {
2248         if (!(len= lex_string_find_qualifier(&m_part[i - 1])))
2249           break;
2250 
2251         lex_string_set(&m_part[i], m_part[i - 1].str, len - 1);
2252         lex_string_shorten_down(&m_part[i - 1], len);
2253         } // endfor i
2254 
2255       } // endif name
2256 
2257     // If it was not specified, set schema as the passed db name
2258     if (db && !m_part[1].length)
2259       lex_string_set(&m_part[1], db, strlen(db));
2260 
2261   } // end of SQLQualifiedName
2262 
2263   SQLCHAR *ptr(uint i)
2264   {
2265     DBUG_ASSERT(i < max_parts);
2266     return (SQLCHAR *) (m_part[i].length ? m_part[i].str : NULL);
2267   } // end of ptr
2268 
2269   SQLSMALLINT length(uint i)
2270   {
2271     DBUG_ASSERT(i < max_parts);
2272     return (SQLSMALLINT)m_part[i].length;
2273   } // end of length
2274 
2275 }; // end of class SQLQualifiedName
2276 
2277 /***********************************************************************/
2278 /*  Allocate recset and call SQLTables, SQLColumns or SQLPrimaryKeys.  */
2279 /***********************************************************************/
2280 int ODBConn::GetCatInfo(CATPARM *cap)
2281   {
2282   PGLOBAL& g = m_G;
2283   void    *buffer;
2284   int      i, irc;
2285   bool     b;
2286   PCSZ     fnc = "Unknown";
2287   UWORD    n = 0;
2288   SWORD    ncol, len, tp;
2289   SQLULEN  crow = 0;
2290   PQRYRES  qrp = cap->Qrp;
2291   PCOLRES  crp;
2292   RETCODE  rc = 0;
2293   HSTMT    hstmt = NULL;
2294   SQLLEN  *vl, *vlen = NULL;
2295   PVAL    *pval = NULL;
2296   char*   *pbuf = NULL;
2297 
2298   try {
2299     b = false;
2300 
2301     if (!m_hstmt) {
2302       rc = SQLAllocStmt(m_hdbc, &hstmt);
2303 
2304       if (!Check(rc))
2305         ThrowDBX(SQL_INVALID_HANDLE, "SQLAllocStmt");
2306 
2307     } else
2308       ThrowDBX(MSG(SEQUENCE_ERROR));
2309 
2310     b = true;
2311 
2312     // Currently m_Catver should be always 0 here
2313     assert(!m_Catver);     // This may be temporary
2314 
2315     if (qrp->Maxres > 0)
2316       m_RowsetSize = 1;
2317     else
2318       ThrowDBX("0-sized result");
2319 
2320     SQLQualifiedName name(cap);
2321 
2322     // Now do call the proper ODBC API
2323     switch (cap->Id) {
2324       case CAT_TAB:
2325         fnc = "SQLTables";
2326         rc = SQLTables(hstmt, name.ptr(2), name.length(2),
2327                               name.ptr(1), name.length(1),
2328                               name.ptr(0), name.length(0),
2329 					                    (SQLCHAR *)cap->Pat,
2330 					                    cap->Pat ? SQL_NTS : 0);
2331         break;
2332       case CAT_COL:
2333         fnc = "SQLColumns";
2334         rc = SQLColumns(hstmt, name.ptr(2), name.length(2),
2335                                name.ptr(1), name.length(1),
2336                                name.ptr(0), name.length(0),
2337 															 (SQLCHAR *)cap->Pat,
2338 					                     cap->Pat ? SQL_NTS : 0);
2339         break;
2340       case CAT_KEY:
2341         fnc = "SQLPrimaryKeys";
2342         rc = SQLPrimaryKeys(hstmt, name.ptr(2), name.length(2),
2343                                    name.ptr(1), name.length(1),
2344                                    name.ptr(0), name.length(0));
2345         break;
2346       case CAT_STAT:
2347         fnc = "SQLStatistics";
2348         rc = SQLStatistics(hstmt, name.ptr(2), name.length(2),
2349                                   name.ptr(1), name.length(1),
2350                                   name.ptr(0), name.length(0),
2351                                   cap->Unique, cap->Accuracy);
2352         break;
2353       case CAT_SPC:
2354         ThrowDBX("SQLSpecialColumns not available yet");
2355       default:
2356         ThrowDBX("Invalid SQL function id");
2357       } // endswitch infotype
2358 
2359     if (!Check(rc))
2360       ThrowDBX(rc, fnc, hstmt);
2361 
2362 		// Some data source do not implement SQLNumResultCols
2363     if (Check(SQLNumResultCols(hstmt, &ncol)))
2364       // n because we no more ignore the first column
2365       if ((n = (UWORD)qrp->Nbcol) > (UWORD)ncol)
2366         ThrowDBX(MSG(COL_NUM_MISM));
2367 
2368     // Unconditional to handle STRBLK's
2369     pval = (PVAL *)PlugSubAlloc(g, NULL, n * sizeof(PVAL));
2370     vlen = (SQLLEN *)PlugSubAlloc(g, NULL, n * sizeof(SQLLEN));
2371     pbuf = (char**)PlugSubAlloc(g, NULL, n * sizeof(char*));
2372 
2373     // Now bind the column buffers
2374     for (n = 0, crp = qrp->Colresp; crp; crp = crp->Next) {
2375       if ((tp = GetSQLCType(crp->Type)) == SQL_TYPE_NULL) {
2376         sprintf(g->Message, MSG(INV_COLUMN_TYPE), crp->Type, crp->Name);
2377         ThrowDBX(g->Message);
2378         } // endif tp
2379 
2380       if (!(len = GetTypeSize(crp->Type, crp->Length))) {
2381         len = 255;           // for STRBLK's
2382         ((STRBLK*)crp->Kdata)->SetSorted(true);
2383         } // endif len
2384 
2385       pval[n] = AllocateValue(g, crp->Type, len);
2386 			pval[n]->SetNullable(true);
2387 
2388       if (crp->Type == TYPE_STRING) {
2389         pbuf[n] = (char*)PlugSubAlloc(g, NULL, len);
2390         buffer = pbuf[n];
2391       } else
2392         buffer = pval[n]->GetTo_Val();
2393 
2394       vl = vlen + n;
2395 
2396       // n + 1 because column numbers begin with 1
2397       rc = SQLBindCol(hstmt, n + 1, tp, buffer, len, vl);
2398 
2399       if (!Check(rc))
2400         ThrowDBX(rc, "SQLBindCol", hstmt);
2401 
2402       n++;
2403       } // endfor crp
2404 
2405     fnc = "SQLFetch";
2406 
2407     // Now fetch the result
2408     // Extended fetch cannot be used because of STRBLK's
2409     for (i = 0; i < qrp->Maxres; i++) {
2410       if ((rc = SQLFetch(hstmt)) == SQL_NO_DATA_FOUND)
2411         break;
2412       else if (rc != SQL_SUCCESS) {
2413         if (trace(2) || (trace(1) && rc != SQL_SUCCESS_WITH_INFO)) {
2414           UCHAR   msg[SQL_MAX_MESSAGE_LENGTH + 1];
2415           UCHAR   state[SQL_SQLSTATE_SIZE + 1];
2416           RETCODE erc;
2417           SDWORD  native;
2418 
2419           htrc("SQLFetch: row %d rc=%d\n", i+1, rc);
2420           erc = SQLError(m_henv, m_hdbc, hstmt, state, &native, msg,
2421                          SQL_MAX_MESSAGE_LENGTH - 1, &len);
2422 
2423           if (rc != SQL_INVALID_HANDLE)
2424             // Skip non-errors
2425             for (n = 0; n < MAX_NUM_OF_MSG
2426                  && (erc == SQL_SUCCESS || erc == SQL_SUCCESS_WITH_INFO)
2427                  && strcmp((char*)state, "00000"); n++) {
2428               htrc("%s: %s, Native=%d\n", state, msg, native);
2429               erc = SQLError(m_henv, m_hdbc, hstmt, state, &native,
2430                              msg, SQL_MAX_MESSAGE_LENGTH - 1, &len);
2431               } // endfor n
2432 
2433           } // endif trace
2434 
2435         if (rc != SQL_SUCCESS_WITH_INFO)
2436           qrp->BadLines++;
2437 
2438         } // endif rc
2439 
2440       for (n = 0, crp = qrp->Colresp; crp; n++, crp = crp->Next) {
2441 				if (vlen[n] == SQL_NO_TOTAL)
2442 					ThrowDBX("Unexpected SQL_NO_TOTAL returned from SQLFetch");
2443 				else if (vlen[n] == SQL_NULL_DATA)
2444           pval[n]->SetNull(true);
2445         else if (crp->Type == TYPE_STRING/* && vlen[n] != SQL_NULL_DATA*/)
2446           pval[n]->SetValue_char(pbuf[n], (int)vlen[n]);
2447         else
2448           pval[n]->SetNull(false);
2449 
2450         crp->Kdata->SetValue(pval[n], i);
2451         cap->Vlen[n][i] = vlen[n];
2452         } // endfor crp
2453 
2454       } // endfor i
2455 
2456 #if 0
2457     if ((crow = i) && (rc == SQL_NO_DATA || rc == SQL_SUCCESS_WITH_INFO))
2458       rc = SQL_SUCCESS;
2459 
2460     if (rc == SQL_NO_DATA_FOUND) {
2461       if (cap->Pat)
2462         sprintf(g->Message, MSG(NO_TABCOL_DATA), cap->Tab, cap->Pat);
2463       else
2464         sprintf(g->Message, MSG(NO_TAB_DATA), cap->Tab);
2465 
2466       ThrowDBX(g->Message);
2467     } else if (rc == SQL_SUCCESS) {
2468       if ((rc = SQLFetch(hstmt)) != SQL_NO_DATA_FOUND)
2469         qrp->Truncated = true;
2470 
2471     } else
2472       ThrowDBX(rc, fnc, hstmt);
2473 #endif // 0
2474 
2475     if (!rc || rc == SQL_NO_DATA || rc == SQL_SUCCESS_WITH_INFO) {
2476       if ((rc = SQLFetch(hstmt)) != SQL_NO_DATA_FOUND)
2477         qrp->Truncated = true;
2478 
2479       crow = i;
2480     } else
2481       ThrowDBX(rc, fnc, hstmt);
2482 
2483     irc = (int)crow;
2484   } catch(DBX *x) {
2485     if (trace(1))
2486       for (int i = 0; i < MAX_NUM_OF_MSG && x->m_ErrMsg[i]; i++)
2487         htrc(x->m_ErrMsg[i]);
2488 
2489     sprintf(g->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
2490     irc = -1;
2491   } // end try/catch
2492 
2493   if (b)
2494     SQLCancel(hstmt);
2495 
2496   // All this (hstmt vs> m_hstmt) to be revisited
2497   if (hstmt)
2498     rc = SQLFreeStmt(hstmt, SQL_DROP);
2499 
2500   return irc;
2501   } // end of GetCatInfo
2502 
2503 /***********************************************************************/
2504 /*  Allocate a CONNECT result structure from the ODBC result.          */
2505 /***********************************************************************/
2506 PQRYRES ODBConn::AllocateResult(PGLOBAL g)
2507   {
2508   bool         uns;
2509   PODBCCOL     colp;
2510   PCOLRES     *pcrp, crp;
2511   PQRYRES      qrp;
2512 
2513   if (!m_Rows) {
2514     strcpy(g->Message, "Void result");
2515     return NULL;
2516     } // endif m_Res
2517 
2518   /*********************************************************************/
2519   /*  Allocate the result storage for future retrieval.                */
2520   /*********************************************************************/
2521   qrp = (PQRYRES)PlugSubAlloc(g, NULL, sizeof(QRYRES));
2522   pcrp = &qrp->Colresp;
2523   qrp->Continued = FALSE;
2524   qrp->Truncated = FALSE;
2525   qrp->Info = FALSE;
2526   qrp->Suball = TRUE;
2527   qrp->BadLines = 0;
2528   qrp->Maxsize = m_Rows;
2529   qrp->Maxres = m_Rows;
2530   qrp->Nbcol = 0;
2531   qrp->Nblin = 0;
2532   qrp->Cursor = 0;
2533 
2534   for (colp = (PODBCCOL)m_Tdb->Columns; colp;
2535        colp = (PODBCCOL)colp->GetNext())
2536     if (!colp->IsSpecial()) {
2537       *pcrp = (PCOLRES)PlugSubAlloc(g, NULL, sizeof(COLRES));
2538       crp = *pcrp;
2539       pcrp = &crp->Next;
2540       memset(crp, 0, sizeof(COLRES));
2541       crp->Ncol = ++qrp->Nbcol;
2542       crp->Name = colp->GetName();
2543       crp->Type = colp->GetResultType();
2544       crp->Prec = colp->GetScale();
2545       crp->Length = colp->GetLength();
2546       crp->Clen = colp->GetBuflen();
2547       uns = colp->IsUnsigned();
2548 
2549       if (!(crp->Kdata = AllocValBlock(g, NULL, crp->Type, m_Rows,
2550                                      crp->Clen, 0, FALSE, TRUE, uns))) {
2551         sprintf(g->Message, MSG(INV_RESULT_TYPE),
2552                           GetFormatType(crp->Type));
2553         return NULL;
2554         } // endif Kdata
2555 
2556       if (!colp->IsNullable())
2557         crp->Nulls = NULL;
2558       else {
2559         crp->Nulls = (char*)PlugSubAlloc(g, NULL, m_Rows);
2560         memset(crp->Nulls, ' ', m_Rows);
2561       } // endelse Nullable
2562 
2563       colp->SetCrp(crp);
2564       } // endif colp
2565 
2566   *pcrp = NULL;
2567 //qrp->Nblin = n;
2568   return qrp;
2569   } // end of AllocateResult
2570 
2571 /***********************************************************************/
2572 /*  Restart from beginning of result set                               */
2573 /***********************************************************************/
2574 int ODBConn::Rewind(char *sql, ODBCCOL *tocols)
2575   {
2576   int rc, rbuf = -1;
2577 
2578   if (!m_hstmt)
2579     rbuf = -1;
2580   else if (m_Full)
2581     rbuf = m_Rows;           // No need to "rewind"
2582   else if (m_Scrollable) {
2583     SQLULEN  crow;
2584 
2585     try {
2586       rc = SQLExtendedFetch(m_hstmt, SQL_FETCH_FIRST, 1, &crow, NULL);
2587 
2588       if (!Check(rc))
2589         ThrowDBX(rc, "SQLExtendedFetch", m_hstmt);
2590 
2591       rbuf = (int)crow;
2592     } catch(DBX *x) {
2593       sprintf(m_G->Message, "%s: %s", x->m_Msg, x->GetErrorMessage(0));
2594       rbuf = -1;
2595     } // end try/catch
2596 
2597   } else if (ExecDirectSQL(sql, tocols) >= 0)
2598     rbuf = 0;
2599 
2600   return rbuf;
2601   } // end of Rewind
2602 
2603 /***********************************************************************/
2604 /*  Disconnect connection                                              */
2605 /***********************************************************************/
2606 void ODBConn::Close()
2607   {
2608   RETCODE rc;
2609 
2610   if (m_hstmt) {
2611     // Is required for multiple tables
2612     rc = SQLFreeStmt(m_hstmt, SQL_DROP);
2613     m_hstmt = NULL;
2614     } // endif m_hstmt
2615 
2616   if (m_hdbc != SQL_NULL_HDBC) {
2617     if (m_Transact) {
2618       rc = SQLEndTran(SQL_HANDLE_DBC, m_hdbc, SQL_COMMIT);
2619       m_Transact = false;
2620       } // endif m_Transact
2621 
2622     rc = SQLDisconnect(m_hdbc);
2623 
2624     if (trace(1) && rc != SQL_SUCCESS)
2625       htrc("Error: SQLDisconnect rc=%d\n", rc);
2626 
2627     rc = SQLFreeConnect(m_hdbc);
2628 
2629     if (trace(1) && rc != SQL_SUCCESS)
2630       htrc("Error: SQLFreeConnect rc=%d\n", rc);
2631 
2632     m_hdbc = SQL_NULL_HDBC;
2633     } // endif m_hdbc
2634 
2635   if (m_henv != SQL_NULL_HENV) {
2636     rc = SQLFreeEnv(m_henv);
2637 
2638     if (trace(1) && rc != SQL_SUCCESS)   // Nothing we can do
2639       htrc("Error: SQLFreeEnv failure ignored in Close\n");
2640 
2641     m_henv = SQL_NULL_HENV;
2642     } // endif m_henv
2643 
2644 	if (m_Fp)
2645 		m_Fp->Count = 0;
2646 
2647   } // end of Close
2648