Partilhar via


Usando tipos de valor grande

Antes do SQL Server 2005, trabalhar com tipos de dados de valor grande exigia procedimentos especiais. Os tipos de dados de valor grande são aqueles que excedem o tamanho de linha máximo de 8 KB. O SQL Server 2005 introduziu um especificador max para os tipos de dados varchar, nvarchar e varbinary a fim de permitir o armazenamento de valores de até 2^31 -1 bytes. As colunas de tabela e variáveis Transact-SQL podem especificar os tipos de dados varchar (max), nvarchar (max) ou varbinary (max).

ObservaçãoObservação

Os tipos de dados de valor grande podem ter entre 1 e 8 KB ou podem ser especificados como ilimitado.

Antes, apenas os tipos de dados do SQL Server como text, ntext e image podiam ter tais comprimentos. O especificador max para varchar, nvarchar e varbinary tornou esses tipos de dados redundantes. No entanto, pelo fato de esses tipos de dados longos ainda estarem disponíveis, a maioria das interfaces para os componentes de dados OLE DB e ODBC permanecerá a mesma. Para fins de compatibilidade com versões anteriores, o sinalizador DBCOLUMNFLAGS_ISLONG no provedor OLE DB do SQL Server Native Client e o SQL_LONGVARCHAR no driver ODBC do SQL Server Native Client permanecem em uso. Os provedores e drivers criados no SQL Server 2005 e posterior continuam a usar essas condições para os novos tipos quando definidos como comprimento máximo ilimitado.

ObservaçãoObservação

Você também pode especificar os tipos de dados varchar(max), nvarchar(max) e varbinary(max) como tipos de parâmetro de entrada e saída de procedimentos armazenados, tipos de retorno de função ou nas funções CAST e CONVERT.

Provedor OLE DB do SQL Server Native Client

O provedor OLE DB do SQL Server Native Client expõe os tipos varchar (max), varbinary (max) e nvarchar (max) como DBTYPE_STR, DBTYPE_BYTES e DBTYPE_WSTR, respectivamente.

Os tipos de dados varchar(max), varbinary(max) e nvarchar(max) nas colunas com o tamanho max definido como ilimitado são representados como ISLONG por meio de conjuntos de linhas e interfaces principais do esquema OLE DB que retornam tipos de dados de coluna.

A implementação do comando de objeto IAccessor foi alterada para permitir a associação como DBTYPE_IUNKNOWN. Se o consumidor especificar DBTYPE_IUNKNOWN e definir pObject como nulo, o provedor retornará a interface ISequentialStream para o consumidor de modo que ele possa transmitir os dados varchar(max), nvarchar(max) ou varbinary(max) usando variáveis de saída.

Os valores de parâmetro de saída transmitidos são retornados após as linhas de resultado. Se o aplicativo tentar avançar para o próximo conjunto de resultados chamando IMultipleResults::GetResult sem consumir todos os valores de parâmetro de saída retornados, DB_E_OBJECTOPEN será retornado.

A fim de dar suporte ao recurso de streaming, o provedor OLE DB do SQL Server Native Client exige que os parâmetros de comprimento variável sejam acessados em ordem sequencial. Isso significa que DBPROP_ACCESSORDER deve ser definido como DBPROPVAL_AO_SEQUENTIALSTORAGEOBJECTS ou DBPROPVAL_AO_SEQUENTIAL toda vez que as colunas varchar(max), nvarchchar(max) ou varbinary(max) ou os parâmetros de saída estiverem associados a DBTYPE_IUNKNOWN. As chamadas para IRowset::GetData resultarão em falha com DBSTATUS_E_UNAVAILABLE se essa restrição de ordem de acesso não for atendida. Essa restrição não se aplica quando não há associações de saída usando DBTYPE_IUNKNOWN.

O provedor OLE DB do SQL Server Native Client também dá suporte aos parâmetros de saída de associação, como DBTYPE_IUNKNOWN, para tipos de dados de valor grande a fim de facilitar cenários onde um procedimento armazenado retorne ao cliente tipos de valor grande como valores de retorno expostos como DBTYPE_IUNKNOWN.

