1145 lines
37 KiB
C++
1145 lines
37 KiB
C++
//---------------------------------------------------------------------------
|
|
// Microsoft OLE DB Programmer's Reference Sample
|
|
// Copyright (C) 1998 By Microsoft Corporation.
|
|
//
|
|
// @doc
|
|
//
|
|
// @module ROWSET.CPP
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// Includes
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
#include "prsample.h" // Programmer's Reference Sample includes
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// myCreateRowset
|
|
//
|
|
// This function creates an OLE DB Rowset object from the given
|
|
// provider's Session object. It first obtains a default table
|
|
// name from the user through the tables schema rowset, if
|
|
// supported, then creates a Rowset object by one of two methods:
|
|
//
|
|
// - If the user requested that the Rowset object be created
|
|
// from a Command object, it creates a Command object, then
|
|
// obtains command text from the user, sets properties and
|
|
// the command text, and finally executes the command to
|
|
// create the Rowset object
|
|
// - Otherwise, the function obtains a table name from the user
|
|
// and calls IOpenRowset::OpenRowset to create a Rowset object
|
|
// over that table that supports the requested properties
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
HRESULT myCreateRowset
|
|
(
|
|
IUnknown * pUnkSession,
|
|
IUnknown ** ppUnkRowset
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
IUnknown * pUnkCommand = NULL;
|
|
IOpenRowset * pIOpenRowset = NULL;
|
|
WCHAR wszTableName[MAX_NAME_LEN + 1] = {0};
|
|
|
|
const ULONG cProperties = 2;
|
|
DBPROP rgProperties[cProperties];
|
|
DBPROPSET rgPropSets[1];
|
|
|
|
// Obtain a default table name from the user by displaying the
|
|
// tables schema rowset if schema rowsets are supported.
|
|
CHECK_HR(hr = myCreateSchemaRowset(DBSCHEMA_TABLES, pUnkSession,
|
|
MAX_NAME_LEN, wszTableName));
|
|
|
|
// Set properties on the rowset, to request additional functionality
|
|
myAddRowsetProperties(rgPropSets, cProperties, rgProperties);
|
|
|
|
// If the user requested that the rowset be created from a
|
|
// Command object, create a Command, set its properties and
|
|
// text and execute it to create the Rowset object
|
|
if( g_dwFlags & USE_COMMAND )
|
|
{
|
|
WCHAR wszCommandText[MAX_NAME_LEN + 1];
|
|
|
|
// Attempt to create the Command object from the provider's
|
|
// Session object. Note that Commands are not supported by
|
|
// all providers, and this will fail in that case
|
|
CHECK_HR(hr = myCreateCommand(pUnkSession, &pUnkCommand));
|
|
|
|
// Get the command text that we will execute from the user
|
|
if( !myGetInputFromUser(wszCommandText, _countof(wszCommandText), L"\nType the command "
|
|
L"to execute [Enter = `select * from %s`]: ", wszTableName) )
|
|
{
|
|
swprintf(wszCommandText, _countof(wszCommandText), L"select * from %s", wszTableName);
|
|
}
|
|
|
|
// And execute the Command the user entered
|
|
CHECK_HR(hr = myExecuteCommand(pUnkCommand, wszCommandText,
|
|
1, rgPropSets, ppUnkRowset));
|
|
}
|
|
// Otherwise, the user gets the default behavior, which is to use
|
|
// IOpenRowset to create the Rowset object from the Session object.
|
|
// IOpenRowset is supported by all providers; it takes a TableID
|
|
// and creates a rowset containing all rows in that table. It is
|
|
// similar to using SQL command text of "select * from TableID"
|
|
else
|
|
{
|
|
DBID TableID;
|
|
|
|
// Create the TableID
|
|
TableID.eKind = DBKIND_NAME;
|
|
TableID.uName.pwszName = wszTableName;
|
|
|
|
// Obtain the table name from the user
|
|
myGetInputFromUser(wszTableName, _countof(wszTableName),L"\nType the name of the table "
|
|
L"to use [Enter = `%s`]: ", wszTableName);
|
|
|
|
// Get the IOpenRowset interface and create a Rowset object
|
|
// over the requested table through OpenRowset
|
|
XCHECK_HR(hr = pUnkSession->QueryInterface(
|
|
IID_IOpenRowset, (void**)&pIOpenRowset));
|
|
XCHECK_HR(hr = pIOpenRowset->OpenRowset(
|
|
NULL, //pUnkOuter
|
|
&TableID, //pTableID
|
|
NULL, //pIndexID
|
|
IID_IRowset, //riid
|
|
1, //cPropSets
|
|
rgPropSets, //rgPropSets
|
|
ppUnkRowset //ppRowset
|
|
));
|
|
}
|
|
|
|
CLEANUP:
|
|
if( pIOpenRowset )
|
|
pIOpenRowset->Release();
|
|
if( pUnkCommand )
|
|
pUnkCommand->Release();
|
|
return hr;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// mySetupBindings
|
|
//
|
|
// This function takes an IUnknown pointer from a Rowset object
|
|
// and creates a bindings array that describes how we want the
|
|
// data we fetch from the Rowset to be laid out in memory. It
|
|
// also calculates the total size of a row so that we can use
|
|
// this to allocate memory for the rows that we will fetch
|
|
// later.
|
|
//
|
|
// For each column in the Rowset, there will be a corresponding
|
|
// element in the bindings array that describes how the
|
|
// provider should transfer the data, including length and
|
|
// status, for that column. This element also specifies the data
|
|
// type that the provider should return the column as. We will
|
|
// bind all columns as DBTYPE_WSTR, with a few exceptions
|
|
// detailed below, as providers are required to support the
|
|
// conversion of their column data to this type in the vast
|
|
// majority of cases. The exception to our binding as
|
|
// DBTYPE_WSTR is if the native column data type is
|
|
// DBTYPE_IUNKNOWN or if the user has requested that BLOB
|
|
// columns be bound as ISequentialStream objects, in which case
|
|
// we will bind those columns as ISequentialStream objects.
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
HRESULT mySetupBindings
|
|
(
|
|
IUnknown * pUnkRowset,
|
|
DBORDINAL * pcBindings,
|
|
DBBINDING ** prgBindings,
|
|
DBORDINAL * pcbRowSize
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
DBORDINAL cColumns;
|
|
DBCOLUMNINFO * rgColumnInfo = NULL;
|
|
LPWSTR pStringBuffer = NULL;
|
|
IColumnsInfo * pIColumnsInfo = NULL;
|
|
|
|
ULONG iCol;
|
|
DBORDINAL dwOffset = 0;
|
|
DBBINDING * rgBindings = NULL;
|
|
|
|
ULONG cStorageObjs = 0;
|
|
BOOL fMultipleObjs = FALSE;
|
|
|
|
// Obtain the column information for the Rowset; from this, we can find
|
|
// out the following information that we need to construct the bindings
|
|
// array:
|
|
// - the number of columns
|
|
// - the ordinal of each column
|
|
// - the precision and scale of numeric columns
|
|
// - the OLE DB data type of the column
|
|
XCHECK_HR(hr = pUnkRowset->QueryInterface(
|
|
IID_IColumnsInfo, (void**)&pIColumnsInfo));
|
|
XCHECK_HR(hr = pIColumnsInfo->GetColumnInfo(
|
|
&cColumns, //pcColumns
|
|
&rgColumnInfo, //prgColumnInfo
|
|
&pStringBuffer //ppStringBuffer
|
|
));
|
|
|
|
// Allocate memory for the bindings array; there is a one-to-one
|
|
// mapping between the columns returned from GetColumnInfo and our
|
|
// bindings
|
|
rgBindings = (DBBINDING*)CoTaskMemAlloc(cColumns * sizeof(DBBINDING));
|
|
CHECK_MEMORY(hr, rgBindings);
|
|
memset(rgBindings, 0, cColumns * sizeof(DBBINDING));
|
|
|
|
// Determine if the Rowset supports multiple storage object bindings;
|
|
// if it does not, we will only bind the first BLOB column or IUnknown
|
|
// column as an ISequentialStream object, and will bind the rest as
|
|
// DBTYPE_WSTR
|
|
myGetProperty(pUnkRowset, IID_IRowset, DBPROP_MULTIPLESTORAGEOBJECTS,
|
|
DBPROPSET_ROWSET, &fMultipleObjs);
|
|
|
|
// Construct the binding array element for each column
|
|
for( iCol = 0; iCol < cColumns; iCol++ )
|
|
{
|
|
// This binding applies to the ordinal of this column
|
|
rgBindings[iCol].iOrdinal = rgColumnInfo[iCol].iOrdinal;
|
|
|
|
// We are asking the provider to give us the data for this column
|
|
// (DBPART_VALUE), the length of that data (DBPART_LENGTH), and
|
|
// the status of the column (DBPART_STATUS)
|
|
rgBindings[iCol].dwPart = DBPART_VALUE|DBPART_LENGTH|DBPART_STATUS;
|
|
|
|
// The following values are the offsets to the status, length, and
|
|
// data value that the provider will fill with the appropriate values
|
|
// when we fetch data later. When we fetch data, we will pass a
|
|
// pointer to a buffer that the provider will copy column data to,
|
|
// in accordance with the binding we have provided for that column;
|
|
// these are offsets into that future buffer
|
|
rgBindings[iCol].obStatus = dwOffset;
|
|
rgBindings[iCol].obLength = dwOffset + sizeof(DBSTATUS);
|
|
rgBindings[iCol].obValue = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG);
|
|
|
|
// Any memory allocated for the data value will be owned by us, the
|
|
// client. Note that no data will be allocated in this case, as the
|
|
// DBTYPE_WSTR bindings we are using will tell the provider to simply
|
|
// copy data directly into our provided buffer
|
|
rgBindings[iCol].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
|
|
|
|
// This is not a parameter binding
|
|
rgBindings[iCol].eParamIO = DBPARAMIO_NOTPARAM;
|
|
|
|
// We want to use the precision and scale of the column
|
|
rgBindings[iCol].bPrecision = rgColumnInfo[iCol].bPrecision;
|
|
rgBindings[iCol].bScale = rgColumnInfo[iCol].bScale;
|
|
|
|
// Bind this column as DBTYPE_WSTR, which tells the provider to
|
|
// copy a Unicode string representation of the data into our buffer,
|
|
// converting from the native type if necessary
|
|
rgBindings[iCol].wType = DBTYPE_WSTR;
|
|
|
|
// Initially, we set the length for this data in our buffer to 0;
|
|
// the correct value for this will be calculated directly below
|
|
rgBindings[iCol].cbMaxLen = 0;
|
|
|
|
// Determine the maximum number of bytes required in our buffer to
|
|
// contain the Unicode string representation of the provider's native
|
|
// data type, including room for the NULL-termination character
|
|
switch( rgColumnInfo[iCol].wType )
|
|
{
|
|
case DBTYPE_NULL:
|
|
case DBTYPE_EMPTY:
|
|
case DBTYPE_I1:
|
|
case DBTYPE_I2:
|
|
case DBTYPE_I4:
|
|
case DBTYPE_UI1:
|
|
case DBTYPE_UI2:
|
|
case DBTYPE_UI4:
|
|
case DBTYPE_R4:
|
|
case DBTYPE_BOOL:
|
|
case DBTYPE_I8:
|
|
case DBTYPE_UI8:
|
|
case DBTYPE_R8:
|
|
case DBTYPE_CY:
|
|
case DBTYPE_ERROR:
|
|
// When the above types are converted to a string, they
|
|
// will all fit into 25 characters, so use that plus space
|
|
// for the NULL-terminator
|
|
rgBindings[iCol].cbMaxLen = (25 + 1) * sizeof(WCHAR);
|
|
break;
|
|
|
|
case DBTYPE_DECIMAL:
|
|
case DBTYPE_NUMERIC:
|
|
case DBTYPE_DATE:
|
|
case DBTYPE_DBDATE:
|
|
case DBTYPE_DBTIMESTAMP:
|
|
case DBTYPE_GUID:
|
|
// Converted to a string, the above types will all fit into
|
|
// 50 characters, so use that plus space for the terminator
|
|
rgBindings[iCol].cbMaxLen = (50 + 1) * sizeof(WCHAR);
|
|
break;
|
|
|
|
case DBTYPE_BYTES:
|
|
// In converting DBTYPE_BYTES to a string, each byte
|
|
// becomes two characters (e.g. 0xFF -> "FF"), so we
|
|
// will use double the maximum size of the column plus
|
|
// include space for the NULL-terminator
|
|
rgBindings[iCol].cbMaxLen =
|
|
(rgColumnInfo[iCol].ulColumnSize * 2 + 1) * sizeof(WCHAR);
|
|
break;
|
|
|
|
case DBTYPE_STR:
|
|
case DBTYPE_WSTR:
|
|
case DBTYPE_BSTR:
|
|
// Going from a string to our string representation,
|
|
// we can just take the maximum size of the column,
|
|
// a count of characters, and include space for the
|
|
// terminator, which is not included in the column size
|
|
rgBindings[iCol].cbMaxLen =
|
|
(rgColumnInfo[iCol].ulColumnSize + 1) * sizeof(WCHAR);
|
|
break;
|
|
|
|
default:
|
|
// For any other type, we will simply use our maximum
|
|
// column buffer size, since the display size of these
|
|
// columns may be variable (e.g. DBTYPE_VARIANT) or
|
|
// unknown (e.g. provider-specific types)
|
|
rgBindings[iCol].cbMaxLen = MAX_COL_SIZE;
|
|
break;
|
|
};
|
|
|
|
// If the provider's native data type for this column is
|
|
// DBTYPE_IUNKNOWN or this is a BLOB column and the user
|
|
// has requested that we bind BLOB columns as ISequentialStream
|
|
// objects, bind this column as an ISequentialStream object if
|
|
// the provider supports our creating another ISequentialStream
|
|
// binding
|
|
if( (rgColumnInfo[iCol].wType == DBTYPE_IUNKNOWN ||
|
|
((rgColumnInfo[iCol].dwFlags & DBCOLUMNFLAGS_ISLONG) &&
|
|
(g_dwFlags & USE_ISEQSTREAM))) &&
|
|
(fMultipleObjs || !cStorageObjs) )
|
|
{
|
|
// To create an ISequentialStream object, we will
|
|
// bind this column as DBTYPE_IUNKNOWN to indicate
|
|
// that we are requesting this column as an object
|
|
rgBindings[iCol].wType = DBTYPE_IUNKNOWN;
|
|
|
|
// We want to allocate enough space in our buffer for
|
|
// the ISequentialStream pointer we will obtain from
|
|
// the provider
|
|
rgBindings[iCol].cbMaxLen = sizeof(ISequentialStream *);
|
|
|
|
// To specify the type of object that we want from the
|
|
// provider, we need to create a DBOBJECT structure and
|
|
// place it in our binding for this column
|
|
rgBindings[iCol].pObject =
|
|
(DBOBJECT *)CoTaskMemAlloc(sizeof(DBOBJECT));
|
|
CHECK_MEMORY(hr, rgBindings[iCol].pObject);
|
|
|
|
// Direct the provider to create an ISequentialStream
|
|
// object over the data for this column
|
|
rgBindings[iCol].pObject->iid = IID_ISequentialStream;
|
|
|
|
// We want read access on the ISequentialStream
|
|
// object that the provider will create for us
|
|
rgBindings[iCol].pObject->dwFlags = STGM_READ;
|
|
|
|
// Keep track of the number of storage objects (ISequentialStream
|
|
// is a storage interface) that we have requested, so that we
|
|
// can avoid requesting multiple storage objects from a provider
|
|
// that only supports a single storage object in our bindings
|
|
cStorageObjs++;
|
|
}
|
|
|
|
// Ensure that the bound maximum length is no more than the
|
|
// maximum column size in bytes that we've defined
|
|
rgBindings[iCol].cbMaxLen
|
|
= min(rgBindings[iCol].cbMaxLen, MAX_COL_SIZE);
|
|
|
|
// Update the offset past the end of this column's data, so
|
|
// that the next column will begin in the correct place in
|
|
// the buffer
|
|
dwOffset = rgBindings[iCol].cbMaxLen + rgBindings[iCol].obValue;
|
|
|
|
// Ensure that the data for the next column will be correctly
|
|
// aligned for all platforms, or, if we're done with columns,
|
|
// that if we allocate space for multiple rows that the data
|
|
// for every row is correctly aligned
|
|
dwOffset = ROUNDUP(dwOffset);
|
|
}
|
|
|
|
// Return the row size (the current dwOffset is the size of the row),
|
|
// the count of bindings, and the bindings array to the caller
|
|
*pcbRowSize = dwOffset;
|
|
*pcBindings = cColumns;
|
|
*prgBindings = rgBindings;
|
|
|
|
CLEANUP:
|
|
CoTaskMemFree(rgColumnInfo);
|
|
CoTaskMemFree(pStringBuffer);
|
|
if( pIColumnsInfo )
|
|
pIColumnsInfo->Release();
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// myCreateAccessor
|
|
//
|
|
// This function takes an IUnknown pointer for a Rowset object
|
|
// and creates an Accessor that describes the layout of the
|
|
// buffer we will use when we fetch data. The provider will fill
|
|
// this buffer according to the description contained in the
|
|
// Accessor that we will create here.
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
HRESULT myCreateAccessor
|
|
(
|
|
IUnknown * pUnkRowset,
|
|
HACCESSOR * phAccessor,
|
|
DBORDINAL * pcBindings,
|
|
DBBINDING ** prgBindings,
|
|
DBORDINAL * pcbRowSize
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
IAccessor * pIAccessor = NULL;
|
|
|
|
// An Accessor is basically a handle to a collection of bindings.
|
|
// To create the Accessor, we need to first create an array of
|
|
// bindings for the columns in the Rowset
|
|
CHECK_HR(hr = mySetupBindings(pUnkRowset, pcBindings, prgBindings,
|
|
pcbRowSize));
|
|
|
|
// Now that we have an array of bindings, tell the provider to
|
|
// create the Accessor for those bindings. We get back a handle
|
|
// to this Accessor, which we will use when fetching data
|
|
XCHECK_HR(hr = pUnkRowset->QueryInterface(
|
|
IID_IAccessor, (void**)&pIAccessor));
|
|
XCHECK_HR(hr = pIAccessor->CreateAccessor(
|
|
DBACCESSOR_ROWDATA, //dwAccessorFlags
|
|
*pcBindings, //cBindings
|
|
*prgBindings, //rgBindings
|
|
0, //cbRowSize
|
|
phAccessor, //phAccessor
|
|
NULL //rgStatus
|
|
));
|
|
|
|
CLEANUP:
|
|
if( pIAccessor )
|
|
pIAccessor->Release();
|
|
return hr;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// myDisplayRowset
|
|
//
|
|
// This function will display data from a Rowset object and will
|
|
// allow the user to perform basic navigation of the rowset.
|
|
//
|
|
// The function takes a pointer to a Rowset object's IUnknown
|
|
// and, optionally, the name of a column and a buffer that will
|
|
// receive the value of that column when the user selects a row.
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
HRESULT myDisplayRowset
|
|
(
|
|
IUnknown * pUnkRowset,
|
|
LPCWSTR pwszColToReturn,
|
|
ULONG cchBuffer,
|
|
LPWSTR pwszBuffer
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
IRowset * pIRowset = NULL;
|
|
DBORDINAL cBindings;
|
|
DBBINDING * rgBindings = NULL;
|
|
HACCESSOR hAccessor = DB_NULL_HACCESSOR;
|
|
DBORDINAL cbRowSize;
|
|
void * pData = NULL;
|
|
ULONG * rgDispSize = NULL;
|
|
DBCOUNTITEM cRowsObtained;
|
|
HROW * rghRows = NULL;
|
|
ULONG iRow;
|
|
LONG cRows = MAX_ROWS;
|
|
LONG iRetCol = -1;
|
|
BOOL fCanFetchBackwards;
|
|
DBORDINAL iIndex;
|
|
void * pCurData;
|
|
|
|
// Obtain the IRowset interface for use in fetching rows and data
|
|
XCHECK_HR(hr = pUnkRowset->QueryInterface(
|
|
IID_IRowset, (void**)&pIRowset));
|
|
|
|
// Determine whether this rowset supports fetching data backwards;
|
|
// we use this to determine whether the rowset can support moving
|
|
// to the previous set of rows, described in more detail below
|
|
myGetProperty(pUnkRowset, IID_IRowset, DBPROP_CANFETCHBACKWARDS,
|
|
DBPROPSET_ROWSET, &fCanFetchBackwards);
|
|
|
|
// If the caller wants us to return the data for a particular column
|
|
// from a user-selected row, we need to turn the column name into a
|
|
// column ordinal
|
|
if( pwszColToReturn )
|
|
CHECK_HR(hr = myFindColumn(pUnkRowset, pwszColToReturn, &iRetCol));
|
|
|
|
// Create an Accessor. An Accessor is basically a handle to a
|
|
// collection of bindings that describes to the provider how to
|
|
// copy (and convert, if necessary) column data into our buffer.
|
|
// The Accessor that this creates will bind all columns as either
|
|
// DBTYPE_WSTR (a Unicode string) or as an ISequentialStream object
|
|
// (used for BLOB data). This will also give us the size of the
|
|
// row buffer that the Accessor describes to the provider
|
|
CHECK_HR(hr = myCreateAccessor(pUnkRowset, &hAccessor,
|
|
&cBindings, &rgBindings, &cbRowSize));
|
|
|
|
// Allocate enough memory to hold cRows rows of data; this is
|
|
// where the actual row data from the provider will be placed
|
|
pData = CoTaskMemAlloc(cbRowSize * MAX_ROWS);
|
|
CHECK_MEMORY(hr, pData);
|
|
|
|
// Allocate memory for an array that we will use to calculate the
|
|
// maximum display size used by each column in the current set of
|
|
// rows
|
|
rgDispSize = (ULONG *)CoTaskMemAlloc(cBindings * sizeof(ULONG));
|
|
CHECK_MEMORY(hr, rgDispSize);
|
|
|
|
// In this loop, we perform the following process:
|
|
// - reset the maximum display size array
|
|
// - try to get cRows row handles from the provider
|
|
// - these handles are then used to actually get the row data from the
|
|
// provider copied into our allocated buffer
|
|
// - calculate the maximum display size for each column
|
|
// - release the row handles to the rows we obtained
|
|
// - display the column names for the rowset
|
|
// - display the row data for the rows that we fetched
|
|
// - get user input
|
|
// - free the provider-allocated row handle array
|
|
// - repeat unless the user has chosen to quit or has selected a row
|
|
while( hr == S_OK )
|
|
{
|
|
// Clear the maximum display size array
|
|
memset(rgDispSize, 0, cBindings * sizeof(ULONG));
|
|
|
|
// Attempt to get cRows row handles from the provider
|
|
XCHECK_HR(hr = pIRowset->GetNextRows(
|
|
DB_NULL_HCHAPTER, //hChapter
|
|
0, //lOffset
|
|
cRows, //cRows
|
|
&cRowsObtained, //pcRowsObtained
|
|
&rghRows //prghRows
|
|
));
|
|
|
|
// Loop over the row handles obtained from GetNextRows,
|
|
// actually fetching the data for these rows into our buffer
|
|
for( iRow = 0; iRow < cRowsObtained; iRow++ )
|
|
{
|
|
// Find the location in our buffer where we want to place
|
|
// the data for this row. Note that if we fetched rows
|
|
// backwards (cRows < 0), the row handles obtained from the
|
|
// provider are reversed from the order in which we want to
|
|
// actually display the data on the screen, so we will
|
|
// account for this. This ensures that the resulting order
|
|
// of row data in the pData buffer matches the order we
|
|
// wish to use to display the data
|
|
iIndex = cRows > 0 ? iRow : cRowsObtained - iRow - 1;
|
|
pCurData = (BYTE*)pData + (cbRowSize * iIndex);
|
|
|
|
// Get the data for this row handle. The provider will copy
|
|
// (and convert, if necessary) the data for each of the
|
|
// columns that are described in our Accessor into the given
|
|
// buffer (pCurData)
|
|
XCHECK_HR(hr = pIRowset->GetData(
|
|
rghRows[iRow], //hRow
|
|
hAccessor, //hAccessor
|
|
pCurData //pData
|
|
));
|
|
|
|
// Update the maximum display size array, accounting for this row
|
|
CHECK_HR(hr = myUpdateDisplaySize(cBindings, rgBindings,
|
|
pCurData, rgDispSize));
|
|
}
|
|
|
|
// If we obtained rows, release the row handles for the retrieved rows
|
|
// and display the names of the rowset columns before we display the data
|
|
if( cRowsObtained )
|
|
{
|
|
// Release the row handles that we obtained
|
|
XCHECK_HR(hr = pIRowset->ReleaseRows(
|
|
cRowsObtained, //cRows
|
|
rghRows, //rghRows
|
|
NULL, //rgRowOptions
|
|
NULL, //rgRefCounts
|
|
NULL //rgRowStatus
|
|
));
|
|
|
|
|
|
// Display the names of the rowset columns
|
|
CHECK_HR(hr = myDisplayColumnNames(pIRowset, rgDispSize));
|
|
}
|
|
|
|
// For each row that we obtained the data for, display this data
|
|
for( iRow = 0; iRow < cRowsObtained; iRow++ )
|
|
{
|
|
// Get a pointer to the data for this row
|
|
pCurData = (BYTE*)pData + (cbRowSize* iRow);
|
|
|
|
// And display the row data
|
|
CHECK_HR(hr = myDisplayRow(iRow, cBindings, rgBindings,
|
|
pCurData, rgDispSize));
|
|
}
|
|
|
|
// Allow the user to navigate the rowset. This displays the appropriate
|
|
// prompts, gets the user's input, may call IRowset::RestartPosition,
|
|
// and may copy data from a selected row to the selection buffer, if so
|
|
// directed. This will return S_OK if the user asked for more rows,
|
|
// S_FALSE if the user selected a row, or E_FAIL if the user quits
|
|
hr = myInteractWithRowset(
|
|
pIRowset, // IRowset pointer, for RestartPosition
|
|
&cRows, // updated with fetch direction value
|
|
cRowsObtained, // to indicate selection range
|
|
fCanFetchBackwards, // whether [P]revious is supported
|
|
pData, // data pointer for copying selection
|
|
cbRowSize, // size of rows for copying selection
|
|
iRetCol >= 0 ? // bindings for the selection column,
|
|
&rgBindings[iRetCol] : // or NULL if no selection column
|
|
NULL,
|
|
cchBuffer, // size of the selection buffer
|
|
pwszBuffer); // pointer to the selection buffer
|
|
|
|
// Since we are allowing the provider to allocate the memory for
|
|
// the row handle array, we will free this memory and reset the
|
|
// pointer to NULL. If this is not NULL on the next call to GetNextRows,
|
|
// the provider will assume that it points to an allocated array of
|
|
// the required size (which may not be the case if we obtained less
|
|
// than cRows rows from this last call to GetNextRows
|
|
CoTaskMemFree(rghRows);
|
|
rghRows = NULL;
|
|
}
|
|
|
|
CLEANUP:
|
|
myFreeBindings(cBindings, rgBindings);
|
|
CoTaskMemFree(rgDispSize);
|
|
CoTaskMemFree(pData);
|
|
if( pIRowset )
|
|
pIRowset->Release();
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// myInteractWithRowset
|
|
//
|
|
// This function allows the user to interact with the rowset. It
|
|
// prompts the user appropriately, gets the user's input, may
|
|
// call IRowset::RestartPosition if the user requests a restart,
|
|
// and will copy data from a selected row to the selection
|
|
// buffer.
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
HRESULT myInteractWithRowset
|
|
(
|
|
IRowset * pIRowset,
|
|
LONG * pcRows,
|
|
DBCOUNTITEM cRowsObtained,
|
|
BOOL fCanFetchBackwards,
|
|
void * pData,
|
|
DBORDINAL cbRowSize,
|
|
DBBINDING * pBinding,
|
|
ULONG cchBuffer,
|
|
LPWSTR pwszBuffer
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
CHAR ch;
|
|
|
|
// Let the user know if no rows were fetched
|
|
if( !cRowsObtained )
|
|
printf("\n*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*\n" \
|
|
"* *\n" \
|
|
"* No rows obtained on this fetch! *\n" \
|
|
"* *\n" \
|
|
"*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*\n");
|
|
|
|
// Print navigation options
|
|
if( fCanFetchBackwards )
|
|
printf("\n[P]revious; [N]ext; [R]estart; ");
|
|
else
|
|
printf("\n[N]ext; [R]estart; ");
|
|
|
|
// Print selection options
|
|
if( cRowsObtained && pwszBuffer && pBinding )
|
|
printf("[0]-[%d] for a row; ", cRowsObtained - 1);
|
|
|
|
// User can always quit the program
|
|
printf("[Q]uit? ");
|
|
|
|
// Get the user's input
|
|
while( TRUE )
|
|
{
|
|
// Get a character from the console
|
|
ch = myGetChar();
|
|
|
|
// Look for one of the allowed options; if not found, go
|
|
// back around and wait for another input from the user
|
|
|
|
// If we're looking for a row selection, allow the user to select
|
|
// a row that we fetched, then copy the data from the requested
|
|
// column into the selection buffer we were passed
|
|
if( pwszBuffer && pBinding &&
|
|
ch >= '0' && ch < (int)('0' + cRowsObtained) )
|
|
{
|
|
// Save the data for the selected row
|
|
ULONG nSelection = ch - '0';
|
|
//pwszBuffer is of size cchBuffer+1
|
|
_snwprintf_s(pwszBuffer, cchBuffer+1, cchBuffer, L"%s",
|
|
(WCHAR *)((BYTE *)pData + cbRowSize * nSelection +
|
|
pBinding->obValue));
|
|
pwszBuffer[cchBuffer] = L'\0';
|
|
hr = S_FALSE;
|
|
}
|
|
// If the provider supports fetching backwards, set *pcRows
|
|
// to -MAX_ROWS. When GetNextRows is called with this value,
|
|
// it will fetch rows backwards from the current position
|
|
// until it fetches MAX_ROWS rows or hits the end of the rowset
|
|
else if( fCanFetchBackwards && ch == 'p' )
|
|
{
|
|
// Fetch backwards
|
|
*pcRows = -MAX_ROWS;
|
|
}
|
|
// Set *pcRows so that the next call to GetNextRows fetches
|
|
// MAX_ROWS rows forward from the current position
|
|
else if( ch == 'n' )
|
|
{
|
|
// Fetch forward
|
|
*pcRows = MAX_ROWS;
|
|
}
|
|
// Call IRowset::RestartPosition and fetch the first MAX_ROWS
|
|
// rows of the rowset forward from there
|
|
else if( ch == 'r' )
|
|
{
|
|
// RestartPosition
|
|
*pcRows = MAX_ROWS;
|
|
XCHECK_HR(hr = pIRowset->RestartPosition(DB_NULL_HCHAPTER));
|
|
|
|
// Restarting a command may return the DB_S_COMMANDREEXECUTED
|
|
// warning. If this happens, we still want the caller to
|
|
// continue to display data, so we will reset the result code
|
|
// to S_OK
|
|
hr = S_OK;
|
|
}
|
|
// Quit the program
|
|
else if( ch == 'q' )
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
// Invalid option; go back up and get another character from the user
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Echo the character and stop waiting for input
|
|
printf("%c\n", ch);
|
|
break;
|
|
}
|
|
|
|
CLEANUP:
|
|
return hr;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// myDisplayColumnNames
|
|
//
|
|
// This function takes an IUnknown pointer to a Rowset object
|
|
// and displays the names of the columns of that Rowset.
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
HRESULT myDisplayColumnNames
|
|
(
|
|
IUnknown * pUnkRowset,
|
|
ULONG * rgDispSize
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
IColumnsInfo * pIColumnsInfo = NULL;
|
|
DBORDINAL cColumns;
|
|
DBCOLUMNINFO * rgColumnInfo = NULL;
|
|
LPOLESTR pStringsBuffer = NULL;
|
|
WCHAR wszColumn[MAX_DISPLAY_SIZE + 1];
|
|
LPWSTR pwszColName;
|
|
ULONG iCol;
|
|
size_t cSpaces;
|
|
ULONG iSpace;
|
|
|
|
// Get the IColumnsInfo interface for the Rowset
|
|
XCHECK_HR(hr = pUnkRowset->QueryInterface(
|
|
IID_IColumnsInfo, (void**)&pIColumnsInfo));
|
|
|
|
// Get the columns information
|
|
XCHECK_HR(hr = pIColumnsInfo->GetColumnInfo(
|
|
&cColumns, //pcColumns
|
|
&rgColumnInfo, //prgColumnInfo
|
|
&pStringsBuffer //ppStringBuffer
|
|
));
|
|
|
|
// Display the title of the row index column
|
|
wprintf(L" Row | ");
|
|
|
|
// Display all column names
|
|
for( iCol = 0; iCol < cColumns; iCol++ )
|
|
{
|
|
pwszColName = rgColumnInfo[iCol].pwszName;
|
|
|
|
// If the column name is NULL, we'll use a default string
|
|
if( !pwszColName )
|
|
{
|
|
// Is this the bookmark column?
|
|
if( !rgColumnInfo[iCol].iOrdinal )
|
|
pwszColName = L"Bmk";
|
|
else
|
|
pwszColName = L"(null)";
|
|
}
|
|
|
|
// Ensure that the name is no longer than MAX_DISPLAY_SIZE
|
|
wcsncpy_s(wszColumn, _countof(wszColumn), pwszColName, MAX_DISPLAY_SIZE);
|
|
wszColumn[min(rgDispSize[iCol], MAX_DISPLAY_SIZE)] = L'\0';
|
|
|
|
// Figure out how many spaces we need to print after this column name
|
|
cSpaces = min(rgDispSize[iCol], MAX_DISPLAY_SIZE) - wcslen(wszColumn);
|
|
|
|
// Print the column name
|
|
wprintf(L"%s", wszColumn);
|
|
|
|
// Now print any spaces necessary to align this column
|
|
for(iSpace = 0; iSpace < cSpaces; iSpace++ )
|
|
_putch(' ');
|
|
|
|
// Now end the column with a separator marker if necessary
|
|
if( iCol < cColumns - 1 )
|
|
wprintf(L" | ");
|
|
}
|
|
|
|
// Done with the header, so print a newline
|
|
wprintf(L"\n");
|
|
|
|
CLEANUP:
|
|
CoTaskMemFree(rgColumnInfo);
|
|
CoTaskMemFree(pStringsBuffer);
|
|
if( pIColumnsInfo )
|
|
pIColumnsInfo->Release();
|
|
return hr;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// myDisplayRow
|
|
//
|
|
// This function displays the data for a row.
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
HRESULT myDisplayRow
|
|
(
|
|
ULONG iRow,
|
|
DBORDINAL cBindings,
|
|
DBBINDING * rgBindings,
|
|
void * pData,
|
|
ULONG * rgDispSize
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
WCHAR wszColumn[MAX_DISPLAY_SIZE + 1];
|
|
DBSTATUS dwStatus;
|
|
ULONG ulLength;
|
|
void * pvValue;
|
|
ULONG iCol;
|
|
ULONG cbRead;
|
|
ISequentialStream * pISeqStream = NULL;
|
|
size_t cSpaces;
|
|
ULONG iSpace;
|
|
|
|
// Display the row number
|
|
wprintf(L" [%d] | ", iRow);
|
|
|
|
// For each column that we have bound, display the data
|
|
for( iCol = 0; iCol < cBindings; iCol++ )
|
|
{
|
|
// We have bound status, length, and the data value for all
|
|
// columns, so we know that these can all be used
|
|
dwStatus = *(DBSTATUS *)((BYTE *)pData + rgBindings[iCol].obStatus);
|
|
ulLength = *(ULONG *)((BYTE *)pData + rgBindings[iCol].obLength);
|
|
pvValue = (BYTE *)pData + rgBindings[iCol].obValue;
|
|
|
|
// Check the status of this column. This decides
|
|
// exactly what will be displayed for the column
|
|
switch( dwStatus )
|
|
{
|
|
// The data is NULL, so don't try to display it
|
|
case DBSTATUS_S_ISNULL:
|
|
wcscpy_s(wszColumn, _countof(wszColumn), L"(null)");
|
|
break;
|
|
|
|
// The data was fetched, but may have been truncated.
|
|
// Display string data for this column to the user
|
|
case DBSTATUS_S_TRUNCATED:
|
|
case DBSTATUS_S_OK:
|
|
case DBSTATUS_S_DEFAULT:
|
|
{
|
|
// We have either bound the column as a Unicode string
|
|
// (DBTYPE_WSTR) or as an ISequentialStream object
|
|
// (DBTYPE_IUNKNOWN), and have to do different processing
|
|
// for each one of these possibilities
|
|
switch( rgBindings[iCol].wType )
|
|
{
|
|
case DBTYPE_WSTR:
|
|
{
|
|
// Copy the string data
|
|
wcsncpy_s(wszColumn, _countof(wszColumn),(WCHAR *)pvValue, MAX_DISPLAY_SIZE);
|
|
wszColumn[MAX_DISPLAY_SIZE - 1] = L'\0';
|
|
break;
|
|
}
|
|
|
|
case DBTYPE_IUNKNOWN:
|
|
{
|
|
// We've bound this as an ISequentialStream object,
|
|
// therefore the data in our buffer is a pointer
|
|
// to the object's ISequentialStream interface
|
|
pISeqStream = *(ISequentialStream**)pvValue;
|
|
|
|
// We call ISequentialStream::Read to read bytes from
|
|
// the stream blindly into our buffer, simply as a
|
|
// demonstration of ISequentialStream. To display the
|
|
// data properly, the native provider type of this
|
|
// column should be accounted for; it could be
|
|
// DBTYPE_WSTR, in which case this works, or it could
|
|
// be DBTYPE_STR or DBTYPE_BYTES, in which case this
|
|
// won't display the data correctly
|
|
CHECK_HR(hr = pISeqStream->Read(
|
|
wszColumn, //pBuffer
|
|
MAX_DISPLAY_SIZE, //cBytes
|
|
&cbRead //pcBytesRead
|
|
));
|
|
|
|
// Since streams don't provide NULL-termination,
|
|
// we'll NULL-terminate the resulting string ourselves
|
|
wszColumn[cbRead / sizeof(WCHAR)] = L'\0';
|
|
|
|
// Release the stream object, now that we're done
|
|
pISeqStream->Release();
|
|
pISeqStream = NULL;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// This is an error status, so don't try to display the data
|
|
default:
|
|
wcscpy_s(wszColumn, _countof(wszColumn), L"(error status)");
|
|
break;
|
|
}
|
|
|
|
// Determine how many spaces we need to add after displaying this
|
|
// data to align it with this column in other rows
|
|
cSpaces = min(rgDispSize[iCol], MAX_DISPLAY_SIZE) - wcslen(wszColumn);
|
|
|
|
// Print the column data
|
|
wprintf(L"%s", wszColumn);
|
|
|
|
// Now print any spaces necessary
|
|
for(iSpace = 0; iSpace < cSpaces; iSpace++ )
|
|
_putch(' ');
|
|
|
|
// Now end the column with a separator marker if necessary
|
|
if( iCol < cBindings - 1 )
|
|
wprintf(L" | ");
|
|
}
|
|
|
|
CLEANUP:
|
|
if( pISeqStream )
|
|
pISeqStream->Release();
|
|
|
|
// Print the row separator
|
|
wprintf(L"\n");
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// myFreeBindings
|
|
//
|
|
// This function frees a bindings array and any allocated
|
|
// structures contained in that array.
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
void myFreeBindings
|
|
(
|
|
DBORDINAL cBindings,
|
|
DBBINDING * rgBindings
|
|
)
|
|
{
|
|
ULONG iBind;
|
|
|
|
// Free any memory used by DBOBJECT structures in the array
|
|
for( iBind = 0; iBind < cBindings; iBind++ )
|
|
CoTaskMemFree(rgBindings[iBind].pObject);
|
|
|
|
// Now free the bindings array itself
|
|
CoTaskMemFree(rgBindings);
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// myAddRowsetProperties
|
|
//
|
|
// This function sets up the given DBPROPSET and DBPROP
|
|
// structures, adding two optional properties that describe
|
|
// features that we would like to use on the Rowset created
|
|
// with these properties applied:
|
|
// - DBPROP_CANFETCHBACKWARDS -- the rowset should support
|
|
// fetching rows backwards from our current cursor position
|
|
// - DBPROP_IRowsetLocate -- the rowset should support
|
|
// the IRowsetLocate interface and its semantics
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
void myAddRowsetProperties(DBPROPSET* pPropSet, ULONG cProperties, DBPROP* rgProperties)
|
|
{
|
|
// Initialize the property set array
|
|
pPropSet->rgProperties = rgProperties;
|
|
pPropSet->cProperties = cProperties;
|
|
pPropSet->guidPropertySet = DBPROPSET_ROWSET;
|
|
|
|
// Add the following two properties (as OPTIONAL) to the property
|
|
// array contained in the property set array in order to request
|
|
// that they be supported by the rowset we will create. Because
|
|
// these are optional, the rowset we obtain may or may not support
|
|
// this functionality. We will check for the functionality that
|
|
// we need once the rowset is created and will modify our behavior
|
|
// appropriately
|
|
myAddProperty(&rgProperties[0], DBPROP_CANFETCHBACKWARDS);
|
|
myAddProperty(&rgProperties[1], DBPROP_IRowsetLocate);
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// myUpdateDisplaySize
|
|
//
|
|
// This function updates the rgDispSize array, keeping the
|
|
// maximum of the display size needed for the given data and
|
|
// the previous maximum size already in the array.
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
HRESULT myUpdateDisplaySize
|
|
(
|
|
DBORDINAL cBindings,
|
|
DBBINDING * rgBindings,
|
|
void * pData,
|
|
ULONG * rgDispSize
|
|
)
|
|
{
|
|
DBSTATUS dwStatus;
|
|
ULONG cchLength;
|
|
ULONG iCol;
|
|
|
|
// Loop through the bindings, comparing the size of each column
|
|
// against the previously found maximum size for that column
|
|
for( iCol = 0; iCol < cBindings; iCol++ )
|
|
{
|
|
dwStatus = *(DBSTATUS *)((BYTE *)pData + rgBindings[iCol].obStatus);
|
|
cchLength = ((*(ULONG *)((BYTE *)pData + rgBindings[iCol].obLength))
|
|
/ sizeof(WCHAR));
|
|
|
|
// The length that we need to display depends on the status
|
|
// of this column and generally on the data in the column
|
|
switch( dwStatus )
|
|
{
|
|
case DBSTATUS_S_ISNULL:
|
|
cchLength = 6; // "(null)"
|
|
break;
|
|
|
|
case DBSTATUS_S_TRUNCATED:
|
|
case DBSTATUS_S_OK:
|
|
case DBSTATUS_S_DEFAULT:
|
|
if( rgBindings[iCol].wType == DBTYPE_IUNKNOWN )
|
|
cchLength = 2 + 8; // "0x%08lx"
|
|
|
|
// Ensure that the length is at least the minimum display size
|
|
cchLength = max(cchLength, MIN_DISPLAY_SIZE);
|
|
break;
|
|
|
|
default:
|
|
cchLength = 14; // "(error status)"
|
|
break;
|
|
}
|
|
|
|
if( rgDispSize[iCol] < cchLength )
|
|
rgDispSize[iCol] = cchLength;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// myFindColumn
|
|
//
|
|
// Find the index of the column described in pwszName and return
|
|
// S_OK, or, if not found, S_FALSE.
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
HRESULT myFindColumn
|
|
(
|
|
IUnknown * pUnkRowset,
|
|
LPCWSTR pwszName,
|
|
LONG * plIndex
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
IColumnsInfo * pIColumnsInfo = NULL;
|
|
DBORDINAL cColumns;
|
|
DBCOLUMNINFO * rgColumnInfo = NULL;
|
|
OLECHAR * pStringsBuffer = NULL;
|
|
ULONG iCol;
|
|
|
|
// Get the IColumnsInfo interface
|
|
XCHECK_HR(hr = pUnkRowset->QueryInterface(
|
|
IID_IColumnsInfo, (void**)&pIColumnsInfo));
|
|
|
|
// Get the columns information
|
|
XCHECK_HR(hr = pIColumnsInfo->GetColumnInfo(
|
|
&cColumns, //pcColumns
|
|
&rgColumnInfo, //prgColumnInfo
|
|
&pStringsBuffer //ppStringBuffer
|
|
));
|
|
|
|
// Assume that we'll find the column
|
|
hr = S_OK;
|
|
|
|
// Search for the column we need
|
|
for( iCol = 0; iCol < cColumns; iCol++ )
|
|
{
|
|
// If the column name matches we've found the column...
|
|
if( rgColumnInfo[iCol].pwszName &&
|
|
!wcscmp(pwszName, rgColumnInfo[iCol].pwszName) )
|
|
{
|
|
*plIndex = iCol;
|
|
goto CLEANUP;
|
|
}
|
|
}
|
|
|
|
// If we didn't find the column, we'll return S_FALSE
|
|
hr = S_FALSE;
|
|
|
|
CLEANUP:
|
|
CoTaskMemFree(rgColumnInfo);
|
|
CoTaskMemFree(pStringsBuffer);
|
|
if( pIColumnsInfo )
|
|
pIColumnsInfo->Release();
|
|
return hr;
|
|
}
|
|
|
|
|