1 /////////////////////////////////////////////////////////////////////////////// 
   3 // Purpose:     Implementation of the wxDbTable class. 
   5 // Modified by: George Tasker 
   7 //                      -Dynamic cursor support - Only one predefined cursor, as many others as 
   8 //                          you need may be created on demand 
   9 //                      -Reduced number of active cursors significantly  
  10 //                      -Query-Only wxDbTable objects  
  13 // Copyright:   (c) 1996 Remstar International, Inc. 
  14 // Licence:     wxWindows licence, plus: 
  15 // Notice:      This class library and its intellectual design are free of charge for use, 
  16 //              modification, enhancement, debugging under the following conditions: 
  17 //              1) These classes may only be used as part of the implementation of a 
  18 //                 wxWindows-based application 
  19 //              2) All enhancements and bug fixes are to be submitted back to the wxWindows 
  20 //                 user groups free of all charges for use with the wxWindows library. 
  21 //              3) These classes may not be distributed as part of any other class library, 
  22 //                 DLL, text (written or electronic), other than a complete distribution of 
  23 //                 the wxWindows GUI development toolkit. 
  24 /////////////////////////////////////////////////////////////////////////////// 
  31 // Use this line for wxWindows v1.x 
  33 // Use this line for wxWindows v2.x 
  34 #include  "wx/wxprec.h" 
  35 #include "wx/version.h" 
  37 #if wxMAJOR_VERSION == 2 
  39 #   pragma implementation "dbtable.h" 
  43 #ifdef DBDEBUG_CONSOLE 
  44     #include "wx/ioswrap.h" 
  52 #if wxMAJOR_VERSION == 2 
  54         #include "wx/string.h" 
  55         #include "wx/object.h" 
  58         #include "wx/msgdlg.h" 
  61     #include "wx/filefn.h" 
  64 #if wxMAJOR_VERSION == 1 
  65 #   if defined(wx_msw) || defined(wx_x) 
  83 #if   wxMAJOR_VERSION == 1 
  85 #elif wxMAJOR_VERSION == 2 
  86     #include "wx/dbtable.h" 
  90 // The HPUX preprocessor lines below were commented out on 8/20/97 
  91 // because macros.h currently redefines DEBUG and is unneeded. 
  93 // #    include <macros.h> 
  96 #    include <sys/minmax.h> 
 100 ULONG lastTableID 
= 0; 
 108 /********** wxDbTable::wxDbTable() **********/ 
 109 wxDbTable::wxDbTable(wxDb 
*pwxDb
, const char *tblName
, const int nCols
, 
 110                     const char *qryTblName
, bool qryOnly
, const char *tblPath
) 
 112     pDb                 
= pwxDb
;                    // Pointer to the wxDb object 
 116     hstmtDefault        
= 0;                        // Initialized below 
 117     hstmtCount          
= 0;                        // Initialized first time it is needed 
 124     noCols              
= nCols
;                    // No. of cols in the table 
 125     where               
= "";                       // Where clause 
 126     orderBy             
= "";                       // Order By clause 
 127     from                
= "";                       // From clause 
 128     selectForUpdate     
= FALSE
;                    // SELECT ... FOR UPDATE; Indicates whether to include the FOR UPDATE phrase 
 134     wxStrcpy(tableName
, tblName
);               // Table Name 
 136         wxStrcpy(tablePath
, tblPath
);           // Table Path - used for dBase files 
 138     if (qryTblName
)                             // Name of the table/view to query 
 139         wxStrcpy(queryTableName
, qryTblName
); 
 141         wxStrcpy(queryTableName
, tblName
); 
 146     pDb
->incrementTableCount(); 
 149     tableID 
= ++lastTableID
; 
 150     s
.sprintf("wxDbTable constructor (%-20s) tableID:[%6lu] pDb:[%p]", tblName
,tableID
,pDb
); 
 153     wxTablesInUse 
*tableInUse
; 
 154     tableInUse            
= new wxTablesInUse(); 
 155     tableInUse
->tableName 
= tblName
; 
 156     tableInUse
->tableID   
= tableID
; 
 157     tableInUse
->pDb       
= pDb
; 
 158     TablesInUse
.Append(tableInUse
); 
 161     pDb
->WriteSqlLog(s
.c_str()); 
 163     // Grab the HENV and HDBC from the wxDb object 
 164     henv 
= pDb
->GetHENV(); 
 165     hdbc 
= pDb
->GetHDBC(); 
 167     // Allocate space for column definitions 
 169         colDefs 
= new wxDbColDef
[noCols
];  // Points to the first column defintion 
 171     // Allocate statement handles for the table 
 174         // Allocate a separate statement handle for performing inserts 
 175         if (SQLAllocStmt(hdbc
, &hstmtInsert
) != SQL_SUCCESS
) 
 176             pDb
->DispAllErrors(henv
, hdbc
); 
 177         // Allocate a separate statement handle for performing deletes 
 178         if (SQLAllocStmt(hdbc
, &hstmtDelete
) != SQL_SUCCESS
) 
 179             pDb
->DispAllErrors(henv
, hdbc
); 
 180         // Allocate a separate statement handle for performing updates 
 181         if (SQLAllocStmt(hdbc
, &hstmtUpdate
) != SQL_SUCCESS
) 
 182             pDb
->DispAllErrors(henv
, hdbc
); 
 184     // Allocate a separate statement handle for internal use 
 185     if (SQLAllocStmt(hdbc
, &hstmtInternal
) != SQL_SUCCESS
) 
 186         pDb
->DispAllErrors(henv
, hdbc
); 
 188     // Set the cursor type for the statement handles 
 189     cursorType 
= SQL_CURSOR_STATIC
; 
 191     if (SQLSetStmtOption(hstmtInternal
, SQL_CURSOR_TYPE
, cursorType
) != SQL_SUCCESS
) 
 193         // Check to see if cursor type is supported 
 194         pDb
->GetNextError(henv
, hdbc
, hstmtInternal
); 
 195         if (! wxStrcmp(pDb
->sqlState
, "01S02"))  // Option Value Changed 
 198             // Datasource does not support static cursors.  Driver 
 199             // will substitute a cursor type.  Call SQLGetStmtOption() 
 200             // to determine which cursor type was selected. 
 201             if (SQLGetStmtOption(hstmtInternal
, SQL_CURSOR_TYPE
, &cursorType
) != SQL_SUCCESS
) 
 202               pDb
->DispAllErrors(henv
, hdbc
, hstmtInternal
); 
 203 #ifdef DBDEBUG_CONSOLE 
 204             cout 
<< "Static cursor changed to: "; 
 207               case SQL_CURSOR_FORWARD_ONLY
: 
 208                 cout 
<< "Forward Only";     break; 
 209               case SQL_CURSOR_STATIC
: 
 210                 cout 
<< "Static";           break; 
 211               case SQL_CURSOR_KEYSET_DRIVEN
: 
 212                 cout 
<< "Keyset Driven";    break; 
 213               case SQL_CURSOR_DYNAMIC
: 
 214                 cout 
<< "Dynamic";          break; 
 216             cout 
<< endl 
<< endl
; 
 220             if (pDb
->FwdOnlyCursors() && cursorType 
!= SQL_CURSOR_FORWARD_ONLY
) 
 222                 // Force the use of a forward only cursor... 
 223                 cursorType 
= SQL_CURSOR_FORWARD_ONLY
; 
 224                 if (SQLSetStmtOption(hstmtInternal
, SQL_CURSOR_TYPE
, cursorType
) != SQL_SUCCESS
) 
 226                     // Should never happen 
 227                     pDb
->GetNextError(henv
, hdbc
, hstmtInternal
); 
 234             pDb
->DispNextError(); 
 235             pDb
->DispAllErrors(henv
, hdbc
, hstmtInternal
); 
 238 #ifdef DBDEBUG_CONSOLE 
 240         cout 
<< "Cursor Type set to STATIC" << endl 
<< endl
; 
 245         // Set the cursor type for the INSERT statement handle 
 246         if (SQLSetStmtOption(hstmtInsert
, SQL_CURSOR_TYPE
, SQL_CURSOR_FORWARD_ONLY
) != SQL_SUCCESS
) 
 247             pDb
->DispAllErrors(henv
, hdbc
, hstmtInsert
); 
 248         // Set the cursor type for the DELETE statement handle 
 249         if (SQLSetStmtOption(hstmtDelete
, SQL_CURSOR_TYPE
, SQL_CURSOR_FORWARD_ONLY
) != SQL_SUCCESS
) 
 250             pDb
->DispAllErrors(henv
, hdbc
, hstmtDelete
); 
 251         // Set the cursor type for the UPDATE statement handle 
 252         if (SQLSetStmtOption(hstmtUpdate
, SQL_CURSOR_TYPE
, SQL_CURSOR_FORWARD_ONLY
) != SQL_SUCCESS
) 
 253             pDb
->DispAllErrors(henv
, hdbc
, hstmtUpdate
); 
 256     // Make the default cursor the active cursor 
 257     hstmtDefault 
= GetNewCursor(FALSE
,FALSE
); 
 258     assert(hstmtDefault
); 
 259     hstmt 
= *hstmtDefault
; 
 261 }  // wxDbTable::wxDbTable() 
 264 /********** wxDbTable::~wxDbTable() **********/ 
 265 wxDbTable::~wxDbTable() 
 270         s