Para trabalhar com esses tipos, um aplicativo tem as seguintes opções:

  • Associar como um tipo que suporta associações com o tipo de base da coluna (eg for nvarchar(max), associar como um tipo que possa ser vinculado a nvarchar). Se o buffer não for grande o suficiente, ocorrerá truncamento, exatamente como para o tipo de base, embora os valores maiores estejam disponíveis agora.

  • Associar como um tipo que suporta conversões com o tipo de base da coluna e também especifica DBTYPE_BYREF.

  • Associar como DBTYPE_IUNKNOWN e usar streaming.

Ao informar o tamanho máximo de uma coluna, o provedor OLE DB do SQL Server Native Client usará:

  • O tamanho máximo definido, por exemplo 2000, para uma coluna varchar(2000) ou

  • O valor "ilimitado" que no caso de uma coluna varchar (max) é igual a ~ 0. Esse valor é definido para a propriedade de metadados DBCOLUMN_COLUMNSIZE.

As regras padrão de conversão serão aplicadas a uma coluna varchar(max), o que significa que qualquer conversão válida para uma coluna varchar(2000) também será válida para uma coluna varchar(max). O mesmo vale para as colunas nvarchar (max) e varbinary (max).

Ao recuperar tipos de valor grande, a abordagem mais eficiente é associar como DBTYPE_IUNKNOWN e definir a propriedade do conjunto de linhas DBPROP_ACCESSORDER como DBPROPVAL_AO_SEQUENTIALSTORAGEOBJECTS. Esse procedimento fará com que o valor seja transmitido diretamente da rede sem utilização de buffer intermediário, como no seguinte exemplo:

#define UNICODE
#define _UNICODE
#define DBINITCONSTANTS
#define INITGUID
#define OLEDBVER 0x0250  // To include the correct interfaces.

#include <stdio.h>
#include <tchar.h>
#include <stddef.h>
#include <iostream>

using std::cout;
using std::endl;

#include <windows.h>

#include <oledb.h>
#include "sqlncli.h"
#include <oledberr.h>

#define CHKHR_GOTO(hr, errMsg, Label) \
   if (FAILED(hr)) \
   { \
      cout << errMsg << endl; \
      goto Label; \
   }

#define MAX_COL_SIZE 8000

// 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)

HRESULT InitializeAndEstablishConnection(IDBInitialize** ppIDBInitialize);
void UnInitializeConnection(IDBInitialize* pIDBInitialize);
HRESULT CreateAndSetCommand(IDBInitialize* pIDBInitialize, ICommandText** ppICommandText);
HRESULT ProcessResultSet(IRowset* pIRowset);

void DisplayTime()
{
   SYSTEMTIME st;
   GetSystemTime(&st);
   cout<< st.wHour << ":" << st.wMinute << ":" << st.wSecond << "." << st.wMilliseconds << endl;
}

void main()
{
   HRESULT hr;
   IDBInitialize* pIDBInitialize = NULL;
   ICommandText* pICommandText = NULL;
   IMultipleResults* pIMultipleResults = NULL;
   IRowset* pIRowset = NULL;

   hr = InitializeAndEstablishConnection(&pIDBInitialize);
   CHKHR_GOTO(hr, L"Failed to establish connection.", _ExitMain);

   hr = CreateAndSetCommand(pIDBInitialize, &pICommandText);
   CHKHR_GOTO(hr, L"Failed to set up command object.", _ExitMain);

   DisplayTime();

   hr = pICommandText->Execute(NULL, 
      IID_IMultipleResults, 
      NULL, 
      NULL, 
     (IUnknown **) &pIMultipleResults);

   CHKHR_GOTO(hr, L"Failed to execute command.", _ExitMain);

   while (1)
   {
      hr = pIMultipleResults->GetResult(
         NULL, 
         DBRESULTFLAG_DEFAULT, 
         IID_IRowset, 
         NULL, 
         (IUnknown**)&pIRowset);

   CHKHR_GOTO(hr, L"Failed to obtain a results from MR object.", _ExitMain);

   if (hr == DB_S_NORESULT)
      break;

      if (pIRowset)
      {
         hr = ProcessResultSet(pIRowset); 
         CHKHR_GOTO(hr, L"Failed to process the current Rowset.", _ExitMain);

         pIRowset->Release();
         pIRowset = NULL;
      }
   }

   DisplayTime();

_ExitMain:

   if (pIRowset)
   {
      pIRowset->Release();
      pIRowset = NULL;
   }

   if (pIMultipleResults)
   {
      pIMultipleResults->Release();
      pIMultipleResults = NULL;
   }

   if (pICommandText)
   {
      pICommandText->Release();
      pICommandText = NULL;
   }

   UnInitializeConnection(pIDBInitialize);
   return;
};

