1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        src/common/db.cpp
3 // Purpose:     Implementation of the wxDb class.  The wxDb class represents a connection
4 //              to an ODBC data source.  The wxDb class allows operations on the data
5 //              source such as opening and closing the data source.
6 // Author:      Doug Card
7 // Modified by: George Tasker
8 //              Bart Jourquin
9 //              Mark Johnson, wxWindows@mj10777.de
10 // Mods:        Dec, 1998:
11 //                -Added support for SQL statement logging and database cataloging
12 // Mods:        April, 1999
13 //                -Added QUERY_ONLY mode support to reduce default number of cursors
14 //                -Added additional SQL logging code
15 //                -Added DEBUG-ONLY tracking of wxTable objects to detect orphaned DB connections
16 //                -Set ODBC option to only read committed writes to the DB so all
17 //                   databases operate the same in that respect
18 // Created:     9.96
19 // RCS-ID:      $Id: db.cpp 52489 2008-03-14 14:14:57Z JS $
20 // Copyright:   (c) 1996 Remstar International, Inc.
21 // Licence:     wxWindows licence
22 ///////////////////////////////////////////////////////////////////////////////
23 
24 #include "wx/wxprec.h"
25 
26 #ifdef __BORLANDC__
27     #pragma hdrstop
28 #endif
29 
30 #if wxUSE_ODBC
31 
32 #ifndef WX_PRECOMP
33     #include "wx/object.h"
34     #include "wx/list.h"
35     #include "wx/string.h"
36     #include "wx/utils.h"
37     #include "wx/log.h"
38     #include "wx/app.h"
39 #endif
40 
41 #ifdef DBDEBUG_CONSOLE
42     #include "wx/ioswrap.h"
43 #endif
44 
45 #include "wx/filefn.h"
46 #include "wx/wxchar.h"
47 
48 #include <stdio.h>
49 #include <string.h>
50 #include <assert.h>
51 #include <stdlib.h>
52 #include <ctype.h>
53 
54 #include "wx/db.h"
55 
56 // DLL options compatibility check:
57 WX_CHECK_BUILD_OPTIONS("wxODBC")
58 
59 WXDLLIMPEXP_DATA_ODBC(wxDbList*) PtrBegDbList = 0;
60 
61 wxChar const *SQL_LOG_FILENAME         = wxT("sqllog.txt");
62 wxChar const *SQL_CATALOG_FILENAME     = wxT("catalog.txt");
63 
64 #ifdef __WXDEBUG__
65     #include "wx/thread.h"
66 
67     extern wxList TablesInUse;
68 #if wxUSE_THREADS
69     extern wxCriticalSection csTablesInUse;
70 #endif // wxUSE_THREADS
71 #endif
72 
73 // SQL Log defaults to be used by GetDbConnection
74 wxDbSqlLogState SQLLOGstate = sqlLogOFF;
75 
76 static wxString SQLLOGfn = SQL_LOG_FILENAME;
77 
78 // The wxDb::errorList is copied to this variable when the wxDb object
79 // is closed.  This way, the error list is still available after the
80 // database object is closed.  This is necessary if the database
81 // connection fails so the calling application can show the operator
82 // why the connection failed.  Note: as each wxDb object is closed, it
83 // will overwrite the errors of the previously destroyed wxDb object in
84 // this variable.  NOTE: This occurs during a CLOSE, not a FREEing of the
85 // connection
86 wxChar DBerrorList[DB_MAX_ERROR_HISTORY][DB_MAX_ERROR_MSG_LEN+1];
87 
88 
89 // This type defines the return row-struct form
90 // SQLTablePrivileges, and is used by wxDB::TablePrivileges.
91 typedef struct
92 {
93    wxChar        tableQual[128+1];
94    wxChar        tableOwner[128+1];
95    wxChar        tableName[128+1];
96    wxChar        grantor[128+1];
97    wxChar        grantee[128+1];
98    wxChar        privilege[128+1];
99    wxChar        grantable[3+1];
100 } wxDbTablePrivilegeInfo;
101 
102 
103 /********** wxDbConnectInf Constructor - form 1 **********/
wxDbConnectInf()104 wxDbConnectInf::wxDbConnectInf()
105 {
106     Henv = 0;
107     freeHenvOnDestroy = false;
108 
109     Initialize();
110 }  // Constructor
111 
112 
113 /********** wxDbConnectInf Constructor - form 2 **********/
wxDbConnectInf(HENV henv,const wxString & dsn,const wxString & userID,const wxString & password,const wxString & defaultDir,const wxString & fileType,const wxString & description)114 wxDbConnectInf::wxDbConnectInf(HENV henv, const wxString &dsn, const wxString &userID,
115                        const wxString &password, const wxString &defaultDir,
116                        const wxString &fileType, const wxString &description)
117 {
118     Henv = 0;
119     freeHenvOnDestroy = false;
120 
121     Initialize();
122 
123     if (henv)
124         SetHenv(henv);
125     else
126         AllocHenv();
127 
128     SetDsn(dsn);
129     SetUserID(userID);
130     SetPassword(password);
131     SetDescription(description);
132     SetFileType(fileType);
133     SetDefaultDir(defaultDir);
134 }  // wxDbConnectInf Constructor
135 
136 
~wxDbConnectInf()137 wxDbConnectInf::~wxDbConnectInf()
138 {
139     if (freeHenvOnDestroy)
140     {
141         FreeHenv();
142     }
143 }  // wxDbConnectInf Destructor
144 
145 
146 
147 /********** wxDbConnectInf::Initialize() **********/
Initialize()148 bool wxDbConnectInf::Initialize()
149 {
150     freeHenvOnDestroy = false;
151 
152     if (freeHenvOnDestroy && Henv)
153         FreeHenv();
154 
155     Henv = 0;
156     Dsn[0] = 0;
157     Uid[0] = 0;
158     AuthStr[0] = 0;
159     ConnectionStr[0] = 0;
160     Description.Empty();
161     FileType.Empty();
162     DefaultDir.Empty();
163 
164     useConnectionStr = false;
165 
166     return true;
167 }  // wxDbConnectInf::Initialize()
168 
169 
170 /********** wxDbConnectInf::AllocHenv() **********/
AllocHenv()171 bool wxDbConnectInf::AllocHenv()
172 {
173     // This is here to help trap if you are getting a new henv
174     // without releasing an existing henv
175     wxASSERT(!Henv);
176 
177     // Initialize the ODBC Environment for Database Operations
178     if (SQLAllocEnv(&Henv) != SQL_SUCCESS)
179     {
180         wxLogDebug(wxT("A problem occurred while trying to get a connection to the data source"));
181         return false;
182     }
183 
184     freeHenvOnDestroy = true;
185 
186     return true;
187 }  // wxDbConnectInf::AllocHenv()
188 
189 
FreeHenv()190 void wxDbConnectInf::FreeHenv()
191 {
192     wxASSERT(Henv);
193 
194     if (Henv)
195         SQLFreeEnv(Henv);
196 
197     Henv = 0;
198     freeHenvOnDestroy = false;
199 
200 }  // wxDbConnectInf::FreeHenv()
201 
202 
SetDsn(const wxString & dsn)203 void wxDbConnectInf::SetDsn(const wxString &dsn)
204 {
205     wxASSERT(dsn.length() < WXSIZEOF(Dsn));
206 
207     wxStrncpy(Dsn, dsn, WXSIZEOF(Dsn)-1);
208     Dsn[WXSIZEOF(Dsn)-1] = 0;  // Prevent buffer overrun
209 }  // wxDbConnectInf::SetDsn()
210 
211 
SetUserID(const wxString & uid)212 void wxDbConnectInf::SetUserID(const wxString &uid)
213 {
214     wxASSERT(uid.length() < WXSIZEOF(Uid));
215     wxStrncpy(Uid, uid, WXSIZEOF(Uid)-1);
216     Uid[WXSIZEOF(Uid)-1] = 0;  // Prevent buffer overrun
217 }  // wxDbConnectInf::SetUserID()
218 
219 
SetPassword(const wxString & password)220 void wxDbConnectInf::SetPassword(const wxString &password)
221 {
222     wxASSERT(password.length() < WXSIZEOF(AuthStr));
223 
224     wxStrncpy(AuthStr, password, WXSIZEOF(AuthStr)-1);
225     AuthStr[WXSIZEOF(AuthStr)-1] = 0;  // Prevent buffer overrun
226 }  // wxDbConnectInf::SetPassword()
227 
SetConnectionStr(const wxString & connectStr)228 void wxDbConnectInf::SetConnectionStr(const wxString &connectStr)
229 {
230     wxASSERT(connectStr.length() < WXSIZEOF(ConnectionStr));
231 
232     useConnectionStr = wxStrlen(connectStr) > 0;
233 
234     wxStrncpy(ConnectionStr, connectStr, WXSIZEOF(ConnectionStr)-1);
235     ConnectionStr[WXSIZEOF(ConnectionStr)-1] = 0;  // Prevent buffer overrun
236 }  // wxDbConnectInf::SetConnectionStr()
237 
238 
239 /********** wxDbColFor Constructor **********/
wxDbColFor()240 wxDbColFor::wxDbColFor()
241 {
242     Initialize();
243 }  // wxDbColFor::wxDbColFor()
244 
245 
246 /********** wxDbColFor::Initialize() **********/
Initialize()247 void wxDbColFor::Initialize()
248 {
249     s_Field.Empty();
250     int i;
251     for (i=0; i<7; i++)
252     {
253         s_Format[i].Empty();
254         s_Amount[i].Empty();
255         i_Amount[i] = 0;
256     }
257     i_Nation      = 0;                     // 0=EU, 1=UK, 2=International, 3=US
258     i_dbDataType  = 0;
259     i_sqlDataType = 0;
260     Format(1,DB_DATA_TYPE_VARCHAR,0,0,0);  // the Function that does the work
261 }  // wxDbColFor::Initialize()
262 
263 
264 /********** wxDbColFor::Format() **********/
Format(int Nation,int dbDataType,SWORD sqlDataType,short columnLength,short decimalDigits)265 int wxDbColFor::Format(int Nation, int dbDataType, SWORD sqlDataType,
266                        short columnLength, short decimalDigits)
267 {
268     // ----------------------------------------------------------------------------------------
269     // -- 19991224 : mj10777 : Create
270     // There is still a lot of work to do here, but it is a start
271     // It handles all the basic data-types that I have run into up to now
272     // The main work will have be with Dates and float Formatting
273     //    (US 1,000.00 ; EU 1.000,00)
274     // There are wxWindow plans for locale support and the new wxDateTime.  If
275     //    they define some constants (wxEUROPEAN) that can be gloably used,
276     //    they should be used here.
277     // ----------------------------------------------------------------------------------------
278     // There should also be a function to scan in a string to fill the variable
279     // ----------------------------------------------------------------------------------------
280     wxString tempStr;
281     i_Nation      = Nation;                                       // 0 = timestamp , 1=EU, 2=UK, 3=International, 4=US
282     i_dbDataType  = dbDataType;
283     i_sqlDataType = sqlDataType;
284     s_Field.Printf(wxT("%s%d"),s_Amount[1].c_str(),i_Amount[1]);  // OK for VARCHAR, INTEGER and FLOAT
285 
286     if (i_dbDataType == 0)                                        // Filter unsupported dbDataTypes
287     {
288         if ((i_sqlDataType == SQL_VARCHAR)
289 #if wxUSE_UNICODE
290     #if defined(SQL_WCHAR)
291             || (i_sqlDataType == SQL_WCHAR)
292     #endif
293     #if defined(SQL_WVARCHAR)
294             || (i_sqlDataType == SQL_WVARCHAR)
295     #endif
296 #endif
297             || (i_sqlDataType == SQL_LONGVARCHAR))
298             i_dbDataType = DB_DATA_TYPE_VARCHAR;
299         if ((i_sqlDataType == SQL_C_DATE) || (i_sqlDataType == SQL_C_TIMESTAMP))
300             i_dbDataType = DB_DATA_TYPE_DATE;
301         if (i_sqlDataType == SQL_C_BIT)
302             i_dbDataType = DB_DATA_TYPE_INTEGER;
303         if (i_sqlDataType == SQL_NUMERIC)
304             i_dbDataType = DB_DATA_TYPE_VARCHAR;   // glt - ??? is this right?
305         if (i_sqlDataType == SQL_REAL)
306             i_dbDataType = DB_DATA_TYPE_FLOAT;
307         if (i_sqlDataType == SQL_C_BINARY)
308             i_dbDataType = DB_DATA_TYPE_BLOB;
309     }
310 
311     if ((i_dbDataType == DB_DATA_TYPE_INTEGER) && (i_sqlDataType == SQL_C_DOUBLE))
312     {   // DBASE Numeric
313         i_dbDataType = DB_DATA_TYPE_FLOAT;
314     }
315 
316     switch(i_dbDataType)     // TBD: Still a lot of proper formatting to do
317     {
318         case DB_DATA_TYPE_VARCHAR:
319             s_Field = wxT("%s");
320             break;
321         case DB_DATA_TYPE_INTEGER:
322             s_Field = wxT("%d");
323             break;
324         case DB_DATA_TYPE_FLOAT:
325             if (decimalDigits == 0)
326                 decimalDigits = 2;
327             tempStr.Printf(wxT("%%%d.%d"), columnLength, decimalDigits);
328             s_Field.Printf(wxT("%sf"), tempStr.c_str());
329             break;
330         case DB_DATA_TYPE_DATE:
331             if (i_Nation == 0)      // timestamp       YYYY-MM-DD HH:MM:SS.SSS (tested for SYBASE)
332             {
333                 s_Field = wxT("%04d-%02d-%02d %02d:%02d:%02d.%03d");
334             }
335             if (i_Nation == 1)      // European        DD.MM.YYYY HH:MM:SS.SSS
336             {
337                 s_Field = wxT("%02d.%02d.%04d %02d:%02d:%02d.%03d");
338             }
339             if (i_Nation == 2)      // UK              DD/MM/YYYY HH:MM:SS.SSS
340             {
341                 s_Field = wxT("%02d/%02d/%04d %02d:%02d:%02d.%03d");
342             }
343             if (i_Nation == 3)      // International   YYYY-MM-DD HH:MM:SS.SSS
344             {
345                 s_Field = wxT("%04d-%02d-%02d %02d:%02d:%02d.%03d");
346             }
347             if (i_Nation == 4)      // US              MM/DD/YYYY HH:MM:SS.SSS
348             {
349                 s_Field = wxT("%02d/%02d/%04d %02d:%02d:%02d.%03d");
350             }
351             break;
352           case DB_DATA_TYPE_BLOB:
353             s_Field.Printf(wxT("Unable to format(%d)-SQL(%d)"), dbDataType,sqlDataType);        //
354                 break;
355         default:
356             s_Field.Printf(wxT("Unknown Format(%d)-SQL(%d)"), dbDataType,sqlDataType);        //
357             break;
358     };
359     return TRUE;
360 }  // wxDbColFor::Format()
361 
362 
363 /********** wxDbColInf Constructor **********/
wxDbColInf()364 wxDbColInf::wxDbColInf()
365 {
366     Initialize();
367 }  // wxDbColInf::wxDbColInf()
368 
369 
370 /********** wxDbColInf Destructor ********/
~wxDbColInf()371 wxDbColInf::~wxDbColInf()
372 {
373     if (pColFor)
374         delete pColFor;
375     pColFor = NULL;
376 }  // wxDbColInf::~wxDbColInf()
377 
378 
Initialize()379 bool wxDbColInf::Initialize()
380 {
381     catalog[0]      = 0;
382     schema[0]       = 0;
383     tableName[0]    = 0;
384     colName[0]      = 0;
385     sqlDataType     = 0;
386     typeName[0]     = 0;
387     columnLength    = 0;
388     bufferSize      = 0;
389     decimalDigits   = 0;
390     numPrecRadix    = 0;
391     nullable        = 0;
392     remarks[0]      = 0;
393     dbDataType      = 0;
394     PkCol           = 0;
395     PkTableName[0]  = 0;
396     FkCol           = 0;
397     FkTableName[0]  = 0;
398     pColFor         = NULL;
399 
400     return true;
401 }  // wxDbColInf::Initialize()
402 
403 
404 /********** wxDbTableInf Constructor ********/
wxDbTableInf()405 wxDbTableInf::wxDbTableInf()
406 {
407     Initialize();
408 }  // wxDbTableInf::wxDbTableInf()
409 
410 
411 /********** wxDbTableInf Constructor ********/
~wxDbTableInf()412 wxDbTableInf::~wxDbTableInf()
413 {
414     if (pColInf)
415         delete [] pColInf;
416     pColInf = NULL;
417 }  // wxDbTableInf::~wxDbTableInf()
418 
419 
Initialize()420 bool wxDbTableInf::Initialize()
421 {
422     tableName[0]    = 0;
423     tableType[0]    = 0;
424     tableRemarks[0] = 0;
425     numCols         = 0;
426     pColInf         = NULL;
427 
428     return true;
429 }  // wxDbTableInf::Initialize()
430 
431 
432 /********** wxDbInf Constructor *************/
wxDbInf()433 wxDbInf::wxDbInf()
434 {
435     Initialize();
436 }  // wxDbInf::wxDbInf()
437 
438 
439 /********** wxDbInf Destructor *************/
~wxDbInf()440 wxDbInf::~wxDbInf()
441 {
442   if (pTableInf)
443     delete [] pTableInf;
444   pTableInf = NULL;
445 }  // wxDbInf::~wxDbInf()
446 
447 
448 /********** wxDbInf::Initialize() *************/
Initialize()449 bool wxDbInf::Initialize()
450 {
451     catalog[0]      = 0;
452     schema[0]       = 0;
453     numTables       = 0;
454     pTableInf       = NULL;
455 
456     return true;
457 }  // wxDbInf::Initialize()
458 
459 
460 /********** wxDb Constructor **********/
wxDb(const HENV & aHenv,bool FwdOnlyCursors)461 wxDb::wxDb(const HENV &aHenv, bool FwdOnlyCursors)
462 {
463     // Copy the HENV into the db class
464     henv = aHenv;
465     fwdOnlyCursors = FwdOnlyCursors;
466 
467     initialize();
468 } // wxDb::wxDb()
469 
470 
471 /********** wxDb Destructor **********/
~wxDb()472 wxDb::~wxDb()
473 {
474     wxASSERT_MSG(!IsCached(),wxT("Cached connections must not be manually deleted, use\nwxDbFreeConnection() or wxDbCloseConnections()."));
475 
476     if (IsOpen())
477     {
478         Close();
479     }
480 }  // wxDb destructor
481 
482 
483 
484 /********** PRIVATE! wxDb::initialize PRIVATE! **********/
485 /********** wxDb::initialize() **********/
initialize()486 void wxDb::initialize()
487 /*
488  * Private member function that sets all wxDb member variables to
489  * known values at creation of the wxDb
490  */
491 {
492     int i;
493 
494     fpSqlLog      = 0;            // Sql Log file pointer
495     sqlLogState   = sqlLogOFF;    // By default, logging is turned off
496     nTables       = 0;
497     dbmsType      = dbmsUNIDENTIFIED;
498 
499     wxStrcpy(sqlState,wxEmptyString);
500     wxStrcpy(errorMsg,wxEmptyString);
501     nativeError = cbErrorMsg = 0;
502     for (i = 0; i < DB_MAX_ERROR_HISTORY; i++)
503         wxStrcpy(errorList[i], wxEmptyString);
504 
505     // Init typeInf structures
506     typeInfVarchar.TypeName.Empty();
507     typeInfVarchar.FsqlType      = 0;
508     typeInfVarchar.Precision     = 0;
509     typeInfVarchar.CaseSensitive = 0;
510     typeInfVarchar.MaximumScale  = 0;
511 
512     typeInfInteger.TypeName.Empty();
513     typeInfInteger.FsqlType      = 0;
514     typeInfInteger.Precision     = 0;
515     typeInfInteger.CaseSensitive = 0;
516     typeInfInteger.MaximumScale  = 0;
517 
518     typeInfFloat.TypeName.Empty();
519     typeInfFloat.FsqlType      = 0;
520     typeInfFloat.Precision     = 0;
521     typeInfFloat.CaseSensitive = 0;
522     typeInfFloat.MaximumScale  = 0;
523 
524     typeInfDate.TypeName.Empty();
525     typeInfDate.FsqlType      = 0;
526     typeInfDate.Precision     = 0;
527     typeInfDate.CaseSensitive = 0;
528     typeInfDate.MaximumScale  = 0;
529 
530     typeInfBlob.TypeName.Empty();
531     typeInfBlob.FsqlType      = 0;
532     typeInfBlob.Precision     = 0;
533     typeInfBlob.CaseSensitive = 0;
534     typeInfBlob.MaximumScale  = 0;
535 
536     typeInfMemo.TypeName.Empty();
537     typeInfMemo.FsqlType      = 0;
538     typeInfMemo.Precision     = 0;
539     typeInfMemo.CaseSensitive = 0;
540     typeInfMemo.MaximumScale  = 0;
541 
542     // Error reporting is turned OFF by default
543     silent = true;
544 
545     // Allocate a data source connection handle
546     if (SQLAllocConnect(henv, &hdbc) != SQL_SUCCESS)
547         DispAllErrors(henv);
548 
549     // Initialize the db status flag
550     DB_STATUS = 0;
551 
552     // Mark database as not open as of yet
553     dbIsOpen = false;
554     dbIsCached = false;
555     dbOpenedWithConnectionString = false;
556 }  // wxDb::initialize()
557 
558 
559 /********** PRIVATE! wxDb::convertUserID PRIVATE! **********/
560 //
561 // NOTE: Return value from this function MUST be copied
562 //       immediately, as the value is not good after
563 //       this function has left scope.
564 //
convertUserID(const wxChar * userID,wxString & UserID)565 const wxChar *wxDb::convertUserID(const wxChar *userID, wxString &UserID)
566 {
567     if (userID)
568     {
569         if (!wxStrlen(userID))
570             UserID = uid;
571         else
572             UserID = userID;
573     }
574     else
575         UserID.Empty();
576 
577     // dBase does not use user names, and some drivers fail if you try to pass one
578     if ( Dbms() == dbmsDBASE
579          || Dbms() == dbmsXBASE_SEQUITER )
580         UserID.Empty();
581 
582     // Some databases require user names to be specified in uppercase,
583     // so force the name to uppercase
584     if ((Dbms() == dbmsORACLE) ||
585         (Dbms() == dbmsMAXDB))
586         UserID = UserID.Upper();
587 
588     return UserID.c_str();
589 }  // wxDb::convertUserID()
590 
591 
determineDataTypes(bool failOnDataTypeUnsupported)592 bool wxDb::determineDataTypes(bool failOnDataTypeUnsupported)
593 {
594     size_t iIndex;
595 
596     // These are the possible SQL types we check for use against the datasource we are connected
597     // to for the purpose of determining which data type to use for the basic character strings
598     // column types
599     //
600     // NOTE: The first type in this enumeration that is determined to be supported by the
601     //       datasource/driver is the one that will be used.
602     SWORD PossibleSqlCharTypes[] = {
603 #if wxUSE_UNICODE && defined(SQL_WVARCHAR)
604         SQL_WVARCHAR,
605 #endif
606         SQL_VARCHAR,
607 #if wxUSE_UNICODE && defined(SQL_WVARCHAR)
608         SQL_WCHAR,
609 #endif
610         SQL_CHAR
611     };
612 
613     // These are the possible SQL types we check for use against the datasource we are connected
614     // to for the purpose of determining which data type to use for the basic non-floating point
615     // column types
616     //
617     // NOTE: The first type in this enumeration that is determined to be supported by the
618     //       datasource/driver is the one that will be used.
619     SWORD PossibleSqlIntegerTypes[] = {
620         SQL_INTEGER
621     };
622 
623     // These are the possible SQL types we check for use against the datasource we are connected
624     // to for the purpose of determining which data type to use for the basic floating point number
625     // column types
626     //
627     // NOTE: The first type in this enumeration that is determined to be supported by the
628     //       datasource/driver is the one that will be used.
629     SWORD PossibleSqlFloatTypes[] = {
630         SQL_DOUBLE,
631         SQL_REAL,
632         SQL_FLOAT,
633         SQL_DECIMAL,
634         SQL_NUMERIC
635     };
636 
637     // These are the possible SQL types we check for use agains the datasource we are connected
638     // to for the purpose of determining which data type to use for the date/time column types
639     //
640     // NOTE: The first type in this enumeration that is determined to be supported by the
641     //       datasource/driver is the one that will be used.
642     SWORD PossibleSqlDateTypes[] = {
643         SQL_TIMESTAMP,
644         SQL_DATE,
645 #ifdef SQL_DATETIME
646         SQL_DATETIME
647 #endif
648     };
649 
650     // These are the possible SQL types we check for use agains the datasource we are connected
651     // to for the purpose of determining which data type to use for the BLOB column types.
652     //
653     // NOTE: The first type in this enumeration that is determined to be supported by the
654     //       datasource/driver is the one that will be used.
655     SWORD PossibleSqlBlobTypes[] = {
656         SQL_LONGVARBINARY,
657         SQL_VARBINARY
658     };
659 
660     // These are the possible SQL types we check for use agains the datasource we are connected
661     // to for the purpose of determining which data type to use for the MEMO column types
662     // (a type which allow to store large strings; like VARCHAR just with a bigger precision)
663     //
664     // NOTE: The first type in this enumeration that is determined to be supported by the
665     //       datasource/driver is the one that will be used.
666     SWORD PossibleSqlMemoTypes[] = {
667         SQL_LONGVARCHAR,
668     };
669 
670 
671     // Query the data source regarding data type information
672 
673     //
674     // The way it was determined which SQL data types to use was by calling SQLGetInfo
675     // for all of the possible SQL data types to see which ones were supported.  If
676     // a type is not supported, the SQLFetch() that's called from getDataTypeInfo()
677     // fails with SQL_NO_DATA_FOUND.  This is ugly because I'm sure the three SQL data
678     // types I've selected below will not always be what we want.  These are just
679     // what happened to work against an Oracle 7/Intersolv combination.  The following is
680     // a complete list of the results I got back against the Oracle 7 database:
681     //
682     // SQL_BIGINT             SQL_NO_DATA_FOUND
683     // SQL_BINARY             SQL_NO_DATA_FOUND
684     // SQL_BIT                SQL_NO_DATA_FOUND
685     // SQL_CHAR               type name = 'CHAR', Precision = 255
686     // SQL_DATE               SQL_NO_DATA_FOUND
687     // SQL_DECIMAL            type name = 'NUMBER', Precision = 38
688     // SQL_DOUBLE             type name = 'NUMBER', Precision = 15
689     // SQL_FLOAT              SQL_NO_DATA_FOUND
690     // SQL_INTEGER            SQL_NO_DATA_FOUND
691     // SQL_LONGVARBINARY      type name = 'LONG RAW', Precision = 2 billion
692     // SQL_LONGVARCHAR        type name = 'LONG', Precision = 2 billion
693     // SQL_NUMERIC            SQL_NO_DATA_FOUND
694     // SQL_REAL               SQL_NO_DATA_FOUND
695     // SQL_SMALLINT           SQL_NO_DATA_FOUND
696     // SQL_TIME               SQL_NO_DATA_FOUND
697     // SQL_TIMESTAMP          type name = 'DATE', Precision = 19
698     // SQL_VARBINARY          type name = 'RAW', Precision = 255
699     // SQL_VARCHAR            type name = 'VARCHAR2', Precision = 2000
700     // =====================================================================
701     // Results from a Microsoft Access 7.0 db, using a driver from Microsoft
702     //
703     // SQL_VARCHAR            type name = 'TEXT', Precision = 255
704     // SQL_TIMESTAMP          type name = 'DATETIME'
705     // SQL_DECIMAL            SQL_NO_DATA_FOUND
706     // SQL_NUMERIC            type name = 'CURRENCY', Precision = 19
707     // SQL_FLOAT              SQL_NO_DATA_FOUND
708     // SQL_REAL               type name = 'SINGLE', Precision = 7
709     // SQL_DOUBLE             type name = 'DOUBLE', Precision = 15
710     // SQL_INTEGER            type name = 'LONG', Precision = 10
711 
712     // Query the data source for info about itself
713     if (!getDbInfo(failOnDataTypeUnsupported))
714         return false;
715 
716     // --------------- Varchar - (Variable length character string) ---------------
717     for (iIndex = 0; iIndex < WXSIZEOF(PossibleSqlCharTypes) &&
718                      !getDataTypeInfo(PossibleSqlCharTypes[iIndex], typeInfVarchar); ++iIndex)
719     {}
720 
721     if (iIndex < WXSIZEOF(PossibleSqlCharTypes))
722         typeInfVarchar.FsqlType = PossibleSqlCharTypes[iIndex];
723     else if (failOnDataTypeUnsupported)
724         return false;
725 
726     // --------------- Float ---------------
727     for (iIndex = 0; iIndex < WXSIZEOF(PossibleSqlFloatTypes) &&
728                      !getDataTypeInfo(PossibleSqlFloatTypes[iIndex], typeInfFloat); ++iIndex)
729     {}
730 
731     if (iIndex < WXSIZEOF(PossibleSqlFloatTypes))
732         typeInfFloat.FsqlType = PossibleSqlFloatTypes[iIndex];
733     else if (failOnDataTypeUnsupported)
734         return false;
735 
736     // --------------- Integer -------------
737     for (iIndex = 0; iIndex < WXSIZEOF(PossibleSqlIntegerTypes) &&
738                      !getDataTypeInfo(PossibleSqlIntegerTypes[iIndex], typeInfInteger); ++iIndex)
739     {}
740 
741     if (iIndex < WXSIZEOF(PossibleSqlIntegerTypes))
742         typeInfInteger.FsqlType = PossibleSqlIntegerTypes[iIndex];
743     else if (failOnDataTypeUnsupported)
744     {
745         // If no non-floating point data types are supported, we'll
746         // use the type assigned for floats to store integers as well
747         if (!getDataTypeInfo(typeInfFloat.FsqlType, typeInfInteger))
748         {
749             if (failOnDataTypeUnsupported)
750                 return false;
751         }
752         else
753             typeInfInteger.FsqlType = typeInfFloat.FsqlType;
754     }
755 
756     // --------------- Date/Time ---------------
757     for (iIndex = 0; iIndex < WXSIZEOF(PossibleSqlDateTypes) &&
758                      !getDataTypeInfo(PossibleSqlDateTypes[iIndex], typeInfDate); ++iIndex)
759     {}
760 
761     if (iIndex < WXSIZEOF(PossibleSqlDateTypes))
762         typeInfDate.FsqlType = PossibleSqlDateTypes[iIndex];
763     else if (failOnDataTypeUnsupported)
764         return false;
765 
766     // --------------- BLOB ---------------
767     for (iIndex = 0; iIndex < WXSIZEOF(PossibleSqlBlobTypes) &&
768                      !getDataTypeInfo(PossibleSqlBlobTypes[iIndex], typeInfBlob); ++iIndex)
769     {}
770 
771     if (iIndex < WXSIZEOF(PossibleSqlBlobTypes))
772         typeInfBlob.FsqlType = PossibleSqlBlobTypes[iIndex];
773     else if (failOnDataTypeUnsupported)
774         return false;
775 
776     // --------------- MEMO ---------------
777     for (iIndex = 0; iIndex < WXSIZEOF(PossibleSqlMemoTypes) &&
778                      !getDataTypeInfo(PossibleSqlMemoTypes[iIndex], typeInfMemo); ++iIndex)
779     {}
780 
781     if (iIndex < WXSIZEOF(PossibleSqlMemoTypes))
782         typeInfMemo.FsqlType = PossibleSqlMemoTypes[iIndex];
783     else if (failOnDataTypeUnsupported)
784         return false;
785 
786     return true;
787 }  // wxDb::determineDataTypes
788 
789 
open(bool failOnDataTypeUnsupported)790 bool wxDb::open(bool failOnDataTypeUnsupported)
791 {
792 /*
793     If using Intersolv branded ODBC drivers, this is the place where you would substitute
794     your branded driver license information
795 
796     SQLSetConnectOption(hdbc, 1041, (UDWORD) wxEmptyString);
797     SQLSetConnectOption(hdbc, 1042, (UDWORD) wxEmptyString);
798 */
799 
800     // Mark database as open
801     dbIsOpen = true;
802 
803     // Allocate a statement handle for the database connection
804     if (SQLAllocStmt(hdbc, &hstmt) != SQL_SUCCESS)
805         return(DispAllErrors(henv, hdbc));
806 
807     // Set Connection Options
808     if (!setConnectionOptions())
809         return false;
810 
811     if (!determineDataTypes(failOnDataTypeUnsupported))
812         return false;
813 
814 #ifdef DBDEBUG_CONSOLE
815     cout << wxT("VARCHAR DATA TYPE: ") << typeInfVarchar.TypeName << endl;
816     cout << wxT("INTEGER DATA TYPE: ") << typeInfInteger.TypeName << endl;
817     cout << wxT("FLOAT   DATA TYPE: ") << typeInfFloat.TypeName << endl;
818     cout << wxT("DATE    DATA TYPE: ") << typeInfDate.TypeName << endl;
819     cout << wxT("BLOB    DATA TYPE: ") << typeInfBlob.TypeName << endl;
820     cout << wxT("MEMO    DATA TYPE: ") << typeInfMemo.TypeName << endl;
821     cout << endl;
822 #endif
823 
824     // Completed Successfully
825     return true;
826 }
827 
Open(const wxString & inConnectStr,bool failOnDataTypeUnsupported)828 bool wxDb::Open(const wxString& inConnectStr, bool failOnDataTypeUnsupported)
829 {
830     wxASSERT(inConnectStr.length());
831     return Open(inConnectStr, NULL, failOnDataTypeUnsupported);
832 }
833 
Open(const wxString & inConnectStr,SQLHWND parentWnd,bool failOnDataTypeUnsupported)834 bool wxDb::Open(const wxString& inConnectStr, SQLHWND parentWnd, bool failOnDataTypeUnsupported)
835 {
836     dsn        = wxEmptyString;
837     uid        = wxEmptyString;
838     authStr    = wxEmptyString;
839 
840     RETCODE retcode;
841 
842     if (!FwdOnlyCursors())
843     {
844         // Specify that the ODBC cursor library be used, if needed.  This must be
845         // specified before the connection is made.
846         retcode = SQLSetConnectOption(hdbc, SQL_ODBC_CURSORS, SQL_CUR_USE_IF_NEEDED);
847 
848 #ifdef DBDEBUG_CONSOLE
849         if (retcode == SQL_SUCCESS)
850             cout << wxT("SQLSetConnectOption(CURSOR_LIB) successful") << endl;
851         else
852             cout << wxT("SQLSetConnectOption(CURSOR_LIB) failed") << endl;
853 #else
854         wxUnusedVar(retcode);
855 #endif
856     }
857 
858     // Connect to the data source
859     SQLTCHAR outConnectBuffer[SQL_MAX_CONNECTSTR_LEN+1];  // MS recommends at least 1k buffer
860     short outConnectBufferLen;
861 
862     inConnectionStr = inConnectStr;
863 
864     retcode = SQLDriverConnect(hdbc, parentWnd, (SQLTCHAR FAR *)inConnectionStr.c_str(),
865                         (SWORD)inConnectionStr.length(), (SQLTCHAR FAR *)outConnectBuffer,
866                         WXSIZEOF(outConnectBuffer), &outConnectBufferLen, SQL_DRIVER_COMPLETE );
867 
868     if ((retcode != SQL_SUCCESS) &&
869         (retcode != SQL_SUCCESS_WITH_INFO))
870         return(DispAllErrors(henv, hdbc));
871 
872     outConnectBuffer[outConnectBufferLen] = 0;
873     outConnectionStr = outConnectBuffer;
874     dbOpenedWithConnectionString = true;
875 
876     return open(failOnDataTypeUnsupported);
877 }
878 
879 /********** wxDb::Open() **********/
Open(const wxString & Dsn,const wxString & Uid,const wxString & AuthStr,bool failOnDataTypeUnsupported)880 bool wxDb::Open(const wxString &Dsn, const wxString &Uid, const wxString &AuthStr, bool failOnDataTypeUnsupported)
881 {
882     wxASSERT(!Dsn.empty());
883     dsn        = Dsn;
884     uid        = Uid;
885     authStr    = AuthStr;
886 
887     inConnectionStr = wxEmptyString;
888     outConnectionStr = wxEmptyString;
889 
890     RETCODE retcode;
891 
892     if (!FwdOnlyCursors())
893     {
894         // Specify that the ODBC cursor library be used, if needed.  This must be
895         // specified before the connection is made.
896         retcode = SQLSetConnectOption(hdbc, SQL_ODBC_CURSORS, SQL_CUR_USE_IF_NEEDED);
897 
898 #ifdef DBDEBUG_CONSOLE
899         if (retcode == SQL_SUCCESS)
900             cout << wxT("SQLSetConnectOption(CURSOR_LIB) successful") << endl;
901         else
902             cout << wxT("SQLSetConnectOption(CURSOR_LIB) failed") << endl;
903 #else
904         wxUnusedVar( retcode );
905 #endif
906     }
907 
908     // Connect to the data source
909     retcode = SQLConnect(hdbc, (SQLTCHAR FAR *) dsn.c_str(), SQL_NTS,
910                          (SQLTCHAR FAR *) uid.c_str(), SQL_NTS,
911                          (SQLTCHAR FAR *) authStr.c_str(), SQL_NTS);
912 
913     if ((retcode != SQL_SUCCESS) &&
914         (retcode != SQL_SUCCESS_WITH_INFO))
915         return(DispAllErrors(henv, hdbc));
916 
917     return open(failOnDataTypeUnsupported);
918 
919 } // wxDb::Open()
920 
921 
Open(wxDbConnectInf * dbConnectInf,bool failOnDataTypeUnsupported)922 bool wxDb::Open(wxDbConnectInf *dbConnectInf, bool failOnDataTypeUnsupported)
923 {
924     wxASSERT(dbConnectInf);
925 
926     // Use the connection string if one is present
927     if (dbConnectInf->UseConnectionStr())
928         return Open(dbConnectInf->GetConnectionStr(), failOnDataTypeUnsupported);
929     else
930         return Open(dbConnectInf->GetDsn(), dbConnectInf->GetUserID(),
931                     dbConnectInf->GetPassword(), failOnDataTypeUnsupported);
932 }  // wxDb::Open()
933 
934 
Open(wxDb * copyDb)935 bool wxDb::Open(wxDb *copyDb)
936 {
937     dsn              = copyDb->GetDatasourceName();
938     uid              = copyDb->GetUsername();
939     authStr          = copyDb->GetPassword();
940     inConnectionStr  = copyDb->GetConnectionInStr();
941     outConnectionStr = copyDb->GetConnectionOutStr();
942 
943     RETCODE retcode;
944 
945     if (!FwdOnlyCursors())
946     {
947         // Specify that the ODBC cursor library be used, if needed.  This must be
948         // specified before the connection is made.
949         retcode = SQLSetConnectOption(hdbc, SQL_ODBC_CURSORS, SQL_CUR_USE_IF_NEEDED);
950 
951 #ifdef DBDEBUG_CONSOLE
952         if (retcode == SQL_SUCCESS)
953             cout << wxT("SQLSetConnectOption(CURSOR_LIB) successful") << endl;
954         else
955             cout << wxT("SQLSetConnectOption(CURSOR_LIB) failed") << endl;
956 #else
957         wxUnusedVar( retcode );
958 #endif
959     }
960 
961     if (copyDb->OpenedWithConnectionString())
962     {
963         // Connect to the data source
964         SQLTCHAR outConnectBuffer[SQL_MAX_CONNECTSTR_LEN+1];
965         short outConnectBufferLen;
966 
967         inConnectionStr = copyDb->GetConnectionInStr();
968 
969         retcode = SQLDriverConnect(hdbc, NULL, (SQLTCHAR FAR *)inConnectionStr.c_str(),
970                             (SWORD)inConnectionStr.length(), (SQLTCHAR FAR *)outConnectBuffer,
971                             WXSIZEOF(outConnectBuffer), &outConnectBufferLen, SQL_DRIVER_COMPLETE);
972 
973         if ((retcode != SQL_SUCCESS) &&
974             (retcode != SQL_SUCCESS_WITH_INFO))
975             return(DispAllErrors(henv, hdbc));
976 
977         outConnectBuffer[outConnectBufferLen] = 0;
978         outConnectionStr = outConnectBuffer;
979         dbOpenedWithConnectionString = true;
980     }
981     else
982     {
983         // Connect to the data source
984         retcode = SQLConnect(hdbc, (SQLTCHAR FAR *) dsn.c_str(), SQL_NTS,
985                              (SQLTCHAR FAR *) uid.c_str(), SQL_NTS,
986                              (SQLTCHAR FAR *) authStr.c_str(), SQL_NTS);
987     }
988 
989     if ((retcode != SQL_SUCCESS) &&
990         (retcode != SQL_SUCCESS_WITH_INFO))
991         return(DispAllErrors(henv, hdbc));
992 
993 /*
994     If using Intersolv branded ODBC drivers, this is the place where you would substitute
995     your branded driver license information
996 
997     SQLSetConnectOption(hdbc, 1041, (UDWORD) wxEmptyString);
998     SQLSetConnectOption(hdbc, 1042, (UDWORD) wxEmptyString);
999 */
1000 
1001     // Mark database as open
1002     dbIsOpen = true;
1003 
1004     // Allocate a statement handle for the database connection
1005     if (SQLAllocStmt(hdbc, &hstmt) != SQL_SUCCESS)
1006         return(DispAllErrors(henv, hdbc));
1007 
1008     // Set Connection Options
1009     if (!setConnectionOptions())
1010         return false;
1011 
1012     // Instead of Querying the data source for info about itself, it can just be copied
1013     // from the wxDb instance that was passed in (copyDb).
1014     wxStrcpy(dbInf.serverName,copyDb->dbInf.serverName);
1015     wxStrcpy(dbInf.databaseName,copyDb->dbInf.databaseName);
1016     wxStrcpy(dbInf.dbmsName,copyDb->dbInf.dbmsName);
1017     wxStrcpy(dbInf.dbmsVer,copyDb->dbInf.dbmsVer);
1018     dbInf.maxConnections = copyDb->dbInf.maxConnections;
1019     dbInf.maxStmts = copyDb->dbInf.maxStmts;
1020     wxStrcpy(dbInf.driverName,copyDb->dbInf.driverName);
1021     wxStrcpy(dbInf.odbcVer,copyDb->dbInf.odbcVer);
1022     wxStrcpy(dbInf.drvMgrOdbcVer,copyDb->dbInf.drvMgrOdbcVer);
1023     wxStrcpy(dbInf.driverVer,copyDb->dbInf.driverVer);
1024     dbInf.apiConfLvl = copyDb->dbInf.apiConfLvl;
1025     dbInf.cliConfLvl = copyDb->dbInf.cliConfLvl;
1026     dbInf.sqlConfLvl = copyDb->dbInf.sqlConfLvl;
1027     wxStrcpy(dbInf.outerJoins,copyDb->dbInf.outerJoins);
1028     wxStrcpy(dbInf.procedureSupport,copyDb->dbInf.procedureSupport);
1029     wxStrcpy(dbInf.accessibleTables,copyDb->dbInf.accessibleTables);
1030     dbInf.cursorCommitBehavior = copyDb->dbInf.cursorCommitBehavior;
1031     dbInf.cursorRollbackBehavior = copyDb->dbInf.cursorRollbackBehavior;
1032     dbInf.supportNotNullClause = copyDb->dbInf.supportNotNullClause;
1033     wxStrcpy(dbInf.supportIEF,copyDb->dbInf.supportIEF);
1034     dbInf.txnIsolation = copyDb->dbInf.txnIsolation;
1035     dbInf.txnIsolationOptions = copyDb->dbInf.txnIsolationOptions;
1036     dbInf.fetchDirections = copyDb->dbInf.fetchDirections;
1037     dbInf.lockTypes = copyDb->dbInf.lockTypes;
1038     dbInf.posOperations = copyDb->dbInf.posOperations;
1039     dbInf.posStmts = copyDb->dbInf.posStmts;
1040     dbInf.scrollConcurrency = copyDb->dbInf.scrollConcurrency;
1041     dbInf.scrollOptions = copyDb->dbInf.scrollOptions;
1042     dbInf.staticSensitivity = copyDb->dbInf.staticSensitivity;
1043     dbInf.txnCapable = copyDb->dbInf.txnCapable;
1044     dbInf.loginTimeout = copyDb->dbInf.loginTimeout;
1045 
1046     // VARCHAR = Variable length character string
1047     typeInfVarchar.FsqlType         = copyDb->typeInfVarchar.FsqlType;
1048     typeInfVarchar.TypeName         = copyDb->typeInfVarchar.TypeName;
1049     typeInfVarchar.Precision        = copyDb->typeInfVarchar.Precision;
1050     typeInfVarchar.CaseSensitive    = copyDb->typeInfVarchar.CaseSensitive;
1051     typeInfVarchar.MaximumScale     = copyDb->typeInfVarchar.MaximumScale;
1052 
1053     // Float
1054     typeInfFloat.FsqlType         = copyDb->typeInfFloat.FsqlType;
1055     typeInfFloat.TypeName         = copyDb->typeInfFloat.TypeName;
1056     typeInfFloat.Precision        = copyDb->typeInfFloat.Precision;
1057     typeInfFloat.CaseSensitive    = copyDb->typeInfFloat.CaseSensitive;
1058     typeInfFloat.MaximumScale     = copyDb->typeInfFloat.MaximumScale;
1059 
1060     // Integer
1061     typeInfInteger.FsqlType         = copyDb->typeInfInteger.FsqlType;
1062     typeInfInteger.TypeName         = copyDb->typeInfInteger.TypeName;
1063     typeInfInteger.Precision        = copyDb->typeInfInteger.Precision;
1064     typeInfInteger.CaseSensitive    = copyDb->typeInfInteger.CaseSensitive;
1065     typeInfInteger.MaximumScale     = copyDb->typeInfInteger.MaximumScale;
1066 
1067     // Date/Time
1068     typeInfDate.FsqlType         = copyDb->typeInfDate.FsqlType;
1069     typeInfDate.TypeName         = copyDb->typeInfDate.TypeName;
1070     typeInfDate.Precision        = copyDb->typeInfDate.Precision;
1071     typeInfDate.CaseSensitive    = copyDb->typeInfDate.CaseSensitive;
1072     typeInfDate.MaximumScale     = copyDb->typeInfDate.MaximumScale;
1073 
1074     // Blob
1075     typeInfBlob.FsqlType         = copyDb->typeInfBlob.FsqlType;
1076     typeInfBlob.TypeName         = copyDb->typeInfBlob.TypeName;
1077     typeInfBlob.Precision        = copyDb->typeInfBlob.Precision;
1078     typeInfBlob.CaseSensitive    = copyDb->typeInfBlob.CaseSensitive;
1079     typeInfBlob.MaximumScale     = copyDb->typeInfBlob.MaximumScale;
1080 
1081     // Memo
1082     typeInfMemo.FsqlType         = copyDb->typeInfMemo.FsqlType;
1083     typeInfMemo.TypeName         = copyDb->typeInfMemo.TypeName;
1084     typeInfMemo.Precision        = copyDb->typeInfMemo.Precision;
1085     typeInfMemo.CaseSensitive    = copyDb->typeInfMemo.CaseSensitive;
1086     typeInfMemo.MaximumScale     = copyDb->typeInfMemo.MaximumScale;
1087 
1088 #ifdef DBDEBUG_CONSOLE
1089     cout << wxT("VARCHAR DATA TYPE: ") << typeInfVarchar.TypeName << endl;
1090     cout << wxT("INTEGER DATA TYPE: ") << typeInfInteger.TypeName << endl;
1091     cout << wxT("FLOAT   DATA TYPE: ") << typeInfFloat.TypeName << endl;
1092     cout << wxT("DATE    DATA TYPE: ") << typeInfDate.TypeName << endl;
1093     cout << wxT("BLOB    DATA TYPE: ") << typeInfBlob.TypeName << endl;
1094     cout << wxT("MEMO    DATA TYPE: ") << typeInfMemo.TypeName << endl;
1095     cout << endl;
1096 #endif
1097 
1098     // Completed Successfully
1099     return true;
1100 } // wxDb::Open() 2
1101 
1102 
1103 /********** wxDb::setConnectionOptions() **********/
setConnectionOptions(void)1104 bool wxDb::setConnectionOptions(void)
1105 /*
1106  * NOTE: The Intersolv/Oracle 7 driver was "Not Capable" of setting the login timeout.
1107  */
1108 {
1109     SWORD cb;
1110 
1111     // I need to get the DBMS name here, because some of the connection options
1112     // are database specific and need to call the Dbms() function.
1113     RETCODE retcode;
1114 
1115     retcode = SQLGetInfo(hdbc, SQL_DBMS_NAME, (UCHAR *) dbInf.dbmsName, sizeof(dbInf.dbmsName), &cb);
1116     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
1117         return(DispAllErrors(henv, hdbc));
1118 
1119     /* retcode = */ SQLSetConnectOption(hdbc, SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF);
1120     /* retcode = */ SQLSetConnectOption(hdbc, SQL_OPT_TRACE, SQL_OPT_TRACE_OFF);
1121 //  SQLSetConnectOption(hdbc, SQL_TXN_ISOLATION, SQL_TXN_READ_COMMITTED);  // No dirty reads
1122 
1123     // By default, MS Sql Server closes cursors on commit and rollback.  The following
1124     // call to SQLSetConnectOption() is needed to force SQL Server to preserve cursors
1125     // after a transaction.  This is a driver specific option and is not part of the
1126     // ODBC standard.  Note: this behavior is specific to the ODBC interface to SQL Server.
1127     // The database settings don't have any effect one way or the other.
1128     if (Dbms() == dbmsMS_SQL_SERVER)
1129     {
1130         const long SQL_PRESERVE_CURSORS = 1204L;
1131         const long SQL_PC_ON = 1L;
1132         /* retcode = */ SQLSetConnectOption(hdbc, SQL_PRESERVE_CURSORS, SQL_PC_ON);
1133     }
1134 
1135     // Display the connection options to verify them
1136 #ifdef DBDEBUG_CONSOLE
1137     long l;
1138     cout << wxT("****** CONNECTION OPTIONS ******") << endl;
1139 
1140     retcode = SQLGetConnectOption(hdbc, SQL_AUTOCOMMIT, &l);
1141     if (retcode != SQL_SUCCESS)
1142         return(DispAllErrors(henv, hdbc));
1143     cout << wxT("AUTOCOMMIT: ") << (l == SQL_AUTOCOMMIT_OFF ? "OFF" : "ON") << endl;
1144 
1145     retcode = SQLGetConnectOption(hdbc, SQL_ODBC_CURSORS, &l);
1146     if (retcode != SQL_SUCCESS)
1147         return(DispAllErrors(henv, hdbc));
1148     cout << wxT("ODBC CURSORS: ");
1149     switch(l)
1150     {
1151         case(SQL_CUR_USE_IF_NEEDED):
1152             cout << wxT("SQL_CUR_USE_IF_NEEDED");
1153             break;
1154         case(SQL_CUR_USE_ODBC):
1155             cout << wxT("SQL_CUR_USE_ODBC");
1156             break;
1157         case(SQL_CUR_USE_DRIVER):
1158             cout << wxT("SQL_CUR_USE_DRIVER");
1159             break;
1160     }
1161     cout << endl;
1162 
1163     retcode = SQLGetConnectOption(hdbc, SQL_OPT_TRACE, &l)
1164     if (retcode != SQL_SUCCESS)
1165         return(DispAllErrors(henv, hdbc));
1166     cout << wxT("TRACING: ") << (l == SQL_OPT_TRACE_OFF ? wxT("OFF") : wxT("ON")) << endl;
1167 
1168     cout << endl;
1169 #endif
1170 
1171     // Completed Successfully
1172     return true;
1173 
1174 } // wxDb::setConnectionOptions()
1175 
1176 
1177 /********** wxDb::getDbInfo() **********/
getDbInfo(bool failOnDataTypeUnsupported)1178 bool wxDb::getDbInfo(bool failOnDataTypeUnsupported)
1179 {
1180     SWORD cb;
1181     RETCODE retcode;
1182 
1183     retcode = SQLGetInfo(hdbc, SQL_SERVER_NAME, (UCHAR*) dbInf.serverName, sizeof(dbInf.serverName), &cb);
1184     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1185     {
1186         DispAllErrors(henv, hdbc);
1187         if (failOnDataTypeUnsupported)
1188             return false;
1189     }
1190 
1191     retcode = SQLGetInfo(hdbc, SQL_DATABASE_NAME, (UCHAR*) dbInf.databaseName, sizeof(dbInf.databaseName), &cb);
1192     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1193     {
1194         DispAllErrors(henv, hdbc);
1195         if (failOnDataTypeUnsupported)
1196             return false;
1197     }
1198 
1199     retcode = SQLGetInfo(hdbc, SQL_DBMS_NAME, (UCHAR*) dbInf.dbmsName, sizeof(dbInf.dbmsName), &cb);
1200     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1201     {
1202         DispAllErrors(henv, hdbc);
1203         if (failOnDataTypeUnsupported)
1204             return false;
1205     }
1206 
1207     // 16-Mar-1999
1208     // After upgrading to MSVC6, the original 20 char buffer below was insufficient,
1209     // causing database connectivity to fail in some cases.
1210     retcode = SQLGetInfo(hdbc, SQL_DBMS_VER, (UCHAR*) dbInf.dbmsVer, sizeof(dbInf.dbmsVer), &cb);
1211     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1212     {
1213         DispAllErrors(henv, hdbc);
1214         if (failOnDataTypeUnsupported)
1215             return false;
1216     }
1217 
1218     retcode = SQLGetInfo(hdbc, SQL_ACTIVE_CONNECTIONS, (UCHAR*) &dbInf.maxConnections, sizeof(dbInf.maxConnections), &cb);
1219     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1220     {
1221         DispAllErrors(henv, hdbc);
1222         if (failOnDataTypeUnsupported)
1223             return false;
1224     }
1225 
1226     retcode = SQLGetInfo(hdbc, SQL_ACTIVE_STATEMENTS, (UCHAR*) &dbInf.maxStmts, sizeof(dbInf.maxStmts), &cb);
1227     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1228     {
1229         DispAllErrors(henv, hdbc);
1230         if (failOnDataTypeUnsupported)
1231             return false;
1232     }
1233 
1234     retcode = SQLGetInfo(hdbc, SQL_DRIVER_NAME, (UCHAR*) dbInf.driverName, sizeof(dbInf.driverName), &cb);
1235     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1236     {
1237         DispAllErrors(henv, hdbc);
1238         if (failOnDataTypeUnsupported)
1239             return false;
1240     }
1241 
1242     retcode = SQLGetInfo(hdbc, SQL_DRIVER_ODBC_VER, (UCHAR*) dbInf.odbcVer, sizeof(dbInf.odbcVer), &cb);
1243     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1244     {
1245         DispAllErrors(henv, hdbc);
1246         if (failOnDataTypeUnsupported)
1247             return false;
1248     }
1249 
1250     retcode = SQLGetInfo(hdbc, SQL_ODBC_VER, (UCHAR*) dbInf.drvMgrOdbcVer, sizeof(dbInf.drvMgrOdbcVer), &cb);
1251     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
1252     {
1253         DispAllErrors(henv, hdbc);
1254         if (failOnDataTypeUnsupported)
1255             return false;
1256     }
1257 
1258     retcode = SQLGetInfo(hdbc, SQL_DRIVER_VER, (UCHAR*) dbInf.driverVer, sizeof(dbInf.driverVer), &cb);
1259     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1260     {
1261         DispAllErrors(henv, hdbc);
1262         if (failOnDataTypeUnsupported)
1263             return false;
1264     }
1265 
1266     retcode = SQLGetInfo(hdbc, SQL_ODBC_API_CONFORMANCE, (UCHAR*) &dbInf.apiConfLvl, sizeof(dbInf.apiConfLvl), &cb);
1267     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1268     {
1269         DispAllErrors(henv, hdbc);
1270         if (failOnDataTypeUnsupported)
1271             return false;
1272     }
1273 
1274     retcode = SQLGetInfo(hdbc, SQL_ODBC_SAG_CLI_CONFORMANCE, (UCHAR*) &dbInf.cliConfLvl, sizeof(dbInf.cliConfLvl), &cb);
1275     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1276     {
1277         // Not all drivers support this call - Nick Gorham(unixODBC)
1278         dbInf.cliConfLvl = 0;
1279         DispAllErrors(henv, hdbc);
1280         if (failOnDataTypeUnsupported)
1281             return false;
1282     }
1283 
1284     retcode = SQLGetInfo(hdbc, SQL_ODBC_SQL_CONFORMANCE, (UCHAR*) &dbInf.sqlConfLvl, sizeof(dbInf.sqlConfLvl), &cb);
1285     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1286     {
1287         DispAllErrors(henv, hdbc);
1288         if (failOnDataTypeUnsupported)
1289             return false;
1290     }
1291 
1292     retcode = SQLGetInfo(hdbc, SQL_OUTER_JOINS, (UCHAR*) dbInf.outerJoins, sizeof(dbInf.outerJoins), &cb);
1293     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1294     {
1295         DispAllErrors(henv, hdbc);
1296         if (failOnDataTypeUnsupported)
1297             return false;
1298     }
1299 
1300     retcode = SQLGetInfo(hdbc, SQL_PROCEDURES, (UCHAR*) dbInf.procedureSupport, sizeof(dbInf.procedureSupport), &cb);
1301     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1302     {
1303         DispAllErrors(henv, hdbc);
1304         if (failOnDataTypeUnsupported)
1305             return false;
1306     }
1307 
1308     retcode = SQLGetInfo(hdbc, SQL_ACCESSIBLE_TABLES, (UCHAR*) dbInf.accessibleTables, sizeof(dbInf.accessibleTables), &cb);
1309     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1310     {
1311         DispAllErrors(henv, hdbc);
1312         if (failOnDataTypeUnsupported)
1313             return false;
1314     }
1315 
1316     retcode = SQLGetInfo(hdbc, SQL_CURSOR_COMMIT_BEHAVIOR, (UCHAR*) &dbInf.cursorCommitBehavior, sizeof(dbInf.cursorCommitBehavior), &cb);
1317     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1318     {
1319         DispAllErrors(henv, hdbc);
1320         if (failOnDataTypeUnsupported)
1321             return false;
1322     }
1323 
1324     retcode = SQLGetInfo(hdbc, SQL_CURSOR_ROLLBACK_BEHAVIOR, (UCHAR*) &dbInf.cursorRollbackBehavior, sizeof(dbInf.cursorRollbackBehavior), &cb);
1325     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1326     {
1327         DispAllErrors(henv, hdbc);
1328         if (failOnDataTypeUnsupported)
1329             return false;
1330     }
1331 
1332     retcode = SQLGetInfo(hdbc, SQL_NON_NULLABLE_COLUMNS, (UCHAR*) &dbInf.supportNotNullClause, sizeof(dbInf.supportNotNullClause), &cb);
1333     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1334     {
1335         DispAllErrors(henv, hdbc);
1336         if (failOnDataTypeUnsupported)
1337             return false;
1338     }
1339 
1340     retcode = SQLGetInfo(hdbc, SQL_ODBC_SQL_OPT_IEF, (UCHAR*) dbInf.supportIEF, sizeof(dbInf.supportIEF), &cb);
1341     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1342     {
1343         DispAllErrors(henv, hdbc);
1344         if (failOnDataTypeUnsupported)
1345             return false;
1346     }
1347 
1348     retcode = SQLGetInfo(hdbc, SQL_DEFAULT_TXN_ISOLATION, (UCHAR*) &dbInf.txnIsolation, sizeof(dbInf.txnIsolation), &cb);
1349     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1350     {
1351         DispAllErrors(henv, hdbc);
1352         if (failOnDataTypeUnsupported)
1353             return false;
1354     }
1355 
1356     retcode = SQLGetInfo(hdbc, SQL_TXN_ISOLATION_OPTION, (UCHAR*) &dbInf.txnIsolationOptions, sizeof(dbInf.txnIsolationOptions), &cb);
1357     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1358     {
1359         DispAllErrors(henv, hdbc);
1360         if (failOnDataTypeUnsupported)
1361             return false;
1362     }
1363 
1364     retcode = SQLGetInfo(hdbc, SQL_FETCH_DIRECTION, (UCHAR*) &dbInf.fetchDirections, sizeof(dbInf.fetchDirections), &cb);
1365     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1366     {
1367         DispAllErrors(henv, hdbc);
1368         if (failOnDataTypeUnsupported)
1369             return false;
1370     }
1371 
1372     retcode = SQLGetInfo(hdbc, SQL_LOCK_TYPES, (UCHAR*) &dbInf.lockTypes, sizeof(dbInf.lockTypes), &cb);
1373     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1374     {
1375         DispAllErrors(henv, hdbc);
1376         if (failOnDataTypeUnsupported)
1377             return false;
1378     }
1379 
1380     retcode = SQLGetInfo(hdbc, SQL_POS_OPERATIONS, (UCHAR*) &dbInf.posOperations, sizeof(dbInf.posOperations), &cb);
1381     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1382     {
1383         DispAllErrors(henv, hdbc);
1384         if (failOnDataTypeUnsupported)
1385             return false;
1386     }
1387 
1388     retcode = SQLGetInfo(hdbc, SQL_POSITIONED_STATEMENTS, (UCHAR*) &dbInf.posStmts, sizeof(dbInf.posStmts), &cb);
1389     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1390     {
1391         DispAllErrors(henv, hdbc);
1392         if (failOnDataTypeUnsupported)
1393             return false;
1394     }
1395 
1396     retcode = SQLGetInfo(hdbc, SQL_SCROLL_CONCURRENCY, (UCHAR*) &dbInf.scrollConcurrency, sizeof(dbInf.scrollConcurrency), &cb);
1397     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1398     {
1399         DispAllErrors(henv, hdbc);
1400         if (failOnDataTypeUnsupported)
1401             return false;
1402     }
1403 
1404     retcode = SQLGetInfo(hdbc, SQL_SCROLL_OPTIONS, (UCHAR*) &dbInf.scrollOptions, sizeof(dbInf.scrollOptions), &cb);
1405     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1406     {
1407         DispAllErrors(henv, hdbc);
1408         if (failOnDataTypeUnsupported)
1409             return false;
1410     }
1411 
1412     retcode = SQLGetInfo(hdbc, SQL_STATIC_SENSITIVITY, (UCHAR*) &dbInf.staticSensitivity, sizeof(dbInf.staticSensitivity), &cb);
1413     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1414     {
1415         DispAllErrors(henv, hdbc);
1416         if (failOnDataTypeUnsupported)
1417             return false;
1418     }
1419 
1420     retcode = SQLGetInfo(hdbc, SQL_TXN_CAPABLE, (UCHAR*) &dbInf.txnCapable, sizeof(dbInf.txnCapable), &cb);
1421     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1422     {
1423         DispAllErrors(henv, hdbc);
1424         if (failOnDataTypeUnsupported)
1425             return false;
1426     }
1427 
1428     retcode = SQLGetInfo(hdbc, SQL_LOGIN_TIMEOUT, (UCHAR*) &dbInf.loginTimeout, sizeof(dbInf.loginTimeout), &cb);
1429     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO )
1430     {
1431         DispAllErrors(henv, hdbc);
1432         if (failOnDataTypeUnsupported)
1433             return false;
1434     }
1435 
1436 #ifdef DBDEBUG_CONSOLE
1437     cout << wxT("***** DATA SOURCE INFORMATION *****") << endl;
1438     cout << wxT(wxT("SERVER Name: ") << dbInf.serverName << endl;
1439     cout << wxT("DBMS Name: ") << dbInf.dbmsName << wxT("; DBMS Version: ") << dbInf.dbmsVer << endl;
1440     cout << wxT("ODBC Version: ") << dbInf.odbcVer << wxT("; Driver Version: ") << dbInf.driverVer << endl;
1441 
1442     cout << wxT("API Conf. Level: ");
1443     switch(dbInf.apiConfLvl)
1444     {
1445         case SQL_OAC_NONE:      cout << wxT("None");       break;
1446         case SQL_OAC_LEVEL1:    cout << wxT("Level 1");    break;
1447         case SQL_OAC_LEVEL2:    cout << wxT("Level 2");    break;
1448     }
1449     cout << endl;
1450 
1451     cout << wxT("SAG CLI Conf. Level: ");
1452     switch(dbInf.cliConfLvl)
1453     {
1454         case SQL_OSCC_NOT_COMPLIANT:    cout << wxT("Not Compliant");    break;
1455         case SQL_OSCC_COMPLIANT:        cout << wxT("Compliant");        break;
1456     }
1457     cout << endl;
1458 
1459     cout << wxT("SQL Conf. Level: ");
1460     switch(dbInf.sqlConfLvl)
1461     {
1462         case SQL_OSC_MINIMUM:     cout << wxT("Minimum Grammar");     break;
1463         case SQL_OSC_CORE:        cout << wxT("Core Grammar");        break;
1464         case SQL_OSC_EXTENDED:    cout << wxT("Extended Grammar");    break;
1465     }
1466     cout << endl;
1467 
1468     cout << wxT("Max. Connections: ")       << dbInf.maxConnections   << endl;
1469     cout << wxT("Outer Joins: ")            << dbInf.outerJoins       << endl;
1470     cout << wxT("Support for Procedures: ") << dbInf.procedureSupport << endl;
1471     cout << wxT("All tables accessible : ") << dbInf.accessibleTables << endl;
1472     cout << wxT("Cursor COMMIT Behavior: ");
1473     switch(dbInf.cursorCommitBehavior)
1474     {
1475         case SQL_CB_DELETE:        cout << wxT("Delete cursors");      break;
1476         case SQL_CB_CLOSE:         cout << wxT("Close cursors");       break;
1477         case SQL_CB_PRESERVE:      cout << wxT("Preserve cursors");    break;
1478     }
1479     cout << endl;
1480 
1481     cout << wxT("Cursor ROLLBACK Behavior: ");
1482     switch(dbInf.cursorRollbackBehavior)
1483     {
1484         case SQL_CB_DELETE:      cout << wxT("Delete cursors");      break;
1485         case SQL_CB_CLOSE:       cout << wxT("Close cursors");       break;
1486         case SQL_CB_PRESERVE:    cout << wxT("Preserve cursors");    break;
1487     }
1488     cout << endl;
1489 
1490     cout << wxT("Support NOT NULL clause: ");
1491     switch(dbInf.supportNotNullClause)
1492     {
1493         case SQL_NNC_NULL:        cout << wxT("No");        break;
1494         case SQL_NNC_NON_NULL:    cout << wxT("Yes");       break;
1495     }
1496     cout << endl;
1497 
1498     cout << wxT("Support IEF (Ref. Integrity): ") << dbInf.supportIEF   << endl;
1499     cout << wxT("Login Timeout: ")                << dbInf.loginTimeout << endl;
1500 
1501     cout << endl << endl << wxT("more ...") << endl;
1502     getchar();
1503 
1504     cout << wxT("Default Transaction Isolation: ";
1505     switch(dbInf.txnIsolation)
1506     {
1507         case SQL_TXN_READ_UNCOMMITTED:  cout << wxT("Read Uncommitted");    break;
1508         case SQL_TXN_READ_COMMITTED:    cout << wxT("Read Committed");      break;
1509         case SQL_TXN_REPEATABLE_READ:   cout << wxT("Repeatable Read");     break;
1510         case SQL_TXN_SERIALIZABLE:      cout << wxT("Serializable");        break;
1511 #ifdef ODBC_V20
1512         case SQL_TXN_VERSIONING:        cout << wxT("Versioning");          break;
1513 #endif
1514     }
1515     cout << endl;
1516 
1517     cout << wxT("Transaction Isolation Options: ");
1518     if (dbInf.txnIsolationOptions & SQL_TXN_READ_UNCOMMITTED)
1519         cout << wxT("Read Uncommitted, ");
1520     if (dbInf.txnIsolationOptions & SQL_TXN_READ_COMMITTED)
1521         cout << wxT("Read Committed, ");
1522     if (dbInf.txnIsolationOptions & SQL_TXN_REPEATABLE_READ)
1523         cout << wxT("Repeatable Read, ");
1524     if (dbInf.txnIsolationOptions & SQL_TXN_SERIALIZABLE)
1525         cout << wxT("Serializable, ");
1526 #ifdef ODBC_V20
1527     if (dbInf.txnIsolationOptions & SQL_TXN_VERSIONING)
1528         cout << wxT("Versioning");
1529 #endif
1530     cout << endl;
1531 
1532     cout << wxT("Fetch Directions Supported:") << endl << wxT("   ");
1533     if (dbInf.fetchDirections & SQL_FD_FETCH_NEXT)
1534         cout << wxT("Next, ");
1535     if (dbInf.fetchDirections & SQL_FD_FETCH_PRIOR)
1536         cout << wxT("Prev, ");
1537     if (dbInf.fetchDirections & SQL_FD_FETCH_FIRST)
1538         cout << wxT("First, ");
1539     if (dbInf.fetchDirections & SQL_FD_FETCH_LAST)
1540         cout << wxT("Last, ");
1541     if (dbInf.fetchDirections & SQL_FD_FETCH_ABSOLUTE)
1542         cout << wxT("Absolute, ");
1543     if (dbInf.fetchDirections & SQL_FD_FETCH_RELATIVE)
1544         cout << wxT("Relative, ");
1545 #ifdef ODBC_V20
1546     if (dbInf.fetchDirections & SQL_FD_FETCH_RESUME)
1547         cout << wxT("Resume, ");
1548 #endif
1549     if (dbInf.fetchDirections & SQL_FD_FETCH_BOOKMARK)
1550         cout << wxT("Bookmark");
1551     cout << endl;
1552 
1553     cout << wxT("Lock Types Supported (SQLSetPos): ");
1554     if (dbInf.lockTypes & SQL_LCK_NO_CHANGE)
1555         cout << wxT("No Change, ");
1556     if (dbInf.lockTypes & SQL_LCK_EXCLUSIVE)
1557         cout << wxT("Exclusive, ");
1558     if (dbInf.lockTypes & SQL_LCK_UNLOCK)
1559         cout << wxT("UnLock");
1560     cout << endl;
1561 
1562     cout << wxT("Position Operations Supported (SQLSetPos): ");
1563     if (dbInf.posOperations & SQL_POS_POSITION)
1564         cout << wxT("Position, ");
1565     if (dbInf.posOperations & SQL_POS_REFRESH)
1566         cout << wxT("Refresh, ");
1567     if (dbInf.posOperations & SQL_POS_UPDATE)
1568         cout << wxT("Upd, "));
1569     if (dbInf.posOperations & SQL_POS_DELETE)
1570         cout << wxT("Del, ");
1571     if (dbInf.posOperations & SQL_POS_ADD)
1572         cout << wxT("Add");
1573     cout << endl;
1574 
1575     cout << wxT("Positioned Statements Supported: ");
1576     if (dbInf.posStmts & SQL_PS_POSITIONED_DELETE)
1577         cout << wxT("Pos delete, ");
1578     if (dbInf.posStmts & SQL_PS_POSITIONED_UPDATE)
1579         cout << wxT("Pos update, ");
1580     if (dbInf.posStmts & SQL_PS_SELECT_FOR_UPDATE)
1581         cout << wxT("Select for update");
1582     cout << endl;
1583 
1584     cout << wxT("Scroll Concurrency: ");
1585     if (dbInf.scrollConcurrency & SQL_SCCO_READ_ONLY)
1586         cout << wxT("Read Only, ");
1587     if (dbInf.scrollConcurrency & SQL_SCCO_LOCK)
1588         cout << wxT("Lock, ");
1589     if (dbInf.scrollConcurrency & SQL_SCCO_OPT_ROWVER)
1590         cout << wxT("Opt. Rowver, ");
1591     if (dbInf.scrollConcurrency & SQL_SCCO_OPT_VALUES)
1592         cout << wxT("Opt. Values");
1593     cout << endl;
1594 
1595     cout << wxT("Scroll Options: ");
1596     if (dbInf.scrollOptions & SQL_SO_FORWARD_ONLY)
1597         cout << wxT("Fwd Only, ");
1598     if (dbInf.scrollOptions & SQL_SO_STATIC)
1599         cout << wxT("Static, ");
1600     if (dbInf.scrollOptions & SQL_SO_KEYSET_DRIVEN)
1601         cout << wxT("Keyset Driven, ");
1602     if (dbInf.scrollOptions & SQL_SO_DYNAMIC)
1603         cout << wxT("Dynamic, ");
1604     if (dbInf.scrollOptions & SQL_SO_MIXED)
1605         cout << wxT("Mixed");
1606     cout << endl;
1607 
1608     cout << wxT("Static Sensitivity: ");
1609     if (dbInf.staticSensitivity & SQL_SS_ADDITIONS)
1610         cout << wxT("Additions, ");
1611     if (dbInf.staticSensitivity & SQL_SS_DELETIONS)
1612         cout << wxT("Deletions, ");
1613     if (dbInf.staticSensitivity & SQL_SS_UPDATES)
1614         cout << wxT("Updates");
1615     cout << endl;
1616 
1617     cout << wxT("Transaction Capable?: ");
1618     switch(dbInf.txnCapable)
1619     {
1620         case SQL_TC_NONE:          cout << wxT("No");            break;
1621         case SQL_TC_DML:           cout << wxT("DML Only");      break;
1622         case SQL_TC_DDL_COMMIT:    cout << wxT("DDL Commit");    break;
1623         case SQL_TC_DDL_IGNORE:    cout << wxT("DDL Ignore");    break;
1624         case SQL_TC_ALL:           cout << wxT("DDL & DML");     break;
1625     }
1626     cout << endl;
1627 
1628     cout << endl;
1629 #endif
1630 
1631     // Completed Successfully
1632     return true;
1633 
1634 } // wxDb::getDbInfo()
1635 
1636 
1637 /********** wxDb::getDataTypeInfo() **********/
1638 bool wxDb::getDataTypeInfo(SWORD fSqlType, wxDbSqlTypeInfo &structSQLTypeInfo)
1639 {
1640 /*
1641  * fSqlType will be something like SQL_VARCHAR.  This parameter determines
1642  * the data type inf. is gathered for.
1643  *
1644  * wxDbSqlTypeInfo is a structure that is filled in with data type information,
1645  */
1646     RETCODE retcode;
1647     SQLLEN  cbRet;
1648 
1649     // Get information about the data type specified
1650     if (SQLGetTypeInfo(hstmt, fSqlType) != SQL_SUCCESS)
1651         return(DispAllErrors(henv, hdbc, hstmt));
1652 
1653     // Fetch the record
1654     retcode = SQLFetch(hstmt);
1655     if (retcode != SQL_SUCCESS)
1656     {
1657 #ifdef DBDEBUG_CONSOLE
1658         if (retcode == SQL_NO_DATA_FOUND)
1659             cout << wxT("SQL_NO_DATA_FOUND fetching information about data type.") << endl;
1660 #endif
1661         DispAllErrors(henv, hdbc, hstmt);
1662         SQLFreeStmt(hstmt, SQL_CLOSE);
1663         return false;
1664     }
1665 
1666     wxChar typeName[DB_TYPE_NAME_LEN+1];
1667 
1668     // Obtain columns from the record
1669     if (SQLGetData(hstmt, 1, SQL_C_WXCHAR, typeName, sizeof(typeName), &cbRet) != SQL_SUCCESS)
1670         return(DispAllErrors(henv, hdbc, hstmt));
1671 
1672     structSQLTypeInfo.TypeName = typeName;
1673 
1674     // BJO 20000503: no more needed with new GetColumns...
1675 #if  OLD_GETCOLUMNS
1676     // BJO 991209
1677     if (Dbms() == dbmsMY_SQL)
1678     {
1679         if (structSQLTypeInfo.TypeName == wxT("middleint"))
1680             structSQLTypeInfo.TypeName = wxT("mediumint");
1681         else if (structSQLTypeInfo.TypeName == wxT("middleint unsigned"))
1682             structSQLTypeInfo.TypeName = wxT("mediumint unsigned");
1683         else if (structSQLTypeInfo.TypeName == wxT("integer"))
1684             structSQLTypeInfo.TypeName = wxT("int");
1685         else if (structSQLTypeInfo.TypeName == wxT("integer unsigned"))
1686             structSQLTypeInfo.TypeName = wxT("int unsigned");
1687         else if (structSQLTypeInfo.TypeName == wxT("middleint"))
1688             structSQLTypeInfo.TypeName = wxT("mediumint");
1689         else if (structSQLTypeInfo.TypeName == wxT("varchar"))
1690             structSQLTypeInfo.TypeName = wxT("char");
1691     }
1692 
1693     // BJO 20000427 : OpenLink driver
1694     if (!wxStrncmp(dbInf.driverName, wxT("oplodbc"), 7) ||
1695         !wxStrncmp(dbInf.driverName, wxT("OLOD"), 4))
1696     {
1697         if (structSQLTypeInfo.TypeName == wxT("double precision"))
1698             structSQLTypeInfo.TypeName = wxT("real");
1699     }
1700 #endif
1701 
1702     if (SQLGetData(hstmt, 3, SQL_C_LONG, (UCHAR*) &structSQLTypeInfo.Precision, 0, &cbRet) != SQL_SUCCESS)
1703         return(DispAllErrors(henv, hdbc, hstmt));
1704     if (SQLGetData(hstmt, 8, SQL_C_SHORT, (UCHAR*) &structSQLTypeInfo.CaseSensitive, 0, &cbRet) != SQL_SUCCESS)
1705         return(DispAllErrors(henv, hdbc, hstmt));
1706 //    if (SQLGetData(hstmt, 14, SQL_C_SHORT, (UCHAR*) &structSQLTypeInfo.MinimumScale, 0, &cbRet) != SQL_SUCCESS)
1707 //        return(DispAllErrors(henv, hdbc, hstmt));
1708 
1709     if (SQLGetData(hstmt, 15, SQL_C_SHORT,(UCHAR*)  &structSQLTypeInfo.MaximumScale, 0, &cbRet) != SQL_SUCCESS)
1710         return(DispAllErrors(henv, hdbc, hstmt));
1711 
1712     if (structSQLTypeInfo.MaximumScale < 0)
1713         structSQLTypeInfo.MaximumScale = 0;
1714 
1715     // Close the statement handle which closes open cursors
1716     if (SQLFreeStmt(hstmt, SQL_CLOSE) != SQL_SUCCESS)
1717         return(DispAllErrors(henv, hdbc, hstmt));
1718 
1719     // Completed Successfully
1720     return true;
1721 
1722 } // wxDb::getDataTypeInfo()
1723 
1724 
1725 /********** wxDb::Close() **********/
1726 void wxDb::Close(void)
1727 {
1728     // Close the Sql Log file
1729     if (fpSqlLog)
1730     {
1731         fclose(fpSqlLog);
1732         fpSqlLog = 0;
1733     }
1734 
1735     // Free statement handle
1736     if (dbIsOpen)
1737     {
1738         if (SQLFreeStmt(hstmt, SQL_DROP) != SQL_SUCCESS)
1739             DispAllErrors(henv, hdbc);
1740     }
1741 
1742     // Disconnect from the datasource
1743     if (SQLDisconnect(hdbc) != SQL_SUCCESS)
1744         DispAllErrors(henv, hdbc);
1745 
1746     // Free the connection to the datasource
1747     if (SQLFreeConnect(hdbc) != SQL_SUCCESS)
1748         DispAllErrors(henv, hdbc);
1749 
1750     // There should be zero Ctable objects still connected to this db object
1751     wxASSERT(nTables == 0);
1752 
1753 #ifdef __WXDEBUG__
1754     {
1755 #if wxUSE_THREADS
1756         wxCriticalSectionLocker lock(csTablesInUse);
1757 #endif // wxUSE_THREADS
1758         wxTablesInUse *tiu;
1759         wxList::compatibility_iterator pNode;
1760         pNode = TablesInUse.GetFirst();
1761         wxString s,s2;
1762         while (pNode)
1763         {
1764             tiu = (wxTablesInUse *)pNode->GetData();
1765             if (tiu->pDb == this)
1766             {
1767                 s.Printf(wxT("(%-20s)     tableID:[%6lu]     pDb:[%p]"),
1768                         tiu->tableName, tiu->tableID, wx_static_cast(void*, tiu->pDb));
1769                 s2.Printf(wxT("Orphaned table found using pDb:[%p]"), wx_static_cast(void*, this));
1770                 wxLogDebug(s.c_str(),s2.c_str());
1771             }
1772             pNode = pNode->GetNext();
1773         }
1774     }
1775 #endif
1776 
1777     // Copy the error messages to a global variable
1778     int i;
1779     for (i = 0; i < DB_MAX_ERROR_HISTORY; i++)
1780         wxStrcpy(DBerrorList[i], errorList[i]);
1781 
1782     dbmsType = dbmsUNIDENTIFIED;
1783     dbIsOpen = false;
1784 
1785 } // wxDb::Close()
1786 
1787 
1788 /********** wxDb::CommitTrans() **********/
1789 bool wxDb::CommitTrans(void)
1790 {
1791     if (this)
1792     {
1793         // Commit the transaction
1794         if (SQLTransact(henv, hdbc, SQL_COMMIT) != SQL_SUCCESS)
1795             return(DispAllErrors(henv, hdbc));
1796     }
1797 
1798     // Completed successfully
1799     return true;
1800 
1801 } // wxDb::CommitTrans()
1802 
1803 
1804 /********** wxDb::RollbackTrans() **********/
1805 bool wxDb::RollbackTrans(void)
1806 {
1807     // Rollback the transaction
1808     if (SQLTransact(henv, hdbc, SQL_ROLLBACK) != SQL_SUCCESS)
1809         return(DispAllErrors(henv, hdbc));
1810 
1811     // Completed successfully
1812     return true;
1813 
1814 } // wxDb::RollbackTrans()
1815 
1816 
1817 /********** wxDb::DispAllErrors() **********/
1818 bool wxDb::DispAllErrors(HENV aHenv, HDBC aHdbc, HSTMT aHstmt)
1819 /*
1820  * This function is called internally whenever an error condition prevents the user's
1821  * request from being executed.  This function will query the datasource as to the
1822  * actual error(s) that just occurred on the previous request of the datasource.
1823  *
1824  * The function will retrieve each error condition from the datasource and
1825  * Printf the codes/text values into a string which it then logs via logError().
1826  * If in DBDEBUG_CONSOLE mode, the constructed string will be displayed in the console
1827  * window and program execution will be paused until the user presses a key.
1828  *
1829  * This function always returns false, so that functions which call this function
1830  * can have a line like "return (DispAllErrors(henv, hdbc));" to indicate the failure
1831  * of the user's request, so that the calling code can then process the error message log.
1832  */
1833 {
1834     wxString odbcErrMsg;
1835 
1836    while (SQLError(aHenv, aHdbc, aHstmt, (SQLTCHAR FAR *) sqlState, &nativeError, (SQLTCHAR FAR *) errorMsg, SQL_MAX_MESSAGE_LENGTH - 1, &cbErrorMsg) == SQL_SUCCESS)
1837      {
1838         odbcErrMsg.Printf(wxT("SQL State = %s\nNative Error Code = %li\nError Message = %s\n"),
1839                           sqlState, (long)nativeError, errorMsg);
1840         logError(odbcErrMsg, sqlState);
1841         if (!silent)
1842         {
1843 #ifdef DBDEBUG_CONSOLE
1844             // When run in console mode, use standard out to display errors.
1845             cout << odbcErrMsg.c_str() << endl;
1846             cout << wxT("Press any key to continue...") << endl;
1847             getchar();
1848 #endif
1849 
1850 #ifdef __WXDEBUG__
1851             wxLogDebug(odbcErrMsg,wxT("ODBC DEBUG MESSAGE from DispAllErrors()"));
1852 #endif
1853         }
1854     }
1855 
1856     return false;  // This function always returns false.
1857 
1858 } // wxDb::DispAllErrors()
1859 
1860 
1861 /********** wxDb::GetNextError() **********/
1862 bool wxDb::GetNextError(HENV aHenv, HDBC aHdbc, HSTMT aHstmt)
1863 {
1864    if (SQLError(aHenv, aHdbc, aHstmt, (SQLTCHAR FAR *) sqlState, &nativeError, (SQLTCHAR FAR *) errorMsg, SQL_MAX_MESSAGE_LENGTH - 1, &cbErrorMsg) == SQL_SUCCESS)
1865      return true;
1866     else
1867         return false;
1868 
1869 } // wxDb::GetNextError()
1870 
1871 
1872 /********** wxDb::DispNextError() **********/
1873 void wxDb::DispNextError(void)
1874 {
1875     wxString odbcErrMsg;
1876 
1877     odbcErrMsg.Printf(wxT("SQL State = %s\nNative Error Code = %li\nError Message = %s\n"),
1878                       sqlState, (long)nativeError, errorMsg);
1879     logError(odbcErrMsg, sqlState);
1880 
1881     if (silent)
1882         return;
1883 
1884 #ifdef DBDEBUG_CONSOLE
1885     // When run in console mode, use standard out to display errors.
1886     cout << odbcErrMsg.c_str() << endl;
1887     cout << wxT("Press any key to continue...")  << endl;
1888     getchar();
1889 #endif
1890 
1891 #ifdef __WXDEBUG__
1892     wxLogDebug(odbcErrMsg,wxT("ODBC DEBUG MESSAGE"));
1893 #endif  // __WXDEBUG__
1894 
1895 } // wxDb::DispNextError()
1896 
1897 
1898 /********** wxDb::logError() **********/
1899 void wxDb::logError(const wxString &errMsg, const wxString &SQLState)
1900 {
1901     wxASSERT(errMsg.length());
1902 
1903     static int pLast = -1;
1904     int dbStatus;
1905 
1906     if (++pLast == DB_MAX_ERROR_HISTORY)
1907     {
1908         int i;
1909         for (i = 0; i < DB_MAX_ERROR_HISTORY-1; i++)
1910             wxStrcpy(errorList[i], errorList[i+1]);
1911         pLast--;
1912     }
1913 
1914     wxStrncpy(errorList[pLast], errMsg, DB_MAX_ERROR_MSG_LEN);
1915     errorList[pLast][DB_MAX_ERROR_MSG_LEN-1] = 0;
1916 
1917     if (SQLState.length())
1918         if ((dbStatus = TranslateSqlState(SQLState)) != DB_ERR_FUNCTION_SEQUENCE_ERROR)
1919             DB_STATUS = dbStatus;
1920 
1921     // Add the errmsg to the sql log
1922     WriteSqlLog(errMsg);
1923 
1924 }  // wxDb::logError()
1925 
1926 
1927 /**********wxDb::TranslateSqlState()  **********/
1928 int wxDb::TranslateSqlState(const wxString &SQLState)
1929 {
1930     if (!wxStrcmp(SQLState, wxT("01000")))
1931         return(DB_ERR_GENERAL_WARNING);
1932     if (!wxStrcmp(SQLState, wxT("01002")))
1933         return(DB_ERR_DISCONNECT_ERROR);
1934     if (!wxStrcmp(SQLState, wxT("01004")))
1935         return(DB_ERR_DATA_TRUNCATED);
1936     if (!wxStrcmp(SQLState, wxT("01006")))
1937         return(DB_ERR_PRIV_NOT_REVOKED);
1938     if (!wxStrcmp(SQLState, wxT("01S00")))
1939         return(DB_ERR_INVALID_CONN_STR_ATTR);
1940     if (!wxStrcmp(SQLState, wxT("01S01")))
1941         return(DB_ERR_ERROR_IN_ROW);
1942     if (!wxStrcmp(SQLState, wxT("01S02")))
1943         return(DB_ERR_OPTION_VALUE_CHANGED);
1944     if (!wxStrcmp(SQLState, wxT("01S03")))
1945         return(DB_ERR_NO_ROWS_UPD_OR_DEL);
1946     if (!wxStrcmp(SQLState, wxT("01S04")))
1947         return(DB_ERR_MULTI_ROWS_UPD_OR_DEL);
1948     if (!wxStrcmp(SQLState, wxT("07001")))
1949         return(DB_ERR_WRONG_NO_OF_PARAMS);
1950     if (!wxStrcmp(SQLState, wxT("07006")))
1951         return(DB_ERR_DATA_TYPE_ATTR_VIOL);
1952     if (!wxStrcmp(SQLState, wxT("08001")))
1953         return(DB_ERR_UNABLE_TO_CONNECT);
1954     if (!wxStrcmp(SQLState, wxT("08002")))
1955         return(DB_ERR_CONNECTION_IN_USE);
1956     if (!wxStrcmp(SQLState, wxT("08003")))
1957         return(DB_ERR_CONNECTION_NOT_OPEN);
1958     if (!wxStrcmp(SQLState, wxT("08004")))
1959         return(DB_ERR_REJECTED_CONNECTION);
1960     if (!wxStrcmp(SQLState, wxT("08007")))
1961         return(DB_ERR_CONN_FAIL_IN_TRANS);
1962     if (!wxStrcmp(SQLState, wxT("08S01")))
1963         return(DB_ERR_COMM_LINK_FAILURE);
1964     if (!wxStrcmp(SQLState, wxT("21S01")))
1965         return(DB_ERR_INSERT_VALUE_LIST_MISMATCH);
1966     if (!wxStrcmp(SQLState, wxT("21S02")))
1967         return(DB_ERR_DERIVED_TABLE_MISMATCH);
1968     if (!wxStrcmp(SQLState, wxT("22001")))
1969         return(DB_ERR_STRING_RIGHT_TRUNC);
1970     if (!wxStrcmp(SQLState, wxT("22003")))
1971         return(DB_ERR_NUMERIC_VALUE_OUT_OF_RNG);
1972     if (!wxStrcmp(SQLState, wxT("22005")))
1973         return(DB_ERR_ERROR_IN_ASSIGNMENT);
1974     if (!wxStrcmp(SQLState, wxT("22008")))
1975         return(DB_ERR_DATETIME_FLD_OVERFLOW);
1976     if (!wxStrcmp(SQLState, wxT("22012")))
1977         return(DB_ERR_DIVIDE_BY_ZERO);
1978     if (!wxStrcmp(SQLState, wxT("22026")))
1979         return(DB_ERR_STR_DATA_LENGTH_MISMATCH);
1980     if (!wxStrcmp(SQLState, wxT("23000")))
1981         return(DB_ERR_INTEGRITY_CONSTRAINT_VIOL);
1982     if (!wxStrcmp(SQLState, wxT("24000")))
1983         return(DB_ERR_INVALID_CURSOR_STATE);
1984     if (!wxStrcmp(SQLState, wxT("25000")))
1985         return(DB_ERR_INVALID_TRANS_STATE);
1986     if (!wxStrcmp(SQLState, wxT("28000")))
1987         return(DB_ERR_INVALID_AUTH_SPEC);
1988     if (!wxStrcmp(SQLState, wxT("34000")))
1989         return(DB_ERR_INVALID_CURSOR_NAME);
1990     if (!wxStrcmp(SQLState, wxT("37000")))
1991         return(DB_ERR_SYNTAX_ERROR_OR_ACCESS_VIOL);
1992     if (!wxStrcmp(SQLState, wxT("3C000")))
1993         return(DB_ERR_DUPLICATE_CURSOR_NAME);
1994     if (!wxStrcmp(SQLState, wxT("40001")))
1995         return(DB_ERR_SERIALIZATION_FAILURE);
1996     if (!wxStrcmp(SQLState, wxT("42000")))
1997         return(DB_ERR_SYNTAX_ERROR_OR_ACCESS_VIOL2);
1998     if (!wxStrcmp(SQLState, wxT("70100")))
1999         return(DB_ERR_OPERATION_ABORTED);
2000     if (!wxStrcmp(SQLState, wxT("IM001")))
2001         return(DB_ERR_UNSUPPORTED_FUNCTION);
2002     if (!wxStrcmp(SQLState, wxT("IM002")))
2003         return(DB_ERR_NO_DATA_SOURCE);
2004     if (!wxStrcmp(SQLState, wxT("IM003")))
2005         return(DB_ERR_DRIVER_LOAD_ERROR);
2006     if (!wxStrcmp(SQLState, wxT("IM004")))
2007         return(DB_ERR_SQLALLOCENV_FAILED);
2008     if (!wxStrcmp(SQLState, wxT("IM005")))
2009         return(DB_ERR_SQLALLOCCONNECT_FAILED);
2010     if (!wxStrcmp(SQLState, wxT("IM006")))
2011         return(DB_ERR_SQLSETCONNECTOPTION_FAILED);
2012     if (!wxStrcmp(SQLState, wxT("IM007")))
2013         return(DB_ERR_NO_DATA_SOURCE_DLG_PROHIB);
2014     if (!wxStrcmp(SQLState, wxT("IM008")))
2015         return(DB_ERR_DIALOG_FAILED);
2016     if (!wxStrcmp(SQLState, wxT("IM009")))
2017         return(DB_ERR_UNABLE_TO_LOAD_TRANSLATION_DLL);
2018     if (!wxStrcmp(SQLState, wxT("IM010")))
2019         return(DB_ERR_DATA_SOURCE_NAME_TOO_LONG);
2020     if (!wxStrcmp(SQLState, wxT("IM011")))
2021         return(DB_ERR_DRIVER_NAME_TOO_LONG);
2022     if (!wxStrcmp(SQLState, wxT("IM012")))
2023         return(DB_ERR_DRIVER_KEYWORD_SYNTAX_ERROR);
2024     if (!wxStrcmp(SQLState, wxT("IM013")))
2025         return(DB_ERR_TRACE_FILE_ERROR);
2026     if (!wxStrcmp(SQLState, wxT("S0001")))
2027         return(DB_ERR_TABLE_OR_VIEW_ALREADY_EXISTS);
2028     if (!wxStrcmp(SQLState, wxT("S0002")))
2029         return(DB_ERR_TABLE_NOT_FOUND);
2030     if (!wxStrcmp(SQLState, wxT("S0011")))
2031         return(DB_ERR_INDEX_ALREADY_EXISTS);
2032     if (!wxStrcmp(SQLState, wxT("S0012")))
2033         return(DB_ERR_INDEX_NOT_FOUND);
2034     if (!wxStrcmp(SQLState, wxT("S0021")))
2035         return(DB_ERR_COLUMN_ALREADY_EXISTS);
2036     if (!wxStrcmp(SQLState, wxT("S0022")))
2037         return(DB_ERR_COLUMN_NOT_FOUND);
2038     if (!wxStrcmp(SQLState, wxT("S0023")))
2039         return(DB_ERR_NO_DEFAULT_FOR_COLUMN);
2040     if (!wxStrcmp(SQLState, wxT("S1000")))
2041         return(DB_ERR_GENERAL_ERROR);
2042     if (!wxStrcmp(SQLState, wxT("S1001")))
2043         return(DB_ERR_MEMORY_ALLOCATION_FAILURE);
2044     if (!wxStrcmp(SQLState, wxT("S1002")))
2045         return(DB_ERR_INVALID_COLUMN_NUMBER);
2046     if (!wxStrcmp(SQLState, wxT("S1003")))
2047         return(DB_ERR_PROGRAM_TYPE_OUT_OF_RANGE);
2048     if (!wxStrcmp(SQLState, wxT("S1004")))
2049         return(DB_ERR_SQL_DATA_TYPE_OUT_OF_RANGE);
2050     if (!wxStrcmp(SQLState, wxT("S1008")))
2051         return(DB_ERR_OPERATION_CANCELLED);
2052     if (!wxStrcmp(SQLState, wxT("S1009")))
2053         return(DB_ERR_INVALID_ARGUMENT_VALUE);
2054     if (!wxStrcmp(SQLState, wxT("S1010")))
2055         return(DB_ERR_FUNCTION_SEQUENCE_ERROR);
2056     if (!wxStrcmp(SQLState, wxT("S1011")))
2057         return(DB_ERR_OPERATION_INVALID_AT_THIS_TIME);
2058     if (!wxStrcmp(SQLState, wxT("S1012")))
2059         return(DB_ERR_INVALID_TRANS_OPERATION_CODE);
2060     if (!wxStrcmp(SQLState, wxT("S1015")))
2061         return(DB_ERR_NO_CURSOR_NAME_AVAIL);
2062     if (!wxStrcmp(SQLState, wxT("S1090")))
2063         return(DB_ERR_INVALID_STR_OR_BUF_LEN);
2064     if (!wxStrcmp(SQLState, wxT("S1091")))
2065         return(DB_ERR_DESCRIPTOR_TYPE_OUT_OF_RANGE);
2066     if (!wxStrcmp(SQLState, wxT("S1092")))
2067         return(DB_ERR_OPTION_TYPE_OUT_OF_RANGE);
2068     if (!wxStrcmp(SQLState, wxT("S1093")))
2069         return(DB_ERR_INVALID_PARAM_NO);
2070     if (!wxStrcmp(SQLState, wxT("S1094")))
2071         return(DB_ERR_INVALID_SCALE_VALUE);
2072     if (!wxStrcmp(SQLState, wxT("S1095")))
2073         return(DB_ERR_FUNCTION_TYPE_OUT_OF_RANGE);
2074     if (!wxStrcmp(SQLState, wxT("S1096")))
2075         return(DB_ERR_INF_TYPE_OUT_OF_RANGE);
2076     if (!wxStrcmp(SQLState, wxT("S1097")))
2077         return(DB_ERR_COLUMN_TYPE_OUT_OF_RANGE);
2078     if (!wxStrcmp(SQLState, wxT("S1098")))
2079         return(DB_ERR_SCOPE_TYPE_OUT_OF_RANGE);
2080     if (!wxStrcmp(SQLState, wxT("S1099")))
2081         return(DB_ERR_NULLABLE_TYPE_OUT_OF_RANGE);
2082     if (!wxStrcmp(SQLState, wxT("S1100")))
2083         return(DB_ERR_UNIQUENESS_OPTION_TYPE_OUT_OF_RANGE);
2084     if (!wxStrcmp(SQLState, wxT("S1101")))
2085         return(DB_ERR_ACCURACY_OPTION_TYPE_OUT_OF_RANGE);
2086     if (!wxStrcmp(SQLState, wxT("S1103")))
2087         return(DB_ERR_DIRECTION_OPTION_OUT_OF_RANGE);
2088     if (!wxStrcmp(SQLState, wxT("S1104")))
2089         return(DB_ERR_INVALID_PRECISION_VALUE);
2090     if (!wxStrcmp(SQLState, wxT("S1105")))
2091         return(DB_ERR_INVALID_PARAM_TYPE);
2092     if (!wxStrcmp(SQLState, wxT("S1106")))
2093         return(DB_ERR_FETCH_TYPE_OUT_OF_RANGE);
2094     if (!wxStrcmp(SQLState, wxT("S1107")))
2095         return(DB_ERR_ROW_VALUE_OUT_OF_RANGE);
2096     if (!wxStrcmp(SQLState, wxT("S1108")))
2097         return(DB_ERR_CONCURRENCY_OPTION_OUT_OF_RANGE);
2098     if (!wxStrcmp(SQLState, wxT("S1109")))
2099         return(DB_ERR_INVALID_CURSOR_POSITION);
2100     if (!wxStrcmp(SQLState, wxT("S1110")))
2101         return(DB_ERR_INVALID_DRIVER_COMPLETION);
2102     if (!wxStrcmp(SQLState, wxT("S1111")))
2103         return(DB_ERR_INVALID_BOOKMARK_VALUE);
2104     if (!wxStrcmp(SQLState, wxT("S1C00")))
2105         return(DB_ERR_DRIVER_NOT_CAPABLE);
2106     if (!wxStrcmp(SQLState, wxT("S1T00")))
2107         return(DB_ERR_TIMEOUT_EXPIRED);
2108 
2109     // No match
2110     return(0);
2111 
2112 }  // wxDb::TranslateSqlState()
2113 
2114 
2115 /**********  wxDb::Grant() **********/
2116 bool wxDb::Grant(int privileges, const wxString &tableName, const wxString &userList)
2117 {
2118     wxString sqlStmt;
2119 
2120     // Build the grant statement
2121     sqlStmt  = wxT("GRANT ");
2122     if (privileges == DB_GRANT_ALL)
2123         sqlStmt += wxT("ALL");
2124     else
2125     {
2126         int c = 0;
2127         if (privileges & DB_GRANT_SELECT)
2128         {
2129             sqlStmt += wxT("SELECT");
2130             c++;
2131         }
2132         if (privileges & DB_GRANT_INSERT)
2133         {
2134             if (c++)
2135                 sqlStmt += wxT(", ");
2136             sqlStmt += wxT("INSERT");
2137         }
2138         if (privileges & DB_GRANT_UPDATE)
2139         {
2140             if (c++)
2141                 sqlStmt += wxT(", ");
2142             sqlStmt += wxT("UPDATE");
2143         }
2144         if (privileges & DB_GRANT_DELETE)
2145         {
2146             if (c++)
2147                 sqlStmt += wxT(", ");
2148             sqlStmt += wxT("DELETE");
2149         }
2150     }
2151 
2152     sqlStmt += wxT(" ON ");
2153     sqlStmt += SQLTableName(tableName);
2154     sqlStmt += wxT(" TO ");
2155     sqlStmt += userList;
2156 
2157 #ifdef DBDEBUG_CONSOLE
2158     cout << endl << sqlStmt.c_str() << endl;
2159 #endif
2160 
2161     WriteSqlLog(sqlStmt);
2162 
2163     return(ExecSql(sqlStmt));
2164 
2165 }  // wxDb::Grant()
2166 
2167 
2168 /********** wxDb::CreateView() **********/
2169 bool wxDb::CreateView(const wxString &viewName, const wxString &colList,
2170                       const wxString &pSqlStmt, bool attemptDrop)
2171 {
2172     wxString sqlStmt;
2173 
2174     // Drop the view first
2175     if (attemptDrop && !DropView(viewName))
2176         return false;
2177 
2178     // Build the create view statement
2179     sqlStmt  = wxT("CREATE VIEW ");
2180     sqlStmt += viewName;
2181 
2182     if (colList.length())
2183     {
2184         sqlStmt += wxT(" (");
2185         sqlStmt += colList;
2186         sqlStmt += wxT(")");
2187     }
2188 
2189     sqlStmt += wxT(" AS ");
2190     sqlStmt += pSqlStmt;
2191 
2192     WriteSqlLog(sqlStmt);
2193 
2194 #ifdef DBDEBUG_CONSOLE
2195     cout << sqlStmt.c_str() << endl;
2196 #endif
2197 
2198     return(ExecSql(sqlStmt));
2199 
2200 }  // wxDb::CreateView()
2201 
2202 
2203 /********** wxDb::DropView()  **********/
2204 bool wxDb::DropView(const wxString &viewName)
2205 {
2206 /*
2207  * NOTE: This function returns true if the View does not exist, but
2208  *       only for identified databases.  Code will need to be added
2209  *            below for any other databases when those databases are defined
2210  *       to handle this situation consistently
2211  */
2212     wxString sqlStmt;
2213 
2214     sqlStmt.Printf(wxT("DROP VIEW %s"), viewName.c_str());
2215 
2216     WriteSqlLog(sqlStmt);
2217 
2218 #ifdef DBDEBUG_CONSOLE
2219     cout << endl << sqlStmt.c_str() << endl;
2220 #endif
2221 
2222     if (SQLExecDirect(hstmt, (SQLTCHAR FAR *) sqlStmt.c_str(), SQL_NTS) != SQL_SUCCESS)
2223     {
2224         // Check for "Base table not found" error and ignore
2225         GetNextError(henv, hdbc, hstmt);
2226         if (wxStrcmp(sqlState,wxT("S0002")))  // "Base table not found"
2227         {
2228             // Check for product specific error codes
2229             if (!((Dbms() == dbmsSYBASE_ASA    && !wxStrcmp(sqlState,wxT("42000")))))  // 5.x (and lower?)
2230             {
2231                 DispNextError();
2232                 DispAllErrors(henv, hdbc, hstmt);
2233                 RollbackTrans();
2234                 return false;
2235             }
2236         }
2237     }
2238 
2239     // Commit the transaction
2240     if (!CommitTrans())
2241         return false;
2242 
2243     return true;
2244 
2245 }  // wxDb::DropView()
2246 
2247 
2248 /********** wxDb::ExecSql()  **********/
2249 bool wxDb::ExecSql(const wxString &pSqlStmt)
2250 {
2251     RETCODE retcode;
2252 
2253     SQLFreeStmt(hstmt, SQL_CLOSE);
2254 
2255     retcode = SQLExecDirect(hstmt, (SQLTCHAR FAR *) pSqlStmt.c_str(), SQL_NTS);
2256     if (retcode == SQL_SUCCESS ||
2257         (Dbms() == dbmsDB2 && (retcode == SQL_SUCCESS_WITH_INFO || retcode == SQL_NO_DATA_FOUND)))
2258     {
2259         return true;
2260     }
2261     else
2262     {
2263         DispAllErrors(henv, hdbc, hstmt);
2264         return false;
2265     }
2266 
2267 }  // wxDb::ExecSql()
2268 
2269 
2270 /********** wxDb::ExecSql() with column info **********/
2271 bool wxDb::ExecSql(const wxString &pSqlStmt, wxDbColInf** columns, short& numcols)
2272 {
2273     //execute the statement first
2274     if (!ExecSql(pSqlStmt))
2275         return false;
2276 
2277     SWORD noCols;
2278     if (SQLNumResultCols(hstmt, &noCols) != SQL_SUCCESS)
2279     {
2280         DispAllErrors(henv, hdbc, hstmt);
2281         return false;
2282     }
2283 
2284     if (noCols == 0)
2285         return false;
2286     else
2287         numcols = noCols;
2288 
2289     //  Get column information
2290     short colNum;
2291     wxChar name[DB_MAX_COLUMN_NAME_LEN+1];
2292     SWORD Sword;
2293     SQLLEN Sqllen;
2294     wxDbColInf* pColInf = new wxDbColInf[noCols];
2295 
2296     // Fill in column information (name, datatype)
2297     for (colNum = 0; colNum < noCols; colNum++)
2298     {
2299         if (SQLColAttributes(hstmt, (UWORD)(colNum+1), SQL_COLUMN_NAME,
2300             name, sizeof(name),
2301             &Sword, &Sqllen) != SQL_SUCCESS)
2302         {
2303             DispAllErrors(henv, hdbc, hstmt);
2304             delete[] pColInf;
2305             return false;
2306         }
2307 
2308         wxStrncpy(pColInf[colNum].colName, name, DB_MAX_COLUMN_NAME_LEN);
2309         pColInf[colNum].colName[DB_MAX_COLUMN_NAME_LEN] = 0;  // Prevent buffer overrun
2310 
2311         if (SQLColAttributes(hstmt, (UWORD)(colNum+1), SQL_COLUMN_TYPE,
2312             NULL, 0, &Sword, &Sqllen) != SQL_SUCCESS)
2313         {
2314             DispAllErrors(henv, hdbc, hstmt);
2315             delete[] pColInf;
2316             return false;
2317         }
2318 
2319         switch (Sqllen)
2320         {
2321 #if wxUSE_UNICODE
2322     #if defined(SQL_WCHAR)
2323             case SQL_WCHAR:
2324     #endif
2325     #if defined(SQL_WVARCHAR)
2326             case SQL_WVARCHAR:
2327     #endif
2328 #endif
2329             case SQL_VARCHAR:
2330             case SQL_CHAR:
2331                 pColInf[colNum].dbDataType = DB_DATA_TYPE_VARCHAR;
2332                 break;
2333             case SQL_LONGVARCHAR:
2334                 pColInf[colNum].dbDataType = DB_DATA_TYPE_MEMO;
2335                 break;
2336             case SQL_TINYINT:
2337             case SQL_SMALLINT:
2338             case SQL_INTEGER:
2339             case SQL_BIT:
2340                 pColInf[colNum].dbDataType = DB_DATA_TYPE_INTEGER;
2341                 break;
2342             case SQL_DOUBLE:
2343             case SQL_DECIMAL:
2344             case SQL_NUMERIC:
2345             case SQL_FLOAT:
2346             case SQL_REAL:
2347                 pColInf[colNum].dbDataType = DB_DATA_TYPE_FLOAT;
2348                 break;
2349             case SQL_DATE:
2350             case SQL_TIMESTAMP:
2351                 pColInf[colNum].dbDataType = DB_DATA_TYPE_DATE;
2352                 break;
2353             case SQL_BINARY:
2354                 pColInf[colNum].dbDataType = DB_DATA_TYPE_BLOB;
2355                 break;
2356 #ifdef __WXDEBUG__
2357             default:
2358                 wxString errMsg;
2359                 errMsg.Printf(wxT("SQL Data type %ld currently not supported by wxWidgets"), (long)Sqllen);
2360                 wxLogDebug(errMsg,wxT("ODBC DEBUG MESSAGE"));
2361 #endif
2362         }
2363     }
2364 
2365     *columns = pColInf;
2366     return true;
2367 }  // wxDb::ExecSql()
2368 
2369 /********** wxDb::GetNext()  **********/
2370 bool wxDb::GetNext(void)
2371 {
2372     if (SQLFetch(hstmt) == SQL_SUCCESS)
2373         return true;
2374     else
2375     {
2376         DispAllErrors(henv, hdbc, hstmt);
2377         return false;
2378     }
2379 
2380 }  // wxDb::GetNext()
2381 
2382 
2383 /********** wxDb::GetData()  **********/
2384 bool wxDb::GetData(UWORD colNo, SWORD cType, PTR pData, SDWORD maxLen, SQLLEN FAR *cbReturned)
2385 {
2386     wxASSERT(pData);
2387     wxASSERT(cbReturned);
2388 
2389     long bufferSize = maxLen;
2390 
2391     if (cType == SQL_C_WXCHAR)
2392         bufferSize = maxLen * sizeof(wxChar);
2393 
2394     if (SQLGetData(hstmt, colNo, cType, pData, bufferSize, cbReturned) == SQL_SUCCESS)
2395         return true;
2396     else
2397     {
2398         DispAllErrors(henv, hdbc, hstmt);
2399         return false;
2400     }
2401 
2402 }  // wxDb::GetData()
2403 
2404 
2405 /********** wxDb::GetKeyFields() **********/
2406 int wxDb::GetKeyFields(const wxString &tableName, wxDbColInf* colInf, UWORD noCols)
2407 {
2408     wxChar       szPkTable[DB_MAX_TABLE_NAME_LEN+1];  /* Primary key table name */
2409     wxChar       szFkTable[DB_MAX_TABLE_NAME_LEN+1];  /* Foreign key table name */
2410     SWORD        iKeySeq;
2411     wxChar       szPkCol[DB_MAX_COLUMN_NAME_LEN+1];   /* Primary key column     */
2412     wxChar       szFkCol[DB_MAX_COLUMN_NAME_LEN+1];   /* Foreign key column     */
2413     SQLRETURN    retcode;
2414     SQLLEN       cb;
2415     SWORD        i;
2416     wxString     tempStr;
2417     /*
2418      * -----------------------------------------------------------------------
2419      * -- 19991224 : mj10777 : Create                                   ------
2420      * --          : Three things are done and stored here :            ------
2421      * --          : 1) which Column(s) is/are Primary Key(s)           ------
2422      * --          : 2) which tables use this Key as a Foreign Key      ------
2423      * --          : 3) which columns are Foreign Key and the name      ------
2424      * --          :     of the Table where the Key is the Primary Key  -----
2425      * --          : Called from GetColumns(const wxString &tableName,  ------
2426      * --                           int *numCols,const wxChar *userID ) ------
2427      * -----------------------------------------------------------------------
2428      */
2429 
2430     /*---------------------------------------------------------------------*/
2431     /* Get the names of the columns in the primary key.                    */
2432     /*---------------------------------------------------------------------*/
2433     retcode = SQLPrimaryKeys(hstmt,
2434                              NULL, 0,                               /* Catalog name  */
2435                              NULL, 0,                               /* Schema name   */
2436                              (SQLTCHAR FAR *) tableName.c_str(), SQL_NTS); /* Table name    */
2437 
2438     /*---------------------------------------------------------------------*/
2439     /* Fetch and display the result set. This will be a list of the        */
2440     /* columns in the primary key of the tableName table.                  */
2441     /*---------------------------------------------------------------------*/
2442     while ((retcode == SQL_SUCCESS) || (retcode == SQL_SUCCESS_WITH_INFO))
2443     {
2444         retcode = SQLFetch(hstmt);
2445         if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
2446         {
2447             GetData( 4, SQL_C_WXCHAR,  szPkCol,    DB_MAX_COLUMN_NAME_LEN+1, &cb);
2448             GetData( 5, SQL_C_SSHORT, &iKeySeq,    0,                        &cb);
2449             //-------
2450             for (i=0;i<noCols;i++)                          // Find the Column name
2451                 if (!wxStrcmp(colInf[i].colName,szPkCol))   // We have found the Column
2452                     colInf[i].PkCol = iKeySeq;              // Which Primary Key is this (first, second usw.) ?
2453         }  // if
2454     }  // while
2455     SQLFreeStmt(hstmt, SQL_CLOSE);  /* Close the cursor (the hstmt is still allocated).      */
2456 
2457     /*---------------------------------------------------------------------*/
2458     /* Get all the foreign keys that refer to tableName primary key.       */
2459     /*---------------------------------------------------------------------*/
2460     retcode = SQLForeignKeys(hstmt,
2461                              NULL, 0,                            /* Primary catalog */
2462                              NULL, 0,                            /* Primary schema  */
2463                              (SQLTCHAR FAR *)tableName.c_str(), SQL_NTS,/* Primary table   */
2464                              NULL, 0,                            /* Foreign catalog */
2465                              NULL, 0,                            /* Foreign schema  */
2466                              NULL, 0);                           /* Foreign table   */
2467 
2468     /*---------------------------------------------------------------------*/
2469     /* Fetch and display the result set. This will be all of the foreign   */
2470     /* keys in other tables that refer to the tableName  primary key.      */
2471     /*---------------------------------------------------------------------*/
2472     tempStr.Empty();
2473     szPkCol[0] = 0;
2474     while ((retcode == SQL_SUCCESS) || (retcode == SQL_SUCCESS_WITH_INFO))
2475     {
2476         retcode = SQLFetch(hstmt);
2477         if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
2478         {
2479             GetData( 3, SQL_C_WXCHAR,  szPkTable,   DB_MAX_TABLE_NAME_LEN+1,   &cb);
2480             GetData( 4, SQL_C_WXCHAR,  szPkCol,     DB_MAX_COLUMN_NAME_LEN+1,  &cb);
2481             GetData( 5, SQL_C_SSHORT, &iKeySeq,     0,                         &cb);
2482             GetData( 7, SQL_C_WXCHAR,  szFkTable,   DB_MAX_TABLE_NAME_LEN+1,   &cb);
2483             GetData( 8, SQL_C_WXCHAR,  szFkCol,     DB_MAX_COLUMN_NAME_LEN+1,  &cb);
2484             tempStr << _T('[') << szFkTable << _T(']');  // [ ] in case there is a blank in the Table name
2485         }  // if
2486     }  // while
2487 
2488     tempStr.Trim();     // Get rid of any unneeded blanks
2489     if (!tempStr.empty())
2490     {
2491         for (i=0; i<noCols; i++)
2492         {   // Find the Column name
2493             if (!wxStrcmp(colInf[i].colName, szPkCol))           // We have found the Column, store the Information
2494             {
2495                 wxStrncpy(colInf[i].PkTableName, tempStr.c_str(), DB_MAX_TABLE_NAME_LEN);  // Name of the Tables where this Primary Key is used as a Foreign Key
2496                 colInf[i].PkTableName[DB_MAX_TABLE_NAME_LEN] = 0;  // Prevent buffer overrun
2497             }
2498         }
2499     }  // if
2500 
2501     SQLFreeStmt(hstmt, SQL_CLOSE);  /* Close the cursor (the hstmt is still allocated). */
2502 
2503     /*---------------------------------------------------------------------*/
2504     /* Get all the foreign keys in the tablename table.                    */
2505     /*---------------------------------------------------------------------*/
2506     retcode = SQLForeignKeys(hstmt,
2507                              NULL, 0,                             /* Primary catalog   */
2508                              NULL, 0,                             /* Primary schema    */
2509                              NULL, 0,                             /* Primary table     */
2510                              NULL, 0,                             /* Foreign catalog   */
2511                              NULL, 0,                             /* Foreign schema    */
2512                              (SQLTCHAR *)tableName.c_str(), SQL_NTS);/* Foreign table     */
2513 
2514     /*---------------------------------------------------------------------*/
2515     /*  Fetch and display the result set. This will be all of the          */
2516     /*  primary keys in other tables that are referred to by foreign       */
2517     /*  keys in the tableName table.                                       */
2518     /*---------------------------------------------------------------------*/
2519     while ((retcode == SQL_SUCCESS) || (retcode == SQL_SUCCESS_WITH_INFO))
2520     {
2521         retcode = SQLFetch(hstmt);
2522         if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
2523         {
2524             GetData( 3, SQL_C_WXCHAR,  szPkTable,   DB_MAX_TABLE_NAME_LEN+1,  &cb);
2525             GetData( 5, SQL_C_SSHORT, &iKeySeq,     0,                        &cb);
2526             GetData( 8, SQL_C_WXCHAR,  szFkCol,     DB_MAX_COLUMN_NAME_LEN+1, &cb);
2527             //-------
2528             for (i=0; i<noCols; i++)                            // Find the Column name
2529             {
2530                 if (!wxStrcmp(colInf[i].colName,szFkCol))       // We have found the (Foreign Key) Column
2531                 {
2532                     colInf[i].FkCol = iKeySeq;                  // Which Foreign Key is this (first, second usw.) ?
2533                     wxStrncpy(colInf[i].FkTableName, szFkTable, DB_MAX_TABLE_NAME_LEN);  // Name of the Table where this Foriegn is the Primary Key
2534                     colInf[i].FkTableName[DB_MAX_TABLE_NAME_LEN] = 0;  // Prevent buffer overrun
2535                 } // if
2536             }  // for
2537         }  // if
2538     }  // while
2539     SQLFreeStmt(hstmt, SQL_CLOSE);  /* Close the cursor (the hstmt is still allocated). */
2540 
2541     return TRUE;
2542 
2543 }  // wxDb::GetKeyFields()
2544 
2545 
2546 #if OLD_GETCOLUMNS
2547 /********** wxDb::GetColumns() **********/
2548 wxDbColInf *wxDb::GetColumns(wxChar *tableName[], const wxChar *userID)
2549 /*
2550  *        1) The last array element of the tableName[] argument must be zero (null).
2551  *            This is how the end of the array is detected.
2552  *        2) This function returns an array of wxDbColInf structures.  If no columns
2553  *            were found, or an error occurred, this pointer will be zero (null).  THE
2554  *            CALLING FUNCTION IS RESPONSIBLE FOR DELETING THE MEMORY RETURNED WHEN IT
2555  *            IS FINISHED WITH IT.  i.e.
2556  *
2557  *            wxDbColInf *colInf = pDb->GetColumns(tableList, userID);
2558  *            if (colInf)
2559  *            {
2560  *                // Use the column inf
2561  *                .......
2562  *                // Destroy the memory
2563  *                delete [] colInf;
2564  *            }
2565  *
2566  * userID is evaluated in the following manner:
2567  *        userID == NULL  ... UserID is ignored
2568  *        userID == ""    ... UserID set equal to 'this->uid'
2569  *        userID != ""    ... UserID set equal to 'userID'
2570  *
2571  * NOTE: ALL column bindings associated with this wxDb instance are unbound
2572  *       by this function.  This function should use its own wxDb instance
2573  *       to avoid undesired unbinding of columns.
2574  */
2575 {
2576     UWORD       noCols = 0;
2577     UWORD       colNo  = 0;
2578     wxDbColInf *colInf = 0;
2579 
2580     RETCODE  retcode;
2581     SQLLEN   cb;
2582 
2583     wxString TableName;
2584 
2585     wxString UserID;
2586     convertUserID(userID,UserID);
2587 
2588     // Pass 1 - Determine how many columns there are.
2589     // Pass 2 - Allocate the wxDbColInf array and fill in
2590     //                the array with the column information.
2591     int pass;
2592     for (pass = 1; pass <= 2; pass++)
2593     {
2594         if (pass == 2)
2595         {
2596             if (noCols == 0)  // Probably a bogus table name(s)
2597                 break;
2598             // Allocate n wxDbColInf objects to hold the column information
2599             colInf = new wxDbColInf[noCols+1];
2600             if (!colInf)
2601                 break;
2602             // Mark the end of the array
2603             wxStrcpy(colInf[noCols].tableName, wxEmptyString);
2604             wxStrcpy(colInf[noCols].colName, wxEmptyString);
2605             colInf[noCols].sqlDataType = 0;
2606         }
2607         // Loop through each table name
2608         int tbl;
2609         for (tbl = 0; tableName[tbl]; tbl++)
2610         {
2611             TableName = tableName[tbl];
2612             // Oracle and Interbase table names are uppercase only, so force
2613             // the name to uppercase just in case programmer forgot to do this
2614             if ((Dbms() == dbmsORACLE) ||
2615                 (Dbms() == dbmsFIREBIRD) ||
2616                 (Dbms() == dbmsINTERBASE))
2617                 TableName = TableName.Upper();
2618 
2619             SQLFreeStmt(hstmt, SQL_CLOSE);
2620 
2621             // MySQL, SQLServer, and Access cannot accept a user name when looking up column names, so we
2622             // use the call below that leaves out the user name
2623             if (!UserID.empty() &&
2624                 Dbms() != dbmsMY_SQL &&
2625                 Dbms() != dbmsACCESS &&
2626                 Dbms() != dbmsMS_SQL_SERVER)
2627             {
2628                 retcode = SQLColumns(hstmt,
2629                                      NULL, 0,                                // All qualifiers
2630                                      (SQLTCHAR *) UserID.c_str(), SQL_NTS,      // Owner
2631                                      (SQLTCHAR *) TableName.c_str(), SQL_NTS,
2632                                      NULL, 0);                               // All columns
2633             }
2634             else
2635             {
2636                 retcode = SQLColumns(hstmt,
2637                                      NULL, 0,                                // All qualifiers
2638                                      NULL, 0,                                // Owner
2639                                      (SQLTCHAR *) TableName.c_str(), SQL_NTS,
2640                                      NULL, 0);                               // All columns
2641             }
2642             if (retcode != SQL_SUCCESS)
2643             {  // Error occurred, abort
2644                 DispAllErrors(henv, hdbc, hstmt);
2645                 if (colInf)
2646                     delete [] colInf;
2647                 SQLFreeStmt(hstmt, SQL_CLOSE);
2648                 return(0);
2649             }
2650 
2651             while ((retcode = SQLFetch(hstmt)) == SQL_SUCCESS)
2652             {
2653                 if (pass == 1)  // First pass, just add up the number of columns
2654                     noCols++;
2655                 else  // Pass 2; Fill in the array of structures
2656                 {
2657                     if (colNo < noCols)  // Some extra error checking to prevent memory overwrites
2658                     {
2659                         // NOTE: Only the ODBC 1.x fields are retrieved
2660                         GetData( 1, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].catalog,      128+1,                    &cb);
2661                         GetData( 2, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].schema,       128+1,                    &cb);
2662                         GetData( 3, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].tableName,    DB_MAX_TABLE_NAME_LEN+1,  &cb);
2663                         GetData( 4, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].colName,      DB_MAX_COLUMN_NAME_LEN+1, &cb);
2664                         GetData( 5, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].sqlDataType,  0,                        &cb);
2665                         GetData( 6, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].typeName,     128+1,                    &cb);
2666                         GetData( 7, SQL_C_SLONG,  (UCHAR*) &colInf[colNo].columnLength, 0,                        &cb);
2667                         GetData( 8, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].bufferSize,   0,                        &cb);
2668                         GetData( 9, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].decimalDigits,0,                        &cb);
2669                         GetData(10, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].numPrecRadix, 0,                        &cb);
2670                         GetData(11, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].nullable,     0,                        &cb);
2671                         GetData(12, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].remarks,      254+1,                    &cb);
2672 
2673                         // Determine the wxDb data type that is used to represent the native data type of this data source
2674                         colInf[colNo].dbDataType = 0;
2675                         if (!wxStricmp(typeInfVarchar.TypeName,colInf[colNo].typeName))
2676                         {
2677 #ifdef _IODBC_
2678                             // IODBC does not return a correct columnLength, so we set
2679                             // columnLength = bufferSize if no column length was returned
2680                             // IODBC returns the columnLength in bufferSize. (bug)
2681                             if (colInf[colNo].columnLength < 1)
2682                             {
2683                                colInf[colNo].columnLength = colInf[colNo].bufferSize;
2684                             }
2685 #endif
2686                             colInf[colNo].dbDataType = DB_DATA_TYPE_VARCHAR;
2687                         }
2688                         else if (!wxStricmp(typeInfInteger.TypeName, colInf[colNo].typeName))
2689                             colInf[colNo].dbDataType = DB_DATA_TYPE_INTEGER;
2690                         else if (!wxStricmp(typeInfFloat.TypeName, colInf[colNo].typeName))
2691                             colInf[colNo].dbDataType = DB_DATA_TYPE_FLOAT;
2692                         else if (!wxStricmp(typeInfDate.TypeName, colInf[colNo].typeName))
2693                             colInf[colNo].dbDataType = DB_DATA_TYPE_DATE;
2694                         else if (!wxStricmp(typeInfBlob.TypeName, colInf[colNo].typeName))
2695                             colInf[colNo].dbDataType = DB_DATA_TYPE_BLOB;
2696                         colNo++;
2697                     }
2698                 }
2699             }
2700             if (retcode != SQL_NO_DATA_FOUND)
2701             {  // Error occurred, abort
2702                 DispAllErrors(henv, hdbc, hstmt);
2703                 if (colInf)
2704                     delete [] colInf;
2705                 SQLFreeStmt(hstmt, SQL_CLOSE);
2706                 return(0);
2707             }
2708         }
2709     }
2710 
2711     SQLFreeStmt(hstmt, SQL_CLOSE);
2712     return colInf;
2713 
2714 }  // wxDb::GetColumns()
2715 
2716 
2717 /********** wxDb::GetColumns() **********/
2718 
2719 wxDbColInf *wxDb::GetColumns(const wxString &tableName, UWORD *numCols, const wxChar *userID)
2720 //
2721 // Same as the above GetColumns() function except this one gets columns
2722 // only for a single table, and if 'numCols' is not NULL, the number of
2723 // columns stored in the returned wxDbColInf is set in '*numCols'
2724 //
2725 // userID is evaluated in the following manner:
2726 //        userID == NULL  ... UserID is ignored
2727 //        userID == ""    ... UserID set equal to 'this->uid'
2728 //        userID != ""    ... UserID set equal to 'userID'
2729 //
2730 // NOTE: ALL column bindings associated with this wxDb instance are unbound
2731 //       by this function.  This function should use its own wxDb instance
2732 //       to avoid undesired unbinding of columns.
2733 
2734 {
2735     UWORD       noCols = 0;
2736     UWORD       colNo  = 0;
2737     wxDbColInf *colInf = 0;
2738 
2739     RETCODE  retcode;
2740     SQLLEN   cb;
2741 
2742     wxString TableName;
2743 
2744     wxString UserID;
2745     convertUserID(userID,UserID);
2746 
2747     // Pass 1 - Determine how many columns there are.
2748     // Pass 2 - Allocate the wxDbColInf array and fill in
2749     //                the array with the column information.
2750     int pass;
2751     for (pass = 1; pass <= 2; pass++)
2752     {
2753         if (pass == 2)
2754         {
2755             if (noCols == 0)  // Probably a bogus table name(s)
2756                 break;
2757             // Allocate n wxDbColInf objects to hold the column information
2758             colInf = new wxDbColInf[noCols+1];
2759             if (!colInf)
2760                 break;
2761             // Mark the end of the array
2762             wxStrcpy(colInf[noCols].tableName, wxEmptyString);
2763             wxStrcpy(colInf[noCols].colName, wxEmptyString);
2764             colInf[noCols].sqlDataType = 0;
2765         }
2766 
2767         TableName = tableName;
2768         // Oracle and Interbase table names are uppercase only, so force
2769         // the name to uppercase just in case programmer forgot to do this
2770         if ((Dbms() == dbmsORACLE) ||
2771             (Dbms() == dbmsFIREBIRD) ||
2772             (Dbms() == dbmsINTERBASE))
2773             TableName = TableName.Upper();
2774 
2775         SQLFreeStmt(hstmt, SQL_CLOSE);
2776 
2777         // MySQL, SQLServer, and Access cannot accept a user name when looking up column names, so we
2778         // use the call below that leaves out the user name
2779         if (!UserID.empty() &&
2780             Dbms() != dbmsMY_SQL &&
2781             Dbms() != dbmsACCESS &&
2782             Dbms() != dbmsMS_SQL_SERVER)
2783         {
2784             retcode = SQLColumns(hstmt,
2785                                  NULL, 0,                                // All qualifiers
2786                                  (SQLTCHAR *) UserID.c_str(), SQL_NTS,    // Owner
2787                                  (SQLTCHAR *) TableName.c_str(), SQL_NTS,
2788                                  NULL, 0);                               // All columns
2789         }
2790         else
2791         {
2792             retcode = SQLColumns(hstmt,
2793                                  NULL, 0,                                 // All qualifiers
2794                                  NULL, 0,                                 // Owner
2795                                  (SQLTCHAR *) TableName.c_str(), SQL_NTS,
2796                                  NULL, 0);                                // All columns
2797         }
2798         if (retcode != SQL_SUCCESS)
2799         {  // Error occurred, abort
2800             DispAllErrors(henv, hdbc, hstmt);
2801             if (colInf)
2802                 delete [] colInf;
2803             SQLFreeStmt(hstmt, SQL_CLOSE);
2804             if (numCols)
2805                 *numCols = 0;
2806             return(0);
2807         }
2808 
2809         while ((retcode = SQLFetch(hstmt)) == SQL_SUCCESS)
2810         {
2811             if (pass == 1)  // First pass, just add up the number of columns
2812                 noCols++;
2813             else  // Pass 2; Fill in the array of structures
2814             {
2815                 if (colNo < noCols)  // Some extra error checking to prevent memory overwrites
2816                 {
2817                     // NOTE: Only the ODBC 1.x fields are retrieved
2818                     GetData( 1, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].catalog,      128+1,                     &cb);
2819                     GetData( 2, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].schema,       128+1,                     &cb);
2820                     GetData( 3, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].tableName,    DB_MAX_TABLE_NAME_LEN+1,   &cb);
2821                     GetData( 4, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].colName,      DB_MAX_COLUMN_NAME_LEN+1,  &cb);
2822                     GetData( 5, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].sqlDataType,  0,                         &cb);
2823                     GetData( 6, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].typeName,     128+1,                     &cb);
2824                     GetData( 7, SQL_C_SLONG,  (UCHAR*) &colInf[colNo].columnLength, 0,                         &cb);
2825                     // BJO 991214 : SQL_C_SSHORT instead of SQL_C_SLONG, otherwise fails on Sparc (probably all 64 bit architectures)
2826                     GetData( 8, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].bufferSize,   0,                         &cb);
2827                     GetData( 9, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].decimalDigits,0,                         &cb);
2828                     GetData(10, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].numPrecRadix, 0,                         &cb);
2829                     GetData(11, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].nullable,     0,                         &cb);
2830                     GetData(12, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].remarks,      254+1,                     &cb);
2831                     // Start Values for Primary/Foriegn Key (=No)
2832                     colInf[colNo].PkCol = 0;           // Primary key column   0=No; 1= First Key, 2 = Second Key etc.
2833                     colInf[colNo].PkTableName[0] = 0;  // Tablenames where Primary Key is used as a Foreign Key
2834                     colInf[colNo].FkCol = 0;           // Foreign key column   0=No; 1= First Key, 2 = Second Key etc.
2835                     colInf[colNo].FkTableName[0] = 0;  // Foreign key table name
2836 
2837                     // BJO 20000428 : Virtuoso returns type names with upper cases!
2838                     if (Dbms() == dbmsVIRTUOSO)
2839                     {
2840                         wxString s = colInf[colNo].typeName;
2841                         s = s.MakeLower();
2842                         wxStrcmp(colInf[colNo].typeName, s.c_str());
2843                     }
2844 
2845                     // Determine the wxDb data type that is used to represent the native data type of this data source
2846                     colInf[colNo].dbDataType = 0;
2847                     if (!wxStricmp(typeInfVarchar.TypeName, colInf[colNo].typeName))
2848                     {
2849 #ifdef _IODBC_
2850                         // IODBC does not return a correct columnLength, so we set
2851                         // columnLength = bufferSize if no column length was returned
2852                         // IODBC returns the columnLength in bufferSize. (bug)
2853                         if (colInf[colNo].columnLength < 1)
2854                         {
2855                            colInf[colNo].columnLength = colInf[colNo].bufferSize;
2856                         }
2857 #endif
2858 
2859                         colInf[colNo].dbDataType = DB_DATA_TYPE_VARCHAR;
2860                     }
2861                     else if (!wxStricmp(typeInfInteger.TypeName, colInf[colNo].typeName))
2862                         colInf[colNo].dbDataType = DB_DATA_TYPE_INTEGER;
2863                     else if (!wxStricmp(typeInfFloat.TypeName, colInf[colNo].typeName))
2864                         colInf[colNo].dbDataType = DB_DATA_TYPE_FLOAT;
2865                     else if (!wxStricmp(typeInfDate.TypeName, colInf[colNo].typeName))
2866                         colInf[colNo].dbDataType = DB_DATA_TYPE_DATE;
2867                     else if (!wxStricmp(typeInfBlob.TypeName, colInf[colNo].typeName))
2868                         colInf[colNo].dbDataType = DB_DATA_TYPE_BLOB;
2869 
2870                     colNo++;
2871                 }
2872             }
2873         }
2874         if (retcode != SQL_NO_DATA_FOUND)
2875         {  // Error occurred, abort
2876             DispAllErrors(henv, hdbc, hstmt);
2877             if (colInf)
2878                 delete [] colInf;
2879             SQLFreeStmt(hstmt, SQL_CLOSE);
2880             if (numCols)
2881                 *numCols = 0;
2882             return(0);
2883         }
2884     }
2885 
2886     SQLFreeStmt(hstmt, SQL_CLOSE);
2887 
2888     // Store Primary and Foriegn Keys
2889     GetKeyFields(tableName,colInf,noCols);
2890 
2891     if (numCols)
2892         *numCols = noCols;
2893     return colInf;
2894 
2895 }  // wxDb::GetColumns()
2896 
2897 
2898 #else  // New GetColumns
2899 
2900 
2901 /*
2902     BJO 20000503
2903     These are tentative new GetColumns members which should be more database
2904     independent and which always returns the columns in the order they were
2905     created.
2906 
2907     - The first one (wxDbColInf *wxDb::GetColumns(wxChar *tableName[], const
2908       wxChar* userID)) calls the second implementation for each separate table
2909       before merging the results. This makes the code easier to maintain as
2910       only one member (the second) makes the real work
2911     - wxDbColInf *wxDb::GetColumns(const wxString &tableName, int *numCols, const
2912       wxChar *userID) is a little bit improved
2913     - It doesn't anymore rely on the type-name to find out which database-type
2914       each column has
2915     - It ends by sorting the columns, so that they are returned in the same
2916       order they were created
2917 */
2918 
2919 typedef struct
2920 {
2921     UWORD noCols;
2922     wxDbColInf *colInf;
2923 } _TableColumns;
2924 
2925 
2926 wxDbColInf *wxDb::GetColumns(wxChar *tableName[], const wxChar *userID)
2927 {
2928     int i, j;
2929     // The last array element of the tableName[] argument must be zero (null).
2930     // This is how the end of the array is detected.
2931 
2932     UWORD noCols = 0;
2933 
2934     // How many tables ?
2935     int tbl;
2936     for (tbl = 0 ; tableName[tbl]; tbl++);
2937 
2938     // Create a table to maintain the columns for each separate table
2939     _TableColumns *TableColumns = new _TableColumns[tbl];
2940 
2941     // Fill the table
2942     for (i = 0 ; i < tbl ; i++)
2943 
2944     {
2945         TableColumns[i].colInf = GetColumns(tableName[i], &TableColumns[i].noCols, userID);
2946         if (TableColumns[i].colInf == NULL)
2947             return NULL;
2948         noCols += TableColumns[i].noCols;
2949     }
2950 
2951     // Now merge all the separate table infos
2952     wxDbColInf *colInf = new wxDbColInf[noCols+1];
2953 
2954     // Mark the end of the array
2955     wxStrcpy(colInf[noCols].tableName, wxEmptyString);
2956     wxStrcpy(colInf[noCols].colName, wxEmptyString);
2957     colInf[noCols].sqlDataType = 0;
2958 
2959     // Merge ...
2960     int offset = 0;
2961 
2962     for (i = 0 ; i < tbl ; i++)
2963     {
2964         for (j = 0 ; j < TableColumns[i].noCols ; j++)
2965         {
2966             colInf[offset++] = TableColumns[i].colInf[j];
2967         }
2968     }
2969 
2970     delete [] TableColumns;
2971 
2972     return colInf;
2973 }  // wxDb::GetColumns()  -- NEW
2974 
2975 
2976 wxDbColInf *wxDb::GetColumns(const wxString &tableName, int *numCols, const wxChar *userID)
2977 //
2978 // Same as the above GetColumns() function except this one gets columns
2979 // only for a single table, and if 'numCols' is not NULL, the number of
2980 // columns stored in the returned wxDbColInf is set in '*numCols'
2981 //
2982 // userID is evaluated in the following manner:
2983 //        userID == NULL  ... UserID is ignored
2984 //        userID == ""    ... UserID set equal to 'this->uid'
2985 //        userID != ""    ... UserID set equal to 'userID'
2986 //
2987 // NOTE: ALL column bindings associated with this wxDb instance are unbound
2988 //       by this function.  This function should use its own wxDb instance
2989 //       to avoid undesired unbinding of columns.
2990 {
2991     UWORD       noCols = 0;
2992     UWORD       colNo  = 0;
2993     wxDbColInf *colInf = 0;
2994 
2995     RETCODE  retcode;
2996     SDWORD   cb;
2997 
2998     wxString TableName;
2999 
3000     wxString UserID;
3001     convertUserID(userID,UserID);
3002 
3003     // Pass 1 - Determine how many columns there are.
3004     // Pass 2 - Allocate the wxDbColInf array and fill in
3005     //                the array with the column information.
3006     int pass;
3007     for (pass = 1; pass <= 2; pass++)
3008     {
3009         if (pass == 2)
3010         {
3011             if (noCols == 0)  // Probably a bogus table name(s)
3012                 break;
3013             // Allocate n wxDbColInf objects to hold the column information
3014             colInf = new wxDbColInf[noCols+1];
3015             if (!colInf)
3016                 break;
3017             // Mark the end of the array
3018             wxStrcpy(colInf[noCols].tableName, wxEmptyString);
3019             wxStrcpy(colInf[noCols].colName, wxEmptyString);
3020             colInf[noCols].sqlDataType = 0;
3021         }
3022 
3023         TableName = tableName;
3024         // Oracle and Interbase table names are uppercase only, so force
3025         // the name to uppercase just in case programmer forgot to do this
3026         if ((Dbms() == dbmsORACLE) ||
3027             (Dbms() == dbmsFIREBIRD) ||
3028             (Dbms() == dbmsINTERBASE))
3029             TableName = TableName.Upper();
3030 
3031         SQLFreeStmt(hstmt, SQL_CLOSE);
3032 
3033         // MySQL, SQLServer, and Access cannot accept a user name when looking up column names, so we
3034         // use the call below that leaves out the user name
3035         if (!UserID.empty() &&
3036             Dbms() != dbmsMY_SQL &&
3037             Dbms() != dbmsACCESS &&
3038             Dbms() != dbmsMS_SQL_SERVER)
3039         {
3040             retcode = SQLColumns(hstmt,
3041                                  NULL, 0,                              // All qualifiers
3042                                  (UCHAR *) UserID.c_str(), SQL_NTS,    // Owner
3043                                  (UCHAR *) TableName.c_str(), SQL_NTS,
3044                                  NULL, 0);                             // All columns
3045         }
3046         else
3047         {
3048             retcode = SQLColumns(hstmt,
3049                                  NULL, 0,                              // All qualifiers
3050                                  NULL, 0,                              // Owner
3051                                  (UCHAR *) TableName.c_str(), SQL_NTS,
3052                                  NULL, 0);                             // All columns
3053         }
3054         if (retcode != SQL_SUCCESS)
3055         {  // Error occurred, abort
3056             DispAllErrors(henv, hdbc, hstmt);
3057             if (colInf)
3058                 delete [] colInf;
3059             SQLFreeStmt(hstmt, SQL_CLOSE);
3060             if (numCols)
3061                 *numCols = 0;
3062             return(0);
3063         }
3064 
3065         while ((retcode = SQLFetch(hstmt)) == SQL_SUCCESS)
3066         {
3067             if (pass == 1)  // First pass, just add up the number of columns
3068                 noCols++;
3069             else  // Pass 2; Fill in the array of structures
3070             {
3071                 if (colNo < noCols)  // Some extra error checking to prevent memory overwrites
3072                 {
3073                     // NOTE: Only the ODBC 1.x fields are retrieved
3074                     GetData( 1, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].catalog,      128+1,                     &cb);
3075                     GetData( 2, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].schema,       128+1,                     &cb);
3076                     GetData( 3, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].tableName,    DB_MAX_TABLE_NAME_LEN+1,   &cb);
3077                     GetData( 4, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].colName,      DB_MAX_COLUMN_NAME_LEN+1,  &cb);
3078                     GetData( 5, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].sqlDataType,  0,                         &cb);
3079                     GetData( 6, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].typeName,     128+1,                     &cb);
3080                     GetData( 7, SQL_C_SLONG,  (UCHAR*) &colInf[colNo].columnLength, 0,                         &cb);
3081                     GetData( 8, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].bufferSize,   0,                         &cb);
3082                     GetData( 9, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].decimalDigits,0,                         &cb);
3083                     GetData(10, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].numPrecRadix, 0,                         &cb);
3084                     GetData(11, SQL_C_SSHORT, (UCHAR*) &colInf[colNo].nullable,     0,                         &cb);
3085                     GetData(12, SQL_C_WXCHAR, (UCHAR*)  colInf[colNo].remarks,      254+1,                     &cb);
3086                     // Start Values for Primary/Foriegn Key (=No)
3087                     colInf[colNo].PkCol = 0;           // Primary key column   0=No; 1= First Key, 2 = Second Key etc.
3088                     colInf[colNo].PkTableName[0] = 0;  // Tablenames where Primary Key is used as a Foreign Key
3089                     colInf[colNo].FkCol = 0;           // Foreign key column   0=No; 1= First Key, 2 = Second Key etc.
3090                     colInf[colNo].FkTableName[0] = 0;  // Foreign key table name
3091 
3092 #ifdef _IODBC_
3093                     // IODBC does not return a correct columnLength, so we set
3094                     // columnLength = bufferSize if no column length was returned
3095                     // IODBC returns the columnLength in bufferSize. (bug)
3096                     if (colInf[colNo].columnLength < 1)
3097                     {
3098                        colInf[colNo].columnLength = colInf[colNo].bufferSize;
3099                     }
3100 #endif
3101 
3102                     // Determine the wxDb data type that is used to represent the native data type of this data source
3103                     colInf[colNo].dbDataType = 0;
3104                     // Get the intern datatype
3105                     switch (colInf[colNo].sqlDataType)
3106                     {
3107 #if wxUSE_UNICODE
3108     #if defined(SQL_WCHAR)
3109                         case SQL_WCHAR:
3110     #endif
3111     #if defined(SQL_WVARCHAR)
3112                         case SQL_WVARCHAR:
3113     #endif
3114 #endif
3115                         case SQL_VARCHAR:
3116                         case SQL_CHAR:
3117                             colInf[colNo].dbDataType = DB_DATA_TYPE_VARCHAR;
3118                         break;
3119                         case SQL_LONGVARCHAR:
3120                             colInf[colNo].dbDataType = DB_DATA_TYPE_MEMO;
3121                             break;
3122                         case SQL_TINYINT:
3123                         case SQL_SMALLINT:
3124                         case SQL_INTEGER:
3125                         case SQL_BIT:
3126                             colInf[colNo].dbDataType = DB_DATA_TYPE_INTEGER;
3127                             break;
3128                         case SQL_DOUBLE:
3129                         case SQL_DECIMAL:
3130                         case SQL_NUMERIC:
3131                         case SQL_FLOAT:
3132                         case SQL_REAL:
3133                             colInf[colNo].dbDataType = DB_DATA_TYPE_FLOAT;
3134                             break;
3135                         case SQL_DATE:
3136                         case SQL_TIMESTAMP:
3137                             colInf[colNo].dbDataType = DB_DATA_TYPE_DATE;
3138                             break;
3139                         case SQL_BINARY:
3140                             colInf[colNo].dbDataType = DB_DATA_TYPE_BLOB;
3141                             break;
3142 #ifdef __WXDEBUG__
3143                         default:
3144                             wxString errMsg;
3145                             errMsg.Printf(wxT("SQL Data type %d currently not supported by wxWidgets"), colInf[colNo].sqlDataType);
3146                             wxLogDebug(errMsg,wxT("ODBC DEBUG MESSAGE"));
3147 #endif
3148                     }
3149                     colNo++;
3150                 }
3151             }
3152         }
3153         if (retcode != SQL_NO_DATA_FOUND)
3154         {  // Error occurred, abort
3155             DispAllErrors(henv, hdbc, hstmt);
3156             if (colInf)
3157                 delete [] colInf;
3158             SQLFreeStmt(hstmt, SQL_CLOSE);
3159             if (numCols)
3160                 *numCols = 0;
3161             return(0);
3162         }
3163     }
3164 
3165     SQLFreeStmt(hstmt, SQL_CLOSE);
3166 
3167     // Store Primary and Foreign Keys
3168     GetKeyFields(tableName,colInf,noCols);
3169 
3170     ///////////////////////////////////////////////////////////////////////////
3171     // Now sort the the columns in order to make them appear in the right order
3172     ///////////////////////////////////////////////////////////////////////////
3173 
3174     // Build a generic SELECT statement which returns 0 rows
3175     wxString Stmt;
3176 
3177     Stmt.Printf(wxT("select * from \"%s\" where 0=1"), tableName);
3178 
3179     // Execute query
3180     if (SQLExecDirect(hstmt, (UCHAR FAR *) Stmt.c_str(), SQL_NTS) != SQL_SUCCESS)
3181     {
3182         DispAllErrors(henv, hdbc, hstmt);
3183         return NULL;
3184     }
3185 
3186     // Get the number of result columns
3187     if (SQLNumResultCols (hstmt, &noCols) != SQL_SUCCESS)
3188     {
3189         DispAllErrors(henv, hdbc, hstmt);
3190         return NULL;
3191     }
3192 
3193     if (noCols == 0) // Probably a bogus table name
3194         return NULL;
3195 
3196     //  Get the name
3197     int i;
3198     short colNum;
3199     UCHAR name[100];
3200     SWORD Sword;
3201     SDWORD Sdword;
3202     for (colNum = 0; colNum < noCols; colNum++)
3203     {
3204         if (SQLColAttributes(hstmt,colNum+1, SQL_COLUMN_NAME,
3205             name, sizeof(name),
3206             &Sword, &Sdword) != SQL_SUCCESS)
3207         {
3208             DispAllErrors(henv, hdbc, hstmt);
3209             return NULL;
3210         }
3211 
3212         wxString Name1 = name;
3213         Name1 = Name1.Upper();
3214 
3215         // Where is this name in the array ?
3216         for (i = colNum ; i < noCols ; i++)
3217         {
3218             wxString Name2 =  colInf[i].colName;
3219             Name2 = Name2.Upper();
3220             if (Name2 == Name1)
3221             {
3222                 if (colNum != i) // swap to sort
3223                 {
3224                     wxDbColInf tmpColInf = colInf[colNum];
3225                     colInf[colNum] =  colInf[i];
3226                     colInf[i] = tmpColInf;
3227                 }
3228                 break;
3229             }
3230         }
3231     }
3232     SQLFreeStmt(hstmt, SQL_CLOSE);
3233 
3234     ///////////////////////////////////////////////////////////////////////////
3235     // End sorting
3236     ///////////////////////////////////////////////////////////////////////////
3237 
3238     if (numCols)
3239         *numCols = noCols;
3240     return colInf;
3241 
3242 }  // wxDb::GetColumns()
3243 
3244 
3245 #endif  // #else OLD_GETCOLUMNS
3246 
3247 
3248 /********** wxDb::GetColumnCount() **********/
3249 int wxDb::GetColumnCount(const wxString &tableName, const wxChar *userID)
3250 /*
3251  * Returns a count of how many columns are in a table.
3252  * If an error occurs in computing the number of columns
3253  * this function will return a -1 for the count
3254  *
3255  * userID is evaluated in the following manner:
3256  *        userID == NULL  ... UserID is ignored
3257  *        userID == ""    ... UserID set equal to 'this->uid'
3258  *        userID != ""    ... UserID set equal to 'userID'
3259  *
3260  * NOTE: ALL column bindings associated with this wxDb instance are unbound
3261  *       by this function.  This function should use its own wxDb instance
3262  *       to avoid undesired unbinding of columns.
3263  */
3264 {
3265     UWORD    noCols = 0;
3266 
3267     RETCODE  retcode;
3268 
3269     wxString TableName;
3270 
3271     wxString UserID;
3272     convertUserID(userID,UserID);
3273 
3274     TableName = tableName;
3275     // Oracle and Interbase table names are uppercase only, so force
3276     // the name to uppercase just in case programmer forgot to do this
3277     if ((Dbms() == dbmsORACLE) ||
3278         (Dbms() == dbmsFIREBIRD) ||
3279         (Dbms() == dbmsINTERBASE))
3280         TableName = TableName.Upper();
3281 
3282     SQLFreeStmt(hstmt, SQL_CLOSE);
3283 
3284     // MySQL, SQLServer, and Access cannot accept a user name when looking up column names, so we
3285     // use the call below that leaves out the user name
3286     if (!UserID.empty() &&
3287         Dbms() != dbmsMY_SQL &&
3288         Dbms() != dbmsACCESS &&
3289         Dbms() != dbmsMS_SQL_SERVER)
3290     {
3291         retcode = SQLColumns(hstmt,
3292                              NULL, 0,                                // All qualifiers
3293                              (SQLTCHAR *) UserID.c_str(), SQL_NTS,      // Owner
3294                              (SQLTCHAR *) TableName.c_str(), SQL_NTS,
3295                              NULL, 0);                               // All columns
3296     }
3297     else
3298     {
3299         retcode = SQLColumns(hstmt,
3300                              NULL, 0,                                // All qualifiers
3301                              NULL, 0,                                // Owner
3302                              (SQLTCHAR *) TableName.c_str(), SQL_NTS,
3303                              NULL, 0);                               // All columns
3304     }
3305     if (retcode != SQL_SUCCESS)
3306     {  // Error occurred, abort
3307         DispAllErrors(henv, hdbc, hstmt);
3308         SQLFreeStmt(hstmt, SQL_CLOSE);
3309         return(-1);
3310     }
3311 
3312     // Count the columns
3313     while ((retcode = SQLFetch(hstmt)) == SQL_SUCCESS)
3314         noCols++;
3315 
3316     if (retcode != SQL_NO_DATA_FOUND)
3317     {  // Error occurred, abort
3318         DispAllErrors(henv, hdbc, hstmt);
3319         SQLFreeStmt(hstmt, SQL_CLOSE);
3320         return(-1);
3321     }
3322 
3323     SQLFreeStmt(hstmt, SQL_CLOSE);
3324     return noCols;
3325 
3326 }  // wxDb::GetColumnCount()
3327 
3328 
3329 /********** wxDb::GetCatalog() *******/
3330 wxDbInf *wxDb::GetCatalog(const wxChar *userID)
3331 /*
3332  * ---------------------------------------------------------------------
3333  * -- 19991203 : mj10777 : Create                                 ------
3334  * --          : Creates a wxDbInf with Tables / Cols Array       ------
3335  * --          : uses SQLTables and fills pTableInf;              ------
3336  * --          : pColInf is set to NULL and numCols to 0;         ------
3337  * --          : returns pDbInf (wxDbInf)                         ------
3338  * --            - if unsuccessful (pDbInf == NULL)               ------
3339  * --          : pColInf can be filled with GetColumns(..);       ------
3340  * --          : numCols   can be filled with GetColumnCount(..); ------
3341  * ---------------------------------------------------------------------
3342  *
3343  * userID is evaluated in the following manner:
3344  *        userID == NULL  ... UserID is ignored
3345  *        userID == ""    ... UserID set equal to 'this->uid'
3346  *        userID != ""    ... UserID set equal to 'userID'
3347  *
3348  * NOTE: ALL column bindings associated with this wxDb instance are unbound
3349  *       by this function.  This function should use its own wxDb instance
3350  *       to avoid undesired unbinding of columns.
3351  */
3352 {
3353     int      noTab = 0;     // Counter while filling table entries
3354     int      pass;
3355     RETCODE  retcode;
3356     SQLLEN   cb;
3357     wxString tblNameSave;
3358 
3359     wxString UserID;
3360     convertUserID(userID,UserID);
3361 
3362     //-------------------------------------------------------------
3363     // Create the Database Array of catalog entries
3364 
3365     wxDbInf *pDbInf = new wxDbInf;
3366 
3367     //-------------------------------------------------------------
3368     // Table Information
3369     // Pass 1 - Determine how many Tables there are.
3370     // Pass 2 - Create the Table array and fill it
3371     //        - Create the Cols array = NULL
3372     //-------------------------------------------------------------
3373 
3374     for (pass = 1; pass <= 2; pass++)
3375     {
3376         SQLFreeStmt(hstmt, SQL_CLOSE);   // Close if Open
3377         tblNameSave.Empty();
3378 
3379         if (!UserID.empty() &&
3380             Dbms() != dbmsMY_SQL &&
3381             Dbms() != dbmsACCESS &&
3382             Dbms() != dbmsMS_SQL_SERVER)
3383         {
3384             retcode = SQLTables(hstmt,
3385                                 NULL, 0,                             // All qualifiers
3386                                 (SQLTCHAR *) UserID.c_str(), SQL_NTS,   // User specified
3387                                 NULL, 0,                             // All tables
3388                                 NULL, 0);                            // All columns
3389         }
3390         else
3391         {
3392             retcode = SQLTables(hstmt,
3393                                 NULL, 0,           // All qualifiers
3394                                 NULL, 0,           // User specified
3395                                 NULL, 0,           // All tables
3396                                 NULL, 0);          // All columns
3397         }
3398 
3399         if (retcode != SQL_SUCCESS)
3400         {
3401             DispAllErrors(henv, hdbc, hstmt);
3402             pDbInf = NULL;
3403             SQLFreeStmt(hstmt, SQL_CLOSE);
3404             return pDbInf;
3405         }
3406 
3407         while ((retcode = SQLFetch(hstmt)) == SQL_SUCCESS)   // Table Information
3408         {
3409             if (pass == 1)  // First pass, just count the Tables
3410             {
3411                 if (pDbInf->numTables == 0)
3412                 {
3413                     GetData( 1, SQL_C_WXCHAR,   (UCHAR*)  pDbInf->catalog,  128+1, &cb);
3414                     GetData( 2, SQL_C_WXCHAR,   (UCHAR*)  pDbInf->schema,   128+1, &cb);
3415                  }
3416                  pDbInf->numTables++;      // Counter for Tables
3417             }  // if (pass == 1)
3418             if (pass == 2) // Create and fill the Table entries
3419             {
3420                 if (pDbInf->pTableInf == NULL)   // Has the Table Array been created
3421                 {  // no, then create the Array
3422                     pDbInf->pTableInf = new wxDbTableInf[pDbInf->numTables];
3423                     noTab = 0;
3424                 } // if (pDbInf->pTableInf == NULL)   // Has the Table Array been created
3425 
3426                 GetData( 3, SQL_C_WXCHAR,   (UCHAR*)  (pDbInf->pTableInf+noTab)->tableName,    DB_MAX_TABLE_NAME_LEN+1, &cb);
3427                 GetData( 4, SQL_C_WXCHAR,   (UCHAR*)  (pDbInf->pTableInf+noTab)->tableType,    30+1,                    &cb);
3428                 GetData( 5, SQL_C_WXCHAR,   (UCHAR*)  (pDbInf->pTableInf+noTab)->tableRemarks, 254+1,                   &cb);
3429 
3430                 noTab++;
3431             }  // if
3432         }  // while
3433     }  // for
3434     SQLFreeStmt(hstmt, SQL_CLOSE);
3435 
3436     // Query how many columns are in each table
3437     for (noTab=0;noTab<pDbInf->numTables;noTab++)
3438     {
3439         (pDbInf->pTableInf+noTab)->numCols = (UWORD)GetColumnCount((pDbInf->pTableInf+noTab)->tableName,UserID);
3440     }
3441 
3442     return pDbInf;
3443 
3444 }  // wxDb::GetCatalog()
3445 
3446 
3447 /********** wxDb::Catalog() **********/
3448 bool wxDb::Catalog(const wxChar *userID, const wxString &fileName)
3449 /*
3450  * Creates the text file specified in 'filename' which will contain
3451  * a minimal data dictionary of all tables accessible by the user specified
3452  * in 'userID'
3453  *
3454  * userID is evaluated in the following manner:
3455  *        userID == NULL  ... UserID is ignored
3456  *        userID == ""    ... UserID set equal to 'this->uid'
3457  *        userID != ""    ... UserID set equal to 'userID'
3458  *
3459  * NOTE: ALL column bindings associated with this wxDb instance are unbound
3460  *       by this function.  This function should use its own wxDb instance
3461  *       to avoid undesired unbinding of columns.
3462  */
3463 {
3464     wxASSERT(fileName.length());
3465 
3466     RETCODE   retcode;
3467     SQLLEN    cb;
3468     wxChar    tblName[DB_MAX_TABLE_NAME_LEN+1];
3469     wxString  tblNameSave;
3470     wxChar    colName[DB_MAX_COLUMN_NAME_LEN+1];
3471     SWORD     sqlDataType;
3472     wxChar    typeName[30+1];
3473     SDWORD    precision, length;
3474 
3475     FILE *fp = wxFopen(fileName.c_str(),wxT("wt"));
3476     if (fp == NULL)
3477         return false;
3478 
3479     SQLFreeStmt(hstmt, SQL_CLOSE);
3480 
3481     wxString UserID;
3482     convertUserID(userID,UserID);
3483 
3484     if (!UserID.empty() &&
3485         Dbms() != dbmsMY_SQL &&
3486         Dbms() != dbmsACCESS &&
3487         Dbms() != dbmsFIREBIRD &&
3488         Dbms() != dbmsINTERBASE &&
3489         Dbms() != dbmsMS_SQL_SERVER)
3490     {
3491         retcode = SQLColumns(hstmt,
3492                              NULL, 0,                                // All qualifiers
3493                              (SQLTCHAR *) UserID.c_str(), SQL_NTS,      // User specified
3494                              NULL, 0,                                // All tables
3495                              NULL, 0);                               // All columns
3496     }
3497     else
3498     {
3499         retcode = SQLColumns(hstmt,
3500                              NULL, 0,    // All qualifiers
3501                              NULL, 0,    // User specified
3502                              NULL, 0,    // All tables
3503                              NULL, 0);   // All columns
3504     }
3505     if (retcode != SQL_SUCCESS)
3506     {
3507         DispAllErrors(henv, hdbc, hstmt);
3508         fclose(fp);
3509         return false;
3510     }
3511 
3512     wxString outStr;
3513     tblNameSave.Empty();
3514     int cnt = 0;
3515 
3516     while (true)
3517     {
3518         retcode = SQLFetch(hstmt);
3519         if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
3520             break;
3521 
3522         GetData(3,SQL_C_WXCHAR,  (UCHAR *) tblName,     DB_MAX_TABLE_NAME_LEN+1, &cb);
3523         GetData(4,SQL_C_WXCHAR,  (UCHAR *) colName,     DB_MAX_COLUMN_NAME_LEN+1,&cb);
3524         GetData(5,SQL_C_SSHORT,  (UCHAR *)&sqlDataType, 0,                       &cb);
3525         GetData(6,SQL_C_WXCHAR,  (UCHAR *) typeName,    sizeof(typeName),        &cb);
3526         GetData(7,SQL_C_SLONG,   (UCHAR *)&precision,   0,                       &cb);
3527         GetData(8,SQL_C_SLONG,   (UCHAR *)&length,      0,                       &cb);
3528 
3529         if (wxStrcmp(tblName, tblNameSave.c_str()))
3530         {
3531             if (cnt)
3532                 wxFputs(wxT("\n"), fp);
3533             wxFputs(wxT("================================ "), fp);
3534             wxFputs(wxT("================================ "), fp);
3535             wxFputs(wxT("===================== "), fp);
3536             wxFputs(wxT("========= "), fp);
3537             wxFputs(wxT("=========\n"), fp);
3538             outStr.Printf(wxT("%-32s %-32s %-21s %9s %9s\n"),
3539                 wxT("TABLE NAME"), wxT("COLUMN NAME"), wxT("DATA TYPE"), wxT("PRECISION"), wxT("LENGTH"));
3540             wxFputs(outStr.c_str(), fp);
3541             wxFputs(wxT("================================ "), fp);
3542             wxFputs(wxT("================================ "), fp);
3543             wxFputs(wxT("===================== "), fp);
3544             wxFputs(wxT("========= "), fp);
3545             wxFputs(wxT("=========\n"), fp);
3546             tblNameSave = tblName;
3547         }
3548 
3549         outStr.Printf(wxT("%-32s %-32s (%04d)%-15s %9ld %9ld\n"),
3550             tblName, colName, sqlDataType, typeName, precision, length);
3551         if (wxFputs(outStr.c_str(), fp) == EOF)
3552         {
3553             SQLFreeStmt(hstmt, SQL_CLOSE);
3554             fclose(fp);
3555             return false;
3556         }
3557         cnt++;
3558     }
3559 
3560     if (retcode != SQL_NO_DATA_FOUND)
3561         DispAllErrors(henv, hdbc, hstmt);
3562 
3563     SQLFreeStmt(hstmt, SQL_CLOSE);
3564 
3565     fclose(fp);
3566     return(retcode == SQL_NO_DATA_FOUND);
3567 
3568 }  // wxDb::Catalog()
3569 
3570 
3571 bool wxDb::TableExists(const wxString &tableName, const wxChar *userID, const wxString &tablePath)
3572 /*
3573  * Table name can refer to a table, view, alias or synonym.  Returns true
3574  * if the object exists in the database.  This function does not indicate
3575  * whether or not the user has privleges to query or perform other functions
3576  * on the table.
3577  *
3578  * userID is evaluated in the following manner:
3579  *        userID == NULL  ... UserID is ignored
3580  *        userID == ""    ... UserID set equal to 'this->uid'
3581  *        userID != ""    ... UserID set equal to 'userID'
3582  */
3583 {
3584     wxASSERT(tableName.length());
3585 
3586     wxString TableName;
3587 
3588     if (Dbms() == dbmsDBASE)
3589     {
3590         wxString dbName;
3591         if (tablePath.length())
3592             dbName.Printf(wxT("%s/%s.dbf"), tablePath.c_str(), tableName.c_str());
3593         else
3594             dbName.Printf(wxT("%s.dbf"), tableName.c_str());
3595 
3596         bool exists;
3597         exists = wxFileExists(dbName);
3598         return exists;
3599     }
3600 
3601     wxString UserID;
3602     convertUserID(userID,UserID);
3603 
3604     TableName = tableName;
3605     // Oracle and Interbase table names are uppercase only, so force
3606     // the name to uppercase just in case programmer forgot to do this
3607     if ((Dbms() == dbmsORACLE) ||
3608         (Dbms() == dbmsFIREBIRD) ||
3609         (Dbms() == dbmsINTERBASE))
3610         TableName = TableName.Upper();
3611 
3612     SQLFreeStmt(hstmt, SQL_CLOSE);
3613     RETCODE retcode;
3614 
3615     // Some databases cannot accept a user name when looking up table names,
3616     // so we use the call below that leaves out the user name
3617     if (!UserID.empty() &&
3618         Dbms() != dbmsMY_SQL &&
3619         Dbms() != dbmsACCESS &&
3620         Dbms() != dbmsMS_SQL_SERVER &&
3621         Dbms() != dbmsDB2 &&
3622         Dbms() != dbmsFIREBIRD &&
3623         Dbms() != dbmsINTERBASE &&
3624         Dbms() != dbmsPERVASIVE_SQL)
3625     {
3626         retcode = SQLTables(hstmt,
3627                             NULL, 0,                                  // All qualifiers
3628                             (SQLTCHAR *) UserID.c_str(), SQL_NTS,        // Only tables owned by this user
3629                             (SQLTCHAR FAR *)TableName.c_str(), SQL_NTS,
3630                             NULL, 0);                                 // All table types
3631     }
3632     else
3633     {
3634         retcode = SQLTables(hstmt,
3635                             NULL, 0,                                  // All qualifiers
3636                             NULL, 0,                                  // All owners
3637                             (SQLTCHAR FAR *)TableName.c_str(), SQL_NTS,
3638                             NULL, 0);                                 // All table types
3639     }
3640     if (retcode != SQL_SUCCESS)
3641         return(DispAllErrors(henv, hdbc, hstmt));
3642 
3643     retcode = SQLFetch(hstmt);
3644     if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
3645     {
3646         SQLFreeStmt(hstmt, SQL_CLOSE);
3647         return(DispAllErrors(henv, hdbc, hstmt));
3648     }
3649 
3650     SQLFreeStmt(hstmt, SQL_CLOSE);
3651 
3652     return true;
3653 
3654 }  // wxDb::TableExists()
3655 
3656 
3657 /********** wxDb::TablePrivileges() **********/
3658 bool wxDb::TablePrivileges(const wxString &tableName, const wxString &priv, const wxChar *userID,
3659                             const wxChar *schema, const wxString &WXUNUSED(tablePath))
3660 {
3661     wxASSERT(tableName.length());
3662 
3663     wxDbTablePrivilegeInfo  result;
3664     SQLLEN  cbRetVal;
3665     RETCODE retcode;
3666 
3667     // We probably need to be able to dynamically set this based on
3668     // the driver type, and state.
3669     wxChar curRole[]=wxT("public");
3670 
3671     wxString TableName;
3672 
3673     wxString UserID,Schema;
3674     convertUserID(userID,UserID);
3675     convertUserID(schema,Schema);
3676 
3677     TableName = tableName;
3678     // Oracle and Interbase table names are uppercase only, so force
3679     // the name to uppercase just in case programmer forgot to do this
3680     if ((Dbms() == dbmsORACLE) ||
3681         (Dbms() == dbmsFIREBIRD) ||
3682         (Dbms() == dbmsINTERBASE))
3683         TableName = TableName.Upper();
3684 
3685     SQLFreeStmt(hstmt, SQL_CLOSE);
3686 
3687     // Some databases cannot accept a user name when looking up table names,
3688     // so we use the call below that leaves out the user name
3689     if (!Schema.empty() &&
3690         Dbms() != dbmsMY_SQL &&
3691         Dbms() != dbmsACCESS &&
3692         Dbms() != dbmsMS_SQL_SERVER)
3693     {
3694         retcode = SQLTablePrivileges(hstmt,
3695                                      NULL, 0,                                    // Catalog
3696                                      (SQLTCHAR FAR *)Schema.c_str(), SQL_NTS,               // Schema
3697                                      (SQLTCHAR FAR *)TableName.c_str(), SQL_NTS);
3698     }
3699     else
3700     {
3701         retcode = SQLTablePrivileges(hstmt,
3702                                      NULL, 0,                                    // Catalog
3703                                      NULL, 0,                                    // Schema
3704                                      (SQLTCHAR FAR *)TableName.c_str(), SQL_NTS);
3705     }
3706 
3707 #ifdef DBDEBUG_CONSOLE
3708     wxFprintf(stderr ,wxT("SQLTablePrivileges() returned %i \n"),retcode);
3709 #endif
3710 
3711     if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
3712         return (DispAllErrors(henv, hdbc, hstmt));
3713 
3714     bool failed = false;
3715     retcode = SQLFetch(hstmt);
3716     while (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
3717     {
3718         if (SQLGetData(hstmt, 1, SQL_C_WXCHAR, (UCHAR*) result.tableQual, sizeof(result.tableQual), &cbRetVal) != SQL_SUCCESS)
3719             failed = true;
3720 
3721         if (!failed && SQLGetData(hstmt, 2, SQL_C_WXCHAR, (UCHAR*) result.tableOwner, sizeof(result.tableOwner), &cbRetVal) != SQL_SUCCESS)
3722             failed = true;
3723 
3724         if (!failed && SQLGetData(hstmt, 3, SQL_C_WXCHAR, (UCHAR*) result.tableName, sizeof(result.tableName), &cbRetVal) != SQL_SUCCESS)
3725             failed = true;
3726 
3727         if (!failed && SQLGetData(hstmt, 4, SQL_C_WXCHAR, (UCHAR*) result.grantor, sizeof(result.grantor), &cbRetVal) != SQL_SUCCESS)
3728             failed = true;
3729 
3730         if (!failed && SQLGetData(hstmt, 5, SQL_C_WXCHAR, (UCHAR*) result.grantee, sizeof(result.grantee), &cbRetVal) != SQL_SUCCESS)
3731             failed = true;
3732 
3733         if (!failed && SQLGetData(hstmt, 6, SQL_C_WXCHAR, (UCHAR*) result.privilege, sizeof(result.privilege), &cbRetVal) != SQL_SUCCESS)
3734             failed = true;
3735 
3736         if (!failed && SQLGetData(hstmt, 7, SQL_C_WXCHAR, (UCHAR*) result.grantable, sizeof(result.grantable), &cbRetVal) != SQL_SUCCESS)
3737             failed = true;
3738 
3739         if (failed)
3740         {
3741             return(DispAllErrors(henv, hdbc, hstmt));
3742         }
3743 #ifdef DBDEBUG_CONSOLE
3744         wxFprintf(stderr,wxT("Scanning %s privilege on table %s.%s granted by %s to %s\n"),
3745                 result.privilege,result.tableOwner,result.tableName,
3746                 result.grantor, result.grantee);
3747 #endif
3748 
3749         if (UserID.IsSameAs(result.tableOwner,false))
3750         {
3751             SQLFreeStmt(hstmt, SQL_CLOSE);
3752             return true;
3753         }
3754 
3755         if (UserID.IsSameAs(result.grantee,false) &&
3756             !wxStrcmp(result.privilege,priv))
3757         {
3758             SQLFreeStmt(hstmt, SQL_CLOSE);
3759             return true;
3760         }
3761 
3762         if (!wxStrcmp(result.grantee,curRole) &&
3763             !wxStrcmp(result.privilege,priv))
3764         {
3765             SQLFreeStmt(hstmt, SQL_CLOSE);
3766             return true;
3767         }
3768 
3769         retcode = SQLFetch(hstmt);
3770     }
3771 
3772     SQLFreeStmt(hstmt, SQL_CLOSE);
3773     return false;
3774 
3775 }  // wxDb::TablePrivileges
3776 
3777 
3778 const wxString wxDb::SQLTableName(const wxChar *tableName)
3779 {
3780     wxString TableName;
3781 
3782     if (Dbms() == dbmsACCESS)
3783         TableName = _T("\"");
3784     TableName += tableName;
3785     if (Dbms() == dbmsACCESS)
3786         TableName += _T("\"");
3787 
3788     return TableName;
3789 }  // wxDb::SQLTableName()
3790 
3791 
3792 const wxString wxDb::SQLColumnName(const wxChar *colName)
3793 {
3794     wxString ColName;
3795 
3796     if (Dbms() == dbmsACCESS)
3797         ColName = _T("\"");
3798     ColName += colName;
3799     if (Dbms() == dbmsACCESS)
3800         ColName += _T("\"");
3801 
3802     return ColName;
3803 }  // wxDb::SQLColumnName()
3804 
3805 
3806 /********** wxDb::SetSqlLogging() **********/
3807 bool wxDb::SetSqlLogging(wxDbSqlLogState state, const wxString &filename, bool append)
3808 {
3809     wxASSERT(state == sqlLogON  || state == sqlLogOFF);
3810     wxASSERT(state == sqlLogOFF || filename.length());
3811 
3812     if (state == sqlLogON)
3813     {
3814         if (fpSqlLog == 0)
3815         {
3816             fpSqlLog = wxFopen(filename.c_str(), (append ? wxT("at") : wxT("wt")));
3817             if (fpSqlLog == NULL)
3818                 return false;
3819         }
3820     }
3821     else  // sqlLogOFF
3822     {
3823         if (fpSqlLog)
3824         {
3825             if (fclose(fpSqlLog))
3826                 return false;
3827             fpSqlLog = 0;
3828         }
3829     }
3830 
3831     sqlLogState = state;
3832     return true;
3833 
3834 }  // wxDb::SetSqlLogging()
3835 
3836 
3837 /********** wxDb::WriteSqlLog() **********/
3838 bool wxDb::WriteSqlLog(const wxString &logMsg)
3839 {
3840     wxASSERT(logMsg.length());
3841 
3842     if (fpSqlLog == 0 || sqlLogState == sqlLogOFF)
3843         return false;
3844 
3845     if (wxFputs(wxT("\n"),   fpSqlLog) == EOF)
3846         return false;
3847     if (wxFputs(logMsg, fpSqlLog) == EOF)
3848         return false;
3849     if (wxFputs(wxT("\n"),   fpSqlLog) == EOF)
3850         return false;
3851 
3852     return true;
3853 
3854 }  // wxDb::WriteSqlLog()
3855 
3856 
3857 /********** wxDb::Dbms() **********/
3858 wxDBMS wxDb::Dbms(void)
3859 /*
3860  * Be aware that not all database engines use the exact same syntax, and not
3861  * every ODBC compliant database is compliant to the same level of compliancy.
3862  * Some manufacturers support the minimum Level 1 compliancy, and others up
3863  * through Level 3.  Others support subsets of features for levels above 1.
3864  *
3865  * If you find an inconsistency between the wxDb class and a specific database
3866  * engine, and an identifier to this section, and special handle the database in
3867  * the area where behavior is non-conforming with the other databases.
3868  *
3869  *
3870  * NOTES ABOUT ISSUES SPECIFIC TO EACH DATABASE ENGINE
3871  * ---------------------------------------------------
3872  *
3873  * ORACLE
3874  *        - Currently the only database supported by the class to support VIEWS
3875  *
3876  * DBASE
3877  *        - Does not support the SQL_TIMESTAMP structure
3878  *        - Supports only one cursor and one connect (apparently? with Microsoft driver only?)
3879  *        - Does not automatically create the primary index if the 'keyField' param of SetColDef
3880  *            is true.  The user must create ALL indexes from their program.
3881  *        - Table names can only be 8 characters long
3882  *        - Column names can only be 10 characters long
3883  *
3884  * SYBASE (all)
3885  *        - To lock a record during QUERY functions, the reserved word 'HOLDLOCK' must be added
3886  *            after every table name involved in the query/join if that tables matching record(s)
3887  *            are to be locked
3888  *        - Ignores the keywords 'FOR UPDATE'.  Use the HOLDLOCK functionality described above
3889  *
3890  * SYBASE (Enterprise)
3891  *        - If a column is part of the Primary Key, the column cannot be NULL
3892  *        - Maximum row size is somewhere in the neighborhood of 1920 bytes
3893  *
3894  * MY_SQL
3895  *        - If a column is part of the Primary Key, the column cannot be NULL
3896  *        - Cannot support selecting for update [::CanSelectForUpdate()].  Always returns FALSE
3897  *        - Columns that are part of primary or secondary keys must be defined as being NOT NULL
3898  *            when they are created.  Some code is added in ::CreateIndex to try to adjust the
3899  *            column definition if it is not defined correctly, but it is experimental
3900  *        - Does not support sub-queries in SQL statements
3901  *
3902  * POSTGRES
3903  *        - Does not support the keywords 'ASC' or 'DESC' as of release v6.5.0
3904  *        - Does not support sub-queries in SQL statements
3905  *
3906  * DB2
3907  *        - Primary keys must be declared as NOT NULL
3908  *        - Table and index names must not be longer than 13 characters in length (technically
3909  *          table names can be up to 18 characters, but the primary index is created using the
3910  *          base table name plus "_PIDX", so the limit if the table has a primary index is 13.
3911  *
3912  * PERVASIVE SQL
3913  *
3914  * INTERBASE
3915  *        - Columns that are part of primary keys must be defined as being NOT NULL
3916  *          when they are created.  Some code is added in ::CreateIndex to try to adjust the
3917  *          column definition if it is not defined correctly, but it is experimental
3918  */
3919 {
3920     // Should only need to do this once for each new database connection
3921     // so return the value we already determined it to be to save time
3922     // and lots of string comparisons
3923     if (dbmsType != dbmsUNIDENTIFIED)
3924         return(dbmsType);
3925 
3926 #ifdef DBDEBUG_CONSOLE
3927                // When run in console mode, use standard out to display errors.
3928                cout << "Database connecting to: " << dbInf.dbmsName << endl;
3929 #endif  // DBDEBUG_CONSOLE
3930 
3931     wxLogDebug(wxT("Database connecting to: "));
3932     wxLogDebug(dbInf.dbmsName);
3933 
3934     wxChar baseName[25+1];
3935     wxStrncpy(baseName, dbInf.dbmsName, 25);
3936     baseName[25] = 0;
3937 
3938     // RGG 20001025 : add support for Interbase
3939     // GT : Integrated to base classes on 20001121
3940     if (!wxStricmp(dbInf.dbmsName,wxT("Interbase")))
3941         return((wxDBMS)(dbmsType = dbmsINTERBASE));
3942 
3943     // BJO 20000428 : add support for Virtuoso
3944     if (!wxStricmp(dbInf.dbmsName,wxT("OpenLink Virtuoso VDBMS")))
3945       return((wxDBMS)(dbmsType = dbmsVIRTUOSO));
3946 
3947     if (!wxStricmp(dbInf.dbmsName,wxT("Adaptive Server Anywhere")))
3948         return((wxDBMS)(dbmsType = dbmsSYBASE_ASA));
3949 
3950     // BJO 20000427 : The "SQL Server" string is also returned by SQLServer when
3951     // connected through an OpenLink driver.
3952     // Is it also returned by Sybase Adapatitve server?
3953     // OpenLink driver name is OLOD3032.DLL for msw and oplodbc.so for unix
3954     if (!wxStricmp(dbInf.dbmsName,wxT("SQL Server")))
3955     {
3956       if (!wxStrncmp(dbInf.driverName, wxT("oplodbc"), 7) ||
3957           !wxStrncmp(dbInf.driverName, wxT("OLOD"), 4))
3958             return ((wxDBMS)(dbmsMS_SQL_SERVER));
3959         else
3960             return ((wxDBMS)(dbmsType = dbmsSYBASE_ASE));
3961     }
3962 
3963     if (!wxStricmp(dbInf.dbmsName,wxT("Microsoft SQL Server")))
3964         return((wxDBMS)(dbmsType = dbmsMS_SQL_SERVER));
3965 
3966     baseName[10] = 0;
3967     if (!wxStricmp(baseName,wxT("PostgreSQL")))  // v6.5.0
3968         return((wxDBMS)(dbmsType = dbmsPOSTGRES));
3969 
3970     baseName[9] = 0;
3971     if (!wxStricmp(baseName,wxT("Pervasive")))
3972         return((wxDBMS)(dbmsType = dbmsPERVASIVE_SQL));
3973 
3974     baseName[8] = 0;
3975     if (!wxStricmp(baseName,wxT("Informix")))
3976         return((wxDBMS)(dbmsType = dbmsINFORMIX));
3977 
3978     if (!wxStricmp(baseName,wxT("Firebird")))
3979         return((wxDBMS)(dbmsType = dbmsFIREBIRD));
3980 
3981     baseName[6] = 0;
3982     if (!wxStricmp(baseName,wxT("Oracle")))
3983         return((wxDBMS)(dbmsType = dbmsORACLE));
3984     if (!wxStricmp(baseName,wxT("ACCESS")))
3985         return((wxDBMS)(dbmsType = dbmsACCESS));
3986     if (!wxStricmp(baseName,wxT("Sybase")))
3987       return((wxDBMS)(dbmsType = dbmsSYBASE_ASE));
3988 
3989     baseName[5] = 0;
3990     if (!wxStricmp(baseName,wxT("DBASE")))
3991         return((wxDBMS)(dbmsType = dbmsDBASE));
3992     if (!wxStricmp(baseName,wxT("xBase")))
3993         return((wxDBMS)(dbmsType = dbmsXBASE_SEQUITER));
3994     if (!wxStricmp(baseName,wxT("MySQL")))
3995         return((wxDBMS)(dbmsType = dbmsMY_SQL));
3996     if (!wxStricmp(baseName,wxT("MaxDB")))
3997         return((wxDBMS)(dbmsType = dbmsMAXDB));
3998 
3999     baseName[3] = 0;
4000     if (!wxStricmp(baseName,wxT("DB2")))
4001         return((wxDBMS)(dbmsType = dbmsDB2));
4002 
4003     return((wxDBMS)(dbmsType = dbmsUNIDENTIFIED));
4004 
4005 }  // wxDb::Dbms()
4006 
4007 
4008 bool wxDb::ModifyColumn(const wxString &tableName, const wxString &columnName,
4009                         int dataType, ULONG columnLength,
4010                         const wxString &optionalParam)
4011 {
4012     wxASSERT(tableName.length());
4013     wxASSERT(columnName.length());
4014     wxASSERT((dataType == DB_DATA_TYPE_VARCHAR && columnLength > 0) ||
4015              dataType != DB_DATA_TYPE_VARCHAR);
4016 
4017     // Must specify a columnLength if modifying a VARCHAR type column
4018     if (dataType == DB_DATA_TYPE_VARCHAR && !columnLength)
4019         return false;
4020 
4021     wxString dataTypeName;
4022     wxString sqlStmt;
4023     wxString alterSlashModify;
4024 
4025     switch(dataType)
4026     {
4027         case DB_DATA_TYPE_VARCHAR :
4028             dataTypeName = typeInfVarchar.TypeName;
4029             break;
4030         case DB_DATA_TYPE_INTEGER :
4031             dataTypeName = typeInfInteger.TypeName;
4032             break;
4033         case DB_DATA_TYPE_FLOAT :
4034             dataTypeName = typeInfFloat.TypeName;
4035             break;
4036         case DB_DATA_TYPE_DATE :
4037             dataTypeName = typeInfDate.TypeName;
4038             break;
4039         case DB_DATA_TYPE_BLOB :
4040             dataTypeName = typeInfBlob.TypeName;
4041             break;
4042         default:
4043             return false;
4044     }
4045 
4046     // Set the modify or alter syntax depending on the type of database connected to
4047     switch (Dbms())
4048     {
4049         case dbmsORACLE :
4050             alterSlashModify = _T("MODIFY");
4051             break;
4052         case dbmsMS_SQL_SERVER :
4053             alterSlashModify = _T("ALTER COLUMN");
4054             break;
4055         case dbmsUNIDENTIFIED :
4056             return false;
4057         case dbmsSYBASE_ASA :
4058         case dbmsSYBASE_ASE :
4059         case dbmsMY_SQL :
4060         case dbmsPOSTGRES :
4061         case dbmsACCESS :
4062         case dbmsDBASE :
4063         case dbmsXBASE_SEQUITER :
4064         default :
4065             alterSlashModify = _T("MODIFY");
4066             break;
4067     }
4068 
4069     // create the SQL statement
4070     if ( Dbms() == dbmsMY_SQL )
4071     {
4072         sqlStmt.Printf(wxT("ALTER TABLE %s %s %s %s"), tableName.c_str(), alterSlashModify.c_str(),
4073               columnName.c_str(), dataTypeName.c_str());
4074     }
4075     else
4076     {
4077         sqlStmt.Printf(wxT("ALTER TABLE \"%s\" \"%s\" \"%s\" %s"), tableName.c_str(), alterSlashModify.c_str(),
4078               columnName.c_str(), dataTypeName.c_str());
4079     }
4080 
4081     // For varchars only, append the size of the column
4082     if (dataType == DB_DATA_TYPE_VARCHAR &&
4083         (Dbms() != dbmsMY_SQL || dataTypeName != _T("text")))
4084     {
4085         wxString s;
4086         s.Printf(wxT("(%lu)"), columnLength);
4087         sqlStmt += s;
4088     }
4089 
4090     // for passing things like "NOT NULL"
4091     if (optionalParam.length())
4092     {
4093         sqlStmt += wxT(" ");
4094         sqlStmt += optionalParam;
4095     }
4096 
4097     return ExecSql(sqlStmt);
4098 
4099 } // wxDb::ModifyColumn()
4100 
4101 /********** wxDb::EscapeSqlChars() **********/
4102 wxString wxDb::EscapeSqlChars(const wxString& valueOrig)
4103 {
4104     wxString value(valueOrig);
4105     switch (Dbms())
4106     {
4107         case dbmsACCESS:
4108             // Access doesn't seem to care about backslashes, so only escape single quotes.
4109             value.Replace(wxT("'"), wxT("''"));
4110             break;
4111 
4112         default:
4113             // All the others are supposed to be the same for now, add special
4114             // handling for them if necessary
4115             value.Replace(wxT("\\"), wxT("\\\\"));
4116             value.Replace(wxT("'"), wxT("\\'"));
4117             break;
4118     }
4119 
4120     return value;
4121 } // wxDb::EscapeSqlChars()
4122 
4123 
4124 /********** wxDbGetConnection() **********/
4125 wxDb WXDLLIMPEXP_ODBC *wxDbGetConnection(wxDbConnectInf *pDbConfig, bool FwdOnlyCursors)
4126 {
4127     wxDbList *pList;
4128 
4129     // Used to keep a pointer to a DB connection that matches the requested
4130     // DSN and FwdOnlyCursors settings, even if it is not FREE, so that the
4131     // data types can be copied from it (using the wxDb::Open(wxDb *) function)
4132     // rather than having to re-query the datasource to get all the values
4133     // using the wxDb::Open(Dsn,Uid,AuthStr) function
4134     wxDb *matchingDbConnection = NULL;
4135 
4136     // Scan the linked list searching for an available database connection
4137     // that's already been opened but is currently not in use.
4138     for (pList = PtrBegDbList; pList; pList = pList->PtrNext)
4139     {
4140         // The database connection must be for the same datasource
4141         // name and must currently not be in use.
4142         if (pList->Free &&
4143             (pList->PtrDb->FwdOnlyCursors() == FwdOnlyCursors))
4144         {
4145             if (pDbConfig->UseConnectionStr())
4146             {
4147                 if (pList->PtrDb->OpenedWithConnectionString() &&
4148                      (!wxStrcmp(pDbConfig->GetConnectionStr(), pList->ConnectionStr)))
4149                 {
4150                     // Found a free connection
4151                     pList->Free = false;
4152                     return(pList->PtrDb);
4153                 }
4154             }
4155             else
4156             {
4157                 if (!pList->PtrDb->OpenedWithConnectionString() &&
4158                      (!wxStrcmp(pDbConfig->GetDsn(), pList->Dsn)))
4159                 {
4160                     // Found a free connection
4161                     pList->Free = false;
4162                     return(pList->PtrDb);
4163                 }
4164             }
4165         }
4166 
4167         if (pDbConfig->UseConnectionStr())
4168         {
4169             if (!wxStrcmp(pDbConfig->GetConnectionStr(), pList->ConnectionStr))
4170                 matchingDbConnection = pList->PtrDb;
4171         }
4172         else
4173         {
4174             if (!wxStrcmp(pDbConfig->GetDsn(), pList->Dsn) &&
4175                 !wxStrcmp(pDbConfig->GetUserID(), pList->Uid) &&
4176                 !wxStrcmp(pDbConfig->GetPassword(), pList->AuthStr))
4177                 matchingDbConnection = pList->PtrDb;
4178         }
4179     }
4180 
4181     // No available connections.  A new connection must be made and
4182     // appended to the end of the linked list.
4183     if (PtrBegDbList)
4184     {
4185         // Find the end of the list
4186         for (pList = PtrBegDbList; pList->PtrNext; pList = pList->PtrNext);
4187         // Append a new list item
4188         pList->PtrNext = new wxDbList;
4189         pList->PtrNext->PtrPrev = pList;
4190         pList = pList->PtrNext;
4191     }
4192     else  // Empty list
4193     {
4194         // Create the first node on the list
4195         pList = PtrBegDbList = new wxDbList;
4196         pList->PtrPrev = 0;
4197     }
4198 
4199     // Initialize new node in the linked list
4200     pList->PtrNext          = 0;
4201     pList->Free             = false;
4202     pList->Dsn              = pDbConfig->GetDsn();
4203     pList->Uid              = pDbConfig->GetUserID();
4204     pList->AuthStr          = pDbConfig->GetPassword();
4205     pList->ConnectionStr    = pDbConfig->GetConnectionStr();
4206 
4207     pList->PtrDb = new wxDb(pDbConfig->GetHenv(), FwdOnlyCursors);
4208 
4209     bool opened;
4210 
4211     if (!matchingDbConnection)
4212     {
4213         if (pDbConfig->UseConnectionStr())
4214         {
4215             opened = pList->PtrDb->Open(pDbConfig->GetConnectionStr());
4216         }
4217         else
4218         {
4219             opened = pList->PtrDb->Open(pDbConfig->GetDsn(), pDbConfig->GetUserID(), pDbConfig->GetPassword());
4220         }
4221     }
4222     else
4223         opened = pList->PtrDb->Open(matchingDbConnection);
4224 
4225     // Connect to the datasource
4226     if (opened)
4227     {
4228         pList->PtrDb->setCached(true);  // Prevent a user from deleting a cached connection
4229         pList->PtrDb->SetSqlLogging(SQLLOGstate, SQLLOGfn, true);
4230         return(pList->PtrDb);
4231     }
4232     else  // Unable to connect, destroy list item
4233     {
4234         if (pList->PtrPrev)
4235             pList->PtrPrev->PtrNext = 0;
4236         else
4237             PtrBegDbList = 0;        // Empty list again
4238 
4239         pList->PtrDb->CommitTrans(); // Commit any open transactions on wxDb object
4240         pList->PtrDb->Close();       // Close the wxDb object
4241         delete pList->PtrDb;         // Deletes the wxDb object
4242         delete pList;                // Deletes the linked list object
4243         return(0);
4244     }
4245 
4246 }  // wxDbGetConnection()
4247 
4248 
4249 /********** wxDbFreeConnection() **********/
4250 bool WXDLLIMPEXP_ODBC wxDbFreeConnection(wxDb *pDb)
4251 {
4252     wxDbList *pList;
4253 
4254     // Scan the linked list searching for the database connection
4255     for (pList = PtrBegDbList; pList; pList = pList->PtrNext)
4256     {
4257         if (pList->PtrDb == pDb)  // Found it, now free it!!!
4258             return (pList->Free = true);
4259     }
4260 
4261     // Never found the database object, return failure
4262     return false;
4263 
4264 }  // wxDbFreeConnection()
4265 
4266 
4267 /********** wxDbCloseConnections() **********/
4268 void WXDLLIMPEXP_ODBC wxDbCloseConnections(void)
4269 {
4270     wxDbList *pList, *pNext;
4271 
4272     // Traverse the linked list closing database connections and freeing memory as I go.
4273     for (pList = PtrBegDbList; pList; pList = pNext)
4274     {
4275         pNext = pList->PtrNext;       // Save the pointer to next
4276         pList->PtrDb->CommitTrans();  // Commit any open transactions on wxDb object
4277         pList->PtrDb->Close();        // Close the wxDb object
4278         pList->PtrDb->setCached(false);  // Allows deletion of the wxDb instance
4279         delete pList->PtrDb;          // Deletes the wxDb object
4280         delete pList;                 // Deletes the linked list object
4281     }
4282 
4283     // Mark the list as empty
4284     PtrBegDbList = 0;
4285 
4286 }  // wxDbCloseConnections()
4287 
4288 
4289 /********** wxDbConnectionsInUse() **********/
4290 int WXDLLIMPEXP_ODBC wxDbConnectionsInUse(void)
4291 {
4292     wxDbList *pList;
4293     int cnt = 0;
4294 
4295     // Scan the linked list counting db connections that are currently in use
4296     for (pList = PtrBegDbList; pList; pList = pList->PtrNext)
4297     {
4298         if (pList->Free == false)
4299             cnt++;
4300     }
4301 
4302     return(cnt);
4303 
4304 }  // wxDbConnectionsInUse()
4305 
4306 
4307 
4308 /********** wxDbLogExtendedErrorMsg() **********/
4309 // DEBUG ONLY function
4310 const wxChar WXDLLIMPEXP_ODBC *wxDbLogExtendedErrorMsg(const wxChar *userText,
4311                                                   wxDb *pDb,
4312                                                   const wxChar *ErrFile,
4313                                                   int ErrLine)
4314 {
4315     static wxString msg;
4316     msg = userText;
4317 
4318     wxString tStr;
4319 
4320     if (ErrFile || ErrLine)
4321     {
4322         msg += wxT("File: ");
4323         msg += ErrFile;
4324         msg += wxT("   Line: ");
4325         tStr.Printf(wxT("%d"),ErrLine);
4326         msg += tStr.c_str();
4327         msg += wxT("\n");
4328     }
4329 
4330     msg.Append (wxT("\nODBC errors:\n"));
4331     msg += wxT("\n");
4332 
4333     // Display errors for this connection
4334     int i;
4335     for (i = 0; i < DB_MAX_ERROR_HISTORY; i++)
4336     {
4337         if (pDb->errorList[i])
4338         {
4339             msg.Append(pDb->errorList[i]);
4340             if (wxStrcmp(pDb->errorList[i], wxEmptyString) != 0)
4341                 msg.Append(wxT("\n"));
4342             // Clear the errmsg buffer so the next error will not
4343             // end up showing the previous error that have occurred
4344             wxStrcpy(pDb->errorList[i], wxEmptyString);
4345         }
4346     }
4347     msg += wxT("\n");
4348 
4349     wxLogDebug(msg.c_str());
4350 
4351     return msg.c_str();
4352 }  // wxDbLogExtendedErrorMsg()
4353 
4354 
4355 /********** wxDbSqlLog() **********/
4356 bool wxDbSqlLog(wxDbSqlLogState state, const wxChar *filename)
4357 {
4358     bool append = false;
4359     wxDbList *pList;
4360 
4361     for (pList = PtrBegDbList; pList; pList = pList->PtrNext)
4362     {
4363         if (!pList->PtrDb->SetSqlLogging(state,filename,append))
4364             return false;
4365         append = true;
4366     }
4367 
4368     SQLLOGstate = state;
4369     SQLLOGfn = filename;
4370 
4371     return true;
4372 
4373 }  // wxDbSqlLog()
4374 
4375 
4376 #if 0
4377 /********** wxDbCreateDataSource() **********/
4378 int wxDbCreateDataSource(const wxString &driverName, const wxString &dsn, const wxString &description,
4379                          bool sysDSN, const wxString &defDir, wxWindow *parent)
4380 /*
4381  * !!!! ONLY FUNCTIONAL UNDER MSW with VC6 !!!!
4382  * Very rudimentary creation of an ODBC data source.
4383  *
4384  * ODBC driver must be ODBC 3.0 compliant to use this function
4385  */
4386 {
4387     int result = FALSE;
4388 
4389 //!!!! ONLY FUNCTIONAL UNDER MSW with VC6 !!!!
4390 #ifdef __VISUALC__
4391     int       dsnLocation;
4392     wxString  setupStr;
4393 
4394     if (sysDSN)
4395         dsnLocation = ODBC_ADD_SYS_DSN;
4396     else
4397         dsnLocation = ODBC_ADD_DSN;
4398 
4399     // NOTE: The decimal 2 is an invalid character in all keyword pairs
4400     // so that is why I used it, as wxString does not deal well with
4401     // embedded nulls in strings
4402     setupStr.Printf(wxT("DSN=%s%cDescription=%s%cDefaultDir=%s%c"),dsn,2,description,2,defDir,2);
4403 
4404     // Replace the separator from above with the '\0' separator needed
4405     // by the SQLConfigDataSource() function
4406     int k;
4407     do
4408     {
4409         k = setupStr.Find((wxChar)2,true);
4410         if (k != wxNOT_FOUND)
4411             setupStr[(UINT)k] = wxT('\0');
4412     }
4413     while (k != wxNOT_FOUND);
4414 
4415     result = SQLConfigDataSource((HWND)parent->GetHWND(), dsnLocation,
4416                                  driverName, setupStr.c_str());
4417 
4418     if ((result != SQL_SUCCESS) && (result != SQL_SUCCESS_WITH_INFO))
4419     {
4420         // check for errors caused by ConfigDSN based functions
4421         DWORD retcode = 0;
4422         WORD cb;
4423         wxChar errMsg[SQL_MAX_MESSAGE_LENGTH];
4424         errMsg[0] = wxT('\0');
4425 
4426         // This function is only supported in ODBC drivers v3.0 compliant and above
4427         SQLInstallerError(1,&retcode,errMsg,SQL_MAX_MESSAGE_LENGTH-1,&cb);
4428         if (retcode)
4429         {
4430 #ifdef DBDEBUG_CONSOLE
4431                // When run in console mode, use standard out to display errors.
4432                cout << errMsg << endl;
4433                cout << wxT("Press any key to continue...") << endl;
4434                getchar();
4435 #endif  // DBDEBUG_CONSOLE
4436 
4437 #ifdef __WXDEBUG__
4438                wxLogDebug(errMsg,wxT("ODBC DEBUG MESSAGE"));
4439 #endif  // __WXDEBUG__
4440         }
4441     }
4442     else
4443        result = TRUE;
4444 #else
4445     // Using iODBC/unixODBC or some other compiler which does not support the APIs
4446     // necessary to use this function, so this function is not supported
4447 #ifdef __WXDEBUG__
4448     wxLogDebug(wxT("wxDbCreateDataSource() not available except under VC++/MSW"),wxT("ODBC DEBUG MESSAGE"));
4449 #endif
4450     result = FALSE;
4451 #endif  // __VISUALC__
4452 
4453     return result;
4454 
4455 }  // wxDbCreateDataSource()
4456 #endif
4457 
4458 
4459 /********** wxDbGetDataSource() **********/
4460 bool wxDbGetDataSource(HENV henv, wxChar *Dsn, SWORD DsnMaxLength, wxChar *DsDesc,
4461                        SWORD DsDescMaxLength, UWORD direction)
4462 /*
4463  * Dsn and DsDesc will contain the data source name and data source
4464  * description upon return
4465  */
4466 {
4467     SWORD cb1,cb2;
4468     SWORD lengthDsn = (SWORD)(DsnMaxLength*sizeof(wxChar));
4469     SWORD lengthDsDesc = (SWORD)(DsDescMaxLength*sizeof(wxChar));
4470 
4471     if (SQLDataSources(henv, direction, (SQLTCHAR FAR *) Dsn, lengthDsn, &cb1,
4472                        (SQLTCHAR FAR *) DsDesc, lengthDsDesc, &cb2) == SQL_SUCCESS)
4473         return true;
4474     else
4475         return false;
4476 
4477 }  // wxDbGetDataSource()
4478 
4479 
4480 // Change this to 0 to remove use of all deprecated functions
4481 #if wxODBC_BACKWARD_COMPATABILITY
4482 /********************************************************************
4483  ********************************************************************
4484  *
4485  * The following functions are all DEPRECATED and are included for
4486  * backward compatibility reasons only
4487  *
4488  ********************************************************************
4489  ********************************************************************/
4490 bool SqlLog(sqlLog state, const wxChar *filename)
4491 {
4492     return wxDbSqlLog((enum wxDbSqlLogState)state, filename);
4493 }
4494 /***** DEPRECATED: use wxGetDataSource() *****/
4495 bool GetDataSource(HENV henv, char *Dsn, SWORD DsnMax, char *DsDesc, SWORD DsDescMax,
4496                          UWORD direction)
4497 {
4498     return wxDbGetDataSource(henv, Dsn, DsnMax, DsDesc, DsDescMax, direction);
4499 }
4500 /***** DEPRECATED: use wxDbGetConnection() *****/
4501 wxDb WXDLLIMPEXP_ODBC *GetDbConnection(DbStuff *pDbStuff, bool FwdOnlyCursors)
4502 {
4503     return wxDbGetConnection((wxDbConnectInf *)pDbStuff, FwdOnlyCursors);
4504 }
4505 /***** DEPRECATED: use wxDbFreeConnection() *****/
4506 bool WXDLLIMPEXP_ODBC FreeDbConnection(wxDb *pDb)
4507 {
4508     return wxDbFreeConnection(pDb);
4509 }
4510 /***** DEPRECATED: use wxDbCloseConnections() *****/
4511 void WXDLLIMPEXP_ODBC CloseDbConnections(void)
4512 {
4513     wxDbCloseConnections();
4514 }
4515 /***** DEPRECATED: use wxDbConnectionsInUse() *****/
4516 int WXDLLIMPEXP_ODBC NumberDbConnectionsInUse(void)
4517 {
4518     return wxDbConnectionsInUse();
4519 }
4520 #endif
4521 
4522 
4523 #endif
4524  // wxUSE_ODBC
4525