Handling Long Data Types

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.

BLOBs as Long Data

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.

BLOBs as Storage Objects

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.

Accessing BLOB data with storage objects

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:

  1. Sets the dwType 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.

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

To read data from a BLOB column using a storage object, the consumer:

  1. Creates an accessor that includes a binding for the column.

  2. Calls IRowset::GetData with this accessor. The provider creates a storage object over the BLOB's data and returns a pointer to the requested storage interface (ISequentialStream) on this object.

  3. Calls methods on the storage interface to read the BLOB's data (ISequentialStream::Read).

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:

  1. Create a binding structure to retrieve the ISequentialStream interface from an OLE storage object.

  2. Obtain the IAccessor interface.

  3. Call CreateAccessor to create the accessor.

  4. Call GetNextRows to retrieve the row handles.

  5. Call GetData to retrieve the storage object from a row.

  6. Call ISequentialStream to read the data from the stream.

  7. Repeat steps 4, 5, and 6 to retrieve new storage objects and get the data.
/********************************************************************
* 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:

  1. Calls IRowset::GetData with the accessor that binds the BLOB column. The provider creates a storage object over the BLOB's data and returns a pointer to the requested storage interface (ISequentialStream) on this object.

  2. Calls a method on the storage interface to write data (ISequentialStream::Write).

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.