HRESULT InitializeAndEstablishConnection(IDBInitialize** ppIDBInitialize)
{
   HRESULT hr;
   IDBInitialize* pIDBInitialize = NULL;
   IDBProperties* pIDBProperties = NULL;

   const int NUM_DBINIT_PROPS = 3;
   const wchar_t* const g_wszServer = L".";
   const wchar_t* const g_wszCatalog = L"AdventureWorks2008R2";
   const wchar_t* const g_wszSecurity = L"SSPI";

   DBPROPSET rgdbPropSetInit[1];
   DBPROP rgdbPropInit [NUM_DBINIT_PROPS];

   *ppIDBInitialize = NULL;
   hr = CoInitialize(NULL);
   CHKHR_GOTO(hr, L"Failed to initialize COM.", _ExitInitialize);

   hr = CoCreateInstance(CLSID_SQLNCLI10, 
      NULL, 
      CLSCTX_INPROC_SERVER,
      IID_IDBInitialize, 
      (void**)&pIDBInitialize);

   CHKHR_GOTO(hr, L"Failed to create SQLNCLI10 DataSource object.", _ExitInitialize);

   for(int idxProp = 0; idxProp < NUM_DBINIT_PROPS; idxProp++) 
   {
      VariantInit(&rgdbPropInit[idxProp].vValue);
   }

   rgdbPropInit[0].dwPropertyID = DBPROP_INIT_DATASOURCE;
   rgdbPropInit[0].vValue.vt = VT_BSTR;
   rgdbPropInit[0].vValue.bstrVal= SysAllocString(g_wszServer);
   rgdbPropInit[0].dwOptions = DBPROPOPTIONS_REQUIRED;
   rgdbPropInit[0].colid = DB_NULLID;

   if (rgdbPropInit[0].vValue.bstrVal == NULL)
   {
      hr = E_OUTOFMEMORY;
      goto _ExitInitialize;
   }

   rgdbPropInit[1].dwPropertyID = DBPROP_INIT_CATALOG;
   rgdbPropInit[1].vValue.vt = VT_BSTR;
   rgdbPropInit[1].vValue.bstrVal= SysAllocString(g_wszCatalog);
   rgdbPropInit[1].dwOptions = DBPROPOPTIONS_REQUIRED;
   rgdbPropInit[1].colid = DB_NULLID;

   if (rgdbPropInit[1].vValue.bstrVal == NULL)
   {
      hr = E_OUTOFMEMORY;
      goto _ExitInitialize;
   }

   rgdbPropInit[2].dwPropertyID = DBPROP_AUTH_INTEGRATED;
   rgdbPropInit[2].vValue.vt = VT_BSTR;
   rgdbPropInit[2].vValue.bstrVal= SysAllocString(g_wszSecurity);
   rgdbPropInit[2].dwOptions = DBPROPOPTIONS_REQUIRED;
   rgdbPropInit[2].colid = DB_NULLID;

   if (rgdbPropInit[2].vValue.bstrVal == NULL)
   {
      hr = E_OUTOFMEMORY;
      goto _ExitInitialize;
   }

   rgdbPropSetInit[0].guidPropertySet = DBPROPSET_DBINIT;
   rgdbPropSetInit[0].cProperties = NUM_DBINIT_PROPS;
   rgdbPropSetInit[0].rgProperties = rgdbPropInit;

   hr = pIDBInitialize->QueryInterface(IID_IDBProperties, (void **)&pIDBProperties);
   CHKHR_GOTO(hr, L"Failed to QI DataSource object for IDBProperties.", _ExitInitialize);

   hr = pIDBProperties->SetProperties(1, rgdbPropSetInit); 
   CHKHR_GOTO(hr, L"Failed to set DataSource object Properties.", _ExitInitialize);

   pIDBProperties->Release();
   pIDBProperties = NULL;

   hr = pIDBInitialize->Initialize();
   CHKHR_GOTO(hr, L"Failed to establish connection with the server.", _ExitInitialize);

_ExitInitialize:

   if (pIDBProperties)
   {
      pIDBProperties->Release();
      pIDBProperties = NULL;
   }

   if (FAILED(hr))
   {
      if (pIDBInitialize)
      {
         pIDBInitialize->Release();
         pIDBInitialize = NULL;
      }
   }

   *ppIDBInitialize = pIDBInitialize;
   return hr;
}