.sprintf("wxDbTable destructor (%-20s) tableID:[%6lu] pDb:[%p]", tableName
,tableID
,pDb
); 
 271         pDb
->WriteSqlLog(s
.c_str()); 
 277         TablesInUse
.DeleteContents(TRUE
); 
 281         pNode 
= TablesInUse
.First(); 
 282         while (pNode 
&& !found
) 
 284             if (((wxTablesInUse 
*)pNode
->Data())->tableID 
== tableID
) 
 287                 if (!TablesInUse
.DeleteNode(pNode
)) 
 288                     wxLogDebug (s
.c_str(),wxT("Unable to delete node!")); 
 291                 pNode 
= pNode
->Next(); 
 296             msg
.sprintf(wxT("Unable to find the tableID in the linked\nlist of tables in use.\n\n%s"),s
.c_str()); 
 297             wxLogDebug (msg
.c_str(),wxT("NOTICE...")); 
 302     // Decrement the wxDb table count 
 304         pDb
->decrementTableCount(); 
 306     // Delete memory allocated for column definitions 
 310     // Free statement handles 
 314             if (SQLFreeStmt(hstmtInsert
, SQL_DROP
) != SQL_SUCCESS
) 
 315                 pDb
->DispAllErrors(henv
, hdbc
); 
 318             if (SQLFreeStmt(hstmtDelete
, SQL_DROP
) != SQL_SUCCESS
) 
 321             if (SQLFreeStmt(hstmtUpdate
, SQL_DROP
) != SQL_SUCCESS
) 
 322                 pDb
->DispAllErrors(henv
, hdbc
); 
 326         if (SQLFreeStmt(hstmtInternal
, SQL_DROP
) != SQL_SUCCESS
) 
 327             pDb
->DispAllErrors(henv
, hdbc
); 
 329     // Delete dynamically allocated cursors 
 331         DeleteCursor(hstmtDefault
); 
 334         DeleteCursor(hstmtCount
); 
 337 }  // wxDbTable::~wxDbTable() 
 341 /***************************** PRIVATE FUNCTIONS *****************************/ 
 345 /********** wxDbTable::bindInsertParams() **********/ 
 346 bool wxDbTable::bindInsertParams(void) 
 353     UDWORD  precision   
= 0; 
 356     // Bind each column (that can be inserted) of the table to a parameter marker 
 358     for (i 
= 0, colNo 
= 1; i 
< noCols
; i
++) 
 360         if (! colDefs
[i
].InsertAllowed
) 
 362         switch(colDefs
[i
].DbDataType
) 
 365         case DB_DATA_TYPE_VARCHAR
: 
 366             fSqlType 
= pDb
->GetTypeInfVarchar().FsqlType
;           
 367             precision 
= colDefs
[i
].SzDataObj
; 
 369             colDefs
[i
].CbValue 
= SQL_NTS
; 
 371         case DB_DATA_TYPE_INTEGER
: 
 372             fSqlType 
= pDb
->GetTypeInfInteger().FsqlType
; 
 373             precision 
= pDb
->GetTypeInfInteger().Precision
; 
 375             colDefs
[i
].CbValue 
= 0; 
 377         case DB_DATA_TYPE_FLOAT
: 
 378             fSqlType 
= pDb
->GetTypeInfFloat().FsqlType
; 
 379             precision 
= pDb
->GetTypeInfFloat().Precision
; 
 380             scale 
= pDb
->GetTypeInfFloat().MaximumScale
; 
 381             // SQL Sybase Anywhere v5.5 returned a negative number for the 
 382             // MaxScale.  This caused ODBC to kick out an error on ibscale. 
 383             // I check for this here and set the scale = precision. 
 385             //  scale = (short) precision; 
 386             colDefs
[i
].CbValue 
= 0; 
 388         case DB_DATA_TYPE_DATE
: 
 389             fSqlType 
= pDb
->GetTypeInfDate().FsqlType
; 
 390             precision 
= pDb
->GetTypeInfDate().Precision
; 
 392             colDefs
[i
].CbValue 
= 0; 
 398             colDefs
[i
].CbValue 
= SQL_NULL_DATA
; 
 399             colDefs
[i
].Null 
= FALSE
; 
 402         if (SQLBindParameter(hstmtInsert
, colNo
++, SQL_PARAM_INPUT
, colDefs
[i
].SqlCtype
, 
 403                              fSqlType
, precision
, scale
, (UCHAR
*) colDefs
[i
].PtrDataObj
,  
 404                              precision
+1,&colDefs
[i
].CbValue
) != SQL_SUCCESS
) 
 405           return(pDb
->DispAllErrors(henv
, hdbc
, hstmtInsert
)); 
 408     // Completed successfully 
 411 }  // wxDbTable::bindInsertParams() 
 414 /********** wxDbTable::bindUpdateParams() **********/ 
 415 bool wxDbTable::bindUpdateParams(void) 
 422     UDWORD  precision   
= 0; 
 425     // Bind each UPDATEABLE column of the table to a parameter marker 
 427     for (i 
= 0, colNo 
= 1; i 
< noCols
; i
++) 
 429         if (! colDefs
[i
].Updateable
) 
 431         switch(colDefs
[i
].DbDataType
) 
 433         case DB_DATA_TYPE_VARCHAR
: 
 434             fSqlType 
= pDb
->GetTypeInfVarchar().FsqlType
; 
 435             precision 
= colDefs
[i
].SzDataObj
; 
 437             colDefs
[i
].CbValue 
= SQL_NTS
; 
 439         case DB_DATA_TYPE_INTEGER
: 
 440             fSqlType 
= pDb
->GetTypeInfInteger().FsqlType
; 
 441             precision 
= pDb
->GetTypeInfInteger().Precision
; 
 443             colDefs
[i
].CbValue 
= 0; 
 445         case DB_DATA_TYPE_FLOAT
: 
 446             fSqlType 
= pDb
->GetTypeInfFloat().FsqlType
; 
 447             precision 
= pDb
->GetTypeInfFloat().Precision
; 
 448             scale 
= pDb
->GetTypeInfFloat().MaximumScale
;            
 449             // SQL Sybase Anywhere v5.5 returned a negative number for the 
 450             // MaxScale.  This caused ODBC to kick out an error on ibscale. 
 451             // I check for this here and set the scale = precision. 
 453             // scale = (short) precision; 
 454             colDefs
[i
].CbValue 
= 0; 
 456         case DB_DATA_TYPE_DATE
: 
 457             fSqlType 
= pDb
->GetTypeInfDate().FsqlType
; 
 458             precision 
= pDb
->GetTypeInfDate().Precision
; 
 460             colDefs
[i
].CbValue 
= 0; 
 464         if (SQLBindParameter(hstmtUpdate
, colNo
++, SQL_PARAM_INPUT
, colDefs
[i
].SqlCtype
, 
 465                              fSqlType
, precision
, scale
, (UCHAR
*) colDefs
[i
].PtrDataObj
,  
 466                              precision
+1, &colDefs
[i
].CbValue
) != SQL_SUCCESS
) 
 467           return(pDb
->DispAllErrors(henv
, hdbc
, hstmtUpdate
)); 
 470     // Completed successfully 
 473 }  // wxDbTable::bindUpdateParams() 
 476 /********** wxDbTable::bindCols() **********/ 
 477 bool wxDbTable::bindCols(HSTMT cursor
) 
 481     // Bind each column of the table to a memory address for fetching data 
 483     for (i 
= 0; i 
< noCols
; i
++) 
 485         if (SQLBindCol(cursor
, i
+1, colDefs
[i
].SqlCtype
, (UCHAR
*) colDefs
[i
].PtrDataObj
, 
 486                        colDefs
[i
].SzDataObj
, &cb
) != SQL_SUCCESS
)      
 487           return (pDb
->DispAllErrors(henv
, hdbc
, cursor
)); 
 490     // Completed successfully 
 493 }  // wxDbTable::bindCols() 
 496 /********** wxDbTable::getRec() **********/ 
 497 bool wxDbTable::getRec(UWORD fetchType
) 
 501     if (!pDb
->FwdOnlyCursors()) 
 503         // Fetch the NEXT, PREV, FIRST or LAST record, depending on fetchType 
 507         retcode 
= SQLExtendedFetch(hstmt
, fetchType
, 0, &cRowsFetched
, &rowStatus
); 
 508         if (retcode  
!= SQL_SUCCESS 
&& retcode 
!= SQL_SUCCESS_WITH_INFO
) 
 509             if (retcode 
== SQL_NO_DATA_FOUND
) 
 512                 return(pDb
->DispAllErrors(henv
, hdbc
, hstmt
)); 
 516         // Fetch the next record from the record set 
 517         retcode 
