]> git.saurik.com Git - wxWidgets.git/commitdiff
Added ODBC database classes and sample from RemStar (sample needs work for wxWin 2)
authorJulian Smart <julian@anthemion.co.uk>
Tue, 28 Jul 1998 09:43:44 +0000 (09:43 +0000)
committerJulian Smart <julian@anthemion.co.uk>
Tue, 28 Jul 1998 09:43:44 +0000 (09:43 +0000)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@385 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

16 files changed:
include/wx/db.h [new file with mode: 0644]
include/wx/dbtable.h [new file with mode: 0644]
include/wx/msw/setup.h
samples/db/dbtest.cpp [new file with mode: 0644]
samples/db/dbtest.def [new file with mode: 0644]
samples/db/dbtest.h [new file with mode: 0644]
samples/db/dbtest.ico [new file with mode: 0644]
samples/db/dbtest.rc [new file with mode: 0644]
samples/db/listdb.cpp [new file with mode: 0644]
samples/db/listdb.h [new file with mode: 0644]
samples/db/makefile.nt [new file with mode: 0644]
samples/db/makefile.unx [new file with mode: 0644]
samples/forty/playerdg.cpp
src/common/db.cpp [new file with mode: 0644]
src/common/dbtable.cpp [new file with mode: 0644]
src/msw/makefile.nt

diff --git a/include/wx/db.h b/include/wx/db.h
new file mode 100644 (file)
index 0000000..9f47abf
--- /dev/null
@@ -0,0 +1,362 @@
+///////////////////////////////////////////////////////////////////////////////
+// Name:        db.h
+// Purpose:     Header file wxDB class.  The wxDB class represents a connection
+//              to an ODBC data source.  The wxDB class allows operations on the data
+//              source such as opening and closing the data source.
+// Author:      Doug Card
+// Modified by:
+// Created:     9.96
+// RCS-ID:      $Id$
+// Copyright:   (c) 1996 Remstar International, Inc.
+// Licence:     wxWindows licence, plus:
+// Notice:             This class library and its intellectual design are free of charge for use,
+//              modification, enhancement, debugging under the following conditions:
+//              1) These classes may only be used as part of the implementation of a
+//                 wxWindows-based application
+//              2) All enhancements and bug fixes are to be submitted back to the wxWindows
+//                 user groups free of all charges for use with the wxWindows library.
+//              3) These classes may not be distributed as part of any other class library,
+//                 DLL, text (written or electronic), other than a complete distribution of
+//                 the wxWindows GUI development toolkit.
+///////////////////////////////////////////////////////////////////////////////
+
+/*
+// SYNOPSIS START
+// SYNOPSIS STOP
+*/
+
+#ifndef DB_DOT_H
+#define DB_DOT_H
+
+#ifdef __GNUG__
+#pragma interface "db.h"
+#endif
+
+#if defined(wx_msw) || defined(WIN32)
+#include <windows.h>
+#endif
+
+#define ODBCVER 0x0250
+#include <sql.h>
+#include <sqlext.h>
+
+enum           enumDummy               {enumDum1};
+
+#define SQL_C_BOOLEAN (sizeof(int) == 2 ? SQL_C_USHORT : SQL_C_ULONG)
+#define SQL_C_ENUM (sizeof(enumDummy) == 2 ? SQL_C_USHORT : SQL_C_ULONG)    //glt 2-21-97
+
+/*
+#ifndef Bool
+#define Bool int
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+*/
+
+// Database Globals
+const DB_TYPE_NAME_LEN                                         = 40;
+const DB_MAX_STATEMENT_LEN                                     = 2048;
+const DB_MAX_WHERE_CLAUSE_LEN                          = 1024;
+const DB_MAX_ERROR_MSG_LEN                                     = 512;
+const DB_MAX_ERROR_HISTORY                                     = 5;
+const DB_MAX_TABLE_NAME_LEN                            = 128;
+const DB_MAX_COLUMN_NAME_LEN                           = 128;
+
+const DB_DATA_TYPE_VARCHAR                                     = 1;
+const DB_DATA_TYPE_INTEGER                                     = 2;
+const DB_DATA_TYPE_FLOAT                                       = 3;
+const  DB_DATA_TYPE_DATE                                               = 4;
+
+const DB_SELECT_KEYFIELDS                                      = 1;
+const DB_SELECT_WHERE                                          = 2;
+const DB_SELECT_MATCHING                                       = 3;
+const DB_SELECT_STATEMENT                                      = 4;
+
+const DB_UPD_KEYFIELDS                                         = 1;
+const DB_UPD_WHERE                                                     = 2;
+
+const DB_DEL_KEYFIELDS                                         = 1;
+const DB_DEL_WHERE                                                     = 2;
+const DB_DEL_MATCHING                                          = 3;
+
+const DB_WHERE_KEYFIELDS                                       = 1;
+const DB_WHERE_MATCHING                                                = 2;
+
+const DB_CURSOR0                                                               = 0;
+const DB_CURSOR1                                                               = 1;
+const DB_CURSOR2                                                               = 2;
+//const DB_CURSOR3                                                     = 3;
+//const DB_CURSOR4                                                     = 4;
+//const DB_CURSOR5                                                     = 5;
+
+const DB_GRANT_SELECT                                          = 1;
+const DB_GRANT_INSERT                                          = 2;
+const DB_GRANT_UPDATE                                          = 4;
+const DB_GRANT_DELETE                                          = 8;
+const DB_GRANT_ALL                                                     = DB_GRANT_SELECT | DB_GRANT_INSERT | DB_GRANT_UPDATE | DB_GRANT_DELETE;
+
+// ODBC Error codes (derived from ODBC SqlState codes)
+enum ODBC_ERRORS
+{
+       DB_FAILURE                                                                      = 0,
+       DB_SUCCESS                                                                      = 1,
+       DB_ERR_NOT_IN_USE,
+       DB_ERR_GENERAL_WARNING,                                                                 // SqlState = '01000'
+       DB_ERR_DISCONNECT_ERROR,                                                                // SqlState = '01002'
+       DB_ERR_DATA_TRUNCATED,                                                                  // SqlState = '01004'
+       DB_ERR_PRIV_NOT_REVOKED,                                                                // SqlState = '01006'
+       DB_ERR_INVALID_CONN_STR_ATTR,                                                   // SqlState = '01S00'
+       DB_ERR_ERROR_IN_ROW,                                                                            // SqlState = '01S01'
+       DB_ERR_OPTION_VALUE_CHANGED,                                                    // SqlState = '01S02'
+       DB_ERR_NO_ROWS_UPD_OR_DEL,                                                              // SqlState = '01S03'
+       DB_ERR_MULTI_ROWS_UPD_OR_DEL,                                                   // SqlState = '01S04'
+       DB_ERR_WRONG_NO_OF_PARAMS,                                                              // SqlState = '07001'
+       DB_ERR_DATA_TYPE_ATTR_VIOL,                                                     // SqlState = '07006'
+       DB_ERR_UNABLE_TO_CONNECT,                                                               // SqlState = '08001'
+       DB_ERR_CONNECTION_IN_USE,                                                               // SqlState = '08002'
+       DB_ERR_CONNECTION_NOT_OPEN,                                                     // SqlState = '08003'
+       DB_ERR_REJECTED_CONNECTION,                                                     // SqlState = '08004'
+       DB_ERR_CONN_FAIL_IN_TRANS,                                                              // SqlState = '08007'
+       DB_ERR_COMM_LINK_FAILURE,                                                               // SqlState = '08S01'
+       DB_ERR_INSERT_VALUE_LIST_MISMATCH,                                      // SqlState = '21S01'
+       DB_ERR_DERIVED_TABLE_MISMATCH,                                          // SqlState = '21S02'
+       DB_ERR_STRING_RIGHT_TRUNC,                                                              // SqlState = '22001'
+       DB_ERR_NUMERIC_VALUE_OUT_OF_RNG,                                                // SqlState = '22003'
+       DB_ERR_ERROR_IN_ASSIGNMENT,                                                     // SqlState = '22005'
+       DB_ERR_DATETIME_FLD_OVERFLOW,                                                   // SqlState = '22008'
+       DB_ERR_DIVIDE_BY_ZERO,                                                                  // SqlState = '22012'
+       DB_ERR_STR_DATA_LENGTH_MISMATCH,                                                // SqlState = '22026'
+       DB_ERR_INTEGRITY_CONSTRAINT_VIOL,                                       // SqlState = '23000'
+       DB_ERR_INVALID_CURSOR_STATE,                                                    // SqlState = '24000'
+       DB_ERR_INVALID_TRANS_STATE,                                                     // SqlState = '25000'
+       DB_ERR_INVALID_AUTH_SPEC,                                                               // SqlState = '28000'
+       DB_ERR_INVALID_CURSOR_NAME,                                                     // SqlState = '34000'
+       DB_ERR_SYNTAX_ERROR_OR_ACCESS_VIOL,                                     // SqlState = '37000'
+       DB_ERR_DUPLICATE_CURSOR_NAME,                                                   // SqlState = '3C000'
+       DB_ERR_SERIALIZATION_FAILURE,                                                   // SqlState = '40001'
+       DB_ERR_SYNTAX_ERROR_OR_ACCESS_VIOL2,                            // SqlState = '42000'
+       DB_ERR_OPERATION_ABORTED,                                                               // SqlState = '70100'
+       DB_ERR_UNSUPPORTED_FUNCTION,                                                    // SqlState = 'IM001'
+       DB_ERR_NO_DATA_SOURCE,                                                                  // SqlState = 'IM002'
+       DB_ERR_DRIVER_LOAD_ERROR,                                                               // SqlState = 'IM003'
+       DB_ERR_SQLALLOCENV_FAILED,                                                              // SqlState = 'IM004'
+       DB_ERR_SQLALLOCCONNECT_FAILED,                                          // SqlState = 'IM005'
+       DB_ERR_SQLSETCONNECTOPTION_FAILED,                                      // SqlState = 'IM006'
+       DB_ERR_NO_DATA_SOURCE_DLG_PROHIB,                                       // SqlState = 'IM007'
+       DB_ERR_DIALOG_FAILED,                                                                   // SqlState = 'IM008'
+       DB_ERR_UNABLE_TO_LOAD_TRANSLATION_DLL,                          // SqlState = 'IM009'
+       DB_ERR_DATA_SOURCE_NAME_TOO_LONG,                                       // SqlState = 'IM010'
+       DB_ERR_DRIVER_NAME_TOO_LONG,                                                    // SqlState = 'IM011'
+       DB_ERR_DRIVER_KEYWORD_SYNTAX_ERROR,                                     // SqlState = 'IM012'
+       DB_ERR_TRACE_FILE_ERROR,                                                                // SqlState = 'IM013'
+       DB_ERR_TABLE_OR_VIEW_ALREADY_EXISTS,                            // SqlState = 'S0001'
+       DB_ERR_TABLE_NOT_FOUND,                                                                 // SqlState = 'S0002'
+       DB_ERR_INDEX_ALREADY_EXISTS,                                                    // SqlState = 'S0011'
+       DB_ERR_INDEX_NOT_FOUND,                                                                 // SqlState = 'S0012'
+       DB_ERR_COLUMN_ALREADY_EXISTS,                                                   // SqlState = 'S0021'
+       DB_ERR_COLUMN_NOT_FOUND,                                                                // SqlState = 'S0022'
+       DB_ERR_NO_DEFAULT_FOR_COLUMN,                                                   // SqlState = 'S0023'
+       DB_ERR_GENERAL_ERROR,                                                                   // SqlState = 'S1000'
+       DB_ERR_MEMORY_ALLOCATION_FAILURE,                                       // SqlState = 'S1001'
+       DB_ERR_INVALID_COLUMN_NUMBER,                                                   // SqlState = 'S1002'
+       DB_ERR_PROGRAM_TYPE_OUT_OF_RANGE,                                       // SqlState = 'S1003'
+       DB_ERR_SQL_DATA_TYPE_OUT_OF_RANGE,                                      // SqlState = 'S1004'
+       DB_ERR_OPERATION_CANCELLED,                                                     // SqlState = 'S1008'
+       DB_ERR_INVALID_ARGUMENT_VALUE,                                          // SqlState = 'S1009'
+       DB_ERR_FUNCTION_SEQUENCE_ERROR,                                         // SqlState = 'S1010'
+       DB_ERR_OPERATION_INVALID_AT_THIS_TIME,                          // SqlState = 'S1011'
+       DB_ERR_INVALID_TRANS_OPERATION_CODE,                            // SqlState = 'S1012'
+       DB_ERR_NO_CURSOR_NAME_AVAIL,                                                    // SqlState = 'S1015'
+       DB_ERR_INVALID_STR_OR_BUF_LEN,                                          // SqlState = 'S1090'
+       DB_ERR_DESCRIPTOR_TYPE_OUT_OF_RANGE,                            // SqlState = 'S1091'
+       DB_ERR_OPTION_TYPE_OUT_OF_RANGE,                                                // SqlState = 'S1092'
+       DB_ERR_INVALID_PARAM_NO,                                                                // SqlState = 'S1093'
+       DB_ERR_INVALID_SCALE_VALUE,                                                     // SqlState = 'S1094'
+       DB_ERR_FUNCTION_TYPE_OUT_OF_RANGE,                                      // SqlState = 'S1095'
+       DB_ERR_INF_TYPE_OUT_OF_RANGE,                                                   // SqlState = 'S1096'
+       DB_ERR_COLUMN_TYPE_OUT_OF_RANGE,                                                // SqlState = 'S1097'
+       DB_ERR_SCOPE_TYPE_OUT_OF_RANGE,                                         // SqlState = 'S1098'
+       DB_ERR_NULLABLE_TYPE_OUT_OF_RANGE,                                      // SqlState = 'S1099'
+       DB_ERR_UNIQUENESS_OPTION_TYPE_OUT_OF_RANGE,             // SqlState = 'S1100'
+       DB_ERR_ACCURACY_OPTION_TYPE_OUT_OF_RANGE,                       // SqlState = 'S1101'
+       DB_ERR_DIRECTION_OPTION_OUT_OF_RANGE,                           // SqlState = 'S1103'
+       DB_ERR_INVALID_PRECISION_VALUE,                                         // SqlState = 'S1104'
+       DB_ERR_INVALID_PARAM_TYPE,                                                              // SqlState = 'S1105'
+       DB_ERR_FETCH_TYPE_OUT_OF_RANGE,                                         // SqlState = 'S1106'
+       DB_ERR_ROW_VALUE_OUT_OF_RANGE,                                          // SqlState = 'S1107'
+       DB_ERR_CONCURRENCY_OPTION_OUT_OF_RANGE,                 // SqlState = 'S1108'
+       DB_ERR_INVALID_CURSOR_POSITION,                                         // SqlState = 'S1109'
+       DB_ERR_INVALID_DRIVER_COMPLETION,                                       // SqlState = 'S1110'
+       DB_ERR_INVALID_BOOKMARK_VALUE,                                          // SqlState = 'S1111'
+       DB_ERR_DRIVER_NOT_CAPABLE,                                                              // SqlState = 'S1C00'
+       DB_ERR_TIMEOUT_EXPIRED                                                                  // SqlState = 'S1T00'
+};
+
+struct DbStuff
+{
+       HENV Henv;
+       char Dsn[SQL_MAX_DSN_LENGTH+1]; // Data Source Name
+       char Uid[20];                                                   // User ID
+       char AuthStr[20];                                               // Authorization string (password)
+};
+
+typedef struct
+{
+       char    TypeName[DB_TYPE_NAME_LEN];
+       int     FsqlType;
+       long    Precision;
+       short   CaseSensitive;
+//     short MinimumScale;
+       short MaximumScale;
+} SqlTypeInfo;
+
+class CcolInf
+{
+public:
+       char tableName[DB_MAX_TABLE_NAME_LEN+1];
+       char colName[DB_MAX_COLUMN_NAME_LEN+1];
+       int  sqlDataType;
+};
+
+class wxDB
+{
+private:
+
+       // Private data
+       bool dbIsOpen;
+       char *dsn;                      // Data source name
+       char *uid;                      // User ID
+       char *authStr;          // Authorization string (password)
+
+       // Private member functions
+       bool getDbInfo(void);
+       bool getDataTypeInfo(SWORD fSqlType, SqlTypeInfo &structSQLTypeInfo);
+       bool setConnectionOptions(void);
+       void logError(char *errMsg, char *SQLState);
+
+public:
+
+       // The following structure contains database information gathered from the
+       // datasource when the datasource is first opened.
+       struct
+       {
+               char   dbmsName[40];                                                            // Name of the dbms product
+               char   dbmsVer[20];                                                             // Version # of the dbms product
+               char   driverName[40];                                                  // Driver name
+               char   odbcVer[20];                                                             // ODBC version of the driver
+               char   drvMgrOdbcVer[20];                                               // ODBC version of the driver manager
+               char   driverVer[40];                                                   // Driver version
+               char   serverName[40];                                                  // Server Name, typically a connect string
+               char     databaseName[128];                                             // Database filename
+               char   outerJoins[2];                                                   // Indicates whether the data source supports outer joins
+               char     procedureSupport[2];                                   // Indicates whether the data source supports stored procedures
+               UWORD  maxConnections;                                                  // Maximum # of connections the data source supports
+               UWORD  maxStmts;                                                                        // Maximum # of HSTMTs per HDBC
+               UWORD  apiConfLvl;                                                              // ODBC API conformance level
+               UWORD  cliConfLvl;                                                              // Indicates whether the data source is SAG compliant
+               UWORD  sqlConfLvl;                                                              // SQL conformance level
+               UWORD  cursorCommitBehavior;                                    // Indicates how cursors are affected by a db commit
+               UWORD  cursorRollbackBehavior;                          // Indicates how cursors are affected by a db rollback
+               UWORD  supportNotNullClause;                                    // Indicates if data source supports NOT NULL clause
+               char   supportIEF[2];                                                   // Integrity Enhancement Facility (Referential Integrity)
+               UDWORD txnIsolation;                                                            // Default transaction isolation level supported by the driver
+               UDWORD txnIsolationOptions;                                     // Transaction isolation level options available
+               UDWORD fetchDirections;                                                 // Fetch directions supported
+               UDWORD lockTypes;                                                                       // Lock types supported in SQLSetPos
+               UDWORD posOperations;                                                   // Position operations supported in SQLSetPos
+               UDWORD posStmts;                                                                        // Position statements supported
+               UDWORD scrollConcurrency;                                               // Concurrency control options supported for scrollable cursors
+               UDWORD scrollOptions;                                                   // Scroll Options supported for scrollable cursors
+               UDWORD staticSensitivity;                                               // Indicates if additions, deletions and updates can be detected
+               UWORD  txnCapable;                                                              // Indicates if the data source supports transactions
+               UDWORD loginTimeout;                                                            // Number seconds to wait for a login request
+       } dbInf;
+
+       // ODBC handles
+       HENV  henv;             // ODBC Environment handle
+       HDBC  hdbc;             // ODBC DB Connection handle
+       HSTMT hstmt;    // ODBC Statement handle
+
+       // ODBC Error Inf.
+       char   sqlState[20];
+       SDWORD nativeError;
+       char   errorMsg[SQL_MAX_MESSAGE_LENGTH];
+       SWORD  cbErrorMsg;
+       char   errorList[DB_MAX_ERROR_HISTORY][DB_MAX_ERROR_MSG_LEN];
+       int    DB_STATUS;
+
+       //Error reporting mode
+       bool silent;
+
+       // Inf. about logical data types VARCHAR, INTEGER, FLOAT and DATE.
+       // This inf. is obtained from the ODBC driver by use of the
+       // SQLGetTypeInfo() function.  The key piece of inf. is the
+       // type name the data source uses for each logical data type.
+       // e.g. VARCHAR; Oracle calls it VARCHAR2.
+       SqlTypeInfo typeInfVarchar, typeInfInteger, typeInfFloat, typeInfDate;
+       
+       // Public member functions
+       wxDB(HENV &aHenv);
+       bool            Open(char *Dsn, char *Uid, char *AuthStr);  // Data Source Name, User ID, Password
+       void            Close(void);
+       bool            CommitTrans(void);
+       bool            RollbackTrans(void);
+   bool                DispAllErrors(HENV aHenv, HDBC aHdbc = SQL_NULL_HDBC, HSTMT aHstmt = SQL_NULL_HSTMT);
+   bool                GetNextError(HENV aHenv, HDBC aHdbc = SQL_NULL_HDBC, HSTMT aHstmt = SQL_NULL_HSTMT);
+   void                DispNextError(void);
+       bool            CreateView(char *viewName, char *colList, char *pSqlStmt);
+       bool            ExecSql(char *pSqlStmt);
+       bool       Grant(int privileges, char *tableName, char *userList = "PUBLIC");
+       int        TranslateSqlState(char *SQLState);
+       CcolInf *GetColumns(char *tableName[]);
+       char      *GetDatabaseName(void)        {return dbInf.dbmsName;}
+       char      *GetDataSource(void)          {return dsn;}
+       char      *GetUsername(void)                    {return uid;}
+       char      *GetPassword(void)                    {return authStr;}
+       bool            IsOpen(void)                            {return dbIsOpen;}
+       HENV            GetHENV(void)                           {return henv;}
+       HDBC            GetHDBC(void)                           {return hdbc;}
+       HSTMT           GetHSTMT(void)                          {return hstmt;}
+       bool            TableExists(char *tableName);  // Table name can refer to a table, view, alias or synonym
+       void            LogError(char *errMsg, char *SQLState = 0) {logError(errMsg, SQLState);}
+
+};  // wxDB
+
+// This structure forms a node in a linked list.  The linked list of "DbList" objects
+// keeps track of allocated database connections.  This allows the application to
+// open more than one database connection through ODBC for multiple transaction support
+// or for multiple database support.
+
+struct DbList
+{
+       DbList *PtrPrev;                                                        // Pointer to previous item in the list
+       char    Dsn[SQL_MAX_DSN_LENGTH+1];      // Data Source Name
+       wxDB     *PtrDb;                                                                // Pointer to the wxDB object
+       bool    Free;                                                           // Is item free or in use?
+       DbList *PtrNext;                                                        // Pointer to next item in the list
+};
+
+// The following routines allow a user to get new database connections, free them
+// for other code segments to use, or close all of them when the application has
+// completed.
+
+wxDB  *GetDbConnection(DbStuff *pDbStuff);
+bool  FreeDbConnection(wxDB *pDb);
+void  CloseDbConnections(void);
+int    NumberDbConnectionsInUse(void);
+
+// This routine allows you to query a driver manager
+// for a list of available datasources.  Call this routine
+// the first time using SQL_FETCH_FIRST.  Continue to call it
+// using SQL_FETCH_NEXT until you've exhausted the list.
+bool GetDataSource(HENV henv, char *Dsn, SWORD DsnMax, char *DsDesc, SWORD DsDescMax,
+                                                UWORD direction = SQL_FETCH_NEXT);
+
+#endif
+
diff --git a/include/wx/dbtable.h b/include/wx/dbtable.h
new file mode 100644 (file)
index 0000000..23e6d10
--- /dev/null
@@ -0,0 +1,165 @@
+///////////////////////////////////////////////////////////////////////////////
+// Name:        table.h
+// Purpose:     Declaration of the wxTable class.
+// Author:      Doug Card
+// Modified by:
+// Created:     9.96
+// RCS-ID:      $Id$
+// Copyright:   (c) 1996 Remstar International, Inc.
+// Licence:     wxWindows licence, plus:
+// Notice:             This class library and its intellectual design are free of charge for use,
+//              modification, enhancement, debugging under the following conditions:
+//              1) These classes may only be used as part of the implementation of a
+//                 wxWindows-based application
+//              2) All enhancements and bug fixes are to be submitted back to the wxWindows
+//                 user groups free of all charges for use with the wxWindows library.
+//              3) These classes may not be distributed as part of any other class library,
+//                 DLL, text (written or electronic), other than a complete distribution of
+//                 the wxWindows GUI development toolkit.
+///////////////////////////////////////////////////////////////////////////////
+
+/*
+// SYNOPSIS START
+// SYNOPSIS STOP
+*/
+
+#ifndef TABLE_DOT_H
+#define TABLE_DOT_H
+
+#ifdef __GNUG__
+#pragma interface "dbtable.h"
+#endif
+
+#include "wx/db.h"
+
+const ROWID_LEN = 24;  // 18 is the max, 24 is in case it gets larger
+
+// The following class is used to define a column of a table.
+// The wxTable constructor will dynamically allocate as many of
+// these as there are columns in the table.  The class derived
+// from wxTable must initialize these column definitions in it's
+// constructor.  These column definitions provide inf. to the
+// wxTable class which allows it to create a table in the data
+// source, exchange data between the data source and the C++
+// object, and so on.
+
+class CcolDef
+{
+public:
+       char             ColName[DB_MAX_COLUMN_NAME_LEN+1];     // Column Name  glt 4/19/97 added one for the null terminator
+       int              DbDataType;                                                            // Logical Data Type; e.g. DB_DATA_TYPE_INTEGER
+       int              SqlCtype;                                                                      // C data type; e.g. SQL_C_LONG
+       void            *PtrDataObj;                                                            // Address of the data object
+       int              SzDataObj;                                                                     // Size, in bytes, of the data object
+       bool             KeyField;                                                                      // TRUE if this column is part of the PRIMARY KEY to the table; Date fields should NOT be KeyFields.
+       bool             Updateable;                                                            // Specifies whether this column is updateable
+       bool             InsertAllowed;                                                 // Specifies whether this column should be included in an INSERT statement
+       bool             DerivedCol;                                                            // Specifies whether this column is a derived value
+       SDWORD   CbValue;                                                                       // Internal use only!!!
+};  // CcolDef
+
+// This structure is used when creating secondary indexes.
+class CidxDef
+{
+public:
+       char             ColName[DB_MAX_COLUMN_NAME_LEN+1];     // Column Name  glt 4/19/97 added one for the null terminator
+       bool             Ascending;
+};  // CidxDef
+
+class wxTable
+{
+private:
+
+       // Private member variables
+       int currCursorNo;
+
+       // Private member functions
+       bool bindInsertParams(void);
+       bool bindUpdateParams(void);
+       bool bindCols(HSTMT cursor);
+       bool getRec(UWORD fetchType);
+       bool execDelete(char *pSqlStmt);
+       bool execUpdate(char *pSqlStmt);
+       bool query(int queryType, bool forUpdate, bool distinct, char *pSqlStmt = 0);
+
+public:
+
+       // Pointer to the database object this table belongs to
+       wxDB *pDb;
+
+       // ODBC Handles
+       HENV  henv;                                                     // ODBC Environment handle
+       HDBC  hdbc;                                                     // ODBC DB Connection handle
+       HSTMT hstmt;                                            // ODBC Statement handle
+//     HSTMT c0, c1, c2, c3, c4, c5;   // Cursors 0 through 5
+       HSTMT c0, c1, c2;                                       // Limited to Cursors 0 through 2 for now
+       HSTMT hstmtInsert;                              // ODBC Statement handle used specifically for inserts
+       HSTMT hstmtDelete;                              // ODBC Statement handle used specifically for deletes
+       HSTMT hstmtUpdate;                              // ODBC Statement handle used specifically for updates
+       HSTMT hstmtCount;                                       // ODBC Statement handle used specifically for COUNT(*)
+
+       // Table Inf.
+       char tableName[DB_MAX_TABLE_NAME_LEN+1];                        // Table name
+       char queryTableName[DB_MAX_TABLE_NAME_LEN+1];   // Query Table Name
+       int  noCols;                                                                                            // # of columns in the table
+
+       // Column Definitions
+       CcolDef *colDefs;       // Array of CcolDef structures
+
+       // Where and Order By clauses
+       char *where;                    // Standard SQL where clause, minus the word WHERE
+       char *orderBy;                  // Standard SQL order by clause, minus the ORDER BY
+
+       // Flags
+       bool selectForUpdate;
+
+       // Public member functions
+       wxTable(wxDB *pwxDB, const char *tblName, const int nCols, const char *qryTblName = 0);
+       ~wxTable();
+       bool     Open(void);
+       bool     CreateTable(void);
+       bool     CreateIndex(char * idxName, bool unique, int noIdxCols, CidxDef *pIdxDefs);
+       bool     CloseCursor(HSTMT cursor);
+       int      Insert(void);
+       bool     Update(void);
+       bool     Update(char *pSqlStmt);
+       bool     UpdateWhere(char *pWhereClause);
+       bool     Delete(void);
+       bool     DeleteWhere(char *pWhereClause);
+       bool     DeleteMatching(void);
+       bool     Query(bool forUpdate = FALSE, bool distinct = FALSE);
+       bool     QueryBySqlStmt(char *pSqlStmt);
+       bool     QueryMatching(bool forUpdate = FALSE, bool distinct = FALSE);
+       bool     QueryOnKeyFields(bool forUpdate = FALSE, bool distinct = FALSE);
+       bool     GetNext(void)   { return(getRec(SQL_FETCH_NEXT));  }
+       bool     operator++(int) { return(getRec(SQL_FETCH_NEXT));  }
+#ifndef FWD_ONLY_CURSORS
+       bool     GetPrev(void)   { return(getRec(SQL_FETCH_PRIOR)); }
+       bool     operator--(int) { return(getRec(SQL_FETCH_PRIOR)); }
+       bool     GetFirst(void)  { return(getRec(SQL_FETCH_FIRST)); }
+       bool     GetLast(void)   { return(getRec(SQL_FETCH_LAST));  }
+#endif
+       bool     IsCursorClosedOnCommit(void);
+       bool     IsColNull(int colNo);
+       UWORD    GetRowNum(void);
+       void     GetSelectStmt(char *pSqlStmt, int typeOfSelect, bool distinct);
+       void     GetDeleteStmt(char *pSqlStmt, int typeOfDel, char *pWhereClause = 0);
+       void     GetUpdateStmt(char *pSqlStmt, int typeOfUpd, char *pWhereClause = 0);
+       void     GetWhereClause(char *pWhereClause, int typeOfWhere);
+       bool     CanSelectForUpdate(void);
+       bool     CanUpdByROWID(void);
+       void     ClearMemberVars(void);
+       bool     SetQueryTimeout(UDWORD nSeconds);
+       void     SetColDefs (int index, char *fieldName, int dataType, void *pData, int cType,
+                                                 int size, bool keyField = FALSE, bool upd = TRUE,
+                                                 bool insAllow = TRUE, bool derivedCol = FALSE);
+       bool     SetCursor(int cursorNo = DB_CURSOR0);
+       int      GetCursor(void) { return(currCursorNo); }
+       ULONG    Count(void);
+       int      DB_STATUS(void) { return(pDb->DB_STATUS); }
+       bool     Refresh(void);
+
+};  // wxTable
+
+#endif
+
index 634b36658ff3fd2bcdb49dba076404aa5038d06b..2d472aed3456c85ab607d684826a06617c75748d 100644 (file)
 #define USE_ODBC                   1
                                     // Define 1 to use ODBC classes
 
-#define USE_ODBC_IN_MSW_ONLY           1
-
-#if USE_ODBC && USE_ODBC_IN_MSW_ONLY
-#undef USE_ODBC
-#define USE_ODBC 0
-#endif
-
 #define USE_IOSTREAMH     1
                                   // VC++ 4.2 and above allows <iostream> and <iostream.h>
                                   // but you can't mix them. Set to 1 for <iostream.h>,
