Partager via


Appendix F: Sample OLE DB Consumer Application

This sample demonstrates the rowset programming and object model for an OLE DB consumer. It creates data source, session, and rowset objects; allows the user to display and navigate the rows in the rowset; and handles errors, if any. Command line switches determine whether an enumerator, class ID, user prompt, or connection string is used to create the data source object, whether or not a command is used to create the rowset, and so on.

Sample OLE DB Consumer Application Source

Source files for the Sample OLE DB Consumer Application are located in the PRSample directory where the OLE DB samples are installed, and consist of the following files:

Source and Project Files

command.cpp

prsample.h

datasource.cpp

prsample.mak

enum.cpp

prsample.rc

error.cpp

resource.h

main.cpp

rowset.cpp

prsample.dsp

session.cpp

Example

// prsample.h
//---------------------------------------------------------------------------
// Microsoft? OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft? Corporation.
//
// @doc
//
// @module PRSAMPLE.H
//
//---------------------------------------------------------------------------

// Includes
// Summary of Routines

#ifndef __PRSAMPLE_H__
#define __PRSAMPLE_H__

#include "oledb.h"         // OLE DB Header
#include "oledberr.h"      // OLE DB Errors

#include "msdasc.h"        // OLE DB Service Component header
#include "msdaguid.h"      // OLE DB Root Enumerator
#include "msdasql.h"       // MSDASQL - Default provider

#include <stdio.h>         // input and output functions
#include <conio.h>         // getch, putch
#include <locale.h>        // setlocale

// Globals
extern DWORD g_dwFlags;


// Defines
#define __LONGSTRING(string) L##string
#define LONGSTRING(string) __LONGSTRING(string)

// Goes to CLEANUP on Failure_
#define CHECK_HR(hr)      \
   if(FAILED(hr))         \
      goto CLEANUP

