Accessing Rowsets with CRowset

CRowset is a class that wraps some of the functionality of OLE DB rowset interfaces.

The many interfaces that are available to a particular rowset make rowsets cumbersome to consume. Often, more code is dedicated to acquire an interface than is actually required to set up parameters and call methods of the interface itself. To simplify this process, this SDK includes CRowset, a specialized rowset wrapper class that exposes methods that allow users to perform the most basic operations on a rowset without having to manage interfaces. The wrapper traverses interfaces when necessary, caching the commonly used interfaces on demand and releasing cached interfaces on the behalf of a consumer.

It is possible for a consumer to include calls to both the CRowset class and to the OLE DB interfaces directly. The following methods are supplied to allow the programmer to gain access to the OLE DB interfaces exposed by the attached rowset:

// Allow consumers to gain access to key Rowset interfaces.
// These interfaces are returned without AddRef.
//
IRowset* GetRowset(void);
IAccessor* GetAccessor(void);
IRowPosition* GetRowPosition(void);
IDataSource* GetDataSource(void);

These return the requested interface if it is implemented by the attached rowset. If CRowset is not attached, they return Null.

The reference count of the returned interface is not incremented. This allows the caller to quickly test whether an interface is available without having to call Release. The caller must call AddRef to take ownership of a returned interface.

if (m_pRowset->GetRowset() == NULL)
   // Do something that pertains to a detached rowset
   // like returning an error.
else
   // Use the rowset.

The CRowset class is declared in Rowset.h and implemented in Rowset.cpp. CRowset provides its own implementation of some operations that may not be available because a provider does not implement the necessary optional interface.

Attaching Rowset and CRowset Object

To use CRowset, consumers create an instance of the class. They then attach a rowset to the object and begin consumption.

CRowset::Attach

Once an instance of a CRowset object is created, CRowset::Attach must be called to attach it to an IRowset base interface, an IRowPosition interface or an IDataSource interface before it is usable. CRowset::Attach cleans up any interface from a previous attachment before attaching itself to the new interface. It is valid to call this method with a Null argument. In that case, only the cleanup process is performed.

This method performs a number of checks on the IUnknown interface to determine to which of the three interfaces to make the attachment.

HRESULT CRowset::Attach(IUnknown *punk, BSTR bstrDM /* = NULL */)
{
   // If we have a previous attachment then clean up first
   //
   if (m_pDataSource || m_pRowset || m_pRowsetPos)
   {
      SetChapter(NULL);
      ZeroMemory((void*)&m_properties, sizeof m_properties);
      m_columns.RemoveAll();

      // Release all Rowset interfaces
      //
      RELEASE_OBJECT(m_pRowsetPos);
      RELEASE_OBJECT(m_pRowsetLocate);
      RELEASE_OBJECT(m_pRowsetScroll);
      RELEASE_OBJECT(m_pRowsetExactScroll);
      RELEASE_OBJECT(m_pRowsetFind);
      RELEASE_OBJECT(m_pRowsetInfo);
      RELEASE_OBJECT(m_pRowsetIdentity);
      RELEASE_OBJECT(m_pRowsetChange);
      RELEASE_OBJECT(m_pRowsetUpdate);
      RELEASE_OBJECT(m_pAccessor);
      RELEASE_OBJECT(m_pConvertType);
      RELEASE_OBJECT(m_pColumnsInfo);
      RELEASE_OBJECT(m_pRowset);
      RELEASE_OBJECT(m_pDataSource);

      if (m_bstrDM)
      {
         SysFreeString(m_bstrDM);
         m_bstrDM = NULL;
      }
   }
   if (NULL == punk)
      return S_OK;

   // Is this a DataSource interface?
   //
   HRESULT hr = punk->QueryInterface(IID_DataSource, (void**)&m_pDataSource);
   if (m_pDataSource)
   {
      // Try to retrieve an IRowPosition from the DataSource. This call
      // will fail if the DataSource is not initialized, which will
      // leave m_pRowsetPos NULL
      //
      m_pDataSource->getDataMember(bstrDM, IID_IRowPosition, (IUnknown**)&m_pRowsetPos);
      if (bstrDM)
         m_bstrDM = SysAllocString((WCHAR*)bstrDM);

      // Return S_FALSE if the DataSource is not initialized
      //
      if (NULL == m_pRowsetPos)
         hr = S_FALSE;
   }
   else
   {
      // Is this an IRowset?
      //
      hr = punk->QueryInterface(IID_IRowset, (void**)&m_pRowset);

      // Is this a RowPostion interface?
      //
      if (NULL == m_pRowset)
         hr = punk->QueryInterface(IID_IRowPosition, (void**)&m_pRowsetPos);
   }
   // Once we have IRowPosition, we can get the associated IRowset
   //
   if (m_pRowsetPos)
      hr = m_pRowsetPos->GetRowset(IID_IRowset, (IUnknown**)&m_pRowset);

   // Get the rowset properties if IRowset is available
   //
   if (m_pRowset)
   {
      // Do one time initialization of the 
      // contained ROWSETPROPERTIES structure
      //
      GetProperties();
   }
   return hr;
}

