Sample Provider Overview
The OLE DB sample provider, SAMPPROV.DLL, is a learning tool for developers new to OLE DB. Users can displaying the source code in the debugger to learn about building OLE DB providers. Although this provider uses a simple interface to access data in the underlying data source, it demonstrates the general procedures that developers need to follow to expose rowsets over a file-based data source using IOpenRowset.
Sample Provider Features
The sample provider exposes data in a single, comma-separated value file named CUSTOMER.CSV. If you alter the code to use a different file, you must follow the formatting conventions of the sample file, which follow.
The first line of a .csv file that can be read by SAMPPROV must contain the column names in quotation marks. The second line must list the column types and lengths. All additional lines contain data, in comma-separated format. Every line of data in the file must contain the correct number of values. The following example illustrates the file format.
"Column1","Column2","Column3"
Char(20),SLONG,SLONG
"Name1",10,-10
@@@@@@@@@@@@@@@@@@@@@ // Deleted Row
"Name2",110,-110
In addition, the sample provider replaces deleted rows in the file with the at sign (@). These rows are ignored by the sample provider when the file is initialized. If a user updates a row, the new data is appended to the end of the file and the original row is overwritten with at signs, indicating that it is deleted.
Sample Provider Limitations
The following are limitations in SAMPPROV.DLL:
-
This code example is for illustrative purposes; the sample provider has not been rigorously tested.
-
IDBInitialize::Initialize requires the path to the .csv file. If the path is not supplied by the calling application, SAMPPROV prompts the user for the path.
-
The data source object can create only one session; the session can create only one rowset. This is because the underlying Cfileio object opens the .csv text files in exclusive mode, and has no mechanism to share access to the file.
-
The use of properties is very simple. Only a small set of properties are illustrated, and properties are read-only.
Sample Provider Files
The following source files are included with the sample provider. The source files are located in the <install directory>\SAMPLES\SAMPPROV directory. The sample provider executable file SAMPPROV.DLL is located in the <install directory>\BIN directory.
File name |
Description |
ACCESSOR.cpp |
CImpIAccessor object implementation. |
ASSERTS.cpp |
Simple assertion routines. |
ASSERTS.h |
Simple assertion routine header file. |
BITARRAY.cpp |
An implementation of a bit array class used by the internal buffer to mark released or unreleased rows. |
BITARRAY.h |
Class definitions for the bit array class. |
CLASSFAC.cpp |
DLL entry and exit points and the OLE ClassFactory class. |
CLASSFAC.h |
Class definitions for CClassFactory and DLL entry points. |
COLINFO.cpp |
IColumnsInfo interface implementation. |
CRTSESS.cpp |
IDBCreateSession interface implementation. |
CVTTYPE.cpp |
IConvertType interface implementation. |
DATASRC.cpp |
CDataSource object implementation. |
DATASRC.h |
CDataSource base object and contained interface definitions. |
DBINIT.cpp |
IDBInitialize interface implementation. |
DBPROP.cpp |
IDBProperties and IDBInfo interface implementations. |
DBSESS.cpp |
CDBSession object implementation. |
DBSESS.h |
CDBSession base object and contained interface definitions. |
EXTBUFF.cpp |
The class that provides array-based access to a big block of memory. |
EXTBUFF.h |
Class definitions for CExtBuffer class. |
FILEIDX.cpp |
The index array code for a comma-separated value (.csv) provider. |
FILEIDX.h |
Class definitions for CFileIO class. |
FILEIO.cpp |
The file manipulation code for a comma-separated value (.csv) provider. |
FILEIO.h |
Class definitions for CFileIO class. |
GLOBALS.cpp |
Global initialization object. |
GUIDS.h |
Internal GUIDs. |
HASHTBL.cpp |
Hashing routines for row manipulation. |
HASHTBL.h |
Class definitions for CHashTbl class and miscellaneous bookmark functions. |
HEADERS.cpp |
Precompiled headers module. |
HEADERS.h |
Precompiled headers. |
IROWSET.cpp |
IRowset interface implementation. |
LOCAL.RC |
Localizable resource. |
OPNROWST.cpp |
IOpenRowset interface implementation. |
PERSIST.cpp |
IPersist interface implementation. |
RC.h |
Resource IDs. |
ROWCHNG.cpp |
IRowsetChange interface implementation. |
ROWINFO.cpp |
IRowsetInfo interface implementation. |
ROWSET.cpp |
CRowset object implementation. |
ROWSET.h |
CRowset base object and contained interface definitions. |
SAMPPROV.DEF |
Project definitions. |
SAMPPROV.h |
Main include file. |
SAMPPROV.MAK |
Microsoft Developer Studio–generated NMAKE File. |
SAMPPROV.RC |
Main resource file. |
SAMPVER.h |
Version include file. |
UTILPROP.cpp |
Properties utility object implementation. |
UTILPROP.h |
CUtilProp object definitions. |
Building the Sample Provider
The file <install directory>\SAMPLES\SAMPPROV\SAMPPROV.MAK is set up for compilation with NMAKE, the Microsoft® Program Maintenance Utility provided with Microsoft Developer Studio. SAMPPROV must have access to the following OLE DB files:
OLEDB.h
OLEDBERR.h
OLEDB.LIB
Verify that these files are on the path referred to by the INCLUDE environment variable.
The following example syntax builds a debug version of SAMPPROV on the Intel® x86 platform based on the Win32® (x86) Dynamic-Link Library configuration.
NMAKE /f "sampprov.mak" CFG="sampprov - Win32 x86 Debug"
To build SAMPPROV on another platform or with a different configuration, use one of the following configuration options.
Configuration option |
Type of file built |
"sampprov - Win32 x86 Debug" |
Debug version of SAMPPROV based on Win32 (x86) Dynamic-Link Library. |
"sampprov - Win32 x86 Release" |
Retail version of SAMPPROV based on Win32 (x86) Dynamic-Link Library. |
"sampprov - Win32 (ALPHA) axp Debug" |
Debug version of SAMPPROV based on Win32 (ALPHA) Dynamic-Link Library. |
"sampprov - Win32 (ALPHA) axp Release" |
Retail version of SAMPPROV based on Win32 (ALPHA) Dynamic-Link Library. |
"sampprov - Win32 (PPC) ppc Debug" |
Debug version of SAMPPROV based on Win32 (PPC) Dynamic-Link Library. |
"sampprov - Win32 (PPC) ppc Release" |
Retail version of SAMPPROV based on Win32 (PPC) Dynamic-Link Library. |
For additional information on using NMAKE, see the Developer Studio documentation.
Writing an OLE DB Provider: An Introduction
The purpose of this section is to offer some general guidelines on how to construct an OLE DB data provider.
Information Needed to Write an OLE DB Provider
OLE DB is based on the Component Object Model. Developers must be familiar with this model as well as with basic object-oriented coding practices to create an OLE DB provider. Although many different implementations are possible, the sample provider implements the TDataSource, TDBSession, and TRowset CoTypes. The sample provider uses the IOpenRowset interface, which is a required interface on the session for all providers. IOpenRowset is used by consumers that need access to the full data set and do not need to specify selection criteria.
Developers should become familiar with data source objects, sessions, and rowsets by reading the OLE DB Programmer’s Reference. In addition, developers may want to read the “Overview” in the OLE DB Programmer’s Reference to get a general introduction to OLE DB conventions, design considerations, and a complete introduction to the objects that make up OLE DB.
The Sample Provider
The sample provider offers access to a fixed-length text data file using OLE DB interfaces. The purpose of this sample provider is purely illustrative. The capabilities of the sample provider are limited; these limitations are discussed in “Sample Provider Overview,” previously in this paper.
The sample provider opens the sample data file and enables the consumer to read the data, using the IRowset interface, in a forward-only fashion. The consumer can update or delete the current row but cannot add new rows. The format of the data file is discussed in “Sample Provider Overview,” previously in this paper.
Objects and Interfaces Implemented by the Sample Provider
The sample provider implements three OLE DB objects.
-
The data source object, which enables consumers to connect and initialize the interaction with the data file.
-
The session, which enables consumers to create a rowset for the data set in the data file.
-
The rowset, which exposes a data set to the consumer.
These objects, along with the interfaces implemented in the sample, are described in detail in the following topics, which also contain some of the implementation code for specific interfaces and methods.
The following table lists the interfaces and methods implemented in the sample provider along with the name of the source code file in which each can be found.
Interface name |
Method name |
Source code file |
IDBInitialize |
Initialize |
DBInit.cpp |
|
Uninitialize |
DBInit.cpp |
IDBCreateSession |
CreateSession |
CrtSess.cpp |
IDBProperties |
GetProperties |
UtilProp.cpp |
|
GetPropertyInfo |
UtilProp.cpp |
|
SetProperties |
UtilProp.cpp |
IPersist |
GetClassID |
Persist.cpp |
IGetDataSource |
GetDataSource |
DBSess.cpp |
IOpenRowset |
OpenRowset |
OpnRowst.cpp |
IColumnsInfo |
GetColumnInfo |
ColInfo.cpp |
|
MapColumnIDs |
ColInfo.cpp |
IConvertType |
CanConvert |
CvtType.cpp |
IAccessor |
AddRefAccessor |
Accessor.cpp |
|
CreateAccessor |
Accessor.cpp |
|
GetBindings |
Accessor.cpp |
|
ReleaseAccessor |
Accessor.cpp |
IRowset |
AddRefRows |
IRowset.cpp |
|
GetData |
IRowset.cpp |
|
GetNextRows |
IRowset.cpp |
|
ReleaseRows |
IRowset.cpp |
|
RestartPosition |
IRowset.cpp |
IRowsetInfo |
GetProperties |
RowInfo.cpp |
|
GetReferencedRowset |
RowInfo.cpp |
|
GetSpecification |
RowInfo.cpp |
IRowsetChange |
DeleteRows |
RowChng.cpp |
|
InsertRow |
RowChng.cpp |
|
SetData |
RowChng.cpp |
ISessionProperties |
GetProperties |
DBSess.cpp |
|
SetProperties |
DBSess.cpp |
Setting and Getting Provider Properties
The data source object is the first object created when a consumer instantiates an OLE DB data provider by calling CoCreateInstance or through some other technique.
The data source object provides the starting point for communications between the provider and consumer. For example, a consumer can call CoCreateInstance and request an IDBInitialize interface pointer to instantiate a data source object. The provider’s developer must generate a CLSID (class ID) and store the ID in the Windows Registry. The consumer can use this CLSID with CoCreateInstance to instantiate the data source object. To facilitate this operation, providers should add their application or library information to the Windows Registry upon installation on a new system. The sample provider setup program registers the sample provider in the Windows Registry.
The data source object is also responsible for setting and returning information about the properties supported by the provider and exposing the list of supported keywords and literals. This functionality is supported through the mandatory IDBProperties interface and the optional IDBInfo interface. The IDBProperties interface contains three methods:
-
GetProperties returns the list of properties currently set on the data source object.
-
GetPropertyInfo returns information about supported rowset and data source properties.
-
SetProperties sets the properties on the data source object.
The IDBInfo interface contains two methods:
-
GetKeywords returns a list of supported keywords.
-
GetLiteralInfo returns information about literals used in text commands.
In the sample provider implementation, no command interfaces are implemented, so GetKeywords returns NULL. GetLiteralInfo is not implemented and returns E_NOTIMPL. GetProperties, GetPropertyInfo, and SetProperties are handled by passing the calls to the utility object that manages properties, which is found in UtilProp.cpp. The structure containing the properties known to the sample provider, which is also found in UtilProp.cpp, follows.
// Struct containing the properties we know about. The GUID and string fields are // initialized in the constructor, because C++ makes it awkward to do so at declaration
// time. So, if you change this table, be sure to make parallel changes in CUtilProp::CUtilProp.
PROPSTRUCT s_rgprop[] =
{
/* 0 */ {DBPROP_IAccessor, FLAGS_ROWSETRO, VT_BOOL, TRUE, 0, NULL, L"IAccessor"},
/* 1 */ {DBPROP_IColumnsInfo, FLAGS_ROWSETRO, VT_BOOL, TRUE, 0, NULL, L"IColumnsInfo"},
/* 2 */ {DBPROP_IConvertType, FLAGS_ROWSETRO, VT_BOOL, TRUE, 0, NULL, L"IConvertType"},
/* 3 */ {DBPROP_IRowset, FLAGS_ROWSETRO, VT_BOOL, TRUE, 0, NULL, L"IRowset"},
/* 4 */ {DBPROP_IRowsetChange, FLAGS_ROWSETRW, VT_BOOL, TRUE, 0, NULL, L"IRowsetChange"},
/* 5 */ {DBPROP_IRowsetInfo, FLAGS_ROWSETRO, VT_BOOL, TRUE, 0, NULL, L"IRowsetInfo"},
/* 6 */ {DBPROP_ACTIVESESSIONS, FLAGS_DATASOURCE, VT_I4, FALSE, 1, NULL, L"Active Sessions"},
/* 7 */ {DBPROP_PROVIDERNAME, FLAGS_DATASOURCE, VT_BSTR, FALSE, 0, L"SAMPPROV.DLL",L"Provider Name"},
/* 8 */ {DBPROP_PROVIDEROLEDBVER,FLAGS_DATASOURCE, VT_BSTR, FALSE, 0, L"01.10", L"OLE DB Version"},
/* 9 */ {DBPROP_PROVIDERVER, FLAGS_DATASOURCE, VT_BSTR, FALSE, 0, _TEXT(VER_PRODUCTVERSION_STR), L"Provider Version"},
/* 10*/ {DBPROP_INIT_DATASOURCE, FLAGS_DBINITRW, VT_BSTR, FALSE, 0, L"", L"Data Source"},
/* 11*/ {DBPROP_INIT_HWND, FLAGS_DBINITRW, VT_I4, FALSE, 0, NULL, L"Window Handle"},
/* 12*/ {DBPROP_INIT_PROMPT, FLAGS_DBINITRW, VT_I2, FALSE, 4, NULL, L"Prompt"}
};
|
The source code for GetPropertyInfo, which passes property information back to the provider, follows.
// CUtilProp::GetPropertyInfo ----------------------------------------- //
// @mfunc Returns information about rowset and data source properties
// supported by the provider
//
// @rdesc HRESULT
// @flag S_OK | The method succeeded
// @flag E_INVALIDARG | pcPropertyIDSets or prgPropertyInfo was NULL
// @flag E_OUTOFMEMORY | Out of memory
//
STDMETHODIMP CUtilProp::GetPropertyInfo
(
BOOL fDSOInitialized, //@parm IN | if Initialized
ULONG cPropertyIDSets, //@parm IN | # properties
const DBPROPIDSET rgPropertyIDSets[], //@parm IN | Array of property sets
ULONG* pcPropertyInfoSets, //@parm OUT | # DBPROPSET structures
DBPROPINFOSET** prgPropertyInfoSets, //@parm OUT | DBPROPSET structures property
// | information returned
WCHAR** ppDescBuffer //@parm OUT | Property descriptions
)
{
BOOL fRet = TRUE;
BOOL fPropsinError = FALSE;
BOOL fPropsSucceed = FALSE;
BOOL fIsSpecialGUID = FALSE;
BOOL fIsNotSpecialGUID = FALSE;
ULONG cProps = 0;
ULONG cCount = 0;
ULONG ulPropertySets = 0;
WCHAR* pDescBuffer = NULL;
DBPROPINFO* pPropInfo;
DBPROPINFOSET* pPropInfoSet;
// init out params
if (pcPropertyInfoSets)
*pcPropertyInfoSets = 0;
if (prgPropertyInfoSets)
*prgPropertyInfoSets = NULL;
if (ppDescBuffer)
*ppDescBuffer = NULL;
// Check Arguments, on failure post HRESULT to error queue
if( ((cPropertyIDSets > 0) && !rgPropertyIDSets) ||
!pcPropertyInfoSets || !prgPropertyInfoSets )
return ResultFromScode( E_INVALIDARG );
// New argument check for > 1 cPropertyIDs and NULL pointer for
// array of property ids.
for(ULONG ul=0; ul<cPropertyIDSets; ul++)
{
if( rgPropertyIDSets[ul].cPropertyIDs && !(rgPropertyIDSets[ul].rgPropertyIDs) )
return ResultFromScode( E_INVALIDARG );
if (DBPROPSET_ROWSETALL == rgPropertyIDSets[ul].guidPropertySet ||
DBPROPSET_DATASOURCEALL == rgPropertyIDSets[ul].guidPropertySet ||
DBPROPSET_DATASOURCEINFOALL == rgPropertyIDSets[ul].guidPropertySet ||
DBPROPSET_SESSIONALL == rgPropertyIDSets[ul].guidPropertySet ||
DBPROPSET_DBINITALL == rgPropertyIDSets[ul].guidPropertySet)
fIsSpecialGUID = TRUE;
else
fIsNotSpecialGUID = TRUE;
if(fIsSpecialGUID && fIsNotSpecialGUID)
return ResultFromScode( E_INVALIDARG );
}
// save the count of PropertyIDSets
cProps = cPropertyIDSets;
// If the consumer does not restrict the property sets
// by specify an array of property sets and a cPropertySets
// greater than 0, then we need to make sure we
// have some to return
if ( (cPropertyIDSets == 0) )
{
if( fDSOInitialized )
cProps = NUMBER_OF_SUPPORTED_PROPERTY_SETS;
else
cProps = 1;
}
// use task memory allocater to alloc a DBPROPINFOSET struct
pPropInfoSet = (DBPROPINFOSET*) g_pIMalloc->Alloc(cProps *
sizeof( DBPROPINFOSET ));
if ( !pPropInfoSet )
return ResultFromScode( E_OUTOFMEMORY );
memset( pPropInfoSet, 0, (cProps * sizeof( DBPROPINFOSET )));
// Alloc memory for ppDescBuffer
if ( ppDescBuffer )
{
pDescBuffer = (WCHAR*)g_pIMalloc->Alloc(NUMBER_OF_SUPPORTED_PROPERTIES *
CCH_GETPROPERTYINFO_DESCRIP_BUFFER_SIZE * sizeof(WCHAR) );
if( pDescBuffer )
{
memset(pDescBuffer, 0, (NUMBER_OF_SUPPORTED_PROPERTIES *
CCH_GETPROPERTYINFO_DESCRIP_BUFFER_SIZE * sizeof(WCHAR)));
*ppDescBuffer = pDescBuffer;
}
else
{
g_pIMalloc->Free( pPropInfoSet );
return ResultFromScode( E_OUTOFMEMORY );
}
}
// For each supported Property Set
for (ulPropertySets=0; ulPropertySets < cProps; ulPropertySets++)
{
BOOL fGetAllProps = FALSE;
// If no restrictions return all properties from the three supported property sets
if( cPropertyIDSets == 0 )
{
fGetAllProps = TRUE;
// only do this once
if (ulPropertySets == 0)
{
pPropInfoSet[0].guidPropertySet = DBPROPSET_DBINIT;
pPropInfoSet[0].cPropertyInfos = NUMBER_OF_SUPPORTED_DBINIT_PROPERTIES;
if ( fDSOInitialized )
{
pPropInfoSet[1].guidPropertySet = DBPROPSET_DATASOURCEINFO;
pPropInfoSet[1].cPropertyInfos = NUMBER_OF_SUPPORTED_DATASOURCEINFO_PROPERTIES;
pPropInfoSet[2].guidPropertySet = DBPROPSET_ROWSET;
pPropInfoSet[2].cPropertyInfos = NUMBER_OF_SUPPORTED_ROWSET_PROPERTIES;
}
}
}
else
{
pPropInfoSet[ulPropertySets].guidPropertySet = rgPropertyIDSets[ulPropertySets].guidPropertySet;
pPropInfoSet[ulPropertySets].cPropertyInfos = rgPropertyIDSets[ulPropertySets].cPropertyIDs;
if ((rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_DBINITALL) ||
(rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_DBINIT &&
rgPropertyIDSets[ulPropertySets].cPropertyIDs == 0))
{
fGetAllProps = TRUE;
pPropInfoSet[ulPropertySets].guidPropertySet = DBPROPSET_DBINIT;
pPropInfoSet[ulPropertySets].cPropertyInfos = NUMBER_OF_SUPPORTED_DBINIT_PROPERTIES;
}
else if (rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_DATASOURCEALL)
{
// Since we do not support it should return DB_E_ERRORSOCCURRED with 0 & NULL
fPropsinError = TRUE;
pPropInfoSet[ulPropertySets].guidPropertySet = DBPROPSET_DATASOURCE;
pPropInfoSet[ulPropertySets].cPropertyInfos = 0;
}
else if( fDSOInitialized )
{
if( (rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_DATASOURCEINFOALL) ||
((rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_DATASOURCEINFO) &&
(rgPropertyIDSets[ulPropertySets].cPropertyIDs == 0)) )
{
fGetAllProps = TRUE;
pPropInfoSet[ulPropertySets].guidPropertySet = DBPROPSET_DATASOURCEINFO;
pPropInfoSet[ulPropertySets].cPropertyInfos = NUMBER_OF_SUPPORTED_DATASOURCEINFO_PROPERTIES;
}
else if (rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_SESSIONALL)
{
// Since we do not support it should return a error with 0 & NULL
fPropsinError = TRUE;
pPropInfoSet[ulPropertySets].guidPropertySet = DBPROPSET_SESSION;
pPropInfoSet[ulPropertySets].cPropertyInfos = 0;
}
else if( (rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_ROWSETALL) ||
((rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_ROWSET) &&
(rgPropertyIDSets[ulPropertySets].cPropertyIDs == 0)) )
{
fGetAllProps = TRUE;
pPropInfoSet[ulPropertySets].guidPropertySet = DBPROPSET_ROWSET;
pPropInfoSet[ulPropertySets].cPropertyInfos = NUMBER_OF_SUPPORTED_ROWSET_PROPERTIES;
}
else if (rgPropertyIDSets[ulPropertySets].cPropertyIDs == 0)
fPropsinError = TRUE;
}
else if (rgPropertyIDSets[ulPropertySets].cPropertyIDs == 0)
{
// Since we do not support it should return a error with 0 & NULL
fPropsinError = TRUE;
}
}
if (pPropInfoSet[ulPropertySets].cPropertyInfos)
{
// use task memory allocater to alloc array of DBPROPINFO structs
pPropInfo = (DBPROPINFO*) g_pIMalloc->Alloc(sizeof( DBPROPINFO ) *
pPropInfoSet[ulPropertySets].cPropertyInfos);
if (!pPropInfo)
{
if ( ppDescBuffer )
*ppDescBuffer = NULL;
g_pIMalloc->Free( pPropInfoSet );
g_pIMalloc->Free( pDescBuffer );
return ResultFromScode( E_OUTOFMEMORY );
}
pPropInfoSet[ulPropertySets].rgPropertyInfos = &pPropInfo[0];
memset( pPropInfo, 0,
(pPropInfoSet[ulPropertySets].cPropertyInfos * sizeof( DBPROPINFO )));
}
// for each prop in our table..
for (cCount=0; cCount < pPropInfoSet[ulPropertySets].cPropertyInfos; cCount++)
{
// init the Variant right up front
// that way we can VariantClear with no worried (if we need to)
VariantInit( &pPropInfo[cCount].vValues );
// set the description pointer
pPropInfo[cCount].pwszDescription = pDescBuffer;
// Check supported property sets
if ( (pPropInfoSet[ulPropertySets].guidPropertySet == DBPROPSET_DBINIT) &&
(fGetAllProps) )
{
// load up their DBPROPINFO from our table
fPropsSucceed = TRUE;
fRet = LoadDBPROPINFO( &m_rgproperties[START_OF_SUPPORTED_DBINIT_PROPERTIES + cCount],
&pPropInfo[cCount] );
}
else if ( (pPropInfoSet[ulPropertySets].guidPropertySet == DBPROPSET_DATASOURCEINFO) &&
fGetAllProps && fDSOInitialized)
{
// load up their DBPROPINFO from our table
fPropsSucceed = TRUE;
fRet = LoadDBPROPINFO( &m_rgproperties[START_OF_SUPPORTED_DATASOURCEINFO_PROPERTIES + cCount],
&pPropInfo[cCount] );
}
else if ( (pPropInfoSet[ulPropertySets].guidPropertySet == DBPROPSET_ROWSET) &&
fGetAllProps && fDSOInitialized)
{
// load up their DBPROPINFO from our table
fPropsSucceed = TRUE;
fRet = LoadDBPROPINFO( &m_rgproperties[START_OF_SUPPORTED_ROWSET_PROPERTIES + cCount],
&pPropInfo[cCount] );
}
else
{
ULONG ulIndex;
pPropInfo[cCount].dwPropertyID = rgPropertyIDSets[ulPropertySets].rgPropertyIDs[cCount];
pPropInfo[cCount].dwFlags = DBPROPFLAGS_NOTSUPPORTED;
if ( (GetPropIndex(rgPropertyIDSets[ulPropertySets].rgPropertyIDs[cCount], &ulIndex)) &&
(fDSOInitialized || (pPropInfoSet[ulPropertySets].guidPropertySet == DBPROPSET_DBINIT)) )
{
fPropsSucceed = TRUE;
fRet = LoadDBPROPINFO( &m_rgproperties[ulIndex], &pPropInfo[cCount] );
}
else
{
fPropsinError = TRUE;
pPropInfo[cCount].pwszDescription = NULL;
}
}
if (!fRet)
{
ULONG ulFor;
// something went wrong
// clear all variants used so far..
for (ulFor = 0; ulFor < cCount; ulFor++)
VariantClear( &pPropInfo[ulFor].vValues );
// .. delete the pPropInfo array, return failure
if ( ppDescBuffer ) *ppDescBuffer = NULL;
g_pIMalloc->Free( pPropInfo );
g_pIMalloc->Free( pPropInfoSet );
g_pIMalloc->Free( pDescBuffer );
return ResultFromScode( E_FAIL );
}
// move the description pointer to the next
if ( pPropInfo[cCount].pwszDescription )
pDescBuffer += (wcslen(pPropInfo[cCount].pwszDescription) + sizeof(CHAR));
}
// Set local back to FALSE
fGetAllProps = FALSE;
}
// set count of properties and property information
*pcPropertyInfoSets = cProps;
*prgPropertyInfoSets = pPropInfoSet;
if ( !fPropsSucceed && fPropsinError )
{
if ( ppDescBuffer ) *ppDescBuffer = NULL;
g_pIMalloc->Free( pDescBuffer );
return ResultFromScode( DB_E_ERRORSOCCURRED );
}
else if ( fPropsSucceed && fPropsinError )
return ResultFromScode( DB_S_ERRORSOCCURRED );
else
return ResultFromScode( S_OK );
}
|
The source code for GetProperties, which returns the current settings of all supported properties, follows. Immediately following the creation of the data source object, none of the properties are set. Properties are set by the SetProperties method.
// CUtilProp::GetProperties ---------------------------------------------------- //
// @mfunc Returns current settings of all properties supported by the DSO/rowset
//
// @rdesc HRESULT
// @flag S_OK | The method succeeded
// @flag E_INVALIDARG | pcProperties or prgPropertyInfo was NULL
// @flag E_OUTOFMEMORY | Out of memory
//
STDMETHODIMP CUtilProp::GetProperties
(
DWORD dwBitMask, //@parm IN | Mask if Initialized
ULONG cPropertyIDSets, //@parm IN | # of restiction property IDs
const DBPROPIDSET rgPropertyIDSets[], //@parm IN | restriction guids
ULONG* pcPropertySets, //@parm OUT | count of properties returned
DBPROPSET** prgPropertySets //@parm OUT | property information returned
)
{
BOOL fRet = TRUE;
BOOL fPropsinError = FALSE;
BOOL fPropsSucceed = FALSE;
ULONG cProps = 0;
ULONG cCount = 0;
ULONG ulPropertySets = 0;
DBPROP* pProp;
DBPROPSET* pPropSet;
// save the count of PropertyIDSets
cProps = cPropertyIDSets;
// If the consumer does not restrict the property sets
// by specify an array of property sets and a cPropertySets
// greater than 0, then we need to make sure we
// have some to return
if( cPropertyIDSets == 0 )
{
// only allow the DBINIT and DATASOURCE if Initialized
if ( (dwBitMask & PROPSET_DSOINIT) == PROPSET_DSOINIT )
cProps = 2;
else
cProps = 1;
}
// use task memory allocater to alloc a DBPROPINFOSET struct
pPropSet = (DBPROPSET*) g_pIMalloc->Alloc(cProps *
sizeof( DBPROPSET ));
if ( !pPropSet )
return ResultFromScode( E_OUTOFMEMORY );
memset( pPropSet, 0, (cProps * sizeof( DBPROPSET )));
// For each supported Property Set
for (ulPropertySets=0; ulPropertySets < cProps; ulPropertySets++)
{
BOOL fGetAllProps = FALSE;
// If no restrictions return all properties from the three supported property sets
if ( cPropertyIDSets == 0 )
{
fGetAllProps = TRUE;
// only do this once
if ( ulPropertySets == 0 )
{
if( !(dwBitMask & PROPSET_SESSION) )
{
if ( !(dwBitMask & PROPSET_ROWSET) )
{
pPropSet[0].guidPropertySet = DBPROPSET_DBINIT;
pPropSet[0].cProperties = NUMBER_OF_SUPPORTED_DBINIT_PROPERTIES;
if( dwBitMask & PROPSET_INIT )
{
pPropSet[1].guidPropertySet = DBPROPSET_DATASOURCEINFO;
pPropSet[1].cProperties = NUMBER_OF_SUPPORTED_DATASOURCEINFO_PROPERTIES;
}
}
else
{
pPropSet[0].guidPropertySet = DBPROPSET_ROWSET;
pPropSet[0].cProperties = NUMBER_OF_SUPPORTED_ROWSET_PROPERTIES;
}
}
}
}
else
{
pPropSet[ulPropertySets].guidPropertySet = rgPropertyIDSets[ulPropertySets].guidPropertySet;
pPropSet[ulPropertySets].cProperties = rgPropertyIDSets[ulPropertySets].cPropertyIDs;
if( rgPropertyIDSets[ulPropertySets].cPropertyIDs == 0 )
{
fGetAllProps = TRUE;
if( rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_DBINIT )
{
pPropSet[ulPropertySets].cProperties = NUMBER_OF_SUPPORTED_DBINIT_PROPERTIES;
}
else if( (rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_DATASOURCEINFO) &&
((dwBitMask & PROPSET_DSOINIT) == PROPSET_DSOINIT) )
{
pPropSet[ulPropertySets].cProperties = NUMBER_OF_SUPPORTED_DATASOURCEINFO_PROPERTIES;
}
else if( (rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_ROWSET) &&
(dwBitMask & PROPSET_ROWSET))
{
pPropSet[ulPropertySets].cProperties = NUMBER_OF_SUPPORTED_ROWSET_PROPERTIES;
}
else
{
fGetAllProps = FALSE;
}
}
}
if( pPropSet[ulPropertySets].cProperties )
{
// use task memory allocater to alloc array of DBPROPINFO structs
pProp = (DBPROP*) g_pIMalloc->Alloc(sizeof( DBPROP ) *
pPropSet[ulPropertySets].cProperties);
if (!pProp)
{
for(ULONG ul=0; ul<ulPropertySets; ul++)
{
for(ULONG ul2=0; ul2<pPropSet[ul].cProperties; ul2++)
VariantClear( &pPropSet[ul].rgProperties[ul2].vValue );
g_pIMalloc->Free( pPropSet[ul].rgProperties );
}
g_pIMalloc->Free( pPropSet );
return ResultFromScode( E_OUTOFMEMORY );
}
pPropSet[ulPropertySets].rgProperties = &pProp[0];
memset( pProp, 0,
(pPropSet[ulPropertySets].cProperties * sizeof( DBPROP )));
}
// for each prop in our table..
for (cCount=0; cCount < pPropSet[ulPropertySets].cProperties; cCount++)
{
// init the Variant right up front
// that way we can VariantClear with no worried (if we need to)
VariantInit( &pProp[cCount].vValue );
// Check supported property sets
if ( pPropSet[ulPropertySets].guidPropertySet == DBPROPSET_DBINIT &&
fGetAllProps )
{
fPropsSucceed = TRUE;
// load up their DBPROP from our table
fRet = LoadDBPROP( &m_rgproperties[START_OF_SUPPORTED_DBINIT_PROPERTIES + cCount],
&pProp[cCount] );
}
else if ( pPropSet[ulPropertySets].guidPropertySet == DBPROPSET_DATASOURCEINFO &&
fGetAllProps )
{
fPropsSucceed = TRUE;
// load up their DBPROPINFO from our table
fRet = LoadDBPROP( &m_rgproperties[START_OF_SUPPORTED_DATASOURCEINFO_PROPERTIES + cCount],
&pProp[cCount] );
}
else if ( pPropSet[ulPropertySets].guidPropertySet == DBPROPSET_ROWSET &&
fGetAllProps )
{
fPropsSucceed = TRUE;
// load up their DBPROPINFO from our table
fRet = LoadDBPROP( &m_rgproperties[START_OF_SUPPORTED_ROWSET_PROPERTIES + cCount],
&pProp[cCount] );
}
else
{
ULONG ulIndex;
pProp[cCount].dwPropertyID = rgPropertyIDSets[ulPropertySets].rgPropertyIDs[cCount];
pProp[cCount].dwStatus = DBPROPSTATUS_NOTSUPPORTED;
if( (GetPropIndex(rgPropertyIDSets[ulPropertySets].rgPropertyIDs[cCount], &ulIndex)) &&
(((dwBitMask & PROPSET_DSO) &&
(rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_DBINIT)) ||
(((dwBitMask & PROPSET_DSOINIT) == PROPSET_DSOINIT) &&
((rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_DATASOURCE) ||
(rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_DATASOURCEINFO))) ||
((dwBitMask & PROPSET_SESSION) &&
(rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_SESSION)) ||
((dwBitMask & PROPSET_ROWSET) &&
(rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_ROWSET))) )
{
fPropsSucceed = TRUE;
fRet = LoadDBPROP( &m_rgproperties[ulIndex], &pProp[cCount] );
}
else
fPropsinError = TRUE;
}
if (!fRet)
{
ULONG ulFor;
// something went wrong
// clear all variants used so far..
for (ulFor = 0; ulFor < cCount; ulFor++)
VariantClear( &pProp[ulFor].vValue );
// .. delete the pPropInfo array, return failure
g_pIMalloc->Free( pProp );
g_pIMalloc->Free( pPropSet );
return ResultFromScode( E_FAIL );
}
}
// Set local back to FALSE
fGetAllProps = FALSE;
}
// set count of properties and property information
*pcPropertySets = cProps;
*prgPropertySets = pPropSet;
if ( !fPropsSucceed && fPropsinError )
return ResultFromScode( DB_E_ERRORSOCCURRED );
else if ( fPropsSucceed && fPropsinError )
return ResultFromScode( DB_S_ERRORSOCCURRED );
else
return ResultFromScode( S_OK );
}
|
The source code for SetProperties, which sets the values of all supported properties, follows. The consumer must set properties on the provider before initializing the data source object. In most providers, these properties provide information such as a database location, database name, user ID, and password. The sample provider requires only one value: a valid directory path to the data file.
// CUtilProp::SetProperties ---------------------------------------------------- //
// @mfunc Set current settings of properties supported by the DSO/rowset
//
// @rdesc HRESULT
// @flag S_OK | The method succeeded
// @flag E_INVALIDARG | pcProperties or prgPropertyInfo was NULL
// @flag E_OUTOFMEMORY | Out of memory
//
STDMETHODIMP CUtilProp::SetProperties
(
DWORD dwBitMask, //@parm IN Type of PropSet
ULONG cPropertyIDSets, //@parm IN # of DBPROPSET
DBPROPSET rgPropertyIDSets[] //@parm INOUT Array of property sets
)
{
ULONG ulPropertySets = 0;
ULONG cCount = 0;
BOOL fSetAllProps = TRUE;
BOOL fOnePropSet = FALSE;
// For each supported Property Set
for (ulPropertySets=0; ulPropertySets < cPropertyIDSets; ulPropertySets++)
{
ULONG ulIndex = 0;
// for each prop in the propset
for (cCount=0; cCount < rgPropertyIDSets[ulPropertySets].cProperties; cCount++)
{
rgPropertyIDSets[ulPropertySets].rgProperties[cCount].dwStatus = DBPROPSTATUS_OK;
// only allow the DBINIT and DATASOURCE
if ( (dwBitMask & PROPSET_DSO) &&
((rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_DBINIT) &&
(dwBitMask & PROPSET_INIT)) )
{
rgPropertyIDSets[ulPropertySets].rgProperties[cCount].dwStatus = DBPROPSTATUS_NOTSETTABLE;
fSetAllProps = FALSE;
}
else if ( ((dwBitMask & PROPSET_DSO) &&
(rgPropertyIDSets[ulPropertySets].guidPropertySet != DBPROPSET_DATASOURCE &&
rgPropertyIDSets[ulPropertySets].guidPropertySet != DBPROPSET_DBINIT)) ||
(rgPropertyIDSets[ulPropertySets].guidPropertySet == DBPROPSET_DATASOURCE &&
!(dwBitMask & PROPSET_INIT)) )
{
rgPropertyIDSets[ulPropertySets].rgProperties[cCount].dwStatus = DBPROPSTATUS_NOTSUPPORTED;
fSetAllProps = FALSE;
}
// only allow the SESSION
else if ( (dwBitMask & PROPSET_SESSION) &&
(rgPropertyIDSets[ulPropertySets].guidPropertySet != DBPROPSET_SESSION) )
{
rgPropertyIDSets[ulPropertySets].rgProperties[cCount].dwStatus = DBPROPSTATUS_NOTSUPPORTED;
fSetAllProps = FALSE;
}
// only allow the ROWSET
else if ( (dwBitMask & PROPSET_ROWSET) &&
(rgPropertyIDSets[ulPropertySets].guidPropertySet != DBPROPSET_ROWSET) )
{
rgPropertyIDSets[ulPropertySets].rgProperties[cCount].dwStatus = DBPROPSTATUS_NOTSUPPORTED;
fSetAllProps = FALSE;
}
else
{
// get the index in the array
if ( GetPropIndex(rgPropertyIDSets[ulPropertySets].rgProperties[cCount].dwPropertyID, &ulIndex) )
{
// arg checking for the prop
if ( (DBPROPOPTIONS_SETIFCHEAP != rgPropertyIDSets[ulPropertySets].rgProperties[cCount].dwOptions) &&
(DBPROPOPTIONS_REQUIRED != rgPropertyIDSets[ulPropertySets].rgProperties[cCount].dwOptions) )
{
rgPropertyIDSets[ulPropertySets].rgProperties[cCount].dwStatus = DBPROPSTATUS_BADOPTION;
fSetAllProps = FALSE;
}
else if ( ((m_rgproperties[ulIndex].vtType != rgPropertyIDSets[ulPropertySets].rgProperties[cCount].vValue.vt) &&
(VT_EMPTY != rgPropertyIDSets[ulPropertySets].rgProperties[cCount].vValue.vt)) ||
(IsValidValue(&rgPropertyIDSets[ulPropertySets].rgProperties[cCount]) == S_FALSE))
{
rgPropertyIDSets[ulPropertySets].rgProperties[cCount].dwStatus = DBPROPSTATUS_BADVALUE;
fSetAllProps = FALSE;
}
else if ( !(m_rgproperties[ulIndex].dwFlags & DBPROPFLAGS_WRITE) )
{
if ( ((rgPropertyIDSets[ulPropertySets].rgProperties[cCount].vValue.vt == VT_BOOL) &&
(rgPropertyIDSets[ulPropertySets].rgProperties[cCount].vValue.boolVal != m_rgproperties[ulIndex].boolVal)) ||
((rgPropertyIDSets[ulPropertySets].rgProperties[cCount].vValue.vt == VT_I4) &&
(rgPropertyIDSets[ulPropertySets].rgProperties[cCount].vValue.lVal != m_rgproperties[ulIndex].longVal)) ||
((rgPropertyIDSets[ulPropertySets].rgProperties[cCount].vValue.vt == VT_BSTR) &&
(wcscmp(rgPropertyIDSets[ulPropertySets].rgProperties[cCount].vValue.bstrVal,m_rgproperties[ulIndex].pwstrVal))) )
{
rgPropertyIDSets[ulPropertySets].rgProperties[cCount].dwStatus = DBPROPSTATUS_NOTSETTABLE;
fSetAllProps = FALSE;
}
else
fOnePropSet = TRUE;
}
else
{
fOnePropSet = TRUE;
if( rgPropertyIDSets[ulPropertySets].rgProperties->vValue.vt == VT_EMPTY )
{
memcpy( &m_rgproperties[ulIndex], &s_rgprop[ulIndex], sizeof(PROPSTRUCT));
}
else
{
// switch on the propid
switch( m_rgproperties[ulIndex].dwPropertyID )
{
case DBPROP_INIT_DATASOURCE:
wcscpy(m_wszFilePath, V_BSTR(&rgPropertyIDSets[ulPropertySets].rgProperties->vValue));
m_rgproperties[ulIndex].pwstrVal = m_wszFilePath;
break;
case DBPROP_INIT_HWND:
case DBPROP_INIT_PROMPT:
m_rgproperties[ulIndex].longVal = V_I4(&rgPropertyIDSets[ulPropertySets].rgProperties[cCount].vValue);
break;
case DBPROP_IRowsetChange:
m_rgproperties[ulIndex].boolVal = V_BOOL(&rgPropertyIDSets[ulPropertySets].rgProperties[cCount].vValue);
break;
}
}
}
}
else
{
rgPropertyIDSets[ulPropertySets].rgProperties[cCount].dwStatus = DBPROPSTATUS_NOTSUPPORTED;
fSetAllProps = FALSE;
}
}
}
}
// Figure out the retcode
if ( fSetAllProps )
return ResultFromScode( S_OK );
else if ( fOnePropSet && !fSetAllProps )
return ResultFromScode( DB_S_ERRORSOCCURRED );
else
return ResultFromScode( DB_E_ERRORSOCCURRED );
}
|
Initializing and Uninitializing the Data Source Object
The IDBInitialize interface contains two methods: Initialize and Uninitialize. The following sections discuss the implementation of these methods in the sample provider.
Initializing the Data Source Object
Initialize enables consumers to explicitly initialize a data source object. Consumers must set properties on the data source object before attempting to initialize it; and consumers must supply a valid directory path to the data file in IDBProperties::SetProperties. If the directory path is invalid, the sample provider returns an E_FAIL error on initialization. The source code for IDBInitialize::Initialize follows; you can find the complete source code for IDBInitialize in DBInit.cpp.
// CImpIDBInitialize::Initialize --------------------------------------------- //
// @mfunc Initializes the DataSource object.. For this provider it requires
// that a valid path is given to where the file will be located.
//
// @rdesc HRESULT
// @flag S_OK | Path exists
// @flag E_FAIL | Invalid path
// @flag E_INVALIDARG | Invalid Parameters passed in
// @flag DB_E_ALREADYINITIALIZED | Datasource Object already initialized
//
STDMETHODIMP CImpIDBInitialize::Initialize
(
)
{
HRESULT hr;
DBPROPIDSET rgPropertyIDSets[1];
ULONG cPropertySets;
DBPROPSET* prgPropertySets;
DBPROPID rgPropId[2];
char szNewVal[MAX_PATH ];
int nPrompt = DBPROMPT_NOPROMPT;
assert( m_pObj );
if (m_pObj->m_fDSOInitialized)
return ResultFromScode( DB_E_ALREADYINITIALIZED );
rgPropId[0] = DBPROP_INIT_DATASOURCE;
rgPropId[1] = DBPROP_INIT_PROMPT;
rgPropertyIDSets[0].guidPropertySet = DBPROPSET_DBINIT;
rgPropertyIDSets[0].rgPropertyIDs = rgPropId;
rgPropertyIDSets[0].cPropertyIDs = 2;
// Get the value of the DBPROP_INIT_DATASOURCE property
hr = m_pObj->m_pUtilProp->GetProperties(
PROPSET_DSO,
1,
rgPropertyIDSets,
&cPropertySets,
&prgPropertySets );
// On failure treat it as if we were opening with prompt..
if( SUCCEEDED(hr) && (prgPropertySets[0].rgProperties[0].vValue.vt) )
WideCharToMultiByte( CP_ACP, 0, V_BSTR(&prgPropertySets[0].rgProperties->vValue), -1,
szNewVal, sizeof( szNewVal ), NULL, NULL );
else
*szNewVal = '\0';
// Get the Prompt value
nPrompt = V_I2(&prgPropertySets[0].rgProperties[1].vValue);
// Free the memory
if (prgPropertySets)
{
for(ULONG ulIndex=0; ulIndex<prgPropertySets[0].cProperties; ulIndex++)
VariantClear(&prgPropertySets[0].rgProperties[ulIndex].vValue);
g_pIMalloc->Free(prgPropertySets[0].rgProperties);
g_pIMalloc->Free(prgPropertySets);
}
// if caller didn't supply a directory path, ask the user
if (nPrompt != DBPROMPT_NOPROMPT)
{
if (!BrowseDirs( GetDesktopWindow(), g_hInstance, NULL, szNewVal ))
{
return ResultFromScode( E_FAIL );
}
else
{
lstrcpyn( m_pObj->m_szPath, szNewVal, MAX_PATH );
m_pObj->m_fDSOInitialized = TRUE;
return ResultFromScode( S_OK );
}
}
else // caller did supply a directory path
{
// Get the current Directory
char szCurrentDir[MAX_PATH];
GetCurrentDirectory( MAX_PATH, szCurrentDir );
// Check if the directory is a valid directory
if (SetCurrentDirectory( szNewVal ))
{
// Restore to the original directory.
SetCurrentDirectory( szCurrentDir );
lstrcpyn( m_pObj->m_szPath, szNewVal, MAX_PATH );
m_pObj->m_fDSOInitialized = TRUE;
return ResultFromScode( S_OK );
}
else
{
return ResultFromScode( E_FAIL );
}
}
}
|
Uninitializing the Data Source Object
Uninitialize enables consumers to return the data source object to an uninitialized state. It is an error to call IDBInitialize::Uninitialize when there are open sessions or rowsets on the data source object. The source code for IDBInitialize::Uninitialize follows.
// CImpIDBInitialize::Uninitialize --------------------------------------------- //
// @mfunc Returns the Data Source Object to an uninitialized state
//
// @rdesc HRESULT
// @flag S_OK | The method succeeded
// @flag DB_E_OBJECTOPEN | A DBSession object was already created
//
STDMETHODIMP CImpIDBInitialize::Uninitialize
(
void
)
{
assert( m_pObj );
if (!m_pObj->m_fDSOInitialized)
{
// data source object is not initialized; do nothing
return ResultFromScode( S_OK );
}
else
{
if (!m_pObj->m_fDBSessionCreated)
{
// DSO initialized, but no DBSession has been created.
// So, reset DSO to uninitialized state
m_pObj->m_fDSOInitialized = FALSE;
return ResultFromScode( S_OK );
}
else
{
// DBSession has already been created; trying to uninit
// the DSO now is an error
return ResultFromScode( DB_E_OBJECTOPEN );
}
}
}
|
Getting the Class ID of the Data Source Object
Consumers can retrieve the class ID of the data source object by calling IPersist::GetClassID. Generally, the consumer does this only when the provider does not support IPersistFile and the consumer wants to persist the class ID and initialization property values for later use.
The source code for IPersist::GetClassID follows; you can find the complete source code for the IPersist interface in Persist.cpp.
// CImpIPersist::GetClassID -------------------------------------------------- //
// @mfunc Get the CLSID of the DSO.
//
// @rdesc HRESULT
// @flag S_OK | The method succeeded.
// @flag E_FAIL | Provider-specific error.
//
STDMETHODIMP CImpIPersist::GetClassID( CLSID *pClassID )
{
// Check the pointer
if (pClassID)
{
memcpy( pClassID, &CLSID_SampProv, sizeof(CLSID) );
return ResultFromScode(S_OK);
}
return ResultFromScode(E_FAIL);
}
|
Creating a Session
After you initialize the data source object, you must create a session object to manage the session and provide the framework needed to create a rowset with IOpenRowset::OpenRowset. The IDBCreateSession::CreateSession interface on the data source object enables you to create a new session object and returns an interface pointer to the session.
The source code for IDBCreateSession::CreateSession follows; you can find the complete source code for the IDBCreateSession interface in CrtSess.cpp.
// CImpIDBCreateSession::CreateSession ------------------------------------------------ //
// @mfunc Creates a new DB Session object from the DSO, and returns the
// requested interface on the newly created object.
//
// @rdesc HRESULT
// @flag S_OK | The method succeeded.
// @flag E_INVALIDARG | ppDBSession was NULL
// @flag DB_E_NOAGGREGATION | pUnkOuter was not NULL (this object does not support
// being aggregated)
// @flag E_FAIL | Provider-specific error. This provider can only create
// one DBSession
// @flag E_OUTOFMEMORY | Out of memory
// @flag E_NOINTERFACE | Could not obtain requested interface on DBSession object
//
STDMETHODIMP CImpIDBCreateSession::CreateSession
(
IUnknown* pUnkOuter, //@parm IN | Controlling IUnknown if being aggregated
REFIID riid, //@parm IN | The ID of the interface
IUnknown** ppDBSession //@parm OUT | A pointer to memory in which to return the interface pointer
)
{
CDBSession* pDBSession = NULL;
HRESULT hr;
// check in-params and NULL out-params in case of error
if (ppDBSession)
*ppDBSession = NULL;
else
return ResultFromScode( E_INVALIDARG );
if (pUnkOuter)
return ResultFromScode( DB_E_NOAGGREGATION );
assert( m_pObj );
// Check to see if the DSO is Uninitialized
if (!m_pObj->m_fDSOInitialized)
return ResultFromScode( E_UNEXPECTED );
// this Data Source object can only create 1 DBSession...
if (m_pObj->m_fDBSessionCreated)
return ResultFromScode( E_FAIL );
// open a DBSession object
pDBSession = new CDBSession( NULL );
if (!pDBSession)
return ResultFromScode( E_OUTOFMEMORY );
// initialize the object
if (!pDBSession->FInit( m_pObj->m_szPath, m_pObj ))
{
delete pDBSession;
return ResultFromScode( E_FAIL );
}
// get requested interface pointer on DBSession
hr = pDBSession->QueryInterface( riid, (void **) ppDBSession );
if (FAILED( hr ))
{
delete pDBSession;
return ResultFromScode( hr );
}
// all went well
m_pObj->m_fDBSessionCreated = TRUE;
return ResultFromScode( S_OK );
}
|
Once the session has been created, the provider must expose the interface pointer to the data source object that created the session. This interface pointer is exposed through the mandatory interface IGetDataSource. The code for IGetDataSource::GetDataSource follows; you can find the complete source code for the IGetDataSource interface in DBSess.cpp.
//----------------------------------------------------------------------------- // CImpIGetDataSource::GetDataSource
//
// @mfunc Retrieve an interface pointer on the session object
//
// @rdesc
// @flag S_OK | Session Object Interface returned
// @flag E_INVALIDARG | ppDataSource was NULL
// @flag E_NOINTERFACE | IID not supported
//
STDMETHODIMP CImpIGetDataSource::GetDataSource
(
REFIID riid, // @parm IN | IID desired
IUnknown** ppDataSource // @parm OUT | ptr to interface
)
{
// Check Function Arguments
if( ppDataSource == NULL )
return ResultFromScode(E_INVALIDARG);
assert( m_pObj->m_pCDataSource );
return ((m_pObj->m_pCDataSource)->QueryInterface(riid, (LPVOID*)ppDataSource));
}
|
Creating a Rowset
The session contains the interface that enables consumers to open a data file and create a rowset object containing all rows in the data file. The sample provider implements IOpenRowset on the session to create this rowset. The following section describes this interface.
Instantiating and Exposing a Rowset
The IOpenRowset interface contains a single method: OpenRowset. IOpenRowset is a required interface on the session. IOpenRowset::OpenRowset can be used by providers that do not support command objects to generate a rowset of all rows in a table or index. This provides a way for consumers to open and manipulate individual tables or indexes in a database with relatively low resource overhead. In the sample provider implementation, IOpenRowset accepts a file name as the name of the table and concatenates that with the directory name provided through IDBProperties::SetProperties. IOpenRowset uses the directory name and file name to instantiate a rowset and expose the rowset to the consumer.
The source code for the IOpenRowset::OpenRowset method follows; you can find the complete source code for the IOpenRowset interface in OpnRowst.cpp.
// CImpIOpenRowset::OpenRowset ------------------------------------------------ //
// @mfunc Opens and returns a rowset that includes all rows from a single base table
//
// @rdesc HRESULT
// @flag S_OK | The method succeeded
// @flag E_INVALIDARG | pTableID was NULL
// @flag E_FAIL | Provider-specific error
// @flag DB_E_NOTABLE | Specified table does not exist in current Data
// | Data Source object
// @flag E_OUTOFMEMORY | Out of memory
// @flag E_NOINTERFACE | The requested interface was not available
STDMETHODIMP CImpIOpenRowset::OpenRowset
(
IUnknown* pUnkOuter, //@parm IN | Controlling unknown, if any
DBID* pTableID, //@parm IN | table to open
DBID* pIndexID, //@parm IN | DBID of the index
REFIID riid, //@parm IN | interface to return
ULONG cProperties, //@parm IN | count of properties
DBPROPSET rgProperties[], //@parm INOUT | array of property values
IUnknown** ppRowset //@parm OUT | where to return interface
)
{
int cCharsCopied;
char szFileName[MAX_PATH ];
char szFile[_MAX_PATH ] = "";
CFileIO* pFileio = NULL;
CRowset* pRowset = NULL;
HRESULT hr;
HRESULT hrProp = ResultFromScode( S_OK );
// NULL out-params in case of error
if( ppRowset )
*ppRowset = NULL;
// Validate in-params
if (pUnkOuter)
return ResultFromScode( DB_E_NOAGGREGATION );
// Check Arguments
if ( !pTableID && !pIndexID )
return ResultFromScode( E_INVALIDARG );
if ( riid == IID_NULL)
return ResultFromScode( E_NOINTERFACE );
assert( m_pObj->m_pUtilProp );
// Check Arguments for use by properties
hr = m_pObj->m_pUtilProp->SetPropertiesArgChk(cProperties, rgProperties);
if( FAILED(hr) )
return hr;
// If the eKind is not known to use, basically it
// means we have no table identifier
if ( (!pTableID ) || ( pTableID->eKind != DBKIND_NAME ) ||
( (pTableID->eKind == DBKIND_NAME) && (!(pTableID->uName.pwszName)) ) ||
( wcslen(pTableID->uName.pwszName)==0 ) )
return ResultFromScode(DB_E_NOTABLE);
// We only accept NULL for pIndexID
if( pIndexID )
return ResultFromScode(DB_E_NOINDEX);
assert( m_pObj );
// this DBSession object can only create 1 Rowset object
if (m_pObj->m_fRowsetCreated)
return ResultFromScode( E_FAIL );
// set the properties
if ( cProperties )
hr = m_pObj->m_pUtilProp->SetProperties(PROPSET_ROWSET, cProperties, rgProperties);
if( (hr == DB_E_ERRORSOCCURRED) ||
(hr == DB_S_ERRORSOCCURRED) )
{
// If all the properties set were SETIFCHEAP then we can set
// our status to DB_S_ERRORSOCCURRED and continue.
for(ULONG ul=0;ul<cProperties; ul++)
{
for(ULONG ul2=0;ul2<rgProperties[ul].cProperties; ul2++)
{
// Check for a required property that failed, if found, we must return
// DB_E_ERRORSOCCURRED
if( (rgProperties[ul].rgProperties[ul2].dwStatus != DBPROPSTATUS_OK) &&
(rgProperties[ul].rgProperties[ul2].dwOptions != DBPROPOPTIONS_SETIFCHEAP) )
return ResultFromScode(DB_E_ERRORSOCCURRED);
}
}
hrProp = ResultFromScode(DB_S_ERRORSOCCURRED);
}
if( FAILED(hr) && (hrProp==S_OK) )
return hr;
// if ppRowset NULL return
if ( !ppRowset )
return ResultFromScode( S_OK );
// get file name
cCharsCopied = WideCharToMultiByte( CP_ACP, 0, pTableID->uName.pwszName, -1,
szFileName, sizeof( szFileName ), NULL, NULL );
if (!cCharsCopied)
return ResultFromScode( E_FAIL );
// Concatenate the path and filename
lstrcat( szFile, m_pObj->m_szPath );
lstrcat( szFile, "\\" );
lstrcat( szFile, szFileName );
// open and initialize a file object
pFileio = new CFileIO();
if (!pFileio)
return ResultFromScode( E_OUTOFMEMORY );
hr = pFileio->fInit( szFile );
if (FAILED( hr ))
{
delete pFileio;
return hr;
}
// open and initialize a rowset\cursor object
pRowset = new CRowset( NULL );
if (!pRowset)
{
delete pFileio;
return ResultFromScode( E_OUTOFMEMORY );
}
// Initialize the rowset\cursor.
// For now, since don't yet support "setable" properties, so no properties to pass.
// The rowset will always create all of its interfaces.
// This is all-or-nothing.
if (!pRowset->FInit( pFileio ))
{
delete pFileio;
delete pRowset;
return ResultFromScode( DB_E_NOTABLE );
}
//At this point we have handed off the pFileio pointer to the sample
//provider so null it out.
pFileio = NULL;
// get requested interface pointer on rowset\cursor
hr = pRowset->QueryInterface( riid, (void **) ppRowset );
if (FAILED( hr ))
{
delete pRowset;
delete pFileio;
return ResultFromScode( hr );
}
//Assign creator pointer. Used for IRowsetInfo::GetSpecification
pRowset->m_pCreator = m_pObj;
pRowset->m_pCreator->AddRef();
m_pObj->m_fRowsetCreated = TRUE;
return (hr == S_OK) ? hrProp : hr;
}
|
Using the Rowset
Rowsets are the unifying abstraction that enables all OLE DB providers to expose base data, command results, or schema information, in tabular format. Conceptually, a rowset is a collection of rows in which each row contains columns of data.
The IOpenRowset interface exposed by the session generates a rowset object, which encapsulates the provider’s data. A rowset typically has an internal buffer, or row cache, which enables shared access to the provider’s data by multiple consumers. The following section describes how providers and consumers interact with the rowset.
Consumer and Provider Interactions with the Rowset
After receiving the rowset interface pointer, the consumer can request rowset metadata from the provider through IColumnsInfo, if it does not have prior knowledge of the data representation. The consumer uses the metadata to specify bindings, which describe how the consumer wants to receive the data. The consumer creates bindings by requesting IAccessor from the provider and specifying the bindings through IAccessor::CreateAccessor. The provider returns a handle to the accessor to the consumer.
The consumer then requests a number of rows from the provider using IRowset::GetNextRows. The provider retrieves the data for these rows and stores it in the data cache. The provider then returns an array of row handles to the consumer. Each row handle returned by the provider has an initial reference count of one. The consumer is then free to get the data for any rows from the provider using GetData. The consumer supplies GetData with the row handle, the handle of an accessor, and the buffer location into which to return the data; the provider copies the data to the location specified by the consumer.
Consumers using the sample provider can delete or change data in the rowset, but they cannot add new rows. To update rows, consumers call IRowsetChange::SetData, which sets the data in the data cache to the values specified by the consumer. To delete rows from the data cache, the consumer calls IRowsetChange::DeleteRows. A third method on IRowsetChange, IRowsetChange::InsertRow, is not implemented in the sample provider.
When the consumer makes any change to data in the data cache, the effects of the change are written to the data source immediately. OLE DB specifies a change-buffering model, which enables the consumer to make changes that are not realized until the consumer calls IRowsetUpdate::Update; this model is not supported by the sample provider.
When the consumer has finished working with a row, it can release the row by calling IRowset::ReleaseRows. ReleaseRows simply decrements the reference count on the row in the data cache. If the reference count for that row reaches zero, the row data is released from the data cache.
Exposing Metadata
Providers expose information about the columns of a rowset through IColumnsInfo. The information for each column is returned in a DBCOLUMNINFO structure. For more details on the information contained in the DBCOLUMNINFO structure, see IColumnsInfo::GetColumnsInfo in the OLE DB Programmer’s Reference. OLE DB also enables sophisticated providers to expose a richer set of metadata through IColumnsRowset; the sample provider does not implement this interface.
Exposing Metadata to Consumers
The GetColumnInfo method returns metadata that is most commonly used by consumers: column ID, column name, the ordinal number of the column in the rowset, the column’s data type, and so on. This information helps the consumer determine the binding type.
The provider returns the information in an array of DBCOLUMNINFO structures, one DBCOLUMNINFO structure per column in the rowset. The order of the structures returned in the array is the order in which the columns appear in the rowset.
The source code for IColumnsInfo::GetColumnInfo follows; you can find the complete source code for IColumnsInfo in ColInfo.cpp.
// CImpIColumnsInfo::GetColumnInfo ------------------------------------------- //
// @mfunc Returns the column metadata needed by most consumers.
//
// @rdesc HRESULT
// @flag S_OK | The method succeeded
// @flag E_OUTOFMEMORY | Out of memory
// @flag E_INVALIDARG | pcColumns or prginfo or ppStringsbuffer was NULL
//
STDMETHODIMP CImpIColumnsInfo::GetColumnInfo
(
ULONG* pcColumns, //@parm OUT | Number of columns in rowset
DBCOLUMNINFO** prgInfo, //@parm OUT | Array of DBCOLUMNINFO Structures
WCHAR** ppStringsBuffer //@parm OUT | Storage for all string values
)
{
ULONG icol;
ULONG icolStart;
DBCOLUMNINFO* rgdbcolinfo;
WCHAR* pstrBuffer;
// Initialize
if (pcColumns)
*pcColumns = 0;
if (prgInfo)
*prgInfo = NULL;
if (ppStringsBuffer)
*ppStringsBuffer = NULL;
// Usual argument checking, prescribed by the spec.
if (pcColumns == NULL || prgInfo == NULL || ppStringsBuffer == NULL)
return ResultFromScode( E_INVALIDARG );
icolStart = 1;
rgdbcolinfo = (DBCOLUMNINFO *) g_pIMalloc->Alloc( m_pObj->m_cCols*sizeof( DBCOLUMNINFO ));
if (rgdbcolinfo == NULL)
return ResultFromScode( E_OUTOFMEMORY );
memcpy( rgdbcolinfo, &(m_pObj->m_rgdbcolinfo[icolStart]), m_pObj->m_cCols*sizeof( DBCOLUMNINFO ));
// Copy the heap for column names.
if (m_pObj->m_cbHeapUsed)
{
ptrdiff_t dp;
pstrBuffer = (WCHAR *) g_pIMalloc->Alloc( m_pObj->m_cbHeapUsed );
if (pstrBuffer == NULL)
{
g_pIMalloc->Free( rgdbcolinfo );
return ResultFromScode( E_OUTOFMEMORY );
}
memcpy( pstrBuffer, m_pObj->m_pbHeap, m_pObj->m_cbHeapUsed );
dp = (LONG) pstrBuffer - (LONG) (m_pObj->m_pbHeap);
dp >>= 1;
// Loop through columns and adjust pointers to column names.
for (icol =0; icol < m_pObj->m_cCols; icol++)
{
if (rgdbcolinfo[icol].pwszName)
rgdbcolinfo[icol].pwszName += dp;
}
}
*prgInfo = rgdbcolinfo;
*ppStringsBuffer = pstrBuffer;
*pcColumns = m_pObj->m_cCols;
return ResultFromScode( S_OK );
}
|
Returning Column Ordinals
For purposes of identification, columns in a rowset are identified by a column ID, which is a value of type DBID in the DBCOLUMNINFO structure. Although this is sufficient to uniquely identify the column, some providers may need to refer to the column by its position, or ordinal value.
The MapColumnIDs method returns column ordinals for all column IDs provided in the rgColumnIDs array. Column ordinals do not change during the life of the rowset, but may change between different instances of the rowset. The source code for IColumnsInfo::MapColumnIDs follows.
// CImpIColumnsInfo::MapColumnIDs -------------------------------------------- //
// @mfunc Returns an array of ordinals of the columns in a rowset that are
// identified by the specified column IDs.
//
// @rdesc HRESULT
// @flag S_OK | The method succeeded
// @flag E_INVALIDARG | cColumnIDs was not 0 and rgColumnIDs was NULL,
// rgColumns was NULL
// @flag DB_E_COLUMNUNAVAILABLE | An element of rgColumnIDs was invalid
//
STDMETHODIMP CImpIColumnsInfo::MapColumnIDs
(
ULONG cColumnIDs, //@parm IN | Number of Column IDs to map
const DBID rgColumnIDs[], //@parm IN | Column IDs to map
ULONG rgColumns[] //@parm OUT | Ordinal values
)
{
ULONG ulError = 0;
ULONG i;
// If cColumnIDs is 0 return
if (0 == cColumnIDs)
return ResultFromScode( S_OK );
// Check arguments
if ((cColumnIDs != 0) && (NULL == rgColumnIDs))
return ResultFromScode( E_INVALIDARG );
if (NULL == rgColumns)
return ResultFromScode( E_INVALIDARG );
// Walk the Column ID structs and determine
// the ordinal value
for (i=0; i < cColumnIDs; i++)
{
if ((rgColumnIDs[i].uName.ulPropid < 1) ||
(rgColumnIDs[i].uName.ulPropid > m_pObj->m_cCols) ||
(rgColumnIDs[i].eKind != DBKIND_GUID_PROPID) ||
(rgColumnIDs[i].uGuid.guid != GUID_NULL))
{
rgColumns[i] = DB_INVALIDCOLUMN;
ulError++;
}
else
rgColumns[i] = rgColumnIDs[i].uName.ulPropid;
}
if (!ulError)
return ResultFromScode( S_OK );
else if (ulError < cColumnIDs)
return ResultFromScode( DB_S_ERRORSOCCURRED );
else
return ResultFromScode( DB_E_ERRORSOCCURRED );
}
|
Creating and Using Accessors
Consumers describe the memory structure for their buffers through a process called binding. An accessor is a group of bindings. The sample provider supports a very basic type of accessor and cannot support reference accessors, which allow the consumer direct access to the rowset’s data cache.
Accessors are implemented through IAccessor. You can create accessors with IAccessor::CreateAccessor and release accessors with IAccessor::ReleaseAccessor. You can use IAccessor::GetBindings to determine the bindings in an existing accessor. IAccessor::AddRefAccessor enables the consumer to add a reference count to an existing accessor. The following sections describe these methods.
Determining Supported Conversions
Before the consumer creates an accessor, it can call IConvertType::CanConvert to determine if the provider supports a particular conversion.
The source code for IConvertType::CanConvert follows; you can find the complete source code for IConvertType in CvtType.cpp.
//---------------------------------------------------------------------------- // CImpIConvertType::CanConvert
//
// @mfunc Used by consumer to determine provider support for a conversion
//
// @rdesc HRESULT indicating the status of the method
// @flag S_OK | Conversion supported
// @flag S_FALSE | Conversion unsupported
// @flag DB_E_BADCONVERTFLAG | dwConvertFlags was invalid
// @flag DB_E_BADCONVERTFLAG | called on rowset for DBCONVERTFLAG_PARAMETER
// @flag OTHER | HRESULTS returned from support functions
//
STDMETHODIMP CImpIConvertType::CanConvert
(
DBTYPE wFromType, //@parm IN | src type
DBTYPE wToType, //@parm IN | dst type
DBCONVERTFLAGS dwConvertFlags //@parm IN | conversion flags
)
{
// Check Arguments
if( dwConvertFlags != DBCONVERTFLAGS_COLUMN &&
dwConvertFlags != DBCONVERTFLAGS_PARAMETER )
return (ResultFromScode(DB_E_BADCONVERTFLAG));
// Called on rowset, but asking about parameters?
// Since we only support conversions on a Rowset we always
// return error for DBCONVERTFLAGS_PARAMETER
if( dwConvertFlags == DBCONVERTFLAGS_PARAMETER)
return (ResultFromScode(DB_E_BADCONVERTFLAG));
// Ask the conversion library for the answer
return g_pIDataConvert->CanConvert(wFromType, wToType);
}
|
Creating an Accessor
CreateAccessor associates a set of bindings with an accessor handle that is used to send data to or fetch data from the rowset’s data cache. The sample provider supports only the DBACCESSOR_ROWDATA accessor flag, which specifies that the accessor is to be used for rowset data.
The source code for IAccessor::CreateAccessor follows; you can find the complete source code for IAccessor in Accessor.cpp.
// CImpIAccessor::CreateAccessor ----------------------------------------- //
// @mfunc Creates a set of bindings that can be used to send data
// to or retrieve data from the data cache.
// NOTE: Currently does not support returning rgStatus[].
//
// @rdesc HRESULT
// @flag S_OK | Method Succeeded
// @flag E_FAIL | Provider specific Error
// @flag E_INVALIDARG | pHAccessor was NULL, dwAccessorFlags was
// invalid, or cBindings was not 0 and
// rgBindings was NULL
// @flag E_OUTOFMEMORY | Out of Memory
// @flag DB_E_ERRORSOCCURRED | dwBindPart in an rgBindings element was invalid, OR
// | Column number specified was out of range, OR
// | Requested coercion is not supported.
// @flag OTHER | Other HRESULTs returned by called functions
//
STDMETHODIMP CImpIAccessor::CreateAccessor
(
DBACCESSORFLAGS dwAccessorFlags,
ULONG cBindings, //@parm IN | Number of Bindings
const DBBINDING rgBindings[], //@parm IN | Array of DBBINDINGS
ULONG cbRowSize, //@parm IN | Number of bytes in consumer's buffer
HACCESSOR* phAccessor, //@parm OUT | Accessor Handle
DBBINDSTATUS rgStatus[] //@parm OUT | Binding status
)
{
PACCESSOR pAccessor;
ULONG hAccessor;
ULONG ibind;
ULONG icol;
HRESULT hr;
// Check Parameters
if( (cBindings && !rgBindings) ||
(phAccessor == NULL) )
return ResultFromScode( E_INVALIDARG );
// init out params
*phAccessor = (HACCESSOR) 0;
// Check for the binding types we don't accept..
if ((dwAccessorFlags & DBACCESSOR_PASSBYREF) ||
(dwAccessorFlags & DBACCESSOR_PARAMETERDATA))
return ResultFromScode( E_INVALIDARG );
// .. then check for the binding type that is required
if (!(dwAccessorFlags & DBACCESSOR_ROWDATA))
return ResultFromScode( E_INVALIDARG );
// Check on the bindings the user gave us.
for (ibind =0, hr =NOERROR; ibind < cBindings; ibind++)
{
icol = rgBindings[ibind].iOrdinal;
// make sure column number is in range
if (!(0 < icol && icol <= m_pObj->m_cCols))
{
TRACE( "CreateAccessor failure: binding %d, bad column number %d\n", ibind, icol );
hr = ResultFromScode( DB_E_ERRORSOCCURRED );
break;
}
// At least one of these valid parts has to be set. In SetData I assume it is the case.
if ((rgBindings[ibind].dwPart & DBPART_VALUE) == 0
&& (rgBindings[ibind].dwPart & DBPART_LENGTH) == 0
&& (rgBindings[ibind].dwPart & DBPART_STATUS) == 0)
{
TRACE( "CreateAccessor failure: binding %d, column %d, _VALUE, _LENGTH, _STATUS not specified", ibind, icol );
hr = ResultFromScode( DB_E_ERRORSOCCURRED );
if ( rgStatus )
{
// Set Bind status to DBBINDSTATUS_BADBINDINFO
}
break;
}
// Make sure we can do the coercion that is requested
if( NOERROR != g_pIDataConvert->CanConvert(rgBindings[ibind].wType, m_pObj->m_rgdbcolinfo[icol].wType) ||
NOERROR != g_pIDataConvert->CanConvert(m_pObj->m_rgdbcolinfo[icol].wType, rgBindings[ibind].wType) )
{
TRACE( "CreateAccessor failure: binding %d, column %d, cannot coerce types\n", ibind, icol );
hr = ResultFromScode( DB_E_ERRORSOCCURRED );
if ( rgStatus )
{
// Set Bind status to DBBINDSTATUS_UNSUPPORTEDCONVERSION
}
break;
}
}
// Any errors amongst those checks?
if (hr != NOERROR)
{
return hr;
}
// Make a copy of the client's binding array, and the type of binding.
// Note: accessors with no bindings (cBindings=0) are legal.
pAccessor = (ACCESSOR *) new BYTE[sizeof( ACCESSOR ) + (cBindings - 1) *sizeof( DBBINDING )];
if (pAccessor == NULL)
return ResultFromScode( E_OUTOFMEMORY );
// We store a ptr to the newly created variable-sized ACCESSOR.
// We have an array of ptrs (to ACCESSOR's).
// The handle is the index into the array of ptrs.
// The InsertIntoExtBuffer function appends to the end of the array.
assert( m_pObj->m_pextbufferAccessor );
hr = m_pObj->m_pextbufferAccessor->InsertIntoExtBuffer( &pAccessor, hAccessor );
if (FAILED( hr ))
{
delete [] pAccessor;
return ResultFromScode( E_OUTOFMEMORY );
}
assert( hAccessor );
// Copy the client's bindings into the ACCESSOR.
pAccessor->dwAccessorFlags = dwAccessorFlags;
pAccessor->cBindings = cBindings;
pAccessor->cRef = 1; // Establish Reference count.
memcpy( &(pAccessor->rgBindings[0]), &rgBindings[0], cBindings*sizeof( DBBINDING ));
// fill out-param and return
*phAccessor = (HACCESSOR) hAccessor;
return ResultFromScode( S_OK );
}
|
Returning Accessor Bindings
GetBindings returns the bindings in an existing accessor. The source code for IAccessor::GetBindings follows; you can find the complete source code for IAccessor in Accessor.cpp.
// CImpIAccessor::GetBindings -------------------------------------------------- //
// @mfunc Returns the bindings in an accessor
//
// @rdesc HRESULT
// @flag S_OK | Method Succeeded
// @flag E_INVALIDARG | pdwAccessorFlags/pcBinding/prgBinding were NULL
// @flag E_OUTOFMEMORY | Out of Memory
// @flag DB_E_BADACCESSORHANDLE | Invalid Accessor given
//
STDMETHODIMP CImpIAccessor::GetBindings
(
HACCESSOR hAccessor, //@parm IN | Accessor Handle
DBACCESSORFLAGS* pdwAccessorFlags, //@parm OUT | Binding Type flag
ULONG* pcBindings, //@parm OUT | Number of Bindings returned
DBBINDING** prgBindings //@parm OUT | Bindings
)
{
// Retrieve our accessor structure from the client's hAccessor,
// make a copy of the bindings for the user, then done.
PACCESSOR pAccessor;
ULONG cBindingSize;
HRESULT hr;
// check parameters
if (!pdwAccessorFlags || !pcBindings || !prgBindings)
return ResultFromScode( E_INVALIDARG );
// init out-params
*pdwAccessorFlags = 0;
*pcBindings = 0;
*prgBindings = NULL;
// Validate Accessor Handle
hr = m_pObj->m_pextbufferAccessor->GetItemOfExtBuffer((ULONG) hAccessor, &pAccessor );
if (FAILED( hr ) || pAccessor == NULL)
return ResultFromScode( DB_E_BADACCESSORHANDLE );
// Allocate and return Array of bindings
cBindingSize = pAccessor->cBindings * sizeof( DBBINDING );
*prgBindings = (DBBINDING *) g_pIMalloc->Alloc( cBindingSize );
if (*prgBindings)
{
*pdwAccessorFlags = pAccessor->dwAccessorFlags;
*pcBindings = pAccessor->cBindings;
memcpy( *prgBindings, pAccessor->rgBindings, cBindingSize );
}
else
{
return ResultFromScode( E_OUTOFMEMORY );
}
// all went well..
return ResultFromScode( S_OK );
}
|
Adding a Reference Count to an Existing Accessor
AddRefAccessor adds a reference count to an existing accessor. The source code for IAccessor::AddRefAccessor follows.
// CImpIAccessor::AddRefAccessor ----------------------------------------- //
// @mfunc Adds a reference count to an existing accessor
//
// @rdesc HRESULT
// @flag S_OK | Method Succeeded
// @flag E_FAIL | Provider specific Error
//
STDMETHODIMP CImpIAccessor::AddRefAccessor
(
HACCESSOR hAccessor, //@parm IN | Accessor Handle
ULONG* pcRefCounts //@parm OUT | Reference Count
)
{
// Retrieve our accessor structure from the client's hAccessor,
// free it, then mark accessor ptr as unused.
// We do not re-use accessor handles. This way, we hope
// to catch more client errors. (Also, ExtBuffer doesn't
// maintain a free list, so it doesn't know how to.)
PACCESSOR pAccessor;
HRESULT hr;
if( pcRefCounts )
*pcRefCounts = 0;
hr = m_pObj->m_pextbufferAccessor->GetItemOfExtBuffer((ULONG) hAccessor, &pAccessor );
if (FAILED( hr ) || pAccessor == NULL)
return ResultFromScode( DB_E_BADACCESSORHANDLE );
InterlockedIncrement(&(pAccessor->cRef));
if( pcRefCounts )
*pcRefCounts = (ULONG)(pAccessor->cRef);
return ResultFromScode(S_OK);
}
|
Releasing an Accessor
ReleaseAccessor decrements the reference count on an accessor; when the reference count reaches zero, the accessor is released. The source code for IAccessor::ReleaseAccessor follows; you can find the complete source code for IAccessor in Accessor.cpp.
// CImpIAccessor::ReleaseAccessor --------------------------------------- //
// @mfunc Releases an Accessor
//
// @rdesc HRESULT
// @flag S_OK | Method Succeeded
// @flag DB_E_BADACCESSORHANDLE | hAccessor was invalid
//
STDMETHODIMP CImpIAccessor::ReleaseAccessor
(
HACCESSOR hAccessor, //@parm IN | Accessor handle to release
ULONG* pcRefCounts //@parm OUT | Reference Count
)
{
// Retrieve our accessor structure from the client's hAccessor,
// free it, then mark accessor ptr as unused.
// We do not re-use accessor handles. This way, we hope
// to catch more client errors. (Also, ExtBuffer doesn't
// maintain a free list, so it doesn't know how to.)
PACCESSOR pAccessor;
HRESULT hr;
if( pcRefCounts )
*pcRefCounts = 0;
hr = m_pObj->m_pextbufferAccessor->GetItemOfExtBuffer((ULONG) hAccessor, &pAccessor );
if (FAILED( hr ) || pAccessor == NULL)
return ResultFromScode( DB_E_BADACCESSORHANDLE );
// Free the actual structure.
InterlockedDecrement(&(pAccessor->cRef));
assert( pAccessor->cRef >= 0 );
if( pAccessor->cRef <= 0 )
{
delete [] pAccessor;
if( pcRefCounts )
*pcRefCounts = 0;
}
else
{
if( pcRefCounts )
*pcRefCounts = (ULONG)(pAccessor->cRef);
}
// Store a null in our array-of-ptrs,
// so we know next time that it is invalid.
// (operator[] returns a ptr to the space where the ptr is stored.)
*(PACCESSOR*) ((*m_pObj->m_pextbufferAccessor)[ (ULONG) hAccessor]) = NULL;
return ResultFromScode( S_OK );
}
|
Exposing Rowset Information
IRowsetInfo enables consumers to learn about the properties of a rowset through IRowsetInfo::GetProperties. In providers that implement bookmarks, consumers can get an interface pointer to a rowset referenced by a bookmark column with IRowsetInfo::GetReferencedRowset. Consumers can get an interface pointer to the object that created the rowset by calling IRowsetInfo::GetSpecification.
In the sample provider implementation, calls to IRowsetInfo::GetProperties are handled by passing the call to the utility object that manages properties, which is found in UtilProp.cpp. The sample provider doesn’t support bookmarks, so all calls to IRowsetInfo::GetReferencedRowset return the error DB_E_NOTAREFERENCECOLUMN. The sample provider doesn’t store an interface pointer to the object that created the rowset, so calls to IRowsetInfo::GetSpecification are returned with the success code S_OK and the pointer to the memory in which to return the interface pointer is set to NULL.
Managing Rowsets
IRowset provides methods for fetching rows sequentially, exposing data from those rows to consumers, and managing the rows in the rowset. IRowset contains five methods: AddRefRows, GetData, GetNextRows, ReleaseRows, and RestartPosition. The source code for IRowset::AddRefRows follows; you can find the complete source code for the IRowset interface in IRowset.cpp.
Incrementing the Reference Count on Row Handles
AddRefRows increments the reference count on the row handles supplied by the caller. AddRefRows enables consumers to make multiple references to a row in the data cache.
// CImpIRowset::AddRefRows -------------------------------------------------- //
// @mfunc Adds a reference count to an existing row handle
//
// @rdesc HResult
// @flag S_OK | Method Succeeded
// @flag E_INVALIDARG | rghRows was NULL and cRows was not 0
// @flag DB_E_BADROWHANDLE | An element of rghRows was invalid
STDMETHODIMP CImpIRowset::AddRefRows
(
ULONG cRows, // @parm IN | Number of rows to refcount
const HROW rghRows[], // @parm IN | Array of row handles to refcount
ULONG* pcRefCounted, // @parm OUT | Number successfully refcounted
ULONG rgRefCounts[] // @parm OUT | Array of refcounts
)
{
ULONG ihRow = 0;
ROWBUFF *pRowBuff;
ULONG cAddRef;
// init out param
if (pcRefCounted)
*pcRefCounted = 0;
// check params
if (cRows && !rghRows)
return ResultFromScode( E_INVALIDARG );
cAddRef = 0;
// for each of the HROWs the caller provided...
for (ihRow = 0; ihRow < cRows; ihRow++)
{
// check the row handle
if (S_OK != (m_pObj->m_prowbitsIBuffer)->IsSlotSet((ULONG) rghRows[ihRow] ))
return ResultFromScode( DB_E_BADROWHANDLE );
// bump refcount
pRowBuff = m_pObj->GetRowBuff((ULONG) rghRows[ihRow] );
assert( pRowBuff->ulRefCount != 0 );
assert( m_pObj->m_ulRowRefCount != 0 );
++pRowBuff->ulRefCount;
++m_pObj->m_ulRowRefCount;
// bump total refcounted
cAddRef++;
// stuff new refcount into caller's array
if (rgRefCounts)
rgRefCounts[ihRow] = pRowBuff->ulRefCount;
}
// fill out-param
if (pcRefCounted)
(*pcRefCounted) = cAddRef;
return ResultFromScode( S_OK );
}
|
Populating the Data Cache
IRowset::GetNextRows gets the next sequence of rows from the file and places them in the rowset’s data cache. When GetNextRows is first called, it starts at the beginning of the file. After that, GetNextRows maintains information about its current position so it can proceed forward from that position. The sample provider does not support rowsets with reversible direction. The sample provider does, however, support IRowset::RestartPosition, which repositions the next fetch position to the beginning of the file. The source code for IRowset::GetNextRows follows.
// CImpIRowset::GetNextRows -------------------------------------------------- //
// @mfunc Fetches rows in a sequential style, remembering the previous position
//
// @rdesc HRESULT
// @flag S_OK | Method Succeeded
// @flag DB_S_ENDOFROWSET | Reached end of rowset
// @flag DB_E_CANTFETCHBACKWARDS | cRows was negative and we can't fetch backwards
// @flag DB_E_ROWSNOTRELEASED | Must release all HROWs before calling GetNextRows
// @flag E_FAIL | Provider-specific error
// @flag E_INVALIDARG | pcRowsObtained or prghRows was NULL
// @flag E_OUTOFMEMORY | Out of Memory
// @flag OTHER | Other HRESULTs returned by called functions
//
STDMETHODIMP CImpIRowset::GetNextRows
(
HCHAPTER hReserved, //@parm IN | Reserved for future use. Ingored.
LONG cRowsToSkip, //@parm IN | Rows to skip before reading
LONG cRows, //@parm IN | Number of rows to fetch
ULONG *pcRowsObtained, //@parm OUT | Number of rows obtained
HROW **prghRows //@parm OUT | Array of Hrows obtained
)
{
ULONG cRowsTmp;
BOOL fExtraRow;
ULONG cSlotAlloc =0;
ULONG di;
LONG irow, ih;
ULONG irowFirst, irowLast;
PROWBUFF prowbuff;
HRESULT hr;
BYTE *pbRowFirst;
// Check validity of arguments.
if (pcRowsObtained == NULL || prghRows == NULL)
return ResultFromScode( E_INVALIDARG );
// init out-params
*pcRowsObtained = 0;
// No-op case always succeeds.
if (cRows == 0 && cRowsToSkip == 0)
return ResultFromScode( S_OK );
// This implementation doesn't support scrolling backward.
if (cRows < 0 || cRowsToSkip < 0)
return ResultFromScode( DB_E_CANTFETCHBACKWARDS );
// Are there any unreleased rows?
if ((m_pObj->m_prowbitsIBuffer)->ArrayEmpty() != S_OK)
return ResultFromScode( DB_E_ROWSNOTRELEASED );
assert( m_pObj->m_ulRowRefCount == 0 ); // should be true since array was empty
// Is the cursor fully materialized (end-of-cursor condition)?
if (m_pObj->m_dwStatus & STAT_ENDOFCURSOR)
return ResultFromScode( DB_S_ENDOFROWSET );
assert( m_pObj->m_rgbRowData );
if (FAILED( m_pObj->Rebind((BYTE *) m_pObj->GetRowBuff( m_pObj->m_irowMin ))))
return ResultFromScode( E_FAIL );
// Sometimes we need an extra row to fetch data and copy it to appropriate place.
fExtraRow = (cRows > 1);
cRowsTmp = cRows + (fExtraRow ? 1 : 0);
if (FAILED( hr = GetNextSlots( m_pObj->m_pIBuffer, cRowsTmp, &irowFirst )))
return hr;
cSlotAlloc = cRowsTmp;
//
// Fetch Data
//
if (cRowsToSkip)
{
// Calculate the new position
m_pObj->m_irowFilePos += cRowsToSkip;
// Check if skip causes END_OF_ROWSET
if (m_pObj->m_irowFilePos > m_pObj->m_pFileio->GetRowCnt() ||
m_pObj->m_irowFilePos <= 0)
{
m_pObj->m_dwStatus |= STAT_ENDOFCURSOR;
return ResultFromScode( DB_S_ENDOFROWSET );
}
}
pbRowFirst = (BYTE *) m_pObj->GetRowBuff( irowFirst );
for (irow =1; irow <= cRows; irow++)
{
// Get the Data from the File into the row buffer
if (S_FALSE == (hr = m_pObj->m_pFileio->Fetch( m_pObj->m_irowFilePos + irow )))
{
m_pObj->m_dwStatus |= STAT_ENDOFCURSOR;
break;
}
else
{
if (FAILED( hr ))
return ResultFromScode( E_FAIL );
}
// Got a row, so copy it from bound row to the destination.
// Very first row is Fetch buffer, we give out rows [2...cRows+1].
if (fExtraRow)
memcpy( pbRowFirst + (m_pObj->m_cbRowSize * irow), pbRowFirst, m_pObj->m_cbRowSize );
}
cRowsTmp = irow - 1; //Irow will be +1 because of For Loop
m_pObj->m_irowLastFilePos = m_pObj->m_irowFilePos;
m_pObj->m_irowFilePos += cRowsTmp;
//
// Through fetching many rows of data
//
// Allocate row handles for client.
// Note that we need to use IMalloc for this.
//
// Should only malloc cRowsTmp, instead of cRows.
//
// Modified to use IMalloc.
// Should malloc cRows, since client will assume it's that big.
//
*pcRowsObtained = cRowsTmp;
if (*prghRows == NULL)
{
*prghRows = (HROW *) g_pIMalloc->Alloc( cRows*sizeof( HROW ));
}
if (NULL == *prghRows)
return ResultFromScode( E_OUTOFMEMORY );
//
// Fill in the status information: Length, IsNull
// May be able to wait until first call to GetData,
// but have to do it sometime.
//
// Suggest keeping an array of structs of accessor info.
// One element is whether accessor requires any status info or length info.
// Then we could skip this whole section.
//
// Added check for cRowsTmp to MarkRows call.
// Don't want to call if cRowsTmp==0.
// (Range passed to MarkRows is inclusive, so can't specify marking 0 rows.)
//
// Note that SetSlots is a CBitArray member function -- not an IBuffer function.
//
// Bits are [0...n-1], row handles are [1...n].
//
// Cleanup. Row handles, bits, indices are the same [m....(m+n)], where m is some # >0,
//
// Added row-wise reference counts and cursor-wise reference counts.
//
// Set row handles, fix data length field and compute data status field.//
di = fExtraRow ? 1 : 0;
m_pObj->m_cRows = cRowsTmp;
irowLast = irowFirst + di + cRowsTmp - 1;
// Cleanup extra slots where no hRow actually was put..
// ** Because of less rows than asked for
// ** Because of temporary for for data transfer.
if (fExtraRow)
if (FAILED( hr = ReleaseSlots( m_pObj->m_pIBuffer, irowFirst, 1 )))
return hr;
if (cSlotAlloc > (cRowsTmp + di))
if (FAILED( hr = ReleaseSlots( m_pObj->m_pIBuffer, irowFirst + cRowsTmp + di, (cSlotAlloc - cRowsTmp - di))))
return hr;
for (irow = (LONG) (irowFirst + di), ih =0; irow <= (LONG) irowLast; irow++, ih++)
{
// Increment the rows-read count,
// then store it as the bookmark in the very first DWORD of the row.
prowbuff = m_pObj->GetRowBuff( irow );
// Insert the bookmark and its row number (from 1...n) into a hash table.
// This allows us to quickly determine the presence of a row in mem, given the bookmark.
// The bookmark is contained in the row buffer, at the very beginning.
// Bookmark is the row number within the entire result set [1...num_rows_read].
// This was a new Bookmark, not in memory,
// so return to user (in *prghRows) the hRow we stored.
prowbuff->ulRefCount++;
prowbuff->pbBmk = (BYTE*) m_pObj->m_irowLastFilePos + ih + 1;
m_pObj->m_ulRowRefCount++;
(*prghRows)[ih] = (HROW) (irow);
}
if (m_pObj->m_dwStatus & STAT_ENDOFCURSOR)
return ResultFromScode( DB_S_ENDOFROWSET );
else
return ResultFromScode( S_OK );
}
|
Retrieving Data from the Data Cache
IRowset::GetData enables consumers to retrieve data from the data cache. GetData uses the bindings in the accessor to determine how the data should be returned and what data should be returned to the consumer’s buffer. Then, GetData converts the data in the cache to the type specified in the binding and transfers the converted data to the consumer. The source code for IRowset::GetData follows.
// CImpIRowset::GetData -------------------------------------------------- //
// @mfunc Retrieves data from the rowset's cache
//
// @rdesc HRESULT
// @flag S_OK | Method Succeeded
// @flag DB_S_ERRORSOCCURED | Could not coerce a column value
// @flag DB_E_BADACCESSORHANDLE | Invalid Accessor given
// @flag DB_E_BADROWHANDLE | Invalid row handle given
// @flag E_INVALIDARG | pData was NULL
// @flag OTHER | Other HRESULTs returned by called functions
//
STDMETHODIMP CImpIRowset::GetData
(
HROW hRow, //@parm IN | Row Handle
HACCESSOR hAccessor, //@parm IN | Accessor to use
void *pData //@parm OUT | Pointer to buffer where data should go.
)
{
PACCESSOR pAccessor;
ULONG icol, ibind;
ROWBUFF *pRowBuff;
COLUMNDATA *pColumnData;
DBBINDING *pBinding;
ULONG cBindings;
ULONG ulErrorCount;
DBTYPE dwSrcType;
DBTYPE dwDstType;
void *pSrc;
void *pDst;
ULONG ulSrcLength;
ULONG *pulDstLength;
ULONG ulDstMaxLength;
DWORD dwSrcStatus;
DWORD *pdwDstStatus;
DWORD dwPart;
HRESULT hr;
// Coerce data for row 'hRow', according to hAccessor.
// Put in location 'pData'. Offsets and types are in hAccessor's bindings.
//
// Return S_OK if all lossless conversions,
// return DB_S_ERRORSOCCURED if lossy conversion (truncation, rounding, etc.)
// Return E_FAIL, etc., if horrible errors.
// GetItemOfExtBuffer is basically operator[].
// It takes an index (or handle) (referenced from 1...n),
// and a ptr for where to write the data.
//
// It holds ptrs to a variable-length ACCESSOR struct.
// So we get the ACCESSOR ptr for the client's accessor handle.
assert( m_pObj->m_pextbufferAccessor );
hr = m_pObj->m_pextbufferAccessor->GetItemOfExtBuffer((ULONG) hAccessor, &pAccessor );
if (FAILED( hr ) || pAccessor == NULL)
return ResultFromScode( DB_E_BADACCESSORHANDLE );
assert( pAccessor );
cBindings = pAccessor->cBindings;
pBinding = pAccessor->rgBindings;
// IsSlotSet returns S_OK if row is marked.
// S_FALSE if row is not marked.
// The "mark" means that there is data present in the row.
// Rows are [1...n], slot marks are [0...n-1].
if (m_pObj->m_prowbitsIBuffer->IsSlotSet((ULONG) hRow ) != S_OK)
return ResultFromScode( DB_E_BADROWHANDLE );
// Ensure a place to put data.
if (pData == NULL)
return ResultFromScode( E_INVALIDARG );
pRowBuff = m_pObj->GetRowBuff((ULONG) hRow );
// Internal error for a 0 reference count on this row,
// since we depend on the slot-set stuff.
assert( pRowBuff->ulRefCount );
ulErrorCount = 0;
for (ibind = 0; ibind < cBindings; ibind++)
{
icol = pBinding[ibind].iOrdinal;
pColumnData = (COLUMNDATA *) ((BYTE *) pRowBuff + m_pObj->m_rgdwDataOffsets[icol]);
dwSrcType = m_pObj->m_rgdbcolinfo[icol].wType;
pSrc = &(pColumnData->bData);
ulSrcLength = pColumnData->dwLength;
dwSrcStatus = pColumnData->dwStatus;
ulDstMaxLength = pBinding[ibind].cbMaxLen;
dwDstType = pBinding[ibind].wType;
dwPart = pBinding[ibind].dwPart;
pDst = dwPart & DBPART_VALUE ? ((BYTE*) pData + pBinding[ibind].obValue) : NULL;
pulDstLength = dwPart & DBPART_LENGTH ? (ULONG *) ((BYTE*) pData + pBinding[ibind].obLength) : NULL;
pdwDstStatus = dwPart & DBPART_STATUS ? (ULONG *) ((BYTE*) pData + pBinding[ibind].obStatus) : NULL;
hr = g_pIDataConvert->DataConvert(
dwSrcType,
dwDstType,
ulSrcLength,
pulDstLength,
pSrc,
pDst,
ulDstMaxLength,
dwSrcStatus,
pdwDstStatus,
0, // bPrecision for conversion to DBNUMERIC
0, // bScale for conversion to DBNUMERIC
DBDATACONVERT_DEFAULT);
if (FAILED( hr ))
return hr; // fatal error
if (hr != ResultFromScode( S_OK ))
ulErrorCount++; // can't coerce
}
// We report any lossy conversions with a special status.
// Note that DB_S_ERRORSOCCURED is a success, rather than failure.
return ResultFromScode( ulErrorCount ? DB_S_ERRORSOCCURRED : S_OK );
}
|
Decrementing the Reference Count on Row Handles
IRowset::ReleaseRows decrements the reference count on the rows specified in the rghRows array. A consumer must call ReleaseRows once for each time a row was fetched or each time the row had its reference count incremented by AddRefRow. When the reference count reaches zero, the row is released if the rowset is in immediate update mode.
In providers that implement IRowsetUpdate, rows are released unless there are pending changes on the row; the sample provider always performs rowset updates in immediate mode, which means that changes are immediately applied to the underlying data source. Therefore, the sample provider does not recognize any changes as pending. The source code for IRowset::ReleaseRows follows.
// CImpIRowset::ReleaseRows --------------------------------------- //
// @mfunc Releases row handles
//
// @rdesc HRESULT
// @flag S_OK | Method Succeeded
// @flag E_INVALIDARG | Invalid parameters were specified
// @flag DB_E_BADROWHANDLE | Row handle was invalid
//
STDMETHODIMP CImpIRowset::ReleaseRows
(
ULONG cRow, //@parm IN | Number of rows to release
const HROW rghRow[], //@parm IN | Array of handles of rows to be released
DBROWOPTIONS rgRowOptions[], //@parm IN | Additional Options
ULONG *pcRowReleased, //@parm OUT | Count of rows actually released
ULONG rgRefCount[] //@parm OUT | Array of refcnts for the rows
)
{
ULONG ihRow;
BOOL fEncounteredError;
ROWBUFF *pRowBuff;
ULONG cRowReleased = 0;
// check params
if (cRow && !rghRow)
return ResultFromScode( E_INVALIDARG );
// init out param
if (pcRowReleased)
*pcRowReleased = 0;
fEncounteredError = FALSE;
ihRow = 0;
while (ihRow < cRow)
{
if ((m_pObj->m_prowbitsIBuffer)->IsSlotSet((ULONG) rghRow[ihRow] ) == S_OK)
{
// Found valid row, so decrement reference counts.
// (Internal error for refcount to be 0 here, since slot set.)
pRowBuff = m_pObj->GetRowBuff((ULONG) rghRow[ihRow] );
assert( pRowBuff->ulRefCount != 0 );
assert( m_pObj->m_ulRowRefCount != 0 );
--pRowBuff->ulRefCount;
--m_pObj->m_ulRowRefCount;
if (rgRefCount)
rgRefCount[ihRow] = pRowBuff->ulRefCount;
if (pRowBuff->ulRefCount == 0)
{
ReleaseSlots( m_pObj->m_pIBuffer, (ULONG) rghRow[ihRow], 1 );
cRowReleased++;
}
ihRow++;
}
else
{
// It is an error for client to try to release a row
// for which "IsSetSlot" is false. Client gave us an invalid handle.
// Ignore it (we can't release it...) and report error when done.
fEncounteredError = TRUE;
if (rgRefCount)
rgRefCount[ihRow] = 0;
// stop looping if we hit an error
break;
}
}
if (pcRowReleased)
*pcRowReleased = cRowReleased;
if (fEncounteredError)
return ResultFromScode( DB_E_BADROWHANDLE );
else
return ResultFromScode( S_OK );
}
|
Returning to the First Row of the Rowset
IRowset::RestartPosition moves the next fetch position used by GetNextRows to the first row of the rowset. The source code for IRowset::RestartPosition follows.
// CImpIRowset::ResartPosition --------------------------------------- //
// @mfunc Repositions the next fetch position to the start of the rowset
//
// - all rows must be released before calling this method
// - it is not expensive to Restart us, because we are from a single table
//
//
// @rdesc HRESULT
// @flag S_OK | Method Succeeded
// @flag DB_E_ROWSNOTRELEASED | All HROWs must be released before calling
//
STDMETHODIMP CImpIRowset::RestartPosition
(
HCHAPTER hReserved //@parm IN | Reserved for future use. Ignored.
)
{
// make sure all rows have been released
if (S_OK != (m_pObj->m_prowbitsIBuffer)->ArrayEmpty())
return DB_E_ROWSNOTRELEASED;
assert( m_pObj->m_ulRowRefCount == 0 ); // should be true since array was empty
// set "next fetch" position to the start of the rowset
m_pObj->m_irowFilePos = 0;
// clear "end of cursor" flag
m_pObj->m_dwStatus &= ~STAT_ENDOFCURSOR;
return ResultFromScode( S_OK );
}
|
Updating Rows
IRowsetChange enables consumers to change the values of columns in a row of data. If the consumer wants to change the data, it must first construct an accessor for the columns to be changed. IRowsetChange contains three methods: DeleteRows, InsertRow, and SetData. The sample provider does not implement the InsertRow method.
Setting the New Data Values
IRowsetChange::SetData sets new data values for columns in a row. SetData cannot be used to change values in a deleted row. When SetData updates a row, it overwrites the row data in the provider’s data cache and in the underlying data source with at signs (@), causing the row to appear deleted. Then, it appends the new row to the end of the file. Changes made through SetData are applied to the data source immediately. The source code for IRowsetChange::SetData follows; you can find the complete source code for IRowsetChange in RowChng.cpp.
// CImpIRowsetChange::SetData ------------------------------------------------ //
// @mfunc Sets new data values into fields of a row.
//
// @rdesc HRESULT
// @flag S_OK | The method succeeded
// @flag E_OUTOFMEMORY | Out of memory
// @flag DB_E_BADACCESSORHANDLE | Bad accessor handle
// @flag DB_E_READONLYACCESSOR | Tried to write through a read-only accessor
// @flag DB_E_BADROWHANDLE | Invalid row handle
// @flag E_INVALIDARG | pData was NULL
// @flag E_FAIL | Provider-specific error
// @flag OTHER | Other HRESULTs returned by called functions
//
STDMETHODIMP CImpIRowsetChange::SetData
(
HROW hRow, //@parm IN | Handle of the row in which to set the data
HACCESSOR hAccessor, //@parm IN | Handle to the accessor to use
void* pData //@parm IN | Pointer to the data
)
{
PACCESSOR paccessor;
ULONG icol, ibind;
BYTE* pbProvRow;
HRESULT hr;
ULONG cBindings;
DBBINDING* pBinding;
ULONG dwErrorCount;
DBTYPE dwSrcType;
DBTYPE dwDstType;
void* pSrc;
void* pDst;
ULONG dwSrcLength;
ULONG* pdwDstLength;
ULONG dwDstMaxLength;
DWORD dwSrcStatus;
DWORD* pdwDstStatus;
DWORD dwPart;
PCOLUMNDATA pColumnData;
BYTE b;
BYTE* rgbRowDataSave = NULL;
rgbRowDataSave = (BYTE *) malloc( m_pObj->m_cbRowSize );
if (NULL == rgbRowDataSave)
return ResultFromScode( E_OUTOFMEMORY );
if ( m_pObj->m_pextbufferAccessor == NULL
|| FAILED( m_pObj->m_pextbufferAccessor->GetItemOfExtBuffer((ULONG) hAccessor, &paccessor))
|| paccessor == NULL)
{
return ResultFromScode( DB_E_BADACCESSORHANDLE );
}
assert( paccessor );
cBindings = paccessor->cBindings;
pBinding = paccessor->rgBindings;
// Is row handle right?
if ((m_pObj->m_prowbitsIBuffer)->IsSlotSet((ULONG) hRow ) != S_OK)
return ResultFromScode( DB_E_BADROWHANDLE );
// Ensure a source of data.
if (pData == NULL)
return ResultFromScode( E_INVALIDARG );
pbProvRow = (BYTE *) (m_pObj->GetRowBuff((ULONG) hRow ));
// Save the row.
memcpy( rgbRowDataSave, pbProvRow, m_pObj->m_cbRowSize );
// Apply accessor to data.
for (ibind = 0, dwErrorCount = 0; ibind < cBindings; ibind++)
{
icol = pBinding[ibind].iOrdinal;
pColumnData = (COLUMNDATA *) (pbProvRow + m_pObj->m_rgdwDataOffsets[icol]);
dwDstType = m_pObj->m_rgdbcolinfo[icol].wType;
pDst = &(pColumnData->bData);
pdwDstLength = (ULONG *) & (pColumnData->dwLength);
pdwDstStatus = &(pColumnData->dwStatus);
dwDstMaxLength = m_pObj->m_rgdbcolinfo[icol].ulColumnSize;
dwPart = pBinding[ibind].dwPart;
dwSrcType = pBinding[ibind].wType;
if ((dwPart & DBPART_VALUE) == 0)
{
if (((dwPart & DBPART_STATUS)
&& (*(ULONG *) ((BYTE*) pData + pBinding[ibind].obStatus) & DBSTATUS_S_ISNULL))
|| ((dwPart & DBPART_LENGTH) && *(ULONG *) ((BYTE*) pData + pBinding[ibind].obLength) == 0))
{
pSrc = &b;
b = 0x00;
}
else
return ResultFromScode( E_FAIL );
}
else
{
pSrc = (void *) ((BYTE*) pData + pBinding[ibind].obValue);
}
dwSrcLength = (dwPart & DBPART_LENGTH) ? *(ULONG *) ((BYTE*) pData + pBinding[ibind].obLength) : 0;
dwSrcStatus = (dwPart & DBPART_STATUS) ? *(ULONG *) ((BYTE*) pData + pBinding[ibind].obStatus)
: DBSTATUS_S_OK;
hr = g_pIDataConvert->DataConvert(
dwSrcType,
dwDstType,
dwSrcLength,
pdwDstLength,
pSrc,
pDst,
dwDstMaxLength,
dwSrcStatus,
pdwDstStatus,
0, // bPrecision for conversion to DBNUMERIC
0, // bScale for conversion to DBNUMERIC
DBDATACONVERT_SETDATABEHAVIOR);
if (FAILED( hr ))
return hr; // fatal error
if (hr != S_OK)
dwErrorCount++; // rounding or truncation or can't coerce
}
// Carry out the update.
if (FAILED( m_pObj->m_pFileio->UpdateRow((ULONG) ((PROWBUFF) pbProvRow)->pbBmk, m_pObj->m_rgdwDataOffsets, pbProvRow )))
{
// Restore the row to its previous state */
memcpy( pbProvRow, rgbRowDataSave, m_pObj->m_cbRowSize );
return ResultFromScode( E_FAIL );
}
free( rgbRowDataSave );
// We report any lossy conversions with a special status.
// Note that DB_S_ERRORSOCCURED is a success, rather than failure.
return ResultFromScode( dwErrorCount ? DB_S_ERRORSOCCURRED : S_OK );
}
|
Deleting Rows
IRowsetChange also enables consumers to delete rows from the rowset. IRowsetChange::DeleteRows deletes rows from the rowset by overwriting the entire row with at signs (@). Deletions made through DeleteRows are applied to the data source immediately. The source code for IRowsetChange::DeleteRows follows; you can find the complete source code for IRowsetChange in RowChg.cpp.
// CImpIRowsetChange::DeleteRows --------------------------------------- //
// @mfunc Deletes rows from the provider. If Errors on individual rows
// occur, the DBERRORINFO array is updated to reflect the error and S_FALSE
// is returned instead of S_OK.
//
// @rdesc HRESULT indicating the status of the method
// @Flag S_OK | All row handles deleted
// @Flag DB_S_ERRORSOCCURRED | Some, but not all, row handles deleted
// @Flag E_INVALIDARG | Arguments did not match spec.
// @Flag E_OUTOFMEMORY | Could not allocated error array
//
STDMETHODIMP CImpIRowsetChange::DeleteRows
(
HCHAPTER hReserved, //@parm IN | Reserved for future use
ULONG cRows, //@parm IN | Number of rows to delete
const HROW rghRows[], //@parm IN | Array of handles to delete
DBROWSTATUS rgRowStatus[] //@parm OUT | Error information
)
{
ULONG ihRow = 0L;
ULONG cErrors = 0L;
ULONG cRowReleased = 0L;
BYTE* pbProvRow;
// If No Row handle, just return.
if (0 == cRows)
return ResultFromScode( S_OK );
// Check for Invalid Arguments
if ((cRows >= 1) && (NULL == rghRows))
return ResultFromScode( E_INVALIDARG );
// Process row handles
while (ihRow < cRows)
{
if (rgRowStatus)
rgRowStatus[ihRow] = DBROWSTATUS_S_OK;
// Is row handle valid
if (S_OK != (m_pObj->m_prowbitsIBuffer)->IsSlotSet((ULONG) rghRows[ihRow] ))
{
// Log Error
if (rgRowStatus)
rgRowStatus[ihRow]= DBROWSTATUS_E_INVALID;
cErrors++;
ihRow++;
continue;
}
// Get RowBuffer to look at which row this applies to
pbProvRow = (BYTE *) (m_pObj->GetRowBuff((ULONG) rghRows[ihRow] ));
// Has row already been deleted
// S_OK means deleted
if (S_OK == m_pObj->m_pFileio->IsDeleted((ULONG) ((PROWBUFF) pbProvRow)->pbBmk ))
{
if (rgRowStatus)
rgRowStatus[ihRow] = DBROWSTATUS_E_DELETED;
cErrors++;
ihRow++;
continue;
}
// Delete the Row,
if (S_OK != m_pObj->m_pFileio->DeleteRow((ULONG) ((PROWBUFF) pbProvRow)->pbBmk ))
{
// Some better decision as to what rowstatus to set could be done here..
if (rgRowStatus)
rgRowStatus[ihRow] = DBROWSTATUS_E_PERMISSIONDENIED;
cErrors++;
ihRow++;
continue;
}
} //while
// If everything went OK except errors in rows use DB_S_ERRORSOCCURRED.
return cErrors ? (cErrors < cRows) ?
ResultFromScode(DB_S_ERRORSOCCURRED) :
ResultFromScode(DB_E_ERRORSOCCURRED) :
ResultFromScode(S_OK);
}
|