// Goes to CLEANUP on Failure, and displays any ErrorInfo
#define XCHECK_HR(hr) { \
   if( g_dwFlags & DISPLAY_METHODCALLS )                            \
      fwprintf(stderr, LONGSTRING(#hr) L"\n");                      \
   if(FAILED(myHandleResult(hr, LONGSTRING(__FILE__), __LINE__)))   \
      goto CLEANUP;                                                 \
}
#define CHECK_MEMORY(hr, pv)   \
{                              \
   if(!pv)                     \
   {                           \
      hr = E_OUTOFMEMORY;      \
      CHECK_HR(hr);            \
   }                           \
}


#define MAX_COL_SIZE         5000
#define MAX_NAME_LEN         256

#define MAX_ROWS              10
#define MAX_DISPLAY_SIZE      20
#define MIN_DISPLAY_SIZE      3

// ROUNDUP on all platforms pointers must be aligned properly
#define ROUNDUP_AMOUNT            8
#define ROUNDUP_(size,amount) (((ULONG)(size)+((amount)-1))&~((amount)-1))
#define ROUNDUP(size)             ROUNDUP_(size, ROUNDUP_AMOUNT)

enum {
   // Connecting
   USE_PROMPTDATASOURCE      = 0x0001,
   USE_ENUMERATOR            = 0x0002,

   // Rowset
   USE_COMMAND               = 0x0010,

   // Storage objects
   USE_ISEQSTREAM            = 0x0100,

   // Display options
   DISPLAY_METHODCALLS       = 0x1000,
   DISPLAY_INSTRUCTIONS      = 0x2000,
};


// Function prototypes
// Main
BOOL   myParseCommandLine();
void   myDisplayInstructions();
BOOL   myGetInputFromUser(LPWSTR pwszInput, LPCWSTR pwszFmt, ...);
CHAR   myGetChar();

// Enumerator
HRESULT myCreateEnumerator(REFCLSID clsidEnumerator, CLSID* pCLSID);

// Data source
HRESULT myCreateDataSource(IUnknown** ppUnkDataSource);
HRESULT myDoInitialization(IUnknown* pIUnknown);
HRESULT myGetProperty(IUnknown* pIUnknown, REFIID riid, DBPROPID 
                      dwPropertyID, REFGUID guidPropertySet, BOOL* pbValue);

void myAddProperty(DBPROP* pProp, DBPROPID dwPropertyID, VARTYPE vtType = 
                   VT_BOOL, LONG lValue = VARIANT_TRUE, DBPROPOPTIONS 
                   dwOptions =    DBPROPOPTIONS_OPTIONAL);

// Session
HRESULT myCreateSession(IUnknown* pUnkDataSource, IUnknown** ppUnkSession);
HRESULT myCreateSchemaRowset(GUID guidSchema, IUnknown* pUnkSession,
                             ULONG cchBuffer, LPWSTR pwszBuffer);

// Command
HRESULT myCreateCommand(IUnknown* pUnkSession, IUnknown** ppUnkCommand);
HRESULT myExecuteCommand(IUnknown* pUnkCommand, WCHAR* pwszCommandText, 
                         ULONG cPropSets, DBPROPSET* rgPropSets, 
                         IUnknown** ppUnkRowset);

// Rowset
HRESULT myCreateRowset(IUnknown* pUnkSession, IUnknown** ppUnkRowset);
HRESULT mySetupBindings(IUnknown* pUnkRowset, DBCOUNTITEM* pcBindings, 
                        DBBINDING** prgBindings, DBLENGTH* pcbRowSize);
HRESULT myCreateAccessor(IUnknown* pUnkRowset, HACCESSOR* phAccessor, 
                         DBCOUNTITEM* pcBindings, DBBINDING** prgBindings, 
                         DBLENGTH* pcbRowSize);

HRESULT myDisplayRowset(IUnknown* pUnkRowset, LPCWSTR pwszColToReturn, 
                        ULONG cchBuffer, LPWSTR pwszBuffer);
HRESULT myDisplayColumnNames(IUnknown* pUnkRowset, ULONG* rgDispSize);
HRESULT myDisplayRow(DBCOUNTITEM iRow, DBCOUNTITEM cBindings, 
                        DBBINDING* rgBindings, void* pData, ULONG * rgDispSize);

HRESULT myInteractWithRowset(IRowset* pIRowset, DBROWCOUNT* pcRows, 
                   DBCOUNTITEM  cRowsObtained, BOOL fCanFetchBackwards, 
                   void* pData, DBLENGTH cbRowSize, DBBINDING* pBinding,
                   ULONG cchBuffer, LPWSTR pwszBuffer);
HRESULT myFindColumn(IUnknown * pUnkRowset, LPCWSTR pwszName, DBORDINAL* plIndex);
HRESULT myUpdateDisplaySize(DBCOUNTITEM cBindings, DBBINDING* rgBindings,
                           void* pData, ULONG* rgDispSize);
void myFreeBindings(DBCOUNTITEM cBindings, DBBINDING* rgBindings);
void myAddRowsetProperties(DBPROPSET* pPropSet, ULONG cProperties, DBPROP* rgProperties);

// Error
HRESULT myHandleResult(HRESULT hrReturned, LPCWSTR pwszFile, ULONG ulLine);
HRESULT myDisplayErrorRecord(HRESULT hrReturned, ULONG iRecord, IErrorRecords* pIErrorRecords, 
                             LPCWSTR pwszFile, ULONG ulLine);
HRESULT myDisplayErrorInfo(HRESULT hrReturned, IErrorInfo* pIErrorInfo, 
                           LPCWSTR pwszFile, ULONG ulLine);
HRESULT myGetSqlErrorInfo(ULONG iRecord, IErrorRecords* pIErrorRecords, 
                          BSTR* pBstr, LONG* plNativeError);

#endif   // __PRSAMPLE_H__

// main.cpp
// compile with: ole32.lib
//---------------------------------------------------------------------------
// Microsoft OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft Corporation.
//
// @doc
//
// @module MAIN.CPP
//
//---------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#define DBINITCONSTANTS   // Store all OLE DB consts inside
// this .obj file.
#include "prsample.h"     // Programmer's Reference Sample includes


/////////////////////////////////////////////////////////////////
// Globals
//
/////////////////////////////////////////////////////////////////
DWORD g_dwFlags = USE_PROMPTDATASOURCE | DISPLAY_METHODCALLS;


/////////////////////////////////////////////////////////////////

// main
// Summary of Routines
//
// This is a simple OLE DB application that will display a
// rowset and will allow basic navigation of that rowset by the
// user.
//
// In the sample, functions that begin with 'my' are implemented
// in the sample code; all other functions are either OLE DB
// methods or standard system methods. In addition, two
// macros are used repeatedly throughout the sample:
//  - CHECK_HR(hr) - this macro goes to the CLEANUP label if
//     FAILED(hr), where hr is usually a method call.
//  - XCHECK_HR(hr) - this macro prints the string
//     representation of hr to stderr and if FAILED(hr), attempts
//     to obtain and display any extended error information
//     posted by the last method call and then jumps to the CLEANUP
//     label.
//
// This is the entry point for the sample. This function will:
//  - parse command line arguments passed to the sample.
//  - display appropriate instructions based on these arguments.
//  - create an OLE DB data source object for a user-chosen
//     provider.
//  - create an OLE DB session object from the provider's
//     data source object.
//  - create an OLE DB rowset object, over a table specified by
//     the user, from the provider's session object.
//  - display the rowset data and will allow the user to
//     navigate over the rowset.
//
/////////////////////////////////////////////////////////////////
int main() {
   HRESULT      hr;
   IUnknown *   pUnkDataSource   = NULL;
   IUnknown *   pUnkSession      = NULL;
   IUnknown *   pUnkRowset       = NULL;

   // Parse command line arguments, if any; this will update
   // the value of g_dwFlags as appropriate for the arguments.
   if ( !myParseCommandLine() )
      return EXIT_FAILURE;

   // Display instructions for the given command line arguments.
   myDisplayInstructions();

   // Initialize OLE
   hr = CoInitialize(NULL);
   if ( FAILED(hr) )
      return EXIT_FAILURE;

   // Create the data source object using the OLE DB service components.
   CHECK_HR(hr = myCreateDataSource(&pUnkDataSource));

   // Create a session object from the data source object.
   CHECK_HR(hr = myCreateSession(pUnkDataSource, &pUnkSession));

   // Create a rowset object from the session object, either directly
   // from the session or through a command object.
   CHECK_HR(hr = myCreateRowset(pUnkSession, &pUnkRowset));

   // Display the rowset object data to the user.
   CHECK_HR(hr = myDisplayRowset(pUnkRowset, NULL, 0, NULL));

CLEANUP:
   if( pUnkRowset )
      pUnkRowset->Release();
   if( pUnkSession )
      pUnkSession->Release();
   if( pUnkDataSource )
      pUnkDataSource->Release();

   CoUninitialize();

   if( FAILED(hr) )
      return EXIT_FAILURE;

   return EXIT_SUCCESS;
}

/////////////////////////////////////////////////////////////////

// myParseCommandLine
// Summary of Routines
//
// This function parses the application's command line arguments
// and sets the appropriate bits in g_dwFlags. If an invalid
// argument is encountered, a usage message is displayed and
// the function returns FALSE; otherwise, TRUE is returned.
//
/////////////////////////////////////////////////////////////////
BOOL myParseCommandLine () {
   int      iArg;
   CHAR *   psz;

   // Set the locale for all C run-time functions.
   setlocale(LC_ALL, ".ACP");

   // Go through each command line argument and set the appropriate
   // bits in g_dwFlags, depending on the chosen options.
   for ( iArg = 1; iArg < __argc; iArg++ ) {
      // Inspect the current argument string.
      psz = __argv[iArg];

      // Valid options begin with '-' or '/'.
      if( psz[0] == '-' || psz[0] == '/' ) {
         // The next character is the option.
         switch( tolower(psz[1]) ) {
         case 'u':
            // Use the service components UI to prompt for and create
            // the data source object; the enumerator is not used.
            g_dwFlags |= USE_PROMPTDATASOURCE;
            g_dwFlags &= ~USE_ENUMERATOR;
            continue;
         case 'e':
            // Use the enumerator to select the provider, and then use
            // IDataInitialize to create the data source object.
            // Don't use the UI to prompt for the data source.
            g_dwFlags |= USE_ENUMERATOR;
            g_dwFlags &= ~USE_PROMPTDATASOURCE;
            continue;
         case 'c':
            // Use ICommand instead of IOpenRowset.
            g_dwFlags |= USE_COMMAND;
            continue;
         case 'b':
            // Use ISequentialStream to fetch BLOB column data.
            g_dwFlags |= USE_ISEQSTREAM;
            continue;
         case 'n':
            // Don't display method call strings as part of
            // the extended error checking macro.
            g_dwFlags &= ~DISPLAY_METHODCALLS;
            continue;
         }
      }

      // Invalid argument; show the usage flags to the user.
      fprintf(stderr, "Usage: %s [-u] [-e] [-c] [-b] [-n]\n\nWhere:\n\t" \
         "u = Use the Microsoft Data Links UI " \
         "to create the DataSource\n\t" \
         "e = Use the Enumerator and IDataInitialize " \
         "to create the DataSource\n\t" \
         "c = Use ICommand instead of IOpenRowset to create the Rowset\n\t" \
         "b = Use ISequentialStream for BLOB columns\n\t" \
         "n = Don't display method call strings\n",
         __argv[0]);

      return FALSE;
   }

   return TRUE;
}

/////////////////////////////////////////////////////////////////

// myDisplayInstructions
// Summary of Routines
//
// This function asks the user whether they would like
// instructions displayed for the application. If so, it
// displays the instructions appropriate to the flags set
// in g_dwFlags.
//
/////////////////////////////////////////////////////////////////
void myDisplayInstructions() {
   CHAR ch;

   // Display header and ask the user if they want instructions.
   printf("\nOLE DB Programmer's Reference Sample\n" \
      "====================================\n\n");
   printf("Display instructions [Y or N]? ");
   do {
      ch = myGetChar();
   }
   while( ch != 'y' && ch != 'n' );
   printf("%c\n\n", ch);

   // No instructions, so we're done.
   if( ch == 'n' )
      return;

   // Display basic instructions.
   printf("This application is a simple OLE DB sample that will display\n" \
      "a rowset and will allow basic navigation of that rowset by\n" \
      "the user. The application will perform the following\n" \
      "steps:\n\n");

   // Display data source creation instructions.
   if( g_dwFlags & USE_PROMPTDATASOURCE ) {
      printf(" - Creates a data source object through the Microsoft Data\n Links UI. This allows the user to select the OLE DB\n provider to use and to set connection properties.\n");
   }
   else {
      printf(" - Creates a data source object through IDataInitialize::\n");
      printf("CreateDBInstance, which allows the OLE DB service\n");
      printf("component manager to add additional functionality to\n");
      printf("the provider as requested. The user will select the\n");
      printf("provider to use from a rowset obtained from the OLE DB enumerator.\n");
   }

   // Display session creation and table-selection instructions.
   printf(" - Creates a session object from the data source object.\n");
   printf(" - If the provider supports the schema rowset interface,\n" \
      "   creates a TABLES schema rowset and allows the user to\n" \
      "   select a table name from this rowset.\n");

   // Display rowset creation instructions
   if( g_dwFlags & USE_COMMAND ) {
      printf(" - Creates a cxommand object from the session object and\n" \
         "   allows the user to specify command text for this Command,\n" \
         "   then executes the command to create the final rowset.\n");
   }
   else
      printf(" - Creates the final rowset over the table specified by the user.\n");

   printf(" - Displays this rowset and allows the user to perform basic\n" \
      "   navigation of that rowset.\n\n");

   // Wait for the user to press a key before continuing.
   printf("Press a key to continue...");
   myGetChar();
   printf("\n\n");
}


/////////////////////////////////////////////////////////////////

// myGetInputFromUser
// Summary of Routines
//
// This function prompts the user with the contents of pwszFmt
// and any accompanying variable arguments and then gets a string
// as input from the user. If the string is non-empty, it is
// copied into pwszInput and the function returns TRUE;
// otherwise, this function returns FALSE.
//
/////////////////////////////////////////////////////////////////
BOOL myGetInputFromUser (LPWSTR pwszInput, LPCWSTR pwszFmt,  ... ) {
   va_list vargs;
   WCHAR wszBuffer[MAX_NAME_LEN + 1] = { 0 };

   // Create the string with variable arguments...
   va_start(vargs, pwszFmt);
   _vsnwprintf_s(wszBuffer, _countof(wszBuffer), MAX_NAME_LEN, pwszFmt, vargs);
   va_end(vargs);

   // Output the string...
   wprintf(wszBuffer);

   // Now get the Input from the user....
   _getws_s(wszBuffer, MAX_NAME_LEN + 1);
   if( wszBuffer[0] ) {
      wcscpy_s(pwszInput, sizeof(pwszInput), wszBuffer);
      return TRUE;
   }

   return FALSE;
}


/////////////////////////////////////////////////////////////////

// myGetChar
// Summary of Routines
//
// This function gets a character from the keyboard and
// converts it to lowercase before returning it.
//
/////////////////////////////////////////////////////////////////
CHAR myGetChar() {
   CHAR ch;

   // Get a character from the keyboard.
   ch = _getch();

   // Re-read for the actual key value if necessary.
   if( !ch || ch == 0xE0 )
      ch = _getch();

   return tolower(ch);
}

//---------------------------------------------------------------------------
// Microsoft OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft Corporation.
//
// @doc
//
// @module ENUM.CPP
//
//---------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#include "prsample.h"      // Programmer's Reference Sample includes



/////////////////////////////////////////////////////////////////

// myCreateEnumerator
// Summary of Routines
//
// This function creates an enumerator, obtains a sources rowset
// from it, displays the rowset to the user, and allows the user
// to specify the ProgID of a provider. The CLSID that matches
// this ProgID is retuned to the caller in *pCLSID.
//
/////////////////////////////////////////////////////////////////
HRESULT myCreateEnumerator
   (
   REFCLSID           clsidEnumerator,
   CLSID *            pCLSID
   )
{
   HRESULT            hr;
   IUnknown *         pIUnkEnumerator               = NULL;
   ISourcesRowset *   pISourcesRowset               = NULL;
   IRowset *          pIRowset                      = NULL;
   IDBInitialize *    pIDBInitialize                = NULL;
   WCHAR              wszProgID[MAX_NAME_LEN + 1]   = {0};
   
   const ULONG        cProperties                   = 2;
   DBPROP             rgProperties[cProperties];
   DBPROPSET          rgPropSets[1];

   // Create the enumerator object. We ask for IUnknown when creating
   // the enumerator because some enumerators may require initialization
   // before we can obtain a sources rowset from the enumerator. This is
   // indicated by whether the enumerator object exposes IDBInitialize
   // or not. (We don't want to ask for IDBInitialize, since enumerators
   // that don't require initialization will cause the CoCreateInstance
   // to fail.)
   XCHECK_HR(hr = CoCreateInstance(
            clsidEnumerator,                    // clsid -- enumerator
            NULL,                               // pUnkOuter
            CLSCTX_INPROC_SERVER,               // dwClsContext
            IID_IUnknown,                       // riid
            (void**)&pIUnkEnumerator            // ppvObj
            ));

   // If the enumerator exposes IDBInitialize, we need to initialize it.
   // See IDBInitialize
   if( SUCCEEDED(hr = pIUnkEnumerator->QueryInterface(IID_IDBInitialize,
            (void**)&pIDBInitialize)) )
   {
      CHECK_HR(hr = myDoInitialization(pIUnkEnumerator));
   }

   // Set properties on the rowset, to request additional functionality.
   myAddRowsetProperties(rgPropSets, cProperties, rgProperties);

   // Obtain a sources rowset from the enumerator. This rowset contains
   // all of the OLE DB providers that this enumerator is able to list.
   // See ISourcesRowset and IRowset
   XCHECK_HR(hr = pIUnkEnumerator->QueryInterface(IID_ISourcesRowset,
            (void**)&pISourcesRowset));
   XCHECK_HR(hr = pISourcesRowset->GetSourcesRowset(
            NULL,                               // pUnkOuter
            IID_IRowset,                        // riid
            1,                                  // cPropSets
            rgPropSets,                         // rgPropSets
            (IUnknown**)&pIRowset               // ppRowset
            ));

   // Display the rowset to the user. This will allow the user to
   // perform basic navigation of the rowset and will allow the user
   // to select a row containing a desired provider.
   CHECK_HR(hr = myDisplayRowset(pIRowset,
            L"SOURCES_NAME", MAX_NAME_LEN, wszProgID));

   // Obtain the ProgID for the provider to use from the user.
   // The default value for this is the value of the SOURCES_NAME
   // column in the row selected by the user previously.
   myGetInputFromUser(wszProgID, L"\nType the ProgID of a provider"
            L" to use [Enter = `[%s]`]: ", wszProgID);
   XCHECK_HR(hr = CLSIDFromProgID(wszProgID, pCLSID));

CLEANUP:
   if( pIUnkEnumerator )
      pIUnkEnumerator->Release();
   if( pISourcesRowset )
      pISourcesRowset->Release();
   if( pIRowset )
      pIRowset->Release();
   if( pIDBInitialize )
      pIDBInitialize->Release();
   return hr;
}

//---------------------------------------------------------------------------
// Microsoft OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft Corporation.
//
// @doc
//
// @module DATASOURCE.CPP
//
//---------------------------------------------------------------------------


/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#include "prsample.h"      // Programmer's Reference Sample includes

/////////////////////////////////////////////////////////////////

// myCreateDataSource
// Summary of Routines
//
// This function creates an OLE DB data source object for a
// provider selected by the user, sets initialization properties
// for the data source, and initializes the data source. The
// function returns a pointer to the data source object's
// IUnknown in *ppUnkDataSource.
//
/////////////////////////////////////////////////////////////////
HRESULT myCreateDataSource
   (
   IUnknown **             ppUnkDataSource
   )
{
   HRESULT                 hr;
   IDataInitialize *       pIDataInitialize       = NULL;
   IDBPromptInitialize *   pIDBPromptInitialize   = NULL;
   IDBInitialize *         pIDBInitialize         = NULL;
   CLSID                   clsid                  = CLSID_MSDASQL;

   // Use the Microsoft Data Links UI to create the data source
   // object. This will allow the user to select the provider
   // to connect to and to set the initialization properties
   // for the data source object, which will be created by the
   // Data Links UI.
   if( g_dwFlags & USE_PROMPTDATASOURCE )
   {
      // Create the Data Links UI object, and obtain the
      // IDBPromptInitialize interface from it.
      // See IDBPromptInitialize
      XCHECK_HR(hr = CoCreateInstance(
               CLSID_DataLinks,                 // clsid -- Data Links UI
               NULL,                            // pUnkOuter
               CLSCTX_INPROC_SERVER,            // dwClsContext
               IID_IDBPromptInitialize,         // riid
               (void**)&pIDBPromptInitialize    // ppvObj
               ));

      // Invoke the Data Links UI to allow the user to select
      // the provider and set initialization properties for
      // the data source object that this will create.
      // See IDBPromptInitialize and IDBInitialize
      XCHECK_HR(hr = pIDBPromptInitialize->PromptDataSource(
               NULL,                             // pUnkOuter
               GetDesktopWindow(),               // hWndParent
               DBPROMPTOPTIONS_PROPERTYSHEET,    // dwPromptOptions
               0,                                // cSourceTypeFilter
               NULL,                             // rgSourceTypeFilter
               NULL,                             // pwszszzProviderFilter
               IID_IDBInitialize,                // riid
               (IUnknown**)&pIDBInitialize       // ppDataSource
               ));

      // We've obtained a data source object from the Data Links UI. This
      // object has had its initialization properties set, so all we
      // need to do is Initialize it.
      XCHECK_HR(hr = pIDBInitialize->Initialize());
   }
   // We are not using the Data Links UI to create the data source
   // object. Instead, we will enumerate the providers installed on this
   // system through the OLE DB enumerator and will allow the user to
   // select the ProgID of the provider for which we will create a
   // data source object.
   else
   {
      // Use the OLE DB enumerator to obtain a rowset of installed
      // providers, and then allow the user to select a provider from
      // this rowset.
      CHECK_HR(hr = myCreateEnumerator (CLSID_OLEDB_ENUMERATOR, &clsid));

      // We will create the data source object through the OLE DB service
      // component IDataInitialize interface, so we need to create an
      // instance of the data initialization object.
      // See IDataInitialize
      XCHECK_HR(hr = CoCreateInstance(
               CLSID_MSDAINITIALIZE,          // clsid -- data initialize
               NULL,                          // pUnkOuter
               CLSCTX_INPROC_SERVER,          // dwClsContext
               IID_IDataInitialize,           // riid
               (void**)&pIDataInitialize      // ppvObj
               ));

      // Use IDataInitialize::CreateDBInstance to create an uninitialized
      // data source object for the chosen provider. By using this
      // service component method, the service component manager can
      // provide additional functionality beyond what is natively
      // supported by the provider if the consumer requests that
      // functionality.
      // See IDBInitialize
      XCHECK_HR(hr = pIDataInitialize->CreateDBInstance(
               clsid,                                // clsid -- provider
               NULL,                                 // pUnkOuter
               CLSCTX_INPROC_SERVER,                 // dwClsContext
               NULL,                                 // pwszReserved
               IID_IDBInitialize,                    // riid
               (IUnknown**)&pIDBInitialize           // ppDataSource
               ));

      // Initialize the data source object by setting any required
      // initialization properties and calling IDBInitialize::Initialize.
      CHECK_HR(hr = myDoInitialization(pIDBInitialize));
   }

CLEANUP:
   *ppUnkDataSource = pIDBInitialize;
   if( pIDataInitialize )
      pIDataInitialize->Release();
   if( pIDBPromptInitialize )
      pIDBPromptInitialize->Release();
   return hr;
}


/////////////////////////////////////////////////////////////////

// myDoInitialization
// Summary of Routines
//
// This function sets initialization properties that tell the
// provider to prompt the user for any information required to
// initialize the provider and then calls the provider's
// initialization function.
//
/////////////////////////////////////////////////////////////////
HRESULT myDoInitialization
   (
   IUnknown *        pIUnknown
   )
{
   HRESULT           hr;
   IDBInitialize *   pIDBInitialize   = NULL;
   IDBProperties *   pIDBProperties   = NULL;
   HWND              hWnd             = GetDesktopWindow();
   
   const ULONG       cProperties      = 2;
   DBPROP            rgProperties[cProperties];
   DBPROPSET         rgPropSets[1];

   // To initialize the data source object, most providers require
   // some initialization properties to be set by the consumer. For
   // example, these might include the data source to connect to and the
   // user ID and password to use to establish identity. We will ask the
   // provider to prompt the user for this required information by
   // setting the following properties:
   myAddProperty (&rgProperties[0],DBPROP_INIT_PROMPT,VT_I2,DBPROMPT_COMPLETE);
   myAddProperty (&rgProperties[1],DBPROP_INIT_HWND,  VT_I4,(LONG)hWnd);

   rgPropSets[0].rgProperties      = rgProperties;
   rgPropSets[0].cProperties       = cProperties;
   rgPropSets[0].guidPropertySet   = DBPROPSET_DBINIT;

   // Obtain the needed interfaces.
   // See IDBProperties and IDBInitialize
   XCHECK_HR(hr = pIUnknown->QueryInterface(IID_IDBProperties,
            (void**)&pIDBProperties));
   XCHECK_HR(hr = pIUnknown->QueryInterface(IID_IDBInitialize,
            (void**)&pIDBInitialize));

   // If a provider requires initialization properties, it must support
   // the properties that we are setting (_PROMPT and _HWND). However,
   // some providers do not need initialization properties and may
   // therefore not support the _PROMPT and _HWND properties. Because of
   // this, we will not check the return value from SetProperties.
   hr = pIDBProperties->SetProperties(1, rgPropSets);

   // Now that we've set our properties, initialize the provider.
   XCHECK_HR(hr = pIDBInitialize->Initialize());

CLEANUP:
   if( pIDBProperties )
      pIDBProperties->Release();
   if( pIDBInitialize )
      pIDBInitialize->Release();
   return hr;
}

/////////////////////////////////////////////////////////////////

// myGetProperty
// Summary of Routines
//
// This function gets the BOOL value for the specified property
// and returns the result in *pbValue.
//
/////////////////////////////////////////////////////////////////
HRESULT myGetProperty
   (
   IUnknown *             pIUnknown,
   REFIID                 riid,
   DBPROPID               dwPropertyID,
   REFGUID                guidPropertySet,
   BOOL *                 pbValue
   )
{
   HRESULT                hr;
   DBPROPID               rgPropertyIDs[1];
   DBPROPIDSET            rgPropertyIDSets[1];
   
   ULONG                  cPropSets        = 0;
   DBPROPSET *            rgPropSets       = NULL;

   IDBProperties *        pIDBProperties   = NULL;
   ISessionProperties *   pISesProps       = NULL;
   ICommandProperties *   pICmdProps       = NULL;
   IRowsetInfo *          pIRowsetInfo     = NULL;

   // Initialize the output value
   *pbValue = FALSE;

   // Set up the property ID array
   rgPropertyIDs[0] = dwPropertyID;
   
   // Set up the Property ID Set
   rgPropertyIDSets[0].rgPropertyIDs     = rgPropertyIDs;
   rgPropertyIDSets[0].cPropertyIDs      = 1;
   rgPropertyIDSets[0].guidPropertySet   = guidPropertySet;

   // Get the property value for this property from the provider, but
   // don't try to display extended error information, since this may
   // not be a supported property. A failure is, in fact, expected if
   // the property is not supported.
   // See IDBProperties, ISessionProperties, ICommandProperties,
   // and IRowsetInfo
   if( riid == IID_IDBProperties )
   {
      XCHECK_HR(hr = pIUnknown->QueryInterface(IID_IDBProperties,
               (void**)&pIDBProperties));
      CHECK_HR(hr = pIDBProperties->GetProperties(
               1,                                 // cPropertyIDSets
               rgPropertyIDSets,                  // rgPropertyIDSets
               &cPropSets,                        // pcPropSets
               &rgPropSets                        // prgPropSets
               ));
   }
   else if( riid == IID_ISessionProperties )
   {
      XCHECK_HR(hr = pIUnknown->QueryInterface(IID_ISessionProperties,
               (void**)&pISesProps));
      CHECK_HR(hr = pISesProps->GetProperties(
               1,                                 // cPropertyIDSets
               rgPropertyIDSets,                  // rgPropertyIDSets
               &cPropSets,                        // pcPropSets
               &rgPropSets                        // prgPropSets
               ));
   }
   else if( riid == IID_ICommandProperties )
   {
      XCHECK_HR(hr = pIUnknown->QueryInterface(IID_ICommandProperties,
               (void**)&pICmdProps));
      CHECK_HR(hr = pICmdProps->GetProperties(
               1,                                 // cPropertyIDSets
               rgPropertyIDSets,                  // rgPropertyIDSets
               &cPropSets,                        // pcPropSets
               &rgPropSets                        // prgPropSets
               ));
   }
   else
   {
      XCHECK_HR(hr = pIUnknown->QueryInterface(IID_IRowsetInfo,
               (void**)&pIRowsetInfo));
      CHECK_HR(hr = pIRowsetInfo->GetProperties(
               1,                                 // cPropertyIDSets
               rgPropertyIDSets,                  // rgPropertyIDSets
               &cPropSets,                        // pcPropSets
               &rgPropSets                        // prgPropSets
               ));
   }

   // Return the value for this property to the caller if
   // it's a VT_BOOL type value, as expected.
   if( V_VT(&rgPropSets[0].rgProperties[0].vValue) == VT_BOOL )
      *pbValue = V_BOOL(&rgPropSets[0].rgProperties[0].vValue);

CLEANUP:
   // Notice that in addition to calling IMalloc::Free
   // for the rgProperties element within each element of
   // rgPropSets, cleanup code also calls VariantClear for
   // the vValue member of each DBPROP structure in
   // order to prevent a memory leak in cases where the
   // variant contains a reference type (such as a BSTR.) 


   ULONG iPropSet;
   ULONG iProp;

   Assert((rgPropSets != NULL) || (cPropSets == 0));
   if( rgPropSets )
   {
      for(iPropSet = 0; iPropSet < cPropSets; iPropSet++)
      {
         Assert((rgPropSets[iPropSet].cProperties == 0) ||
               (rgPropSets[iPropSet].rgProperties != NULL));
         if(rgPropSets[iPropSet].rgProperties)
         {
            for(iProp = 0;
               iProp < rgPropSets[iPropSet].cProperties;
               iProp++)
            {
         VariantClear(&(rgPropSets[iPropSet].rgProperties[iProp].vValue));
            }
            Free(rgPropSets[iPropSet].rgProperties);
         }
      }
      Free(rgPropSets);
   }
   if( pIDBProperties )
      pIDBProperties->Release();
   if( pISesProps )
      pISesProps->Release();
   if( pICmdProps )
      pICmdProps->Release();
   if( pIRowsetInfo )
      pIRowsetInfo->Release();
   return hr;
}

/////////////////////////////////////////////////////////////////

// myAddProperty
// Summary of Routines
//
// This function initializes the property structure pProp.
//
/////////////////////////////////////////////////////////////////
void myAddProperty
   (
   DBPROP *           pProp,
   DBPROPID           dwPropertyID,
   VARTYPE            vtType,
   LONG               lValue,
   DBPROPOPTIONS      dwOptions
   )
{
   // Set up the property structure.
   pProp->dwPropertyID    = dwPropertyID;
   pProp->dwOptions       = dwOptions;
   pProp->dwStatus        = DBPROPSTATUS_OK;
   pProp->colid           = DB_NULLID;
   V_VT(&pProp->vValue)   = vtType;

   // Since VARIANT data is a union, we can place the value in any
   // member (except for VT_DECIMAL, which is a union with the whole
   // VARIANT structure -- but we know we're not passing VT_DECIMAL).
   V_I4(&pProp->vValue)   = lValue;
}

//---------------------------------------------------------------------------
// Microsoft OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft Corporation.
//
// @doc
//
// @module SESSION.CPP
//
//---------------------------------------------------------------------------


/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#include "prsample.h"      // Programmer's Reference Sample includes



/////////////////////////////////////////////////////////////////

// myCreateSession
// Summary of Routines
//
// Create an OLE DB session object from the given data source
// object. The IDBCreateSession interface is mandatory, so this
// is a simple operation.
//
/////////////////////////////////////////////////////////////////
HRESULT   myCreateSession
   (
   IUnknown *           pUnkDataSource,
   IUnknown **          ppUnkSession
   )
{
   HRESULT              hr;
   IDBCreateSession *   pIDBCreateSession      = NULL;
   
   //Create a session object from a data source object
   // See IDBCreateSession and IOpenRowset
   XCHECK_HR(hr = pUnkDataSource->QueryInterface(
            IID_IDBCreateSession, (void**)&pIDBCreateSession));
   XCHECK_HR(hr = pIDBCreateSession->CreateSession(
            NULL,                                   // pUnkOuter
            IID_IOpenRowset,                        // riid
            ppUnkSession                            // ppSession
            ));

CLEANUP:
   if( pIDBCreateSession )
      pIDBCreateSession->Release();
   return hr;
}


/////////////////////////////////////////////////////////////////

// myCreateSchemaRowset
// Summary of Routines
//
// If the provider supports IDBSchemaRowset, this function will
// obtain the tables schema rowset, will display this rowset to
// the user, and will allow the user to select a row in the
// rowset containing the name of a table of interest.
//
/////////////////////////////////////////////////////////////////
HRESULT   myCreateSchemaRowset
   (
   GUID                guidSchema,
   IUnknown *          pUnkSession,
   ULONG               cchBuffer,
   LPWSTR              pwszBuffer
   )
{
   HRESULT             hr                 = S_OK;
   IDBSchemaRowset *   pIDBSchemaRowset   = NULL;
   IUnknown *          pUnkRowset         = NULL;
   
   const ULONG         cProperties        = 2;
   DBPROP              rgProperties[cProperties];
   DBPROPSET           rgPropSets[1];

   // Attempt to obtain the IDBSchemaRowset interface on the session
   // object. This is not a mandatory interface; if it is not supported,
   // we are done.
   // See IDBSchemaRowset
   CHECK_HR(pUnkSession->QueryInterface(
               IID_IDBSchemaRowset, (void**)&pIDBSchemaRowset));
   
   // Set properties on the rowset, to request additional functionality.
   myAddRowsetProperties(rgPropSets, cProperties, rgProperties);

   // Get the requested schema rowset. If IDBSchemaRowset is supported,
   // the following schema rowsets are required to be supported:
   // DBSCHEMA_TABLES, DBSCHEMA_COLUMNS, and DBSCHEMA_PROVIDERTYPES
   // We know that we will be asking for one of these, so it is not
   // necessary to call IDBSchemaRowset::GetSchemas in this case.
   // See IRowset
   XCHECK_HR(hr = pIDBSchemaRowset->GetRowset(
            NULL,                              // pUnkOuter
            guidSchema,                        // guidSchema
            0,                                 // cRestrictions
            NULL,                              // rgRestrictions
            IID_IRowset,                       // riid
            1,                                 // cPropSets
            rgPropSets,                        // rgPropSets
            &pUnkRowset                        // ppRowset
            ));

   // Display the rowset to the user. This will allow the user to
   // perform basic navigation of the rowset and will allow the user
   // to select a row containing a desired table name (taken from the
   // TABLE_NAME column).
   CHECK_HR(hr = myDisplayRowset(pUnkRowset,
      L"TABLE_NAME", cchBuffer, pwszBuffer));

CLEANUP:
   if( pIDBSchemaRowset )
      pIDBSchemaRowset->Release();
   if( pUnkRowset )
      pUnkRowset->Release();
   return hr;
}

// rowset.cpp
//---------------------------------------------------------------------------
// Microsoft OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft Corporation.
//
// @doc
//
// @module ROWSET.CPP
//
//---------------------------------------------------------------------------


/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#include "prsample.h"         // Programmer's Reference Sample includes


/////////////////////////////////////////////////////////////////

// myCreateRowset
// Summary of Routines
//
// This function creates an OLE DB rowset object from the given
// provider's session object. It first obtains a default table
// name from the user through the tables schema rowset, if
// supported, and then creates a rowset object by one of two methods:
//
// - If the user requested that the rowset object be created
//    from a command object, it creates a command object and then
//    obtains command text from the user, sets properties and
//    the command text, and finally executes the command to
//    create the rowset object.
// - Otherwise, the function obtains a table name from the user
//    and calls IOpenRowset::OpenRowset to create a rowset object
//    over that table that supports the requested properties.
//
/////////////////////////////////////////////////////////////////
HRESULT   myCreateRowset (IUnknown * pUnkSession, IUnknown ** ppUnkRowset) {
   HRESULT         hr;
   IUnknown *      pUnkCommand                      = NULL;
   IOpenRowset *   pIOpenRowset                     = NULL;
   WCHAR           wszTableName[MAX_NAME_LEN + 1]   = {0};

   const ULONG     cProperties                      = 2;
   DBPROP          rgProperties[cProperties];
   DBPROPSET       rgPropSets[1];

   // Obtain a default table name from the user by displaying the
   // tables schema rowset if schema rowsets are supported.
   CHECK_HR(hr = myCreateSchemaRowset(DBSCHEMA_TABLES, pUnkSession, MAX_NAME_LEN, wszTableName));

   // Set properties on the rowset, to request additional functionality.
   myAddRowsetProperties(rgPropSets, cProperties, rgProperties);

   // If the user requested that the rowset be created from a
   // Ccommand object, create a command, set its properties and
   // text, and execute it to create the rowset object.
   if( g_dwFlags & USE_COMMAND ) {
      WCHAR wszCommandText[MAX_NAME_LEN + 1];

      // Attempt to create the command object from the provider's
      // session object. Note that commands are not supported by
      // all providers, and this will fail in that case.
      CHECK_HR(hr = myCreateCommand(pUnkSession, &pUnkCommand));

      // From the user, get the command text that we will execute.
      if ( ! myGetInputFromUser(wszCommandText, L"\nType the command "
            L"to execute [Enter = `select * from [%s]`]: ", wszTableName) )
         swprintf_s(wszCommandText, _countof(wszCommandText), L"select * from %s", wszTableName);

      // And execute the command the user entered.
      CHECK_HR(hr = myExecuteCommand(pUnkCommand, wszCommandText, 1, rgPropSets, ppUnkRowset));
   }
   // Otherwise, the user gets the default behavior, which is to use
   // IOpenRowset to create the rowset object from the session object.
   // IOpenRowset is supported by all providers; it takes a TableID
   // and creates a rowset containing all rows in that table. It is
   // similar to using SQL command text of "SELECT * FROM TableID".
   else {
      DBID TableID;

      // Create the TableID.
      TableID.eKind            = DBKIND_NAME;
      TableID.uName.pwszName   = wszTableName;

      // Obtain the table name from the user.
      myGetInputFromUser(wszTableName, L"\nType the name of the table "
         L"to use [Enter = `[%s]`]: ", wszTableName);

      // Get the IOpenRowset interface, and create a rowset object
      // over the requested table through OpenRowset.
      // See IOpenRowset

      XCHECK_HR(hr = pUnkSession->QueryInterface(
         IID_IOpenRowset, (void**)&pIOpenRowset));
      XCHECK_HR(hr = pIOpenRowset->OpenRowset(
         NULL,                                 // pUnkOuter
         &TableID,                             // pTableID
         NULL,                                 // pIndexID
         IID_IRowset,                          // riid
         1,                                    // cPropSets
         rgPropSets,                           // rgPropSets
         ppUnkRowset                           // ppRowset
         ));
   }

CLEANUP:
   if( pIOpenRowset )
      pIOpenRowset->Release();
   if( pUnkCommand )
      pUnkCommand->Release();
   return hr;
}


/////////////////////////////////////////////////////////////////

// mySetupBindings
// Summary of Routines
//
// This function takes an IUnknown pointer from a rowset object
// and creates a bindings array that describes how we want the
// data we fetch from the rowset to be laid out in memory. It
// also calculates the total size of a row so that we can use
// this to allocate memory for the rows that we will fetch
// later.
//
// For each column in the rowset, there will be a corresponding
// element in the bindings array that describes how the
// provider should transfer the data, including length and
// status, for that column. This element also specifies the data
// type that the provider should return the column as. We will
// bind all columns as DBTYPE_WSTR, with a few exceptions
// detailed below, as providers are required to support the
// conversion of their column data to this type in the vast
// majority of cases. The exception to our binding as
// DBTYPE_WSTR is if the native column data type is
// DBTYPE_IUNKNOWN or if the user has requested that BLOB
// columns be bound as ISequentialStream objects, in which case
// we will bind those columns as ISequentialStream objects.
//
/////////////////////////////////////////////////////////////////
HRESULT mySetupBindings ( IUnknown * pUnkRowset,
                         DBCOUNTITEM * pcBindings,
                         DBBINDING ** prgBindings,
                         DBLENGTH * pcbRowSize ) 
{
   HRESULT          hr;
   DBORDINAL       cColumns;
   DBCOLUMNINFO *   rgColumnInfo    = NULL;
   LPWSTR           pStringBuffer   = NULL;
   IColumnsInfo *   pIColumnsInfo   = NULL;

   DBORDINAL        iCol;
   DBBYTEOFFSET     dwOffset        = 0;
   DBBINDING *      rgBindings      = NULL;

   ULONG            cStorageObjs    = 0;
   BOOL             fMultipleObjs   = FALSE;

   // Obtain the column information for the rowset; from this, we can
   // find out the following information that we need to construct the
   // bindings array:
   // - the number of columns
   // - the ordinal of each column
   // - the precision and scale of numeric columns
   // - the OLE DB data type of the column
   // See IColumnsInfo
   XCHECK_HR(hr = pUnkRowset->QueryInterface(IID_IColumnsInfo, (void**)&pIColumnsInfo));
   XCHECK_HR(hr = pIColumnsInfo->GetColumnInfo( &cColumns,   // pcColumns
                                                &rgColumnInfo,   // prgColumnInfo
                                                &pStringBuffer)   // ppStringBuffer
                                                );

   // Allocate memory for the bindings array; there is a one-to-one
   // mapping between the columns returned from GetColumnInfo and our
   // bindings.
   rgBindings = (DBBINDING*)CoTaskMemAlloc(cColumns * sizeof(DBBINDING));
   CHECK_MEMORY(hr, rgBindings);
   memset(rgBindings, 0, cColumns * sizeof(DBBINDING));

   // Determine if the rowset supports multiple storage object bindings.
   // If it does not, we will bind only the first BLOB column or IUnknown
   // column as an ISequentialStream object, and we will bind the rest as
   // DBTYPE_WSTR.
   // See ISequentialStream
   myGetProperty(pUnkRowset, IID_IRowset, DBPROP_MULTIPLESTORAGEOBJECTS,
      DBPROPSET_ROWSET, &fMultipleObjs);

   // Construct the binding array element for each column.
   for( iCol = 0; iCol < cColumns; iCol++ ) {
      // This binding applies to the ordinal of this column.
      rgBindings[iCol].iOrdinal = rgColumnInfo[iCol].iOrdinal;

      // We are asking the provider to give us the data for this column
      // (DBPART_VALUE), the length of that data (DBPART_LENGTH), and
      // the status of the column (DBPART_STATUS).
      rgBindings[iCol].dwPart = DBPART_VALUE|DBPART_LENGTH|DBPART_STATUS;

      // The following values are the offsets to the status, length, and
      // data value that the provider will fill with the appropriate
      // values when we fetch data later. When we fetch data, we will
      // pass a pointer to a buffer that the provider will copy column
      // data to, in accordance with the binding we have provided for
      // that column; these are offsets into that future buffer.
      rgBindings[iCol].obStatus   = dwOffset;
      rgBindings[iCol].obLength   = dwOffset + sizeof(DBSTATUS);
      rgBindings[iCol].obValue    = dwOffset + sizeof(DBSTATUS) + sizeof(DBLENGTH);

      // Any memory allocated for the data value will be owned by us, the
      // client. Note that no data will be allocated in this case, as the
      // DBTYPE_WSTR bindings we are using will tell the provider to
      // simply copy data directly into our provided buffer.
      rgBindings[iCol].dwMemOwner   = DBMEMOWNER_CLIENTOWNED;

      // This is not a parameter binding.
      rgBindings[iCol].eParamIO   = DBPARAMIO_NOTPARAM;

      // We want to use the precision and scale of the column.
      rgBindings[iCol].bPrecision   = rgColumnInfo[iCol].bPrecision;
      rgBindings[iCol].bScale       = rgColumnInfo[iCol].bScale;

      // Bind this column as DBTYPE_WSTR, which tells the provider to
      // copy a Unicode string representation of the data into our
      // buffer, converting from the native type if necessary.
      rgBindings[iCol].wType = DBTYPE_WSTR;

      // Initially, we set the length for this data in our buffer to 0;
      // the correct value for this will be calculated directly below.
      rgBindings[iCol].cbMaxLen = 0;

      // Determine the maximum number of bytes required in our buffer to
      // contain the Unicode string representation of the provider's
      // native data type, including room for the NULL-termination
      // character.
      switch( rgColumnInfo[iCol].wType ) {
         case DBTYPE_NULL:
         case DBTYPE_EMPTY:
         case DBTYPE_I1:
         case DBTYPE_I2:
         case DBTYPE_I4:
         case DBTYPE_UI1:
         case DBTYPE_UI2:
         case DBTYPE_UI4:
         case DBTYPE_R4:
         case DBTYPE_BOOL:
         case DBTYPE_I8:
         case DBTYPE_UI8:
         case DBTYPE_R8:
         case DBTYPE_CY:
         case DBTYPE_ERROR:
            // When the above types are converted to a string, they
            // will all fit into 25 characters, so use that plus space
            // for the NULL terminator.
            rgBindings[iCol].cbMaxLen = (25 + 1) * sizeof(WCHAR);
            break;
         case DBTYPE_DECIMAL:
         case DBTYPE_NUMERIC:
         case DBTYPE_DATE:
         case DBTYPE_DBDATE:
         case DBTYPE_DBTIMESTAMP:
         case DBTYPE_GUID:
            // Converted to a string, the above types will all fit into
            // 50 characters, so use that plus space for the terminator.
            rgBindings[iCol].cbMaxLen = (50 + 1) * sizeof(WCHAR);
            break;
         case DBTYPE_BYTES:
            // In converting DBTYPE_BYTES to a string, each byte
            // becomes two characters (e.g. 0xFF -> "FF"), so we
            // will use double the maximum size of the column plus
            // include space for the NULL terminator.
            rgBindings[iCol].cbMaxLen =
               (rgColumnInfo[iCol].ulColumnSize * 2 + 1) * sizeof(WCHAR);
            break;
         case DBTYPE_STR:
         case DBTYPE_WSTR:
         case DBTYPE_BSTR:
            // Going from a string to our string representation,
            // we can just take the maximum size of the column,
            // a count of characters, and include space for the
            // terminator, which is not included in the column size.
            rgBindings[iCol].cbMaxLen =
               (rgColumnInfo[iCol].ulColumnSize + 1) * sizeof(WCHAR);
            break;

         default:
            // For any other type, we will simply use our maximum
            // column buffer size, since the display size of these
            // columns may be variable (e.g. DBTYPE_VARIANT) or
            // unknown (e.g. provider-specific types).
            rgBindings[iCol].cbMaxLen = MAX_COL_SIZE;
            break;
      };

      // If the provider's native data type for this column is
      // DBTYPE_IUNKNOWN or this is a BLOB column and the user
      // has requested that we bind BLOB columns as ISequentialStream
      // objects, bind this column as an ISequentialStream object if
      // the provider supports our creating another ISequentialStream
      // binding.
      if( (rgColumnInfo[iCol].wType == DBTYPE_IUNKNOWN ||
         ((rgColumnInfo[iCol].dwFlags & DBCOLUMNFLAGS_ISLONG) &&
         (g_dwFlags & USE_ISEQSTREAM))) &&
         (fMultipleObjs || !cStorageObjs) )
      {
         // To create an ISequentialStream object, we will
         // bind this column as DBTYPE_IUNKNOWN to indicate
         // that we are requesting this column as an object.
         rgBindings[iCol].wType = DBTYPE_IUNKNOWN;

         // We want to allocate enough space in our buffer for the
         // ISequentialStream pointer we will obtain from the provider.
         rgBindings[iCol].cbMaxLen = sizeof(ISequentialStream *);

         // To specify the type of object that we want from the
         // provider, we need to create a DBOBJECT structure and
         // place it in our binding for this column.
         rgBindings[iCol].pObject =
            (DBOBJECT *)CoTaskMemAlloc(sizeof(DBOBJECT));
         CHECK_MEMORY(hr, rgBindings[iCol].pObject);

         // Direct the provider to create an ISequentialStream
         // object over the data for this column.
         rgBindings[iCol].pObject->iid = IID_ISequentialStream;

         // We want read access on the ISequentialStream
         // object that the provider will create for us.
         rgBindings[iCol].pObject->dwFlags = STGM_READ;

         // Keep track of the number of storage objects
         // (ISequentialStream is a storage interface) that we have
         // requested, so that we can avoid requesting multiple storage
         // objects from a provider that supports only a single storage
         // object in our bindings.
         cStorageObjs++;
      }   

      // Ensure that the bound maximum length is no more than the
      // maximum column size in bytes that we've defined.
      rgBindings[iCol].cbMaxLen = min(rgBindings[iCol].cbMaxLen, MAX_COL_SIZE);

      // Update the offset past the end of this column's data so that the
      // next column will begin in the correct place in the buffer.
      dwOffset = rgBindings[iCol].cbMaxLen + rgBindings[iCol].obValue;

      // Ensure that the data for the next column will be correctly
      // aligned for all platforms, or if we're done with columns,
      // that if we allocate space for multiple rows that the data
      // for every row is correctly aligned.
      dwOffset = ROUNDUP(dwOffset);
   }

   // Return the row size (the current dwOffset is the size of the row),
   // the count of bindings, and the bindings array to the caller.
   *pcbRowSize    = dwOffset;
   *pcBindings    = cColumns;
   *prgBindings   = rgBindings;

CLEANUP:
   CoTaskMemFree(rgColumnInfo);
   CoTaskMemFree(pStringBuffer);
   if( pIColumnsInfo )
      pIColumnsInfo->Release();
   return hr;
}

/////////////////////////////////////////////////////////////////
// myCreateAccessor
// Summary of Routines
//
// This function takes an IUnknown pointer for a rowset object
// and creates an accessor that describes the layout of the
// buffer we will use when we fetch data. The provider will fill
// this buffer according to the description contained in the
// accessor that we will create here.
//
/////////////////////////////////////////////////////////////////
HRESULT myCreateAccessor (IUnknown *     pUnkRowset,
                          HACCESSOR *    phAccessor,
                          DBCOUNTITEM*   pcBindings,
                          DBBINDING **   prgBindings,
                          DBLENGTH*       pcbRowSize )
{
   HRESULT        hr;
   IAccessor *    pIAccessor = NULL;

   // An accessor is basically a handle to a collection of bindings.
   // To create the accessor, we need to first create an array of
   // bindings for the columns in the rowset.
   CHECK_HR(hr = mySetupBindings(pUnkRowset, pcBindings, prgBindings, pcbRowSize));

   // Now that we have an array of bindings, tell the provider to
   // create the accessor for those bindings. We get back a handle
   // to this accessor, which we will use when fetching data.
   // See IAccessor
   XCHECK_HR(hr = pUnkRowset->QueryInterface(IID_IAccessor, (void**)&pIAccessor));
   XCHECK_HR(hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA,   // dwAccessorFlags
                                            *pcBindings,   // cBindings
                                            *prgBindings,   // rgBindings
                                            0,   // cbRowSize
                                            phAccessor,   // phAccessor
                                            NULL));   // rgStatus

CLEANUP:
   if( pIAccessor )
      pIAccessor->Release();
   return hr;
}