The optional parameter is used when the interface is an IDataSource. This parameter is the data member name and can be NULL for the default data member.

Getting Rowset Properties

A rowset’s properties tell consumers what optional interfaces the rowset implements, as well as a number of attributes that describe limitations and capabilities of the rowset, how it responds to editing, and its threading model. Consumers are encouraged to query properties for a particular rowset to establish whether certain optional interfaces are available and whether it possesses capabilities required by the consumer.

CRowset::GetRowsetProperties

This method uses IRowsetInfo::GetProperties to retrieve a rowset’s property values. The first argument can be an external IUnknown of a rowset or Null for the attached rowset.

HRESULT CRowset::GetRowsetProperties(IUnknown *pRowset, const DBPROPID *pPropIDs, VARIANT *pValues, ULONG cPropIDs)
{
   // Validate arguments.
   //
   if (!(pPropIDs && pValues && cPropIDs))
      return E_INVALIDARG;

   HRESULT hr;
   IRowsetInfo * pRowsetInfo = NULL;

   if (pRowset && pRowset != m_pRowset)
      // Traverse to desired interface.
      //
      hr = pRowset->QueryInterface(IID_IRowsetInfo, (void**)&pRowsetInfo);
   else
   {
      // Use attached Rowset.
      //
      hr = InitRowsetInfo();
      pRowsetInfo = m_pRowsetInfo;
   }
   RETURN_ON_FAILURE(hr);

   // Initialize DBPROPSET parameter.
   //
   DBPROPIDSET propidSet;
   propidSet.rgPropertyIDs = (DBPROPID*)pPropIDs;
   propidSet.cPropertyIDs = cPropIDs;
   propidSet.guidPropertySet = DBPROPSET_ROWSET;

   // Retrieve property values.
   //
   DBPROPSET *ppropSet = NULL;   // Array DBPROPSET.
   ULONG    cpropSets = 0;      // Number DBPROPSET in array
   
   hr = pRowsetInfo->GetProperties(1, &propidSet, &cpropSets, &ppropSet);

   if (SUCCEEDED(hr) && NULL != ppropSet)
   {
      // Do a byte copy of the variant property 
      // value and leave it up to the caller to call
      // VariantClear when it is done with the value 
      // to do any necessary cleanup.
      //
      for (ULONG i = 0; i < ppropSet[0].cProperties; i++)
         pValues[i] = ppropSet[0].rgProperties[i].vValue;

      // Free callee-allocated buffer.
      //

CoTaskMemFree((void*)ppropSet[0].rgProperties);

      CoTaskMemFree((void*)ppropSet);
   }
   // If pRowsetInfo is not ours, then release it.
   //
   if (NULL != pRowsetInfo && pRowsetInfo != m_pRowsetInfo)
      pRowsetInfo->Release();

   return hr;
}

ROWSETPROPERTIES Structure

The ROWSETPROPERTIES structure contains a subset of rowset properties that may interest a consumer. CRowset initializes its own copy of this structure after it has successfully attached itself to a rowset. The definition of this structure is located in Abdhelp.h.

struct ROWSETPROPERTIES
{
   // Value properties.
   //
   struct
   {
      long   MaxOpenRows;   // Maximum rows consumers are allowed to hold.
      long   Updatability;   // IRowsetChange methods supported.
   }   value;

   // Boolean properties.
   //
   struct
   {
      UINT   StrongId      :1;   // Newly inserted HROWs can be compared.
      UINT   LiteralId      :1;   // HROWs can be literally compared.
      UINT   HasBookmarks   :1;   // Rows have self-bookmark columns.
                           //   IRowsetLocate is implemented.
      UINT   CanHoldRows      :1;   // HROWs can be held in between GetRows.
      UINT   CanScrollBack   :1;   // GetRows can scroll backward.
                           //   IRowsetScroll is implemented.
      UINT   CanFetchBack   :1;   // GetRows can fetch backward.
      UINT   CanChange      :1;   // IRowsetChange is implemented.
      UINT   CanUpdate      :1;   // IRowsetUpdate is implemented.

      UINT   IRowsetScroll   :1;      // IRowsetScroll is implemented
      UINT   IRowsetExactScroll:1;   // IRowsetExactScroll is implemented
   }   flag;
};

CRowset::GetProperties

The CRowset::GetProperties method is called by CRowset::Attach to initialize the ROWSETPROPERTIES structure member of CRowset. A read-only reference to the structure is returned so callers can inspect the rowset’s properties.

