MDAC 2.5 SDK - Technical Articles


 

Sample Provider Overview

The OLE DB sample provider, SAMPPROV.DLL, is a learning tool for developers new to OLE DB. Users can display 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 when using IOpenRowset to expose rowsets over a file-based data source.

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:

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 a release version of SAMPPROV on the Intel x86 platform based on the Win32 (x86) Dynamic-Link Library configuration, use this syntax:

NMAKE /f "sampprov.mak" CFG="sampprov - Win32 x86 Release"

Note   Neither Microsoft Windows® 2000 nor MDAC 2.5 is supported on Alpha platforms.

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 (COM). 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," earlier 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.

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

Uninitialize

DBInit.cpp

DBInit.cpp

IDBCreateSession CreateSession CrtSess.cpp
IDBProperties

 

 

GetProperties

GetPropertyInfo

SetProperties

UtilProp.cpp

UtilProp.cpp

UtilProp.cpp

IPersist GetClassID Persist.cpp
IGetDataSource GetDataSource DBSess.cpp
IOpenRowset OpenRowset OpnRowst.cpp
IColumnsInfo

 

GetColumnInfo

MapColumnIDs

ColInfo.cpp

ColInfo.cpp

IConvertType CanConvert CvtType.cpp
IAccessor

 

 

 

AddRefAccessor

CreateAccessor

GetBindings

ReleaseAccessor

Accessor.cpp

Accessor.cpp

Accessor.cpp

Accessor.cpp

IRowset

 

 

 

 

AddRefRows

GetData

GetNextRows

ReleaseRows

RestartPosition

IRowset.cpp

IRowset.cpp

IRowset.cpp

IRowset.cpp

IRowset.cpp

IRowsetInfo

 

 

GetProperties

GetReferencedRowset

GetSpecification

RowInfo.cpp

RowInfo.cpp

RowInfo.cpp

IRowsetChange

 

 

DeleteRows

InsertRow

SetData

RowChng.cpp

RowChng.cpp

RowChng.cpp

ISessionProperties

 

GetProperties

SetProperties

DBSess.cpp

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 Microsoft® 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:

The IDBInfo interface contains two methods:

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"}, /* For OLE DB 2.5 and later, replace DBPROP_PROVIDERNAME with DBPROP_PROVIDERFILENAME */
/* 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 might 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 might 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, GetNextRows, GetData, 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 that 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_ERRORSOCCURRED   | 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_ERRORSOCCURRED 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_ERRORSOCCURRED 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_ERRORSOCCURRED 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);
}