📄 rowset.cpp
字号:
//---------------------------------------------------------------------------
// 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, and 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 and 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.
//
/////////////////////////////////////////////////////////////////
HRESULTmyCreateRowset(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
// Ccommand 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));
// From the user, get the command text that we will execute
if( !myGetInputFromUser(wszCommandText, L"\nType the command "
L"to execute [Enter = `select * from %s`]: ", wszTableName) )
{
swprintf(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, 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,
ULONG * pcBindings,
DBBINDING ** prgBindings,
ULONG * pcbRowSize
)
{
HRESULT hr;
ULONG cColumns;
DBCOLUMNINFO * rgColumnInfo = NULL;
LPWSTR pStringBuffer = NULL;
IColumnsInfo * pIColumnsInfo = NULL;
ULONG iCol;
ULONG 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 bind only the first BLOB column or IUnknown
// column as an ISequentialStream object, and we 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 supports only 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 )
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -