Getting and Setting BLOB Data with Storage Objects

The following sections discuss how to bind, get, and set BLOB data using storage objects.

Binding BLOB Data as a Storage Object

To bind BLOB data as a storage object, a consumer creates an accessor that includes a binding to the BLOB column. The consumer will perform the following actions:

  1. Set the wType element of the DBBINDING structure for the BLOB column to DBTYPE_IUNKNOWN.

  2. Set the iid element of the DBOBJECT structure in the binding to IID_ISEQUENTIALSTREAM, IID_ISTREAM, IID_ISTORAGE, or IID_ILOCKBYTES.

    If the consumer specifies an interface that is not supported by the provider, the provider returns E_NOINTERFACE when it validates the accessor. This can occur in IAccessor::CreateAccessor or in a method that uses the accessor, such as IRowset::GetData. To determine which interfaces are supported by the provider, the consumer calls IDBProperties::GetProperties with the DBPROP_STRUCTUREDSTORAGE property.

  3. Set the dwFlags element of the DBOBJECT structure in the binding.

    If the consumer specifies any invalid or unsupported flags, the provider returns DB_E_BADSTORAGEFLAG when it validates the accessor. This can occur in IAccessor::CreateAccessor or in a method that uses the accessor, such as IRowset::GetData.

Getting BLOB Data with Storage Objects

To get BLOB data by using a storage object, a consumer will perform the following actions:

  1. Create an accessor that includes a binding for the column. For more information, see the section on Binding BLOB Data as a Storage Object.

  2. Call IRowset::GetData, IRowsetRefresh::GetLastVisibleData, or IRowsetUpdate::GetOriginalData with this accessor.

    The provider creates a storage object over the BLOB's data and returns a pointer to the requested storage interface (ISequentialStream, IStream, IStorage, or ILockBytes) on this object. If the provider supports only a single open storage object at a time and another storage object is open, the method returns a status of DBSTATUS_E_CANTCREATE for the column.

  3. Call methods on the storage interface to get the BLOB's data, such as ISequentialStream::Read, IStream::Read, ILockBytes::ReadAt, or IStorage::OpenStream. If these methods are used to get character data, the data is not null-terminated.

If the consumer calls IRowset::GetData, IRowsetRefresh::GetLastVisibleData, or IRowsetUpdate::GetOriginalData multiple times for the BLOB column, the provider returns distinct pointers to storage interfaces on each call. This is similar to opening a file multiple times and returning a different file handle each time. It is the consumer's responsibility to call Release on each of these storage interfaces separately.

For example, the following code binds a BLOB column and uses ISequentialStream::Read to get the data:

#include <oledb.h>

IRowset *     pIRowset;
IAccessor *   pIAccessor;
IMalloc *     pIMalloc;