= SQLFetch(hstmt
); 
 518         if (retcode 
!= SQL_SUCCESS 
&& retcode 
!= SQL_SUCCESS_WITH_INFO
) 
 520             if (retcode 
== SQL_NO_DATA_FOUND
) 
 523                 return(pDb
->DispAllErrors(henv
, hdbc
, hstmt
)); 
 527     // Completed successfully 
 530 }  // wxDbTable::getRec() 
 533 /********** wxDbTable::execDelete() **********/ 
 534 bool wxDbTable::execDelete(const char *pSqlStmt
) 
 536     // Execute the DELETE statement 
 537     if (SQLExecDirect(hstmtDelete
, (UCHAR FAR 
*) pSqlStmt
, SQL_NTS
) != SQL_SUCCESS
) 
 538         return(pDb
->DispAllErrors(henv
, hdbc
, hstmtDelete
)); 
 540     // Record deleted successfully 
 543 }  // wxDbTable::execDelete() 
 546 /********** wxDbTable::execUpdate() **********/ 
 547 bool wxDbTable::execUpdate(const char *pSqlStmt
) 
 549     // Execute the UPDATE statement 
 550     if (SQLExecDirect(hstmtUpdate
, (UCHAR FAR 
*) pSqlStmt
, SQL_NTS
) != SQL_SUCCESS
) 
 551         return(pDb
->DispAllErrors(henv
, hdbc
, hstmtUpdate
)); 
 553     // Record deleted successfully 
 556 }  // wxDbTable::execUpdate() 
 559 /********** wxDbTable::query() **********/ 
 560 bool wxDbTable::query(int queryType
, bool forUpdate
, bool distinct
, const char *pSqlStmt
) 
 565 /*  SQLFreeStmt(hstmt, SQL_CLOSE); 
 566   if (SQLExecDirect(hstmt, (UCHAR FAR *) pSqlStmt, SQL_NTS) == SQL_SUCCESS) 
 570       pDb->DispAllErrors(henv, hdbc, hstmt); 
 576     char sqlStmt
[DB_MAX_STATEMENT_LEN
]; 
 578     // Set the selectForUpdate member variable 
 580         // The user may wish to select for update, but the DBMS may not be capable 
 581         selectForUpdate 
= CanSelectForUpdate(); 
 583         selectForUpdate 
= FALSE
; 
 585     // Set the SQL SELECT string 
 586     if (queryType 
!= DB_SELECT_STATEMENT
)               // A select statement was not passed in, 
 587     {                                                               // so generate a select statement. 
 588         BuildSelectStmt(sqlStmt
, queryType
, distinct
); 
 589         pDb
->WriteSqlLog(sqlStmt
); 
 590     } else wxStrcpy(sqlStmt
, pSqlStmt
); 
 592     SQLFreeStmt(hstmt
, SQL_CLOSE
); 
 593     if (SQLExecDirect(hstmt
, (UCHAR FAR 
*) sqlStmt
, SQL_NTS
) == SQL_SUCCESS
) 
 597         pDb
->DispAllErrors(henv
, hdbc
, hstmt
); 
 601     // Make sure the cursor is closed first 
 602     if (! CloseCursor(hstmt
)) 
 605     // Execute the SQL SELECT statement 
 607     retcode 
= SQLExecDirect(hstmt
, (UCHAR FAR 
*) (queryType 
== DB_SELECT_STATEMENT 
? pSqlStmt 
: sqlStmt
), SQL_NTS
);       
 608     if (retcode 
!= SQL_SUCCESS 
&& retcode 
!= SQL_SUCCESS_WITH_INFO
) 
 609       return(pDb
->DispAllErrors(henv
, hdbc
, hstmt
)); 
 611     // Completed successfully 
 614 }  // wxDbTable::query() 
 617 /***************************** PUBLIC FUNCTIONS *****************************/ 
 620 /********** wxDbTable::Open() **********/ 
 621 bool wxDbTable::Open(void) 
 628     // Verify that the table exists in the database 
 629     if (!pDb
->TableExists(tableName
,pDb
->GetUsername(),tablePath
)) 
 632         if (wxStrcmp(tablePath
,"")) 
 633             s
.sprintf(wxT("Error opening '%s/%s'.\n"),tablePath
,tableName
); 
 635             s
.sprintf(wxT("Error opening '%s'.\n"), tableName
); 
 636         if (!pDb
->TableExists(tableName
,NULL
,tablePath
)) 
 637             s 
+= wxT("Table/view does not exist in the database.\n"); 
 639             s 
+= wxT("Current logged in user does not have sufficient privileges to access this table.\n"); 
 640         pDb
->LogError(s
.c_str()); 
 644     // Bind the member variables for field exchange between 
 645     // the wxDbTable object and the ODBC record. 
 649         if (!bindInsertParams())                    // Inserts 
 652         if (!bindUpdateParams())                    // Updates 
 656     if (!bindCols(*hstmtDefault
))                   // Selects 
 659     if (!bindCols(hstmtInternal
))                   // Internal use only 
 663      * Do NOT bind the hstmtCount cursor!!! 
 666     // Build an insert statement using parameter markers 
 667     if (!queryOnly 
&& noCols 
> 0) 
 669         bool needComma 
= FALSE
; 
 670         sqlStmt
.sprintf("INSERT INTO %s (", tableName
); 
 671         for (i 
= 0; i 
< noCols
; i
++) 
 673             if (! colDefs
[i
].InsertAllowed
) 
 677             sqlStmt 
+= colDefs
[i
].ColName
; 
 681         sqlStmt 
+= ") VALUES ("; 
 683                   int insertableCount 
= 0; 
 685         for (i 
= 0; i 
< noCols
; i
++) 
 687             if (! colDefs
[i
].InsertAllowed
) 
 697 //      pDb->WriteSqlLog(sqlStmt); 
 699         // Prepare the insert statement for execution 
 702             if (SQLPrepare(hstmtInsert
, (UCHAR FAR 
*) sqlStmt
.c_str(), SQL_NTS
) != SQL_SUCCESS
) 
 703                 return(pDb
->DispAllErrors(henv
, hdbc
, hstmtInsert
)); 
 709     // Completed successfully 
 712 }  // wxDbTable::Open() 
 715 /********** wxDbTable::Query() **********/ 
 716 bool wxDbTable::Query(bool forUpdate
, bool distinct
) 
 719     return(query(DB_SELECT_WHERE
, forUpdate
, distinct
)); 
 721 }  // wxDbTable::Query() 
 724 /********** wxDbTable::QueryBySqlStmt() **********/ 
 725 bool wxDbTable::QueryBySqlStmt(const char *pSqlStmt
) 
 727     pDb
->WriteSqlLog(pSqlStmt
); 
 729     return(query(DB_SELECT_STATEMENT
, FALSE
, FALSE
, pSqlStmt
)); 
 731 }  // wxDbTable::QueryBySqlStmt() 
 734 /********** wxDbTable::QueryMatching() **********/ 
 735 bool wxDbTable::QueryMatching(bool forUpdate
, bool distinct
) 
 738     return(query(DB_SELECT_MATCHING
, forUpdate
, distinct
)); 
 740 }  // wxDbTable::QueryMatching() 
 743 /********** wxDbTable::QueryOnKeyFields() **********/ 
 744 bool wxDbTable::QueryOnKeyFields(bool forUpdate
, bool distinct
) 
 747     return(query(DB_SELECT_KEYFIELDS
, forUpdate
, distinct
)); 
 749 }  // wxDbTable::QueryOnKeyFields() 
 752 /********** wxDbTable::GetPrev() **********/ 
 753 bool wxDbTable::GetPrev(void) 
 755     if (pDb
->FwdOnlyCursors()) 
 757         wxFAIL_MSG(wxT("GetPrev()::Backward scrolling cursors are not enabled for this instance of wxDbTable")); 
 761         return(getRec(SQL_FETCH_PRIOR
)); 
 762 }  // wxDbTable::GetPrev() 
 765 /********** wxDbTable::operator-- **********/ 
 766 bool wxDbTable::operator--(int) 
 768     if (pDb
->FwdOnlyCursors()) 
 770         wxFAIL_MSG(wxT("operator--:Backward scrolling cursors are not enabled for this instance of wxDbTable")); 
 774         return(getRec(SQL_FETCH_PRIOR
)); 
 775 }  // wxDbTable::operator-- 
 778 /********** wxDbTable::GetFirst() **********/ 
 779 bool wxDbTable::GetFirst(void) 
 781     if (pDb
->FwdOnlyCursors()) 
 783         wxFAIL_MSG(wxT("GetFirst():Backward scrolling cursors are not enabled for this instance of wxDbTable")); 
 787         return(getRec(SQL_FETCH_FIRST
)); 
 788 }  // wxDbTable::GetFirst() 
 791 /********** wxDbTable::GetLast() **********/ 
 792 bool wxDbTable::GetLast(void) 
 794     if (pDb
->FwdOnlyCursors()) 
 796         wxFAIL_MSG(wxT("GetLast()::Backward scrolling cursors are not enabled for this instance of wxDbTable")); 
 800         return(getRec(SQL_FETCH_LAST
)); 
 801 }  // wxDbTable::GetLast() 
 804 /********** wxDbTable::BuildSelectStmt() **********/ 
 805 void wxDbTable::BuildSelectStmt(char *pSqlStmt
, int typeOfSelect
, bool distinct
) 
 807     char whereClause
[DB_MAX_WHERE_CLAUSE_LEN
]; 
 811     // Build a select statement to query the database 
 812     wxStrcpy(pSqlStmt
, "SELECT "); 
 814     // SELECT DISTINCT values only? 
 816         wxStrcat(pSqlStmt
, "DISTINCT "); 
 818     // Was a FROM clause specified to join tables to the base table? 
 819     // Available for ::Query() only!!! 
 820     bool appendFromClause 