/////////////////////////////////////////////////////////////////
// myDisplayRowset
// Summary of Routines
//
// This function will display data from a rowset object and will
// allow the user to perform basic navigation of the rowset.
//
// The function takes a pointer to a rowset object's IUnknown
// and, optionally, the name of a column and a buffer that will
// receive the value of that column when the user selects a row.
//
/////////////////////////////////////////////////////////////////
HRESULT myDisplayRowset( IUnknown *    pUnkRowset,
                        LPCWSTR       pwszColToReturn,
                        ULONG         cchBuffer,
                        LPWSTR        pwszBuffer )
{
   HRESULT       hr;
   IRowset *     pIRowset               = NULL;
   DBCOUNTITEM   cBindings;
   DBBINDING *   rgBindings             = NULL;   
   HACCESSOR     hAccessor              = DB_NULL_HACCESSOR;
   DBLENGTH      cbRowSize;
   void *        pData                  = NULL;
   ULONG *       rgDispSize             = NULL;
   DBCOUNTITEM   cRowsObtained;
   HROW *        rghRows                = NULL;
   DBCOUNTITEM   iRow;
   DBROWCOUNT    cRows                  = MAX_ROWS;
   DB_LORDINAL   iRetCol                = -1;
   BOOL          fCanFetchBackwards;
   DBCOUNTITEM   iIndex;
   void *        pCurData;

   // Obtain the IRowset interface for use in fetching rows and data.  See IRowset
   XCHECK_HR(hr = pUnkRowset->QueryInterface(IID_IRowset, (void**)&pIRowset));

   // Determine whether this rowset supports fetching data backwards;
   // we use this to determine whether the rowset can support moving
   // to the previous set of rows, described in more detail below
   myGetProperty(pUnkRowset, IID_IRowset, DBPROP_CANFETCHBACKWARDS,
      DBPROPSET_ROWSET, &fCanFetchBackwards);

   // If the caller wants us to return the data for a particular column from a
   // user-selected row, we need to turn the column name into a column ordinal.
   if( pwszColToReturn )
      CHECK_HR(hr = myFindColumn(pUnkRowset, pwszColToReturn, &iRetCol));

   // Create an accessor. An accessor is basically a handle to a
   // collection of bindings that describes to the provider how to
   // copy (and convert, if necessary) column data into our buffer.
   // The accessor that this creates will bind all columns either as
   // DBTYPE_WSTR (a Unicode string) or as an ISequentialStream object
   // (used for BLOB data). This will also give us the size of the
   // row buffer that the accessor describes to the provider.
   CHECK_HR(hr = myCreateAccessor(pUnkRowset, &hAccessor,
      &cBindings, &rgBindings, &cbRowSize));

   // Allocate enough memory to hold cRows rows of data; this is
   // where the actual row data from the provider will be placed.
   pData = CoTaskMemAlloc(cbRowSize * MAX_ROWS);
   CHECK_MEMORY(hr, pData);

   // Allocate memory for an array that we will use to calculate the
   // maximum display size used by each column in the current set of rows.
   rgDispSize = (ULONG *)CoTaskMemAlloc(cBindings * sizeof(ULONG));
   CHECK_MEMORY(hr, rgDispSize);

   // In this loop, we perform the following process:
   // - reset the maximum display size array
   // - try to get cRows row handles from the provider
   // - these handles are then used to actually get the row data from the
   //    provider copied into our allocated buffer
   // - calculate the maximum display size for each column
   // - release the row handles to the rows we obtained
   // - display the column names for the rowset
   // - display the row data for the rows that we fetched
   // - get user input
   // - free the provider-allocated row handle array
   // - repeat unless the user has chosen to quit or has selected a row
   while( hr == S_OK ) {
      // Clear the maximum display size array.
      memset(rgDispSize, 0, cBindings * sizeof(ULONG));

      // Attempt to get cRows row handles from the provider.
      XCHECK_HR(hr = pIRowset->GetNextRows(DB_NULL_HCHAPTER,   // hChapter
                                           0,   // lOffset
                                           cRows,   // cRows
                                           &cRowsObtained,   // pcRowsObtained
                                           &rghRows));   // prghRows

      // Loop over the row handles obtained from GetNextRows,
      // actually fetching the data for these rows into our buffer.
      for ( iRow = 0 ; iRow < cRowsObtained ; iRow++ ) {
         // Find the location in our buffer where we want to place
         // the data for this row. Note that if we fetched rows
         // backwards (cRows < 0), the row handles obtained from the
         // provider are reversed from the order in which we want to
         // actually display the data on the screen, so we will
         // account for this. This ensures that the resulting order
         // of row data in the pData buffer matches the order we
         // wish to use to display the data.
         iIndex     = cRows > 0 ? iRow : cRowsObtained - iRow - 1;
         pCurData   = (BYTE*)pData + (cbRowSize * iIndex);

         // Get the data for this row handle. The provider will copy
         // (and convert, if necessary) the data for each of the
         // columns that are described in our Aaccessor into the given
         // buffer (pCurData).
         XCHECK_HR(hr = pIRowset->GetData( rghRows[iRow],   // hRow
                                           hAccessor,   // hAccessor
                                           pCurData));   // pData

         // Update the maximum display size array, accounting for this row.
         CHECK_HR(hr = myUpdateDisplaySize(cBindings, rgBindings, pCurData, rgDispSize));
      }

      // If we obtained rows, release the row handles for the retrieved
      // rows and display the names of the rowset columns before we
      // display the data.
      if( cRowsObtained ) {
         // Release the row handles that we obtained.
         XCHECK_HR(hr = pIRowset->ReleaseRows(
            cRowsObtained,                        // cRows
            rghRows,                              // rghRows
            NULL,                                 // rgRowOptions
            NULL,                                 // rgRefCounts
            NULL                                  // rgRowStatus
            ));

         // Display the names of the rowset columns.
         CHECK_HR(hr = myDisplayColumnNames(pIRowset, rgDispSize));
      }

      // For each row that we obtained the data for, display this data.
      for( iRow = 0; iRow < cRowsObtained; iRow++ ) {
         // Get a pointer to the data for this row.
         pCurData = (BYTE*)pData + (cbRowSize* iRow);

         // And display the row data.
         CHECK_HR(hr = myDisplayRow(iRow, cBindings, rgBindings, pCurData, rgDispSize));
      }

      // Allow the user to navigate the rowset. This displays the
      // appropriate prompts, gets the user's input, may call
      // IRowset::RestartPosition, and may copy data from a selected row
      // to the selection buffer, if so directed. This will return S_OK
      // if the user asked for more rows, S_FALSE if the user selected a
      // row, or E_FAIL if the user quits.
      hr = myInteractWithRowset (
         pIRowset,                // IRowset pointer, for RestartPosition
         &cRows,                  // updated with fetch direction value
         cRowsObtained,           // to indicate selection range
         fCanFetchBackwards,      // whether [P]revious is supported
         pData,                   // data pointer for copying selection
         cbRowSize,               // size of rows for copying selection
         iRetCol >= 0 ?           // bindings for the selection column,
         &rgBindings[iRetCol] : NULL,   // or NULL if no selection column
         cchBuffer,                // size of the selection buffer
         pwszBuffer);             // pointer to the selection buffer

      // Since we are allowing the provider to allocate the memory for
      // the row handle array, we will free this memory and reset the
      // pointer to NULL. If this is not NULL on the next call to
      // GetNextRows, the provider will assume that it points to an
      // allocated array of the required size (which may not be the case
      // if we obtained less than cRows rows from this last call to
      // GetNextRows).
      CoTaskMemFree(rghRows);
      rghRows = NULL;
   }

CLEANUP:
   myFreeBindings(cBindings, rgBindings);
   CoTaskMemFree(rgDispSize);
   CoTaskMemFree(pData);
   if( pIRowset )
      pIRowset->Release();
   return hr;
}