int main() {
   // Assume the consumer has a pointer (pIRowset) that
   // points to the rowset. Create an accessor for the
   // BLOB column. Assume it is column 1.

   HACCESSOR      hAccessor;
   DBBINDSTATUS   rgStatus[1];
   DBOBJECT       ObjectStruct;
   DBBINDING      rgBinding[1] = {
      1,                            // Column 1
      0,                            // Offset to data
      0,                            // Ignore length field
      sizeof(IUnknown *),           // Offset to status field
      NULL,                         // No type info
      &ObjectStruct,                // Object structure
      NULL,                         // Ignore binding extensions
      DBPART_VALUE|DBPART_STATUS,   // Bind value and status
      DBMEMOWNER_CLIENTOWNED,       // Consumer owned memory
      DBPARAMIO_NOTPARAM,           // Not a parameter
      0,                            // Ignore size of data
      0,                            // Reserved
      DBTYPE_IUNKNOWN,              // Type DBTYPE_IUNKNOWN
      0,                            // Precision not applicable
      0                             // Scale not applicable
   } ;
   ISequentialStream *   pISequentialStream;

   // Set the elements in the object structure so that the
   // provider creates a readable ISequentialStream object over
   // the column. The consumer will read data from this object.
   ObjectStruct.dwFlags = STGM_READ;
   ObjectStruct.iid = IID_ISequentialStream;

   pIRowset->QueryInterface(IID_IAccessor, (void**) &pIAccessor);
   pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 1, rgBinding,
               sizeof(IUnknown *) + sizeof(ULONG), &hAccessor, rgStatus);
   pIAccessor->Release();

   // Allocate memory for the returned pointer and the
   // status field. The first sizeof(IUnknown*) bytes are
   // for the pointer to the object, and the next sizeof(ULONG)
   // bytes are for the status.

   void * pData = pIMalloc->Alloc(sizeof(IUnknown *) + sizeof(ULONG));

   // Get the next row, and get the pointer to ISequentialStream.
   HROW *   rghRows = NULL;
   DBROWCOUNT    cRows;

   pIRowset->GetNextRows(NULL, 0, 1, &cRows, &rghRows);
   pIRowset->GetData(rghRows[0], hAccessor, pData);

   // Read and process 5000 bytes at a time.
   BYTE    rgBuffer[5000];
   ULONG   cb;

   if ((ULONG)((BYTE*)pData)[rgBinding[0].obStatus] == DBSTATUS_S_ISNULL) {
   // Process NULL data.
   } else if ((DBSTATUS)((BYTE *)pData)[rgBinding[0].obStatus] ==
               DBSTATUS_S_OK) {
      //Obtain pointer to ISequentialStream.
      pISequentialStream = *(ISequentialStream **)pData;
      do {
         pISequentialStream->Read(rgBuffer,    // Buffer to write the data
                           sizeof(rgBuffer),   // Number of bytes to get
                           &cb);   // Number of bytes written to buffer
         if (cb > 0)               // Check to see if we got data
         {
            // Process data here.
         }
      } while (cb >= sizeof(rgBuffer));
   };

   pIMalloc->Free(rghRows);

   return 0;
};

Setting BLOB Data with Storage Objects

There are two ways a consumer can set BLOB data by using a storage object:

  • Writing data directly to the provider's storage object The provider creates a storage object over the BLOB column and returns a pointer to this storage object to the consumer. The consumer writes data directly to this storage object.

  • By passing a pointer to a consumer's storage object The consumer creates a storage object containing the data and passes a pointer to this storage object to the provider. The provider then reads data from the consumer storage object and writes it to the BLOB column.

It is permissible to open an ISequentialStream storage object with both read and write access (STGM_READWRITE). With such an object, there is a single "current position" that is incremented by both the ISequentialStream::Read and the ISequentialStream::Write methods. In this case, a write would overlay the contents of the stream.

Writing Data Directly to the Provider's Storage Object

To write data directly to the provider's storage object, the consumer performs the following actions:

  1. Creates an accessor that binds the value of the BLOB column.

    The consumer must specify that it needs to have write access to the storage object by at least requesting STGM_WRITE. For more information, see the section on Binding BLOB Data as a Storage Object.

  2. Calls IRowset::GetData with the accessor that binds the value of the BLOB column.

    The provider creates a storage object over the BLOB's data and returns a pointer to the requested storage interface (ISequentialStream, IStream, IStorage, or ILockBytes) on this object. Because IRowsetChange::SetData is not called, this technique does not require IRowsetChange, so there is no requirement that DBPROP_IRowsetChange is VARIANT_TRUE. If the provider supports deferred validation of accessors and if the provider cannot deliver an updatable storage object, writing directly to the storage object would fail with a bad binding status (such as DBBINDSTATUS_E_BADSTORAGEFLAG) on either IAccessor::CreateAccessor or IRowset::GetData.

    If the provider supports only a single open storage object at a time and another storage object is open, the method returns a status of DBSTATUS_E_CANTCREATE for the column.

  3. Calls a method on the storage interface to set data, such as ISequentialStream::Write, IStream::Write, or ILockBytes::WriteAt.

    Character data set with these methods is not null-terminated.

For a similar code example, see this section Getting BLOB Data with Storage Objects. The code for setting BLOB data is essentially the same, except that the consumer must set the dwFlags element of the ObjectStruct structure so that it can write to the storage object. Also, the consumer calls ISequentialStream::Write instead of ISequentialStream::Read.

Passing a Pointer to a Consumer's Storage Object

To pass a pointer to its own storage object, the consumer must perform the following actions:

  1. Create an accessor that binds the value of the BLOB column by at least requesting STGM_WRITE. For more information, see "Binding BLOB Data as a Storage Object," earlier in this section.

    If the consumer's storage object exposes ISequentialStream and the provider needs to know the number of bytes of BLOB data that will be sent before any of the data is sent, this accessor must also bind the length of the BLOB column. For more information, see Limitations of Storage Objects.

  2. Call IRowsetChange::SetData, IRowsetChange::InsertRow, IRowChange::SetColumns, or IRowSchemaChange::AddColumns with the accessor that binds the BLOB column, passing a pointer to a storage interface on the consumer's storage object.

If the provider already has a storage object open over the BLOB's data, the method might return a status of DBSTATUS_E_CANTCREATE for the column. The provider reads the data from the consumer's stream in order to update the column. The provider may read the data at the time IRowsetChange::SetData or IRowsetChange::InsertRow is called, or (if the rowset is in deferred update mode) may defer reading the data until IRowsetUpdate::Update or IRowsetUpdate::Undo is called for the row, or until IRowsetChange::SetData is called again for the same column in the same row. Thus, the consumer's storage object must be valid upon IRowsetChange::SetData or IRowsetChange::InsertRow and must remain valid until the provider releases it. When the provider has finished using the consumer's storage object, it calls IUnknown::Release to release the pointer. For more information about releasing the reference to a storage object, see Releasing a Reference to a Storage Object.

If the provider needs to know the number of bytes of BLOB data that will be sent before any of the data is sent, it uses the Stat method on the consumer's storage object if the consumer's storage object exposes IStream, IStorage, or ILockBytes, or retrieves this number from the length part of the binding if the consumer's storage object exposes ISequentialStream. If the consumer binds the length and the provider does not need to know this information, or if the consumer's storage object exposes IStream, IStorage, or ILockBytes, the provider ignores the bound length value.

To set the BLOB column to a zero-length value, the consumer sets the status flag to DBSTATUS_S_OK. It then either passes a null pointer (instead of a pointer to its own storage object) or passes a pointer to a storage object that contains no data. The provider sets the column to a zero-length value. This is different from setting the status value for the column to DBSTATUS_S_ISNULL. If the consumer sets the column value to NULL and then calls IRowset::GetData, the GetData method returns a status value of DBSTATUS_S_ISNULL. If the consumer sets the column to a zero-length value and then calls IRowset::GetData, the GetData method returns a pointer to a storage object that contains no data.

Note

Not all providers support setting a BLOB column to a zero-length value.

Releasing a Reference to a Storage Object

The provider must release any references on the storage object by the time the row is successfully updated or the change is undone or overwritten; if the rowset is in immediate update mode, the provider must not hold any pointers or reference counts on the consumer's storage object after the call to IRowsetChange::SetData or IRowsetChange::InsertRow in which the storage object is passed. If the rowset is in deferred update mode, the provider must not hold any pointers or reference counts on the consumer's storage object after IRowsetUpdate::Update or IRowsetUpdate::Undo has been called successfully for the row or after IRowsetChange::SetData has been successfully called to replace the contents of the column. If the consumer wants to ensure access to its storage object after SetData returns, it must call IUnknown::AddRef on the pointer before calling IRowsetChange::SetData.

In general, the provider is responsible for releasing storage objects, on either rowsets (IRowsetChange::SetData, IRowsetChange::InsertRow) or rows (IRowChange::SetColumns, IRowSchemaChange::AddColumns), both on success and on any errors. The provider is responsible for releasing storage objects of column types DBTYPE_IUNKNOWN and DBTYPE_IUNKNOWN | DBTYPE_BYREF. It is the consumer's responsibility to release storage objects of type DBTYPE_IDISPATCH or to release any storage object embedded in the following types: DBTYPE_VARIANT, DBTYPE_PROPVARIANT, DBTYPE_VECTOR, and DBTYPE_ARRAY. For more information, see the applicable interface and method descriptions in OLE DB Reference.

