Thursday, September 11, 2008

Showing rows

After showing how to use a rowset to count table rows, let's see how we can use it to actually display the table rows. To illustrate this I wrote a sample (OpenTable) that uses tha last sample's databases and displays the "Orders" table rows in a list view control. As you will see, the sample uses a very basic (and slow) approach to filling in the list view, but this issue will be addressed on a later post when I will discuss bookmarks and their uses. Now, let's see what's new in the code.

First of all, I changed the way a CTable is opened in order to produce a CRowset. Now you can see a new method called CTable::Open that returns a reference to an open CRowset object as its last parameter. This method calls IOpenRowset::OpenRowset and upon success calls the changed CRowset::Open method. As you can see, this method just takes an IRowset pointer as parameter and does the bulk of the work of binding the table columns to a local data buffer.

The method starts by requesting an IColumnsInfo interface from the IRowset via QueryInterface and uses it to retrieve the table column schema information via a call to GetColumnInfo. This information is stored in the provider-owned m_pColumnsInfo (DBCOLUMNINFO*) while the column names are stored in a single OLECHAR buffer (m_pColumnNames), also provider-owned memory.

Now the rowset knows exactly what columns to bind to. Note that if you want to implement a custom column binding (like binding to a subset of columns, or specifying data type conversions on binding), you must replace this step and provide your own set of column specifications. For the time being we will stick to the reported schema information.

To bind the rowset data to your application's buffer, you must build an array of DBBINDING structures and fill it with appropriate data. Then you feed this to a newly-acquired IAccessor interface in order to create an accessor handle. Finally you can use the accessor handle with IRowset::GetData to fill your application's buffer with data from the current row (the row handle must not be NULL and must be valid).

Each entry in the DBBINDING array describes how each column will be bound. This assumes that the row data will be stored somewhere in your application's data space and that each column value, lenght and status will be stored at fixed offsets. It also defines how the data types will be mapped between the provider and the consumer (in this case there is no conversion) and also how BLOBs are handled (more on this on a later post). As you can see from the sample code, the binding assumes that you are placing all the row data in a large buffer where each column binding starts with the column length (DWORD), followed by the column status (also a DWORD) and finally the raw column data (variable size). Note how each binding field is DWORD aligned to avoid alignment faults. This is a very similar approach to the CDynamicAccessor class template from the ATL OLE DB Consumer Templates. An alternative approach would be to build your own structure to store the row data and feed the DBBINDING array with your structure's offsets.

Finally, the code allocates the row data buffer and a DBBINDSTATUS array to store any column binding errors or statues. The code then gets the IAccessor interface and calls CreateAccessor. If all goes well, you can start retrieving data from the table by using the slightly modified MoveFirst and MoveNext methods. These now call IRowset::GetData with the current row handle and the created accessor handle to fill in the data buffer (now we lost the performance edge that allowed us to very quickly count rows, but I will address that later).

To retrieve individual column data from the data buffer, the CRowset class now has three new mehods: GetStatus (retrieves the column status), GetLength (retrieves the column length) and GetValue (retrieves a raw BYTE pointer to the data). The sample code only recognizes two data types: DBTYPE_I4 (int) and DBTYPE_WSTR (WCHAR*) so you will see some empty columns in the list view. I will start covering some of the more exotic data types on the next post, so stay tuned.

No comments: