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 to BLOB data as a storage object, a consumer creates an accessor that includes a binding to the BLOB column. The consumer performs the following actions:

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

  2. Sets 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 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. Sets 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 CreateAccessor or a method that uses the accessor, such as GetData.

Getting BLOB Data with Storage Objects

To get BLOB data using a storage object, a consumer performs the following actions:

  1. Creates an accessor that includes a binding for the column. For more information, see "Binding BLOB Data as a Storage Object" earlier in this section.

  2. Calls 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. Calls methods on the storage interface to get the BLOB's data, such as ISequentialStream::Read, IStream::Read, ILockBytes::ReadAt, or IStorage::OpenStream. If you use these methods to get character data, it is not null terminated.

If the consumer calls GetData, GetLastVisibleData, or 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 to 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
 } ;

 // 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, get the pointer to ISequentialStream.
 HROW * rghRows = NULL;
 ULONG  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 ((ULONG)((BYTE *)pData)[rgBinding[0].obStatus] == DBSTATUS_S_OK) {
  do {
   (*((ISequentialStream **)pData))->Read(rgBuffer, sizeof(rgBuffer), &cb);
   if (cb > 0) {
    ; // Process data
   }
  } while (cb >= sizeof(rgBuffer));
 };

 pIMalloc->Free(rghRows); 
};

Setting BLOB Data with Storage Objects

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

Writing Data Directly to the Provider's Storage Object

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

  1. Creates an accessor that binds the value of the BLOB column. For more information, see "Binding BLOB Data as a Storage Object" earlier in this section.

  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. 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.

    If the storage object is transacted (that is, the STGM_TRANSACTED flag is set in the dwFlags element of the DBOBJECT structure in the binding), the storage object does not publish the changes to the containing rowset until the consumer calls Commit on the storage interface. If the storage object is not transacted (that is, the STGM_DIRECT flag is set), the storage object publishes the changes to the containing rowset when the consumer calls a method on the storage interface to set the changes.

For a similar code example, see "Getting BLOB Data with Storage Objects" earlier in this section. 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 Storage Object

To pass a pointer to its own storage object, the consumer:

  1. Creates an accessor that binds the value of the BLOB column. 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" earlier in this chapter.

  2. Calls IRowsetChange::SetData or IRowsetChange::InsertRow with the accessor that binds the BLOB column. It passes a pointer to a storage interface on the consumer's storage object.

Comments

If the provider already has a storage object open over the BLOB's data, the method returns a status of DBSTATUS_E_CANTCREATE for the column. Otherwise, the provider copies the data from the consumer's storage object to the BLOB column. This is equivalent to the provider destroying the existing data, creating a new (empty) storage object over the BLOB column, repeatedly calling methods to read data from the consumer's storage object and write data to the storage object over the BLOB column, and, if the storage mode is transacted, committing the changes on the storage object over the BLOB column. Character data set in this manner is not null terminated.

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 retrieves this number with the IStream::Stat, IStorage::Stat, or ILockBytes::Stat method on the consumer's storage object (if the consumer's storage object exposes IStream, IStorage, or ILockBytes) or 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 the consumer's storage object exposes IStream, IStorage, or ILockBytes, the provider ignores the bound length value.

When the provider has finished using the consumer's storage object, it calls IUnknown::Release to release the pointer. After SetData or InsertRow returns, the provider must not hold any pointers or reference counts on the consumer's storage object. If the consumer wants to ensure access to its storage object after SetData returns, it must call IUnknown::AddRef on the pointer before calling SetData.

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 GetData, GetData returns a status value of DBSTATUS_S_ISNULL. If the consumer sets the column to a zero-length value and then calls GetData, GetData returns a pointer to a storage object that contains no data.

For example, the following code passes a pointer to a different ISequentialStream object to 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 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 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 *),
pIAccessor->Release();

//Setup pData row buffer
BYTE* pData=(BYTE*)pIMalloc->Alloc(sizeof(IUnknown*)+sizeof(ULONG));

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

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

Getting and Setting BLOB Data with Storage Objects with Parameters

Consumers get and set BLOBs and OLE objects in command parameters in a manner similar to accessing BLOB columns.

To get BLOB data using a storage object as output parameters, a consumer performs the following actions:

  1. Calls ICommandText::SetCommandText, calls ICommandPrepare::Prepare, and calls ICommandWithParameters::GetParameterInfo to get information about parameters.

  2. Creates an accessor that includes a binding for the column. For more information, see "Binding BLOB Data as a Storage Object" earlier in this section.

  3. Calls ICommand::Execute, passing the accessor obtained in Step 2 in the hAccessor element of DBPARAMS. The storage object interface pointer will be returned in the value part of pData. The timing of the availability of the storage object depends on the value of the property DBPROP_OUTPUTPARAMETERAVAILABILITY.

To set BLOB data using a storage object as input parameters, a consumer performs the following actions:

  1. Calls ICommandText::SetCommandText, calls ICommandPrepare::Prepare, and calls ICommandWithParameters::GetParameterInfo to get information about parameters.

  2. Creates an accessor that includes a binding for the column. For more information, see "Binding BLOB Data as a Storage Object" earlier in this section.

  3. Builds the storage object.

  4. Calls ICommand::Execute, passing the accessor obtained in Step 2 and the storage object interface pointer from Step 3 in the hAccessor and value part of pData in DBPARAMS, respectively.