Code Example: Passing a Pointer to a Storage Object

The following code passes a pointer to a different ISequentialStream object to IRowsetChange::SetData to overwrite the existing value:

#include<oledb.h>

IRowsetChange *       pIRowsetChange;
IAccessor *           pIAccessor;
ISequentialStream *   pISeqStr;
HROW                  hrow;

int main() {

   // Assume the consumer has a pointer (pIRowsetChange)
   // that points to the rowset's IRowsetChange interface
   // and a pointer (pISeqStr) that points to an
   // ISequentialStream object not in the rowset. Create an
   // accessor for the BLOB column. Assume it is column 1.

   HACCESSOR      hAccessor;
   DBBINDSTATUS   rgStatus[1];
   DBOBJECT       ObjectStruct;
   DBBINDING      rgBinding[1] = {
      1,                            // Column 1
      0,                            // Offset to data
      sizeof(IUnknown*),            // obLength length field
      0,                            // Ignore status field
      NULL,                         // No type info
      &ObjectStruct,                 // Object structure
      NULL,                         // Ignore binding extension
      DBPART_VALUE|DBPART_LENGTH,   // Bind value and length
      DBMEMOWNER_CLIENTOWNED,       // Consumer owned memory
      DBPARAMIO_NOTPARAM,           // Not a parameter binding
      0,                            // Ignore maxlength
      0,                            // Reserved
      DBTYPE_IUNKNOWN,              // Type DBTYPE_IUNKNOWN
      0,                            // Precision not applicable
      0,                            // Scale not applicable
   };

   // Set the elements in the object structure so that the
   // provider creates a writable ISequentialStream object
   // over the column. The provider will read data from the
   // ISequentialStream object passed to SetData and will
   // write it to this object.
   ObjectStruct.dwFlags = STGM_READ ;
   ObjectStruct.iid = IID_ISequentialStream;

   pIRowsetChange->QueryInterface(IID_IAccessor, (void**) &pIAccessor);
   pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 1, rgBinding,
               sizeof(IUnknown *) + sizeof(ULONG), &hAccessor, rgStatus);
   pIAccessor->Release();

   // Assume you already have a row handle (HROW). Call
   // SetData and pass it the address of the pointer to the
   // object ISequentialStream not in the rowset. The rowset
   // will copy the data from the ISequentialStream object to
   // the rowset. Set up pData row buffer.
   BYTE* pData=(BYTE*)CoTaskMemAlloc(sizeof(IUnknown*)+sizeof(DBLENGTH));

   // Value - pass ISequentialStream pointer to the provider.
   *(ISequentialStream**)(pData+rgBinding[0].obValue)=pISeqStr;
   // LENGTH - Some providers need to know the length of the
   //  stream ahead of time...
   *(DBLENGTH*)(pData+rgBinding[0].obLength)=5000;

   // SetData - The provider will then do an ISequentialStream::Read
   //  on the pISeqStr pointer passed in...
   pIRowsetChange->SetData(hrow, hAccessor, pData);
   CoTaskMemFree(pData);
   return 0;
} 

Getting and Setting BLOB Data with Storage Objects with Parameters

Consumers get and set BLOBs and COM objects in command parameters in a manner similar to accessing BLOB columns as described in the section on Getting and Setting BLOB Data with Storage Objects.

If the consumer binds a command parameter to a storage object, at command execution the consumer passes the storage object pointer, for example ISequentialStream, as the parameter value. At execution, the provider calls a method in the storage object?in this example, **ISequentialStream::Read?**to access the contents of the storage object. The provider does not reset position on the storage object (if the storage object supports setting position).

Providers should be able to handle the storage object, which can be large, in a way that does not adversely impact memory usage.