Appendix B

The following is a complete source code listing for the example presented in "Handling Long Data Types." The example selects the Notes column (LONG_VARCHAR) from the Employees table of the Access Northwind sample database. The database is shipped with the OLE DB SDK and is installed as the ODBC data source OLE_DB_NWind_Jet.

To build the file using Microsoft Visual C++ 4.0 or later:

  1. Create a new console application.

  2. Copy the following code to a new .cpp file.

  3. Ensure that your build directory settings reference the OLE DB SDK include directory.

  4. Alter the build link settings to include linking to Oledb.lib.
/********************************************************************
* Using ISequentialStream to retrieve LONG_VARCHAR/ LONG_VARBINARY
* (BLOB) data
********************************************************************/
#define UNICODE
#define _UNICODE
#define DBINITCONSTANTS
#define INITGUID

#include <windows.h>
#include <stdio.h>

#include <oledb.h>  // OLE DB include files
#include <oledberr.h>
#include <msdasql.h> 

#define  BLOCK_SIZE     250

// Prototypes
HRESULT myInitDSO(IDBInitialize** ppIDBI);
HRESULT mySetInitProps(IDBInitialize*  pIDBInitialize);
HRESULT myCommand(IDBInitialize* pIDBI, IRowset** ppIRowset);
void    myGetBLOBData(IRowset* pIRowset);
void    DumpError(LPSTR lpStr);

IMalloc*        g_pIMalloc = NULL;

/********************************************************************
* OLE DB application main()
********************************************************************/
int main()
    {
    IDBInitialize*  pIDBInitialize = NULL;
    IRowset*        pIRowset = NULL;

    //Init OLE and set up the DLLs.
    CoInitialize(NULL);

    // Get the task memory allocator.
    if (FAILED(CoGetMalloc(MEMCTX_TASK, &g_pIMalloc)))
        goto EXIT;

    // Connect to the data source.
    if (FAILED(myInitDSO(&pIDBInitialize)))
        goto EXIT;

    // Get a session, set and execute a command.
    if (FAILED(myCommand(pIDBInitialize, &pIRowset)))
        goto EXIT;

    myGetBLOBData(pIRowset);

EXIT:
    if (pIRowset != NULL)
        pIRowset->Release();

    if (pIDBInitialize != NULL)
        {
        if (FAILED(pIDBInitialize->Uninitialize()))
            {
            // Uninitialize is not required, but it will fail if an
            // interface has not been released. We can use it for 
            // debugging.
            DumpError("Someone forgot to release something!");
            }
        pIDBInitialize->Release();
        }

    if (g_pIMalloc != NULL)
        g_pIMalloc->Release();

    CoUninitialize();
    
    return (0);
    }


/********************************************************************
* Initialize the data source.
********************************************************************/
HRESULT myInitDSO
    (
    IDBInitialize** ppIDBInitialize    // [out]
    )
    {
    // Create an instance of the MSDASQL (ODBC) provider.
    CoCreateInstance(CLSID_MSDASQL, NULL, CLSCTX_INPROC_SERVER,
        IID_IDBInitialize, (void**)ppIDBInitialize);

    if (*ppIDBInitialize == NULL)
        {
        return (E_FAIL);
        }

    if (FAILED(mySetInitProps(*ppIDBInitialize)))
        {
        return (E_FAIL);
        }

    if (FAILED((*ppIDBInitialize)->Initialize()))
        {
        DumpError("IDBInitialze->Initialize failed.");
        return (E_FAIL);
        }

    return (NOERROR);
    }

/********************************************************************
* Set initialization properties on a data source.
********************************************************************/
HRESULT mySetInitProps
    (
    IDBInitialize*  pIDBInitialize    // [in]
    )
    {
    const ULONG     nProps = 4;
    IDBProperties*  pIDBProperties;
    DBPROP          InitProperties[nProps];
    DBPROPSET       rgInitPropSet;
    HRESULT         hr;

    // Initialize common property options.
    for (ULONG i = 0; i < nProps; i++ )
        {
        VariantInit(&InitProperties[i].vValue);
        InitProperties[i].dwOptions = DBPROPOPTIONS_REQUIRED;
        InitProperties[i].colid = DB_NULLID;
        }
    
    // Level of prompting that will be done to complete the
    // connection process
    InitProperties[0].dwPropertyID = DBPROP_INIT_PROMPT;
    InitProperties[0].vValue.vt = VT_I2;
    InitProperties[0].vValue.iVal = DBPROMPT_NOPROMPT;     

    // Data source name--see the sample source included with the OLE
    // DB SDK.
    InitProperties[1].dwPropertyID = DBPROP_INIT_DATASOURCE;    
    InitProperties[1].vValue.vt = VT_BSTR;
    InitProperties[1].vValue.bstrVal = 
        SysAllocString(OLESTR("OLE_DB_NWind_Jet"));

    // User ID
    InitProperties[2].dwPropertyID = DBPROP_AUTH_USERID;
    InitProperties[2].vValue.vt = VT_BSTR;
    InitProperties[2].vValue.bstrVal = SysAllocString(OLESTR(""));

    // Password
    InitProperties[3].dwPropertyID = DBPROP_AUTH_PASSWORD;
    InitProperties[3].vValue.vt = VT_BSTR;
    InitProperties[3].vValue.bstrVal = SysAllocString(OLESTR(""));

    rgInitPropSet.guidPropertySet = DBPROPSET_DBINIT;
    rgInitPropSet.cProperties = nProps;
    rgInitPropSet.rgProperties = InitProperties;

    // Set initialization properties.
    pIDBInitialize->QueryInterface(IID_IDBProperties, (void**) 
       &pIDBProperties);
    hr = pIDBProperties->SetProperties(1, &rgInitPropSet);

    SysFreeString(InitProperties[1].vValue.bstrVal);
    SysFreeString(InitProperties[2].vValue.bstrVal);
    SysFreeString(InitProperties[3].vValue.bstrVal);

    pIDBProperties->Release();

    if (FAILED(hr))
        {
        DumpError("Set properties failed.");
        }

    return (hr);
    }

/********************************************************************
* Execute a command selecting Notes from Employees.
********************************************************************/
HRESULT myCommand
    (
    IDBInitialize*  pIDBInitialize, // [in]
    IRowset**       ppIRowset       // [out]
    ) 
    {
    IDBCreateSession*   pIDBCreateSession;
    IDBCreateCommand*   pIDBCreateCommand;
    IRowset*            pIRowset;
    ICommandText*       pICommandText;
    LPCTSTR             wSQLString = OLESTR("SELECT Notes ")
                                     OLESTR("FROM Employees");
    LONG                cRowsAffected;
    HRESULT             hr;

    // Set for failure
    *ppIRowset = NULL;

    // Get the DB session object.
    hr = pIDBInitialize->QueryInterface(IID_IDBCreateSession,
        (void**) &pIDBCreateSession);
    if (FAILED(hr))
        {
        DumpError("Session initialization failed.");
        return (hr);
        }

    // Create the session.
    hr = pIDBCreateSession->CreateSession(NULL, IID_IDBCreateCommand,
        (IUnknown**) &pIDBCreateCommand);
    pIDBCreateSession->Release();
    if (FAILED(hr))
        {
        DumpError("Create session failed.");
        return (hr);
        }

    // Create the command object.
    hr = pIDBCreateCommand->CreateCommand(NULL, IID_ICommandText,
        (IUnknown**) &pICommandText);
    if (FAILED(hr))
        {
        DumpError("Create command failed.");
        return (hr);
        }
    pIDBCreateCommand->Release();

    // The command requires the actual text as well as an indicator
    // of its language and dialect.
    pICommandText->SetCommandText(DBGUID_DBSQL, wSQLString);

    // Execute the command.
    hr = pICommandText->Execute(NULL, IID_IRowset, NULL,
         &cRowsAffected, (IUnknown**) &pIRowset);
    if (FAILED(hr))
        {
        DumpError("Command execution failed.");
        }

    pICommandText->Release();

    *ppIRowset = pIRowset;
    return (hr);
    }

/********************************************************************
* 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 struct 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;
    }
    
/********************************************************************
* Dump an error to the console.
********************************************************************/
void DumpError(LPSTR lpStr)
    {
    printf(lpStr);
    printf("\n");
    }