From 108106cfe7af7b582fcaae8f5fe0ac05c545259d Mon Sep 17 00:00:00 2001 From: Julian Smart 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 +#endif + +#define ODBCVER 0x0250 +#include +#include + +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 and // but you can't mix them. Set to 1 for , 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 +#endif //WX_PRECOMP + +IMPLEMENT_APP(DatabaseDemoApp) + +#include // Included strictly for reading the text file with the database parameters + +#include // Required in the file which will get the data source connection +#include // 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 +#include + +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#_!Fkri{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 +#endif //WX_PRECOMP + +#include + +#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 + +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 +#endif +*/ + +#include "wx/wxprec.h" + +#ifdef __BORLANDC__ + #pragma hdrstop +#endif //__BORLANDC__ + +#ifndef WX_PRECOMP + #include +#endif //WX_PRECOMP + +#if USE_ODBC + +#include + +#include +#include +#include + +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 +#endif +*/ + +#include "wx/wxprec.h" + +#ifdef __BORLANDC__ + #pragma hdrstop +#endif //__BORLANDC__ + +#ifndef WX_PRECOMP + #include +#endif //WX_PRECOMP + +#if USE_ODBC + +#include + +#include +#include +#include + +#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 +// # endif +# ifdef __WXLINUX__ +# include +# 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.45.2