void UnInitializeConnection(IDBInitialize* pIDBInitialize)
{
   if (pIDBInitialize)
   {
      pIDBInitialize->Uninitialize();
      pIDBInitialize->Release();
      pIDBInitialize = NULL;
   }
   CoUninitialize();
}

HRESULT CreateAndSetCommand(IDBInitialize* pIDBInitialize, ICommandText** ppICommandText)
{
   HRESULT hr;
   IDBCreateSession* pIDBCreateSession = NULL;
   IDBCreateCommand* pIDBCreateCommand = NULL;
   ICommandText* pICommandText = NULL;
   ICommandProperties* pICommandProperties = NULL;
   DBPROPSET rgCmdPropSet[1];
   DBPROP rgCmdProperties[1];

const wchar_t* const g_wCmdString = L"declare @x xml, @y nvarchar(max); select @x = (SELECT * FROM Sales.SalesOrderHeader FOR XML AUTO); select @x;";

   *ppICommandText = NULL;

   if (!pIDBInitialize)
   {
      hr = E_FAIL;
      goto _ExitCreateAndSetCommand;
   }

   hr = pIDBInitialize->QueryInterface(IID_IDBCreateSession, (void**) &pIDBCreateSession);
   CHKHR_GOTO(hr, L"Failed to obtain IDBCreateSession interface from DSO.", _ExitCreateAndSetCommand);

   hr = pIDBCreateSession->CreateSession(
      NULL, 
      IID_IDBCreateCommand, 
      (IUnknown**) &pIDBCreateCommand);

   CHKHR_GOTO(hr, L"Failed to Create a Session for command execution.", _ExitCreateAndSetCommand);

   hr = pIDBCreateCommand->CreateCommand(
      NULL, 
      IID_ICommandText, 
      (IUnknown**)&pICommandText);

   CHKHR_GOTO(hr, L"Failed to Create a Command object.", _ExitCreateAndSetCommand);

   hr = pICommandText->SetCommandText(DBGUID_DBSQL, g_wCmdString);
   CHKHR_GOTO(hr, L"Failed to Set Command Text.", _ExitCreateAndSetCommand);

   hr = pICommandText->QueryInterface(IID_ICommandProperties, (void**) &pICommandProperties);
   CHKHR_GOTO(hr, L"Failed to obtain ICommandProperties interface from the command object.", _ExitCreateAndSetCommand);

   rgCmdProperties[0].dwPropertyID = DBPROP_ACCESSORDER;
   rgCmdProperties[0].vValue.vt = VT_I4;
   rgCmdProperties[0].vValue.lVal = DBPROPVAL_AO_SEQUENTIAL;
   rgCmdProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED;
   rgCmdProperties[0].colid = DB_NULLID;

   rgCmdPropSet[0].guidPropertySet = DBPROPSET_ROWSET;
   rgCmdPropSet[0].cProperties = 1;
   rgCmdPropSet[0].rgProperties = rgCmdProperties;

   hr = pICommandProperties->SetProperties(1, rgCmdPropSet); 
   CHKHR_GOTO(hr, L"Failed to Set Command object Properties.", _ExitCreateAndSetCommand);

_ExitCreateAndSetCommand:

   if (pICommandProperties)
   {
      pICommandProperties->Release();
      pICommandProperties = NULL;
   }

   if (pIDBCreateCommand)
   {
      pIDBCreateCommand->Release();
      pIDBCreateCommand = NULL;
   }

   if (pIDBCreateSession)
   {
      pIDBCreateSession->Release();
      pIDBCreateSession = NULL;
   }

   if (FAILED(hr))
   {
      if (pICommandText)
      {
         pICommandText->Release();
         pICommandText = NULL;
      }
   }

   *ppICommandText = pICommandText;
   return hr;
}

HRESULT ProcessResultSet(IRowset* pIRowset)
{
   HRESULT hr;

   IColumnsInfo* pIColumnsInfo = NULL;
   DBCOLUMNINFO* pDBColumnInfo = NULL;
   ULONG lNumCols = 0;
   wchar_t* pStringsBuffer = NULL;

   DBBINDING* pBindings = NULL;
   DBOBJECT dbobj;
   ULONG idxBinding;
   IAccessor* pIAccessor = NULL;
   HACCESSOR hAccessor = DB_NULL_HACCESSOR;
   HROW hRows[1] = {DB_NULL_HROW};
   HROW* pRow = &hRows[0];
   BYTE* pBuffer = NULL;

   ULONG lNumRowsRetrieved;
   DBLENGTH dwOffset = 0;

   hr = pIRowset->QueryInterface(IID_IColumnsInfo, (void **)&pIColumnsInfo);
   CHKHR_GOTO(hr, L"Failed to QI Rowset for IColumnsInfo.", _ExitProcessResultSet);

   hr = pIColumnsInfo->GetColumnInfo(&lNumCols, &pDBColumnInfo, &pStringsBuffer);
   CHKHR_GOTO(hr, L"Failed to obtain Column Information.", _ExitProcessResultSet);

   pBindings = new DBBINDING[lNumCols];

   if (!pBindings)
   {
      hr = E_OUTOFMEMORY;
      goto _ExitProcessResultSet;
   }

   memset(pBindings, 0, sizeof(DBBINDING) * lNumCols);

   dbobj.dwFlags = STGM_READ;
   dbobj.iid = IID_ISequentialStream;

   for (idxBinding = 0; idxBinding < lNumCols; idxBinding++) 
   {
      pBindings[idxBinding].iOrdinal = idxBinding + 1;
      pBindings[idxBinding].obStatus = dwOffset;
      pBindings[idxBinding].obLength = dwOffset + sizeof(DBSTATUS);
      pBindings[idxBinding].obValue = dwOffset + sizeof(DBSTATUS) + sizeof(DBLENGTH);

      pBindings[idxBinding].pTypeInfo = NULL;
      pBindings[idxBinding].pBindExt = NULL;
      pBindings[idxBinding].dwPart = DBPART_VALUE | DBPART_LENGTH | DBPART_STATUS;
      pBindings[idxBinding].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
      pBindings[idxBinding].eParamIO = DBPARAMIO_NOTPARAM;
      pBindings[idxBinding].bPrecision = pDBColumnInfo[idxBinding].bPrecision;
      pBindings[idxBinding].bScale = pDBColumnInfo[idxBinding].bScale;

      pBindings[idxBinding].cbMaxLen = 0;
      pBindings[idxBinding].wType = DBTYPE_WSTR;

   // 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( pDBColumnInfo[idxBinding].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.

      pBindings[idxBinding].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.

      pBindings[idxBinding].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.

      pBindings[idxBinding].cbMaxLen = (pDBColumnInfo[idxBinding].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.

      pBindings[idxBinding].cbMaxLen = (pDBColumnInfo[idxBinding].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).
      pBindings[idxBinding].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(pDBColumnInfo[idxBinding].dwFlags & DBCOLUMNFLAGS_ISLONG)
   {
      pBindings[idxBinding].wType = DBTYPE_IUNKNOWN;

      pBindings[idxBinding].cbMaxLen = sizeof(ISequentialStream*);

      pBindings[idxBinding].pObject = (DBOBJECT *)CoTaskMemAlloc(sizeof(DBOBJECT));

      if (!pBindings[idxBinding].pObject)
      {
         hr = E_OUTOFMEMORY;
         goto _ExitProcessResultSet;
      }

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

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

      // Ensure that the bound maximum length is no more than the
      // maximum column size in bytes that we've defined.
      pBindings[idxBinding].cbMaxLen = min(pBindings[idxBinding].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 = pBindings[idxBinding].cbMaxLen + pBindings[idxBinding].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);
   }

   hr = pIRowset->QueryInterface(IID_IAccessor, (void **) &pIAccessor);
   CHKHR_GOTO(hr, L"Failed to obtain Accessor interface", _ExitProcessResultSet);

   hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA,
      lNumCols,
      pBindings,
      0,
      &hAccessor,
      NULL);

   CHKHR_GOTO(hr, L"Failed to create Accessor", _ExitProcessResultSet);
   for (idxBinding = 0; idxBinding < lNumCols; idxBinding++) 
   {
      cout << pDBColumnInfo[idxBinding].pwszName << endl;
   }

   lNumRowsRetrieved = 0;

   hr = pIRowset->GetNextRows(
      NULL,
      0,
      1,
      &lNumRowsRetrieved,
      &pRow);

   CHKHR_GOTO(hr, L"Failed to fetch a row from the rowset", _ExitProcessResultSet);

   pBuffer = new BYTE[sizeof(DBSTATUS) + sizeof(DBLENGTH) + sizeof(IUnknown*)];

   if (!pBuffer)
   {
      hr = E_OUTOFMEMORY;
      goto _ExitProcessResultSet;
   }

   while(lNumRowsRetrieved && hr != DB_S_ENDOFROWSET) 
   {
      memset(pBuffer, 0, sizeof(DBSTATUS) + sizeof(DBLENGTH) + sizeof(IUnknown*));

      hr = pIRowset->GetData(hRows[0], hAccessor, pBuffer);
      CHKHR_GOTO(hr, L"Failed to obtain row data", _ExitProcessResultSet);

      for (idxBinding = 0; idxBinding < lNumCols; idxBinding++)
      {
         if (pBindings[idxBinding].wType == DBTYPE_IUNKNOWN)
         {
            BYTE pbBuff[3000];
            ULONG cbNeeded = sizeof(pbBuff)/sizeof(BYTE);
            ULONG cbRead;
            ULONG cbReadTotal = 0;
            ISequentialStream* pISequentialStream = NULL;

            IUnknown* pIUnknown = *((IUnknown**)(pBuffer + pBindings[idxBinding].obValue));
            pIUnknown->QueryInterface(IID_ISequentialStream, (void**)&pISequentialStream);

            do
            {
               hr = pISequentialStream->Read(pbBuff, cbNeeded, &cbRead);
               cbReadTotal += cbRead;
            }
            while (SUCCEEDED(hr) && hr != S_FALSE && cbRead == cbNeeded);

               cout << "Total Bytes Read: " << cbReadTotal << endl;

               pISequentialStream->Release();
               pISequentialStream = NULL;
               pIUnknown->Release();
               pIUnknown = NULL;
            }
         }

         pIRowset->ReleaseRows(1, pRow, NULL, NULL, NULL);

         hr = pIRowset->GetNextRows(NULL,
            0,
            1,
            &lNumRowsRetrieved,
            &pRow);

         CHKHR_GOTO(hr, L"Failed to fetch a row from the rowset.", _ExitProcessResultSet);
   }

_ExitProcessResultSet:

   pIRowset->ReleaseRows(1, pRow, NULL, NULL, NULL);
   delete [] pBuffer;

   if (pIAccessor)
   {
      if (hAccessor != DB_NULL_HACCESSOR)
      {
         pIAccessor->ReleaseAccessor(hAccessor, NULL);
      }

      pIAccessor->Release();
      pIAccessor = NULL;
   }

   if (pBindings)
   {
      for (idxBinding = 0; idxBinding < lNumCols; idxBinding++)
      {
         if (pBindings[idxBinding].pObject)
         CoTaskMemFree(pBindings[idxBinding].pObject);
      }
   }

   delete [] pBindings;

   CoTaskMemFree(pDBColumnInfo);
   CoTaskMemFree(pStringsBuffer);

   if (pIColumnsInfo)
   {
      pIColumnsInfo->Release();
      pIColumnsInfo = NULL;
   }

   return hr;
}

Para obter mais informações sobre como o provedor OLE DB do SQL Server Native Client expõe tipos de dados de valor grande, consulte BLOBs e objetos OLE.

Driver ODBC do SQL Server Native Client

O driver ODBC do SQL Server Native Client expõe os tipos varchar(max), varbinary(max) e nvarchar(max) como SQL_VARCHAR, SQL_VARBINARY, e SQL_WVARCHAR nas funções API ODBC que aceitam ou retornam tipos de dados SQL ODBC.

Ao informar o tamanho máximo de uma coluna, o driver usará:

  • O tamanho máximo definido, por exemplo 2000, para uma coluna varchar(2000) ou

  • O valor "ilimitado" que no caso de uma coluna varchar (max) é igual a 0.

As regras padrão de conversão são aplicadas a uma coluna varchar(max), o que significa que qualquer conversão válida para uma coluna varchar(2000) também será válida para uma coluna varchar(max). O mesmo vale para as colunas nvarchar (max) e varbinary (max).

A seguir, encontra-se uma lista das funções API ODBC que foram aprimoradas para aceitar tipos de dados de valor grande: