To a rowset, a BLOB or OLE storage object is a large sequence of uninterpreted bytes that a consumer stores in a column. It is the consumer's responsibility to interpret this sequence of bytes. BLOBs and OLE storage objects are categorized as:
A consumer can determine what types of BLOBs, OLE storage objects, and other types of OLE objects that a provider supports by calling IDBProperties with the DBPROP_OLEOBJECTS property.
If the entire BLOB can be held in memory, it is treated as long in-memory data. To read the BLOB data, the consumer binds the column with a type identifier DBTYPE_BYTES, DBTYPE_STR, or DBTYPE_WSTR, and calls IRowset::GetData with an accessor containing this binding. The provider then returns the entire contents of the BLOB to the consumer.
If a BLOB is too large to hold in memory, the consumer manipulates it through the ISequentialStream storage interface. The rows in the rowset are containers of the storage objects.
On retrieval, BLOB columns are deferred by default. Their data is not retrieved and storage objects are not created until GetData is called. In particular, methods that retrieve rows, such as GetNextRows, do not return data for BLOB columns in the data cache.
A storage object created by the provider remains valid until one of the following occurs:
It is the consumer's responsibility to release the storage object, even if the containing row has been released.
To bind to a BLOB data as a storage object, a consumer creates an accessor that includes a binding to the BLOB column. The consumer:
To read data from a BLOB column using a storage object, the consumer:
If the consumer calls GetData, GetVisibleData, 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 a number of times and returning a different file handle each time. It is the consumer's responsibility to call Release on each of these storage interfaces.
For example, the following code example binds to a BLOB column and uses ISequentialStream::Read to read the data. For the complete source code listing, see Appendix B. The general flow of control is:
/********************************************************************
* Retrieve data from an ODBC LONG_VARCHAR column (Notes in
* Employees).
********************************************************************/
void myGetBLOBData
(
IRowset* pIRowset // [in]
)
{
DBOBJECT ObjectStruct; // For binding, retrieve
// an object pointer.
DBBINDING rgBinding[1]; // Bind a single column.
IAccessor* pIAccessor = NULL; // Accessor creation
HACCESSOR hAccessor = NULL;
ULONG ulErrorBinding;
void* pData; // Bound consumer buffer
HROW rghRows[1];
HROW* pRows = &rghRows[0];
ULONG cRows;
char szNotes[BLOCK_SIZE + 1]; // Text data from
// "Notes"
ULONG cbRead; // Count of bytes read
// Set up the object structure for accessor creation. Ask the
// provider to return an ISequentialStream interface for reading.
ObjectStruct.dwFlags = STGM_READ;
ObjectStruct.iid = IID_ISequentialStream;
// Set up the binding structure for the accessor.
rgBinding[0].iOrdinal = 1; // Only one column
rgBinding[0].obValue = 0; // Offset to data
rgBinding[0].obLength = 0; // Ignore length
rgBinding[0].obStatus = sizeof(IUnknown*); // Offset to status
rgBinding[0].pTypeInfo = NULL; // Reserved
rgBinding[0].pObject = &ObjectStruct; // Our interface
// request
rgBinding[0].pBindExt = NULL; // Reserved
rgBinding[0].dwPart = DBPART_VALUE | // Get both VALUE...
DBPART_STATUS; // ...and STATUS
// parts.
rgBinding[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
rgBinding[0].eParamIO = DBPARAMIO_NOTPARAM;
rgBinding[0].cbMaxLen = 0; // Not applicable
rgBinding[0].dwFlags = 0; // Reserved
rgBinding[0].wType = DBTYPE_IUNKNOWN; // Type
// DBTYPE_IUNKNOWN
rgBinding[0].bPrecision = 0; // Not applicable
rgBinding[0].bScale = 0; // Not applicable
// Get the accessor interface and create the accessor.
pIRowset->QueryInterface(IID_IAccessor, (void**) &pIAccessor);
if (FAILED(pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 1,
rgBinding, sizeof(IUnknown*) + sizeof(ULONG), &hAccessor,
&ulErrorBinding)))
{
DumpError("CreateAccessor failed.");
return;
}
// Allocate memory for the returned pointer and the status
// field. The first sizeof(IUnknown*) bytes are for the pointer
// to the object; the next sizeof(ULONG) bytes are for the
// status.
pData = new BYTE[sizeof(IUnknown*) + sizeof(ULONG)];
while (TRUE)
{
// Get the next row.
if (FAILED(pIRowset->GetNextRows(NULL, 0, 1, &cRows,
&pRows)))
{
DumpError("GetNextRows failed.\n");
break;
}
if (cRows == 0)
{
break;
}
// Get the row data, the pointer to an ISequentialStream*.
if (FAILED(pIRowset->GetData(*pRows, hAccessor, pData)))
{
DumpError("GetData failed.\n");
break;
}
// Read and process BLOCK_SIZE bytes at a time.
if ((ULONG)((BYTE*)pData)[rgBinding[0].obStatus] ==
DBSTATUS_S_ISNULL)
{
// Process NULL data.
printf("<null>");
}
else if ((ULONG)((BYTE*)pData)[rgBinding[0].obStatus] ==
DBSTATUS_S_OK)
{
do
{
(*((ISequentialStream**) pData))->Read(szNotes,
BLOCK_SIZE, &cbRead);
if (cbRead > 0)
{
// Process data.
szNotes[cbRead] = (char) NULL;
printf(szNotes);
}
}
while (cbRead >= BLOCK_SIZE);
(*((ISequentialStream**) pData))->Release();
printf("\n\n");
}
pIRowset->ReleaseRows(cRows, pRows, NULL, NULL, NULL);
}
// Clean up.
pIAccessor->ReleaseAccessor(hAccessor, NULL);
pIAccessor->Release();
delete [] pData;
}
To write data to a BLOB column using a storage object, the consumer first creates an accessor that includes a binding for the column and then:
OLE's structured storage model supports both transacted and direct modes. In transacted mode, all changes are buffered, and the buffered changes are persisted or discarded only when an explicit commit or abort request is done. In direct mode, every change is followed by an automatic commit. 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 write the changes.
Alternately, the consumer calls IRowsetChange::SetData or IRowsetChange::InsertRow with the accessor that binds the BLOB column, passing a pointer to a storage interface on a separate storage object.