/////////////////////////////////////////////////////////////////
// myInteractWithRowset
// Summary of Routines
//
// This function allows the user to interact with the rowset. It
// prompts the user appropriately, gets the user's input, may
// call IRowset::RestartPosition if the user requests a restart,
// and will copy data from a selected row to the selection
// buffer.
//
/////////////////////////////////////////////////////////////////
HRESULT myInteractWithRowset ( IRowset *     pIRowset,
                              DBROWCOUNT *  pcRows,
                              DBCOUNTITEM   cRowsObtained,
                              BOOL          fCanFetchBackwards,
                              void *        pData,
                              DBLENGTH      cbRowSize,
                              DBBINDING *   pBinding,
                              ULONG         cchBuffer,
                              LPWSTR        pwszBuffer )
{
   HRESULT       hr = S_OK;
   CHAR          ch;

   // Let the user know if no rows were fetched.
   if( !cRowsObtained )
      printf("\n*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*\n" \
      "*                                 *\n" \
      "* No rows obtained on this fetch! *\n" \
      "*                                 *\n" \
      "*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*\n");

   // Print navigation options.
   if( fCanFetchBackwards )
      printf("\n[P]revious; [N]ext; [R]estart; ");
   else
      printf("\n[N]ext; [R]estart; ");

   // Print selection options.
   if( cRowsObtained && pwszBuffer && pBinding )
      printf("[0]-[%d] for a row; ", cRowsObtained - 1);

   // User can always quit the program.
   printf("[Q]uit? ");

   // Get the user's input.
   while( TRUE ) {
      // Get a character from the console.
      ch = myGetChar();

      // Look for one of the allowed options; if not found, go
      // back around and wait for another input from the user.

      // If we're looking for a row selection, allow the user to select
      // a row that we fetched, and then copy the data from the requested
      // column into the selection buffer we were passed.
      if( pwszBuffer && pBinding && ch >= '0' && ch < (int)('0' + cRowsObtained) ) {
         // Save the data for the selected row.
         ULONG nSelection = ch - '0';
         _snwprintf_s(pwszBuffer, sizeof(pwszBuffer), cchBuffer, L"%s",
            (WCHAR *)((BYTE *)pData + cbRowSize * nSelection + pBinding->obValue));
         pwszBuffer[cchBuffer] = L'\0';
         hr = S_FALSE;
      }
      // If the provider supports fetching backwards, set *pcRows
      // to -MAX_ROWS. When GetNextRows is called with this value,
      // it will fetch rows backwards from the current position
      // until it fetches MAX_ROWS rows or hits the end of the rowset.
      else if( fCanFetchBackwards && ch == 'p' )
         // Fetch backwards.
         *pcRows = -MAX_ROWS;

      // Set *pcRows so that the next call to GetNextRows fetches
      // MAX_ROWS rows forward from the current position.
      else if( ch == 'n' )
         // Fetch forward
         *pcRows = MAX_ROWS;
      // Call IRowset::RestartPosition, and fetch the first MAX_ROWS
      // rows of the rowset forward from there.
      else if( ch == 'r' ) {
         // RestartPosition
         *pcRows = MAX_ROWS;
         XCHECK_HR(hr = pIRowset->RestartPosition(DB_NULL_HCHAPTER));

         // Restarting a command may return the DB_S_COMMANDREEXECUTED
         // warning. If this happens, we still want the caller to
         // continue to display data, so we will reset the result code.
         // to S_OK.
         hr = S_OK;
      }
      // Quit the program.
      else if( ch == 'q' )
         hr = E_FAIL;

      // Invalid option; go back up and get another character from the
      // user.
      else
         continue;

      // Echo the character and stop waiting for input.
      printf("%c\n", ch);
      break;
   }

CLEANUP:
   return hr;
}

/////////////////////////////////////////////////////////////////
// myDisplayColumnNames
// Summary of Routines
//
// This function takes an IUnknown pointer to a rowset object
// and displays the names of the columns of that rowset.
//
/////////////////////////////////////////////////////////////////
HRESULT myDisplayColumnNames( IUnknown *       pUnkRowset,
                             ULONG *          rgDispSize )
{
   HRESULT          hr;
   IColumnsInfo *   pIColumnsInfo    = NULL;
   DBORDINAL        cColumns;
   DBCOLUMNINFO *   rgColumnInfo     = NULL;
   LPOLESTR         pStringsBuffer   = NULL;
   WCHAR            wszColumn[MAX_DISPLAY_SIZE + 1];
   LPWSTR           pwszColName;
   DBORDINAL        iCol;
   ULONG            cSpaces;
   ULONG            iSpace;

   // Get the IColumnsInfo interface for the rowset.
   // See IColumnsInfo
   XCHECK_HR(hr = pUnkRowset->QueryInterface(
      IID_IColumnsInfo, (void**)&pIColumnsInfo));

   // Get the columns information.
   XCHECK_HR(hr = pIColumnsInfo->GetColumnInfo(
      &cColumns,                            // pcColumns
      &rgColumnInfo,                        // prgColumnInfo
      &pStringsBuffer                       // ppStringBuffer
      ));

   // Display the title of the row index column.
   wprintf(L" Row | ");

   // Display all column names.
   for( iCol = 0; iCol < cColumns; iCol++ ) {
      pwszColName = rgColumnInfo[iCol].pwszName;

      // If the column name is NULL, we'll use a default string.
      if( !pwszColName ) {
         // Is this the bookmark column?
         if( !rgColumnInfo[iCol].iOrdinal )
            pwszColName = L"Bmk";
         else
            pwszColName = L"(null)";
      }

      // Ensure that the name is no longer than MAX_DISPLAY_SIZE.
      wcsncpy_s(wszColumn, _countof(wszColumn), pwszColName, MAX_DISPLAY_SIZE);
      wszColumn[min(rgDispSize[iCol], MAX_DISPLAY_SIZE)] = L'\0';

      // Figure out how many spaces we need to print after
      // this column name.
      cSpaces = min(rgDispSize[iCol], MAX_DISPLAY_SIZE) - wcslen(wszColumn);

      // Print the column name.
      wprintf(L"%s", wszColumn);

      // Now print any spaces necessary to align this column.
      for(iSpace = 0; iSpace < cSpaces; iSpace++ )
         putch(' ');

      // Now end the column with a separator marker if necessary.
      if( iCol < cColumns - 1 )
         wprintf(L" | ");
   }

   // Done with the header, so print a new line.
   wprintf(L"\n");

CLEANUP:
   CoTaskMemFree(rgColumnInfo);
   CoTaskMemFree(pStringsBuffer);
   if( pIColumnsInfo )
      pIColumnsInfo->Release();
   return hr;
}

/////////////////////////////////////////////////////////////////
// myDisplayRow
// Summary of Routines
//
// This function displays the data for a row.
//
/////////////////////////////////////////////////////////////////
HRESULT myDisplayRow(DBCOUNTITEM iRow,
                     DBCOUNTITEM cBindings,
                     DBBINDING * rgBindings,
                     void * pData,
                     ULONG * rgDispSize )
{
   HRESULT               hr = S_OK;
   WCHAR                 wszColumn[MAX_DISPLAY_SIZE + 1];
   DBSTATUS              dwStatus;
   DBLENGTH              ulLength;
   void *                pvValue;
   DBCOUNTITEM           iCol;
   ULONG                 cbRead;
   ISequentialStream *   pISeqStream = NULL;
   ULONG                 cSpaces;
   ULONG                 iSpace;

   // Display the row number.
   wprintf(L" [%d] | ", iRow);

   // For each column that we have bound, display the data.
   for( iCol = 0; iCol < cBindings; iCol++ ) {
      // We have bound status, length, and the data value for all
      // columns, so we know that these can all be used.
      dwStatus   = *(DBSTATUS *)((BYTE *)pData + rgBindings[iCol].obStatus);
      ulLength   = *(*DBLENGTH*)((BYTE *)pData + rgBindings[iCol].obLength);
      pvValue    = (BYTE *)pData + rgBindings[iCol].obValue;

      // Check the status of this column. This decides
      // exactly what will be displayed for the column.
      switch( dwStatus ) {
         // The data is NULL, so don't try to display it.
         case DBSTATUS_S_ISNULL:
            wcscpy_s(wszColumn, sizeof(wszColumn), L"(null)");
            break;

            // The data was fetched, but may have been truncated.
            // Display string data for this column to the user.
         case DBSTATUS_S_TRUNCATED:
         case DBSTATUS_S_OK:
         case DBSTATUS_S_DEFAULT: {
            // We have bound the column either as a Unicode string
            // (DBTYPE_WSTR) or as an ISequentialStream object
            // (DBTYPE_IUNKNOWN), and we have to do different processing
            // for each one of these possibilities.
            switch( rgBindings[iCol].wType )
            {
            case DBTYPE_WSTR:
               {   
                  // Copy the string data.
                  wcsncpy_s(wszColumn, _countof(wszColumn), (WCHAR *)pvValue, MAX_DISPLAY_SIZE);
                  wszColumn[MAX_DISPLAY_SIZE - 1] = L'\0';
                  break;
               }

            case DBTYPE_IUNKNOWN:
               {
                  // We've bound this as an ISequentialStream object,
                  // therefore the data in our buffer is a pointer
                  // to the object's ISequentialStream interface.
                  pISeqStream = *(ISequentialStream**)pvValue;

                  // We call ISequentialStream::Read to read bytes from
                  // the stream blindly into our buffer, simply as a
                  // demonstration of ISequentialStream. To display the
                  // data properly, the native provider type of this
                  // column should be accounted for; it could be
                  // DBTYPE_WSTR, in which case this works, or it could
                  // be DBTYPE_STR or DBTYPE_BYTES, in which case this
                  // won't display the data correctly.
                  CHECK_HR(hr = pISeqStream->Read(
                     wszColumn,                     // pBuffer
                     MAX_DISPLAY_SIZE,              // cBytes
                     &cbRead                        // pcBytesRead
                     ));

                  // Since streams don't provide NULL-termination,
                  // we'll NULL-terminate the resulting string ourselves.
                  wszColumn[cbRead / sizeof(WCHAR)] = L'\0';

                  // Release the stream object, now that we're done.
                  pISeqStream->Release();
                  pISeqStream = NULL;
                  break;
               }
            }
            break;
         }

         // This is an error status, so don't try to display the data.
      default:
         wcscpy_s(wszColumn, sizeof(wszColumn), L"(error status)");
         break;
      }

      // Determine how many spaces we need to add after displaying this
      // data to align it with this column in other rows.
      cSpaces = min(rgDispSize[iCol], MAX_DISPLAY_SIZE) - wcslen(wszColumn);

      // Print the column data.
      wprintf(L"%s", wszColumn);

      // Now print any spaces necessary.
      for(iSpace = 0; iSpace < cSpaces; iSpace++ )
         putch(' ');

      // Now end the column with a separator marker if necessary.
      if( iCol < cBindings - 1 )
         wprintf(L" | ");
   }

CLEANUP:
   if( pISeqStream )
      pISeqStream->Release();

   // Print the row separator.
   wprintf(L"\n");
   return hr;
}

