MDAC 2.5 SDK - OLE DB Programmer's Reference
Chapter 6: Getting and Setting Data


 

Binding and Accessor Example

The code in this example shows how to set up bindings and use them to create an accessor.

/////////////////////////////////////////////////////////////////
// mySetupBindings
//
// This function takes an IUnknown pointer from a rowset object
// and creates a bindings array that describes how we want the
// data we fetch from the rowset to be laid out in memory. It
// also calculates the total size of a row so that we can use
// this to allocate memory for the rows that we will fetch
// later.
//
// For each column in the rowset, there will be a corresponding
// element in the bindings array that describes how the
// provider should transfer the data, including length and
// status, for that column. This element also specifies the data
// type that the provider should return the column as. We will
// bind all columns as DBTYPE_WSTR, with a few exceptions
// detailed below, as providers are required to support the
// conversion of their column data to this type in the vast
// majority of cases. The exception to our binding as
// DBTYPE_WSTR is if the native column data type is
// DBTYPE_IUNKNOWN or if the user has requested that BLOB
// columns be bound as ISequentialStream objects, in which case
// we will bind those columns as ISequentialStream objects.
//
/////////////////////////////////////////////////////////////////
HRESULT mySetupBindings
   (
   IUnknown *           pUnkRowset, 
   ULONG *              pcBindings, 
   DBBINDING **         prgBindings, 
   ULONG *              pcbRowSize
   )
{
   HRESULT              hr;
   ULONG                cColumns;
   DBCOLUMNINFO *       rgColumnInfo      = NULL;
   LPWSTR               pStringBuffer     = NULL;
   IColumnsInfo *       pIColumnsInfo     = NULL;

   ULONG                iCol;
   ULONG                dwOffset          = 0;
   DBBINDING *          rgBindings        = NULL;
   
   ULONG                cStorageObjs      = 0;
   BOOL                 fMultipleObjs     = FALSE;

   // Obtain the column information for the rowset; from this, we can 
   // find out the following information that we need to construct the 
   // bindings array:
   //  - the number of columns
   //  - the ordinal of each column
   //  - the precision and scale of numeric columns
   //  - the OLE DB data type of the column
   XCHECK_HR(hr = pUnkRowset->QueryInterface(
             IID_IColumnsInfo, (void**)&pIColumnsInfo));
   XCHECK_HR(hr = pIColumnsInfo->GetColumnInfo(
             &cColumns,          // pcColumns
             &rgColumnInfo,      // prgColumnInfo
             &pStringBuffer));   // ppStringBuffer
            

   // Allocate memory for the bindings array; there is a one-to-one
   // mapping between the columns returned from GetColumnInfo and our
   // bindings.
   rgBindings = (DBBINDING*)CoTaskMemAlloc(cColumns * sizeof(DBBINDING));
   CHECK_MEMORY(hr, rgBindings);
   memset(rgBindings, 0, cColumns * sizeof(DBBINDING));

   // Determine whether the rowset supports multiple storage object 
   // bindings. If it does not, we will bind only the first BLOB column 
   // or IUnknown column as an ISequentialStream object, and will bind 
   // the rest as DBTYPE_WSTR.
   myGetProperty(pUnkRowset, IID_IRowset, DBPROP_MULTIPLESTORAGEOBJECTS, 
                 DBPROPSET_ROWSET, &fMultipleObjs);

   // Construct the binding array element for each column.
   for( iCol = 0; iCol < cColumns; iCol++ )
   {
      // This binding applies to the ordinal of this column.
      rgBindings[iCol].iOrdinal   = rgColumnInfo[iCol].iOrdinal;

      // We are asking the provider to give us the data for this column
      // (DBPART_VALUE), the length of that data (DBPART_LENGTH), and
      // the status of the column (DBPART_STATUS).
      rgBindings[iCol].dwPart = DBPART_VALUE|DBPART_LENGTH|DBPART_STATUS;

      // The following values are the offsets to the status, length, and
      // data value that the provider will fill with the appropriate 
      // values when we fetch data later. When we fetch data, we will 
      // pass a pointer to a buffer that the provider will copy column 
      // data to, in accordance with the binding we have provided for 
      // that column;these are offsets into that future buffer.
      rgBindings[iCol].obStatus   = dwOffset;
      rgBindings[iCol].obLength   = dwOffset + sizeof(DBSTATUS);
      rgBindings[iCol].obValue    = dwOffset + sizeof(DBSTATUS) + 
                                     sizeof(ULONG);
      
      // Any memory allocated for the data value will be owned by us, the
      // client. Note that no data will be allocated in this case, as the
      // DBTYPE_WSTR bindings we are using will tell the provider to 
      // simply copy data directly into our provided buffer.
      rgBindings[iCol].dwMemOwner = DBMEMOWNER_CLIENTOWNED;

      // This is not a parameter binding.
      rgBindings[iCol].eParamIO = DBPARAMIO_NOTPARAM;
      
      // We want to use the precision and scale of the column.
      rgBindings[iCol].bPrecision = rgColumnInfo[iCol].bPrecision;
      rgBindings[iCol].bScale = rgColumnInfo[iCol].bScale;

      // Bind this column as DBTYPE_WSTR, which tells the provider to
      // copy a Unicode string representation of the data into our 
      // buffer, converting from the native type if necessary.
      rgBindings[iCol].wType = DBTYPE_WSTR;

      // Initially, we set the length for this data in our buffer to 0;
      // the correct value for this will be calculated directly below.
      rgBindings[iCol].cbMaxLen   = 0;
                  
      // Determine the maximum number of bytes required in our buffer to
      // contain the Unicode string representation of the provider's 
      // native data type, including room for the NULL-termination. 
      character switch( rgColumnInfo[iCol].wType )
      {
         case DBTYPE_NULL:
         case DBTYPE_EMPTY:
         case DBTYPE_I1:
         case DBTYPE_I2:
         case DBTYPE_I4:
         case DBTYPE_UI1:
         case DBTYPE_UI2:
         case DBTYPE_UI4:
         case DBTYPE_R4:
         case DBTYPE_BOOL:
         case DBTYPE_I8:
         case DBTYPE_UI8:
         case DBTYPE_R8:
         case DBTYPE_CY:
         case DBTYPE_ERROR:
            // When the above types are converted to a string, they
            // will all fit into 25 characters, so use that plus space
            // for the NULL-terminator.
            rgBindings[iCol].cbMaxLen = (25 + 1) * sizeof(WCHAR);
            break;

         case DBTYPE_DECIMAL:
         case DBTYPE_NUMERIC:
         case DBTYPE_DATE:
         case DBTYPE_DBDATE:
         case DBTYPE_DBTIMESTAMP:
         case DBTYPE_GUID:
            // Converted to a string, the above types will all fit into
            // 50 characters, so use that plus space for the terminator.
            rgBindings[iCol].cbMaxLen = (50 + 1) * sizeof(WCHAR);
            break;
         
         case DBTYPE_BYTES:
            // In converting DBTYPE_BYTES to a string, each byte
            // becomes two characters (e.g. 0xFF -> "FF"), so we
            // will use double the maximum size of the column plus
            // include space for the NULL-terminator.
            rgBindings[iCol].cbMaxLen =
               (rgColumnInfo[iCol].ulColumnSize * 2 + 1) * sizeof(WCHAR);
            break;

         case DBTYPE_STR:
         case DBTYPE_WSTR:
         case DBTYPE_BSTR:
            // Going from a string to our string representation,
            // we can just take the maximum size of the column,
            // a count of characters, and include space for the
            // terminator, which is not included in the column size.
            rgBindings[iCol].cbMaxLen = 
               (rgColumnInfo[iCol].ulColumnSize + 1) * sizeof(WCHAR);
            break;

         default:
            // For any other type, we will simply use our maximum
            // column buffer size, since the display size of these
            // columns may be variable (e.g. DBTYPE_VARIANT) or
            // unknown (e.g. provider-specific types).
            rgBindings[iCol].cbMaxLen = MAX_COL_SIZE;
            break;
      };
      
      // If the provider's native data type for this column is
      // DBTYPE_IUNKNOWN or this is a BLOB column and the user
      // has requested that we bind BLOB columns as ISequentialStream
      // objects, bind this column as an ISequentialStream object if
      // the provider supports our creating another ISequentialStream
      // binding.
      if( (rgColumnInfo[iCol].wType == DBTYPE_IUNKNOWN ||
          ((rgColumnInfo[iCol].dwFlags & DBCOLUMNFLAGS_ISLONG) &&
          (g_dwFlags & USE_ISEQSTREAM))) &&
          (fMultipleObjs || !cStorageObjs) )
      {
         // To create an ISequentialStream object, we will
         // bind this column as DBTYPE_IUNKNOWN to indicate
         // that we are requesting this column as an object.
         rgBindings[iCol].wType = DBTYPE_IUNKNOWN;

         // We want to allocate enough space in our buffer for
         // the ISequentialStream pointer we will obtain from
         // the provider.
         rgBindings[iCol].cbMaxLen = sizeof(ISequentialStream *);

         // To specify the type of object that we want from the
         // provider, we need to create a DBOBJECT structure and
         // place it in our binding for this column.
         rgBindings[iCol].pObject = 
                        (DBOBJECT *)CoTaskMemAlloc(sizeof(DBOBJECT));
         CHECK_MEMORY(hr, rgBindings[iCol].pObject);

         // Direct the provider to create an ISequentialStream
         // object over the data for this column.
         rgBindings[iCol].pObject->iid = IID_ISequentialStream;

         // We want read access on the ISequentialStream
         // object that the provider will create for us.
         rgBindings[iCol].pObject->dwFlags = STGM_READ;

         // Keep track of the number of storage objects 
         // (ISequentialStream is a storage interface) that we have 
         // requested, so that we can avoid requesting multiple storage 
         // objects from a provider that supports only a single storage 
         // object in our bindings.
         cStorageObjs++;
      }   

      // Ensure that the bound maximum length is no more than the
      // maximum column size in bytes that we've defined.
      rgBindings[iCol].cbMaxLen 
         = min(rgBindings[iCol].cbMaxLen, MAX_COL_SIZE);

      // Update the offset past the end of this column's data so
      // that the next column will begin in the correct place in
      // the buffer.
      dwOffset = rgBindings[iCol].cbMaxLen + rgBindings[iCol].obValue;
      
      // Ensure that the data for the next column will be correctly
      // aligned for all platforms or, if we're done with columns,
      // that if we allocate space for multiple rows that the data
      // for every row is correctly aligned.
      dwOffset = ROUNDUP(dwOffset);
   }

   // Return the row size (the current dwOffset is the size of the row),
   // the count of bindings, and the bindings array to the caller.
   *pcbRowSize    = dwOffset;
   *pcBindings    = cColumns;
   *prgBindings   = rgBindings;

CLEANUP:
   CoTaskMemFree(rgColumnInfo);
   CoTaskMemFree(pStringBuffer);
   if( pIColumnsInfo )
      pIColumnsInfo->Release();
   return hr;
}



/////////////////////////////////////////////////////////////////
// myCreateAccessor
//
// This function takes an IUnknown pointer for a rowset object
// and creates an accessor that describes the layout of the
// buffer we will use when we fetch data. The provider will fill
// this buffer according to the description contained in the
// accessor that we will create here.
//
/////////////////////////////////////////////////////////////////
HRESULT myCreateAccessor
   (
   IUnknown *           pUnkRowset, 
   HACCESSOR *          phAccessor, 
   ULONG *              pcBindings, 
   DBBINDING **         prgBindings, 
   ULONG *              pcbRowSize
   )
{
   HRESULT              hr;
   IAccessor *          pIAccessor = NULL;

   // An accessor is basically a handle to a collection of bindings.
   // To create the accessor, we need to first create an array of
   // bindings for the columns in the rowset.
   CHECK_HR(hr = mySetupBindings(pUnkRowset, pcBindings, prgBindings, 
            pcbRowSize));
   
   // Now that we have an array of bindings, tell the provider to
   // create the accessor for those bindings. We get back a handle
   // to this accessor, which we will use when fetching data.
   XCHECK_HR(hr = pUnkRowset->QueryInterface(
             IID_IAccessor, (void**)&pIAccessor));
   XCHECK_HR(hr = pIAccessor->CreateAccessor(
             DBACCESSOR_ROWDATA,   // dwAccessorFlags
             *pcBindings,          // cBindings
             *prgBindings,         // rgBindings
             0,                    // cbRowSize
             phAccessor,           // phAccessor   
             NULL));               // rgStatus

CLEANUP:
   if( pIAccessor )
      pIAccessor->Release();
   return hr;
}