From 108106cfe7af7b582fcaae8f5fe0ac05c545259d Mon Sep 17 00:00:00 2001
From: Julian Smart <julian@anthemion.co.uk>
Date: Tue, 28 Jul 1998 09:43:44 +0000
Subject: [PATCH] Added ODBC database classes and sample from RemStar (sample
 needs work for wxWin 2)

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@385 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
---
 include/wx/db.h            |  362 +++++++
 include/wx/dbtable.h       |  165 +++
 include/wx/msw/setup.h     |    7 -
 samples/db/dbtest.cpp      | 2041 ++++++++++++++++++++++++++++++++++++
 samples/db/dbtest.def      |    8 +
 samples/db/dbtest.h        |  286 +++++
 samples/db/dbtest.ico      |  Bin 0 -> 766 bytes
 samples/db/dbtest.rc       |    3 +
 samples/db/listdb.cpp      |  412 ++++++++
 samples/db/listdb.h        |  125 +++
 samples/db/makefile.nt     |   97 ++
 samples/db/makefile.unx    |   71 ++
 samples/forty/playerdg.cpp |    6 +-
 src/common/db.cpp          | 1320 +++++++++++++++++++++++
 src/common/dbtable.cpp     | 1445 +++++++++++++++++++++++++
 src/msw/makefile.nt        |   14 +
 16 files changed, 6352 insertions(+), 10 deletions(-)
 create mode 100644 include/wx/db.h
 create mode 100644 include/wx/dbtable.h
 create mode 100644 samples/db/dbtest.cpp
 create mode 100644 samples/db/dbtest.def
 create mode 100644 samples/db/dbtest.h
 create mode 100644 samples/db/dbtest.ico
 create mode 100644 samples/db/dbtest.rc
 create mode 100644 samples/db/listdb.cpp
 create mode 100644 samples/db/listdb.h
 create mode 100644 samples/db/makefile.nt
 create mode 100644 samples/db/makefile.unx
 create mode 100644 src/common/db.cpp
 create mode 100644 src/common/dbtable.cpp

diff --git a/include/wx/db.h b/include/wx/db.h
new file mode 100644
index 0000000000..9f47abf00b
--- /dev/null
+++ b/include/wx/db.h
@@ -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
index 0000000000..23e6d10f8a
--- /dev/null
+++ b/include/wx/dbtable.h
@@ -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
+
diff --git a/include/wx/msw/setup.h b/include/wx/msw/setup.h
index 634b36658f..2d472aed34 100644
--- a/include/wx/msw/setup.h
+++ b/include/wx/msw/setup.h
@@ -142,13 +142,6 @@
 #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
index 0000000000..165444a0ad
--- /dev/null
+++ b/samples/db/dbtest.cpp
@@ -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
index 0000000000..013825e9e7
--- /dev/null
+++ b/samples/db/dbtest.def
@@ -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
index 0000000000..76ee48eaba
--- /dev/null
+++ b/samples/db/dbtest.h
@@ -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
index 0000000000000000000000000000000000000000..c4dbd3f7847a93a316ad9c909120b7c1d6934de0
GIT binary patch
literal 766
zcmc&yF%H5o47>soBd{=cWJKx*coG{!2g*dH4*d#Z>ENM#KqbDxh*%gv3g?E>gd#C=
zj^j8!CyrYnpoHNRit_-N(=MnXbwv#@;!H619SOjJB(a7wm#C_WRs*IPFh*BYif-xj
z3#kVnH?9Etjf;NVW#_!F<FA2@%O6J3#PKiDGFm$PBP(SC+Viq2`JI0+4<5e|kDuK|
z`8>kri{vgpmO_N_v5jX^ikPHnn)21Pc}+dBNb!1f!xt&rs??|Z?h*O1I1K`JvzDv2
k?|bb|`(LN_0Gw_&vo(*`F5rdhe*VV}{{eky@DJ6ypDptktN;K2

literal 0
HcmV?d00001

diff --git a/samples/db/dbtest.rc b/samples/db/dbtest.rc
new file mode 100644
index 0000000000..31d5205e01
--- /dev/null
+++ b/samples/db/dbtest.rc
@@ -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
index 0000000000..330c79db2a
--- /dev/null
+++ b/samples/db/listdb.cpp
@@ -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
index 0000000000..0d966c961e
--- /dev/null
+++ b/samples/db/listdb.h
@@ -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
index 0000000000..2db0a71c6e
--- /dev/null
+++ b/samples/db/makefile.nt
@@ -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
index 0000000000..3c4142d09b
--- /dev/null
+++ b/samples/db/makefile.unx
@@ -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
+
diff --git a/samples/forty/playerdg.cpp b/samples/forty/playerdg.cpp
index aa671d0adc..d2949f4219 100644
--- a/samples/forty/playerdg.cpp
+++ b/samples/forty/playerdg.cpp
@@ -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
index 0000000000..7d04ee2f3e
--- /dev/null
+++ b/src/common/db.cpp
@@ -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
index 0000000000..32c7895396
--- /dev/null
+++ b/src/common/dbtable.cpp
@@ -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
diff --git a/src/msw/makefile.nt b/src/msw/makefile.nt
index 7419e32be2..2fd2395c13 100644
--- a/src/msw/makefile.nt
+++ b/src/msw/makefile.nt
@@ -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$@
-- 
2.50.0