diff --git a/samples/db/dbtest.cpp b/samples/db/dbtest.cpp
new file mode 100644 (file)
index 0000000..165444a
--- /dev/null
@@ -0,0 +1,2041 @@
+///////////////////////////////////////////////////////////////////////////////
+// Name:        dbtest.cpp
+// Purpose:     wxWindows database demo app
+// Author:      George Tasker
+// Modified by:
+// Created:     1998
+// RCS-ID:      $Id$
+// Copyright:   (c) 1998 Remstar International, Inc.
+// Licence:     wxWindows licence
+///////////////////////////////////////////////////////////////////////////////
+
+/*
+ *     SYNOPSIS START
+
+       This sample program demonstrates the cross-platform ODBC database classes
+       donated by the development team at Remstar International.
+
+       The table this sample is based on is developer contact table, and shows
+       some of the simple uses of the database classes wxDB and wxTable.
+
+       
+
+ *     SYNOPSIS END
+ */
+
+#ifdef __GNUG__
+#pragma implementation "dbtest.h"
+#endif
+
+#include  "wx/wxprec.h"
+
+#ifdef    __BORLANDC__
+#pragma hdrstop
+#endif  //__BORLANDC__
+
+#ifndef WX_PRECOMP
+#include  <wx/wx.h>
+#endif //WX_PRECOMP
+
+IMPLEMENT_APP(DatabaseDemoApp)
+
+#include <stdio.h>                                     // Included strictly for reading the text file with the database parameters
+
+#include       <wx/db.h>                                       // Required in the file which will get the data source connection
+#include    <wx/dbtable.h>                             // Has the wxTable object from which all data objects will inherit their data table functionality
+
+extern DbList *PtrBegDbList;           // from wx_db.cpp, used in getting back error results from db connections
+
+#include "dbtest.h"                            // Header file for this demonstration program
+#include "listdb.h"                                    // Code to support the "Lookup" button on the editor dialog
+extern char ListDB_Selection[];        // Used to return the first column value for the selected line from the listDB routines
+extern char ListDB_Selection2[]; // Used to return the second column value for the selected line from the listDB routines
+
+DatabaseDemoFrame      *DemoFrame;             // Pointer to the main frame
+
+
+// This statement initializes the whole application and calls OnInit
+DatabaseDemoApp DatabaseDemoApp;
+
+
+/* Pointer to the main database connection used in the program.  This
+ * pointer would normally be used for doing things as database lookups
+ * for user login names and passwords, getting workstation settings, etc.
+ * ---> IMPORTANT <---
+ * 
+ *             For each database object created which uses this wxDB pointer
+ *    connection to the database, when a CommitTrans() or RollBackTrans()
+ *    will commit or rollback EVERY object which uses this wxDB pointer.
+ *
+ *    To allow each table object (those derived from wxTable) to be 
+ *    individually committed or rolled back, you MUST use a different
+ *    instance of wxDB in the constructor of the table.  Doing so creates 
+ *             more overhead, and will use more database connections (some DBs have
+ *    connection limits...), so use connections sparringly.
+ *
+ *             It is recommended that one "main" database connection be created for
+ *             the entire program to use for READ-ONLY database accesses, but for each
+ *             table object which will do a CommitTrans() or RollbackTrans() that a
+ *             new wxDB object be created and used for it.
+ */
+wxDB   *READONLY_DB;
+
+
+/*
+ * This function will return the exact string(s) from the database engine
+ * indicating all error conditions which have just occured during the
+ * last call to the database engine.
+ *
+ * This demo uses the returned string by displaying it in a wxMessageBox.  The
+ * formatting therefore is not the greatest, but this is just a demo, not a 
+ * finished product.  :-) gt
+ *
+ * NOTE: The value returned by this function is for temporary use only and
+ *       should be copied for long term use
+ */
+char *GetExtendedDBErrorMsg(char *ErrFile, int ErrLine)
+{
+       static wxString msg;
+
+       wxString tStr;
+
+       if (ErrFile || ErrLine)
+       {
+               msg += "\n";
+               msg.Append ('-',80);
+               msg += "\nFile: ";
+               msg += ErrFile;
+               msg += "   Line: ";
+               tStr.sprintf("%d",ErrLine);
+               msg += tStr.GetData();
+               msg += "\n";
+       }
+
+       msg.Append ('-',80);
+       msg.Append ("\nODBC ERRORS\n");
+       msg.Append ('-',80);
+       msg += "\n";
+       // Scan through each database connection displaying
+       // any ODBC errors that have occured.
+       for (DbList *pDbList = PtrBegDbList; pDbList; pDbList = pDbList->PtrNext)
+       {
+               // Skip over any free connections
+               if (pDbList->Free)
+                       continue;
+               // Display errors for this connection
+               for (int i = 0; i < DB_MAX_ERROR_HISTORY; i++)
+               {
+                       if (pDbList->PtrDb->errorList[i])
+                       {
+                               msg.Append(pDbList->PtrDb->errorList[i]);
+                               if (strcmp(pDbList->PtrDb->errorList[i],"") != 0)
+                                       msg.Append("\n");
+                       }
+               }
+       }
+       msg += "\n";
+
+       return msg.GetData();
+}  // GetExtendedDBErrorMsg
+
+
+
+// `Main program' equivalent, creating windows and returning main app frame
+wxFrame *DatabaseDemoApp::OnInit(void)
+{
+       // Create the main frame window
+       DemoFrame = new DatabaseDemoFrame(NULL, "wxWindows Database Demo", 50, 50, 537, 480);
+
+       // Give it an icon
+#ifdef __WXMSW__
+       DemoFrame->SetIcon(wxIcon("db_icon"));
+#endif
+#ifdef __X__
+       DemoFrame->SetIcon(wxIcon("db.xbm"));
+#endif
+
+       // Make a menubar
+       wxMenu *file_menu = new wxMenu;
+       file_menu->Append(FILE_CREATE, "&Create contact table");
+       file_menu->Append(FILE_EXIT, "E&xit");
+
+       wxMenu *edit_menu = new wxMenu;
+       edit_menu->Append(EDIT_PARAMETERS, "&Parameters...");
+
+       wxMenu *about_menu = new wxMenu;
+       about_menu->Append(ABOUT_DEMO, "&About");
+
+       wxMenuBar *menu_bar = new wxMenuBar;
+       menu_bar->Append(file_menu, "&File");
+       menu_bar->Append(edit_menu, "&Edit");
+       menu_bar->Append(about_menu, "&About");
+       DemoFrame->SetMenuBar(menu_bar);
+
+       // Initialize the ODBC Environment for Database Operations
+       if (SQLAllocEnv(&DbConnectInf.Henv) != SQL_SUCCESS)
+       {
+               wxMessageBox("A problem occured while trying to get a connection to the data source","DB CONNECTION ERROR",wxOK | wxICON_EXCLAMATION);
+               return NULL;
+       }
+
+       FILE *paramFile;
+       if ((paramFile = fopen(paramFilename, "r")) == NULL)
+       {
+               wxString tStr;
+               tStr.sprintf("Unable to open the parameter file '%s' for reading.\n\nYou must specify the data source, user name, and\npassword that will be used and save those settings.",paramFilename);
+               wxMessageBox(tStr.GetData(),"File I/O Error...",wxOK | wxICON_EXCLAMATION);
+               DemoFrame->BuildParameterDialog(NULL);
+               if ((paramFile = fopen(paramFilename, "r")) == NULL)
+                       return FALSE;
+       }
+
+       char buffer[1000+1];
+       fgets(buffer, sizeof(params.ODBCSource), paramFile);
+       buffer[strlen(buffer)-1] = '\0';
+       strcpy(params.ODBCSource,buffer);
+
+       fgets(buffer, sizeof(params.UserName), paramFile);
+       buffer[strlen(buffer)-1] = '\0';
+       strcpy(params.UserName,buffer);
+
+       fgets(buffer, sizeof(params.Password), paramFile);
+       buffer[strlen(buffer)-1] = '\0';
+       strcpy(params.Password,buffer);
+
+       fclose(paramFile);
+
+       // Connect to datasource
+       strcpy(DbConnectInf.Dsn,                params.ODBCSource);     // ODBC data source name (created with ODBC Administrator under Win95/NT)
+       strcpy(DbConnectInf.Uid,                params.UserName);               // database username - must already exist in the data source
+       strcpy(DbConnectInf.AuthStr,    params.Password);               // password database username
+       READONLY_DB = GetDbConnection(&DbConnectInf);
+       if (READONLY_DB == 0)
+       {
+               wxMessageBox("Unable to connect to the data source.\n\nCheck the name of your data source to verify it has been correctly entered/spelled.\n\nWith some databases, the user name and password must\nbe created with full rights to the CONTACT table prior to making a connection\n(using tools provided by the database manufacturer)", "DB CONNECTION ERROR...",wxOK | wxICON_EXCLAMATION);
+               DemoFrame->BuildParameterDialog(NULL);
+               strcpy(DbConnectInf.Dsn,                "");
+               strcpy(DbConnectInf.Uid,                "");
+               strcpy(DbConnectInf.AuthStr,    "");
+               wxMessageBox("Now exiting program.\n\nRestart program to try any new settings.","Notice...",wxOK | wxICON_INFORMATION);
+               return(FALSE);
+       }
+
+       DemoFrame->BuildEditorDialog();
+
+       // Show the frame
+       DemoFrame->Show(TRUE);
+
+       // Return the main frame window
+       return DemoFrame;
+}  // DatabaseDemoApp::OnInit()
+
+
+
+// DatabaseDemoFrame constructor
+DatabaseDemoFrame::DatabaseDemoFrame(wxFrame *frame, char *title, int x, int y, int w, int h):
+  wxFrame(frame, title, x, y, w, h)
+{
+// Put any code in necessary for initializing the main frame here
+}
+
+
+// Intercept menu commands
+void DatabaseDemoFrame::OnMenuCommand(int id)
+{
+       switch (id)
+       {
+               case FILE_CREATE:
+                       CreateDataTable();
+                       break;
+               case FILE_EXIT:
+                       Close();
+                       break;
+               case EDIT_PARAMETERS:
+                       if ((pEditorDlg->mode != mCreate) && (pEditorDlg->mode != mEdit))
+                               BuildParameterDialog(this);
+                       else
+                               wxMessageBox("Cannot change database parameters while creating or editing a record","Notice...",wxOK | wxICON_INFORMATION);
+                       break;
+               case ABOUT_DEMO:
+                       wxMessageBox("wxWindows sample program for database classes\n\nContributed on 27 July 1998","About...",wxOK | wxICON_INFORMATION);
+                       break;
+       }
+}  // DatabaseDemoFrame::OnMenuCommand()
+
+
+Bool DatabaseDemoFrame::OnClose(void)
+{
+       // Put any additional checking necessary to make certain it is alright
+       // to close the program here that is not done elsewhere
+
+       return TRUE;
+}  // DatabaseDemoFrame::OnClose()
+
+
+void DatabaseDemoFrame::CreateDataTable()
+{
+       Bool Ok = (wxMessageBox("Any data currently residing in the table will be erased.\n\nAre you sure?","Confirm",wxYES_NO|wxICON_QUESTION) == wxYES);
+
+       if (!Ok)
+               return;
+
+       wxBeginBusyCursor();
+
+       Bool success = TRUE;
+
+       Ccontact *Contact = new Ccontact();
+       if (!Contact)
+       {
+               wxEndBusyCursor();
+               wxMessageBox("Error allocating memory for 'Ccontact'object.\n\nTable was not created.","Error...",wxOK | wxICON_EXCLAMATION);
+               return;
+       }
+
+       if (!Contact->CreateTable())
+       {
+               wxEndBusyCursor();
+               wxString tStr;
+               tStr  = "Error creating CONTACTS table.\nTable was not created.\n\n";
+               tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
+               wxMessageBox(tStr.GetData(),"ODBC Error...",wxOK | wxICON_EXCLAMATION);
+               success = FALSE;
+       }
+       else
+       {
+               if (!Contact->CreateIndexes())
+               {
+                       wxEndBusyCursor();
+                       wxString tStr;
+                       tStr  = "Error creating CONTACTS indexes.\nIndexes will be unavailable.\n\n";
+                       tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
+                       wxMessageBox(tStr.GetData(),"ODBC Error...",wxOK | wxICON_EXCLAMATION);
+                       success = FALSE;
+               }
+       }
+       wxEndBusyCursor();
+
+       delete Contact;
+
+       if (success)
+               wxMessageBox("Table and index(es) were successfully created.","Notice...",wxOK | wxICON_INFORMATION);
+}  // DatabaseDemoFrame::CreateDataTable()
+
+
+void DatabaseDemoFrame::BuildEditorDialog()
+{
+       pEditorDlg = new CeditorDlg(this);
+       if (!pEditorDlg)
+               wxMessageBox("Unable to create the editor dialog for some reason","Error...",wxOK | wxICON_EXCLAMATION);
+}  // DatabaseDemoFrame::BuildEditorDialog()
+
+
+void DatabaseDemoFrame::BuildParameterDialog(wxWindow *parent)
+{
+       pParamDlg = new CparameterDlg(parent);
+
+       if (!pParamDlg)
+               wxMessageBox("Unable to create the parameter dialog for some reason","Error...",wxOK | wxICON_EXCLAMATION);
+}  // DatabaseDemoFrame::BuildParameterDialog()
+
+
+/*
+ * Constructor note: If no wxDB object is passed in, a new connection to the database
+ *     is created for this instance of Ccontact.  This can be a slow process depending
+ *     on the database engine being used, and some database engines have a limit on the
+ *     number of connections (either hard limits, or license restricted) so care should 
+ *     be used to use as few connections as is necessary.  
+ * IMPORTANT: Objects which share a wxDB pointer are ALL acted upon whenever a member 
+ *     function of pDb is called (i.e. CommitTrans() or RollbackTrans(), so if modifying 
+ *     or creating a table objects which use the same pDb, know that all the objects
+ *     will be committed or rolled back when any of the objects has this function call made.
+ */
+Ccontact::Ccontact (wxDB *pwxDB) : wxTable(pwxDB ? pwxDB : GetDbConnection(&DbConnectInf),CONTACT_TABLE_NAME,CONTACT_NO_COLS)
+{
+       // This is used to represent whether the database connection should be released
+       // when this instance of the object is deleted.  If using the same connection
+       // for multiple instance of database objects, then the connection should only be 
+       // released when the last database instance using the connection is deleted
+       freeDbConn = !pwxDB;
+       
+       SetupColumns();
+
+}  // Ccontact Constructor
+
+
+void Ccontact::Initialize()
+{
+       Name[0]                                 = 0;
+       Addr1[0]                                        = 0;
+       Addr2[0]                                        = 0;
+       City[0]                                 = 0;
+       State[0]                                        = 0;
+       PostalCode[0]                   = 0;
+       Country[0]                              = 0;
+       JoinDate.year                   = 1980;
+       JoinDate.month                  = 1;
+       JoinDate.day                    = 1;
+       JoinDate.hour                   = 0;
+       JoinDate.minute         = 0;
+       JoinDate.second         = 0;
+       JoinDate.fraction               = 0;
+       NativeLanguage                  = langENGLISH;
+       IsDeveloper                             = FALSE;
+       Contributions                   = 0;
+       LinesOfCode                             = 0L;
+}  // Ccontact::Initialize
+
+
+Ccontact::~Ccontact()
+{
+       if (freeDbConn)
+       {
+               if (!FreeDbConnection(pDb))
+               {
+                       wxString tStr;
+                       tStr  = "Unable to Free the Ccontact data table handle\n\n";
+                       tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
+                       wxMessageBox(tStr.GetData(),"ODBC Error...",wxOK | wxICON_EXCLAMATION);
+               }
+       }
+}  // Ccontract destructor
+
+
+/*
+ * Handles setting up all the connections for the interface from the wxTable
+ * functions to interface to the data structure used to store records in 
+ * memory, and for all the column definitions that define the table structure
+ */
+void Ccontact::SetupColumns()
+{
+       SetColDefs ( 0,"NAME",                                  DB_DATA_TYPE_VARCHAR,    Name,                                  SQL_C_CHAR,                     sizeof(Name),                           TRUE, TRUE);  // Primary index
+       SetColDefs ( 1,"ADDRESS1",                              DB_DATA_TYPE_VARCHAR,    Addr1,                                 SQL_C_CHAR,                     sizeof(Addr1),                          FALSE,TRUE);
+       SetColDefs ( 2,"ADDRESS2",                              DB_DATA_TYPE_VARCHAR,    Addr2,                                 SQL_C_CHAR,                     sizeof(Addr2),                          FALSE,TRUE);
+       SetColDefs ( 3,"CITY",                                  DB_DATA_TYPE_VARCHAR,    City,                                  SQL_C_CHAR,                     sizeof(City),                           FALSE,TRUE);
+       SetColDefs ( 4,"STATE",                                 DB_DATA_TYPE_VARCHAR,    State,                                 SQL_C_CHAR,                     sizeof(State),                          FALSE,TRUE);
+       SetColDefs ( 5,"POSTAL_CODE",                   DB_DATA_TYPE_VARCHAR,    PostalCode,                    SQL_C_CHAR,                     sizeof(PostalCode),             FALSE,TRUE);
+       SetColDefs ( 6,"COUNTRY",                               DB_DATA_TYPE_VARCHAR,    Country,                               SQL_C_CHAR,                     sizeof(Country),                        FALSE,TRUE);
+       SetColDefs ( 7,"JOIN_DATE",                     DB_DATA_TYPE_DATE,              &JoinDate,                              SQL_C_TIMESTAMP,        sizeof(JoinDate),                       FALSE,TRUE);
+       SetColDefs ( 8,"NATIVE_LANGUAGE",       DB_DATA_TYPE_INTEGER,   &NativeLanguage,                SQL_C_ENUM,                     sizeof(NativeLanguage), FALSE,TRUE);
+       SetColDefs ( 9,"IS_DEVELOPER",          DB_DATA_TYPE_INTEGER,   &IsDeveloper,                   SQL_C_BOOLEAN,          sizeof(Bool),                           FALSE,TRUE);
+       SetColDefs (10,"CONTRIBUTIONS",         DB_DATA_TYPE_INTEGER,   &Contributions,         SQL_C_USHORT,           sizeof(Contributions),  FALSE,TRUE);
+       SetColDefs (11,"LINES_OF_CODE",         DB_DATA_TYPE_INTEGER,   &LinesOfCode,                   SQL_C_ULONG,            sizeof(LinesOfCode),            FALSE,TRUE);
+}  // Ccontact::SetupColumns
+
+
+Bool Ccontact::CreateIndexes(void)
+{
+       // This index could easily be accomplished with an "orderBy" clause, 
+       // but is done to show how to construct a non-primary index.
+       wxString        indexName;
+       CidxDef idxDef[2];
+
+       Bool            Ok = TRUE;
+
+       strcpy(idxDef[0].ColName, "IS_DEVELOPER");
+       idxDef[0].Ascending = TRUE;
+
+       strcpy(idxDef[1].ColName, "NAME");
+       idxDef[1].Ascending = TRUE;
+
+       indexName = CONTACT_TABLE_NAME;
+       indexName += "_IDX1";
+       Ok = CreateIndex(indexName.GetData(), TRUE, 2, idxDef);
+
+       return Ok;
+}  // Ccontact::CreateIndexes()
+
+
+/*
+ * Having a function to do a query on the primary key (and possibly others) is
+ * very efficient and tighter coding so that it is available where ever the object
+ * is.  Great for use with multiple tables when not using views or outer joins
+ */
+Bool Ccontact::FetchByName(char *name)
+{
+       whereStr.sprintf("NAME = '%s'",name);
+       where = this->whereStr.GetData();
+       orderBy = 0;
+
+       if (!Query())
+               return(FALSE);
+
+       // Fetch the record
+       return(GetNext());
+
+}  // Ccontact::FetchByName()
+
+
+/*
+ *
+ *  ************* DIALOGS ***************
+ *
+ */
+
+
+/* CeditorDlg constructor
+ *
+ * Creates the dialog used for creating/editing/deleting/copying a Ccontact object.
+ * This dialog actually is drawn in the main frame of the program
+ *
+ * An instance of Ccontact is created - "Contact" - which is used to hold the Ccontact
+ * object that is currently being worked with.
+ */
+CeditorDlg::CeditorDlg(wxWindow *parent) : wxPanel (parent, 1, 1, 460, 455)
+{
+       // Since the ::OnCommand() function is overridden, this prevents the widget
+       // detection in ::OnCommand() until all widgets have been initialized to prevent
+       // uninitialized pointers from crashing the program
+       widgetPtrsSet = FALSE;
+
+       // Create the data structure and a new database connection.  
+       // (As there is not a pDb being passed in the constructor, a new database 
+       // connection is created)
+       Contact = new Ccontact();
+
+       if (!Contact)
+       {
+               wxMessageBox("Unable to instantiate an instance of Ccontact","Error...",wxOK | wxICON_EXCLAMATION);
+               return;
+       }
+
+       // Check if the table exists or not.  If it doesn't, ask the user if they want to 
+       // create the table.  Continue trying to create the table until it exists, or user aborts
+       while (!Contact->pDb->TableExists((char *)CONTACT_TABLE_NAME))
+       {
+               wxString tStr;
+               tStr.sprintf("Unable to open the table '%s'.\n\nTable may need to be created...?\n\n",CONTACT_TABLE_NAME);
+               tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
+               wxMessageBox(tStr.GetData(),"ODBC Error...",wxOK | wxICON_EXCLAMATION);
+
+               Bool createTable = (wxMessageBox("Do you wish to try to create/clear the CONTACTS table?","Confirm",wxYES_NO|wxICON_QUESTION) == wxYES);
+
+               if (!createTable)
+               {
+                       delete Contact;
+                       Close();
+                       DemoFrame->Close();
+                       return;
+               }
+               else
+                       DemoFrame->CreateDataTable();
+       }
+
+       // Tables must be "opened" before anything other than creating/deleting table can be done
+       if (!Contact->Open())
+       {
+               // Table does exist, there was some problem opening it.  Currently this should
+               // never fail, except in the case of the table not exisiting.  Open() basically
+               // only sets up variable/pointer values, other than checking for table existence.
+               if (Contact->pDb->TableExists((char *)CONTACT_TABLE_NAME))
+               {
+                       wxString tStr;
+                       tStr.sprintf("Unable to open the table '%s'.\n\n",CONTACT_TABLE_NAME);
+                       tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
+                       wxMessageBox(tStr.GetData(),"ODBC Error...",wxOK | wxICON_EXCLAMATION);
+                       delete Contact;
+                       Close();
+                       DemoFrame->Close();
+                       return;
+               }
+       }
+
+       // Build the dialog
+       SetLabelPosition(wxHORIZONTAL);
+
+       wxFont *ButtonFont = new wxFont(12,wxSWISS,wxNORMAL,wxBOLD);
+       wxFont *TextFont   = new wxFont(12,wxSWISS,wxNORMAL,wxNORMAL);
+
+       SetButtonFont(ButtonFont);
+       SetLabelFont(TextFont);
+       SetLabelPosition(wxVERTICAL);
+
+       wxGroupBox *FunctionGrp         = new wxGroupBox(this, "",  15, 1, 497,  69, 0, "FunctionGrp");
+       wxGroupBox *SearchGrp           = new wxGroupBox(this, "", 417, 1, 95, 242, 0, "SearchGrp");
+
+       pCreateBtn              = new wxButton(this, NULL, "&Create",  25,  21, 70, 35, 0, "CreateBtn");
+       pEditBtn                        = new wxButton(this, NULL, "&Edit",     102,  21, 70, 35, 0, "EditBtn");
+       pDeleteBtn              = new wxButton(this, NULL, "&Delete",   179,  21, 70, 35, 0, "DeleteBtn");
+       pCopyBtn                        = new wxButton(this, NULL, "Cop&y",     256,  21, 70, 35, 0, "CopyBtn");
+       pSaveBtn                        = new wxButton(this, NULL, "&Save",     333,  21, 70, 35, 0, "SaveBtn");
+       pCancelBtn              = new wxButton(this, NULL, "C&ancel",   430,  21, 70, 35, 0, "CancelBtn");
+
+       pPrevBtn                        = new wxButton(this, NULL, "<< &Prev",430,  81, 70, 35, 0, "PrevBtn");
+       pNextBtn                        = new wxButton(this, NULL, "&Next >>",430, 121, 70, 35, 0, "NextBtn");
+       pQueryBtn               = new wxButton(this, NULL, "&Query",    430, 161, 70, 35, 0, "QueryBtn");
+       pResetBtn               = new wxButton(this, NULL, "&Reset",    430, 200, 70, 35, 0, "ResetBtn");
+
+       pNameMsg                        = new wxMessage(this, "Name:", 17, 80, -1, -1, 0, "NameMsg");
+       pNameTxt                        = new wxText(this, NULL, "", "", 17, 97, 308, 25, 0, "NameTxt");
+       pNameListBtn    = new wxButton(this, NULL, "&Lookup",   333, 99, 70, 24, 0, "LookupBtn");
+
+       pAddress1Msg    = new wxMessage(this, "Address:", 17, 130, -1, -1, 0, "Address1Msg");
+       pAddress1Txt    = new wxText(this, NULL, "", "", 17, 147, 308, 25, 0, "Address1Txt");
+
+       pAddress2Msg    = new wxMessage(this, "Address:", 17, 180, -1, -1, 0, "Address2Msg");
+       pAddress2Txt    = new wxText(this, NULL, "", "", 17, 197, 308, 25, 0, "Address2Txt");
+
+       pCityMsg                        = new wxMessage(this, "City:", 17, 230, -1, -1, 0, "CityMsg");
+       pCityTxt                        = new wxText(this, NULL, "", "", 17, 247, 225, 25, 0, "CityTxt");
+
+       pStateMsg               = new wxMessage(this, "State:", 250, 230, -1, -1, 0, "StateMsg");
+       pStateTxt               = new wxText(this, NULL, "", "", 250, 247, 153, 25, 0, "StateTxt");
+
+       pCountryMsg             = new wxMessage(this, "Country:", 17, 280, -1, -1, 0, "CountryMsg");
+       pCountryTxt             = new wxText(this, NULL, "", "", 17, 297, 225, 25, 0, "CountryTxt");
+
+       pPostalCodeMsg  = new wxMessage(this, "Postal Code:", 250, 280, -1, -1, 0, "PostalCodeMsg");
+       pPostalCodeTxt  = new wxText(this, NULL, "", "", 250, 297, 153, 25, 0, "PostalCodeTxt");
+
+       char *choice_strings[5];
+       choice_strings[0] = "English";
+       choice_strings[1] = "French";
+       choice_strings[2] = "German";
+       choice_strings[3] = "Spanish";
+       choice_strings[4] = "Other";
+       pNativeLangChoice = new wxChoice(this, NULL, "",17, 346, 277, -1, 5, choice_strings);
+       pNativeLangMsg    = new wxMessage(this, "Native language:", 17, 330, -1, -1, 0, "NativeLangMsg");
+
+       char *radio_strings[2];
+       radio_strings[0]        = "No";
+       radio_strings[1]        = "Yes";
+       pDeveloperRadio = new wxRadioBox(this,NULL,"Developer:",303,330,-1,-1,2,radio_strings,2,wxHORIZONTAL|wxFLAT);
+
+       pJoinDateMsg            = new wxMessage(this, "Date joined:", 17, 380, -1, -1, 0, "JoinDateMsg");
+       pJoinDateTxt            = new wxText(this, NULL, "", "", 17, 397, 150, 25, 0, "JoinDateTxt");
+
+       pContribMsg                     = new wxMessage(this, "Contributions:", 175, 380, -1, -1, 0, "ContribMsg");
+       pContribTxt                     = new wxText(this, NULL, "", "", 175, 397, 120, 25, 0, "ContribTxt");
+
+       pLinesMsg                       = new wxMessage(this, "Lines of code:", 303, 380, -1, -1, 0, "LinesMsg");
+       pLinesTxt                       = new wxText(this, NULL, "", "", 303, 397, 100, 25, 0, "LinesTxt");
+
+       // Now that all the widgets on the panel are created, its safe to allow ::OnCommand() to 
+       // handle all widget processing
+       widgetPtrsSet = TRUE;
+
+       // Setup the orderBy and where clauses to return back a single record as the result set, 
+       // as there will only be one record being shown on the dialog at a time, this optimizes
+       // network traffic by only returning a one row result
+       
+       Contact->orderBy = "NAME";  // field name to sort by
+
+       // The wxString "whereStr" is not a member of the wxTable object, it is a member variable
+       // specifically in the Ccontact class.  It is used here for simpler construction of a varying
+       // length string, and then after the string is built, the wxTable member variable "where" is
+       // assigned the pointer to the constructed string.
+       //
+       // The constructed where clause below has a sub-query within it "SELECT MIN(NAME) FROM %s" 
+       // to achieve a single row (in this case the first name in alphabetical order).
+       Contact->whereStr.sprintf("NAME = (SELECT MIN(NAME) FROM %s)",Contact->tableName);
+       
+       // NOTE: GetData() returns a pointer which may not be valid later, so this is short term use only
+       Contact->where = Contact->whereStr.GetData();  
+
+       // Perform the Query to get the result set.  
+       // NOTE: If there are no rows returned, that is a valid result, so Query() would return TRUE.  
+       //       Only if there is a database error will Query() come back as FALSE
+       if (!Contact->Query())
+       {
+               wxString tStr;
+               tStr  = "ODBC error during Query()\n\n";
+               tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
+               wxMessageBox(tStr.GetData(),"ODBC Error...",wxOK | wxICON_EXCLAMATION);
+               GetParent()->Close();
+               return;
+       }
+
+       // Since Query succeeded, now get the row that was returned
+       if (!Contact->GetNext())
+               // If the GetNext() failed at this point, then there are no rows to retrieve, 
+               // so clear the values in the members of "Contact" so that PutData() blanks the 
+               // widgets on the dialog
+               Contact->Initialize();
+
+       SetMode(mView);
+       PutData();
+
+       Show(TRUE);
+}  // CeditorDlg constructor
+
+
+Bool CeditorDlg::OnClose()
+{
+       // Clean up time
+       if ((mode != mCreate) && (mode != mEdit))
+       {
+               if (Contact)
+                       delete Contact;
+               return TRUE;
+       }
+       else
+       {
+               wxMessageBox("Must finish processing the current record being created/modified before exiting","Notice...",wxOK | wxICON_INFORMATION);
+               return FALSE;
+       }
+}  // CeditorDlg::OnClose()
+
+
+void CeditorDlg::OnCommand(wxWindow& win, wxCommandEvent& event)
+{
+       wxString widgetName;
+       
+       widgetName = win.GetName();
+
+       if (!widgetPtrsSet)
+               return;
+
+       if (widgetName == pCreateBtn->GetName())
+       {
+               Contact->Initialize();
+               PutData();
+               SetMode( mCreate );
+               pNameTxt->SetValue("");
+               pNameTxt->SetFocus();
+               return;
+       }
+
+       if (widgetName == pEditBtn->GetName())
+       {
+               saveName = Contact->Name;
+               SetMode( mEdit );
+               pNameTxt->SetFocus();
+               return;
+       }
+
+       if (widgetName == pCopyBtn->GetName())
+       {
+               SetMode(mCreate);
+               pNameTxt->SetValue("");
+               pNameTxt->SetFocus();
+               return;
+       }
+
+       if (widgetName == pDeleteBtn->GetName())
+       {
+               Bool Ok = (wxMessageBox("Are you sure?","Confirm",wxYES_NO|wxICON_QUESTION) == wxYES);
+
+               if (!Ok)
+                       return;
+
+               if (Ok && Contact->Delete())
+               {
+                       // NOTE: Deletions are not finalized until a CommitTrans() is performed.  
+                       //       If the commit were not performed, the program will continue to 
+                       //       show the table contents as if they were deleted until this instance
+                       //       of Ccontact is deleted.  If the Commit wasn't performed, the 
+                       //       database will automatically Rollback the changes when the database
+                       //       connection is terminated
+                       Contact->pDb->CommitTrans();
+
+                       // Try to get the row that followed the just deleted row in the orderBy sequence
+                       if (!GetNextRec())
+                       {
+                               // There was now row (in sequence) after the just deleted row, so get the
+                               // row which preceded the just deleted row
+                               if (!GetPrevRec())
+                               {
+                                       // There are now no rows remaining, so clear the dialog widgets
+                                       Contact->Initialize();
+                                       PutData();
+                               }
+                       }
+                       SetMode(mode);    // force reset of button enable/disable
+               }
+               else
+                       // Delete failed
+                       Contact->pDb->RollbackTrans();
+
+               SetMode(mView);
+               return;
+       }
+
+       if (widgetName == pSaveBtn->GetName())
+       {
+               Save();
+               return;
+       }
+
+       if (widgetName == pCancelBtn->GetName())
+       {
+               Bool Ok = (wxMessageBox("Are you sure?","Confirm",wxYES_NO|wxICON_QUESTION) == wxYES);
+
+               if (!Ok)
+                       return;
+
+               if (!strcmp(saveName.GetData(),""))
+               {
+                       Contact->Initialize();
+                       PutData();
+                       SetMode(mView);
+                       return;
+               }
+               else
+               {
+                       // Requery previous record
+                       if (Contact->FetchByName(saveName.GetData()))
+                       {
+                               PutData();
+                               SetMode(mView);
+                               return;
+                       }
+               }
+
+               // Previous record not available, retrieve first record in table
+               Contact->whereStr  = "NAME = (SELECT MIN(NAME) FROM ";
+               Contact->whereStr += Contact->tableName;
+               Contact->whereStr += ")";
+
+               Contact->where = Contact->whereStr.GetData();
+               if (!Contact->Query())
+               {
+                       wxString tStr;
+                       tStr  = "ODBC error during Query()\n\n";
+                       tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
+                       wxMessageBox(tStr.GetData(),"ODBC Error...",wxOK | wxICON_EXCLAMATION);
+                       SetMode(mView);
+                       return;
+               }
+               if (Contact->GetNext())  // Successfully read first record
+               {
+                       PutData();
+                       SetMode(mView);
+                       return;
+               }
+               // No contacts are available, clear dialog
+               Contact->Initialize();
+               PutData();
+               SetMode(mView);
+               return;
+       }  // Cancel Button
+
+       if (widgetName == pPrevBtn->GetName())
+       {
+               if (!GetPrevRec())
+                       wxBell();
+               return;
+       }  // Prev Button
+
+       if (widgetName == pNextBtn->GetName())
+       {
+               if (!GetNextRec())
+                       wxBell();
+               return;
+       }  // Next Button
+
+       if (widgetName == pQueryBtn->GetName())
+       {
+               // Display the query dialog box
+               char qryWhere[DB_MAX_WHERE_CLAUSE_LEN+1];
+               strcpy(qryWhere, Contact->qryWhereStr.GetData());
+               char *tblName[] = {(char *)CONTACT_TABLE_NAME, 0};
+               new CqueryDlg(GetParent(), Contact->pDb, tblName, qryWhere);
+
+               // Query the first record in the new record set and
+               // display it, if the query string has changed.
+               if (strcmp(qryWhere, Contact->qryWhereStr.GetData()))
+               {
+                       Contact->orderBy                = "NAME";
+                       Contact->whereStr               = "NAME = (SELECT MIN(NAME) FROM ";
+                       Contact->whereStr               += CONTACT_TABLE_NAME;
+                       // Append the query where string (if there is one)
+                       Contact->qryWhereStr    = qryWhere;
+                       if (strlen(qryWhere))
+                       {
+                               Contact->whereStr               += " WHERE ";
+                               Contact->whereStr               += Contact->qryWhereStr;
+                       }
+                       // Close the expression with a right paren
+                       Contact->whereStr += ")";
+                       // Requery the table
+                       Contact->where = Contact->whereStr.GetData();
+                       if (!Contact->Query())
+                       {
+                               wxString tStr;
+                               tStr  = "ODBC error during Query()\n\n";
+                               tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
+                               wxMessageBox(tStr.GetData(),"ODBC Error...",wxOK | wxICON_EXCLAMATION);
+                               return;
+                       }
+                       // Display the first record from the query set
+                       if (!Contact->GetNext())
+                               Contact->Initialize();
+                       PutData();
+               }
+
+               // Enable/Disable the reset button
+               pResetBtn->Enable(!Contact->qryWhereStr.Empty());
+
+               return;
+       }  // Query button
+
+
+       if (widgetName == pResetBtn->GetName())
+       {
+               // Clear the additional where criteria established by the query feature
+               Contact->qryWhereStr = "";
+
+               // Query the first record in the table
+               Contact->orderBy                = "NAME";
+               Contact->whereStr               = "NAME = (SELECT MIN(NAME) FROM ";
+               Contact->whereStr               += CONTACT_TABLE_NAME;
+               Contact->whereStr               += ")";
+               Contact->where                  = Contact->whereStr.GetData();
+               if (!Contact->Query())
+               {
+                       wxString tStr;
+                       tStr  = "ODBC error during Query()\n\n";
+                       tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
+                       wxMessageBox(tStr.GetData(),"ODBC Error...",wxOK | wxICON_EXCLAMATION);
+                       return;
+               }
+               if (!Contact->GetNext())
+                       Contact->Initialize();
+               PutData();
+               pResetBtn->Enable(FALSE);
+
+               return;
+       }  // Reset button
+
+
+       if (widgetName == pNameListBtn->GetName())
+       {
+               new ClookUpDlg(/* wxWindow      *parent                 */ this,
+                                                       /* char         *windowTitle    */ "Select contact name",
+                                                       /* char         *tableName              */ (char *) CONTACT_TABLE_NAME,
+                                                       /* char         *dispCol1               */ "NAME",
+                                                       /* char         *dispCol2               */ "JOIN_DATE",
+                                                       /* char         *where                  */ "",
+                                                       /* char         *orderBy                        */ "NAME",
+                                                       /* Bool         distinctValues */ TRUE);
+
+               if (ListDB_Selection && strlen(ListDB_Selection))
+               {
+                       wxString w = "NAME = '";
+                       w += ListDB_Selection;
+                       w += "'";
+                       GetRec(w.GetData());
+               }
+
+               return;
+       }
+
+}  // CeditorDlg::OnCommand()
+
+
+void CeditorDlg::FieldsEditable()
+{
+       pNameTxt->Enable((mode == mCreate) || (mode == mEdit));
+       pAddress1Txt->Enable((mode == mCreate) || (mode == mEdit));
+       pAddress2Txt->Enable((mode == mCreate) || (mode == mEdit));
+       pCityTxt->Enable((mode == mCreate) || (mode == mEdit));
+       pStateTxt->Enable((mode == mCreate) || (mode == mEdit));
+       pPostalCodeTxt->Enable((mode == mCreate) || (mode == mEdit));
+       pCountryTxt->Enable((mode == mCreate) || (mode == mEdit));
+
+       pJoinDateTxt->Enable((mode == mCreate) || (mode == mEdit));
+       pContribTxt->Enable((mode == mCreate) || (mode == mEdit));
+       pLinesTxt->Enable((mode == mCreate) || (mode == mEdit));
+       pNativeLangChoice->Enable((mode == mCreate) || (mode == mEdit));
+       pDeveloperRadio->Enable((mode == mCreate) || (mode == mEdit));
+
+}  // CeditorDlg::FieldsEditable()
+
+
+void CeditorDlg::SetMode(enum DialogModes m)
+{
+       Bool    edit = FALSE;
+
+       mode = m;
+       switch (mode)
+       {
+               case mCreate:
+               case mEdit:
+                       edit = TRUE;
+                       break;
+               case mView:
+               case mSearch:
+                       edit = FALSE;
+                       break;
+               default:
+                               break;
+       };
+
+       if (widgetPtrsSet)
+       {
+               pCreateBtn->Enable( !edit );
+               pEditBtn->Enable( !edit && (strcmp(Contact->Name,"")!=0) );
+               pDeleteBtn->Enable( !edit && (strcmp(Contact->Name,"")!=0) );
+               pCopyBtn->Enable( !edit && (strcmp(Contact->Name,"")!=0) );
+               pSaveBtn->Enable( edit );
+               pCancelBtn->Enable( edit );
+               pPrevBtn->Enable( !edit );
+               pNextBtn->Enable( !edit );
+               pQueryBtn->Enable( !edit );
+               pResetBtn->Enable( !edit && !Contact->qryWhereStr.Empty() );
+               pNameListBtn->Enable( !edit );
+       }
+
+       FieldsEditable();
+}  // CeditorDlg::SetMode()
+
+
+Bool CeditorDlg::PutData()
+{
+       wxString tStr;
+
+       pNameTxt->SetValue(Contact->Name);
+       pAddress1Txt->SetValue(Contact->Addr1);
+       pAddress2Txt->SetValue(Contact->Addr2);
+       pCityTxt->SetValue(Contact->City);
+       pStateTxt->SetValue(Contact->State);
+       pCountryTxt->SetValue(Contact->Country);
+       pPostalCodeTxt->SetValue(Contact->PostalCode);
+
+       tStr.sprintf("%d/%d/%d",Contact->JoinDate.month,Contact->JoinDate.day,Contact->JoinDate.year);
+       pJoinDateTxt->SetValue(tStr.GetData());
+
+       tStr.sprintf("%d",Contact->Contributions);
+       pContribTxt->SetValue(tStr.GetData());
+
+       tStr.sprintf("%lu",Contact->LinesOfCode);
+       pLinesTxt->SetValue(tStr.GetData());
+
+       pNativeLangChoice->SetSelection(Contact->NativeLanguage);
+
+       pDeveloperRadio->SetSelection(Contact->IsDeveloper);
+
+       return TRUE;
+}  // Ceditor::PutData()
+
+
+/*
+ * Reads the data out of all the widgets on the dialog.  Some data evaluation is done
+ * to ensure that there is a name entered and that the date field is valid.
+ *
+ * A return value of TRUE means that valid data was retrieved from the dialog, otherwise
+ * invalid data was found (and a message was displayed telling the user what to fix), and
+ * the data was not placed into the appropraite fields of Ccontact
+ */
+Bool CeditorDlg::GetData()
+{
+       // Validate that the data currently entered into the widgets is valid data
+
+       wxString tStr;
+       tStr = pNameTxt->GetValue();
+       if (!strcmp(tStr.GetData(),""))
+       {
+               wxMessageBox("A name is required for entry into the contact table","Notice...",wxOK | wxICON_INFORMATION);
+               return FALSE;
+       }
+
+       Bool    invalid = FALSE;
+       int     mm,dd,yyyy;
+       int     first, second;
+
+       tStr = pJoinDateTxt->GetValue();
+       if (tStr.Freq('/') != 2)
+               invalid = TRUE;
+
+       // Find the month, day, and year tokens
+       if (!invalid)
+       {
+               first           = tStr.First('/');
+               second  = tStr.Last('/');
+
+               mm = atoi(tStr.SubString(0,first));
+               dd = atoi(tStr.SubString(first+1,second));
+               yyyy = atoi(tStr.SubString(second+1,tStr.Length()-1));
+
+               invalid = !(mm && dd && yyyy);
+       }
+
+       // Force Year 2000 compliance
+       if (!invalid && (yyyy < 1000))
+               invalid = TRUE;
+
+       // Check the token ranges for validity
+       if (!invalid)
+       {
+               if (yyyy > 9999)
+                       invalid = TRUE;
+               else if ((mm < 1) || (mm > 12))
+                       invalid = TRUE;
+               else
+               {
+                       if (dd < 1)
+                               invalid = TRUE;
+                       else
+                       {
+                               int days[12] = {31,28,31,30,31,30,
+                                                                        31,31,30,31,30,31};
+                               if (dd > days[mm-1])
+                               {
+                                       invalid = TRUE;
+                                       if ((dd == 29) && (mm == 2))
+                                       {
+                                               if (((yyyy % 4) == 0) && (((yyyy % 100) != 0) || ((yyyy % 400) == 0)))
+                                                       invalid = FALSE;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       if (!invalid)
+       {
+               Contact->JoinDate.month = mm;
+               Contact->JoinDate.day   = dd;
+               Contact->JoinDate.year  = yyyy;
+       }
+       else
+       {
+               wxMessageBox("Improper date format.  Please check the date\nspecified and try again.\n\nNOTE: Dates are in american format (MM/DD/YYYY)","Notice...",wxOK | wxICON_INFORMATION);
+               return FALSE;
+       }
+
+       tStr = pNameTxt->GetValue();
+       strcpy(Contact->Name,tStr.GetData());
+       strcpy(Contact->Addr1,pAddress1Txt->GetValue());
+       strcpy(Contact->Addr2,pAddress2Txt->GetValue());
+       strcpy(Contact->City,pCityTxt->GetValue());
+       strcpy(Contact->State,pStateTxt->GetValue());
+       strcpy(Contact->Country,pCountryTxt->GetValue());
+       strcpy(Contact->PostalCode,pPostalCodeTxt->GetValue());
+
+       Contact->Contributions = atoi(pContribTxt->GetValue());
+       Contact->LinesOfCode = atol(pLinesTxt->GetValue());
+
+       Contact->NativeLanguage = (enum Language) pNativeLangChoice->GetSelection();
+       Contact->IsDeveloper = pDeveloperRadio->GetSelection();
+
+       return TRUE;
+}  // CeditorDlg::GetData()
+
+
+/*
+ * Retrieve data from the dialog, verify the validity of the data, and if it is valid,
+ * try to insert/update the data to the table based on the current 'mode' the dialog
+ * is set to.
+ *
+ * A return value of TRUE means the insert/update was completed successfully, a return
+ * value of FALSE means that Save() failed.  If returning FALSE, then this function
+ * has displayed a detailed error message for the user.
+ */
+Bool CeditorDlg::Save()
+{
+       Bool failed = FALSE;
+
+       // Read the data in the widgets of the dialog to get the user's data
+       if (!GetData())
+               failed = TRUE;
+
+       // Perform any other required validations necessary before saving
+
+
+       if (!failed)
+       {
+               wxBeginBusyCursor();
+
+               if (mode == mCreate)
+               {
+                       RETCODE result = Contact->Insert();
+
+                       failed = (result != DB_SUCCESS);
+                       if (failed)
+                       {
+                               // Some errors may be expected, like a duplicate key, so handle those instances with
+                               // specific error messages.
+                               if (result == DB_ERR_INTEGRITY_CONSTRAINT_VIOL)
+                               {
+                                       wxString tStr;
+                                       tStr  = "A duplicate key value already exists in the table.\nUnable to save record\n\n";
+                                       tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
+                                       wxMessageBox(tStr.GetData(),"ODBC Error...",wxOK | wxICON_EXCLAMATION);
+                               }
+                               else
+                               {
+                                       // Some other unexpexted error occurred
+                                       wxString tStr;
+                                       tStr  = "Database insert failed\n\n";
+                                       tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
+                                       wxMessageBox(tStr.GetData(),"ODBC Error...",wxOK | wxICON_EXCLAMATION);
+                               }
+                       }
+               }
+               else  // mode == mEdit
+               {
+                       if (!Contact->Update())
+                       {
+                               wxString tStr;
+                               tStr  = "Database update failed\n\n";
+                               tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
+                               wxMessageBox(tStr.GetData(),"ODBC Error...",wxOK | wxICON_EXCLAMATION);
+                               failed = TRUE;
+                       }
+               }
+
+               if (!failed)
+               {
+                       Contact->pDb->CommitTrans();
+                       SetMode(mView);  // Sets the dialog mode back to viewing after save is successful
+               }
+               else
+                       Contact->pDb->RollbackTrans();
+
+               wxEndBusyCursor();
+       }
+
+       return !failed;
+}  // CeditorDlg::Save()
+
+
+/*
+ * Where this program is only showing a single row at a time in the dialog,
+ * a special where clause must be built to find just the single row which,
+ * in sequence, would follow the currently displayed row.
+ */
+Bool CeditorDlg::GetNextRec()
+{
+       wxString w;
+
+       w  = "NAME = (SELECT MIN(NAME) FROM ";
+       w += Contact->tableName;
+       w += " WHERE NAME > '";
+       w += Contact->Name;
+       w += "'";
+
+       // If a query where string is currently set, append that criteria
+       if (!Contact->qryWhereStr.Empty())
+       {
+               w += " AND (";
+               w += Contact->qryWhereStr;
+               w += ")";
+       }
+
+       w += ")";
+
+       return(GetRec(w.GetData()));
+
+}  // CeditorDlg::GetNextRec()
+
+
+/*
+ * Where this program is only showing a single row at a time in the dialog,
+ * a special where clause must be built to find just the single row which,
+ * in sequence, would precede the currently displayed row.
+ */
+Bool CeditorDlg::GetPrevRec()
+{
+       wxString w;
+
+       w  = "NAME = (SELECT MAX(NAME) FROM ";
+       w +=    Contact->tableName;
+       w += " WHERE NAME < '";
+       w += Contact->Name;
+       w += "'";
+
+       // If a query where string is currently set, append that criteria
+       if (!Contact->qryWhereStr.Empty())
+       {
+               w += " AND (";
+               w += Contact->qryWhereStr;
+               w += ")";
+       }
+
+       w += ")";
+
+       return(GetRec(w.GetData()));
+
+}  // CeditorDlg::GetPrevRec()
+
+
+/*
+ * This function is here to avoid duplicating this same code in both the
+ * GetPrevRec() and GetNextRec() functions
+ */
+Bool CeditorDlg::GetRec(char *whereStr)
+{
+       Contact->where = whereStr;
+       Contact->orderBy = "NAME";
+       
+       if (!Contact->Query())
+       {
+               wxString tStr;
+               tStr  = "ODBC error during Query()\n\n";
+               tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
+               wxMessageBox(tStr.GetData(),"ODBC Error...",wxOK | wxICON_EXCLAMATION);
+
+               return(FALSE);
+       }
+
+       if (Contact->GetNext())
+       {
+               PutData();
+               return(TRUE);
+       }
+       else
+               return(FALSE);
+}  // CeditorDlg::GetRec()
+
+
+
+/*
+ * CparameterDlg constructor
+ */
+CparameterDlg::CparameterDlg(wxWindow *parent) : wxDialogBox (parent, "ODBC parameter settings", 1, -1, -1, 400, 275)
+{
+       // Since the ::OnCommand() function is overridden, this prevents the widget
+       // detection in ::OnCommand() until all widgets have been initialized to prevent
+       // uninitialized pointers from crashing the program
+       widgetPtrsSet = FALSE;
+
+       // Build the dialog
+       SetLabelPosition(wxVERTICAL);
+
+       wxFont *ButtonFont = new wxFont(12,wxSWISS,wxNORMAL,wxBOLD);
+       wxFont *TextFont   = new wxFont(12,wxSWISS,wxNORMAL,wxNORMAL);
+
+       SetButtonFont(ButtonFont);
+       SetLabelFont(TextFont);
+       SetLabelPosition(wxVERTICAL);
+
+       pParamODBCSourceMsg     = new wxMessage(this, "ODBC data sources:", 10, 10, -1, -1, 0, "ParamODBCSourceMsg");
+       pParamODBCSourceList    = new wxListBox(this, NULL, "", wxSINGLE|wxALWAYS_SB, 10, 29, 285, 150, 0, 0, 0, "ParamODBCSourceList");
+
+       pParamUserNameMsg               = new wxMessage(this, "Database user name:", 10, 193, -1, -1, 0, "ParamUserNameMsg");
+       pParamUserNameTxt               = new wxText(this, NULL, "", "", 10, 209, 140, 25, 0, "ParamUserNameTxt");
+
+       pParamPasswordMsg               = new wxMessage(this, "Password:", 156, 193, -1, -1, 0, "ParamPasswordMsg");
+       pParamPasswordTxt               = new wxText(this, NULL, "", "", 156, 209, 140, 25, 0, "ParamPasswordTxt");
+       
+       pParamSaveBtn                   = new wxButton(this, NULL, "&Save",     310,  21, 70, 35, 0, "ParamSaveBtn");
+       pParamCancelBtn         = new wxButton(this, NULL, "C&ancel",   310,  66, 70, 35, 0, "ParamCancelBtn");
+
+       // Now that all the widgets on the panel are created, its safe to allow ::OnCommand() to 
+       // handle all widget processing
+       widgetPtrsSet = TRUE;
+
+       saved = FALSE;
+       savedParamSettings = DatabaseDemoApp.params;
+
+       Centre(wxBOTH);
+       PutData();
+       Show(TRUE);
+}  // CparameterDlg constructor
+
+
+Bool CparameterDlg::OnClose()
+{
+       // Put any additional checking necessary to make certain it is alright
+       // to close the program here that is not done elsewhere
+       if (!saved)
+       {
+               Bool Ok = (wxMessageBox("No changes have been saved.\n\nAre you sure you wish exit the parameter screen?","Confirm",wxYES_NO|wxICON_QUESTION) == wxYES);
+
+               if (!Ok)
+                       return FALSE;
+
+               DatabaseDemoApp.params = savedParamSettings;
+       }
+
+       if (GetParent() != NULL)
+               GetParent()->SetFocus();
+       return TRUE;
+}  // Cparameter::OnClose()
+
+
+void CparameterDlg::OnCommand(wxWindow& win, wxCommandEvent& event)
+{
+       wxString widgetName;
+       
+       widgetName = win.GetName();
+
+       if (!widgetPtrsSet)
+               return;
+
+       if (widgetName == pParamSaveBtn->GetName())
+       {
+               if (Save())
+               {
+                       wxString tStr;
+                       tStr = "Database parameters have been saved.";
+                       if (GetParent() != NULL)  // The parameter dialog was not called during startup due to a missing cfg file
+                               tStr += "\nNew parameters will take effect the next time the program is started.";
+                       wxMessageBox(tStr.GetData(),"Notice...",wxOK | wxICON_INFORMATION);
+                       saved = TRUE;
+                       Close();
+               }
+               return;
+       }
+
+       if (widgetName == pParamCancelBtn->GetName())
+       {
+               Close();
+               return;
+       }
+}  // CparameterDlg::OnCommand()
+
+
+Bool CparameterDlg::PutData()
+{
+       // Fill the data source list box
+       FillDataSourceList();
+
+       // Fill in the fields from the params object
+       pParamODBCSourceList->SetStringSelection(DatabaseDemoApp.params.ODBCSource);
+       pParamUserNameTxt->SetValue(DatabaseDemoApp.params.UserName);
+       pParamPasswordTxt->SetValue(DatabaseDemoApp.params.Password);
+       return TRUE;
+}  // CparameterDlg::PutData()
+
+
+Bool CparameterDlg::GetData()
+{
+       wxString tStr;
+       if (pParamODBCSourceList->GetStringSelection())
+       {
+               tStr = pParamODBCSourceList->GetStringSelection();
+               if (tStr.Length() > (sizeof(DatabaseDemoApp.params.ODBCSource)-1))
+               {
+                       wxString errmsg;
+                       errmsg.sprintf("ODBC Data source name is longer than the data structure to hold it.\n'Cparameter.ODBCSource' must have a larger character array\nto handle a data source with this long of a name\n\nThe data source currently selected is %d characters long.",tStr.Length());
+                       wxMessageBox(errmsg.GetData(),"Internal program error...",wxOK | wxICON_EXCLAMATION);
+                       return FALSE;
+               }
+               strcpy(DatabaseDemoApp.params.ODBCSource, tStr.GetData());
+       }
+       else
+               return FALSE;
+       
+       tStr = pParamUserNameTxt->GetValue();
+       if (tStr.Length() > (sizeof(DatabaseDemoApp.params.UserName)-1))
+       {
+               wxString errmsg;
+               errmsg.sprintf("User name is longer than the data structure to hold it.\n'Cparameter.UserName' must have a larger character array\nto handle a data source with this long of a name\n\nThe user name currently specified is %d characters long.",tStr.Length());
+               wxMessageBox(errmsg.GetData(),"Internal program error...",wxOK | wxICON_EXCLAMATION);
+               return FALSE;
+       }
+       strcpy(DatabaseDemoApp.params.UserName, tStr.GetData());
+
+       tStr = pParamPasswordTxt->GetValue();
+       if (tStr.Length() > (sizeof(DatabaseDemoApp.params.Password)-1))
+       {
+               wxString errmsg;
+               errmsg.sprintf("Password is longer than the data structure to hold it.\n'Cparameter.Password' must have a larger character array\nto handle a data source with this long of a name\n\nThe password currently specified is %d characters long.",tStr.Length());
+               wxMessageBox(errmsg.GetData(),"Internal program error...",wxOK | wxICON_EXCLAMATION);
+               return FALSE;
+       }
+       strcpy(DatabaseDemoApp.params.Password,tStr.GetData());
+       return TRUE;
+}  // CparameterDlg::GetData()
+
+
+Bool CparameterDlg::Save()
+{
+       Cparameters saveParams = DatabaseDemoApp.params;
+       if (!GetData())
+       {
+               DatabaseDemoApp.params = saveParams;
+               return FALSE;
+       }
+
+       FILE *paramFile;
+       if ((paramFile = fopen(paramFilename, "wt")) == NULL)
+       {
+               wxString tStr;
+               tStr.sprintf("Unable to write/overwrite '%s'.",paramFilename);
+               wxMessageBox(tStr.GetData(),"File I/O Error...",wxOK | wxICON_EXCLAMATION);
+               return FALSE;
+       }
+
+       fputs(DatabaseDemoApp.params.ODBCSource, paramFile);
+       fputc('\n', paramFile);
+       fputs(DatabaseDemoApp.params.UserName, paramFile);
+       fputc('\n', paramFile);
+       fputs(DatabaseDemoApp.params.Password, paramFile);
+       fputc('\n', paramFile);
+       fclose(paramFile);
+
+       return TRUE;
+}  // CparameterDlg::Save()
+
+
+void CparameterDlg::FillDataSourceList()
+{
+       char Dsn[SQL_MAX_DSN_LENGTH + 1];
+       char DsDesc[255];
+       wxStringList strList;
+
+       while(GetDataSource(DbConnectInf.Henv, Dsn, SQL_MAX_DSN_LENGTH+1, DsDesc, 255))
+               strList.Add(Dsn);
+
+       strList.Sort();
+       strList.Add("");
+       char **p = strList.ListToArray();
+
+       for (int i = 0; strlen(p[i]); i++)
+               pParamODBCSourceList->Append(p[i]);
+}  // CparameterDlg::CparameterDlg::FillDataSourceList()
+
+
+// CqueryDlg() constructor
+CqueryDlg::CqueryDlg(wxWindow *parent, wxDB *pDb, char *tblName[], char *pWhereArg) : wxDialogBox (parent, "Query", 1, -1, -1, 480, 360)
+{
+       wxBeginBusyCursor();
+
+       colInf = 0;
+       dbTable = 0;
+       masterTableName = tblName[0];
+       widgetPtrsSet = FALSE;
+       pDB = pDb;
+
+       // Initialize the WHERE clause from the string passed in
+       pWhere = pWhereArg;                                                                                     // Save a pointer to the output buffer
+       if (strlen(pWhere) > DB_MAX_WHERE_CLAUSE_LEN)           // Check the length of the buffer passed in
+       {
+               wxString s;
+               s.sprintf("Maximum where clause length exceeded.\nLength must be less than %d", DB_MAX_WHERE_CLAUSE_LEN+1);
+               wxMessageBox(s.GetData(),"Error...",wxOK | wxICON_EXCLAMATION);
+               Close();
+               return;
+       }
+
+       // Build the dialog
+       SetLabelPosition(wxVERTICAL);
+
+       wxFont *ButtonFont = new wxFont(12,wxSWISS,wxNORMAL,wxBOLD);
+       wxFont *TextFont   = new wxFont(12,wxSWISS,wxNORMAL,wxNORMAL);
+
+       SetButtonFont(ButtonFont);
+       SetLabelFont(TextFont);
+       SetLabelPosition(wxVERTICAL);
+
+       pQueryCol1Msg                   = new wxMessage(this, "Column 1:", 10, 10, 69, 16, 0, "QueryCol1Msg");
+       pQueryCol1Choice                = new wxChoice(this, NULL, "", 10, 27, 250, 27, 0, 0, 0, "QueryCol1Choice");
+
+       pQueryNotMsg                    = new wxMessage(this, "NOT", 268, 10, -1, -1, 0, "QueryNotMsg");
+       pQueryNotCheck                  = new wxCheckBox(this, NULL, "", 275, 37, 20, 20, 0, "QueryNotCheck");
+
+       char *choice_strings[9];
+       choice_strings[0] = "=";
+       choice_strings[1] = "<";
+       choice_strings[2] = ">";
+       choice_strings[3] = "<=";
+       choice_strings[4] = ">=";
+       choice_strings[5] = "Begins";
+       choice_strings[6] = "Contains";
+       choice_strings[7] = "Like";
+       choice_strings[8] = "Between";
+       pQueryOperatorMsg               = new wxMessage(this, "Operator:", 305, 10, -1, -1, 0, "QueryOperatorMsg");
+       pQueryOperatorChoice    = new wxChoice(this, NULL, "", 305, 27, 80, 27, 9, choice_strings, 0, "QueryOperatorChoice");
+       
+       pQueryCol2Msg                   = new wxMessage(this, "Column 2:", 10, 65, 69, 16, 0, "QueryCol2Msg");
+       pQueryCol2Choice                = new wxChoice(this, NULL, "", 10, 82, 250, 27, 0, 0, 0, "QueryCol2Choice");
+
+       pQuerySqlWhereMsg               = new wxMessage(this, "SQL where clause:", 10, 141, -1, -1, 0, "QuerySqlWhereMsg");
+       pQuerySqlWhereMtxt      = new wxMultiText(this, NULL, "", "", 10, 159, 377, 134, 0, "QuerySqlWhereMtxt");
+
+       pQueryAddBtn                    = new wxButton(this, NULL, "&Add",      406,  24, 56, 26, 0, "QueryAddBtn");
+       pQueryAndBtn                    = new wxButton(this, NULL, "A&nd",      406,  58, 56, 26, 0, "QueryAndBtn");
+       pQueryOrBtn                             = new wxButton(this, NULL, "&Or",       406,  92, 56, 26, 0, "QueryOrBtn");
+
+       pQueryLParenBtn         = new wxButton(this, NULL, "(", 406, 126, 26, 26, 0, "QueryLParenBtn");
+       pQueryRParenBtn         = new wxButton(this, NULL, ")", 436, 126, 26, 26, 0, "QueryRParenBtn");
+
+       pQueryDoneBtn                   = new wxButton(this, NULL, "&Done",      406, 185, 56, 26, 0, "QueryDoneBtn");
+       pQueryClearBtn                  = new wxButton(this, NULL, "C&lear", 406, 218, 56, 26, 0, "QueryClearBtn");
+       pQueryCountBtn                  = new wxButton(this, NULL, "&Count", 406, 252, 56, 26, 0, "QueryCountBtn");
+
+       pQueryValue1Msg         = new wxMessage(this, "Value:", 277, 66, -1, -1, 0, "QueryValue1Msg");
+       pQueryValue1Txt         = new wxText(this, NULL, "", "", 277, 83, 108, 25, 0, "QueryValue1Txt");
+
+       pQueryValue2Msg         = new wxMessage(this, "AND", 238, 126, -1, -1, 0, "QueryValue2Msg");
+       pQueryValue2Txt         = new wxText(this, NULL, "", "", 277, 120, 108, 25, 0, "QueryValue2Txt");
+
+       pQueryHintGrp                   = new wxGroupBox(this, "",  10, 291, 377, 40, 0, "QueryHintGrp");
+       pQueryHintMsg                   = new wxMessage(this, "", 16, 306, -1, -1, 0, "QueryHintMsg");
+
+       widgetPtrsSet = TRUE;
+       // Initialize the dialog
+       wxString qualName;
+       pQueryCol2Choice->Append("VALUE -->");
+       colInf = pDB->GetColumns(tblName);
+       for (int i = 0; colInf[i].colName && strlen(colInf[i].colName); i++)
+       {
+               // If there is more than one table being queried, qualify
+               // the column names with the table name prefix.
+               if (tblName[1] && strlen(tblName[1]))
+               {
+                       qualName.sprintf("%s.%s", colInf[i].tableName, colInf[i].colName);
+                       pQueryCol1Choice->Append(qualName.GetData());
+                       pQueryCol2Choice->Append(qualName.GetData());
+               }
+               else  // Single table query, append just the column names
+               {
+                       pQueryCol1Choice->Append(colInf[i].colName);
+                       pQueryCol2Choice->Append(colInf[i].colName);
+               }
+       }
+
+       pQueryCol1Choice->SetSelection(0);
+       pQueryCol2Choice->SetSelection(0);
+       pQueryOperatorChoice->SetSelection(0);
+
+       pQueryValue2Msg->Show(FALSE);
+       pQueryValue2Txt->Show(FALSE);
+
+       pQueryHintMsg->SetLabel(langQRY_EQ);
+
+       pQuerySqlWhereMtxt->SetValue(pWhere);
+
+       wxEndBusyCursor();
+
+       // Display the dialog window
+       SetModal(TRUE);
+       Centre(wxBOTH);
+       Show(TRUE);
+
+}  // CqueryDlg() constructor
+
+
+void CqueryDlg::OnCommand(wxWindow& win, wxCommandEvent& event)
+{
+       // Widget pointers won't be set when the dialog is constructed.
+       // Control is passed through this function once for each widget on
+       // a dialog as the dialog is constructed.
+       if (!widgetPtrsSet)
+               return;
+
+       wxString widgetName = win.GetName();
+
+       // Operator choice box
+       if (widgetName == pQueryOperatorChoice->GetName())
+       {
+               // Set the help text
+               switch((qryOp) pQueryOperatorChoice->GetSelection())
+               {
+               case qryOpEQ:
+                       pQueryHintMsg->SetLabel(langQRY_EQ);
+                       break;
+               case qryOpLT:
+                       pQueryHintMsg->SetLabel(langQRY_LT);
+                       break;
+               case qryOpGT:
+                       pQueryHintMsg->SetLabel(langQRY_GT);
+                       break;
+               case qryOpLE:
+                       pQueryHintMsg->SetLabel(langQRY_LE);
+                       break;
+               case qryOpGE:
+                       pQueryHintMsg->SetLabel(langQRY_GE);
+                       break;
+               case qryOpBEGINS:
+                       pQueryHintMsg->SetLabel(langQRY_BEGINS);
+                       break;
+               case qryOpCONTAINS:
+                       pQueryHintMsg->SetLabel(langQRY_CONTAINS);
+                       break;
+               case qryOpLIKE:
+                       pQueryHintMsg->SetLabel(langQRY_LIKE);
+                       break;
+               case qryOpBETWEEN:
+                       pQueryHintMsg->SetLabel(langQRY_BETWEEN);
+                       break;
+               }
+
+               // Hide the value2 widget
+               pQueryValue2Msg->Show(FALSE);  // BETWEEN will show this widget
+               pQueryValue2Txt->Show(FALSE);  // BETWEEN will show this widget
+
+               // Disable the NOT operator for <, <=, >, >=
+               switch((qryOp) pQueryOperatorChoice->GetSelection())
+               {
+               case qryOpLT:
+               case qryOpGT:
+               case qryOpLE:
+               case qryOpGE:
+                       pQueryNotCheck->SetValue(0);
+                       pQueryNotCheck->Enable(FALSE);
+                       break;
+               default:
+                       pQueryNotCheck->Enable(TRUE);
+                       break;
+               }
+
+               // Manipulate the dialog to handle the selected operator
+               switch((qryOp) pQueryOperatorChoice->GetSelection())
+               {
+               case qryOpEQ:
+               case qryOpLT:
+               case qryOpGT:
+               case qryOpLE:
+               case qryOpGE:
+                       pQueryCol2Choice->Enable(TRUE);
+                       if (pQueryCol2Choice->GetSelection())   // Column name is highlighted
+                       {
+                               pQueryValue1Msg->Show(FALSE);
+                               pQueryValue1Txt->Show(FALSE);
+                       }
+                       else                                                                                            // "Value" is highlighted
+                       {
+                               pQueryValue1Msg->Show(TRUE);
+                               pQueryValue1Txt->Show(TRUE);
+                               pQueryValue1Txt->SetFocus();
+                       }
+                       break;
+               case qryOpBEGINS:
+               case qryOpCONTAINS:
+               case qryOpLIKE:
+                       pQueryCol2Choice->SetSelection(0);
+                       pQueryCol2Choice->Enable(FALSE);
+                       pQueryValue1Msg->Show(TRUE);
+                       pQueryValue1Txt->Show(TRUE);
+                       pQueryValue1Txt->SetFocus();
+                       break;
+               case qryOpBETWEEN:
+                       pQueryCol2Choice->SetSelection(0);
+                       pQueryCol2Choice->Enable(FALSE);
+                       pQueryValue2Msg->Show(TRUE);
+                       pQueryValue2Txt->Show(TRUE);
+                       pQueryValue1Msg->Show(TRUE);
+                       pQueryValue1Txt->Show(TRUE);
+                       pQueryValue1Txt->SetFocus();
+                       break;
+               }
+
+               return;
+
+       }  // Operator choice box
+
+       // Column 2 choice
+       if (widgetName == pQueryCol2Choice->GetName())
+       {
+               if (pQueryCol2Choice->GetSelection())   // Column name is highlighted
+               {
+                       pQueryValue1Msg->Show(FALSE);
+                       pQueryValue1Txt->Show(FALSE);
+               }
+               else                                                                                            // "Value" is highlighted
+               {
+                       pQueryValue1Msg->Show(TRUE);
+                       pQueryValue1Txt->Show(TRUE);
+                       pQueryValue1Txt->SetFocus();
+               }
+               return;
+
+       }  // Column 2 choice
+
+       // Add button
+       if (widgetName == pQueryAddBtn->GetName())
+       {
+               ProcessAddBtn();
+               return;
+
+       }  // Add button
+
+       // And button
+       if (widgetName == pQueryAndBtn->GetName())
+       {
+               AppendToWhere(" AND\n");
+               return;
+
+       }  // And button
+
+       // Or button
+       if (widgetName == pQueryOrBtn->GetName())
+       {
+               AppendToWhere(" OR\n");
+               return;
+
+       }  // Or button
+
+       // Left Paren button
+       if (widgetName == pQueryLParenBtn->GetName())
+       {
+               AppendToWhere("(");
+               return;
+
+       }  // Left Paren button
+
+       // Right paren button
+       if (widgetName == pQueryRParenBtn->GetName())
+       {
+               AppendToWhere(")");
+               return;
+
+       }  // Right Paren button
+
+       // Done button
+       if (widgetName == pQueryDoneBtn->GetName())
+       {
+               // Be sure the where clause will not overflow the output buffer
+               if (strlen(pQuerySqlWhereMtxt->GetValue()) > DB_MAX_WHERE_CLAUSE_LEN)
+               {
+                       wxString s;
+                       s.sprintf("Maximum where clause length exceeded.\nLength must be less than %d", DB_MAX_WHERE_CLAUSE_LEN+1);
+                       wxMessageBox(s.GetData(),"Error...",wxOK | wxICON_EXCLAMATION);
+                       return;
+               }
+               // Validate the where clause for things such as matching parens
+               if (!ValidateWhereClause())
+                       return;
+               // Copy the where clause to the output buffer and exit
+               strcpy(pWhere, pQuerySqlWhereMtxt->GetValue());
+               Close();
+               return;
+
+       }  // Done button
+
+       // Clear button
+       if (widgetName == pQueryClearBtn->GetName())
+       {
+               Bool Ok = (wxMessageBox("Are you sure you wish to clear the Query?","Confirm",wxYES_NO|wxICON_QUESTION) == wxYES);
+
+               if (Ok)
+                       pQuerySqlWhereMtxt->SetValue("");
+               return;
+
+       }  // Clear button
+
+       // Count button
+       if (widgetName == pQueryCountBtn->GetName())
+       {
+               wxBeginBusyCursor();
+               ProcessCountBtn();
+               wxEndBusyCursor();
+               return;
+
+       }  // Count button
+
+}  // CqueryDlg::OnCommand
+
+
+Bool CqueryDlg::OnClose()
+{
+       // Clean up
+       if (colInf)
+       {
+               delete [] colInf;
+               colInf = 0;
+       }
+
+       if (dbTable)
+       {
+               delete dbTable;
+               dbTable = 0;
+       }
+
+       GetParent()->SetFocus();
+       wxEndBusyCursor();
+       return TRUE;
+
+}  // CqueryDlg::OnClose()
+
+/*
+Bool CqueryDlg::SetWidgetPtrs()
+{
+       Bool abort = FALSE;
+
+       abort = abort || !(pQueryCol1Choice = (wxChoice *)GetWidgetPtr("QueryCol1Choice",this));
+       abort = abort || !(pQueryNotCheck = (wxCheckBox *)GetWidgetPtr("QueryNotCheck",this));
+       abort = abort || !(pQueryOperatorChoice = (wxChoice *)GetWidgetPtr("QueryOperatorChoice",this));
+       abort = abort || !(pQueryCol2Choice = (wxChoice *)GetWidgetPtr("QueryCol2Choice",this));
+       abort = abort || !(pQueryValue1Txt = (wxText *)GetWidgetPtr("QueryValue1Txt",this));
+       abort = abort || !(pQueryValue2Txt = (wxText *)GetWidgetPtr("QueryValue2Txt",this));
+       abort = abort || !(pQuerySqlWhereMtxt = (wxMultiText *)GetWidgetPtr("QuerySqlWhereMtxt",this));
+       abort = abort || !(pQueryAddBtn = (wxButton *)GetWidgetPtr("QueryAddBtn",this));
+       abort = abort || !(pQueryAndBtn = (wxButton *)GetWidgetPtr("QueryAndBtn",this));
+       abort = abort || !(pQueryOrBtn = (wxButton *)GetWidgetPtr("QueryOrBtn",this));
+       abort = abort || !(pQueryLParenBtn = (wxButton *)GetWidgetPtr("QueryLParenBtn",this));
+       abort = abort || !(pQueryRParenBtn = (wxButton *)GetWidgetPtr("QueryRParenBtn",this));
+       abort = abort || !(pQueryDoneBtn = (wxButton *)GetWidgetPtr("QueryDoneBtn",this));
+       abort = abort || !(pQueryClearBtn = (wxButton *)GetWidgetPtr("QueryClearBtn",this));
+       abort = abort || !(pQueryCountBtn = (wxButton *)GetWidgetPtr("QueryCountBtn",this));
+       abort = abort || !(pQueryHelpBtn = (wxButton *)GetWidgetPtr("QueryHelpBtn",this));
+       abort = abort || !(pQueryHintMsg = (wxMessage *)GetWidgetPtr("QueryHintMsg",this));
+
+       pFocusTxt = NULL;
+
+       return(widgetPtrsSet = !abort);
+
+}  // CqueryDlg::SetWidgetPtrs
+*/
+
+void CqueryDlg::AppendToWhere(char *s)
+{
+               wxString whereStr = pQuerySqlWhereMtxt->GetValue();
+               whereStr += s;
+               pQuerySqlWhereMtxt->SetValue(whereStr.GetData());
+
+}  // CqueryDlg::AppendToWhere()
+
+
+void CqueryDlg::ProcessAddBtn()
+{
+       qryOp oper = (qryOp) pQueryOperatorChoice->GetSelection();
+
+       // Verify that eveything is filled in correctly
+       if (pQueryCol2Choice->GetSelection() == 0)                      // "Value" is selected
+       {
+               // Verify that value 1 is filled in
+               if (strlen(pQueryValue1Txt->GetValue()) == 0)
+               {
+                       wxBell();
+                       pQueryValue1Txt->SetFocus();
+                       return;
+               }
+               // For the BETWEEN operator, value 2 must be filled in as well
+               if (oper == qryOpBETWEEN &&
+                        strlen(pQueryValue2Txt->GetValue()) == 0)
+               {
+                       wxBell();
+                       pQueryValue2Txt->SetFocus();
+                       return;
+               }
+       }
+
+       // Build the expression and append it to the where clause window
+       wxString s = pQueryCol1Choice->GetStringSelection();
+       
+       if (pQueryNotCheck->GetValue() && (oper != qryOpEQ))
+               s += " NOT";
+       
+       switch(oper)
+       {
+       case qryOpEQ:
+               if (pQueryNotCheck->GetValue()) // NOT box is checked
+                       s += " <>";
+               else
+                       s += " =";
+               break;
+       case qryOpLT:
+               s += " <";
+               break;
+       case qryOpGT:
+               s += " >";
+               break;
+       case qryOpLE:
+               s += " <=";
+               break;
+       case qryOpGE:
+               s += " >=";
+               break;
+       case qryOpBEGINS:
+       case qryOpCONTAINS:
+       case qryOpLIKE:
+               s += " LIKE";
+               break;
+       case qryOpBETWEEN:
+               s += " BETWEEN";
+               break;
+       }
+
+       s += " ";
+
+       int col1Idx = pQueryCol1Choice->GetSelection();
+
+       Bool quote = FALSE;
+       if (colInf[col1Idx].sqlDataType == SQL_VARCHAR  || 
+               oper == qryOpBEGINS                                                                     ||
+               oper == qryOpCONTAINS                                                           ||
+               oper == qryOpLIKE)
+               quote = TRUE;
+
+       if (pQueryCol2Choice->GetSelection())   // Column name
+               s += pQueryCol2Choice->GetStringSelection();
+       else  // Column 2 is a "value"
+       {
+               if (quote)
+                       s += "'";
+               if (oper == qryOpCONTAINS)
+                       s += "%";
+               s += pQueryValue1Txt->GetValue();
+               if (oper == qryOpCONTAINS || oper == qryOpBEGINS)
+                       s += "%";
+               if (quote)
+                       s += "'";
+       }
+
+       if (oper == qryOpBETWEEN)
+       {
+               s += " AND ";
+               if (quote)
+                       s += "'";
+               s += pQueryValue2Txt->GetValue();
+               if (quote)
+                       s += "'";
+       }
+
+       AppendToWhere(s.GetData());
+
+}  // CqueryDlg::ProcessAddBtn()
+
+
+void CqueryDlg::ProcessCountBtn()
+{
+       if (!ValidateWhereClause())
+               return;
+
+       if (dbTable == 0)  // wxTable object needs to be created and opened
+       {
+               if (!(dbTable = new wxTable(pDB, masterTableName, 0)))
+               {
+                       wxMessageBox("Memory allocation failed creating a wxTable object.","Error...",wxOK | wxICON_EXCLAMATION);
+                       return;
+               }
+               if (!dbTable->Open())
+               {
+                       wxString tStr;
+                       tStr  = "ODBC error during Open()\n\n";
+                       tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
+                       wxMessageBox(tStr.GetData(),"ODBC Error...",wxOK | wxICON_EXCLAMATION);
+                       return;
+               }
+       }
+
+       // Count() with WHERE clause
+       dbTable->where = pQuerySqlWhereMtxt->GetValue();
+       ULONG whereCnt = dbTable->Count();
+
+       // Count() of all records in the table
+       dbTable->where = 0;
+       ULONG totalCnt = dbTable->Count();
+
+       if (whereCnt > 0 || totalCnt == 0)
+       {
+               wxString tStr;
+               tStr.sprintf("%lu of %lu records match the query criteria.",whereCnt,totalCnt);
+               wxMessageBox(tStr.GetData(),"Notice...",wxOK | wxICON_INFORMATION);
+       }
+       else
+       {
+               wxString tStr;
+               tStr.sprintf("%lu of %lu records match the query criteria.\n\nEither the criteria entered produced a result set\nwith no records, or there was a syntactical error\nin the clause you entered.\n\nPress the details button to see if any database errors were reported.",whereCnt,totalCnt);
+               wxMessageBox(tStr.GetData(),"Notice...",wxOK | wxICON_INFORMATION);
+       }
+
+       // After a wxMessageBox, the focus does not necessarily return to the
+       // window which was the focus when the message box popped up, so return
+       // focus to the Query dialog for certain
+       SetFocus();
+
+}  // CqueryDlg::ProcessCountBtn()
+
+
+Bool CqueryDlg::ValidateWhereClause()
+{
+       wxString where = pQuerySqlWhereMtxt->GetValue();
+
+       if (where.Freq('(') != where.Freq(')'))
+       {
+               wxMessageBox("There are mismatched parenthesis in the constructed where clause","Error...",wxOK | wxICON_EXCLAMATION);
+               return(FALSE);
+       }
+       // After a wxMessageBox, the focus does not necessarily return to the
+       // window which was the focus when the message box popped up, so return
+       // focus to the Query dialog for certain
+       SetFocus();
+
+       return(TRUE);
+
+}  // CqueryDlg::ValidateWhereClause()
diff --git a/samples/db/dbtest.def b/samples/db/dbtest.def
new file mode 100644 (file)
index 0000000..013825e
--- /dev/null
@@ -0,0 +1,8 @@
+NAME         DBTEST
+DESCRIPTION  'Database wxWindows application'
+EXETYPE      WINDOWS
+STUB         'WINSTUB.EXE'
+CODE         PRELOAD MOVEABLE DISCARDABLE
+DATA         PRELOAD MOVEABLE MULTIPLE
+HEAPSIZE     1024
+STACKSIZE    8192
diff --git a/samples/db/dbtest.h b/samples/db/dbtest.h
new file mode 100644 (file)
index 0000000..76ee48e
--- /dev/null
@@ -0,0 +1,286 @@
+///////////////////////////////////////////////////////////////////////////////
+// Name:        dbtest.h
+// Purpose:     wxWindows database demo app
+// Author:      George Tasker
+// Modified by:
+// Created:     1998
+// RCS-ID:      $Id$
+// Copyright:   (c) 1998 Remstar International, Inc.
+// Licence:     wxWindows licence
+///////////////////////////////////////////////////////////////////////////////
+
+#pragma interface "dbtest.h"
+
+#include <wx/string.h>
+#include <wx/dbtable.h>
+
+enum           DialogModes {mView,mCreate,mEdit,mSearch};
+
+// ID for the menu quit command
+#define FILE_CREATE                    100
+#define FILE_EXIT                              199
+#define EDIT_PARAMETERS                200
+#define ABOUT_DEMO                     300
+
+
+// Name of the table to be created/opened
+const char     CONTACT_TABLE_NAME[]            =       "CONTACTS";
+
+// Nuber of columns in the above table
+const int      CONTACT_NO_COLS                 = 12;           // 0-11
+
+// Global structure for holding ODBC connection information
+struct DbStuff DbConnectInf;
+
+enum Language {langENGLISH, langFRENCH, langGERMAN, langSPANISH, langOTHER};
+
+// Forward class declarations
+class CeditorDlg;
+class CparameterDlg;
+
+const char paramFilename[] = "database.cfg";
+
+
+/*
+ * This class contains the actual data members that are used for transferring
+ * data back and forth from the database to the program.  
+ *
+ * NOTE: The object described in this class is just for example purposes, and has no
+ * real meaning other than to show each type of field being used by the database
+ */
+class CstructContact : public wxObject
+{
+       public:
+               char                                    Name[ 50+1 ];           //      Contact's name
+               char                                    Addr1[ 50+1 ];
+               char                                    Addr2[ 50+1 ];
+               char                                    City[ 25+1 ];
+               char                                    State[ 25+1 ];
+               char                                    PostalCode[ 15+1 ];
+               char                                    Country[ 20+1 ];
+               TIMESTAMP_STRUCT        JoinDate;                       // Date on which this person joined the wxWindows project
+               Language                                NativeLanguage; // Enumerated type indicating person's native language
+               bool                                    IsDeveloper;            // Is this person a developer for wxWindows, or just a subscriber
+               int                                     Contributions;          // Something to show off an integer field
+               ULONG                                   LinesOfCode;            // Something to show off a 'long' field
+};  // CstructContact
+
+
+//
+// NOTE: Ccontact inherits wxTable, which gives access to all the database functionality
+//
+class Ccontact : public wxTable, public CstructContact
+{ 
+       private:
+               bool                             freeDbConn;
+               void                             SetupColumns();
+
+       public:
+               wxString                         whereStr;
+               wxString                         qryWhereStr;   // Where string returned from the query dialog
+
+               Ccontact(wxDB *pwxDB=NULL);
+               ~Ccontact();
+
+               void                             Initialize();
+               bool                             CreateIndexes(void);
+               bool                             FetchByName(char *name);
+
+};  // Ccontact class definition
+
+
+typedef struct Cparameters
+{
+       // The length of these strings were arbitrarily picked, and are
+       // dependent on the OS and database engine you will be using.
+       char    ODBCSource[100+1];
+       char    UserName[25+1];
+       char    Password[25+1];
+} Cparameters;
+
+
+// Define a new application type
+class DatabaseDemoApp: public wxApp
+{
+       public:
+               Cparameters  params;
+               wxFrame         *OnInit(void);
+};  // DatabaseDemoApp
+
+DECLARE_APP(DatabaseDemoApp)
+
+// Define a new frame type
+class DatabaseDemoFrame: public wxFrame
+{ 
+       private:
+               CeditorDlg              *pEditorDlg;
+               CparameterDlg   *pParamDlg;
+
+       public:
+               DatabaseDemoFrame(wxFrame *frame, char *title, int x, int y, int w, int h);
+
+               void    OnMenuCommand(int id);
+               bool    OnClose(void);
+
+               void    CreateDataTable();
+               void    BuildEditorDialog();
+               void    BuildParameterDialog(wxWindow *parent);
+};  // DatabaseDemoFrame
+
+
+
+// *************************** CeditorDlg ***************************
+
+class CeditorDlg : public wxPanel
+{
+       private:
+               bool                                             widgetPtrsSet;
+               wxString                                         saveName;
+
+               // Pointers to all widgets on the dialog
+               wxButton                *pCreateBtn,  *pEditBtn,      *pDeleteBtn,  *pCopyBtn,  *pSaveBtn,  *pCancelBtn;
+               wxButton                *pPrevBtn,    *pNextBtn,      *pQueryBtn,   *pResetBtn, *pDoneBtn,  *pHelpBtn;
+               wxButton                *pNameListBtn;
+               wxText          *pNameTxt,    *pAddress1Txt,  *pAddress2Txt,*pCityTxt,  *pStateTxt, *pCountryTxt,*pPostalCodeTxt;
+               wxMessage       *pNameMsg,    *pAddress1Msg,  *pAddress2Msg,*pCityMsg,  *pStateMsg, *pCountryMsg,*pPostalCodeMsg;
+               wxText          *pJoinDateTxt,*pContribTxt,   *pLinesTxt;
+               wxMessage   *pJoinDateMsg,*pContribMsg,   *pLinesMsg;
+               wxRadioBox      *pDeveloperRadio;
+               wxChoice    *pNativeLangChoice;
+               wxMessage       *pNativeLangMsg;
+
+       public:
+               enum    DialogModes              mode;
+               Ccontact                                        *Contact;       // this is the table object that will be being manipulated
+
+               CeditorDlg(wxWindow *parent);
+               bool    OnClose(void);
+               void    OnCommand(wxWindow& win, wxCommandEvent& event);
+               void    OnActivate(bool) {};  // necessary for hot keys
+
+               void    FieldsEditable();
+               void    SetMode(enum DialogModes m);
+               bool    PutData();
+               bool    GetData();
+               bool    Save();
+               bool    GetNextRec();
+               bool    GetPrevRec();
+               bool    GetRec(char *whereStr);
+};  // CeditorDlg
+
+
+// *************************** CparameterDlg ***************************
+
+class CparameterDlg : public wxDialogBox
+{
+       private:
+               bool                                             widgetPtrsSet;
+               enum    DialogModes              mode;
+               bool                                             saved;
+               Cparameters                              savedParamSettings;
+
+               // Pointers to all widgets on the dialog
+               wxMessage       *pParamODBCSourceMsg;
+               wxListBox       *pParamODBCSourceList;
+               wxMessage       *pParamUserNameMsg,             *pParamPasswordMsg;
+               wxText          *pParamUserNameTxt,             *pParamPasswordTxt;
+               wxButton                *pParamSaveBtn,                 *pParamCancelBtn;
+
+       public:
+               CparameterDlg(wxWindow *parent);
+               bool    OnClose(void);
+               void    OnCommand(wxWindow& win, wxCommandEvent& event);
+               void    OnActivate(bool) {};  // necessary for hot keys
+
+               bool    PutData();
+               bool    GetData();
+               bool    Save();
+               void    FillDataSourceList();
+
+};  // CparameterDlg
+
+
+// *************************** CqueryDlg ***************************
+
+
+// QUERY DIALOG
+enum qryOp
+{
+       qryOpEQ,
+       qryOpLT,
+       qryOpGT,
+       qryOpLE,
+       qryOpGE,
+       qryOpBEGINS,
+       qryOpCONTAINS,
+       qryOpLIKE,
+       qryOpBETWEEN
+};
+
+
+// Query strings
+char * const langQRY_EQ                                                                                = "column = column | value";
+char * const langQRY_LT                                                                                = "column < column | value";
+char * const langQRY_GT                                                                                = "column > column | value";
+char * const langQRY_LE                                                                                = "column <= column | value";
+char * const langQRY_GE                                                                                = "column >= column | value";
+char * const langQRY_BEGINS                                                            = "columns that BEGIN with the string entered";
+char * const langQRY_CONTAINS                                                          = "columns that CONTAIN the string entered";
+char * const langQRY_LIKE                                                                      = "% matches 0 or more of any char; _ matches 1 char";
+char * const langQRY_BETWEEN                                                           = "column BETWEEN value AND value";
+
+
+class CqueryDlg : public wxDialogBox
+{
+       private:
+               CcolInf *colInf;                // Column inf. returned by db->GetColumns()
+               wxTable *dbTable;
+               char            *masterTableName;
+               char            *pWhere;                // A pointer to the storage for the resulting where clause
+               wxDB            *pDB;
+
+       public:
+               bool                                    widgetPtrsSet;
+
+               // Widget pointers
+               wxMessage                       *pQueryCol1Msg;
+               wxChoice                                *pQueryCol1Choice;
+               wxMessage                       *pQueryNotMsg;
+               wxCheckBox                      *pQueryNotCheck;
+               wxMessage                       *pQueryOperatorMsg;
+               wxChoice                                *pQueryOperatorChoice;
+               wxMessage                       *pQueryCol2Msg;
+               wxChoice                                *pQueryCol2Choice;
+               wxMessage                       *pQueryValue1Msg;
+               wxText                          *pQueryValue1Txt;
+               wxMessage                       *pQueryValue2Msg;
+               wxText                          *pQueryValue2Txt;
+               wxMessage                       *pQuerySqlWhereMsg;
+               wxMultiText                     *pQuerySqlWhereMtxt;
+               wxButton                                *pQueryAddBtn;
+               wxButton                                *pQueryAndBtn;
+               wxButton                                *pQueryOrBtn;
+               wxButton                                *pQueryLParenBtn;
+               wxButton                                *pQueryRParenBtn;
+               wxButton                                *pQueryDoneBtn;
+               wxButton                                *pQueryClearBtn;
+               wxButton                                *pQueryCountBtn;
+               wxButton                                *pQueryHelpBtn;
+               wxGroupBox                      *pQueryHintGrp;
+               wxMessage                       *pQueryHintMsg;
+
+               wxText                          *pFocusTxt;
+
+               CqueryDlg(wxWindow *parent, wxDB *pDb, char *tblName[], char *pWhereArg);
+
+               void            OnCommand(wxWindow& win, wxCommandEvent& event);
+               bool            OnClose();
+               void            OnActivate(bool) {};  // necessary for hot keys
+
+//             bool            SetWidgetPtrs();
+               void            AppendToWhere(char *s);
+               void            ProcessAddBtn();
+               void            ProcessCountBtn();
+               bool            ValidateWhereClause();
+
+};  // CqueryDlg
diff --git a/samples/db/dbtest.ico b/samples/db/dbtest.ico
new file mode 100644 (file)
index 0000000..c4dbd3f
Binary files /dev/null and b/samples/db/dbtest.ico differ
diff --git a/samples/db/dbtest.rc b/samples/db/dbtest.rc
new file mode 100644 (file)
index 0000000..31d5205
--- /dev/null
@@ -0,0 +1,3 @@
+db_icon ICON "dbtest.ico"
+#include "wx.rc"
+
diff --git a/samples/db/listdb.cpp b/samples/db/listdb.cpp
new file mode 100644 (file)
index 0000000..330c79d
--- /dev/null
@@ -0,0 +1,412 @@
+///////////////////////////////////////////////////////////////////////////////
+// Name:        listdb.cpp
+// Purpose:     Data table lookup listbox code
+// Author:      George Tasker/Doug Card
+// Modified by:
+// Created:     1996
+// RCS-ID:      $Id$
+// Copyright:   (c) 1996 Remstar International, Inc.
+// Licence:     wxWindows licence
+///////////////////////////////////////////////////////////////////////////////
+
+/*
+// SYNOPSIS START
+
+    Member functions for the classes defined in LISTDB.H
+
+        This class is used to present a generic ListBox lookup window for
+        use with any of the object creation/selection choice widgets.   This 
+        dialog window will present a (possibly) scrolling list of values
+        that come from a data table source.  Based on the object type passed
+        in the constructor, a ListBox is built to present the user with a
+        single selection listbox.
+
+        The string selected from the list box is stored in the Global variable
+        "ListDB_Seclection", and will remain set until another interation of this
+        routine is called.
+
+        For each object (database) type that is to be used, an overridden
+        constructor should be written to appropriately link to the proper
+        data table/object for building the list.
+          
+    The data table record access is all handled through the routines
+    in this module, interfacing with the methods defined in wxTable.
+
+        All objects which use data table access must be initialized and
+        have opened the table prior to passing them in the dialog
+        constructor, and the 'where' query should already have been set
+        and performed before creating this dialog instance.
+    
+// SYNOPSIS STOP
+*/
+
+#ifdef __GNUG__
+#pragma implementation "listdb.h"
+#endif
+
+#include  "wx/wxprec.h"
+
+#ifdef    __BORLANDC__
+#pragma hdrstop
+#endif  //__BORLANDC__
+
+#ifndef WX_PRECOMP
+#include  <wx/wx.h>
+#endif //WX_PRECOMP
+
+#include <wx/dbtable.h>
+
+#include "listdb.h"
+
+// Global structure for holding ODBC connection information
+extern DbStuff DbConnectInf;
+
+// Global database connection
+extern wxDB *READONLY_DB;
+
+
+// Used for passing the selected listbox selection back to the calling
+// routine.  This variable must be declared as 'extern' in the calling
+// source module
+char ListDB_Selection[LOOKUP_COL_LEN+1];
+
+// If the listbox contains two columns of data, the second column is
+// returned in this variable.
+char ListDB_Selection2[LOOKUP_COL_LEN+1];
+
+// Constants
+const LISTDB_NO_SPACES_BETWEEN_COLS = 3;
+
+
+// Clookup constructor
+Clookup::Clookup(char *tblName, char *colName) : wxTable(READONLY_DB, tblName, 1)
+{
+
+       SetColDefs (0, colName, DB_DATA_TYPE_VARCHAR, lookupCol,        SQL_C_CHAR, LOOKUP_COL_LEN+1, FALSE, FALSE);
+
+}  // Clookup()
+
+
+// Clookup2 constructor
+Clookup2::Clookup2(char *tblName, char *colName1, char *colName2, wxDB *pDb)
+   : wxTable(pDb, tblName, (1 + (strlen(colName2) > 0)))
+{
+       int i = 0;
+
+       SetColDefs (i, colName1, DB_DATA_TYPE_VARCHAR, lookupCol1,      SQL_C_CHAR, LOOKUP_COL_LEN+1, FALSE, FALSE);
+
+       if (strlen(colName2) > 0)
+               SetColDefs (++i, colName2, DB_DATA_TYPE_VARCHAR, lookupCol2,    SQL_C_CHAR, LOOKUP_COL_LEN+1, FALSE, FALSE);
+
+}  // Clookup2()
+
+
+// This is a generic lookup constructor that will work with any table and any column
+ClookUpDlg::ClookUpDlg(wxWindow *parent, char *windowTitle, char *tableName, char *colName,
+       char *where, char *orderBy)  : wxDialogBox (parent, "Select...", 1, -1, -1, 400, 290)
+{
+       wxBeginBusyCursor();
+       
+       strcpy(ListDB_Selection,"");
+       widgetPtrsSet = FALSE;
+       lookup  = 0;
+       lookup2 = 0;
+       noDisplayCols = 1;
+       col1Len = 0;
+
+       // Build the dialog
+       SetLabelPosition(wxVERTICAL);
+
+       wxFont *ButtonFont = new wxFont(12,wxSWISS,wxNORMAL,wxBOLD);
+       wxFont *TextFont   = new wxFont(12,wxSWISS,wxNORMAL,wxNORMAL);
+
+       SetButtonFont(ButtonFont);
+       SetLabelFont(TextFont);
+       SetLabelPosition(wxVERTICAL);
+
+       pLookUpSelectList               = new wxListBox(this, NULL, "", wxSINGLE|wxALWAYS_SB, 5, 15, 384, 195, 0, 0, 0, "LookUpSelectList");
+       pLookUpOkBtn                    = new wxButton(this, NULL, "&Ok",               113, 222, 70, 35, 0, "LookUpOkBtn");
+       pLookUpCancelBtn                = new wxButton(this, NULL, "C&ancel",   212, 222, 70, 35, 0, "LookUpCancelBtn");
+
+       widgetPtrsSet = TRUE;
+
+       // Query the lookup table and display the result set
+       if (!(lookup = new Clookup(tableName, colName)))
+       {
+               wxMessageBox("Error allocating memory for 'Clookup'object.","Error...");
+               Close();
+               return;
+       }
+
+       if (!lookup->Open())
+       {
+               wxString tStr;
+               tStr.sprintf("Unable to open the table '%s'.",tableName);
+               wxMessageBox(tStr.GetData(),"ODBC Error...");
+               Close();
+               return;
+       }
+
+       lookup->orderBy = orderBy;
+       lookup->where = where;
+       if (!lookup->Query())
+       {
+               wxMessageBox("ODBC error during Query()","ODBC Error...");
+               Close();
+               return;
+       }
+
+       // Fill in the list box from the query result set
+       while (lookup->GetNext())
+               pLookUpSelectList->Append(lookup->lookupCol);
+
+       // Highlight the first list item
+       pLookUpSelectList->SetSelection(0);
+
+       // Make the OK activate by pressing Enter
+       if (pLookUpSelectList->Number())
+               pLookUpOkBtn->SetDefault();
+       else
+       {
+               pLookUpCancelBtn->SetDefault();
+               pLookUpOkBtn->Enable(FALSE);
+       }
+
+       // Display the dialog window
+       SetTitle(windowTitle);
+       Centre(wxBOTH);
+       wxEndBusyCursor();
+       Show(TRUE);
+
+}  // Generic lookup constructor
+
+
+//
+// This is a generic lookup constructor that will work with any table and any column.
+// It extends the capabilites of the lookup dialog in the following ways:
+//
+//     1) 2 columns rather than one
+// 2) The ability to select DISTINCT column values
+//
+// Only set distinctValues equal to true if necessary.  In many cases, the constraints
+// of the index(es) will enforce this uniqueness.  Selecting DISTINCT does require
+// overhead by the database to ensure that all values returned are distinct.  Therefore,
+// use this ONLY when you need it.
+//
+// For complicated queries, you can pass in the sql select statement.  This would be
+// necessary if joins are involved since by default both columns must come from the
+// same table.
+//
+// If you do query by sql statement, you must pass in the maximum lenght of column1,
+// since it cannot be derived when you query using your own sql statement.
+//
+// The optional database connection can be used if you'd like the lookup class
+// to use a database pointer other than the global READONLY_DB.  This is necessary if
+// records are being saved, but not committed to the db, yet should be included
+// in the lookup window.
+//
+ClookUpDlg::ClookUpDlg(wxWindow *parent, char *windowTitle, char *tableName,
+       char *dispCol1, char *dispCol2, char *where, char *orderBy, bool distinctValues,
+       char *selectStmt, int maxLenCol1, wxDB *pDb, bool allowOk)  : wxDialogBox (parent, "Select...", 1, -1, -1, 400, 290)
+{
+       wxBeginBusyCursor();
+       
+       strcpy(ListDB_Selection,"");
+       strcpy(ListDB_Selection2,"");
+       widgetPtrsSet = FALSE;
+       lookup  = 0;
+       lookup2 = 0;
+       noDisplayCols = (strlen(dispCol2) ? 2 : 1);
+       col1Len = 0;
+
+       // Build the dialog
+       SetLabelPosition(wxVERTICAL);
+
+       wxFont *ButtonFont = new wxFont(12,wxSWISS,wxNORMAL,wxBOLD);
+       wxFont *TextFont   = new wxFont(12,wxSWISS,wxNORMAL,wxNORMAL);
+       wxFont *FixedFont  = new wxFont(12,wxMODERN,wxNORMAL,wxNORMAL);
+
+       SetButtonFont(ButtonFont);
+       SetLabelFont(TextFont);
+       SetLabelPosition(wxVERTICAL);
+
+       // this is done with fixed font so that the second column (if any) will be left
+       // justified in the second column
+       SetButtonFont(FixedFont);
+       pLookUpSelectList               = new wxListBox(this, NULL, "", wxSINGLE|wxALWAYS_SB, 5, 15, 384, 195, 0, 0, 0, "LookUpSelectList");
+       SetButtonFont(ButtonFont);
+       pLookUpOkBtn                    = new wxButton(this, NULL, "&Ok",               113, 222, 70, 35, 0, "LookUpOkBtn");
+       pLookUpCancelBtn                = new wxButton(this, NULL, "C&ancel",   212, 222, 70, 35, 0, "LookUpCancelBtn");
+
+       widgetPtrsSet = TRUE;
+
+       // Query the lookup table and display the result set
+       if (!(lookup2 = new Clookup2(tableName, dispCol1, dispCol2, pDb)))
+       {
+               wxMessageBox("Error allocating memory for 'Clookup2'object.","Error...");
+               Close();
+               return;
+       }
+
+       if (!lookup2->Open())
+       {
+               wxString tStr;
+               tStr.sprintf("Unable to open the table '%s'.",tableName);
+               wxMessageBox(tStr.GetData(),"ODBC Error...");
+               Close();
+               return;
+       }
+
+       // If displaying 2 columns, determine the maximum length of column1
+       int maxColLen;
+       if (maxLenCol1)
+               maxColLen = col1Len = maxLenCol1;  // user passed in max col length for column 1
+       else
+       {
+               maxColLen = LOOKUP_COL_LEN;
+               if (strlen(dispCol2))
+               {
+                       wxString q = "SELECT MAX({fn LENGTH(";
+                       q += dispCol1;
+                       q += ")}), NULL";
+                       q += " FROM ";
+                       q += tableName;
+                       if (strlen(where))
+                       {
+                               q += " WHERE ";
+                               q += where;
+                       }
+                       if (!lookup2->QueryBySqlStmt(q.GetData()))
+                       {
+                               wxMessageBox("ODBC error during QueryBySqlStmt()","ODBC Error...");
+                               Close();
+                               return;
+                       }
+                       if (lookup2->GetNext())
+                               maxColLen = col1Len = atoi(lookup2->lookupCol1);
+                       else
+                               wxMessageBox("ODBC error during GetNext()","ODBC Error...");
+               }
+       }
+
+       // Query the actual record set
+       if (selectStmt && strlen(selectStmt))   // Query by sql stmt passed in
+       {
+               if (!lookup2->QueryBySqlStmt(selectStmt))
+               {
+                       wxMessageBox("ODBC error during QueryBySqlStmt()","ODBC Error...");
+                       Close();
+                       return;
+               }
+       }
+       else    // Query using where and order by clauses
+       {
+               lookup2->orderBy = orderBy;
+               lookup2->where = where;
+               if (!lookup2->Query(FALSE, distinctValues))
+               {
+                       wxMessageBox("ODBC error during Query()","ODBC Error...");
+                       Close();
+                       return;
+               }
+       }
+
+       // Fill in the list box from the query result set
+       wxString s;
+       while (lookup2->GetNext())
+       {
+               s = lookup2->lookupCol1;
+               if (strlen(dispCol2))           // Append the optional column 2
+               {
+                       s.Append(' ', (maxColLen + LISTDB_NO_SPACES_BETWEEN_COLS - strlen(lookup2->lookupCol1)));
+                       s.Append(lookup2->lookupCol2);
+               }
+               pLookUpSelectList->Append(s.GetData());
+       }
+
+       // Highlight the first list item
+       pLookUpSelectList->SetSelection(0);
+
+       // Make the OK activate by pressing Enter
+       if (pLookUpSelectList->Number())
+               pLookUpOkBtn->SetDefault();
+       else
+       {
+               pLookUpCancelBtn->SetDefault();
+               pLookUpOkBtn->Enable(FALSE);
+       }
+
+       pLookUpOkBtn->Enable(allowOk);
+
+       // Display the dialog window
+       SetTitle(windowTitle);
+       Centre(wxBOTH);
+       wxEndBusyCursor();
+       Show(TRUE);
+
+}  // Generic lookup constructor 2
+
+
+bool ClookUpDlg::OnClose(void)
+{
+       widgetPtrsSet = FALSE;
+       GetParent()->Enable(TRUE);
+
+       if (lookup)
+               delete lookup;
+       if (lookup2)
+               delete lookup2;
+
+       wxEndBusyCursor();
+       return TRUE;
+
+}  // ClookUpDlg::OnClose
+
+
+void ClookUpDlg::OnCommand(wxWindow& win, wxCommandEvent& event)
+{
+       wxString widgetName = win.GetName();
+
+       if (widgetPtrsSet)
+       {
+               // OK Button
+               if (widgetName == pLookUpOkBtn->GetName())
+               {
+                       if (pLookUpSelectList->GetSelection() != -1)
+                       {
+                               if (noDisplayCols == 1)
+                                       strcpy (ListDB_Selection, pLookUpSelectList->GetStringSelection());
+                               else  // 2 display columns
+                               {
+                                       wxString s = pLookUpSelectList->GetStringSelection();
+                                       // Column 1
+                                       s = s.SubString(0, col1Len-1);
+                                       s = s.Strip();
+                                       strcpy(ListDB_Selection, s.GetData());
+                                       // Column 2
+                                       s = pLookUpSelectList->GetStringSelection();
+                                       s = s.From(col1Len + LISTDB_NO_SPACES_BETWEEN_COLS);
+                                       s = s.Strip();
+                                       strcpy(ListDB_Selection2, s.GetData());
+                               }
+                       }
+                       else
+                       {
+                               strcpy(ListDB_Selection,"");
+                               strcpy(ListDB_Selection2,"");
+                       }
+                       Close();
+               }  // OK Button
+
+               // Cancel Button
+               if (widgetName == pLookUpCancelBtn->GetName())
+               {
+                       strcpy (ListDB_Selection,"");
+                       strcpy (ListDB_Selection2,"");
+                       Close();
+               }  // Cancel Button
+       }
+
+};  // ClookUpDlg::OnCommand
+
+// *********************************** listdb.cpp **********************************
diff --git a/samples/db/listdb.h b/samples/db/listdb.h
new file mode 100644 (file)
index 0000000..0d966c9
--- /dev/null
@@ -0,0 +1,125 @@
+///////////////////////////////////////////////////////////////////////////////
+// Name:        listdb.h
+// Purpose:     wxWindows database demo app
+// Author:      George Tasker
+// Modified by:
+// Created:     1996
+// RCS-ID:      $Id$
+// Copyright:   (c) 1996 Remstar International, Inc.
+// Licence:     wxWindows licence
+///////////////////////////////////////////////////////////////////////////////
+
+#pragma interface "listdb.h"
+
+/*
+/*
+// SYNOPSIS START
+
+       Contains dialog class for creating a data table lookup listbox
+
+// SYNOPSIS STOP
+*/
+
+#ifndef LISTDB_DOT_H
+#define LISTDB_DOT_H
+
+
+#include <wx/dbtable.h>
+
+const LOOKUP_COL_LEN = 250;
+
+// Global database connection
+extern wxDB *READONLY_DB;
+
+// Clookup class
+class Clookup : public wxTable
+{
+       public:
+
+               char lookupCol[LOOKUP_COL_LEN+1];
+
+               Clookup(char *tblName, char *colName);
+
+};  // Clookup
+
+// Clookup2 class
+class Clookup2 : public wxTable
+{
+       public:
+
+               char lookupCol1[LOOKUP_COL_LEN+1];
+               char lookupCol2[LOOKUP_COL_LEN+1];
+
+               Clookup2(char *tblName, char *colName1, char *colName2, wxDB *pDb);
+
+};  // Clookup2
+
+class ClookUpDlg : public wxDialogBox
+{
+       private:
+               bool                     widgetPtrsSet;
+               int                      currentCursor;
+               Clookup         *lookup;
+               Clookup2                *lookup2;
+               int                      noDisplayCols;
+               int                      col1Len;
+
+               wxListBox       *pLookUpSelectList;
+               wxButton                *pLookUpOkBtn;
+               wxButton                *pLookUpCancelBtn;
+
+       public:
+
+               // This is a generic lookup constructor that will work with any table and any column
+               ClookUpDlg(wxWindow     *parent,
+                                         char          *windowTitle,
+                                         char          *tableName,
+                                         char          *colName,
+                                         char          *where,
+                                         char          *orderBy);
+
+               //
+               // This is a generic lookup constructor that will work with any table and any column.
+               // It extends the capabilites of the lookup dialog in the following ways:
+               //
+               //      1) 2 columns rather than one
+               // 2) The ability to select DISTINCT column values
+               //
+               // Only set distinctValues equal to true if necessary.  In many cases, the constraints
+               // of the index(es) will enforce this uniqueness.  Selecting DISTINCT does require
+               // overhead by the database to ensure that all values returned are distinct.  Therefore,
+               // use this ONLY when you need it.
+               //
+               // For complicated queries, you can pass in the sql select statement.  This would be
+               // necessary if joins are involved since by default both columns must come from the
+               // same table.
+               //
+               // If you do query by sql statement, you must pass in the maximum length of column1,
+               // since it cannot be derived when you query using your own sql statement.
+               //
+               // The optional database connection can be used if you'd like the lookup class
+               // to use a database pointer other than the global READONLY_DB.  This is necessary if
+               // records are being saved, but not committed to the db, yet should be included
+               // in the lookup window.
+               //
+               ClookUpDlg(wxWindow     *parent,
+                                         char          *windowTitle,
+                                         char          *tableName,
+                                         char          *dispCol1,                                      // Must have at least 1 display column
+                                         char          *dispCol2,                                      // Optional
+                                         char          *where,
+                                         char          *orderBy,
+                                         bool           distinctValues,                        // e.g. SELECT DISTINCT ...
+                                         char          *selectStmt = 0,                        // If you wish to query by SQLstmt (complicated lookups)
+                                         int                    maxLenCol1 = 0,                        // Mandatory if querying by SQLstmt
+                                         wxDB                  *pDb = READONLY_DB,             // Database connection pointer
+                                         bool           allowOk                = TRUE);                // is the OK button enabled
+
+               void            OnCommand(wxWindow& win, wxCommandEvent& event);
+               bool            OnClose();
+               void            OnActivate(bool) {};  // necessary for hot keys
+};
+
+#endif  // LISTDB_DOT_H
+
+// ************************************ listdb.h *********************************
diff --git a/samples/db/makefile.nt b/samples/db/makefile.nt
new file mode 100644 (file)
index 0000000..2db0a71
--- /dev/null
@@ -0,0 +1,97 @@
+#
+# File:                makefile.nt
+# Author:      George Tasker
+# Created:     1998
+# Updated:     
+#
+# "%W% %G%"
+#
+# Makefile : Builds database example (MS VC++).
+
+!if "$(FINAL)" == ""
+FINAL=0
+!endif
+
+
+!if "$(MSVCDIR)" == ""
+MSVCDIR=c:\devstudio\vc
+!endif
+
+# Set WXDIR for your system
+WXDIR     = $(WXWIN)
+THISDIR   = $(WXDIR)\samples\database
+WXODBCDIR = $(WXDIR)\utils\wxodbc
+
+!if "$(MSVCDIR)" == ""
+DBLIBS=$(MSDEVDIR)\lib\odbc32.lib
+!else
+DBLIBS=$(MSVCDIR)\lib\odbc32.lib
+!endif
+
+EXTRAINC = -I$(WXODBCDIR)\src
+EXTRALIBS = $(DBLIBS) $(WXODBCDIR)\lib\wxodbc.lib
+
+!include $(WXDIR)\src\ntwxwin.mak
+
+PROGRAM=database
+
+OBJECTS = $(PROGRAM).$(OBJSUFF) listdb.$(OBJSUFF)
+
+all:    wxodbc $(PROGRAM).exe
+
+$(PROGRAM):    $(PROGRAM).exe
+
+gt:
+               cd $(CPPFLAGS)
+
+wxodbc:
+        cd $(WXODBCDIR)\src
+        nmake -f makefile.nt FINAL=$(FINAL)
+        cd $(THISDIR)
+
+wx:
+        cd $(WXDIR)\src\msw
+        nmake -f makefile.nt FINAL=$(FINAL)
+        cd $(THISDIR)
+
+wxclean:
+        cd $(WXDIR)\src\msw
+        nmake -f makefile.nt clean
+        cd $(THISDIR)
+        cd $(WXODBCDIR)\src
+        nmake -f makefile.nt clean
+        cd $(THISDIR)
+
+
+$(PROGRAM).exe: $(DUMMYOBJ) $(OBJECTS) $(PROGRAM).res
+       $(link) @<<
+-out:$(PROGRAM).exe
+$(LINKFLAGS)
+$(DUMMYOBJ) $(OBJECTS) $(PROGRAM).res
+$(LIBS)
+<<
+
+
+listdb.$(OBJSUFF): $(*B).$(SRCSUFF) $(*B).h 
+       $(cc) @<<
+$(CPPFLAGS) /c /Fo$(*B).$(OBJSUFF) /Tp $(*B).$(SRCSUFF)
+<<
+
+
+$(PROGRAM).$(OBJSUFF): $(PROGRAM).$(SRCSUFF) $(PROGRAM).h listdb.h
+       $(cc) @<<
+$(CPPFLAGS) /c /Fo$(*B).$(OBJSUFF) /Tp $(*B).$(SRCSUFF)
+<<
+
+
+$(PROGRAM).res: $(PROGRAM).rc $(WXDIR)\include\wx\msw\wx.rc
+    $(rc) -r /i$(WXDIR)\include -fo$@ $(PROGRAM).rc
+
+
+clean:
+        -erase *.obj
+        -erase *.exe
+        -erase *.res
+        -erase *.map
+        -erase *.sbr
+        -erase *.pdb
diff --git a/samples/db/makefile.unx b/samples/db/makefile.unx
new file mode 100644 (file)
index 0000000..3c4142d
--- /dev/null
@@ -0,0 +1,71 @@
+#
+# File:                makefile.unx
+# Author:      Terry Tompkins
+# Created:     1998
+# Updated:     
+# Copyright:   (c) 1998, Remstar International
+#
+# Makefile for wxDB (UNIX).
+
+OBJDIR=database
+OBJSUFF=.o
+SRCSUFF=.cpp
+WXDIR = $(WXWIN)
+
+# All common UNIX compiler flags and options are now in
+# this central makefile.
+include $(WXDIR)/src/make.env
+
+PROGRAM=database
+
+OBJECTS = $(OBJDIR)/$(PROGRAM).$(OBJSUFF) $(OBJDIR)/table.$(OBJSUFF) $(OBJDIR)/db.$(OBJSUFF) $(OBJDIR)/listdb.$(OBJSUFF)
+
+.SUFFIXES:
+
+all:    $(OBJDIR) $(PROGRAM)$(GUISUFFIX)
+
+wx:
+
+
+motif:
+       $(MAKE) -f makefile.unx GUISUFFIX=_motif GUI=-Dwx_motif GUISUFFIX=_motif OPT='$(OPT)' LDLIBS='$(MOTIFLDLIBS)' WXLIB=$(WXDIR)/lib/libwx_motif.a  OPTIONS='$(OPTIONS)' DEBUG='$(DEBUG)' WARN='$(WARN)' XLIB='$(XLIB)' XINCLUDE='$(XINCLUDE)' XVIEW_LINK=
+
+xview:
+       cd $(WXDIR)/src/x; $(MAKE) -f makefile.unx xview
+       $(MAKE) -f makefile.unx GUI=-Dwx_xview GUISUFFIX=_ol CC=$(CC) OPTIONS='$(OPTIONS)' DEBUG='$(DEBUG)' WARN='$(WARN)' XLIB='$(XLIB)' XINCLUDE='$(XINCLUDE)'
+
+hp:
+       cd $(WXDIR)/src/x; $(MAKE) -f makefile.unx hp
+       $(MAKE) -f makefile.unx GUI=-Dwx_motif GUISUFFIX=_hp CC=CC DEBUG='$(DEBUG)' WARN='-w' \
+         XINCLUDE='$(HPXINCLUDE)' XLIB='$(HPXLIB)' XVIEW_LINK='' LDLIBS='$(HPLDLIBS)'
+
+$(OBJDIR):
+       mkdir $(OBJDIR)
+
+$(PROGRAM)$(GUISUFFIX):        $(DUMMYOBJ) $(DBLIBS) $(OBJECTS) $(WXLIB)
+       $(CC) $(LDFLAGS) -o $(PROGRAM)$(GUISUFFIX) $(OBJECTS) $(XVIEW_LINK) $(LDLIBS)
+
+$(OBJDIR)/$(PROGRAM).$(OBJSUFF):       $(PROGRAM).$(SRCSUFF)
+       $(CC) -c $(CPPFLAGS) -o $@ $(PROGRAM).$(SRCSUFF)
+
+$(OBJDIR)/table.$(OBJSUFF):    table.$(SRCSUFF)
+       $(CC) -c $(CPPFLAGS) -o $@ table.$(SRCSUFF)
+
+$(OBJDIR)/db.$(OBJSUFF):       db.$(SRCSUFF)
+       $(CC) -c $(CPPFLAGS) -o $@ db.$(SRCSUFF)
+
+$(OBJDIR)/listdb.$(OBJSUFF):   listdb.$(SRCSUFF)
+       $(CC) -c $(CPPFLAGS) -o $@ listdb.$(SRCSUFF)
+
+clean_motif:
+       $(MAKE) -f makefile.unx GUISUFFIX=_motif cleanany
+
+clean_ol:
+       $(MAKE) -f makefile.unx GUISUFFIX=_ol cleanany
+
+clean_hp:
+       $(MAKE) -f makefile.unx GUISUFFIX=_hp cleanany
+
+cleanany:
+       rm -f $(OBJECTS) $(PROGRAM)$(GUISUFFIX) core
+
index aa671d0adcdfce8025c5e15a2d2b693cdbf0eeaf..d2949f42193a8f20f16a7303744210318826584b 100644 (file)
@@ -59,8 +59,8 @@ PlayerSelectionDialog::PlayerSelectionDialog(
                                                0, 0,
                                                wxLB_SINGLE
                                                );
-/*
-    Robert Roebling
+#if 1
+//    Robert Roebling
     
        int numPlayers = 0;
        wxString* players = 0;
@@ -70,7 +70,7 @@ PlayerSelectionDialog::PlayerSelectionDialog(
                list->Append(players[i]);
        }
        delete players;
-*/
+#endif
 
        m_textField = new wxTextCtrl(this, -1, "", wxDefaultPosition, wxDefaultSize, 0);
 
diff --git a/src/common/db.cpp b/src/common/db.cpp
new file mode 100644 (file)
index 0000000..7d04ee2
--- /dev/null
@@ -0,0 +1,1320 @@
+///////////////////////////////////////////////////////////////////////////////
+// Name:        db.cpp
+// Purpose:     Implementation of the wxDB class.  The wxDB class represents a connection
+//              to an ODBC data source.  The wxDB class allows operations on the data
+//              source such as opening and closing the data source.
+// Author:      Doug Card
+// Modified by:
+// Created:     9.96
+// RCS-ID:      $Id$
+// Copyright:   (c) 1996 Remstar International, Inc.
+// Licence:     wxWindows licence, plus:
+// Notice:             This class library and its intellectual design are free of charge for use,
+//              modification, enhancement, debugging under the following conditions:
+//              1) These classes may only be used as part of the implementation of a
+//                 wxWindows-based application
+//              2) All enhancements and bug fixes are to be submitted back to the wxWindows
+//                 user groups free of all charges for use with the wxWindows library.
+//              3) These classes may not be distributed as part of any other class library,
+//                 DLL, text (written or electronic), other than a complete distribution of
+//                 the wxWindows GUI development toolkit.
+///////////////////////////////////////////////////////////////////////////////
+
+/*
+// SYNOPSIS START
+// SYNOPSIS STOP
+*/
+
+#pragma implementation "db.h"
+
+/*
+#ifdef _CONSOLE
+       #include <iostream.h>
+#endif
+*/
+
+#include  "wx/wxprec.h"
+
+#ifdef    __BORLANDC__
+  #pragma hdrstop
+#endif  //__BORLANDC__
+
+#ifndef WX_PRECOMP
+  #include  <wx/wx.h>
+#endif //WX_PRECOMP
+
+#if USE_ODBC
+
+#include <wx/db.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+DbList *PtrBegDbList = 0;
+
+/********** wxDB Constructor **********/
+wxDB::wxDB(HENV &aHenv)
+{
+       int i;
+       
+       strcpy(sqlState,"");
+       strcpy(errorMsg,"");
+       nativeError = cbErrorMsg = 0;
+       for (i = 0; i < DB_MAX_ERROR_HISTORY; i++)
+               strcpy(errorList[i], "");
+
+       // Init typeInf structures
+       strcpy(typeInfVarchar.TypeName,"");
+       typeInfVarchar.FsqlType = 0;
+       typeInfVarchar.Precision = 0;
+       typeInfVarchar.CaseSensitive = 0;
+       typeInfVarchar.MaximumScale = 0;
+
+       strcpy(typeInfInteger.TypeName,"");
+       typeInfInteger.FsqlType = 0;
+       typeInfInteger.Precision = 0;
+       typeInfInteger.CaseSensitive = 0;
+       typeInfInteger.MaximumScale = 0;
+
+       strcpy(typeInfFloat.TypeName,"");
+       typeInfFloat.FsqlType = 0;
+       typeInfFloat.Precision = 0;
+       typeInfFloat.CaseSensitive = 0;
+       typeInfFloat.MaximumScale = 0;
+
+       strcpy(typeInfDate.TypeName,"");
+       typeInfDate.FsqlType = 0;
+       typeInfDate.Precision = 0;
+       typeInfDate.CaseSensitive = 0;
+       typeInfDate.MaximumScale = 0;
+       
+       // Error reporting is turned OFF by default
+       silent = TRUE;
+       
+       // Copy the HENV into the db class
+       henv = aHenv;
+
+       // Allocate a data source connection handle
+       if (SQLAllocConnect(henv, &hdbc) != SQL_SUCCESS)
+               DispAllErrors(henv);
+
+       // Initialize the db status flag
+       DB_STATUS = 0;
+
+       // Mark database as not open as of yet
+       dbIsOpen = FALSE;
+
+} // wxDB::wxDB()
+
+/********** wxDB::Open() **********/
+bool wxDB::Open(char *Dsn, char *Uid, char *AuthStr)
+{
+       assert(Dsn);
+       dsn             = Dsn;
+       uid             = Uid;
+       authStr = AuthStr;
+
+#ifndef FWD_ONLY_CURSORS
+
+       RETCODE retcode;
+
+       // Specify that the ODBC cursor library be used, if needed.  This must be
+       // specified before the connection is made.
+       retcode = SQLSetConnectOption(hdbc, SQL_ODBC_CURSORS, SQL_CUR_USE_IF_NEEDED);
+
+       #ifdef _CONSOLE
+               if (retcode == SQL_SUCCESS)
+                       cout << "SQLSetConnectOption(CURSOR_LIB) successful" << endl;
+               else
+                       cout << "SQLSetConnectOption(CURSOR_LIB) failed" << endl;
+       #endif
+
+#endif
+
+       // Connect to the data source
+       if (SQLConnect(hdbc, (UCHAR FAR *) Dsn,         SQL_NTS,
+                                                               (UCHAR FAR *) Uid,              SQL_NTS,
+                                                               (UCHAR FAR *) AuthStr,  SQL_NTS) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       // Mark database as open
+       dbIsOpen = TRUE;
+
+       // Allocate a statement handle for the database connection
+       if (SQLAllocStmt(hdbc, &hstmt) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       // Set Connection Options
+       if (! setConnectionOptions())
+               return(FALSE);
+
+       // Query the data source for inf. about itself
+       if (! getDbInfo())
+               return(FALSE);
+
+       // Query the data source regarding data type information
+
+       //
+       // The way I determined which SQL data types to use was by calling SQLGetInfo
+       // for all of the possible SQL data types to see which ones were supported.  If
+       // a type is not supported, the SQLFetch() that's called from getDataTypeInfo()
+       // fails with SQL_NO_DATA_FOUND.  This is ugly because I'm sure the three SQL data
+       // types I've selected below will not alway's be what we want.  These are just
+       // what happened to work against an Oracle 7/Intersolv combination.  The following is
+       // a complete list of the results I got back against the Oracle 7 database:
+       //
+       // SQL_BIGINT                                           SQL_NO_DATA_FOUND
+       // SQL_BINARY                                           SQL_NO_DATA_FOUND
+       // SQL_BIT                                                      SQL_NO_DATA_FOUND
+       // SQL_CHAR                                                     type name = 'CHAR', Precision = 255
+       // SQL_DATE                                                     SQL_NO_DATA_FOUND
+       // SQL_DECIMAL                                          type name = 'NUMBER', Precision = 38
+       // SQL_DOUBLE                                           type name = 'NUMBER', Precision = 15
+       // SQL_FLOAT                                            SQL_NO_DATA_FOUND
+       // SQL_INTEGER                                          SQL_NO_DATA_FOUND
+       // SQL_LONGVARBINARY                            type name = 'LONG RAW', Precision = 2 billion
+       // SQL_LONGVARCHAR                              type name = 'LONG', Precision = 2 billion
+       // SQL_NUMERIC                                          SQL_NO_DATA_FOUND
+       // SQL_REAL                                                     SQL_NO_DATA_FOUND
+       // SQL_SMALLINT                                 SQL_NO_DATA_FOUND
+       // SQL_TIME                                                     SQL_NO_DATA_FOUND
+       // SQL_TIMESTAMP                                        type name = 'DATE', Precision = 19
+       // SQL_VARBINARY                                        type name = 'RAW', Precision = 255
+       // SQL_VARCHAR                                          type name = 'VARCHAR2', Precision = 2000
+       // =====================================================================
+       // Results from a Microsoft Access 7.0 db, using a driver from Microsoft
+       //
+       // SQL_VARCHAR                                          type name = 'TEXT', Precision = 255
+       // SQL_TIMESTAMP                                        type name = 'DATETIME'
+       // SQL_DECIMAL                                          SQL_NO_DATA_FOUND
+       // SQL_NUMERIC                                          type name = 'CURRENCY', Precision = 19
+       // SQL_FLOAT                                            SQL_NO_DATA_FOUND
+       // SQL_REAL                                                     type name = 'SINGLE', Precision = 7
+       // SQL_DOUBLE                                           type name = 'DOUBLE', Precision = 15
+       // SQL_INTEGER                                          type name = 'LONG', Precision = 10
+
+       // VARCHAR = Variable length character string
+       if (! getDataTypeInfo(SQL_VARCHAR, typeInfVarchar))
+               if (! getDataTypeInfo(SQL_CHAR, typeInfVarchar))
+                       return(FALSE);
+               else
+                       typeInfVarchar.FsqlType = SQL_CHAR;
+       else
+               typeInfVarchar.FsqlType = SQL_VARCHAR;
+
+       // Float
+       if (! getDataTypeInfo(SQL_DOUBLE, typeInfFloat))
+               if (! getDataTypeInfo(SQL_REAL, typeInfFloat))
+                       if (! getDataTypeInfo(SQL_FLOAT, typeInfFloat))
+                               if (! getDataTypeInfo(SQL_DECIMAL, typeInfFloat))
+                                       if (! getDataTypeInfo(SQL_NUMERIC, typeInfFloat))
+                                               return(FALSE);
+                                       else
+                                               typeInfFloat.FsqlType = SQL_NUMERIC;
+                               else
+                                       typeInfFloat.FsqlType = SQL_DECIMAL;
+                       else
+                               typeInfFloat.FsqlType = SQL_FLOAT;
+               else
+                       typeInfFloat.FsqlType = SQL_REAL;
+       else
+               typeInfFloat.FsqlType = SQL_DOUBLE;
+
+       // Integer
+       if (! getDataTypeInfo(SQL_INTEGER, typeInfInteger))
+               // If SQL_INTEGER is not supported, use the floating point
+               // data type to store integers as well as floats
+               if (! getDataTypeInfo(typeInfFloat.FsqlType, typeInfInteger))
+                       return(FALSE);
+               else
+                       typeInfInteger.FsqlType = typeInfFloat.FsqlType;
+       else
+               typeInfInteger.FsqlType = SQL_INTEGER;
+
+       // Date/Time
+       if (! getDataTypeInfo(SQL_TIMESTAMP, typeInfDate))
+               return(FALSE);
+       else
+               typeInfDate.FsqlType = SQL_TIMESTAMP;
+
+#ifdef _CONSOLE
+       cout << "VARCHAR DATA TYPE: " << typeInfVarchar.TypeName << endl;
+       cout << "INTEGER DATA TYPE: " << typeInfInteger.TypeName << endl;
+       cout << "FLOAT   DATA TYPE: " << typeInfFloat.TypeName << endl;
+       cout << "DATE    DATA TYPE: " << typeInfDate.TypeName << endl;
+       cout << endl;
+#endif
+
+       // Completed Successfully
+       return(TRUE);
+
+} // wxDB::Open()
+
+// The Intersolv/Oracle 7 driver was "Not Capable" of setting the login timeout.
+
+/********** wxDB::setConnectionOptions() **********/
+bool wxDB::setConnectionOptions(void)
+{
+       SQLSetConnectOption(hdbc, SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF);
+       SQLSetConnectOption(hdbc, SQL_OPT_TRACE, SQL_OPT_TRACE_OFF);
+
+       // Display the connection options to verify them
+#ifdef _CONSOLE
+       long l;
+       cout << ">>>>> CONNECTION OPTIONS <<<<<<" << endl;
+       
+       if (SQLGetConnectOption(hdbc, SQL_AUTOCOMMIT, &l) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+       cout << "AUTOCOMMIT: " << (l == SQL_AUTOCOMMIT_OFF ? "OFF" : "ON") << endl;
+       
+       if (SQLGetConnectOption(hdbc, SQL_ODBC_CURSORS, &l) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+       cout << "ODBC CURSORS: ";
+       switch(l)
+               {
+               case(SQL_CUR_USE_IF_NEEDED):
+                       cout << "SQL_CUR_USE_IF_NEEDED";
+                       break;
+               case(SQL_CUR_USE_ODBC):
+                       cout << "SQL_CUR_USE_ODBC";
+                       break;
+               case(SQL_CUR_USE_DRIVER):
+                       cout << "SQL_CUR_USE_DRIVER";
+                       break;
+               }
+               cout << endl;
+       
+       if (SQLGetConnectOption(hdbc, SQL_OPT_TRACE, &l) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+       cout << "TRACING: " << (l == SQL_OPT_TRACE_OFF ? "OFF" : "ON") << endl;
+
+       cout << endl;
+#endif
+
+       // Completed Successfully
+       return(TRUE);
+
+} // wxDB::setConnectionOptions()
+
+/********** wxDB::getDbInfo() **********/
+bool wxDB::getDbInfo(void)
+{
+       SWORD cb;
+
+       if (SQLGetInfo(hdbc, SQL_SERVER_NAME, dbInf.serverName, 40, &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_DATABASE_NAME, dbInf.databaseName, 128, &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_DBMS_NAME, dbInf.dbmsName, 40, &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_DBMS_VER, dbInf.dbmsVer, 20, &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_ACTIVE_CONNECTIONS, &dbInf.maxConnections, sizeof(dbInf.maxConnections), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_ACTIVE_STATEMENTS, &dbInf.maxStmts, sizeof(dbInf.maxStmts), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_DRIVER_NAME, dbInf.driverName, 40, &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_DRIVER_ODBC_VER, dbInf.odbcVer, 20, &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_ODBC_VER, dbInf.drvMgrOdbcVer, 20, &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_DRIVER_VER, dbInf.driverVer, 40, &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_ODBC_API_CONFORMANCE, &dbInf.apiConfLvl, sizeof(dbInf.apiConfLvl), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_ODBC_SAG_CLI_CONFORMANCE, &dbInf.cliConfLvl, sizeof(dbInf.cliConfLvl), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_ODBC_SQL_CONFORMANCE, &dbInf.sqlConfLvl, sizeof(dbInf.sqlConfLvl), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_OUTER_JOINS, dbInf.outerJoins, 2, &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_PROCEDURES, dbInf.procedureSupport, 2, &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_CURSOR_COMMIT_BEHAVIOR, &dbInf.cursorCommitBehavior, sizeof(dbInf.cursorCommitBehavior), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_CURSOR_ROLLBACK_BEHAVIOR, &dbInf.cursorRollbackBehavior, sizeof(dbInf.cursorRollbackBehavior), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_NON_NULLABLE_COLUMNS, &dbInf.supportNotNullClause, sizeof(dbInf.supportNotNullClause), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_ODBC_SQL_OPT_IEF, dbInf.supportIEF, 2, &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_DEFAULT_TXN_ISOLATION, &dbInf.txnIsolation, sizeof(dbInf.txnIsolation), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_TXN_ISOLATION_OPTION, &dbInf.txnIsolationOptions, sizeof(dbInf.txnIsolationOptions), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_FETCH_DIRECTION, &dbInf.fetchDirections, sizeof(dbInf.fetchDirections), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_LOCK_TYPES, &dbInf.lockTypes, sizeof(dbInf.lockTypes), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_POS_OPERATIONS, &dbInf.posOperations, sizeof(dbInf.posOperations), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_POSITIONED_STATEMENTS, &dbInf.posStmts, sizeof(dbInf.posStmts), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_SCROLL_CONCURRENCY, &dbInf.scrollConcurrency, sizeof(dbInf.scrollConcurrency), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_SCROLL_OPTIONS, &dbInf.scrollOptions, sizeof(dbInf.scrollOptions), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_STATIC_SENSITIVITY, &dbInf.staticSensitivity, sizeof(dbInf.staticSensitivity), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_TXN_CAPABLE, &dbInf.txnCapable, sizeof(dbInf.txnCapable), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       if (SQLGetInfo(hdbc, SQL_LOGIN_TIMEOUT, &dbInf.loginTimeout, sizeof(dbInf.loginTimeout), &cb) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+#ifdef _CONSOLE
+       cout << ">>>>> DATA SOURCE INFORMATION <<<<<" << endl;
+       cout << "SERVER Name: " << dbInf.serverName << endl;
+       cout << "DBMS Name: " << dbInf.dbmsName << "; DBMS Version: " << dbInf.dbmsVer << endl;
+       cout << "ODBC Version: " << dbInf.odbcVer << "; Driver Version: " << dbInf.driverVer << endl;
+
+       cout << "API Conf. Level: ";
+       switch(dbInf.apiConfLvl)
+       {
+               case SQL_OAC_NONE:      cout << "None";         break;
+               case SQL_OAC_LEVEL1:    cout << "Level 1";      break;
+               case SQL_OAC_LEVEL2:    cout << "Level 2";      break;
+       }
+       cout << endl;
+
+       cout << "SAG CLI Conf. Level: ";
+       switch(dbInf.cliConfLvl)
+       {
+               case SQL_OSCC_NOT_COMPLIANT:    cout << "Not Compliant";        break;
+               case SQL_OSCC_COMPLIANT:                cout << "Compliant";                    break;
+       }
+       cout << endl;
+
+       cout << "SQL Conf. Level: ";
+       switch(dbInf.sqlConfLvl)
+       {
+               case SQL_OSC_MINIMUM:   cout << "Minimum Grammer";              break;
+               case SQL_OSC_CORE:              cout << "Core Grammer";                 break;
+               case SQL_OSC_EXTENDED:  cout << "Extended Grammer";     break;
+       }
+       cout << endl;
+
+       cout << "Max. Connections: " << dbInf.maxConnections << endl;
+       cout << "Outer Joins: " << dbInf.outerJoins << endl;
+       cout << "Support for Procedures: " << dbInf.procedureSupport << endl;
+
+       cout << "Cursor COMMIT Behavior: ";
+       switch(dbInf.cursorCommitBehavior)
+       {
+               case SQL_CB_DELETE:             cout << "Delete cursors";               break;
+               case SQL_CB_CLOSE:              cout << "Close cursors";                break;
+               case SQL_CB_PRESERVE:   cout << "Preserve cursors";     break;
+       }
+       cout << endl;
+
+       cout << "Cursor ROLLBACK Behavior: ";
+       switch(dbInf.cursorRollbackBehavior)
+       {
+               case SQL_CB_DELETE:             cout << "Delete cursors";               break;
+               case SQL_CB_CLOSE:              cout << "Close cursors";                break;
+               case SQL_CB_PRESERVE:   cout << "Preserve cursors";     break;
+       }
+       cout << endl;
+
+       cout << "Support NOT NULL clause: ";
+       switch(dbInf.supportNotNullClause)
+       {
+               case SQL_NNC_NULL:              cout << "No";           break;
+               case SQL_NNC_NON_NULL:  cout << "Yes";          break;
+       }
+       cout << endl;
+
+       cout << "Support IEF (Ref. Integrity): " << dbInf.supportIEF << endl;
+       cout << "Login Timeout: " << dbInf.loginTimeout << endl;
+
+       cout << endl << endl << "more ..." << endl;
+       getchar();
+
+       cout << "Default Transaction Isolation: ";
+       switch(dbInf.txnIsolation)
+       {
+               case SQL_TXN_READ_UNCOMMITTED:  cout << "Read Uncommitted";     break;
+               case SQL_TXN_READ_COMMITTED:            cout << "Read Committed";               break;
+               case SQL_TXN_REPEATABLE_READ:           cout << "Repeatable Read";              break;
+               case SQL_TXN_SERIALIZABLE:                      cout << "Serializable";                 break;
+#ifdef ODBC_V20
+               case SQL_TXN_VERSIONING:                        cout << "Versioning";                   break;
+#endif
+       }
+       cout << endl;
+
+       cout << "Transaction Isolation Options: ";
+       if (dbInf.txnIsolationOptions & SQL_TXN_READ_UNCOMMITTED)
+               cout << "Read Uncommitted, ";
+       if (dbInf.txnIsolationOptions & SQL_TXN_READ_COMMITTED)
+               cout << "Read Committed, ";
+       if (dbInf.txnIsolationOptions & SQL_TXN_REPEATABLE_READ)
+               cout << "Repeatable Read, ";
+       if (dbInf.txnIsolationOptions & SQL_TXN_SERIALIZABLE)
+               cout << "Serializable, ";
+#ifdef ODBC_V20
+       if (dbInf.txnIsolationOptions & SQL_TXN_VERSIONING)
+               cout << "Versioning";
+#endif
+       cout << endl;
+
+       cout << "Fetch Directions Supported:" << endl << "   ";
+       if (dbInf.fetchDirections & SQL_FD_FETCH_NEXT)
+               cout << "Next, ";
+       if (dbInf.fetchDirections & SQL_FD_FETCH_PRIOR)
+               cout << "Prev, ";
+       if (dbInf.fetchDirections & SQL_FD_FETCH_FIRST)
+               cout << "First, ";
+       if (dbInf.fetchDirections & SQL_FD_FETCH_LAST)
+               cout << "Last, ";
+       if (dbInf.fetchDirections & SQL_FD_FETCH_ABSOLUTE)
+               cout << "Absolute, ";
+       if (dbInf.fetchDirections & SQL_FD_FETCH_RELATIVE)
+               cout << "Relative, ";
+#ifdef ODBC_V20
+       if (dbInf.fetchDirections & SQL_FD_FETCH_RESUME)
+               cout << "Resume, ";
+#endif
+       if (dbInf.fetchDirections & SQL_FD_FETCH_BOOKMARK)
+               cout << "Bookmark";
+       cout << endl;
+
+       cout << "Lock Types Supported (SQLSetPos): ";
+       if (dbInf.lockTypes & SQL_LCK_NO_CHANGE)
+               cout << "No Change, ";
+       if (dbInf.lockTypes & SQL_LCK_EXCLUSIVE)
+               cout << "Exclusive, ";
+       if (dbInf.lockTypes & SQL_LCK_UNLOCK)
+               cout << "UnLock";
+       cout << endl;
+
+       cout << "Position Operations Supported (SQLSetPos): ";
+       if (dbInf.posOperations & SQL_POS_POSITION)
+               cout << "Position, ";
+       if (dbInf.posOperations & SQL_POS_REFRESH)
+               cout << "Refresh, ";
+       if (dbInf.posOperations & SQL_POS_UPDATE)
+               cout << "Upd, ";
+       if (dbInf.posOperations & SQL_POS_DELETE)
+               cout << "Del, ";
+       if (dbInf.posOperations & SQL_POS_ADD)
+               cout << "Add";
+       cout << endl;
+
+       cout << "Positioned Statements Supported: ";
+       if (dbInf.posStmts & SQL_PS_POSITIONED_DELETE)
+               cout << "Pos delete, ";
+       if (dbInf.posStmts & SQL_PS_POSITIONED_UPDATE)
+               cout << "Pos update, ";
+       if (dbInf.posStmts & SQL_PS_SELECT_FOR_UPDATE)
+               cout << "Select for update";
+       cout << endl;
+
+       cout << "Scroll Concurrency: ";
+       if (dbInf.scrollConcurrency & SQL_SCCO_READ_ONLY)
+               cout << "Read Only, ";
+       if (dbInf.scrollConcurrency & SQL_SCCO_LOCK)
+               cout << "Lock, ";
+       if (dbInf.scrollConcurrency & SQL_SCCO_OPT_ROWVER)
+               cout << "Opt. Rowver, ";
+       if (dbInf.scrollConcurrency & SQL_SCCO_OPT_VALUES)
+               cout << "Opt. Values";
+       cout << endl;
+
+       cout << "Scroll Options: ";
+       if (dbInf.scrollOptions & SQL_SO_FORWARD_ONLY)
+               cout << "Fwd Only, ";
+       if (dbInf.scrollOptions & SQL_SO_STATIC)
+               cout << "Static, ";
+       if (dbInf.scrollOptions & SQL_SO_KEYSET_DRIVEN)
+               cout << "Keyset Driven, ";
+       if (dbInf.scrollOptions & SQL_SO_DYNAMIC)
+               cout << "Dynamic, ";
+       if (dbInf.scrollOptions & SQL_SO_MIXED)
+               cout << "Mixed";
+       cout << endl;
+
+       cout << "Static Sensitivity: ";
+       if (dbInf.staticSensitivity & SQL_SS_ADDITIONS)
+               cout << "Additions, ";
+       if (dbInf.staticSensitivity & SQL_SS_DELETIONS)
+               cout << "Deletions, ";
+       if (dbInf.staticSensitivity & SQL_SS_UPDATES)
+               cout << "Updates";
+       cout << endl;
+
+       cout << "Transaction Capable?: ";
+       switch(dbInf.txnCapable)
+       {
+               case SQL_TC_NONE:                       cout << "No";                           break;
+               case SQL_TC_DML:                        cout << "DML Only";             break;
+               case SQL_TC_DDL_COMMIT: cout << "DDL Commit";   break;
+               case SQL_TC_DDL_IGNORE: cout << "DDL Ignore";   break;
+               case SQL_TC_ALL:                        cout << "DDL & DML";            break;
+       }
+       cout << endl;
+
+       cout << endl;
+
+#endif
+
+       // Completed Successfully
+       return(TRUE);
+
+} // wxDB::getDbInfo()
+
+/********** wxDB::getDataTypeInfo() **********/
+bool wxDB::getDataTypeInfo(SWORD fSqlType, SqlTypeInfo &structSQLTypeInfo)
+{
+       // fSqlType will be something like SQL_VARCHAR.  This parameter determines
+       // the data type inf. is gathered for.
+       //
+       // SqlTypeInfo is a structure that is filled in with data type information,
+
+       RETCODE retcode;
+       SDWORD cbRet;
+
+       // Get information about the data type specified
+       if (SQLGetTypeInfo(hstmt, fSqlType) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc, hstmt));
+       // Fetch the record
+       if ((retcode = SQLFetch(hstmt)) != SQL_SUCCESS)
+       {
+#ifdef _CONSOLE
+               if (retcode == SQL_NO_DATA_FOUND)
+                       cout << "SQL_NO_DATA_FOUND fetching inf. about data type." << endl;
+#endif
+               DispAllErrors(henv, hdbc, hstmt);
+               SQLFreeStmt(hstmt, SQL_CLOSE);
+               return(FALSE);
+       }
+       // Obtain columns from the record
+       if (SQLGetData(hstmt, 1, SQL_C_CHAR, structSQLTypeInfo.TypeName, DB_TYPE_NAME_LEN, &cbRet) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc, hstmt));
+       if (SQLGetData(hstmt, 3, SQL_C_LONG, &structSQLTypeInfo.Precision, 0, &cbRet) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc, hstmt));
+       if (SQLGetData(hstmt, 8, SQL_C_SHORT, &structSQLTypeInfo.CaseSensitive, 0, &cbRet) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc, hstmt));
+//     if (SQLGetData(hstmt, 14, SQL_C_SHORT, &structSQLTypeInfo.MinimumScale, 0, &cbRet) != SQL_SUCCESS)
+//             return(DispAllErrors(henv, hdbc, hstmt));
+       if (SQLGetData(hstmt, 15, SQL_C_SHORT, &structSQLTypeInfo.MaximumScale, 0, &cbRet) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc, hstmt));
+
+       if (structSQLTypeInfo.MaximumScale < 0)
+               structSQLTypeInfo.MaximumScale = 0;
+
+       // Close the statement handle which closes open cursors
+       if (SQLFreeStmt(hstmt, SQL_CLOSE) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc, hstmt));
+
+       // Completed Successfully
+       return(TRUE);
+
+} // wxDB::getDataTypeInfo()
+
+/********** wxDB::Close() **********/
+void wxDB::Close(void)
+{
+       // Free statement handle
+       if (dbIsOpen)
+       {
+               if (SQLFreeStmt(hstmt, SQL_DROP) != SQL_SUCCESS)
+                       DispAllErrors(henv, hdbc);
+       }
+
+       // Disconnect from the datasource
+       if (SQLDisconnect(hdbc) != SQL_SUCCESS)
+               DispAllErrors(henv, hdbc);
+
+       // Free the connection to the datasource
+       if (SQLFreeConnect(hdbc) != SQL_SUCCESS)
+               DispAllErrors(henv, hdbc);
+
+} // wxDB::Close()
+
+/********** wxDB::CommitTrans() **********/
+bool wxDB::CommitTrans(void)
+{
+       // Commit the transaction
+       if (SQLTransact(henv, hdbc, SQL_COMMIT) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       // Completed successfully
+       return(TRUE);
+
+} // wxDB::CommitTrans()
+
+/********** wxDB::RollbackTrans() **********/
+bool wxDB::RollbackTrans(void)
+{
+       // Rollback the transaction
+       if (SQLTransact(henv, hdbc, SQL_ROLLBACK) != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc));
+
+       // Completed successfully
+       return(TRUE);
+
+} // wxDB::RollbackTrans()
+
+/********** wxDB::DispAllErrors() **********/
+bool wxDB::DispAllErrors(HENV aHenv, HDBC aHdbc, HSTMT aHstmt)
+{
+       char odbcErrMsg[DB_MAX_ERROR_MSG_LEN];
+
+       while (SQLError(aHenv, aHdbc, aHstmt, (UCHAR FAR *) sqlState, &nativeError, (UCHAR FAR *) errorMsg, SQL_MAX_MESSAGE_LENGTH - 1, &cbErrorMsg) == SQL_SUCCESS)
+       {
+               sprintf(odbcErrMsg, "SQL State = %s\nNative Error Code = %li\nError Message = %s\n", sqlState, nativeError, errorMsg);
+               logError(odbcErrMsg, sqlState);
+               if (!silent)
+               {
+#ifdef _CONSOLE
+                       // When run in console mode, use standard out to display errors.
+                       cout << odbcErrMsg << endl;
+                       cout << "Press any key to continue..." << endl;
+                       getchar();
+#endif
+               }
+       }
+
+       return(FALSE);  // This function alway's returns false.
+
+} // wxDB::DispAllErrors()
+
+/********** wxDB::GetNextError() **********/
+bool wxDB::GetNextError(HENV aHenv, HDBC aHdbc, HSTMT aHstmt)
+{
+       if (SQLError(aHenv, aHdbc, aHstmt, (UCHAR FAR *) sqlState, &nativeError, (UCHAR FAR *) errorMsg, SQL_MAX_MESSAGE_LENGTH - 1, &cbErrorMsg) == SQL_SUCCESS)
+               return(TRUE);
+       else
+               return(FALSE);
+
+} // wxDB::GetNextError()
+
+/********** wxDB::DispNextError() **********/
+void wxDB::DispNextError(void)
+{
+       char odbcErrMsg[DB_MAX_ERROR_MSG_LEN];
+
+       sprintf(odbcErrMsg, "SQL State = %s\nNative Error Code = %li\nError Message = %s\n", sqlState, nativeError, errorMsg);
+       logError(odbcErrMsg, sqlState);
+
+       if (silent)
+               return;
+
+#ifdef _CONSOLE
+       // When run in console mode, use standard out to display errors.
+       cout << odbcErrMsg << endl;
+       cout << "Press any key to continue..."  << endl;
+       getchar();
+#endif
+
+} // wxDB::DispNextError()
+
+/********** wxDB::logError() **********/
+void wxDB::logError(char *errMsg, char *SQLState)
+{
+       assert(errMsg && strlen(errMsg));
+
+       static int pLast = -1;
+       int dbStatus;
+
+       if (++pLast == DB_MAX_ERROR_HISTORY)
+       {
+               for (int i = 0; i < DB_MAX_ERROR_HISTORY; i++)
+                       strcpy(errorList[i], errorList[i+1]);
+               pLast--;
+       }
+
+       strcpy(errorList[pLast], errMsg);
+
+       if (SQLState && strlen(SQLState))
+               if ((dbStatus = TranslateSqlState(SQLState)) != DB_ERR_FUNCTION_SEQUENCE_ERROR)
+                       DB_STATUS = dbStatus;
+
+}  // wxDB::logError()
+
+/**********wxDB::TranslateSqlState()  **********/
+int wxDB::TranslateSqlState(char *SQLState)
+{
+       if (!strcmp(SQLState, "01000"))
+               return(DB_ERR_GENERAL_WARNING);
+       if (!strcmp(SQLState, "01002"))
+               return(DB_ERR_DISCONNECT_ERROR);
+       if (!strcmp(SQLState, "01004"))
+               return(DB_ERR_DATA_TRUNCATED);
+       if (!strcmp(SQLState, "01006"))
+               return(DB_ERR_PRIV_NOT_REVOKED);
+       if (!strcmp(SQLState, "01S00"))
+               return(DB_ERR_INVALID_CONN_STR_ATTR);
+       if (!strcmp(SQLState, "01S01"))
+               return(DB_ERR_ERROR_IN_ROW);
+       if (!strcmp(SQLState, "01S02"))
+               return(DB_ERR_OPTION_VALUE_CHANGED);
+       if (!strcmp(SQLState, "01S03"))
+               return(DB_ERR_NO_ROWS_UPD_OR_DEL);
+       if (!strcmp(SQLState, "01S04"))
+               return(DB_ERR_MULTI_ROWS_UPD_OR_DEL);
+       if (!strcmp(SQLState, "07001"))
+               return(DB_ERR_WRONG_NO_OF_PARAMS);
+       if (!strcmp(SQLState, "07006"))
+               return(DB_ERR_DATA_TYPE_ATTR_VIOL);
+       if (!strcmp(SQLState, "08001"))
+               return(DB_ERR_UNABLE_TO_CONNECT);
+       if (!strcmp(SQLState, "08002"))
+               return(DB_ERR_CONNECTION_IN_USE);
+       if (!strcmp(SQLState, "08003"))
+               return(DB_ERR_CONNECTION_NOT_OPEN);
+       if (!strcmp(SQLState, "08004"))
+               return(DB_ERR_REJECTED_CONNECTION);
+       if (!strcmp(SQLState, "08007"))
+               return(DB_ERR_CONN_FAIL_IN_TRANS);
+       if (!strcmp(SQLState, "08S01"))
+               return(DB_ERR_COMM_LINK_FAILURE);
+       if (!strcmp(SQLState, "21S01"))
+               return(DB_ERR_INSERT_VALUE_LIST_MISMATCH);
+       if (!strcmp(SQLState, "21S02"))
+               return(DB_ERR_DERIVED_TABLE_MISMATCH);
+       if (!strcmp(SQLState, "22001"))
+               return(DB_ERR_STRING_RIGHT_TRUNC);
+       if (!strcmp(SQLState, "22003"))
+               return(DB_ERR_NUMERIC_VALUE_OUT_OF_RNG);
+       if (!strcmp(SQLState, "22005"))
+               return(DB_ERR_ERROR_IN_ASSIGNMENT);
+       if (!strcmp(SQLState, "22008"))
+               return(DB_ERR_DATETIME_FLD_OVERFLOW);
+       if (!strcmp(SQLState, "22012"))
+               return(DB_ERR_DIVIDE_BY_ZERO);
+       if (!strcmp(SQLState, "22026"))
+               return(DB_ERR_STR_DATA_LENGTH_MISMATCH);
+       if (!strcmp(SQLState, "23000"))
+               return(DB_ERR_INTEGRITY_CONSTRAINT_VIOL);
+       if (!strcmp(SQLState, "24000"))
+               return(DB_ERR_INVALID_CURSOR_STATE);
+       if (!strcmp(SQLState, "25000"))
+               return(DB_ERR_INVALID_TRANS_STATE);
+       if (!strcmp(SQLState, "28000"))
+               return(DB_ERR_INVALID_AUTH_SPEC);
+       if (!strcmp(SQLState, "34000"))
+               return(DB_ERR_INVALID_CURSOR_NAME);
+       if (!strcmp(SQLState, "37000"))
+               return(DB_ERR_SYNTAX_ERROR_OR_ACCESS_VIOL);
+       if (!strcmp(SQLState, "3C000"))
+               return(DB_ERR_DUPLICATE_CURSOR_NAME);
+       if (!strcmp(SQLState, "40001"))
+               return(DB_ERR_SERIALIZATION_FAILURE);
+       if (!strcmp(SQLState, "42000"))
+               return(DB_ERR_SYNTAX_ERROR_OR_ACCESS_VIOL2);
+       if (!strcmp(SQLState, "70100"))
+               return(DB_ERR_OPERATION_ABORTED);
+       if (!strcmp(SQLState, "IM001"))
+               return(DB_ERR_UNSUPPORTED_FUNCTION);
+       if (!strcmp(SQLState, "IM002"))
+               return(DB_ERR_NO_DATA_SOURCE);
+       if (!strcmp(SQLState, "IM003"))
+               return(DB_ERR_DRIVER_LOAD_ERROR);
+       if (!strcmp(SQLState, "IM004"))
+               return(DB_ERR_SQLALLOCENV_FAILED);
+       if (!strcmp(SQLState, "IM005"))
+               return(DB_ERR_SQLALLOCCONNECT_FAILED);
+       if (!strcmp(SQLState, "IM006"))
+               return(DB_ERR_SQLSETCONNECTOPTION_FAILED);
+       if (!strcmp(SQLState, "IM007"))
+               return(DB_ERR_NO_DATA_SOURCE_DLG_PROHIB);
+       if (!strcmp(SQLState, "IM008"))
+               return(DB_ERR_DIALOG_FAILED);
+       if (!strcmp(SQLState, "IM009"))
+               return(DB_ERR_UNABLE_TO_LOAD_TRANSLATION_DLL);
+       if (!strcmp(SQLState, "IM010"))
+               return(DB_ERR_DATA_SOURCE_NAME_TOO_LONG);
+       if (!strcmp(SQLState, "IM011"))
+               return(DB_ERR_DRIVER_NAME_TOO_LONG);
+       if (!strcmp(SQLState, "IM012"))
+               return(DB_ERR_DRIVER_KEYWORD_SYNTAX_ERROR);
+       if (!strcmp(SQLState, "IM013"))
+               return(DB_ERR_TRACE_FILE_ERROR);
+       if (!strcmp(SQLState, "S0001"))
+               return(DB_ERR_TABLE_OR_VIEW_ALREADY_EXISTS);
+       if (!strcmp(SQLState, "S0002"))
+               return(DB_ERR_TABLE_NOT_FOUND);
+       if (!strcmp(SQLState, "S0011"))
+               return(DB_ERR_INDEX_ALREADY_EXISTS);
+       if (!strcmp(SQLState, "S0012"))
+               return(DB_ERR_INDEX_NOT_FOUND);
+       if (!strcmp(SQLState, "S0021"))
+               return(DB_ERR_COLUMN_ALREADY_EXISTS);
+       if (!strcmp(SQLState, "S0022"))
+               return(DB_ERR_COLUMN_NOT_FOUND);
+       if (!strcmp(SQLState, "S0023"))
+               return(DB_ERR_NO_DEFAULT_FOR_COLUMN);
+       if (!strcmp(SQLState, "S1000"))
+               return(DB_ERR_GENERAL_ERROR);
+       if (!strcmp(SQLState, "S1001"))
+               return(DB_ERR_MEMORY_ALLOCATION_FAILURE);
+       if (!strcmp(SQLState, "S1002"))
+               return(DB_ERR_INVALID_COLUMN_NUMBER);
+       if (!strcmp(SQLState, "S1003"))
+               return(DB_ERR_PROGRAM_TYPE_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1004"))
+               return(DB_ERR_SQL_DATA_TYPE_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1008"))
+               return(DB_ERR_OPERATION_CANCELLED);
+       if (!strcmp(SQLState, "S1009"))
+               return(DB_ERR_INVALID_ARGUMENT_VALUE);
+       if (!strcmp(SQLState, "S1010"))
+               return(DB_ERR_FUNCTION_SEQUENCE_ERROR);
+       if (!strcmp(SQLState, "S1011"))
+               return(DB_ERR_OPERATION_INVALID_AT_THIS_TIME);
+       if (!strcmp(SQLState, "S1012"))
+               return(DB_ERR_INVALID_TRANS_OPERATION_CODE);
+       if (!strcmp(SQLState, "S1015"))
+               return(DB_ERR_NO_CURSOR_NAME_AVAIL);
+       if (!strcmp(SQLState, "S1090"))
+               return(DB_ERR_INVALID_STR_OR_BUF_LEN);
+       if (!strcmp(SQLState, "S1091"))
+               return(DB_ERR_DESCRIPTOR_TYPE_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1092"))
+               return(DB_ERR_OPTION_TYPE_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1093"))
+               return(DB_ERR_INVALID_PARAM_NO);
+       if (!strcmp(SQLState, "S1094"))
+               return(DB_ERR_INVALID_SCALE_VALUE);
+       if (!strcmp(SQLState, "S1095"))
+               return(DB_ERR_FUNCTION_TYPE_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1096"))
+               return(DB_ERR_INF_TYPE_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1097"))
+               return(DB_ERR_COLUMN_TYPE_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1098"))
+               return(DB_ERR_SCOPE_TYPE_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1099"))
+               return(DB_ERR_NULLABLE_TYPE_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1100"))
+               return(DB_ERR_UNIQUENESS_OPTION_TYPE_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1101"))
+               return(DB_ERR_ACCURACY_OPTION_TYPE_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1103"))
+               return(DB_ERR_DIRECTION_OPTION_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1104"))
+               return(DB_ERR_INVALID_PRECISION_VALUE);
+       if (!strcmp(SQLState, "S1105"))
+               return(DB_ERR_INVALID_PARAM_TYPE);
+       if (!strcmp(SQLState, "S1106"))
+               return(DB_ERR_FETCH_TYPE_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1107"))
+               return(DB_ERR_ROW_VALUE_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1108"))
+               return(DB_ERR_CONCURRENCY_OPTION_OUT_OF_RANGE);
+       if (!strcmp(SQLState, "S1109"))
+               return(DB_ERR_INVALID_CURSOR_POSITION);
+       if (!strcmp(SQLState, "S1110"))
+               return(DB_ERR_INVALID_DRIVER_COMPLETION);
+       if (!strcmp(SQLState, "S1111"))
+               return(DB_ERR_INVALID_BOOKMARK_VALUE);
+       if (!strcmp(SQLState, "S1C00"))
+               return(DB_ERR_DRIVER_NOT_CAPABLE);
+       if (!strcmp(SQLState, "S1T00"))
+               return(DB_ERR_TIMEOUT_EXPIRED);
+
+       // No match
+       return(0);
+
+}  // wxDB::TranslateSqlState()
+       
+/**********  wxDB::Grant() **********/
+bool wxDB::Grant(int privileges, char *tableName, char *userList)
+{
+       char sqlStmt[DB_MAX_STATEMENT_LEN];
+
+       // Build the grant statement
+       strcpy(sqlStmt, "GRANT ");
+       if (privileges == DB_GRANT_ALL)
+               strcat(sqlStmt, "ALL");
+       else
+       {
+               int c = 0;
+               if (privileges & DB_GRANT_SELECT)
+               {
+                       strcat(sqlStmt, "SELECT");
+                       c++;
+               }
+               if (privileges & DB_GRANT_INSERT)
+               {
+                       if (c++)
+                               strcat(sqlStmt, ", ");
+                       strcat(sqlStmt, "INSERT");
+               }
+               if (privileges & DB_GRANT_UPDATE)
+               {
+                       if (c++)
+                               strcat(sqlStmt, ", ");
+                       strcat(sqlStmt, "UPDATE");
+               }
+               if (privileges & DB_GRANT_DELETE)
+               {
+                       if (c++)
+                               strcat(sqlStmt, ", ");
+                       strcat(sqlStmt, "DELETE");
+               }
+       }
+
+       strcat(sqlStmt, " ON ");
+       strcat(sqlStmt, tableName);
+       strcat(sqlStmt, " TO ");
+       strcat(sqlStmt, userList);
+
+#ifdef _CONSOLE
+       cout << endl << sqlStmt << endl;
+#endif
+
+       return(ExecSql(sqlStmt));
+
+}  // wxDB::Grant()
+
+/********** wxDB::CreateView() **********/
+bool wxDB::CreateView(char *viewName, char *colList, char *pSqlStmt)
+{
+       char sqlStmt[DB_MAX_STATEMENT_LEN];
+
+       // Drop the view first
+       sprintf(sqlStmt, "DROP VIEW %s", viewName);
+       if (SQLExecDirect(hstmt, (UCHAR FAR *) sqlStmt, SQL_NTS) != SQL_SUCCESS)
+       {
+               // Check for sqlState = S0002, "Table or view not found".
+               // Ignore this error, bomb out on any other error.
+               // SQL Sybase Anwhere v5.5 returns an access violation error here
+               // (sqlstate = 42000) rather than an S0002.
+               GetNextError(henv, hdbc, hstmt);
+               if (strcmp(sqlState, "S0002") && strcmp(sqlState, "42000"))
+               {
+                       DispNextError();
+                       DispAllErrors(henv, hdbc, hstmt);
+                       RollbackTrans();
+                       return(FALSE);
+               }
+       }
+
+#ifdef _CONSOLE
+       cout << endl << sqlStmt << endl;
+#endif
+
+       // Build the create view statement
+       strcpy(sqlStmt, "CREATE VIEW ");
+       strcat(sqlStmt, viewName);
+       
+       if (strlen(colList))
+       {
+               strcat(sqlStmt, " (");
+               strcat(sqlStmt, colList);
+               strcat(sqlStmt, ")");
+       }
+
+       strcat(sqlStmt, " AS ");
+       strcat(sqlStmt, pSqlStmt);
+
+#ifdef _CONSOLE
+       cout << sqlStmt << endl;
+#endif
+
+       return(ExecSql(sqlStmt));
+
+}  // wxDB::CreateView()
+
+/********** wxDB::ExecSql()  **********/
+bool wxDB::ExecSql(char *pSqlStmt)
+{
+       if (SQLExecDirect(hstmt, (UCHAR FAR *) pSqlStmt, SQL_NTS) == SQL_SUCCESS)
+               return(TRUE);
+       else
+       {
+               DispAllErrors(henv, hdbc, hstmt);
+               return(FALSE);
+       }
+
+}  // wxDB::ExecSql()
+
+/********** wxDB::GetColumns() **********/
+/*
+ *             1) The last array element of the tableName[] argument must be zero (null).
+ *                     This is how the end of the array is detected.
+ *             2) This function returns an array of CcolInf structures.  If no columns
+ *                     were found, or an error occured, this pointer will be zero (null).  THE
+ *                     CALLING FUNCTION IS RESPONSIBLE FOR DELETING THE MEMORY RETURNED WHEN IT
+ *                     IS FINISHED WITH IT.  i.e.
+ *
+ *                     CcolInf *colInf = pDb->GetColumns(tableList);
+ *                     if (colInf)
+ *                     {
+ *                             // Use the column inf
+ *                             .......
+ *                             // Destroy the memory
+ *                             delete [] colInf;
+ *                     }
+ */
+CcolInf *wxDB::GetColumns(char *tableName[])
+{
+       UINT noCols = 0;
+       UINT colNo = 0;
+       CcolInf *colInf = 0;
+       RETCODE retcode;
+       SDWORD cb;
+       char tblName[DB_MAX_TABLE_NAME_LEN+1];
+       char colName[DB_MAX_COLUMN_NAME_LEN+1];
+       SWORD sqlDataType;
+
+       // Pass 1 - Determine how many columns there are.
+       // Pass 2 - Allocate the CcolInf array and fill in
+       //                              the array with the column information.
+       for (int pass = 1; pass <= 2; pass++)
+       {
+               if (pass == 2)
+               {
+                       if (noCols == 0)  // Probably a bogus table name(s)
+                               break;
+                       // Allocate n CcolInf objects to hold the column information
+                       colInf = new CcolInf[noCols+1];
+                       if (!colInf)
+                               break;
+                       // Mark the end of the array
+                       strcpy(colInf[noCols].tableName, "");
+                       strcpy(colInf[noCols].colName, "");
+                       colInf[noCols].sqlDataType = 0;
+               }
+               // Loop through each table name
+               for (int tbl = 0; tableName[tbl]; tbl++)
+               {
+                       SQLFreeStmt(hstmt, SQL_CLOSE);
+                       retcode = SQLColumns(hstmt,
+                                                                               NULL, 0,                                                                                        // All qualifiers
+                                                                               NULL, 0,                                                                                        // All owners
+                                                                               (UCHAR *) tableName[tbl], SQL_NTS,
+                                                                               NULL, 0);                                                                               // All columns
+                       if (retcode != SQL_SUCCESS)
+                       {  // Error occured, abort
+                               DispAllErrors(henv, hdbc, hstmt);
+                               if (colInf)
+                                       delete [] colInf;
+                               return(0);
+                       }
+                       SQLBindCol(hstmt, 3, SQL_C_CHAR,   tblName,      DB_MAX_TABLE_NAME_LEN+1,  &cb);
+                       SQLBindCol(hstmt, 4, SQL_C_CHAR,   colName,      DB_MAX_COLUMN_NAME_LEN+1, &cb);
+                       SQLBindCol(hstmt, 5, SQL_C_SSHORT, &sqlDataType, 0,                        &cb);
+                       while ((retcode = SQLFetch(hstmt)) == SQL_SUCCESS)
+                       {
+                               if (pass == 1)  // First pass, just add up the number of columns
+                                       noCols++;
+                               else  // Pass 2; Fill in the array of structures
+                               {
+                                       if (colNo < noCols)  // Some extra error checking to prevent memory overwrites
+                                       {
+                                               strcpy(colInf[colNo].tableName, tblName);
+                                               strcpy(colInf[colNo].colName, colName);
+                                               colInf[colNo].sqlDataType = sqlDataType;
+                                               colNo++;
+                                       }
+                               }
+                       }
+                       if (retcode != SQL_NO_DATA_FOUND)
+                       {  // Error occured, abort
+                               DispAllErrors(henv, hdbc, hstmt);
+                               if (colInf)
+                                       delete [] colInf;
+                               return(0);
+                       }
+               }
+       }
+
+       SQLFreeStmt(hstmt, SQL_CLOSE);
+       return colInf;
+
+}  // wxDB::GetColumns()
+
+
+// Table name can refer to a table, view, alias or synonym.  Returns true
+// if the object exists in the database.  This function does not indicate
+// whether or not the user has privleges to query or perform other functions
+// on the table.
+bool wxDB::TableExists(char *tableName)
+{
+       assert(tableName && strlen(tableName));
+
+       SQLFreeStmt(hstmt, SQL_CLOSE);
+       RETCODE retcode = SQLTables(hstmt,
+                                                                                NULL, 0,                                                                               // All qualifiers
+                                                                                NULL, 0,                                                                               // All owners
+                                                                                (UCHAR FAR *)tableName, SQL_NTS,
+                                                                                NULL, 0);                                                                              // All table types
+       if (retcode != SQL_SUCCESS)
+               return(DispAllErrors(henv, hdbc, hstmt));
+
+       if (SQLFetch(hstmt) != SQL_SUCCESS)
+       {
+               SQLFreeStmt(hstmt, SQL_CLOSE);
+               return(DispAllErrors(henv, hdbc, hstmt));
+       }
+
+       SQLFreeStmt(hstmt, SQL_CLOSE);
+       return(TRUE);
+
+}  // wxDB::TableExists()
+
+
+/********** GetDbConnection() **********/
+wxDB *GetDbConnection(DbStuff *pDbStuff)
+{
+       DbList *pList;
+
+       // Scan the linked list searching for an available database connection
+       // that's already been opened but is currently not in use.
+       for (pList = PtrBegDbList; pList; pList = pList->PtrNext)
+       {
+               // The database connection must be for the same datasource
+               // name and must currently not be in use.
+               if (pList->Free && (! strcmp(pDbStuff->Dsn, pList->Dsn)))  // Found a free connection
+               {
+                       pList->Free = FALSE;
+                       return(pList->PtrDb);
+               }
+       }
+
+       // No available connections.  A new connection must be made and
+       // appended to the end of the linked list.
+       if (PtrBegDbList)
+       {
+               // Find the end of the list
+               for (pList = PtrBegDbList; pList->PtrNext; pList = pList->PtrNext);
+               // Append a new list item
+               pList->PtrNext = new DbList;
+               pList->PtrNext->PtrPrev = pList;
+               pList = pList->PtrNext;
+       }
+       else    // Empty list
+       {
+               // Create the first node on the list
+               pList = PtrBegDbList = new DbList;
+               pList->PtrPrev = 0;
+       }
+
+       // Initialize new node in the linked list
+       pList->PtrNext = 0;
+       pList->Free = FALSE;
+       strcpy(pList->Dsn, pDbStuff->Dsn);
+       pList->PtrDb = new wxDB(pDbStuff->Henv);
+
+       // Connect to the datasource
+       if (pList->PtrDb->Open(pDbStuff->Dsn, pDbStuff->Uid, pDbStuff->AuthStr))
+               return(pList->PtrDb);
+       else  // Unable to connect, destroy list item
+       {
+               if (pList->PtrPrev)
+                       pList->PtrPrev->PtrNext = 0;
+               else
+                       PtrBegDbList = 0;                               // Empty list again
+               pList->PtrDb->CommitTrans();    // Commit any open transactions on wxDB object
+               pList->PtrDb->Close();                  // Close the wxDB object
+               delete pList->PtrDb;                            // Deletes the wxDB object
+               delete pList;                                           // Deletes the linked list object
+               return(0);
+       }
+
+}  // GetDbConnection()
+
+/********** FreeDbConnection() **********/
+bool FreeDbConnection(wxDB *pDb)
+{
+       DbList *pList;
+
+       // Scan the linked list searching for the database connection
+       for (pList = PtrBegDbList; pList; pList = pList->PtrNext)
+       {
+               if (pList->PtrDb == pDb)                // Found it!!!
+                       return(pList->Free = TRUE);
+       }
+
+       // Never found the database object, return failure
+       return(FALSE);
+
+}  // FreeDbConnection()
+
+/********** CloseDbConnections() **********/
+void CloseDbConnections(void)
+{
+       DbList *pList, *pNext;
+       
+       // Traverse the linked list closing database connections and freeing memory as I go.
+       for (pList = PtrBegDbList; pList; pList = pNext)
+       {
+               pNext = pList->PtrNext;                 // Save the pointer to next
+               pList->PtrDb->CommitTrans();    // Commit any open transactions on wxDB object
+               pList->PtrDb->Close();                  // Close the wxDB object
+               delete pList->PtrDb;                            // Deletes the wxDB object
+               delete pList;                                           // Deletes the linked list object
+       }
+
+       // Mark the list as empty
+       PtrBegDbList = 0;
+
+}  // CloseDbConnections()
+
+/********** NumberDbConnectionsInUse() **********/
+int NumberDbConnectionsInUse(void)
+{
+       DbList *pList;
+       int cnt = 0;
+
+       // Scan the linked list counting db connections that are currently in use
+       for (pList = PtrBegDbList; pList; pList = pList->PtrNext)
+       {
+               if (pList->Free == FALSE)
+                       cnt++;
+       }
+
+       return(cnt);
+
+}  // NumberDbConnectionsInUse()
+
+/********** GetDataSource() **********/
+bool GetDataSource(HENV henv, char *Dsn, SWORD DsnMax, char *DsDesc, SWORD DsDescMax,
+                                                UWORD direction)
+{
+       SWORD cb;
+
+       if (SQLDataSources(henv, direction, (UCHAR FAR *) Dsn, DsnMax, &cb,
+                                                        (UCHAR FAR *) DsDesc, DsDescMax, &cb) == SQL_SUCCESS)
+               return(TRUE);
+       else
+               return(FALSE);
+
+}  // GetDataSource()
+
+#endif
+    // USE_ODBC
diff --git a/src/common/dbtable.cpp b/src/common/dbtable.cpp
new file mode 100644 (file)
index 0000000..32c7895
--- /dev/null
@@ -0,0 +1,1445 @@
+///////////////////////////////////////////////////////////////////////////////
+// Name:        table.cpp
+// Purpose:     Implementation of the wxTable class.
+// Author:      Doug Card
+// Modified by:
+// Created:     9.96
+// RCS-ID:      $Id$
+// Copyright:   (c) 1996 Remstar International, Inc.
+// Licence:     wxWindows licence, plus:
+// Notice:             This class library and its intellectual design are free of charge for use,
+//              modification, enhancement, debugging under the following conditions:
+//              1) These classes may only be used as part of the implementation of a
+//                 wxWindows-based application
+//              2) All enhancements and bug fixes are to be submitted back to the wxWindows
+//                 user groups free of all charges for use with the wxWindows library.
+//              3) These classes may not be distributed as part of any other class library,
+//                 DLL, text (written or electronic), other than a complete distribution of
+//                 the wxWindows GUI development toolkit.
+///////////////////////////////////////////////////////////////////////////////
+
+/*
+// SYNOPSIS START
+// SYNOPSIS STOP
+*/
+
+/*
+#ifdef _CONSOLE
+       #include <iostream.h>
+#endif
+*/
+
+#include  "wx/wxprec.h"
+
+#ifdef    __BORLANDC__
+  #pragma hdrstop
+#endif  //__BORLANDC__
+
+#ifndef WX_PRECOMP
+  #include  <wx/wx.h>
+#endif //WX_PRECOMP
+
+#if USE_ODBC
+
+#include <wx/dbtable.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __WXUNIX__
+// The HPUX preprocessor lines below were commented out on 8/20/97
+// because macros.h currently redefines DEBUG and is unneeded.
+// #  ifdef HPUX
+// #    include <macros.h>
+// #  endif
+#  ifdef __WXLINUX__
+#    include <sys/minmax.h>
+#  endif
+#endif
+
+/********** wxTable::wxTable() **********/
+wxTable::wxTable(wxDB *pwxDB, const char *tblName, const int nCols, const char *qryTblName)
+{
+       // Assign member variables
+       pDb = pwxDB;                                                                    // Pointer to the wxDB object
+       strcpy(tableName, tblName);                             // Table Name
+       if (qryTblName)                                                         // Name of the table/view to query
+               strcpy(queryTableName, qryTblName);
+       else
+               strcpy(queryTableName, tblName);
+
+       noCols = nCols;                                                         // No. of cols in the table
+       where = 0;                                                                              // Where clause
+       orderBy = 0;                                                                    // Order By clause
+       selectForUpdate = FALSE;                                        // SELECT ... FOR UPDATE; Indicates whether to include the FOR UPDATE phrase
+
+       // Grab the HENV and HDBC from the wxDB object
+       henv = pDb->henv;
+       hdbc = pDb->hdbc;
+
+       // Allocate space for column definitions
+       if (noCols)
+               colDefs = new CcolDef[noCols];  // Points to the first column defintion
+       else
+               colDefs = 0;
+
+       // Allocate statement handles for the table
+       if (SQLAllocStmt(hdbc, &c0) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc);
+       if (SQLAllocStmt(hdbc, &c1) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc);
+       if (SQLAllocStmt(hdbc, &c2) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc);
+//     if (SQLAllocStmt(hdbc, &c3) != SQL_SUCCESS)
+//             pDb->DispAllErrors(henv, hdbc);
+//     if (SQLAllocStmt(hdbc, &c4) != SQL_SUCCESS)
+//             pDb->DispAllErrors(henv, hdbc);
+//     if (SQLAllocStmt(hdbc, &c5) != SQL_SUCCESS)
+//             pDb->DispAllErrors(henv, hdbc);
+       // Allocate a separate statement handle for performing inserts
+       if (SQLAllocStmt(hdbc, &hstmtInsert) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc);
+       // Allocate a separate statement handle for performing deletes
+       if (SQLAllocStmt(hdbc, &hstmtDelete) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc);
+       // Allocate a separate statement handle for performing updates
+       if (SQLAllocStmt(hdbc, &hstmtUpdate) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc);
+       // Allocate a separate statement handle for performing count(*) function
+       if (SQLAllocStmt(hdbc, &hstmtCount) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc);
+
+       // Set the cursor type for the statement handles
+       UDWORD cursorType = SQL_CURSOR_STATIC;
+       if (SQLSetStmtOption(c1, SQL_CURSOR_TYPE, cursorType) != SQL_SUCCESS)
+       {
+               // Check to see if cursor type is supported
+               pDb->GetNextError(henv, hdbc, c1);
+               if (! strcmp(pDb->sqlState, "01S02"))  // Option Value Changed
+               {
+                       // Datasource does not support static cursors.  Driver
+                       // will substitute a cursor type.  Call SQLGetStmtOption()
+                       // to determine which cursor type was selected.
+                       if (SQLGetStmtOption(c1, SQL_CURSOR_TYPE, &cursorType) != SQL_SUCCESS)
+                               pDb->DispAllErrors(henv, hdbc, c1);
+#ifdef _CONSOLE
+                       cout << "Static cursor changed to: ";
+                       switch(cursorType)
+                       {
+                       case SQL_CURSOR_FORWARD_ONLY:
+                               cout << "Forward Only";         break;
+                       case SQL_CURSOR_STATIC:
+                               cout << "Static";                               break;
+                       case SQL_CURSOR_KEYSET_DRIVEN:
+                               cout << "Keyset Driven";        break;
+                       case SQL_CURSOR_DYNAMIC:
+                               cout << "Dynamic";                      break;
+                       }
+                       cout << endl << endl;
+#endif
+               }
+               else
+               {
+                       pDb->DispNextError();
+                       pDb->DispAllErrors(henv, hdbc, c1);
+               }
+       }
+#ifdef _CONSOLE
+       else
+               cout << "Cursor Type set to STATIC" << endl << endl;
+#endif
+
+       if (SQLSetStmtOption(c0, SQL_CURSOR_TYPE, cursorType) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc, c0);
+       if (SQLSetStmtOption(c2, SQL_CURSOR_TYPE, cursorType) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc, c2);
+//     if (SQLSetStmtOption(c3, SQL_CURSOR_TYPE, cursorType) != SQL_SUCCESS)
+//             pDb->DispAllErrors(henv, hdbc, c3);
+//     if (SQLSetStmtOption(c4, SQL_CURSOR_TYPE, cursorType) != SQL_SUCCESS)
+//             pDb->DispAllErrors(henv, hdbc, c4);
+//     if (SQLSetStmtOption(c5, SQL_CURSOR_TYPE, cursorType) != SQL_SUCCESS)
+//             pDb->DispAllErrors(henv, hdbc, c5);
+
+       // Set the cursor type for the INSERT statement handle
+       if (SQLSetStmtOption(hstmtInsert, SQL_CURSOR_TYPE, SQL_CURSOR_FORWARD_ONLY) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc, hstmtInsert);
+       // Set the cursor type for the DELETE statement handle
+       if (SQLSetStmtOption(hstmtDelete, SQL_CURSOR_TYPE, SQL_CURSOR_FORWARD_ONLY) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc, hstmtDelete);
+       // Set the cursor type for the UPDATE statement handle
+       if (SQLSetStmtOption(hstmtUpdate, SQL_CURSOR_TYPE, SQL_CURSOR_FORWARD_ONLY) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc, hstmtUpdate);
+       // Set the cursor type for the COUNT(*) statement handle
+       if (SQLSetStmtOption(hstmtCount, SQL_CURSOR_TYPE, SQL_CURSOR_FORWARD_ONLY) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc, hstmtCount);
+
+       // Copy cursor 1 to the default cursor
+       hstmt = c1;
+       currCursorNo = DB_CURSOR1;
+
+}  // wxTable::wxTable()
+
+/********** wxTable::~wxTable() **********/
+wxTable::~wxTable()
+{
+       // Delete memory allocated for column definitions
+       if (colDefs)
+               delete [] colDefs;
+
+       // Free statement handles
+       if (SQLFreeStmt(c0, SQL_DROP) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc);
+       if (SQLFreeStmt(c1, SQL_DROP) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc);
+       if (SQLFreeStmt(c2, SQL_DROP) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc);
+//     if (SQLFreeStmt(c3, SQL_DROP) != SQL_SUCCESS)
+//             pDb->DispAllErrors(henv, hdbc);
+//     if (SQLFreeStmt(c4, SQL_DROP) != SQL_SUCCESS)
+//             pDb->DispAllErrors(henv, hdbc);
+//     if (SQLFreeStmt(c5, SQL_DROP) != SQL_SUCCESS)
+//             pDb->DispAllErrors(henv, hdbc);
+       if (SQLFreeStmt(hstmtInsert, SQL_DROP) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc);
+       if (SQLFreeStmt(hstmtDelete, SQL_DROP) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc);
+       if (SQLFreeStmt(hstmtUpdate, SQL_DROP) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc);
+       if (SQLFreeStmt(hstmtCount, SQL_DROP) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc);
+
+}  // wxTable::~wxTable()
+
+/********** wxTable::Open() **********/
+bool wxTable::Open(void)
+{
+       int i;
+       char sqlStmt[DB_MAX_STATEMENT_LEN];
+
+       // Verify that the table exists in the database
+       if (!pDb->TableExists(tableName))
+       {
+               char s[128];
+               sprintf(s, "Error opening '%s', table/view does not exist in the database.", tableName);
+               pDb->LogError(s);
+               return(FALSE);
+       }
+
+       // Bind the member variables for field exchange between
+       // the wxTable object and the ODBC record.
+       if(! bindInsertParams())                                                        // Inserts
+               return(FALSE);
+       if(! bindUpdateParams())                                                        // Updates
+               return(FALSE);
+       if(! bindCols(c0))                                                                      // Selects
+               return(FALSE);
+       if(! bindCols(c1))
+               return(FALSE);
+       if(! bindCols(c2))
+               return(FALSE);
+//     if(! bindCols(c3))
+//             return(FALSE);
+//     if(! bindCols(c4))
+//             return(FALSE);
+//     if(! bindCols(c5))
+//             return(FALSE);
+
+       // Build an insert statement using parameter markers
+       if (noCols > 0)
+       {
+               bool needComma = FALSE;
+               sprintf(sqlStmt, "INSERT INTO %s (", tableName);
+               for (i = 0; i < noCols; i++)
+               {
+                       if (! colDefs[i].InsertAllowed)
+                               continue;
+                       if (needComma)
+                               strcat(sqlStmt, ",");
+                       strcat(sqlStmt, colDefs[i].ColName);
+                       needComma = TRUE;
+               }
+               needComma = FALSE;
+               strcat(sqlStmt, ") VALUES (");
+               for (i = 0; i < noCols; i++)
+               {
+                       if (! colDefs[i].InsertAllowed)
+                               continue;
+                       if (needComma)
+                               strcat(sqlStmt, ",");
+                       strcat(sqlStmt, "?");
+                       needComma = TRUE;
+               }
+               strcat(sqlStmt, ")");
+
+               // Prepare the insert statement for execution
+               if (SQLPrepare(hstmtInsert, (UCHAR FAR *) sqlStmt, SQL_NTS) != SQL_SUCCESS)
+                       return(pDb->DispAllErrors(henv, hdbc, hstmtInsert));
+       }
+       
+       // Completed successfully
+       return(TRUE);
+
+}  // wxTable::Open()
+
+/********** wxTable::Query() **********/
+bool wxTable::Query(bool forUpdate, bool distinct)
+{
+
+       return(query(DB_SELECT_WHERE, forUpdate, distinct));
+
+}  // wxTable::Query()
+
+/********** wxTable::QueryBySqlStmt() **********/
+bool wxTable::QueryBySqlStmt(char *pSqlStmt)
+{
+
+       return(query(DB_SELECT_STATEMENT, FALSE, FALSE, pSqlStmt));
+
+}  // wxTable::QueryBySqlStmt()
+
+/********** wxTable::QueryMatching() **********/
+bool wxTable::QueryMatching(bool forUpdate, bool distinct)
+{
+
+       return(query(DB_SELECT_MATCHING, forUpdate, distinct));
+
+}  // wxTable::QueryMatching()
+
+/********** wxTable::QueryOnKeyFields() **********/
+bool wxTable::QueryOnKeyFields(bool forUpdate, bool distinct)
+{
+
+       return(query(DB_SELECT_KEYFIELDS, forUpdate, distinct));
+
+}  // wxTable::QueryOnKeyFields()
+
+/********** wxTable::query() **********/
+bool wxTable::query(int queryType, bool forUpdate, bool distinct, char *pSqlStmt)
+{
+       char sqlStmt[DB_MAX_STATEMENT_LEN];
+
+       // Set the selectForUpdate member variable
+       if (forUpdate)
+               // The user may wish to select for update, but the DBMS may not be capable
+               selectForUpdate = CanSelectForUpdate();
+       else
+               selectForUpdate = FALSE;
+
+       // Set the SQL SELECT string
+       if (queryType != DB_SELECT_STATEMENT)                           // A select statement was not passed in,
+               GetSelectStmt(sqlStmt, queryType, distinct);    // so generate a select statement.
+
+       // Make sure the cursor is closed first
+       if (! CloseCursor(hstmt))
+               return(FALSE);
+
+       // Execute the SQL SELECT statement
+       if (SQLExecDirect(hstmt, (UCHAR FAR *) (queryType == DB_SELECT_STATEMENT ? pSqlStmt : sqlStmt),
+                              SQL_NTS) != SQL_SUCCESS)
+               return(pDb->DispAllErrors(henv, hdbc, hstmt));
+
+       // Completed successfully
+       return(TRUE);
+
+}  // wxTable::query()
+
+/********** wxTable::GetSelectStmt() **********/
+void wxTable::GetSelectStmt(char *pSqlStmt, int typeOfSelect, bool distinct)
+{
+       char whereClause[DB_MAX_WHERE_CLAUSE_LEN];
+
+       whereClause[0] = 0;
+
+       // Build a select statement to query the database
+       strcpy(pSqlStmt, "SELECT ");
+
+       // SELECT DISTINCT values only?
+       if (distinct)
+               strcat(pSqlStmt, "DISTINCT ");
+
+       // Add the column list
+       for (int i = 0; i < noCols; i++)
+       {
+               strcat(pSqlStmt, colDefs[i].ColName);
+               if (i + 1 < noCols)
+                       strcat(pSqlStmt, ",");
+       }
+
+       // If the datasource supports ROWID, get this column as well.  Exception: Don't retrieve
+       // the ROWID if querying distinct records.  The rowid will always be unique.
+       if (!distinct && CanUpdByROWID())
+               strcat(pSqlStmt, ",ROWID");
+
+       // Append the FROM tablename portion
+       strcat(pSqlStmt, " FROM ");
+       strcat(pSqlStmt, queryTableName);
+
+       // Append the WHERE clause.  Either append the where clause for the class
+       // or build a where clause.  The typeOfSelect determines this.
+       switch(typeOfSelect)
+       {
+       case DB_SELECT_WHERE:
+               if (where && strlen(where))     // May not want a where clause!!!
+               {
+                       strcat(pSqlStmt, " WHERE ");
+                       strcat(pSqlStmt, where);
+               }
+               break;
+       case DB_SELECT_KEYFIELDS:
+               GetWhereClause(whereClause, DB_WHERE_KEYFIELDS);
+               if (strlen(whereClause))
+               {
+                       strcat(pSqlStmt, " WHERE ");
+                       strcat(pSqlStmt, whereClause);
+               }
+               break;
+       case DB_SELECT_MATCHING:
+               GetWhereClause(whereClause, DB_WHERE_MATCHING);
+               if (strlen(whereClause))
+               {
+                       strcat(pSqlStmt, " WHERE ");
+                       strcat(pSqlStmt, whereClause);
+               }
+               break;
+       }
+
+       // Append the ORDER BY clause
+       if (orderBy && strlen(orderBy))
+       {
+               strcat(pSqlStmt, " ORDER BY ");
+               strcat(pSqlStmt, orderBy);
+       }
+
+       // SELECT FOR UPDATE if told to do so and the datasource is capable
+       if (selectForUpdate && CanSelectForUpdate())
+               strcat(pSqlStmt, " FOR UPDATE");
+
+}  // wxTable::GetSelectStmt()
+
+/********** wxTable::getRec() **********/
+bool wxTable::getRec(UWORD fetchType)
+{
+       RETCODE retcode;
+
+#ifndef FWD_ONLY_CURSORS
+       // Fetch the NEXT, PREV, FIRST or LAST record, depending on fetchType
+       UDWORD  cRowsFetched;
+       UWORD   rowStatus;
+       if ((retcode = SQLExtendedFetch(hstmt, fetchType, 0, &cRowsFetched, &rowStatus)) != SQL_SUCCESS)
+               if (retcode == SQL_NO_DATA_FOUND)
+                       return(FALSE);
+               else
+                       return(pDb->DispAllErrors(henv, hdbc, hstmt));
+#else
+       // Fetch the next record from the record set
+       if ((retcode = SQLFetch(hstmt)) != SQL_SUCCESS)
+               if (retcode == SQL_NO_DATA_FOUND)
+                       return(FALSE);
+               else
+                       return(pDb->DispAllErrors(henv, hdbc, hstmt));
+#endif
+
+       // Completed successfully
+       return(TRUE);
+
+}  // wxTable::getRec()
+
+/********** wxTable::GetRowNum() **********/
+UWORD wxTable::GetRowNum(void)
+{
+       UDWORD rowNum;
+
+       if (SQLGetStmtOption(hstmt, SQL_ROW_NUMBER, &rowNum) != SQL_SUCCESS)
+       {
+               pDb->DispAllErrors(henv, hdbc, hstmt);
+               return(0);
+       }
+
+       // Completed successfully
+       return((UWORD) rowNum);
+
+}  // wxTable::GetRowNum()
+
+/********** wxTable::bindInsertParams() **********/
+bool wxTable::bindInsertParams(void)
+{
+       SWORD   fSqlType;
+       UDWORD  precision;
+       SWORD   scale;
+
+//glt CcolDef  *tColDef;
+
+       // Bind each column (that can be inserted) of the table to a parameter marker
+       for (int i = 0; i < noCols; i++)
+       {
+//glt tColDef = &colDefs[i];
+               if (! colDefs[i].InsertAllowed)
+                       continue;
+               switch(colDefs[i].DbDataType)
+               {
+               case DB_DATA_TYPE_VARCHAR:
+                       fSqlType = pDb->typeInfVarchar.FsqlType;
+                       precision = colDefs[i].SzDataObj;
+                       scale = 0;
+                       colDefs[i].CbValue = SQL_NTS;
+                       break;
+               case DB_DATA_TYPE_INTEGER:
+                       fSqlType = pDb->typeInfInteger.FsqlType;
+                       precision = pDb->typeInfInteger.Precision;
+                       scale = 0;
+                       colDefs[i].CbValue = 0;
+                       break;
+               case DB_DATA_TYPE_FLOAT:
+                       fSqlType = pDb->typeInfFloat.FsqlType;
+                       precision = pDb->typeInfFloat.Precision;
+                       scale = pDb->typeInfFloat.MaximumScale;
+                       // SQL Sybase Anywhere v5.5 returned a negative number for the
+                       // MaxScale.  This caused ODBC to kick out an error on ibscale.
+                       // I check for this here and set the scale = precision.
+                       //if (scale < 0)
+                       //      scale = (short) precision;
+                       colDefs[i].CbValue = 0;
+                       break;
+               case DB_DATA_TYPE_DATE:
+                       fSqlType = pDb->typeInfDate.FsqlType;
+                       precision = pDb->typeInfDate.Precision;
+                       scale = 0;
+                       colDefs[i].CbValue = 0;
+                       break;
+               }
+               if (SQLBindParameter(hstmtInsert, i+1, SQL_PARAM_INPUT, colDefs[i].SqlCtype,
+                                                                       fSqlType, precision, scale, colDefs[i].PtrDataObj, 
+                                                                       precision+1,&colDefs[i].CbValue) != SQL_SUCCESS)
+                       return(pDb->DispAllErrors(henv, hdbc, hstmtInsert));
+       }
+
+       // Completed successfully
+       return(TRUE);
+
+}  // wxTable::bindInsertParams()
+
+/********** wxTable::bindUpdateParams() **********/
+bool wxTable::bindUpdateParams(void)
+{
+       SWORD   fSqlType;
+       UDWORD  precision;
+       SWORD   scale;
+       
+       // Bind each UPDATEABLE column of the table to a parameter marker
+       for (int i = 0, colNo = 1; i < noCols; i++)
+       {
+               if (! colDefs[i].Updateable)
+                       continue;
+               switch(colDefs[i].DbDataType)
+               {
+               case DB_DATA_TYPE_VARCHAR:
+                       fSqlType = pDb->typeInfVarchar.FsqlType;
+                       precision = colDefs[i].SzDataObj;
+                       scale = 0;
+                       colDefs[i].CbValue = SQL_NTS;
+                       break;
+               case DB_DATA_TYPE_INTEGER:
+                       fSqlType = pDb->typeInfInteger.FsqlType;
+                       precision = pDb->typeInfInteger.Precision;
+                       scale = 0;
+                       colDefs[i].CbValue = 0;
+                       break;
+               case DB_DATA_TYPE_FLOAT:
+                       fSqlType = pDb->typeInfFloat.FsqlType;
+                       precision = pDb->typeInfFloat.Precision;
+                       scale = pDb->typeInfFloat.MaximumScale;
+                       // SQL Sybase Anywhere v5.5 returned a negative number for the
+                       // MaxScale.  This caused ODBC to kick out an error on ibscale.
+                       // I check for this here and set the scale = precision.
+                       //if (scale < 0)
+                       // scale = (short) precision;
+                       colDefs[i].CbValue = 0;
+                       break;
+               case DB_DATA_TYPE_DATE:
+                       fSqlType = pDb->typeInfDate.FsqlType;
+                       precision = pDb->typeInfDate.Precision;
+                       scale = 0;
+                       colDefs[i].CbValue = 0;
+                       break;
+               }
+               if (SQLBindParameter(hstmtUpdate, colNo++, SQL_PARAM_INPUT, colDefs[i].SqlCtype,
+                                                                       fSqlType, precision, scale, colDefs[i].PtrDataObj, 
+                                                                       precision+1, &colDefs[i].CbValue) != SQL_SUCCESS)
+                       return(pDb->DispAllErrors(henv, hdbc, hstmtUpdate));
+       }
+
+       // Completed successfully
+       return(TRUE);
+
+}  // wxTable::bindUpdateParams()
+
+/********** wxTable::bindCols() **********/
+bool wxTable::bindCols(HSTMT cursor)
+{
+       static SDWORD  cb;
+       
+       // Bind each column of the table to a memory address for fetching data
+       for (int i = 0; i < noCols; i++)
+       {
+               if (SQLBindCol(cursor, i+1, colDefs[i].SqlCtype, colDefs[i].PtrDataObj,
+                                                       colDefs[i].SzDataObj, &cb) != SQL_SUCCESS)
+                       return(pDb->DispAllErrors(henv, hdbc, cursor));
+       }
+
+       // Completed successfully
+       return(TRUE);
+
+}  // wxTable::bindCols()
+
+/********** wxTable::CloseCursor() **********/
+bool wxTable::CloseCursor(HSTMT cursor)
+{
+       if (SQLFreeStmt(cursor, SQL_CLOSE) != SQL_SUCCESS)
+               return(pDb->DispAllErrors(henv, hdbc, cursor));
+
+       // Completed successfully
+       return(TRUE);
+
+}  // wxTable::CloseCursor()
+
+/********** wxTable::CreateTable() **********/
+bool wxTable::CreateTable(void)
+{
+       int i, j;
+       char sqlStmt[DB_MAX_STATEMENT_LEN];
+
+#ifdef _CONSOLE
+       cout << "Creating Table " << tableName << "..." << endl;
+#endif
+
+       // Drop the table first
+       sprintf(sqlStmt, "DROP TABLE %s", tableName);
+       if (SQLExecDirect(hstmt, (UCHAR FAR *) sqlStmt, SQL_NTS) != SQL_SUCCESS)
+       {
+               // Check for sqlState = S0002, "Table or view not found".
+               // Ignore this error, bomb out on any other error.
+               // SQL Sybase Anwhere v5.5 returns an access violation error here
+               // (sqlstate = 42000) rather than an S0002.
+               pDb->GetNextError(henv, hdbc, hstmt);
+               if (strcmp(pDb->sqlState, "S0002") && strcmp(pDb->sqlState, "42000"))
+               {
+                       pDb->DispNextError();
+                       pDb->DispAllErrors(henv, hdbc, hstmt);
+                       pDb->RollbackTrans();
+                       CloseCursor(hstmt);
+                       return(FALSE);
+               }
+       }
+
+       // Commit the transaction and close the cursor
+       if (! pDb->CommitTrans())
+               return(FALSE);
+       if (! CloseCursor(hstmt))
+               return(FALSE);
+
+       // Create the table
+#ifdef _CONSOLE
+       for (i = 0; i < noCols; i++)
+       {
+               // Exclude derived columns since they are NOT part of the base table
+               if (colDefs[i].DerivedCol)
+                       continue;
+               cout << i + 1 << ": " << colDefs[i].ColName << "; ";
+               switch(colDefs[i].DbDataType)
+               {
+                       case DB_DATA_TYPE_VARCHAR:
+                               cout << pDb->typeInfVarchar.TypeName << "(" << colDefs[i].SzDataObj << ")";
+                               break;
+                       case DB_DATA_TYPE_INTEGER:
+                               cout << pDb->typeInfInteger.TypeName;
+                               break;
+                       case DB_DATA_TYPE_FLOAT:
+                               cout << pDb->typeInfFloat.TypeName;
+                               break;
+                       case DB_DATA_TYPE_DATE:
+                               cout << pDb->typeInfDate.TypeName;
+                               break;
+               }
+               cout << endl;
+       }
+#endif
+
+       // Build a CREATE TABLE string from the colDefs structure.
+       bool needComma = FALSE;
+       sprintf(sqlStmt, "CREATE TABLE %s (", tableName);
+       for (i = 0; i < noCols; i++)
+       {
+               // Exclude derived columns since they are NOT part of the base table
+               if (colDefs[i].DerivedCol)
+                       continue;
+               // Comma Delimiter
+               if (needComma)
+                  strcat(sqlStmt, ",");
+               // Column Name
+               strcat(sqlStmt, colDefs[i].ColName);
+               strcat(sqlStmt, " ");
+               // Column Type
+               switch(colDefs[i].DbDataType)
+               {
+                       case DB_DATA_TYPE_VARCHAR:
+                               strcat(sqlStmt, pDb->typeInfVarchar.TypeName); break;
+                       case DB_DATA_TYPE_INTEGER:
+                               strcat(sqlStmt, pDb->typeInfInteger.TypeName); break;
+                       case DB_DATA_TYPE_FLOAT:
+                               strcat(sqlStmt, pDb->typeInfFloat.TypeName); break;
+                       case DB_DATA_TYPE_DATE:
+                               strcat(sqlStmt, pDb->typeInfDate.TypeName); break;
+               }
+               // For varchars, append the size of the string
+               if (colDefs[i].DbDataType == DB_DATA_TYPE_VARCHAR)
+               {
+                       char s[10];
+                       // strcat(sqlStmt, "(");
+                       // strcat(sqlStmt, itoa(colDefs[i].SzDataObj, s, 10));
+                       // strcat(sqlStmt, ")");
+                       sprintf(s, "(%d)", colDefs[i].SzDataObj);
+                       strcat(sqlStmt, s);
+               }
+               needComma = TRUE;
+       }
+       // If there is a primary key defined, include it in the create statement
+       for (i = j = 0; i < noCols; i++)
+       {
+               if (colDefs[i].KeyField)
+               {
+                       j++;
+                       break;
+               }
+       }
+       if (j)  // Found a keyfield
+       {
+               strcat(sqlStmt, ",CONSTRAINT ");
+               strcat(sqlStmt, tableName);
+               strcat(sqlStmt, "_PIDX PRIMARY KEY (");
+               // List column name(s) of column(s) comprising the primary key
+               for (i = j = 0; i < noCols; i++)
+               {
+                       if (colDefs[i].KeyField)
+                       {
+                               if (j++) // Multi part key, comma separate names
+                                       strcat(sqlStmt, ",");
+                               strcat(sqlStmt, colDefs[i].ColName);
+                       }
+               }
+          strcat(sqlStmt, ")");
+       }
+       // Append the closing parentheses for the create table statement
+   strcat(sqlStmt, ")");
+
+#ifdef _CONSOLE
+       cout << endl << sqlStmt << endl;
+#endif
+
+       // Execute the CREATE TABLE statement
+       if (SQLExecDirect(hstmt, (UCHAR FAR *) sqlStmt, SQL_NTS) != SQL_SUCCESS)
+       {
+               pDb->DispAllErrors(henv, hdbc, hstmt);
+               pDb->RollbackTrans();
+               CloseCursor(hstmt);
+               return(FALSE);
+       }
+
+       // Commit the transaction and close the cursor
+       if (! pDb->CommitTrans())
+               return(FALSE);
+       if (! CloseCursor(hstmt))
+               return(FALSE);
+
+       // Database table created successfully
+       return(TRUE);
+
+} // wxTable::CreateTable()
+
+/********** wxTable::CreateIndex() **********/
+bool wxTable::CreateIndex(char * idxName, bool unique, int noIdxCols, CidxDef *pIdxDefs)
+{
+       char sqlStmt[DB_MAX_STATEMENT_LEN];
+
+       // Build a CREATE INDEX statement
+       strcpy(sqlStmt, "CREATE ");
+       if (unique)
+               strcat(sqlStmt, "UNIQUE ");
+
+       strcat(sqlStmt, "INDEX ");
+       strcat(sqlStmt, idxName);
+       strcat(sqlStmt, " ON ");
+       strcat(sqlStmt, tableName);
+       strcat(sqlStmt, " (");
+
+       // Append list of columns making up index
+       for (int i = 0; i < noIdxCols; i++)
+       {
+               strcat(sqlStmt, pIdxDefs[i].ColName);
+               if (pIdxDefs[i].Ascending)
+                       strcat(sqlStmt, " ASC");
+               else
+                       strcat(sqlStmt, " DESC");
+               if ((i + 1) < noIdxCols)
+                       strcat(sqlStmt, ",");
+       }
+       
+       // Append closing parentheses
+       strcat(sqlStmt, ")");
+
+#ifdef _CONSOLE
+       cout << endl << sqlStmt << endl << endl;
+#endif
+
+       // Execute the CREATE INDEX statement
+       if (SQLExecDirect(hstmt, (UCHAR FAR *) sqlStmt, SQL_NTS) != SQL_SUCCESS)
+       {
+               pDb->DispAllErrors(henv, hdbc, hstmt);
+               pDb->RollbackTrans();
+               CloseCursor(hstmt);
+               return(FALSE);
+       }
+
+       // Commit the transaction and close the cursor
+       if (! pDb->CommitTrans())
+               return(FALSE);
+       if (! CloseCursor(hstmt))
+               return(FALSE);
+
+       // Index Created Successfully
+       return(TRUE);
+
+}  // wxTable::CreateIndex()
+
+/********** wxTable::Insert() **********/
+int wxTable::Insert(void)
+{
+       // Insert the record by executing the already prepared insert statement
+       if (SQLExecute(hstmtInsert) != SQL_SUCCESS)
+       {
+               // Check to see if integrity constraint was violated
+               pDb->GetNextError(henv, hdbc, hstmtInsert);
+               if (! strcmp(pDb->sqlState, "23000"))  // Integrity constraint violated
+                       return(DB_ERR_INTEGRITY_CONSTRAINT_VIOL);
+               else
+               {
+                       pDb->DispNextError();
+                       pDb->DispAllErrors(henv, hdbc, hstmtInsert);
+                       return(DB_FAILURE);
+               }
+       }
+
+       // Record inserted into the datasource successfully
+       return(DB_SUCCESS);
+
+}  // wxTable::Insert()
+
+/********** wxTable::Update(pSqlStmt) **********/
+bool wxTable::Update(char *pSqlStmt)
+{
+
+       return(execUpdate(pSqlStmt));
+
+}  // wxTable::Update(pSqlStmt)
+
+/********** wxTable::Update() **********/
+bool wxTable::Update(void)
+{
+       char sqlStmt[DB_MAX_STATEMENT_LEN];
+
+       // Build the SQL UPDATE statement
+       GetUpdateStmt(sqlStmt, DB_UPD_KEYFIELDS);
+
+#ifdef _CONSOLE
+       cout << endl << sqlStmt << endl << endl;
+#endif
+
+       // Execute the SQL UPDATE statement
+       return(execUpdate(sqlStmt));
+
+}  // wxTable::Update()
+
+/********** wxTable::UpdateWhere() **********/
+bool wxTable::UpdateWhere(char *pWhereClause)
+{
+       char sqlStmt[DB_MAX_STATEMENT_LEN];
+
+       // Build the SQL UPDATE statement
+       GetUpdateStmt(sqlStmt, DB_UPD_WHERE, pWhereClause);
+
+#ifdef _CONSOLE
+       cout << endl << sqlStmt << endl << endl;
+#endif
+
+       // Execute the SQL UPDATE statement
+       return(execUpdate(sqlStmt));
+
+}  // wxTable::UpdateWhere()
+
+/********** wxTable::Delete() **********/
+bool wxTable::Delete(void)
+{
+       char sqlStmt[DB_MAX_STATEMENT_LEN];
+
+       // Build the SQL DELETE statement
+       GetDeleteStmt(sqlStmt, DB_DEL_KEYFIELDS);
+
+       // Execute the SQL DELETE statement
+       return(execDelete(sqlStmt));
+
+}  // wxTable::Delete()
+
+/********** wxTable::DeleteWhere() **********/
+bool wxTable::DeleteWhere(char *pWhereClause)
+{
+       char sqlStmt[DB_MAX_STATEMENT_LEN];
+
+       // Build the SQL DELETE statement
+       GetDeleteStmt(sqlStmt, DB_DEL_WHERE, pWhereClause);
+
+       // Execute the SQL DELETE statement
+       return(execDelete(sqlStmt));
+
+}  // wxTable::DeleteWhere()
+
+/********** wxTable::DeleteMatching() **********/
+bool wxTable::DeleteMatching(void)
+{
+       char sqlStmt[DB_MAX_STATEMENT_LEN];
+
+       // Build the SQL DELETE statement
+       GetDeleteStmt(sqlStmt, DB_DEL_MATCHING);
+
+       // Execute the SQL DELETE statement
+       return(execDelete(sqlStmt));
+
+}  // wxTable::DeleteMatching()
+
+/********** wxTable::execDelete() **********/
+bool wxTable::execDelete(char *pSqlStmt)
+{
+       // Execute the DELETE statement
+       if (SQLExecDirect(hstmtDelete, (UCHAR FAR *) pSqlStmt, SQL_NTS) != SQL_SUCCESS)
+               return(pDb->DispAllErrors(henv, hdbc, hstmtDelete));
+
+       // Record deleted successfully
+       return(TRUE);
+
+}  // wxTable::execDelete()
+
+/********** wxTable::execUpdate() **********/
+bool wxTable::execUpdate(char *pSqlStmt)
+{
+       // Execute the UPDATE statement
+       if (SQLExecDirect(hstmtUpdate, (UCHAR FAR *) pSqlStmt, SQL_NTS) != SQL_SUCCESS)
+               return(pDb->DispAllErrors(henv, hdbc, hstmtUpdate));
+
+       // Record deleted successfully
+       return(TRUE);
+
+}  // wxTable::execUpdate()
+
+/********** wxTable::GetUpdateStmt() **********/
+void wxTable::GetUpdateStmt(char *pSqlStmt, int typeOfUpd, char *pWhereClause)
+{
+       char whereClause[DB_MAX_WHERE_CLAUSE_LEN];
+       bool firstColumn = TRUE;
+
+       whereClause[0] = 0;
+       sprintf(pSqlStmt, "UPDATE %s SET ", tableName);
+
+       // Append a list of columns to be updated
+       for (int i = 0; i < noCols; i++)
+       {
+               // Only append Updateable columns
+               if (colDefs[i].Updateable)
+               {
+                       if (! firstColumn)
+                               strcat(pSqlStmt, ",");
+                       else
+                               firstColumn = FALSE;
+                       strcat(pSqlStmt, colDefs[i].ColName);
+                       strcat(pSqlStmt, " = ?");
+               }
+       }
+
+       // Append the WHERE clause to the SQL UPDATE statement
+       strcat(pSqlStmt, " WHERE ");
+       switch(typeOfUpd)
+       {
+       case DB_UPD_KEYFIELDS:
+               // If the datasource supports the ROWID column, build
+               // the where on ROWID for efficiency purposes.
+               // e.g. UPDATE PARTS SET C1 = ?, C2 = ? WHERE ROWID = '111.222.333'
+               if (CanUpdByROWID())
+               {
+                       SDWORD cb;
+                       char   rowid[ROWID_LEN];
+
+                       // Get the ROWID value.  If not successful retreiving the ROWID,
+                       // simply fall down through the code and build the WHERE clause
+                       // based on the key fields.
+                       if (SQLGetData(hstmt, noCols+1, SQL_C_CHAR, rowid, ROWID_LEN, &cb) == SQL_SUCCESS)
+                       {
+                               strcat(pSqlStmt, "ROWID = '");
+                               strcat(pSqlStmt, rowid);
+                               strcat(pSqlStmt, "'");
+                               break;
+                       }
+               }
+               // Unable to delete by ROWID, so build a WHERE
+               // clause based on the keyfields.
+               GetWhereClause(whereClause, DB_WHERE_KEYFIELDS);
+               strcat(pSqlStmt, whereClause);
+               break;
+       case DB_UPD_WHERE:
+               strcat(pSqlStmt, pWhereClause);
+               break;
+       }
+
+}  // GetUpdateStmt()
+
+/********** wxTable::GetDeleteStmt() **********/
+void wxTable::GetDeleteStmt(char *pSqlStmt, int typeOfDel, char *pWhereClause)
+{
+       char whereClause[DB_MAX_WHERE_CLAUSE_LEN];
+
+       whereClause[0] = 0;
+
+       // Handle the case of DeleteWhere() and the where clause is blank.  It should
+       // delete all records from the database in this case.
+       if (typeOfDel == DB_DEL_WHERE && (pWhereClause == 0 || strlen(pWhereClause) == 0))
+       {
+               sprintf(pSqlStmt, "DELETE FROM %s", tableName);
+               return;
+       }
+
+       sprintf(pSqlStmt, "DELETE FROM %s WHERE ", tableName);
+
+       // Append the WHERE clause to the SQL DELETE statement
+       switch(typeOfDel)
+       {
+       case DB_DEL_KEYFIELDS:
+               // If the datasource supports the ROWID column, build
+               // the where on ROWID for efficiency purposes.
+               // e.g. DELETE FROM PARTS WHERE ROWID = '111.222.333'
+               if (CanUpdByROWID())
+               {
+                       SDWORD cb;
+                       char   rowid[ROWID_LEN];
+
+                       // Get the ROWID value.  If not successful retreiving the ROWID,
+                       // simply fall down through the code and build the WHERE clause
+                       // based on the key fields.
+                       if (SQLGetData(hstmt, noCols+1, SQL_C_CHAR, rowid, ROWID_LEN, &cb) == SQL_SUCCESS)
+                       {
+                               strcat(pSqlStmt, "ROWID = '");
+                               strcat(pSqlStmt, rowid);
+                               strcat(pSqlStmt, "'");
+                               break;
+                       }
+               }
+               // Unable to delete by ROWID, so build a WHERE
+               // clause based on the keyfields.
+               GetWhereClause(whereClause, DB_WHERE_KEYFIELDS);
+               strcat(pSqlStmt, whereClause);
+               break;
+       case DB_DEL_WHERE:
+               strcat(pSqlStmt, pWhereClause);
+               break;
+       case DB_DEL_MATCHING:
+               GetWhereClause(whereClause, DB_WHERE_MATCHING);
+               strcat(pSqlStmt, whereClause);
+               break;
+       }
+
+}  // GetDeleteStmt()
+
+/********** wxTable::GetWhereClause() **********/
+/*
+ * Note: GetWhereClause() currently ignores timestamp columns.
+ *       They are not included as part of the where clause.
+ */
+
+void wxTable::GetWhereClause(char *pWhereClause, int typeOfWhere)
+{
+       bool moreThanOneColumn = FALSE;
+       char colValue[255];
+
+       // Loop through the columns building a where clause as you go
+       for (int i = 0; i < noCols; i++)
+       {
+               // Determine if this column should be included in the WHERE clause
+               if ((typeOfWhere == DB_WHERE_KEYFIELDS && colDefs[i].KeyField) ||
+                        (typeOfWhere == DB_WHERE_MATCHING  && (! IsColNull(i))))
+               {
+                       // Skip over timestamp columns
+                       if (colDefs[i].SqlCtype == SQL_C_TIMESTAMP)
+                               continue;
+                       // If there is more than 1 column, join them with the keyword "AND"
+                       if (moreThanOneColumn)
+                               strcat(pWhereClause, " AND ");
+                       else
+                               moreThanOneColumn = TRUE;
+                       // Concatenate where phrase for the column
+                       strcat(pWhereClause, colDefs[i].ColName);
+                       strcat(pWhereClause, " = ");
+                       switch(colDefs[i].SqlCtype)
+                       {
+                       case SQL_C_CHAR:
+                               sprintf(colValue, "'%s'", (UCHAR FAR *) colDefs[i].PtrDataObj);
+                               break;
+                       case SQL_C_SSHORT:
+                               sprintf(colValue, "%hi", *((SWORD *) colDefs[i].PtrDataObj));
+                               break;
+                       case SQL_C_USHORT:
+                               sprintf(colValue, "%hu", *((UWORD *) colDefs[i].PtrDataObj));
+                               break;
+                       case SQL_C_SLONG:
+                               sprintf(colValue, "%li", *((SDWORD *) colDefs[i].PtrDataObj));
+                               break;
+                       case SQL_C_ULONG:
+                               sprintf(colValue, "%lu", *((UDWORD *) colDefs[i].PtrDataObj));
+                               break;
+                       case SQL_C_FLOAT:
+                               sprintf(colValue, "%.6f", *((SFLOAT *) colDefs[i].PtrDataObj));
+                               break;
+                       case SQL_C_DOUBLE:
+                               sprintf(colValue, "%.6f", *((SDOUBLE *) colDefs[i].PtrDataObj));
+                               break;
+                       }
+                       strcat(pWhereClause, colValue);
+               }
+       }
+
+}  // wxTable::GetWhereClause()
+
+/********** wxTable::IsColNull() **********/
+bool wxTable::IsColNull(int colNo)
+{
+       switch(colDefs[colNo].SqlCtype)
+       {
+       case SQL_C_CHAR:
+               return(((UCHAR FAR *) colDefs[colNo].PtrDataObj)[0] == 0);
+       case SQL_C_SSHORT:
+               return((  *((SWORD *) colDefs[colNo].PtrDataObj))   == 0);
+       case SQL_C_USHORT:
+               return((   *((UWORD*) colDefs[colNo].PtrDataObj))   == 0);
+       case SQL_C_SLONG:
+               return(( *((SDWORD *) colDefs[colNo].PtrDataObj))   == 0);
+       case SQL_C_ULONG:
+               return(( *((UDWORD *) colDefs[colNo].PtrDataObj))   == 0);
+       case SQL_C_FLOAT:
+               return(( *((SFLOAT *) colDefs[colNo].PtrDataObj))   == 0);
+       case SQL_C_DOUBLE:
+               return((*((SDOUBLE *) colDefs[colNo].PtrDataObj))   == 0);
+       case SQL_C_TIMESTAMP:
+               TIMESTAMP_STRUCT *pDt;
+               pDt = (TIMESTAMP_STRUCT *) colDefs[colNo].PtrDataObj;
+               if (pDt->year == 0 && pDt->month == 0 && pDt->day == 0)
+                       return(TRUE);
+               else
+                       return(FALSE);
+       default:
+               return(TRUE);
+       }
+
+}  // wxTable::IsColNull()
+
+/********** wxTable::CanSelectForUpdate() **********/
+
+bool wxTable::CanSelectForUpdate(void)
+{
+       if (pDb->dbInf.posStmts & SQL_PS_SELECT_FOR_UPDATE)
+               return(TRUE);
+       else
+               return(FALSE);
+
+}  // wxTable::CanSelectForUpdate()
+
+/********** wxTable::CanUpdByROWID() **********/
+bool wxTable::CanUpdByROWID(void)
+{
+
+//@@@@@@glt - returning FALSE for testing purposes, as the ROWID is not getting updated correctly
+       return FALSE;
+
+       if ((! strcmp(pDb->dbInf.dbmsName, "Oracle")) || (! strcmp(pDb->dbInf.dbmsName, "ORACLE")))
+               return(TRUE);
+       else
+               return(FALSE);
+
+}  // wxTable::CanUpdByROWID()
+
+/********** wxTable::IsCursorClosedOnCommit() **********/
+bool wxTable::IsCursorClosedOnCommit(void)
+{
+       if (pDb->dbInf.cursorCommitBehavior == SQL_CB_PRESERVE)
+               return(FALSE);
+       else
+               return(TRUE);
+
+}  // wxTable::IsCursorClosedOnCommit()
+
+/********** wxTable::ClearMemberVars() **********/
+void wxTable::ClearMemberVars(void)
+{
+       // Loop through the columns setting each member variable to zero
+       for (int i = 0; i < noCols; i++)
+       {
+               switch(colDefs[i].SqlCtype)
+               {
+               case SQL_C_CHAR:
+                       ((UCHAR FAR *) colDefs[i].PtrDataObj)[0]        = 0;
+                       break;
+               case SQL_C_SSHORT:
+                       *((SWORD *) colDefs[i].PtrDataObj)                      = 0;
+                       break;
+               case SQL_C_USHORT:
+                       *((UWORD*) colDefs[i].PtrDataObj)                       = 0;
+                       break;
+               case SQL_C_SLONG:
+                       *((SDWORD *) colDefs[i].PtrDataObj)                     = 0;
+                       break;
+               case SQL_C_ULONG:
+                       *((UDWORD *) colDefs[i].PtrDataObj)                     = 0;
+                       break;
+               case SQL_C_FLOAT:
+                       *((SFLOAT *) colDefs[i].PtrDataObj)                     = 0.0f;
+                       break;
+               case SQL_C_DOUBLE:
+                       *((SDOUBLE *) colDefs[i].PtrDataObj)            = 0.0f;
+                       break;
+               case SQL_C_TIMESTAMP:
+                       TIMESTAMP_STRUCT *pDt;
+                       pDt = (TIMESTAMP_STRUCT *) colDefs[i].PtrDataObj;
+                       pDt->year = 0;
+                       pDt->month = 0;
+                       pDt->day = 0;
+                       pDt->hour = 0;
+                       pDt->minute = 0;
+                       pDt->second = 0;
+                       pDt->fraction = 0;
+                       break;
+               }
+       }
+
+}  // wxTable::ClearMemberVars()
+
+/********** wxTable::SetQueryTimeout() **********/
+bool wxTable::SetQueryTimeout(UDWORD nSeconds)
+{
+       if (SQLSetStmtOption(c0, SQL_QUERY_TIMEOUT, nSeconds) != SQL_SUCCESS)
+               return(pDb->DispAllErrors(henv, hdbc, c0));
+       if (SQLSetStmtOption(c1, SQL_QUERY_TIMEOUT, nSeconds) != SQL_SUCCESS)
+               return(pDb->DispAllErrors(henv, hdbc, c1));
+       if (SQLSetStmtOption(c2, SQL_QUERY_TIMEOUT, nSeconds) != SQL_SUCCESS)
+               return(pDb->DispAllErrors(henv, hdbc, c2));
+//     if (SQLSetStmtOption(c3, SQL_QUERY_TIMEOUT, nSeconds) != SQL_SUCCESS)
+//             return(pDb->DispAllErrors(henv, hdbc, c3));
+//     if (SQLSetStmtOption(c4, SQL_QUERY_TIMEOUT, nSeconds) != SQL_SUCCESS)
+//             return(pDb->DispAllErrors(henv, hdbc, c4));
+//     if (SQLSetStmtOption(c5, SQL_QUERY_TIMEOUT, nSeconds) != SQL_SUCCESS)
+//             return(pDb->DispAllErrors(henv, hdbc, c5));
+       if (SQLSetStmtOption(hstmtInsert, SQL_QUERY_TIMEOUT, nSeconds) != SQL_SUCCESS)
+               return(pDb->DispAllErrors(henv, hdbc, hstmtInsert));
+       if (SQLSetStmtOption(hstmtUpdate, SQL_QUERY_TIMEOUT, nSeconds) != SQL_SUCCESS)
+               return(pDb->DispAllErrors(henv, hdbc, hstmtUpdate));
+       if (SQLSetStmtOption(hstmtDelete, SQL_QUERY_TIMEOUT, nSeconds) != SQL_SUCCESS)
+               return(pDb->DispAllErrors(henv, hdbc, hstmtDelete));
+       if (SQLSetStmtOption(hstmtCount, SQL_QUERY_TIMEOUT, nSeconds) != SQL_SUCCESS)
+               return(pDb->DispAllErrors(henv, hdbc, hstmtCount));
+
+       // Completed Successfully
+       return(TRUE);
+
+}  // wxTable::SetQueryTimeout()
+
+/********** wxTable::SetColDefs() **********/
+void wxTable::SetColDefs (int index, char *fieldName, int dataType, void *pData,
+                                                                int cType, int size, bool keyField, bool upd,
+                                                                bool insAllow, bool derivedCol)
+{
+       if (strlen(fieldName) > DB_MAX_COLUMN_NAME_LEN)  // glt 4/21/97
+       {
+               strncpy (colDefs[index].ColName, fieldName, DB_MAX_COLUMN_NAME_LEN);
+               colDefs[index].ColName[DB_MAX_COLUMN_NAME_LEN] = 0;  // glt 10/23/97
+       }
+       else
+               strcpy(colDefs[index].ColName, fieldName);
+
+       colDefs[index].DbDataType               = dataType;
+       colDefs[index].PtrDataObj               = pData;
+       colDefs[index].SqlCtype                 = cType;
+       colDefs[index].SzDataObj                = size;
+       colDefs[index].KeyField                 = keyField;
+       colDefs[index].DerivedCol               = derivedCol;
+       // Derived columns by definition would NOT be "Insertable" or "Updateable"
+       if (derivedCol)
+       {
+               colDefs[index].Updateable               = FALSE;
+               colDefs[index].InsertAllowed    = FALSE;
+       }
+       else
+       {
+               colDefs[index].Updateable               = upd;
+               colDefs[index].InsertAllowed    = insAllow;
+       }
+       
+}  // wxTable::SetColDefs()
+
+/********** wxTable::SetCursor() **********/
+bool wxTable::SetCursor(int cursorNo)
+{
+       switch(cursorNo)
+       {
+       case DB_CURSOR0:
+               hstmt = c0;
+               // currCursorNo doesn't change since Cursor0 is a temp cursor
+               break;
+       case DB_CURSOR1:
+               hstmt = c1;
+               currCursorNo = DB_CURSOR1;
+               break;
+       case DB_CURSOR2:
+               hstmt = c2;
+               currCursorNo = DB_CURSOR2;
+               break;
+//     case DB_CURSOR3:
+//             hstmt = c3;
+//             currCursorNo = DB_CURSOR3;
+//             break;
+//     case DB_CURSOR4:
+//             hstmt = c4;
+//             currCursorNo = DB_CURSOR4;
+//             break;
+//     case DB_CURSOR5:
+//             hstmt = c5;
+//             currCursorNo = DB_CURSOR5;
+//             break;
+       default:
+               return(FALSE);
+       }
+
+       // Refresh the current record
+#ifndef FWD_ONLY_CURSORS
+       UDWORD  cRowsFetched;
+       UWORD   rowStatus;
+       SQLExtendedFetch(hstmt, SQL_FETCH_NEXT,  0, &cRowsFetched, &rowStatus);
+       SQLExtendedFetch(hstmt, SQL_FETCH_PRIOR, 0, &cRowsFetched, &rowStatus);
+#endif
+
+       // Completed successfully
+       return(TRUE);
+
+}  // wxTable::SetCursor()
+
+/********** wxTable::Count() **********/
+ULONG wxTable::Count(void)
+{
+       ULONG l;
+       char sqlStmt[DB_MAX_STATEMENT_LEN];
+       SDWORD cb;
+
+       // Build a "SELECT COUNT(*) FROM queryTableName [WHERE whereClause]" SQL Statement
+       strcpy(sqlStmt, "SELECT COUNT(*) FROM ");
+       strcat(sqlStmt, queryTableName);
+
+       // Add the where clause if one is provided
+       if (where && strlen(where))
+       {
+               strcat(sqlStmt, " WHERE ");
+               strcat(sqlStmt, where);
+       }
+
+       // Execute the SQL statement
+       if (SQLExecDirect(hstmtCount, (UCHAR FAR *) sqlStmt, SQL_NTS) != SQL_SUCCESS)
+       {
+               pDb->DispAllErrors(henv, hdbc, hstmtCount);
+               return(0);
+       }
+
+       // Fetch the record
+       if (SQLFetch(hstmtCount) != SQL_SUCCESS)
+       {
+               pDb->DispAllErrors(henv, hdbc, hstmtCount);
+               return(0);
+       }
+
+       // Obtain the result
+       if (SQLGetData(hstmtCount, 1, SQL_C_ULONG, &l, sizeof(l), &cb) != SQL_SUCCESS)
+       {
+               pDb->DispAllErrors(henv, hdbc, hstmtCount);
+               return(0);
+       }
+
+       // Free the cursor
+       if (SQLFreeStmt(hstmtCount, SQL_CLOSE) != SQL_SUCCESS)
+               pDb->DispAllErrors(henv, hdbc, hstmtCount);
+
+       // Return the record count
+       return(l);
+
+}  // wxTable::Count()
+
+/********** wxTable::Refresh() **********/
+bool wxTable::Refresh(void)
+{
+       bool result = TRUE;
+
+       // Switch to cursor 0
+       int cursorNo = GetCursor();
+       if (!SetCursor())
+               return(FALSE);
+
+       // Save the where and order by clauses
+       char *saveWhere = where;
+       char *saveOrderBy = orderBy;
+
+       // Build a where clause to refetch the record with.  Try and use the
+       // ROWID if it's available, ow use the key fields.
+       char whereClause[DB_MAX_WHERE_CLAUSE_LEN+1];
+       strcpy(whereClause, "");
+       if (CanUpdByROWID())
+       {
+               SDWORD cb;
+               char   rowid[ROWID_LEN+1];
+
+               // Get the ROWID value.  If not successful retreiving the ROWID,
+               // simply fall down through the code and build the WHERE clause
+               // based on the key fields.
+               if (SQLGetData(hstmt, noCols+1, SQL_C_CHAR, rowid, ROWID_LEN, &cb) == SQL_SUCCESS)
+               {
+                       strcat(whereClause, "ROWID = '");
+                       strcat(whereClause, rowid);
+                       strcat(whereClause, "'");
+               }
+       }
+
+       // If unable to use the ROWID, build a where clause from the keyfields
+       if (strlen(whereClause) == 0)
+               GetWhereClause(whereClause, DB_WHERE_KEYFIELDS);
+
+       // Requery the record
+       where = whereClause;
+       orderBy = 0;
+       if (!Query())
+               result = FALSE;
+
+       if (result && !GetNext())
+               result = FALSE;
+
+       // Switch back to original cursor
+       if (!SetCursor(cursorNo))
+               result = FALSE;
+
+       // Restore the original where and order by clauses
+       where     = saveWhere;
+       orderBy = saveOrderBy;
+
+       return(result);
+
+}  // wxTable::Refresh()
+
+#endif
+    // USE_ODBC
index 7419e32be2e85d29b39466ad1e5692afd74f793e..2fd2395c131b2567b5f6f44cc70feb8629527f5a 100644 (file)
@@ -74,6 +74,8 @@ GENERICOBJS= \
 COMMONOBJS = \
   $(COMMDIR)\cmndata.obj \
   $(COMMDIR)\config.obj \
+  $(COMMDIR)\db.obj \
+  $(COMMDIR)\dbtable.obj \
   $(COMMDIR)\docview.obj \
   $(COMMDIR)\dynarray.obj \
   $(COMMDIR)\event.obj \
@@ -722,6 +724,18 @@ $(COMMDIR)/config.obj:     $*.$(SRCSUFF)
 $(CPPFLAGS) /c /Tp $*.$(SRCSUFF) /Fo$@
 <<
 
+$(COMMDIR)/db.obj:     $*.$(SRCSUFF)
+        echo $(CPPFLAGS)
+        cl @<<
+$(CPPFLAGS) /c /Tp $*.$(SRCSUFF) /Fo$@
+<<
+
+$(COMMDIR)/dbtable.obj:     $*.$(SRCSUFF)
+        echo $(CPPFLAGS)
+        cl @<<
+$(CPPFLAGS) /c /Tp $*.$(SRCSUFF) /Fo$@
+<<
+
 $(COMMDIR)/docview.obj:     $*.$(SRCSUFF)
         cl @<<
 $(CPPFLAGS) /c /Tp $*.$(SRCSUFF) /Fo$@