See Rowset.cpp for the definition of CRowset::GetProperties.

#define IS_TRUE(pv) (VT_BOOL == V_VT(pv) && VARIANT_FALSE != V_BOOL(pv))

const ROWSETPROPERTIES& CRowset::GetProperties(void)
{
   // List of DBPROPSET_ROWSET Properties we 
   // are interested in
   //
   static const DBPROPID rgpropid[] =
   {
      // Value props
      //
      DBPROP_MAXOPENROWS,   
      DBPROP_UPDATABILITY,   

      // BOOL props
      //
      DBPROP_STRONGIDENTITY,
      DBPROP_LITERALIDENTITY,
      DBPROP_BOOKMARKS,
      DBPROP_CANHOLDROWS,
      DBPROP_CANSCROLLBACKWARDS,
      DBPROP_CANFETCHBACKWARDS,
      DBPROP_IRowsetChange,
      DBPROP_IRowsetUpdate,
      DBPROP_IRowsetScroll,
      DBPROP_IRowsetExactScroll
   };
   static const ULONG cPropIDs = sizeof rgpropid/sizeof rgpropid[0];

   // Check to see if we need to initialize
   // the properties structure
   //
   if (NULL == m_pRowsetInfo)
   {
      HRESULT hr = InitRowsetInfo();
      
      if (SUCCEEDED(hr))
      {
         // Allocate VARIANT values array
         //
         VARIANT *pValues = new VARIANT[cPropIDs];
         
         if (NULL == pValues)
            hr = E_OUTOFMEMORY;
         else
         {
            // Zero out VARIANTs and get property values
            //
            ZeroMemory((void*)pValues, cPropIDs * sizeof(VARIANT));
            hr = GetRowsetProperties(NULL, rgpropid, pValues, cPropIDs);

            // Save property values in structure
            //
            if (SUCCEEDED(hr))
            {
               m_properties.value.MaxOpenRows   = VT_I4 == V_VT(pValues) ? V_I4(pValues) : 0;
               m_properties.value.Updatability   = VT_I4 == V_VT(pValues+1) ? V_I4(pValues+1) : 0;

               m_properties.flag.StrongId         = IS_TRUE(pValues+2);
               m_properties.flag.LiteralId         = IS_TRUE(pValues+3);
               m_properties.flag.HasBookmarks      = IS_TRUE(pValues+4);
               m_properties.flag.CanHoldRows      = IS_TRUE(pValues+5);
               m_properties.flag.CanScrollBack      = IS_TRUE(pValues+6);
               m_properties.flag.CanFetchBack      = IS_TRUE(pValues+7);
               m_properties.flag.CanChange         = IS_TRUE(pValues+8);
               m_properties.flag.CanUpdate         = IS_TRUE(pValues+9);
               m_properties.flag.IRowsetScroll      = IS_TRUE(pValues+10);
               m_properties.flag.IRowsetExactScroll= IS_TRUE(pValues+11);
            }
            delete [] pValues;
         }
      }
   }
   return m_properties;
}

Columns Information

Once a rowset’s properties have been inspected and it is ready for consumption, the next step is to collect information about the rowset’s columns. A rowset exposes information about its columns through the required IColumnsInfo and the optional IColumnsRowset interfaces. IColumnsRowset exposes metadata in tabular format and is consumed like any other rowset.

Of the two, IColumnsRowset provides the most complete metadata information but is more complicated to consume. IColumnsInfo provides consumers with the most commonly used metadata through an array of DBCOLUMNINFO structures, and is more convenient and easy to consume. For the most part, consumers will find the metadata supplied by DBCOLUMNINFO to be sufficient.

COLUMNINFO Structure

The COLUMNINFO structure is an extension of DBCOLUMNINFO and includes additional metadata about a column. The additional metadata is retrieved using IColumnsRowset, which is demonstrated later in this section. This structure is defined with a constructor and destructor to perform initialization and cleanup of complex data members. Its implementation is in Adbhelp.cpp.

struct COLUMNINFO : public DBCOLUMNINFO
{
public:
   COLUMNINFO();
   ~COLUMNINFO();

   void Clear(BOOL fZeroMemory = TRUE);

// Additional metadata
public:
   VARIANT   varDefault;      // Default value for a column

// Operators
public:
   COLUMNINFO& operator=(const COLUMNINFO& other);
   COLUMNINFO& operator=(const DBCOLUMNINFO& other);
};

CRowset::GetColumns

The CRowset::GetColumns method uses IColumnsInfo::GetColumnInfo to populate an array of COLUMNINFO structures; it then calls CRowset::InitColumnsMetadata to fill additional metadata members for each COLUMNINFO in the array.