= FALSE
; 
 821 #if wxODBC_BACKWARD_COMPATABILITY 
 822     if (typeOfSelect 
== DB_SELECT_WHERE 
&& from 
&& wxStrlen(from
)) 
 823         appendFromClause 
= TRUE
; 
 825     if (typeOfSelect 
== DB_SELECT_WHERE 
&& from
.Length()) 
 826         appendFromClause 
= TRUE
; 
 829     // Add the column list 
 831     for (i 
= 0; i 
< noCols
; i
++) 
 833         // If joining tables, the base table column names must be qualified to avoid ambiguity 
 834         if (appendFromClause
) 
 836             wxStrcat(pSqlStmt
, queryTableName
); 
 837             wxStrcat(pSqlStmt
, "."); 
 839         wxStrcat(pSqlStmt
, colDefs
[i
].ColName
); 
 841             wxStrcat(pSqlStmt
, ","); 
 844     // If the datasource supports ROWID, get this column as well.  Exception: Don't retrieve 
 845     // the ROWID if querying distinct records.  The rowid will always be unique. 
 846     if (!distinct 
&& CanUpdByROWID()) 
 848         // If joining tables, the base table column names must be qualified to avoid ambiguity 
 849         if (appendFromClause
) 
 851             wxStrcat(pSqlStmt
, ","); 
 852             wxStrcat(pSqlStmt
, queryTableName
); 
 853             wxStrcat(pSqlStmt
, ".ROWID"); 
 856             wxStrcat(pSqlStmt
, ",ROWID"); 
 859     // Append the FROM tablename portion 
 860     wxStrcat(pSqlStmt
, " FROM "); 
 861     wxStrcat(pSqlStmt
, queryTableName
); 
 863     // Sybase uses the HOLDLOCK keyword to lock a record during query. 
 864     // The HOLDLOCK keyword follows the table name in the from clause. 
 865     // Each table in the from clause must specify HOLDLOCK or 
 866     // NOHOLDLOCK (the default).  Note: The "FOR UPDATE" clause 
 867     // is parsed but ignored in SYBASE Transact-SQL. 
 868     if (selectForUpdate 
&& (pDb
->Dbms() == dbmsSYBASE_ASA 
|| pDb
->Dbms() == dbmsSYBASE_ASE
)) 
 869         wxStrcat(pSqlStmt
, " HOLDLOCK"); 
 871     if (appendFromClause
) 
 872         wxStrcat(pSqlStmt
, from
); 
 874     // Append the WHERE clause.  Either append the where clause for the class 
 875     // or build a where clause.  The typeOfSelect determines this. 
 878     case DB_SELECT_WHERE
: 
 879 #if wxODBC_BACKWARD_COMPATABILITY 
 880         if (where 
&& wxStrlen(where
))   // May not want a where clause!!! 
 882         if (where
.Length())   // May not want a where clause!!! 
 885             wxStrcat(pSqlStmt
, " WHERE "); 
 886             wxStrcat(pSqlStmt
, where
); 
 889     case DB_SELECT_KEYFIELDS
: 
 890         BuildWhereClause(whereClause
, DB_WHERE_KEYFIELDS
); 
 891         if (wxStrlen(whereClause
)) 
 893             wxStrcat(pSqlStmt
, " WHERE "); 
 894             wxStrcat(pSqlStmt
, whereClause
); 
 897     case DB_SELECT_MATCHING
: 
 898         BuildWhereClause(whereClause
, DB_WHERE_MATCHING
); 
 899         if (wxStrlen(whereClause
)) 
 901             wxStrcat(pSqlStmt
, " WHERE "); 
 902             wxStrcat(pSqlStmt
, whereClause
); 
 907     // Append the ORDER BY clause 
 908 #if wxODBC_BACKWARD_COMPATABILITY 
 909     if (orderBy 
&& wxStrlen(orderBy
)) 
 911          if (orderBy
.Length()) 
 914         wxStrcat(pSqlStmt
, " ORDER BY "); 
 915         wxStrcat(pSqlStmt
, orderBy
); 
 918     // SELECT FOR UPDATE if told to do so and the datasource is capable.  Sybase 
 919     // parses the FOR UPDATE clause but ignores it.  See the comment above on the 
 920     // HOLDLOCK for Sybase. 
 921     if (selectForUpdate 
&& CanSelectForUpdate()) 
 922         wxStrcat(pSqlStmt
, " FOR UPDATE"); 
 924 }  // wxDbTable::BuildSelectStmt() 
 927 /********** wxDbTable::GetRowNum() **********/ 
 928 UWORD 
wxDbTable::GetRowNum(void) 
 932     if (SQLGetStmtOption(hstmt
, SQL_ROW_NUMBER
, (UCHAR
*) &rowNum
) != SQL_SUCCESS
) 
 934         pDb
->DispAllErrors(henv
, hdbc
, hstmt
); 
 938     // Completed successfully 
 939     return((UWORD
) rowNum
); 
 941 }  // wxDbTable::GetRowNum() 
 944 /********** wxDbTable::CloseCursor() **********/ 
 945 bool wxDbTable::CloseCursor(HSTMT cursor
) 
 947     if (SQLFreeStmt(cursor
, SQL_CLOSE
) != SQL_SUCCESS
) 
 948         return(pDb
->DispAllErrors(henv
, hdbc
, cursor
)); 
 950     // Completed successfully 
 953 }  // wxDbTable::CloseCursor() 
 956 /********** wxDbTable::CreateTable() **********/ 
 957 bool wxDbTable::CreateTable(bool attemptDrop
) 
 963 //    char sqlStmt[DB_MAX_STATEMENT_LEN]; 
 966 #ifdef DBDEBUG_CONSOLE 
 967     cout 
<< "Creating Table " << tableName 
<< "..." << endl
; 
 971     if (attemptDrop 
&& !DropTable()) 
 975 #ifdef DBDEBUG_CONSOLE 
 976     for (i 
= 0; i 
< noCols
; i
++) 
 978         // Exclude derived columns since they are NOT part of the base table 
 979         if (colDefs
[i
].DerivedCol
) 
 981         cout 
<< i 
+ 1 << ": " << colDefs
[i
].ColName 
<< "; "; 
 982         switch(colDefs
[i
].DbDataType
) 
 984             case DB_DATA_TYPE_VARCHAR
: 
 985                 cout 
<< pDb
->typeInfVarchar
.TypeName 
<< "(" << colDefs
[i
].SzDataObj 
<< ")"; 
 987             case DB_DATA_TYPE_INTEGER
: 
 988                 cout 
<< pDb
->typeInfInteger
.TypeName
; 
 990             case DB_DATA_TYPE_FLOAT
: 
 991                 cout 
<< pDb
->typeInfFloat
.TypeName
; 
 993             case DB_DATA_TYPE_DATE
: 
 994                 cout 
