Different ODBC Class Libraries in wxWidgets
wxDb/wxDbTable wxODBC Overview
wxODBC Where To Start
wxODBC - Configuring your system for ODBC use
wxODBC - Compiling
wxODBC - Basic Step-By-Step Guide
wxODBC - Known Issues
wxODBC - Sample Code #1
wxDatabase ODBC class overview [DEPRECATED]
Procedures for writing an ODBC application using wxDatabase [DEPRECATED]
wxDatabase class overview [DEPRECATED]
wxQueryCol class overview [DEPRECATED]
wxQueryField class overview [DEPRECATED]
wxRecordSet overview [DEPRECATED]
ODBC SQL data types [DEPRECATED]
A selection of SQL commands [DEPRECATED]
Following is a detailed overview of how to use the wxWidgets ODBC classes - wxDb and wxDbTable and their associated functions. These are the ODBC classes donated by Remstar International, and are collectively referred to herein as the wxODBC classes. Since their initial inclusion with wxWidgets v2.x, they have become the recommended wxWidgets classes for database access.
An older version of some classes ported over from wxWidgets v1.68 still exist (see wxDatabase in odbc.cpp), but are now deprecated in favor of the more robust and comprehensive wxDb/wxDbTable classes. All current and future feature development, as well as active debugging, are only being done on the wxODBC classes. Documentation for the older classes is still provided in this manual. The wxDatabase overview of the older classes follows the overview of the new classes.
The wxODBC classes were designed for database independence. Although SQL and ODBC both have standards which define the minimum requirements they must support to be in compliance with specifications, different database vendors may implement things slightly differently. One example of this is that Oracle requires all user names for the datasources to be supplied in uppercase characters. In situations like this, the wxODBC classes have been written to make this transparent to the programmer when using functions that require database-specific syntax.
Currently several major databases, along with other widely used databases, have been tested and supported through the wxODBC classes. The list of supported databases is certain to grow as more users start implementing software with these classes, but at the time of the writing of this document, users have successfully used the classes with the following datasources:
An up-to-date list can be obtained by looking in the comments of the function wxDb::Dbms in db.cpp, or in the enumerated type wxDBMS in db.h.
**dBase is not truly an ODBC datasource, but there are drivers which can emulate much of the functionality of an ODBC connection to a dBase table. See the wxODBC Known Issues section of this overview for details.
First, if you are not familiar with SQL and ODBC, go to your local bookstore and pick up a good book on each. This documentation is not meant to teach you many details about SQL or ODBC, though you may learn some just from immersion in the subject.
If you have worked with non-SQL/ODBC datasources before, there are some things you will need to un-learn. First some terminology as these phrases will be used heavily in this section of the manual.
Datasource | (usually a database) that contains the data that will be accessed by the wxODBC classes. |
Data table | The section of the datasource that contains the rows and columns of data. |
ODBC driver | The middle-ware software that interprets the ODBC commands sent by your application and converts them to the SQL format expected by the target datasource. |
Datasource connection | An open pipe between your application and the ODBC driver which in turn has a connection to the target datasource. Datasource connections can have a virtually unlimited number of wxDbTable instances using the same connect (dependent on the ODBC driver). A separate connection is not needed for each table (the exception is for isolating commits/rollbacks on different tables from affecting more than the desired table. See the class documentation on wxDb::CommitTrans and wxDb::RollbackTrans.) |
Rows | Similar to records in old relational databases, a row is a collection of one instance of each column of the data table that are all associated with each other. |
Columns | Individual fields associated with each row of a data table. |
Query | Request from the client to the datasource asking for the data that matches the requirements specified in the users request. When a query is performed, the datasource performs the lookup of the rows with satisfy the query, and creates a result set. |
Result set | The data which matches the requirements specified in a query sent to the datasource. Dependent on drivers, a result set typically remains at the datasource (no data is transmitted to the ODBC driver) until the client actually instructs the ODBC driver to retrieve it. |
Cursor | A logical pointer into the result set that a query generates, indicating the next record that will be returned to the client when a request for the next record is made. |
Scrolling cursors | Scrolling refers to the movement of cursors through the result set. Cursors can always scroll forward sequentially in the result set (FORWARD ONLY scrolling cursors). With Forward only scrolling cursors, once a row in the result set has been returned to the ODBC driver and on to the client, there is no way to have the cursor move backward in the result set to look at the row that is previous to the current row in the result set. If BACKWARD scrolling cursors are supported by both the ODBC driver and the datasource that are being used, then backward scrolling cursor functions may be used ( wxDbTable::GetPrev, wxDbTable::GetFirst, and wxDbTable::GetLast). If the datasource or the ODBC driver only support forward scrolling cursors, your program and logic must take this in to account. |
Commit/Rollback | Commit will physically save insertions/deletions/updates, while rollback basically does an undo of everything done against the datasource connection that has not been previously committed. Note that Commit and Rollbacks are done on a connection, not on individual tables. All tables which use a shared connection to the datasource are all committed/rolled back at the same time when a call to wxDb::CommitTrans or wxDb::RollbackTrans is made. |
Index | Indexes are datasource-maintained lookup structures that allow the datasource to quickly locate data rows based on the values of certain columns. Without indexes, the datasource would need to do a sequential search of a table every time a query request is made. Proper unique key index construction can make datasource queries nearly instantaneous. |
Before you are able to read data from a data table in a datasource, you must have a connection to the datasource. Each datasource connection may be used to open multiple tables all on the same connection (number of tables open are dependent on the driver, datasource configuration and the amount of memory on the client workstation). Multiple connections can be opened to the same datasource by the same client (number of concurrent connections is dependent on the driver and datasource configuration).
When a query is performed, the client passes the query to the ODBC driver, and the driver then translates it and passes it along to the datasource. The database engine (in most cases - exceptions are text and dBase files) running on the machine hosting the database does all the work of performing the search for the requested data. The client simply waits for a status to come back through the ODBC driver from the datasource.
Depending on the ODBC driver, the result set either remains "queued" on the database server side, or is transferred to the machine that the driver is queued on. The client does not receive this data. The client must request some or all of the result set to be returned before any data rows are returned to the client application.
Result sets do not need to include all columns of every row matching the query. In fact, result sets can actually be joinings of columns from two or more data tables, may have derived column values, or calculated values returned.
For each result set, a cursor is maintained (typically by the database) which keeps track of where in the result set the user currently is. Depending on the database, ODBC driver, and how you configured the wxWidgets ODBC settings in setup.h (see wxODBC - Compiling), cursors can be either forward or backward scrolling. At a minimum, cursors must scroll forward. For example, if a query resulted in a result set with 100 rows, as the data is read by the client application, it will read row 1, then 2, then 3, etc. With forward only cursors, once the cursor has moved to the next row, the previous row cannot be accessed again without re-querying the datasource for the result set over again. Backward scrolling cursors allow you to request the previous row from the result set, actually scrolling the cursor backward.
Backward scrolling cursors are not supported on all database/driver combinations. For this reason, forward-only cursors are the default in the wxODBC classes. If your datasource does support backward scrolling cursors and you wish to use them, make the appropriate changes in setup.h to enable them (see wxODBC - Compiling). For greatest portability between datasources, writing your program in such a way that it only requires forward scrolling cursors is your best bet. On the other hand, if you are focusing on using only datasources that support backward scrolling cursors, potentially large performance benefits can be gained from using them.
There is a limit to the number of cursors that can be open on each connection to the datasource, and usually a maximum number of cursors for the datasource itself. This is all dependent on the database. Each connection that is opened (each instance of a wxDb) opens a minimum of 5 cursors on creation that are required for things such as updates/deletions/rollbacks/queries. Cursors are a limited resource, so use care in creating large numbers of cursors.
Additional cursors can be created if necessary with the wxDbTable::GetNewCursor function. One example use for additional cursors is to track multiple scroll points in result sets. By creating a new cursor, a program could request a second result set from the datasource while still maintaining the original cursor position in the first result set.
Different than non-SQL/ODBC datasources, when a program performs an insertion, deletion, or update (or other SQL functions like altering tables, etc) through ODBC, the program must issue a "commit" to the datasource to tell the datasource that the action(s) it has been told to perform are to be recorded as permanent. Until a commit is performed, any other programs that query the datasource will not see the changes that have been made (although there are databases that can be configured to auto-commit). NOTE: With most datasources, until the commit is performed, any cursor that is open on that same datasource connection will be able to see the changes that are uncommitted. Check your database's documentation/configuration to verify this before relying on it though.
A rollback is basically an UNDO command on the datasource connection. When a rollback is issued, the datasource will flush all commands it has been told to do since the last commit that was performed.
NOTE: Commits/Rollbacks are done on datasource connections (wxDb instances) not on the wxDbTable instances. This means that if more than one table shares the same connection, and a commit or rollback is done on that connection, all pending changes for ALL tables using that connection are committed/rolled back.
Before you are able to access a datasource, you must have installed and configured an ODBC driver. Doing this is system specific, so it will not be covered in detail here. But here are a few details to get you started.
Most database vendors provide at least a minimal ODBC driver with their database product. In practice, many of these drivers have proven to be slow and/or incomplete. Rumour has it that this is because the vendors do not want you using the ODBC interface to their products; they want you to use their applications to access the data.
Whatever the reason, for database-intensive applications, you may want to consider using a third-party ODBC driver for your needs. One example of a third-party set of ODBC drivers that has been heavily tested and used is Rogue Wave's drivers. Rogue Wave has drivers available for many different platforms and databases. Under Microsoft Windows, install the ODBC driver you are planning to use. You will then use the ODBC Administrator in the Control Panel to configure an instance of the driver for your intended datasource. Note that with all flavors of NT, this configuration can be set up as a System or User DSN (datasource name). Configuring it as a system resource will make it available to all users (if you are logged in as 'administrator'), otherwise the datasource will only be available to the user who configured the DSN.
Under Unix, iODBC is used for implementation of the ODBC API. To compile the wxODBC classes, you must first obtain iODBC from http://www.iodbc.org and install it. (Note: wxWidgets currently includes a version of iODBC.) Then you must create the file " /.odbc.ini" (or optionally create "/etc/odbc.ini" for access for all users on the system). This file contains the settings for your system/datasource. Below is an example section of a odbc.ini file for use with the "samples/db" sample program using MySQL:
[contacts] Trace = Off TraceFile= stderr Driver = /usr/local/lib/libmyodbc.so DSN = contacts SERVER = 192.168.1.13 USER = qet PASSWORD = PORT = 3306
The wxWidgets setup.h file has several settings in it pertaining to compiling the wxODBC classes.
wxUSE_ODBC | This must be set to 1 in order for the compiler to compile the wxODBC classes. Without setting this to 1, there will be no access to any of the wxODBC classes. The default is 0. |
wxODBC_FWD_ONLY_CURSORS | When a new database connection is requested, this setting controls the default of whether the connection allows only forward scrolling cursors, or forward and backward scrolling cursors (see the section in "WHERE TO START" on cursors for more information on cursors). This default can be overridden by passing a second parameter to either the wxDbGetConnection or wxDb constructor. The default is 1. |
wxODBC_BACKWARD_COMPATABILITY | Between v2.0 and 2.2, massive renaming efforts were done to the ODBC classes to get naming conventions similar to those used throughout wxWidgets, as well as to preface all wxODBC classes names and functions with a wxDb preface. Because this renaming would affect applications written using the v2.0 names, this compile-time directive was added to allow those programs written for v2.0 to still compile using the old naming conventions. These deprecated names are all #define'd to their corresponding new function names at the end of the db.cpp/dbtable.cpp source files. These deprecated class/function names should not be used in future development, as at some point in the future they will be removed. The default is 0. |
Under MS Windows
You are required to include the "odbc32.lib" provided by your compiler vendor in the list of external libraries to be linked in. If using the makefiles supplied with wxWidgets, this library should already be included for use with makefile.b32, makefile.vc, and makefile.g95.
You cannot compile the wxODBC classes under Win16 - sorry.
Under Unix --with-odbc flag for configure
To use the classes in an application, there are eight basic steps:
Following each of these steps is detailed to explain the step, and to hopefully mention as many of the pitfalls that beginning users fall in to when first starting to use the classes. Throughout the steps, small snippets of code are provided to show the syntax of performing the step. A complete code snippet is provided at the end of this overview that shows a complete working flow of all these steps (see wxODBC - Sample Code #1).
Define datasource connection information
To be able to connect to a datasource through the ODBC driver, a program must supply a minimum of three pieces of information: Datasource name, User ID, and Authorization string (password). A fourth piece of information, a default directory indicating where the data file is stored, is required for Text and dBase drivers for ODBC.
The wxWidgets data class wxDbConnectInf exists for holding all of these values, plus some others that may be desired.
The 'Henv' member is the environment handle used to access memory for use by the ODBC driver. Use of this member is described below in the "Getting a Connection to the Datasource" section.
The 'Dsn' must exactly match the datasource name used to configure the ODBC datasource (in the ODBC Administrator (MSW only) or in the .odbc.ini file).
The 'Uid' is the User ID that is to be used to log in to the datasource. This User ID must already have been created and assigned rights within the datasource to which you are connecting. The user that the connection is establish by will determine what rights and privileges the datasource connection will allow the program to have when using the connection that this connection information was used to establish. Some datasources are case sensitive for User IDs, and though the wxODBC classes attempt to hide this from you by manipulating whatever data you pass in to match the datasource's needs, it is always best to pass the 'Uid' in the case that the datasource requires.
The 'AuthStr' is the password for the User ID specified in the 'Uid' member. As with the 'Uid', some datasources are case sensitive (in fact most are). The wxODBC classes do NOT try to manage the case of the 'AuthStr' at all. It is passed verbatim to the datasource, so you must use the case that the datasource is expecting.
The 'defaultDir' member is used with file based datasources (i.e. dBase, FoxPro, text files). It contains a full path to the location where the data table or file is located. When setting this value, use forward slashes '/' rather than backslashes ' avoid compatibility differences between ODBC drivers.
The other fields are currently unused. The intent of these fields are that they will be used to write our own ODBC Administrator type program that will work on both MSW and Un*x systems, regardless of the datasource. Very little work has been done on this to date.
Get a Datasource Connection
There are two methods of establishing a connection to a datasource. You may either manually create your own wxDb instance and open the connection, or you may use the caching functions provided with the wxODBC classes to create/maintain/delete the connections.
Regardless of which method you use, you must first have a fully populated wxDbConnectInf object. In the wxDbConnectInf instance, provide a valid Dns, Uid, and AuthStr (along with a 'defaultDir' if necessary). Before using this though, you must allocate an environment handle to the 'Henv' member.
wxDbConnectInf DbConnectInf; DbConnectInf.SetDsn("MyDSN"); DbConnectInf.SetUserID("MyUserName"); DbConnectInf.SetPassword("MyPassword"); DbConnectInf.SetDefaultDir("");To allocate an environment handle for the ODBC connection to use, the wxDbConnectInf class has a datasource independent method for creating the necessary handle:
if (DbConnectInf.AllocHenv()) { wxMessageBox("Unable to allocate an ODBC environment handle", "DB CONNECTION ERROR", wxOK | wxICON_EXCLAMATION); return; }When the wxDbConnectInf::AllocHenv() function is called successfully, a value of TRUE will be returned. A value of FALSE means allocation failed, and the handle will be undefined.
A shorter form of doing the above steps is encapsulated into the long form of the constructor for wxDbConnectInf.
wxDbConnectInf *DbConnectInf; DbConnectInf = new wxDbConnectInf(NULL, "MyDSN", "MyUserName", "MyPassword", "");This shorthand form of initializing the constructor passes a NULL for the SQL environment handle, telling the constructor to allocate a handle during construction. This handle is also managed for the life of wxDbConnectInf instance, and is freed automatically upon destruction of the instance.
Once the wxDbConnectInf instance is initialized, you are ready to connect to the datasource.
To manually create datasource connections, you must create a wxDb instance, and then open it.
wxDb *db = new wxDb(DbConnectInf->GetHenv()); opened = db->Open(DbConnectInf);The first line does the house keeping needed to initialize all the members of the wxDb class. The second line actually sends the request to the ODBC driver to open a connection to its associated datasource using the parameters supplied in the call to wxDb::Open.
A more advanced form of opening a connection is to use the connection caching functions that are included with the wxODBC classes. The caching mechanisms perform the same functions as the manual approach to opening a connection, but they also manage each connection they have created, re-using them and cleaning them up when they are closed, without you needing to do the coding.
To use the caching function wxDbGetConnection to get a connection to a datasource, simply call it with a single parameter of the type wxDbConnectInf:
db = wxDbGetConnection(DbConnectInf);The wxDb pointer that is returned is both initialized and opened. If something failed in creating or opening the connection, the return value from wxDbGetConnection will be NULL.
The connection that is returned is either a new connection, or it is a "free" connection from the cache of connections that the class maintains that was no longer in use. Any wxDb instance created with a call to wxDbGetConnection is recorded in a linked list of established connections. When a program is finished with a connection, a call to wxDbFreeConnection is made, and the datasource connection will then be tagged as FREE, making it available for the next call to wxDbGetConnection that needs a connection using the same connection information (Dsn, Uid, AuthStr). The cached connections remain cached until a call to wxDbCloseConnections is made, at which time all cached connections are closed and deleted.
Besides the obvious advantage of using the single command caching routine to obtain a datasource connection, using cached connections can be quite a performance boost as well. Each time that a new connection is created (not retrieved from the cache of free connections), the wxODBC classes perform many queries against the datasource to determine the datasource's datatypes and other fundamental behaviours. Depending on the hardware, network bandwidth, and datasource speed, this can in some cases take a few seconds to establish the new connection (with well-balanced systems, it should only be a fraction of a second). Re-using already established datasource connections rather than creating/deleting, creating/deleting connections can be quite a time-saver.
Another time-saver is the "copy connection" features of both wxDb::Open and wxDbGetConnection. If manually creating a wxDb instance and opening it, you must pass an existing connection to the wxDb::Open function yourself to gain the performance benefit of copying existing connection settings. The wxDbGetConnection function automatically does this for you, checking the Dsn, Uid, and AuthStr parameters when you request a connection for any existing connections that use those same settings. If one is found, wxDbGetConnection copies the datasource settings for datatypes and other datasource specific information that was previously queried, rather than re-querying the datasource for all those same settings.
One final note on creating a connection. When a connection is created, it will default to only allowing cursor scrolling to be either forward only, or both backward and forward scrolling. The default behavior is determined by the setting wxODBC_FWD_ONLY_CURSORS in setup.h when you compile the wxWidgets library. The library default is to only support forward scrolling cursors only, though this can be overridden by parameters for wxDb() constructor or the wxDbGetConnection function. All datasources and ODBC drivers must support forward scrolling cursors. Many datasources support backward scrolling cursors, and many ODBC drivers support backward scrolling cursors. Before planning on using backward scrolling cursors, you must be certain that both your datasource and ODBC driver fully support backward scrolling cursors. See the small blurb about "Scrolling cursors" in the definitions at the beginning of this overview, or other details of setting the cursor behavior in the wxDb class documentation.
Create Table Definition
Data can be accessed in a datasource's tables directly through various functions of the wxDb class (see wxDb::GetData). But to make life much simpler, the wxDbTable class encapsulates all of the SQL specific API calls that would be necessary to do this, wrapping it in an intuitive class of APIs.
The first step in accessing data in a datasource's tables via the wxDbTable class is to create a wxDbTable instance.
table = new wxDbTable(db, tableName, numTableColumns, "", !wxDB_QUERY_ONLY, "");When you create the instance, you indicate the previously established datasource connection to be used to access the table, the name of the primary table that is to be accessed with the datasource's tables, how many columns of each row are going to be returned, the name of the view of the table that will actually be used to query against (works with Oracle only at this time), whether the data returned is for query purposes only, and finally the path to the table, if different than the path specified when connecting to the datasource.
Each of the above parameters are described in detail in the wxDbTable class' description, but one special note here about the fifth parameter - the queryOnly setting. If a wxDbTable instance is created as wxDB_QUERY_ONLY, then no inserts/deletes/updates can be performed using this instance of the wxDbTable. Any calls to wxDb::CommitTrans or wxDb::RollbackTrans against the datasource connection used by this wxDbTable instance are ignored by this instance. If the wxDbTable instance is created with !wxDB_QUERY_ONLY as shown above, then all the cursors and other overhead associated with being able to insert/update/delete data in the table are created, and thereby those operations can then be performed against the associated table with this wxDbTable instance.
If a table is to be accessed via a wxDbTable instance, and the table will only be read from, not written to, there is a performance benefit (not as many cursors need to be maintained/updated, hence speeding up access times), as well as a resource savings due to fewer cursors being created for the wxDbTable instance. Also, with some datasources, the number of simultaneous cursors is limited.
When defining the columns to be retrievable by the wxDbTable instance, you can specify anywhere from one column up to all columns in the table.
table->SetColDefs(0, "FIRST_NAME", DB_DATA_TYPE_VARCHAR, FirstName, SQL_C_CHAR, sizeof(name), TRUE, TRUE); table->SetColDefs(1, "LAST_NAME", DB_DATA_TYPE_VARCHAR, LastName, SQL_C_CHAR, sizeof(LastName), TRUE, TRUE);Notice that column definitions start at index 0 and go up to one less than the number of columns specified when the wxDbTable instance was created (in this example, two columns - one with index 0, one with index 1).
The above lines of code "bind" the datasource columns specified to the memory variables in the client application. So when the application makes a call to wxDbTable::GetNext (or any other function that retrieves data from the result set), the variables that are bound to the columns will have the column value stored into them. See the wxDbTable::SetColDefs class documentation for more details on all the parameters for this function.
The bound memory variables have undefined data in them until a call to a function that retrieves data from a result set is made (e.g. wxDbTable::GetNext, wxDbTable::GetPrev, etc). The variables are not initialized to any data by the wxODBC classes, and they still contain undefined data after a call to wxDbTable::Query. Only after a successful call to one of the ::GetXxxx() functions is made do the variables contain valid data.
It is not necessary to define column definitions for columns whose data is not going to be returned to the client. For example, if you want to query the datasource for all users with a first name of 'GEORGE', but you only want the list of last names associated with those rows (why return the FIRST_NAME column every time when you already know it is 'GEORGE'), you would only have needed to define one column above.
You may have as many wxDbTable instances accessing the same table using the same wxDb instance as you desire. There is no limit imposed by the classes on this. All datasources supported (so far) also have no limitations on this.
Open the table
Opening the table is not technically doing anything with the datasource itself. Calling wxDbTable::Open simply does all the housekeeping of checking that the specified table exists, that the current connected user has at least SELECT privileges for accessing the table, setting up the requisite cursors, binding columns and cursors, and constructing the default INSERT statement that is used when a new row is inserted into the table (non-wxDB_QUERY_ONLY tables only).
if (!table->Open()) { // An error occurred opening (setting up) the table }The only reason that a call to wxDbTable::Open is likely to fail is if the user has insufficient privileges to even SELECT the table. Other problems could occur, such as being unable to bind columns, but these other reason point to some lack of resource (like memory). Any errors generated internally in the wxDbTable::Open function are logged to the error log if SQL logging is turned on for the classes.
Use the table
To use the table and the definitions that are now set up, we must first define what data we want the datasource to collect in to a result set, tell it where to get the data from, and in what sequence we want the data returned.
// the WHERE clause limits/specifies which rows in the table // are to be returned in the result set table->SetWhereClause("FIRST_NAME = 'GEORGE'"); // Result set will be sorted in ascending alphabetical // order on the data in the 'LAST_NAME' column of each row // If the same last name is in the table for two rows, // sub-sort on the 'AGE' column table->SetOrderByClause("LAST_NAME, AGE"); // No other tables (joins) are used for this query table->SetFromClause("");The above lines will be used to tell the datasource to return in the result all the rows in the table whose column "FIRST_NAME" contains the name 'GEORGE' (note the required use of the single quote around the string literal) and that the result set will return the rows sorted by ascending last names (ascending is the default, and can be overridden with the "DESC" keyword for datasources that support it - "LAST_NAME DESC").
Specifying a blank WHERE clause will result in the result set containing all rows in the datasource.
Specifying a blank ORDERBY clause means that the datasource will return the result set in whatever sequence it encounters rows which match the selection criteria. What this sequence is can be hard to determine. Typically it depends on the index that the datasource used to find the rows which match the WHERE criteria. BEWARE - relying on the datasource to return data in a certain sequence when you have not provided an ORDERBY clause will eventually cause a problem for your program. Databases can be tuned to be COST-based, SPEED-based, or some other basis for how it gets your result set. In short, if you need your result set returned in a specific sequence, ask for it that way by providing an ORDERBY clause.
Using an ORDERBY clause can be a performance hit, as the database must sort the items before making the result set available to the client. Creating efficient indexes that cause the data to be "found" in the correct ORDERBY sequence can be a big performance benefit. Also, in the large majority of cases, the database will be able to sort the records faster than your application can read all the records in (unsorted) and then sort them. Let the database do the work for you!
Notice in the example above, a column that is not included in the bound data columns ('AGE') will be used to sub-sort the result set.
The FROM clause in this example is blanked, as we are not going to be performing any table joins with this simple query. When the FROM clause is blank, it is assumed that all columns referenced are coming from the default table for the wxDbTable instance.
After the selection criteria have been specified, the program can now ask the datasource to perform the search and create a result set that can be retrieved:
// Instruct the datasource to perform a query based on the // criteria specified above in the where/orderBy/from clauses. if (!table->Query()) { // An error occurred performing the query }Typically, when an error occurs when calling wxDbTable::Query, it is a syntax problem in the WHERE clause that was specified. The exact SQL (datasource-specific) reason for what caused the failure of wxDbTable::Query (and all other operations against the datasource can be found by parsing the table's database connection's "errorList[]" array member for the stored text of the error.
When the wxDbTable::Query returns TRUE, the database was able to successfully complete the requested query using the provided criteria. This does not mean that there are any rows in the result set, it just mean that the query was successful.
To retrieve the data, one of the data fetching routines must be used to request a row from the result set, and to store the data from the result set into the bound memory variables. After wxDbTable::Query has completed successfully, the default/current cursor is placed so it is pointing just before the first record in the result set. If the result set is empty (no rows matched the criteria), then any calls to retrieve data from the result set will return FALSE.
wxString msg; while (table->GetNext()) { msg.Printf("Row #%lu -- First Name : %s Last Name is %s", table->GetRowNum(), FirstName, LastName); wxMessageBox(msg, "Data", wxOK | wxICON_INFORMATION, NULL); }The sample code above will read the next record in the result set repeatedly until the end of the result set has been reached. The first time that wxDbTable::GetNext is called right after the successful call to wxDbTable::Query, it actually returns the first record in the result set.
When wxDbTable::GetNext is called and there are no rows remaining in the result set after the current cursor position, wxDbTable::GetNext (as well as all the other wxDbTable::GetXxxxx() functions) will return FALSE.
Close the table
When the program is done using a wxDbTable instance, it is as simple as deleting the table pointer (or if declared statically, letting the variable go out of scope). Typically the default destructor will take care of all that is required for cleaning up the wxDbTable instance.
if (table) { delete table; table = NULL; }Deleting a wxDbTable instance releases all of its cursors, deletes the column definitions and frees the SQL environment handles used by the table (but not the environment handle used by the datasource connection that the wxDbTable instance was using).
Close the datasource connection
After all tables that have been using a datasource connection have been closed (this can be verified by calling wxDb::GetTableCount and checking that it returns 0), then you may close the datasource connection. The method of doing this is dependent on whether the non-caching or caching method was used to obtain the datasource connection.
If the datasource connection was created manually (non-cached), closing the connection is done like this:
if (db) { db->Close(); delete db; db = NULL; }If the program used the wxDbGetConnection function to get a datasource connection, the following is the code that should be used to free the connection(s):
if (db) { wxDbFreeConnection(db); db = NULL; }Note that the above code just frees the connection so that it can be re-used on the next call the wxDbGetConnection. To actually dispose of the connection, releasing all of its resources (other than the environment handle), do the following:
wxDbCloseConnections();Release the ODBC environment handle
Once all of the connections that used the ODBC environment handle (in this example it was stored in "DbConnectInf.Henv") have been closed, then it is safe to release the environment handle:
DbConnectInf->FreeHenv();Or, if the long form of the constructor was used and the constructor was allowed to allocate its own SQL environment handle, leaving scope or destruction of the wxDbConnectInf will free the handle automatically.
delete DbConnectInf;
As with creating wxWidgets, writing the wxODBC classes was not the simple task of writing an application to run on a single type of computer system. The classes need to be cross-platform for different operating systems, and they also needed to take in to account different database manufacturers and different ODBC driver manufacturers. Because of all the possible combinations of OS/database/drivers, it is impossible to say that these classes will work perfectly with datasource ABC, ODBC driver XYZ, on platform LMN. You may run in to some incompatibilities or unsupported features when moving your application from one environment to another. But that is what makes cross-platform programming fun. It is also pinpoints one of the great things about open source software. It can evolve!
The most common difference between different database/ODBC driver manufacturers in regards to these wxODBC classes is the lack of standard error codes being returned to the calling program. Sometimes manufacturers have even changed the error codes between versions of their databases/drivers.
In all the tested databases, every effort has been made to determine the correct error codes and handle them in the class members that need to check for specific error codes (such as TABLE DOES NOT EXIST when you try to open a table that has not been created yet). Adding support for additional databases in the future requires adding an entry for the database in the wxDb::Dbms function, and then handling any error codes returned by the datasource that do not match the expected values.
Databases
Following is a list of known issues and incompatibilities that the wxODBC classes have between different datasources. An up to date listing of known issues can be seen in the comments of the source for wxDb::Dbms.
ORACLE
DBASE
NOTE: dBase is not a true ODBC datasource. You only have access to as much functionality as the driver can emulate.
SYBASE (all)
SYBASE (Enterprise)
mySQL
POSTGRES
DB2
UNICODE with wxODBC classes
The ODBC classes support for Unicode is yet in early experimental stage and hasn't been tested extensively. It might work for you or it might not: please report the bugs/problems you have encountered in the latter case.
Simplest example of establishing/opening a connection to an ODBC datasource, binding variables to the columns for read/write usage, opening an existing table in the datasource, setting the query parameters (where/orderBy/from), querying the datasource, reading each row of the result set, then cleaning up.
NOTE: Not all error trapping is shown here, to reduce the size of the code and to make it more easily readable.
wxDbConnectInf *DbConnectInf = NULL; wxDb *db = NULL; // The database connection wxDbTable *table = NULL; // The data table to access wxChar FirstName[50+1]; // buffer for data from column "FIRST_NAME" wxChar LastName[50+1]; // buffer for data from column "LAST_NAME" bool errorOccured = FALSE; const wxChar tableName[] = "CONTACTS"; const UWORD numTableColumns = 2; // Number of bound columns FirstName[0] = 0; LastName[0] = 0; DbConnectInf = new wxDbConnectInf(NULL,"MyDSN","MyUserName", "MyPassword"); if (!DbConnectInf || !DbConnectInf->GetHenv()) { wxMessageBox("Unable to allocate an ODBC environment handle", "DB CONNECTION ERROR", wxOK | wxICON_EXCLAMATION); return; } // Get a database connection from the cached connections db = wxDbGetConnection(DbConnectInf); // Create the table connection table = new wxDbTable(db, tableName, numTableColumns, "", !wxDB_QUERY_ONLY, ""); // // Bind the columns that you wish to retrieve. Note that there must be // 'numTableColumns' calls to SetColDefs(), to match the wxDbTable definition // // Not all columns need to be bound, only columns whose values are to be // returned back to the client. // table->SetColDefs(0, "FIRST_NAME", DB_DATA_TYPE_VARCHAR, FirstName, SQL_C_CHAR, sizeof(name), TRUE, TRUE); table->SetColDefs(1, "LAST_NAME", DB_DATA_TYPE_VARCHAR, LastName, SQL_C_CHAR, sizeof(LastName), TRUE, TRUE); // Open the table for access table->Open(); // Set the WHERE clause to limit the result set to only // return all rows that have a value of 'GEORGE' in the // FIRST_NAME column of the table. table->SetWhereClause("FIRST_NAME = 'GEORGE'"); // Result set will be sorted in ascending alphabetical // order on the data in the 'LAST_NAME' column of each row table->SetOrderByClause("LAST_NAME"); // No other tables (joins) are used for this query table->SetFromClause(""); // Instruct the datasource to perform a query based on the // criteria specified above in the where/orderBy/from clauses. if (!table->Query()) { wxMessageBox("Error on Query()","ERROR!", wxOK | wxICON_EXCLAMATION); errorOccured = TRUE; } wxString msg; // Start and continue reading every record in the table // displaying info about each record read. while (table->GetNext()) { msg.Printf("Row #%lu -- First Name : %s Last Name is %s", table->GetRowNum(), FirstName, LastName); wxMessageBox(msg, "Data", wxOK | wxICON_INFORMATION, NULL); } // If the wxDbTable instance was successfully created // then delete it as I am done with it now. if (table) { delete table; table = NULL; } // If we have a valid wxDb instance, then free the connection // (meaning release it back in to the cache of datasource // connections) for the next time a call to wxDbGetConnection() // is made. if (db) { wxDbFreeConnection(db); db = NULL; } // The program is now ending, so we need to close // any cached connections that are still being // maintained. wxDbCloseConnections(); // Release the environment handle that was created // for use with the ODBC datasource connections delete DbConnectInf;
Classes: wxDatabase, wxRecordSet, wxQueryCol, wxQueryField
Documentation for the old wxDatabase class and its associated classes is still included in the class documentation and in this overview section, but support for these old classes has been phased out, and all future development work is being done solely on the new wxDb/wxDbTable classes.
wxWidgets provides a set of classes for accessing a subset of Microsoft's ODBC (Open Database Connectivity) product. Currently, this wrapper is available under MS Windows only, although ODBC may appear on other platforms, and a generic or product-specific SQL emulator for the ODBC classes may be provided in wxWidgets at a later date.
ODBC presents a unified API (Application Programmer's Interface) to a wide variety of databases, by interfacing indirectly to each database or file via an ODBC driver. The language for most of the database operations is SQL, so you need to learn a small amount of SQL as well as the wxWidgets ODBC wrapper API. Even though the databases may not be SQL-based, the ODBC drivers translate SQL into appropriate operations for the database or file: even text files have rudimentary ODBC support, along with dBASE, Access, Excel and other file formats.
The run-time files for ODBC are bundled with many existing database packages, including MS Office. The required header files, sql.h and sqlext.h, are bundled with several compilers including MS VC++ and Watcom C++. The only other way to obtain these header files is from the ODBC SDK, which is only available with the MS Developer Network CD-ROMs -- at great expense. If you have odbc.dll, you can make the required import library odbc.lib using the tool 'implib'. You need to have odbc.lib in your compiler library path.
The minimum you need to distribute with your application is odbc.dll, which must go in the Windows system directory. For the application to function correctly, ODBC drivers must be installed on the user's machine. If you do not use the database classes, odbc.dll will be loaded but not called (so ODBC does not need to be setup fully if no ODBC calls will be made).
A sample is distributed with wxWidgets in samples/odbc. You will need to install the sample dbf file as a data source using the ODBC setup utility, available from the control panel if ODBC has been fully installed.
You first need to create a wxDatabase object. If you want to get information from the ODBC manager instead of from a particular database (for example using wxRecordSet::GetDataSources), then you do not need to call wxDatabase::Open. If you do wish to connect to a datasource, then call wxDatabase::Open. You can reuse your wxDatabase object, calling wxDatabase::Close and wxDatabase::Open multiple times.
Then, create a wxRecordSet object for retrieving or sending information. For ODBC manager information retrieval, you can create it as a dynaset (retrieve the information as needed) or a snapshot (get all the data at once). If you are going to call wxRecordSet::ExecuteSQL, you need to create it as a snapshot. Dynaset mode is not yet implemented for user data.
Having called a function such as wxRecordSet::ExecuteSQL or wxRecordSet::GetDataSources, you may have a number of records associated with the recordset, if appropriate to the operation. You can now retrieve information such as the number of records retrieved and the actual data itself. Use wxRecordSet::GetFieldData or wxRecordSet::GetFieldDataPtr to get the data or a pointer to it, passing a column index or name. The data returned will be for the current record. To move around the records, use wxRecordSet::MoveNext, wxRecordSet::MovePrev and associated functions.
You can use the same recordset for multiple operations, or delete the recordset and create a new one.
Note that when you delete a wxDatabase, any associated recordsets also get deleted, so beware of holding onto invalid pointers.
Class: wxDatabase
DEPRECATED
Use wxDb and wxDbTable instead.
Every database object represents an ODBC connection. To do anything useful with a database object you need to bind a wxRecordSet object to it. All you can do with wxDatabase is opening/closing connections and getting some info about it (users, passwords, and so on).
See also
Class: wxQueryCol
DEPRECATED
Use wxDb and wxDbTable instead.
Every data column is represented by an instance of this class. It contains the name and type of a column and a list of wxQueryFields where the real data is stored. The links to user-defined variables are stored here, as well.
See also
Class: wxQueryField
DEPRECATED
Use wxDb and wxDbTable instead.
As every data column is represented by an instance of the class wxQueryCol, every data item of a specific column is represented by an instance of wxQueryField. Each column contains a list of wxQueryFields. If wxRecordSet is of the type wxOPEN_TYPE_DYNASET, there will be only one field for each column, which will be updated every time you call functions like wxRecordSet::Move or wxRecordSet::GoTo. If wxRecordSet is of the type wxOPEN_TYPE_SNAPSHOT, all data returned by an ODBC function will be loaded at once and the number of wxQueryField instances for each column will depend on the number of records.
See also
Class: wxRecordSet
DEPRECATED
Use wxDb and wxDbTable instead.
Each wxRecordSet represents a database query. You can make multiple queries at a time by using multiple wxRecordSets with a wxDatabase or you can make your queries in sequential order using the same wxRecordSet.
See also
These are the data types supported in ODBC SQL. Note that there are other, extended level conformance types, not currently supported in wxWidgets.
CHAR(n) | A character string of fixed length n. |
VARCHAR(n) | A varying length character string of maximum length n. |
LONG VARCHAR(n) | A varying length character string: equivalent to VARCHAR for the purposes of ODBC. |
DECIMAL(p, s) | An exact numeric of precision p and scale s. |
NUMERIC(p, s) | Same as DECIMAL. |
SMALLINT | A 2 byte integer. |
INTEGER | A 4 byte integer. |
REAL | A 4 byte floating point number. |
FLOAT | An 8 byte floating point number. |
DOUBLE PRECISION | Same as FLOAT. |
These data types correspond to the following ODBC identifiers:
SQL_CHAR | A character string of fixed length. |
SQL_VARCHAR | A varying length character string. |
SQL_DECIMAL | An exact numeric. |
SQL_NUMERIC | Same as SQL_DECIMAL. |
SQL_SMALLINT | A 2 byte integer. |
SQL_INTEGER | A 4 byte integer. |
SQL_REAL | A 4 byte floating point number. |
SQL_FLOAT | An 8 byte floating point number. |
SQL_DOUBLE | Same as SQL_FLOAT. |
See also
The following is a very brief description of some common SQL commands, with examples.
See also
Creates a table.
Example:
CREATE TABLE Book (BookNumber INTEGER PRIMARY KEY , CategoryCode CHAR(2) DEFAULT 'RO' NOT NULL , Title VARCHAR(100) UNIQUE , NumberOfPages SMALLINT , RetailPriceAmount NUMERIC(5,2) )
Inserts records into a table.
Example:
INSERT INTO Book (BookNumber, CategoryCode, Title) VALUES(5, 'HR', 'The Lark Ascending')
The Select operation retrieves rows and columns from a table. The criteria for selection and the columns returned may be specified.
Examples:
SELECT * FROM Book
Selects all rows and columns from table Book.
SELECT Title, RetailPriceAmount FROM Book WHERE RetailPriceAmount > 20.0
Selects columns Title and RetailPriceAmount from table Book, returning only the rows that match the WHERE clause.
SELECT * FROM Book WHERE CatCode = 'LL' OR CatCode = 'RR'
Selects all columns from table Book, returning only the rows that match the WHERE clause.
SELECT * FROM Book WHERE CatCode IS NULL
Selects all columns from table Book, returning only rows where the CatCode column is NULL.
SELECT * FROM Book ORDER BY Title
Selects all columns from table Book, ordering by Title, in ascending order. To specify descending order, add DESC after the ORDER BY Title clause.
SELECT Title FROM Book WHERE RetailPriceAmount >= 20.0 AND RetailPriceAmount <= 35.0
Selects records where RetailPriceAmount conforms to the WHERE expression.
Updates records in a table.
Example:
UPDATE Incident SET X = 123 WHERE ASSET = 'BD34'
This example sets a field in column 'X' to the number 123, for the record where the column ASSET has the value 'BD34'.