/////////////////////////////////////////////////////////////////
// myFreeBindings
// Summary of Routines
//
//   This function frees a bindings array and any allocated
// structures contained in that array.
//
/////////////////////////////////////////////////////////////////
void myFreeBindings ( DBCOUNTITEM   cBindings, DBBINDING *   rgBindings ) {
   DBCOUNTITEM   iBind;

   // Free any memory used by DBOBJECT structures in the array.
   for( iBind = 0; iBind < cBindings; iBind++ )
      CoTaskMemFree(rgBindings[iBind].pObject);

   // Now free the bindings array itself.
   CoTaskMemFree(rgBindings);
}

/////////////////////////////////////////////////////////////////
// myAddRowsetProperties
// Summary of Routines
//
// This function sets up the given DBPROPSET and DBPROP
// structures, adding two optional properties that describe
// features that we would like to use on the rowset created
// with these properties applied:
// - DBPROP_CANFETCHBACKWARDS -- the rowset should support
//    fetching rows backwards from our current cursor position.
// - DBPROP_IRowsetLocate -- the rowset should support
//    the IRowsetLocate interface and its semantics.
//
/////////////////////////////////////////////////////////////////
void myAddRowsetProperties(DBPROPSET* pPropSet, ULONG cProperties, DBPROP* rgProperties) {
   // Initialize the property set array.
   pPropSet->rgProperties      = rgProperties;
   pPropSet->cProperties       = cProperties;
   pPropSet->guidPropertySet   = DBPROPSET_ROWSET;

   // Add the following two properties (as OPTIONAL) to the property
   // array contained in the property set array in order to request
   // that they be supported by the rowset we will create. Because
   // these are optional, the rowset we obtain may or may not support
   // this functionality. We will check for the functionality that
   // we need once the rowset is created and will modify our behavior
   // appropriately.
   myAddProperty(&rgProperties[0], DBPROP_CANFETCHBACKWARDS);
   myAddProperty(&rgProperties[1], DBPROP_IRowsetLocate);
}

/////////////////////////////////////////////////////////////////
// myUpdateDisplaySize
// Summary of Routines
//
// This function updates the rgDispSize array, keeping the
// maximum of the display size needed for the given data and
// the previous maximum size already in the array.
//
/////////////////////////////////////////////////////////////////
HRESULT myUpdateDisplaySize (DBCOUNTITEM   cBindings,
                             DBBINDING *   rgBindings,
                             void *        pData,
                             ULONG *       rgDispSize )
{
   DBSTATUS      dwStatus;
   ULONG         cchLength;
   DBCOUNTITEM   iCol;

   // Loop through the bindings, comparing the size of each column
   // against the previously found maximum size for that column.
   for( iCol = 0; iCol < cBindings; iCol++ ) {
      dwStatus = *(DBSTATUS *)((BYTE *)pData + rgBindings[iCol].obStatus);
      cchLength = ((*(ULONG *)((BYTE *)pData + rgBindings[iCol].obLength))
         / sizeof(WCHAR));

      // The length that we need to display depends on the status
      // of this column and generally on the data in the column.
      switch( dwStatus )
      {
      case DBSTATUS_S_ISNULL:
         cchLength = 6;                              // "(null)"
         break;

      case DBSTATUS_S_TRUNCATED:
      case DBSTATUS_S_OK:
      case DBSTATUS_S_DEFAULT:
         if( rgBindings[iCol].wType == DBTYPE_IUNKNOWN )
            cchLength = 2 + 8;                      // "0x%08lx"

         // Ensure that the length is at least the minimum
         // display size.
         cchLength = max(cchLength, MIN_DISPLAY_SIZE);
         break;

      default:
         cchLength = 14;                        // "(error status)"
         break;
      }

      if( rgDispSize[iCol] < cchLength )
         rgDispSize[iCol] = cchLength;
   }

   return S_OK;
}

/////////////////////////////////////////////////////////////////
// myFindColumn
// Summary of Routines
//
// Find the index of the column described in pwszName, and return
// S_OK or, if not found, S_FALSE.
//
/////////////////////////////////////////////////////////////////
HRESULT myFindColumn ( IUnknown *       pUnkRowset,
                      LPCWSTR          pwszName,
                      DBORDINAL*       plIndex )
{
   HRESULT          hr;
   IColumnsInfo *   pIColumnsInfo    = NULL;
   DBORDINAL*       cColumns;
   DBCOLUMNINFO *   rgColumnInfo     = NULL;
   OLECHAR *        pStringsBuffer   = NULL;
   DBORDINAL*       iCol;

   // Get the IColumnsInfo interface.
   // See IColumnsInfo
   XCHECK_HR(hr = pUnkRowset->QueryInterface(IID_IColumnsInfo, (void**)&pIColumnsInfo));

   // Get the columns information.
   XCHECK_HR(hr = pIColumnsInfo->GetColumnInfo( &cColumns,   // pcColumns
                                                &rgColumnInfo,   // prgColumnInfo
                                                &pStringsBuffer));   // ppStringBuffer

   // Assume that we'll find the column.
   hr = S_OK;

   // Search for the column we need.
   for( iCol = 0; iCol < cColumns; iCol++ ) {
      // If the column name matches, we've found the column....
      if( rgColumnInfo[iCol].pwszName && !wcscmp(pwszName, rgColumnInfo[iCol].pwszName) ) {
         *plIndex = iCol;
         goto CLEANUP;
      }
   }

   // If we didn't find the column, we'll return S_FALSE.
   hr = S_FALSE;

CLEANUP:
   CoTaskMemFree(rgColumnInfo);
   CoTaskMemFree(pStringsBuffer);
   if( pIColumnsInfo )
      pIColumnsInfo->Release();
   return hr;
}

//---------------------------------------------------------------------------
// Microsoft OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft Corporation.
//
// @doc
//
// @module COMMAND.CPP
//
//---------------------------------------------------------------------------


/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#include "prsample.h"         // Programmer's Reference Sample includes



/////////////////////////////////////////////////////////////////