<< pDb
->typeInfDate
.TypeName
; 
1001     // Build a CREATE TABLE string from the colDefs structure. 
1002     bool needComma 
= FALSE
; 
1003     sqlStmt
.sprintf("CREATE TABLE %s (", tableName
); 
1005     for (i 
= 0; i 
< noCols
; i
++) 
1007         // Exclude derived columns since they are NOT part of the base table 
1008         if (colDefs
[i
].DerivedCol
) 
1014         sqlStmt 
+= colDefs
[i
].ColName
; 
1017         switch(colDefs
[i
].DbDataType
) 
1019             case DB_DATA_TYPE_VARCHAR
: 
1020                 sqlStmt 
+= pDb
->GetTypeInfVarchar().TypeName
; break; 
1021             case DB_DATA_TYPE_INTEGER
: 
1022                 sqlStmt 
+= pDb
->GetTypeInfInteger().TypeName
; break; 
1023             case DB_DATA_TYPE_FLOAT
: 
1024                 sqlStmt 
+= pDb
->GetTypeInfFloat().TypeName
; break; 
1025             case DB_DATA_TYPE_DATE
: 
1026                 sqlStmt 
+= pDb
->GetTypeInfDate().TypeName
; break; 
1028         // For varchars, append the size of the string 
1029         if (colDefs
[i
].DbDataType 
== DB_DATA_TYPE_VARCHAR
) 
1032             // wxStrcat(sqlStmt, "("); 
1033             // wxStrcat(sqlStmt, itoa(colDefs[i].SzDataObj, s, 10)); 
1034             // wxStrcat(sqlStmt, ")"); 
1035             s
.sprintf("(%d)", colDefs
[i
].SzDataObj
); 
1036             sqlStmt 
+= s
.c_str(); 
1039         if (pDb
->Dbms() == dbmsSYBASE_ASE 
|| pDb
->Dbms() == dbmsMY_SQL
) 
1041             if (colDefs
[i
].KeyField
) 
1043                 sqlStmt 
+= " NOT NULL"; 
1049     // If there is a primary key defined, include it in the create statement 
1050     for (i 
= j 
= 0; i 
< noCols
; i
++) 
1052         if (colDefs
[i
].KeyField
) 
1058     if (j 
&& pDb
->Dbms() != dbmsDBASE
)  // Found a keyfield 
1060         if (pDb
->Dbms() != dbmsMY_SQL
) 
1062             sqlStmt 
+= ",CONSTRAINT "; 
1063             sqlStmt 
+= tableName
; 
1064             sqlStmt 
+= "_PIDX PRIMARY KEY ("; 
1068             /* MySQL goes out on this one. We also declare the relevant key NON NULL above */ 
1069             sqlStmt 
+= ", PRIMARY KEY ("; 
1072         // List column name(s) of column(s) comprising the primary key 
1073         for (i 
= j 
= 0; i 
< noCols
; i
++) 
1075             if (colDefs
[i
].KeyField
) 
1077                 if (j
++) // Multi part key, comma separate names 
1079                 sqlStmt 
+= colDefs
[i
].ColName
; 
1084     // Append the closing parentheses for the create table statement 
1087     pDb
->WriteSqlLog(sqlStmt
.c_str()); 
1089 #ifdef DBDEBUG_CONSOLE 
1090     cout 
<< endl 
<< sqlStmt
.c_str() << endl
; 
1093     // Execute the CREATE TABLE statement 
1094     RETCODE retcode 
= SQLExecDirect(hstmt
, (UCHAR FAR 
*) sqlStmt
.c_str(), SQL_NTS
); 
1095     if (retcode 
!= SQL_SUCCESS 
&& retcode 
!= SQL_SUCCESS_WITH_INFO
) 
1097         pDb
->DispAllErrors(henv
, hdbc
, hstmt
); 
1098         pDb
->RollbackTrans(); 
1103     // Commit the transaction and close the cursor 
1104     if (! pDb
->CommitTrans()) 
1106     if (! CloseCursor(hstmt
)) 
1109     // Database table created successfully 
1112 } // wxDbTable::CreateTable() 
1115 /********** wxDbTable::DropTable() **********/ 
1116 bool wxDbTable::DropTable() 
1118     // NOTE: This function returns TRUE if the Table does not exist, but 
1119     //       only for identified databases.  Code will need to be added 
1120     //       below for any other databases when those databases are defined 
1121     //       to handle this situation consistently 
1125     sqlStmt
.sprintf("DROP TABLE %s", tableName
); 
1127     pDb
->WriteSqlLog(sqlStmt
.c_str()); 
1129 #ifdef DBDEBUG_CONSOLE 
1130     cout 
<< endl 
<< sqlStmt
.c_str() << endl
; 
1133     if (SQLExecDirect(hstmt
, (UCHAR FAR 
*) sqlStmt
.c_str(), SQL_NTS
) != SQL_SUCCESS
) 
1135         // Check for "Base table not found" error and ignore 
1136         pDb
->GetNextError(henv
, hdbc
, hstmt
);    
1137         if (wxStrcmp(pDb
->sqlState
,"S0002") && wxStrcmp(pDb
->sqlState
, "S1000"))  // "Base table not found"  
1139             // Check for product specific error codes 
1140             if (!((pDb
->Dbms() == dbmsSYBASE_ASA    
&& !wxStrcmp(pDb
->sqlState
,"42000"))   ||  // 5.x (and lower?) 
1141                   (pDb
->Dbms() == dbmsSYBASE_ASE    
&& !wxStrcmp(pDb
->sqlState
,"37000"))   ||    
1142                   (pDb
->Dbms() == dbmsPOSTGRES      
&& !wxStrcmp(pDb
->sqlState
,"08S01"))))      
1144                 pDb
->DispNextError(); 
1145                 pDb
->DispAllErrors(henv
, hdbc
, hstmt
); 
1146                 pDb
->RollbackTrans(); 
1153     // Commit the transaction and close the cursor 
1154     if (! pDb
->CommitTrans()) 
1156     if (! CloseCursor(hstmt
)) 
1160 }  // wxDbTable::DropTable() 
1163 /********** wxDbTable::CreateIndex() **********/ 
1164 bool wxDbTable::CreateIndex(const char * idxName
, bool unique
, int noIdxCols
, wxDbIdxDef 
*pIdxDefs
, bool attemptDrop
) 
1166 //    char sqlStmt[DB_MAX_STATEMENT_LEN]; 
1169     // Drop the index first 
1170     if (attemptDrop 
&& !DropIndex(idxName
)) 
1173     // Build a CREATE INDEX statement 
1174     sqlStmt 
= "CREATE "; 
1176         sqlStmt 
+= "UNIQUE "; 
1178     sqlStmt 
+= "INDEX "; 
1181     sqlStmt 
+= tableName
; 
1184     // Append list of columns making up index 
1186     for (i 
= 0; i 
< noIdxCols
; i
++) 
1188         sqlStmt 
+= pIdxDefs
[i
].ColName
; 
1189         /* Postgres doesn't cope with ASC */ 
1190         if (pDb
->Dbms() != dbmsPOSTGRES
) 
1192             if (pIdxDefs
[i
].Ascending
) 
1198         if ((i 
+ 1) < noIdxCols
) 
1202     // Append closing parentheses 
1205     pDb
->WriteSqlLog(sqlStmt
.c_str()); 
1207 #ifdef DBDEBUG_CONSOLE 
1208     cout 
<< endl 
<< sqlStmt
.c_str() << endl 
<< endl
; 
1211     // Execute the CREATE INDEX statement 
1212     if (SQLExecDirect(hstmt
, (UCHAR FAR 
*) sqlStmt
.c_str(), SQL_NTS
) != SQL_SUCCESS
) 
1214         pDb
->DispAllErrors(henv
, hdbc
, hstmt
); 
1215         pDb
->RollbackTrans(); 
1220     // Commit the transaction and close the cursor 
1221     if (! pDb
->CommitTrans()) 
1223     if (! CloseCursor(hstmt
)) 
1226     // Index Created Successfully 
1229 }  // wxDbTable::CreateIndex() 
1232 /********** wxDbTable::DropIndex() **********/ 
1233 bool wxDbTable::DropIndex(const char * idxName
) 
1235     // NOTE: This function returns TRUE if the Index does not exist, but 
1236     //       only for identified databases.  Code will need to be added 
1237     //          below for any other databases when those databases are defined 
1238     //       to handle this situation consistently 
1242     if (pDb
->Dbms() == dbmsACCESS
) 
1243         sqlStmt
.sprintf("DROP INDEX %s ON %s",idxName
,tableName
); 
1244     else if (pDb
->Dbms() == dbmsSYBASE_ASE
) 
1245         sqlStmt
.sprintf("DROP INDEX %s.%s",tableName
,idxName
); 
1247         sqlStmt
.sprintf("DROP INDEX %s",idxName
); 
1249     pDb
->WriteSqlLog(sqlStmt
.c_str()); 
1251 #ifdef DBDEBUG_CONSOLE 
1252     cout 
<< endl 
<< sqlStmt
.c_str() << endl
; 
1255     if (SQLExecDirect(hstmt
, (UCHAR FAR 
*) sqlStmt
.c_str(), SQL_NTS
) != SQL_SUCCESS
) 
1257         // Check for "Index not found" error and ignore 
1258         pDb
->GetNextError(henv
, hdbc
, hstmt
); 
1259         if (wxStrcmp(pDb
->sqlState
,"S0012"))  // "Index not found" 
1261             // Check for product specific error codes 
1262           if (!((pDb
->Dbms() == dbmsSYBASE_ASA    
&& !wxStrcmp(pDb
->sqlState
,"42000")) ||  // v5.x (and lower?) 
1263                 (pDb
->Dbms() == dbmsSYBASE_ASE    
&& !wxStrcmp(pDb
->sqlState
,"37000"))   || 
1264                 (pDb
->Dbms() == dbmsMS_SQL_SERVER 
&& !wxStrcmp(pDb
->sqlState
,"S1000"))   || 
1265                 (pDb
->Dbms() == dbmsSYBASE_ASE    
&& !wxStrcmp(pDb
->sqlState
,"S0002")) ||  // Base table not found 
1266                 (pDb
->Dbms() == dbmsMY_SQL        
&& !wxStrcmp(pDb
->sqlState
,"42S02")) ||    // untested 
1267                 (pDb
->Dbms() == dbmsPOSTGRES      
&& !wxStrcmp(pDb
->sqlState
,"08S01")) 
1270                 pDb
->DispNextError(); 
1271                 pDb
->DispAllErrors(henv
, hdbc
, hstmt
); 
1272                 pDb
->RollbackTrans(); 
1279     // Commit the transaction and close the cursor 
1280     if (! pDb
->CommitTrans()) 
1282     if (! CloseCursor(hstmt
)) 
1286 }  // wxDbTable::DropIndex() 
1289 /********** wxDbTable::Insert() **********/ 
1290 int wxDbTable::Insert(void) 
1293     if (queryOnly 
|| !insertable
) 
1298     // Insert the record by executing the already prepared insert statement 
1300     retcode
=SQLExecute(hstmtInsert
); 
1301     if (retcode 
!= SQL_SUCCESS 
&& retcode 
!= SQL_SUCCESS_WITH_INFO
) 
1303         // Check to see if integrity constraint was violated 
1304         pDb
->GetNextError(henv
, hdbc
, hstmtInsert
); 
1305         if (! wxStrcmp(pDb
->sqlState
, "23000"))  // Integrity constraint violated 
1306             return(DB_ERR_INTEGRITY_CONSTRAINT_VIOL
); 
1309             pDb
->DispNextError(); 
1310             pDb
->DispAllErrors(henv
, hdbc
, hstmtInsert
); 
1315     // Record inserted into the datasource successfully 
1318 }  // wxDbTable::Insert() 
1321 /********** wxDbTable::Update() **********/ 
1322 bool wxDbTable::Update(void) 
1328     char sqlStmt
[DB_MAX_STATEMENT_LEN
]; 
1330     // Build the SQL UPDATE statement 
1331     BuildUpdateStmt(sqlStmt
, DB_UPD_KEYFIELDS
); 
1333     pDb
->WriteSqlLog(sqlStmt
); 
1335 #ifdef DBDEBUG_CONSOLE 
1336     cout 
<< endl 
<< sqlStmt 
<< endl 
<< endl
; 
1339     // Execute the SQL UPDATE statement 
1340     return(execUpdate(sqlStmt
)); 
1342 }  // wxDbTable::Update() 
1345 /********** wxDbTable::Update(pSqlStmt) **********/ 
1346 bool wxDbTable::Update(const char *pSqlStmt
) 
1352     pDb
->WriteSqlLog(pSqlStmt
); 
1354     return(execUpdate(pSqlStmt
)); 
1356 }  // wxDbTable::Update(pSqlStmt) 
1359 /********** wxDbTable::UpdateWhere() **********/ 
1360 bool wxDbTable::UpdateWhere(const char *pWhereClause
) 
1366     char sqlStmt
[DB_MAX_STATEMENT_LEN
]; 
1368     // Build the SQL UPDATE statement 
1369     BuildUpdateStmt(sqlStmt
, DB_UPD_WHERE
, pWhereClause
); 
1371     pDb
->WriteSqlLog(sqlStmt
); 
1373 #ifdef DBDEBUG_CONSOLE 
1374     cout 
<< endl 
<< sqlStmt 
<< endl 
<< endl
; 
1377     // Execute the SQL UPDATE statement 
1378     return(execUpdate(sqlStmt
)); 
1380 }  // wxDbTable::UpdateWhere() 
1383 /********** wxDbTable::Delete() **********/ 
1384 bool wxDbTable::Delete(void) 
1390     char sqlStmt
[DB_MAX_STATEMENT_LEN
]; 
1392     // Build the SQL DELETE statement 
1393     BuildDeleteStmt(sqlStmt
, DB_DEL_KEYFIELDS
); 
1395     pDb
->WriteSqlLog(sqlStmt
); 
1397     // Execute the SQL DELETE statement 
1398     return(execDelete(sqlStmt
)); 
1400 }  // wxDbTable::Delete() 
1403 /********** wxDbTable::DeleteWhere() **********/ 
1404 bool wxDbTable::DeleteWhere(const char *pWhereClause
) 
1410     char sqlStmt
[DB_MAX_STATEMENT_LEN
]; 
1412     // Build the SQL DELETE statement 
1413     BuildDeleteStmt(sqlStmt
, DB_DEL_WHERE
, pWhereClause
); 
1415     pDb
->WriteSqlLog(sqlStmt
); 
1417     // Execute the SQL DELETE statement 
1418     return(execDelete(sqlStmt
)); 
1420 }  // wxDbTable::DeleteWhere() 
1423 /********** wxDbTable::DeleteMatching() **********/ 
1424 bool wxDbTable::DeleteMatching(void) 
1430     char sqlStmt
[DB_MAX_STATEMENT_LEN
]; 
1432     // Build the SQL DELETE statement 
1433     BuildDeleteStmt(sqlStmt
, DB_DEL_MATCHING
); 
1435     pDb
->WriteSqlLog(sqlStmt
); 
1437     // Execute the SQL DELETE statement 
1438     return(execDelete(sqlStmt
)); 
1440 }  // wxDbTable::DeleteMatching() 
1443 /********** wxDbTable::BuildUpdateStmt() **********/ 
1444 void wxDbTable::BuildUpdateStmt(char *pSqlStmt
, int typeOfUpd
, const char *pWhereClause
) 
1450     char whereClause
[DB_MAX_WHERE_CLAUSE_LEN
]; 
1451     bool firstColumn 
= TRUE
; 
1454     sprintf(pSqlStmt
, "UPDATE %s SET ", tableName
); 
1456     // Append a list of columns to be updated 
1458     for (i 
= 0; i 
< noCols
; i
++) 
1460         // Only append Updateable columns 
1461         if (colDefs
[i
].Updateable
) 
1464                 wxStrcat(pSqlStmt
, ","); 
1466                 firstColumn 
= FALSE
; 
1467             wxStrcat(pSqlStmt
, colDefs
[i
].ColName
); 
1468             wxStrcat(pSqlStmt
, " = ?"); 
1472     // Append the WHERE clause to the SQL UPDATE statement 
1473     wxStrcat(pSqlStmt
, " WHERE "); 
1476     case DB_UPD_KEYFIELDS
: 
1477         // If the datasource supports the ROWID column, build 
1478         // the where on ROWID for efficiency purposes. 
1479         // e.g. UPDATE PARTS SET Col1 = ?, Col2 = ? WHERE ROWID = '111.222.333' 
1480         if (CanUpdByROWID()) 
1483             char   rowid
[wxDB_ROWID_LEN
]; 
1485             // Get the ROWID value.  If not successful retreiving the ROWID, 
1486             // simply fall down through the code and build the WHERE clause 
1487             // based on the key fields. 
1488             if (SQLGetData(hstmt
, noCols
+1, SQL_C_CHAR
, (UCHAR
*) rowid
, wxDB_ROWID_LEN
, &cb
) == SQL_SUCCESS
) 
1490                 wxStrcat(pSqlStmt
, "ROWID = '"); 
1491                 wxStrcat(pSqlStmt
, rowid
); 
1492                 wxStrcat(pSqlStmt
, "'"); 
1496         // Unable to delete by ROWID, so build a WHERE 
1497         // clause based on the keyfields. 
1498         BuildWhereClause(whereClause
, DB_WHERE_KEYFIELDS
); 
1499         wxStrcat(pSqlStmt
, whereClause
); 
1502         wxStrcat(pSqlStmt
, pWhereClause
); 
1505 }  // BuildUpdateStmt() 
1508 /********** wxDbTable::BuildDeleteStmt() **********/ 
1509 void wxDbTable::BuildDeleteStmt(char *pSqlStmt
, int typeOfDel
, const char *pWhereClause
) 
1515     char whereClause
[DB_MAX_WHERE_CLAUSE_LEN
]; 
1519     // Handle the case of DeleteWhere() and the where clause is blank.  It should 
1520     // delete all records from the database in this case. 
1521     if (typeOfDel 
== DB_DEL_WHERE 
&& (pWhereClause 
== 0 || wxStrlen(pWhereClause
) == 0)) 
1523         sprintf(pSqlStmt
, "DELETE FROM %s", tableName
); 
1527     sprintf(pSqlStmt
, "DELETE FROM %s WHERE ", tableName
); 
1529     // Append the WHERE clause to the SQL DELETE statement 
1532     case DB_DEL_KEYFIELDS
: 
1533         // If the datasource supports the ROWID column, build 
1534         // the where on ROWID for efficiency purposes. 
1535         // e.g. DELETE FROM PARTS WHERE ROWID = '111.222.333' 
1536         if (CanUpdByROWID()) 
1539             char   rowid
[wxDB_ROWID_LEN
]; 
1541             // Get the ROWID value.  If not successful retreiving the ROWID, 
1542             // simply fall down through the code and build the WHERE clause 
1543             // based on the key fields. 
1544             if (SQLGetData(hstmt
, noCols
+1, SQL_C_CHAR
, (UCHAR
*) rowid
, wxDB_ROWID_LEN
, &cb
) == SQL_SUCCESS
) 
1546                 wxStrcat(pSqlStmt
, "ROWID = '"); 
1547                 wxStrcat(pSqlStmt
, rowid
); 
1548                 wxStrcat(pSqlStmt
, "'"); 
1552         // Unable to delete by ROWID, so build a WHERE 
1553         // clause based on the keyfields. 
1554         BuildWhereClause(whereClause
, DB_WHERE_KEYFIELDS
); 
1555         wxStrcat(pSqlStmt
, whereClause
); 
1558         wxStrcat(pSqlStmt
, pWhereClause
); 
1560     case DB_DEL_MATCHING
: 
1561         BuildWhereClause(whereClause
, DB_WHERE_MATCHING
); 
1562         wxStrcat(pSqlStmt
, whereClause
); 
1566 }  // BuildDeleteStmt() 
1569 /********** wxDbTable::BuildWhereClause() **********/ 
1570 void wxDbTable::BuildWhereClause(char *pWhereClause
, int typeOfWhere
, 
1571                                  const char *qualTableName
, bool useLikeComparison
) 
1573  * Note: BuildWhereClause() currently ignores timestamp columns. 
1574  *       They are not included as part of the where clause. 
1577     bool moreThanOneColumn 
= FALSE
; 
1580     // Loop through the columns building a where clause as you go 
1582     for (i 
= 0; i 
< noCols
; i
++) 
1584         // Determine if this column should be included in the WHERE clause 
1585         if ((typeOfWhere 
== DB_WHERE_KEYFIELDS 
&& colDefs
[i
].KeyField
) || 
1586              (typeOfWhere 
== DB_WHERE_MATCHING  
&& (! IsColNull(i
)))) 
1588             // Skip over timestamp columns 
1589             if (colDefs
[i
].SqlCtype 
== SQL_C_TIMESTAMP
) 
1591             // If there is more than 1 column, join them with the keyword "AND" 
1592             if (moreThanOneColumn
) 
1593                 wxStrcat(pWhereClause
, " AND "); 
1595                 moreThanOneColumn 
= TRUE
; 
1596             // Concatenate where phrase for the column 
1597             if (qualTableName 
&& wxStrlen(qualTableName
)) 
1599                 wxStrcat(pWhereClause
, qualTableName
); 
1600                 wxStrcat(pWhereClause
, "."); 
1602             wxStrcat(pWhereClause
, colDefs
[i
].ColName
); 
1603             if (useLikeComparison 
&& (colDefs
[i
].SqlCtype 
== SQL_C_CHAR
)) 
1604                 wxStrcat(pWhereClause
, " LIKE "); 
1606                 wxStrcat(pWhereClause
, " = "); 
1607             switch(colDefs
[i
].SqlCtype
) 
1610                 sprintf(colValue
, "'%s'", (UCHAR FAR 
*) colDefs
[i
].PtrDataObj
); 
1613                 sprintf(colValue
, "%hi", *((SWORD 
*) colDefs
[i
].PtrDataObj
)); 
1616                 sprintf(colValue
, "%hu", *((UWORD 
*) colDefs
[i
].PtrDataObj
)); 
1619                 sprintf(colValue
, "%li", *((SDWORD 
*) colDefs
[i
].PtrDataObj
)); 
1622                 sprintf(colValue
, "%lu", *((UDWORD 
*) colDefs
[i
].PtrDataObj
)); 
1625                 sprintf(colValue
, "%.6f", *((SFLOAT 
*) colDefs
[i
].PtrDataObj
)); 
1628                 sprintf(colValue
, "%.6f", *((SDOUBLE 
*) colDefs
[i
].PtrDataObj
)); 
1631             wxStrcat(pWhereClause
, colValue
); 
1634 }  // wxDbTable::BuildWhereClause() 
1637 /********** wxDbTable::IsColNull() **********/ 
1638 bool wxDbTable::IsColNull(int colNo
) 
1640     switch(colDefs
[colNo
].SqlCtype
) 
1643         return(((UCHAR FAR 
*) colDefs
[colNo
].PtrDataObj
)[0] == 0); 
1645         return((  *((SWORD 
*) colDefs
[colNo
].PtrDataObj
))   == 0); 
1647         return((   *((UWORD
*) colDefs
[colNo
].PtrDataObj
))   == 0); 
1649         return(( *((SDWORD 
*) colDefs
[colNo
].PtrDataObj
))   == 0); 
1651         return(( *((UDWORD 
*) colDefs
[colNo
].PtrDataObj
))   == 0); 
1653         return(( *((SFLOAT 
*) colDefs
[colNo
].PtrDataObj
))   == 0); 
1655         return((*((SDOUBLE 
*) colDefs
[colNo
].PtrDataObj
))   == 0); 
1656     case SQL_C_TIMESTAMP
: 
1657         TIMESTAMP_STRUCT 
*pDt
; 
1658         pDt 
= (TIMESTAMP_STRUCT 
*) colDefs
[colNo
].PtrDataObj
; 
1659         if (pDt
->year 
== 0 && pDt
->month 
== 0 && pDt
->day 
== 0) 
1666 }  // wxDbTable::IsColNull() 
1669 /********** wxDbTable::CanSelectForUpdate() **********/ 
1670 bool wxDbTable::CanSelectForUpdate(void) 
1672     if (pDb
->Dbms() == dbmsMY_SQL
) 
1675     if (pDb
->dbInf
.posStmts 
& SQL_PS_SELECT_FOR_UPDATE
) 
1680 }  // wxDbTable::CanSelectForUpdate() 
1683 /********** wxDbTable::CanUpdByROWID() **********/ 
1684 bool wxDbTable::CanUpdByROWID(void) 
1687  * NOTE: Returning FALSE for now until this can be debugged, 
1688  *        as the ROWID is not getting updated correctly 
1692     if (pDb
->Dbms() == dbmsORACLE
) 
1697 }  // wxDbTable::CanUpdByROWID() 
1700 /********** wxDbTable::IsCursorClosedOnCommit() **********/ 
1701 bool wxDbTable::IsCursorClosedOnCommit(void) 
1703     if (pDb
->dbInf
.cursorCommitBehavior 
== SQL_CB_PRESERVE
) 
1708 }  // wxDbTable::IsCursorClosedOnCommit() 
1711 /********** wxDbTable::ClearMemberVars() **********/ 
1712 void wxDbTable::ClearMemberVars(void) 
1714     // Loop through the columns setting each member variable to zero 
1716     for (i 
= 0; i 
< noCols
; i
++) 
1718         switch(colDefs
[i
].SqlCtype
) 
1721             ((UCHAR FAR 
*) colDefs
[i
].PtrDataObj
)[0]    = 0; 
1724             *((SWORD 
*) colDefs
[i
].PtrDataObj
)          = 0; 
1727             *((UWORD
*) colDefs
[i
].PtrDataObj
)           = 0; 
1730             *((SDWORD 
*) colDefs
[i
].PtrDataObj
)         = 0; 
1733             *((UDWORD 
*) colDefs
[i
].PtrDataObj
)         = 0; 
1736             *((SFLOAT 
*) colDefs
[i
].PtrDataObj
)         = 0.0f
; 
1739             *((SDOUBLE 
*) colDefs
[i
].PtrDataObj
)        = 0.0f
; 
1741         case SQL_C_TIMESTAMP
: 
1742             TIMESTAMP_STRUCT 
*pDt
; 
1743             pDt 
= (TIMESTAMP_STRUCT 
*) colDefs
[i
].PtrDataObj
; 
1756 }  // wxDbTable::ClearMemberVars() 
1759 /********** wxDbTable::SetQueryTimeout() **********/ 
1760 bool wxDbTable::SetQueryTimeout(UDWORD nSeconds
) 
1762     if (SQLSetStmtOption(hstmtInsert
, SQL_QUERY_TIMEOUT
, nSeconds
) != SQL_SUCCESS
) 
1763         return(pDb
->DispAllErrors(henv
, hdbc
, hstmtInsert
)); 
1764     if (SQLSetStmtOption(hstmtUpdate
, SQL_QUERY_TIMEOUT
, nSeconds
) != SQL_SUCCESS
) 
1765         return(pDb
->DispAllErrors(henv
, hdbc
, hstmtUpdate
)); 
1766     if (SQLSetStmtOption(hstmtDelete
, SQL_QUERY_TIMEOUT
, nSeconds
) != SQL_SUCCESS
) 
1767         return(pDb
->DispAllErrors(henv
, hdbc
, hstmtDelete
)); 
1768     if (SQLSetStmtOption(hstmtInternal
, SQL_QUERY_TIMEOUT
, nSeconds
) != SQL_SUCCESS
) 
1769         return(pDb
->DispAllErrors(henv
, hdbc
, hstmtInternal
)); 
1771     // Completed Successfully 
1774 }  // wxDbTable::SetQueryTimeout() 
1777 /********** wxDbTable::SetColDefs() **********/ 
1778 void wxDbTable::SetColDefs (int index
, const char *fieldName
, int dataType
, void *pData
, 
1779                                  int cType
, int size
, bool keyField
, bool upd
, 
1780                                  bool insAllow
, bool derivedCol
) 
1782     if (!colDefs
)  // May happen if the database connection fails 
1785     if (wxStrlen(fieldName
) > (unsigned int) DB_MAX_COLUMN_NAME_LEN
) 
1787         wxStrncpy (colDefs
[index
].ColName
, fieldName
, DB_MAX_COLUMN_NAME_LEN
); 
1788         colDefs
[index
].ColName
[DB_MAX_COLUMN_NAME_LEN
] = 0; 
1791         wxStrcpy(colDefs
[index
].ColName
, fieldName
); 
1793     colDefs
[index
].DbDataType       
= dataType
; 
1794     colDefs
[index
].PtrDataObj       
= pData
; 
1795     colDefs
[index
].SqlCtype         
= cType
; 
1796     colDefs
[index
].SzDataObj        
= size
; 
1797     colDefs
[index
].KeyField         
= keyField
; 
1798     colDefs
[index
].DerivedCol       
= derivedCol
; 
1799     // Derived columns by definition would NOT be "Insertable" or "Updateable" 
1802         colDefs
[index
].Updateable       
= FALSE
; 
1803         colDefs
[index
].InsertAllowed    
= FALSE
; 
1807         colDefs
[index
].Updateable       
= upd
; 
1808         colDefs
[index
].InsertAllowed    
= insAllow
; 
1811     colDefs
[index
].Null                 
= FALSE
; 
1813 }  // wxDbTable::SetColDefs() 
1816 /********** wxDbTable::SetColDef() **********/ 
1817 wxDbColDataPtr
* wxDbTable::SetColDefs (wxDbColInf 
*pColInfs
, ULONG numCols
) 
1820     wxDbColDataPtr 
*pColDataPtrs 
= NULL
; 
1827         pColDataPtrs 
= new wxDbColDataPtr
[numCols
+1]; 
1829         for (index 
= 0; index 
< numCols
; index
++) 
1831             // Process the fields 
1832             switch (pColInfs
[index
].dbDataType
) 
1834                 case DB_DATA_TYPE_VARCHAR
: 
1836                    pColDataPtrs
[index
].PtrDataObj 
= new char[pColInfs
[index
].bufferLength
+1]; 
1837                    pColDataPtrs
[index
].SzDataObj  
= pColInfs
[index
].columnSize
; 
1838                    pColDataPtrs
[index
].SqlCtype   
= SQL_C_CHAR
; 
1841                 case DB_DATA_TYPE_INTEGER
: 
1843                     // Can be long or short 
1844                     if (pColInfs
[index
].bufferLength 
== sizeof(long)) 
1846                       pColDataPtrs
[index
].PtrDataObj 
= new long; 
1847                       pColDataPtrs
[index
].SzDataObj  
= sizeof(long); 
1848                       pColDataPtrs
[index
].SqlCtype   
= SQL_C_SLONG
; 
1852                         pColDataPtrs
[index
].PtrDataObj 
= new short; 
1853                         pColDataPtrs
[index
].SzDataObj  
= sizeof(short); 
1854                         pColDataPtrs
[index
].SqlCtype   
= SQL_C_SSHORT
; 
1858                 case DB_DATA_TYPE_FLOAT
: 
1860                     // Can be float or double 
1861                     if (pColInfs
[index
].bufferLength 
== sizeof(float)) 
1863                         pColDataPtrs
[index
].PtrDataObj 
= new float; 
1864                         pColDataPtrs
[index
].SzDataObj  
= sizeof(float); 
1865                         pColDataPtrs
[index
].SqlCtype   
= SQL_C_FLOAT
; 
1869                         pColDataPtrs
[index
].PtrDataObj 
= new double; 
1870                         pColDataPtrs
[index
].SzDataObj  
= sizeof(double); 
1871                         pColDataPtrs
[index
].SqlCtype   
= SQL_C_DOUBLE
; 
1875                 case DB_DATA_TYPE_DATE
: 
1877                     pColDataPtrs
[index
].PtrDataObj 
= new TIMESTAMP_STRUCT
; 
1878                     pColDataPtrs
[index
].SzDataObj  
= sizeof(TIMESTAMP_STRUCT
); 
1879                     pColDataPtrs
[index
].SqlCtype   
= SQL_C_TIMESTAMP
; 
1884          SetColDefs (index
,pColInfs
[index
].colName
,pColInfs
[index
].dbDataType
, pColDataPtrs
[index
].PtrDataObj
, pColDataPtrs
[index
].SqlCtype
, pColDataPtrs
[index
].SzDataObj
); 
1887     return (pColDataPtrs
); 
1888 } // wxDbTable::SetColDef() 
1891 /********** wxDbTable::SetCursor() **********/ 
1892 void wxDbTable::SetCursor(HSTMT 
*hstmtActivate
) 
1894     if (hstmtActivate 
== wxDB_DEFAULT_CURSOR
) 
1895         hstmt 
= *hstmtDefault
; 
1897         hstmt 
= *hstmtActivate
; 
1899 }  // wxDbTable::SetCursor() 
1902 /********** wxDbTable::Count(const char *) **********/ 
1903 ULONG 
wxDbTable::Count(const char *args
) 
1909     // Build a "SELECT COUNT(*) FROM queryTableName [WHERE whereClause]" SQL Statement 
1910     sqlStmt  
= "SELECT COUNT("; 
1912     sqlStmt 
+= ") FROM "; 
1913     sqlStmt 
+= queryTableName
; 
1914 #if wxODBC_BACKWARD_COMPATABILITY 
1915     if (from 
&& wxStrlen(from
)) 
1921     // Add the where clause if one is provided 
1922 #if wxODBC_BACKWARD_COMPATABILITY 
1923     if (where 
&& wxStrlen(where
)) 
1928         sqlStmt 
+= " WHERE "; 
1932     pDb
->WriteSqlLog(sqlStmt
.c_str()); 
1934     // Initialize the Count cursor if it's not already initialized 
1937         hstmtCount 
= GetNewCursor(FALSE
,FALSE
); 
1943     // Execute the SQL statement 
1944     if (SQLExecDirect(*hstmtCount
, (UCHAR FAR 
*) sqlStmt
.c_str(), SQL_NTS
) != SQL_SUCCESS
) 
1946         pDb
->DispAllErrors(henv
, hdbc
, *hstmtCount
); 
1951     if (SQLFetch(*hstmtCount
) != SQL_SUCCESS
) 
1953         pDb
->DispAllErrors(henv
, hdbc
, *hstmtCount
); 
1957     // Obtain the result 
1958     if (SQLGetData(*hstmtCount
, 1, SQL_C_ULONG
, &l
, sizeof(l
), &cb
) != SQL_SUCCESS
) 
1960         pDb
->DispAllErrors(henv
, hdbc
, *hstmtCount
); 
1965     if (SQLFreeStmt(*hstmtCount
, SQL_CLOSE
) != SQL_SUCCESS
) 
1966         pDb
->DispAllErrors(henv
, hdbc
, *hstmtCount
); 
1968     // Return the record count 
1971 }  // wxDbTable::Count() 
1974 /********** wxDbTable::Refresh() **********/ 
1975 bool wxDbTable::Refresh(void) 
1979     // Switch to the internal cursor so any active cursors are not corrupted 
1980     HSTMT currCursor 
= GetCursor(); 
1981     hstmt 
= hstmtInternal
; 
1982 #if wxODBC_BACKWARD_COMPATABILITY 
1983     // Save the where and order by clauses 
1984     char *saveWhere 
= where
; 
1985     char *saveOrderBy 
= orderBy
; 
1987     wxString saveWhere 
= where
; 
1988     wxString saveOrderBy 
= orderBy
; 
1990     // Build a where clause to refetch the record with.  Try and use the 
1991     // ROWID if it's available, ow use the key fields. 
1992     char whereClause
[DB_MAX_WHERE_CLAUSE_LEN
+1]; 
1993     wxStrcpy(whereClause
, ""); 
1994     if (CanUpdByROWID()) 
1997         char   rowid
[wxDB_ROWID_LEN
+1]; 
1999         // Get the ROWID value.  If not successful retreiving the ROWID, 
2000         // simply fall down through the code and build the WHERE clause 
2001         // based on the key fields. 
2002         if (SQLGetData(hstmt
, noCols
+1, SQL_C_CHAR
, (UCHAR
*) rowid
, wxDB_ROWID_LEN
, &cb
) == SQL_SUCCESS
) 
2004             wxStrcat(whereClause
, queryTableName
); 
2005             wxStrcat(whereClause
, ".ROWID = '"); 
2006             wxStrcat(whereClause
, rowid
); 
2007             wxStrcat(whereClause
, "'"); 
2011     // If unable to use the ROWID, build a where clause from the keyfields 
2012     if (wxStrlen(whereClause
) == 0) 
2013         BuildWhereClause(whereClause
, DB_WHERE_KEYFIELDS
, queryTableName
); 
2015     // Requery the record 
2016     where 
= whereClause
; 
2021     if (result 
&& !GetNext()) 
2024     // Switch back to original cursor 
2025     SetCursor(&currCursor
); 
2027     // Free the internal cursor 
2028     if (SQLFreeStmt(hstmtInternal
, SQL_CLOSE
) != SQL_SUCCESS
) 
2029         pDb
->DispAllErrors(henv
, hdbc
, hstmtInternal
); 
2031     // Restore the original where and order by clauses 
2033     orderBy 
= saveOrderBy
; 
2037 }  // wxDbTable::Refresh() 
2040 /********** wxDbTable::SetNull(int colNo) **********/ 
2041 bool wxDbTable::SetNull(int colNo
) 
2044         return(colDefs
[colNo
].Null 
= TRUE
); 
2048 }  // wxDbTable::SetNull(int colNo) 
2051 /********** wxDbTable::SetNull(char *colName) **********/ 
2052 bool wxDbTable::SetNull(const char *colName
) 
2055     for (i 
= 0; i 
< noCols
; i
++) 
2057         if (!wxStricmp(colName
, colDefs
[i
].ColName
)) 
2062         return(colDefs
[i
].Null 
= TRUE
); 
2066 }  // wxDbTable::SetNull(char *colName) 
2069 /********** wxDbTable::GetNewCursor() **********/ 
2070 HSTMT 
*wxDbTable::GetNewCursor(bool setCursor
, bool bindColumns
) 
2072     HSTMT 
*newHSTMT 
= new HSTMT
; 
2077     if (SQLAllocStmt(hdbc
, newHSTMT
) != SQL_SUCCESS
) 
2079         pDb
->DispAllErrors(henv
, hdbc
); 
2084     if (SQLSetStmtOption(*newHSTMT
, SQL_CURSOR_TYPE
, cursorType
) != SQL_SUCCESS
) 
2086         pDb
->DispAllErrors(henv
, hdbc
, *newHSTMT
); 
2093         if(!bindCols(*newHSTMT
)) 
2101         SetCursor(newHSTMT
); 
2105 }   // wxDbTable::GetNewCursor() 
2108 /********** wxDbTable::DeleteCursor() **********/ 
2109 bool wxDbTable::DeleteCursor(HSTMT 
*hstmtDel
) 
2113     if (!hstmtDel
)  // Cursor already deleted 
2116     if (SQLFreeStmt(*hstmtDel
, SQL_DROP
) != SQL_SUCCESS
) 
2118         pDb
->DispAllErrors(henv
, hdbc
); 
2126 }  // wxDbTable::DeleteCursor() 
2128 #endif  // wxUSE_ODBC