HRESULT CRowset::GetColumns(COLUMNINFO*& pColumns, ULONG& cColumns)
{
   // Clear out bound parameters.
   //
   pColumns = NULL;
   cColumns = 0;

   HRESULT hr = InitColumnsInfo();
   RETURN_ON_FAILURE(hr);

   // Get the column information.
   //
   OLECHAR         *wszColumns   = NULL;
   DBCOLUMNINFO   *pDBColumns   = NULL;
   
   hr = m_pColumnsInfo->GetColumnInfo(&cColumns, &pDBColumns, &wszColumns);
   
   if (SUCCEEDED(hr) && cColumns > 0)
   {
      // Allocate COLUMNINFO array.
      //
      pColumns = new COLUMNINFO[cColumns];
      if (NULL == pColumns)
      {
         cColumns = 0;
         hr = E_OUTOFMEMORY;
      }
      else for (register ULONG i = 0; i < cColumns; i++)
         pColumns[i] = pDBColumns[i];
   }
   // Free callee-allocated buffers.
   //
   if (pDBColumns)
   {
      for (register ULONG i = 0; i < cColumns; i++)
      {
         if (pDBColumns[i].pTypeInfo)
            pDBColumns[i].pTypeInfo->Release();
      }
      CoTaskMemFree((void*)pDBColumns);
      CoTaskMemFree((void*)wszColumns);
   }
   // Get the default value metadata for
   // each column.
   //
   if (cColumns)
      InitColumnsMetadata(pColumns, cColumns);

   return hr;
}

CRowset::InitColumnsMetadata

This method is a protected member of CRowset and cannot be called by consumers, but it is discussed here because it uses IColumnsRowset::GetColumnsRowset to retrieve the rowset containing column metadata for the base rowset. The column rowset is read-only and can be consumed in the same manner as the base rowset. This method also demonstrates the use of more advanced CRowset methods that are discussed later in this section.

HRESULT CRowset::InitColumnsMetadata(COLUMNINFO*& pColumns, ULONG cColumns)
{
   if (!(pColumns && cColumns))
      return E_INVALIDARG;

   // Determine if the rowset implements IColumnsRowset.
   //
   IColumnsRowset   *pColRowset = NULL;
   HRESULT hr = m_pRowset->QueryInterface(IID_IColumnsRowset, (void **)&pColRowset);
   RETURN_ON_FAILURE(hr);

   IUnknown   *punkRowset   = NULL;   // Columns Rowset
   CRowset      *pRowset   = NULL;   // Used to consume Columns Rowset
   HROW      *phRows      = NULL;   // Receives row handles
   ULONG      cRows      = 0;   // Receives row handles count
   HACCESSOR   haccessor   = NULL;   // Accessor for Columns Rowset
   DBBINDING   dbBinding;         // Binding for default value column
   CBookmark bmk(sizeof DBBMK_FIRSTROW, &DBBMK_FIRSTROW);

   // Get the IRowset for all required metadata columns
   // and the optional default value column. The returned
   // rowset is read-only and can be consumed like any other.
   //
   hr = pColRowset->GetColumnsRowset(NULL, 1, &DBCOLUMN_DEFAULTVALUE, 
    IID_IRowset, 0, NULL, &punkRowset);
   pColRowset->Release();
   if (FAILED(hr)) goto CleanUp;

   // Attach it to a CRowset.
   // 
   pRowset = new CRowset();
   if (NULL == pRowset)
   {
      hr = E_OUTOFMEMORY;
      goto CleanUp;
   }

   hr = pRowset->Attach(punkRowset);
   if (FAILED(hr)) goto CleanUp;

   // Create binding for the default value column.
   //
   InitBinding(dbBinding, 0, DBTYPE_VARIANT, sizeof(VARIANT), offsetof(COLUMNINFO, varDefault));
   hr = pRowset->GetColumnOrdinals(&dbBinding.iOrdinal, &DBCOLUMN_DEFAULTVALUE, 1);
   if (FAILED(hr) || DB_INVALIDCOLUMN == dbBinding.iOrdinal) goto CleanUp;

   // The number of rows fetched needs
   // to be the same as the number of columns.
   //
   hr = pRowset->GetRows(bmk, 0, (long)cColumns, phRows, cRows);
   if (FAILED(hr) || cRows != cColumns) goto CleanUp;

   // Create an accessor.
   //
   hr = pRowset->CreateAccessor(&dbBinding, 1, haccessor);

   if (NULL != haccessor)
   {
      // Get the default value for each column.
      //
      for (ULONG i = 0; i < cRows; i++)
         pRowset->GetData(phRows[i], haccessor, (void*)(pColumns+i));

      pRowset->ReleaseAccessor(haccessor);
   }
   
CleanUp:
   if (punkRowset)
      punkRowset->Release();

   if (pRowset)
   {
      if (phRows)
         pRowset->FreeRows(phRows, cRows);

      delete pRowset;
   }
   return hr;
}