// myCreateCommand
// Summary of Routines
//
// This function takes an IUnknown pointer on a session object
// and attempts to create a command object using the session's
// IDBCreateCommand interface. Since this interface is optional,
// this may fail.
//
/////////////////////////////////////////////////////////////////
HRESULT myCreateCommand
   (
   IUnknown *           pUnkSession,
   IUnknown **          ppUnkCommand
   )
{
   HRESULT              hr;
   IDBCreateCommand *   pIDBCreateCommand = NULL;
   
   // Attempt to create a command object from the session object
   // See IDBCreateCommand
   XCHECK_HR(hr = pUnkSession->QueryInterface(
            IID_IDBCreateCommand, (void**)&pIDBCreateCommand));
   XCHECK_HR(hr = pIDBCreateCommand->CreateCommand(
            NULL,                                       // pUnkOuter
            IID_ICommand,                               // riid
            ppUnkCommand                                // ppCommand
            ));

CLEANUP:
   if( pIDBCreateCommand )
      pIDBCreateCommand->Release();
   return hr;
}

/////////////////////////////////////////////////////////////////

// myExecuteCommand
// Summary of Routines
//
// This function takes an IUnknown pointer on a command object
// and performs the following steps to create a new rowset
// object:
// - sets the given properties on the command object; these
//    properties will be applied by the provider to any rowset
//    created by this command.
// - sets the given command text for the command.
// - executes the command to create a new rowset object.
//
/////////////////////////////////////////////////////////////////
HRESULT myExecuteCommand
   (
   IUnknown *             pUnkCommand,
   WCHAR *                pwszCommandText,
   ULONG                  cPropSets,
   DBPROPSET*             rgPropSets,
   IUnknown **            ppUnkRowset
   )
{
   HRESULT                hr;
   ICommandText *           pICommandText         = NULL;
   ICommandProperties *   pICommandProperties   = NULL;

   // Set the properties on the command object.
   // See ICommandProperties
   XCHECK_HR(hr = pUnkCommand->QueryInterface(
            IID_ICommandProperties, (void**)&pICommandProperties));
   XCHECK_HR(hr = pICommandProperties->SetProperties(cPropSets, rgPropSets));

   // Set the text for this command, using the default command text
   // dialect. All providers that support commands must support this
   // dialect, and providers that support SQL must be able to recognize
   // an SQL command as SQL when this dialect is specified.
   // See ICommandText
   XCHECK_HR(hr = pUnkCommand->QueryInterface(
            IID_ICommandText, (void**)&pICommandText));
   XCHECK_HR(hr = pICommandText->SetCommandText(
            DBGUID_DEFAULT,                        // guidDialect
            pwszCommandText                        // pwszCommandText
            ));

   // And execute the command. Note that the user could have
   // entered a non-row returning command, so we will check for
   // that and return failure to prevent the display of the
   // nonexistent rowset by the caller.
   XCHECK_HR(hr = pICommandText->Execute(   
            NULL,                              // pUnkOuter
            IID_IRowset,                       // riid
            NULL,                              // pParams
            NULL,                              // pcRowsAffected
            ppUnkRowset                        // ppRowset
            ));
      
   if( !*ppUnkRowset )
   {
      printf("\nThe command executed successfully, but did not " \
             "return a rowset.\nNo rowset will be displayed.\n");
      hr = E_FAIL;
   }


CLEANUP:
   if( pICommandText )
      pICommandText->Release();
   if( pICommandProperties )
      pICommandProperties->Release();
   return hr;
}

//---------------------------------------------------------------------------
// Microsoft OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft Corporation.
//
// @doc
//
// @module ERROR.CPP
//
//---------------------------------------------------------------------------


////////////////////////////////////////////////////////////////////////
// Includes
//
////////////////////////////////////////////////////////////////////////
#include "prsample.h"         // Programmer's Reference Sample includes


////////////////////////////////////////////////////////////////////////

// myHandleResult
// Summary of Routines
//
// This function is called as part of the XCHECK_HR macro; it takes an
// HRESULT, which is returned by the method called in the XCHECK_HR
// macro, and the file and line number where the method call was made.
// If the method call failed, this function attempts to get and display
// the extended error information for the call from the IErrorInfo,
// IErrorRecords, and ISQLErrorInfo interfaces.
//
////////////////////////////////////////////////////////////////////////
HRESULT myHandleResult
   (
   HRESULT           hrReturned,
   LPCWSTR           pwszFile,
   ULONG             ulLine
   )
{
   HRESULT           hr;
   IErrorInfo *      pIErrorInfo      = NULL;
   IErrorRecords *   pIErrorRecords   = NULL;
   ULONG             cRecords;
   ULONG             iErr;

   // If the method called as part of the XCHECK_HR macro failed,
   // we will attempt to get extended error information for the call.
   if( FAILED(hrReturned) )
   {
      // Obtain the current error object, if any, by using the
      // Automation GetErrorInfo function, which will give
      // us back an IErrorInfo interface pointer if successful.
      hr = GetErrorInfo(0, &pIErrorInfo);

      // We've got the IErrorInfo interface pointer on the error object.
      if( SUCCEEDED(hr) && pIErrorInfo )
      {
         // OLE DB extends the Automation error model by allowing
         // error objects to support the IErrorRecords interface. This
         // interface can expose information on multiple errors.
         // See IErrorRecords
         hr = pIErrorInfo->QueryInterface(IID_IErrorRecords,
                  (void**)&pIErrorRecords);
         if( SUCCEEDED(hr) )
         {
            // Get the count of error records from the object.
            CHECK_HR(hr = pIErrorRecords->GetRecordCount(&cRecords));
            
            // Loop through the set of error records, and
            // display the error information for each one.
            for( iErr = 0; iErr < cRecords; iErr++ )
            {
               myDisplayErrorRecord(hrReturned, iErr, pIErrorRecords,
                  pwszFile, ulLine);
            }
         }
         // The object didn't support IErrorRecords; display
         // the error information for this single error.
         else
         {
            myDisplayErrorRecord(hrReturned, pIErrorInfo, pwszFile, ulLine);
         }
      }
      // There was no error object, so just display the HRESULT
      // to the user.
      else
      {
         wprintf(L"\nNo Error Info posted; HResult: 0x%08x\n"
            L"File: %s, Line: %d\n", hrReturned, pwszFile, ulLine);
      }
   }

CLEANUP:
   if( pIErrorInfo )
      pIErrorInfo->Release();
   if( pIErrorRecords )
      pIErrorRecords->Release();
   return hrReturned;
}


////////////////////////////////////////////////////////////////////////

// myDisplayErrorRecord
// Summary of Routines
//
// This function displays the error information for a single error
// record, including information from ISQLErrorInfo, if supported.
//
////////////////////////////////////////////////////////////////////////
HRESULT myDisplayErrorRecord
   (
   HRESULT           hrReturned,
   ULONG             iRecord,
   IErrorRecords *   pIErrorRecords,
   LPCWSTR           pwszFile,
   ULONG             ulLine
   )
{
   HRESULT           hr;
   IErrorInfo *      pIErrorInfo       = NULL;
   BSTR              bstrDescription   = NULL;
   BSTR              bstrSource        = NULL;
   BSTR              bstrSQLInfo       = NULL;

   static LCID       lcid              = GetUserDefaultLCID();

   LONG              lNativeError      = 0;
   ERRORINFO         ErrorInfo;

   // Get the IErrorInfo interface pointer for this error record.
   CHECK_HR(hr = pIErrorRecords->GetErrorInfo(iRecord, lcid, &pIErrorInfo));
   
   // Get the description of this error.
   CHECK_HR(hr = pIErrorInfo->GetDescription(&bstrDescription));
      
   // Get the source of this error.
   CHECK_HR(hr = pIErrorInfo->GetSource(&bstrSource));

   // Get the basic error information for this record.
   CHECK_HR(hr = pIErrorRecords->GetBasicErrorInfo(iRecord, &ErrorInfo));

   // If the error object supports ISQLErrorInfo, get this information.
   myGetSqlErrorInfo(iRecord, pIErrorRecords, &bstrSQLInfo, &lNativeError);

   // Display the error information to the user.
   if( bstrSQLInfo )
   {
      wprintf(L"\nErrorRecord:  HResult: 0x%08x\nDescription: %s\n"
         L"SQLErrorInfo: %s\nSource: %s\nFile: %s, Line: %d\n",
         ErrorInfo.hrError,
         bstrDescription,
         bstrSQLInfo,
         bstrSource,
         pwszFile,
         ulLine);
   }
   else
   {
      wprintf(L"\nErrorRecord:  HResult: 0x%08x\nDescription: %s\n"
         L"Source: %s\nFile: %s, Line: %d\n",
         ErrorInfo.hrError,
         bstrDescription,
         bstrSource,
         pwszFile,
         ulLine);
   }

CLEANUP:
   if( pIErrorInfo )
      pIErrorInfo->Release();
   SysFreeString(bstrDescription);
   SysFreeString(bstrSource);
   SysFreeString(bstrSQLInfo);
   return hr;
}


////////////////////////////////////////////////////////////////////////

// myDisplayErrorInfo
// Summary of Routines
//
// This function displays basic error information for an error object
// that doesn't support the IErrorRecords interface.
//
////////////////////////////////////////////////////////////////////////
HRESULT myDisplayErrorInfo
   (
   HRESULT        hrReturned,
   IErrorInfo *   pIErrorInfo,
   LPCWSTR        pwszFile,
   ULONG          ulLine
   )
{
   HRESULT        hr;
   BSTR           bstrDescription   = NULL;
   BSTR           bstrSource        = NULL;

   // Get the description of the error.
   CHECK_HR(hr = pIErrorInfo->GetDescription(&bstrDescription));
      
   // Get the source of the error -- this will be the window title.
   CHECK_HR(hr = pIErrorInfo->GetSource(&bstrSource));

   // Display this error information.
   wprintf(L"\nErrorInfo:  HResult: 0x%08x, Description: %s\nSource:
            %s\n" L"File: %s, Line: %d\n",
            hrReturned,
            bstrDescription,
            bstrSource,
            pwszFile,
            ulLine);

CLEANUP:
   SysFreeString(bstrDescription);
   SysFreeString(bstrSource);
   return hr;
}


////////////////////////////////////////////////////////////////////////

// myGetSqlErrorInfo
// Summary of Routines
//
// If the error object supports ISQLErrorInfo, get the SQL error
// string and native error code for this error.
//
////////////////////////////////////////////////////////////////////////
HRESULT myGetSqlErrorInfo
   (
   ULONG             iRecord,
   IErrorRecords *   pIErrorRecords,
   BSTR *            pBstr,
   LONG *            plNativeError
   )
{
   HRESULT           hr;
   ISQLErrorInfo *   pISQLErrorInfo   = NULL;
   LONG              lNativeError     = 0;

   // Attempt to get the ISQLErrorInfo interface for this error
   // record through GetCustomErrorObject. Note that ISQLErrorInfo
   // is not mandatory, so failure is acceptable here.
   // See ISQLErrorInfo
   CHECK_HR(hr = pIErrorRecords->GetCustomErrorObject(
            iRecord,                               // iRecord
            IID_ISQLErrorInfo,                     // riid
            (IUnknown**)&pISQLErrorInfo            // ppISQLErrorInfo
            ));

   // If we obtained the ISQLErrorInfo interface, get the SQL
   // error string and native error code for this error.
   if( pISQLErrorInfo )
      hr = pISQLErrorInfo->GetSQLInfo(pBstr, &lNativeError);

CLEANUP:
   if( plNativeError )
      *plNativeError = lNativeError;
   if( pISQLErrorInfo )
      pISQLErrorInfo->Release();
   return hr;
}

Comments

This topic is a part of: