Поделиться через


Использование типов больших значений

До выхода SQL Server 2005 работа с типами больших значений требовала особой обработки. Типы больших значений превосходят максимальный размер строки 8КБ. В SQL Server 2005 введен спецификатор max для типов данных varchar, nvarchar и varbinary, чтобы разрешить хранение значений до 2^31 -1 байт. Столбцы таблицы и переменные Transact-SQL могут задавать типы данных varchar(max), nvarchar(max) и varbinary(max).

ПримечаниеПримечание

Типы больших значений могут иметь максимальный размер от 1 до 8 КБ, или они могут быть заданы как неограниченные.

Ранее только типы данных SQL Servertext, ntext и image могли достигать такой длины. Спецификатор max для varchar, nvarchar и varbinary делали эти типы данных излишними. Однако поскольку длинные типы данных до сих пор доступны, большинство интерфейсов к компонентам доступа к данным OLE DB и ODBC остаются теми же. Для обратной совместимости с более ранними выпусками флаг DBCOLUMNFLAGS_ISLONG в поставщике OLE DB для собственного клиента SQL Server и флаг SQL_LONGVARCHAR в драйвере ODBC для собственного клиента SQL Server до сих пор используются. Поставщики и драйверы, написанные для SQL Server 2005 и SQL Server 2008 продолжают использовать эти условия для новых типов при установке максимальной длины в значение «неограниченная».

ПримечаниеПримечание

Можно также задать типы данных varchar(max), nvarchar(max) и varbinary(max) в качестве типов входных и выходных параметров хранимых процедур, типов возвращаемых значений функций или в функциях CAST и CONVERT.

Поставщик OLE DB для собственного клиента SQL Server

Поставщик OLE DB для собственного клиента задает типы SQL Servervarchar(max), varbinary(max) и nvarchar(max) как соответственно DBTYPE_STR, DBTYPE_BYTES и DBTYPE_WSTR.

Типы данных varchar(max), varbinary(max) и nvarchar(max) в столбцах с размером max, установленным в неограниченное значение, представляются как ISLONG через основные наборы строк схемы OLE DB и интерфейсы, возвращающие типы столбцов.

Реализация IAccessor объекта команды была изменена, чтобы разрешить привязку DBTYPE_IUNKNOWN. Если потребитель задает DBTYPE_IUNKNOWN и устанавливает pObject в значение NULL, то поставщик возвратит потребителю интерфейс ISequentialStream, чтобы потребитель мог направить данные varchar(max), nvarchar(max), or varbinary(max) вне выходных переменных.

Поток значений выходных параметров возвращается после любых результирующих строк. Если приложение пытается перейти к следующему набору данных, вызывая IMultipleResults::GetResult и не используя все значения выходных параметров, то возвращается DB_E_OBJECTOPEN.

Чтобы поддержать потоковую передачу, поставщик OLE DB для собственного клиента SQL Server требует, чтобы доступ к параметрам переменной длины осуществлялся последовательно. Это означает, что DBPROP_ACCESSORDER необходимо установить либо в значение DBPROPVAL_AO_SEQUENTIALSTORAGEOBJECTS, либо в значение DBPROPVAL_AO_SEQUENTIAL каждый раз, когда столбцы varchar(max), nvarchchar(max) или varbinary(max) или выходные параметры привязываются к DBTYPE_IUNKNOWN. Если не следовать этому ограничению на порядок доступа, то вызов IRowset::GetData завершится с ошибкой DBSTATUS_E_UNAVAILABLE. Это ограничение не применяется, если нет выходных привязок с использованием DBTYPE_IUNKNOWN.

Поставщик OLE DB для собственного клиента SQL Server также поддерживает привязку выходных параметров как DBTYPE_IUNKNOWN для типов больших значений, чтобы облегчить сценарии, при которых хранимая процедура возвращает типы больших значений, которые представляются клиенту как DBTYPE_IUNKNOWN.

Для работы с этими типами у приложения есть следующие возможности.

  • Выполните привязку, указав тип, имеющий поддерживаемые привязки к основному типу столбца (например, для nvarchar(max) выполните привязку, указав тип, который можно привязать к типу nvarchar). Если буфер недостаточно большой, то происходит усечение, точно так же, как и для базового типа, хотя большие значения в данный момент доступны.

  • Выполните привязку, указав указав тип, имеющий поддерживаемые привязки к основному типу столбца, и также укажите DBTYPE_BYREF.

  • Выполните привязку, указав тип DBTYPE_IUNKNOWN, и используйте потоковую передачу.

Когда собственный поставщик клиента OLE DB SQL Server сообщает максимальный размер столбца, он сообщает:

  • заданный максимальный размер, например, 2000 для столбца varchar(2000) или

  • значение «неограниченный», если столбец varchar(max) равен ~0. Это значение устанавливается для свойства метаданных DBCOLUMN_COLUMNSIZE.

Стандартные правила преобразования применяются к столбцу varchar(max), это значит, что любое преобразование, допустимое для столбца varchar(2000), также допустимо для столбца varchar(max). То же относится к столбцам nvarchar(max) и varbinary(max).

При получении типов больших значений наиболее эффективным подходом является выполнение привязки с указанием типа DBTYPE_IUNKNOWN и установление свойства DBPROP_ACCESSORDER набора строк в значение DBPROPVAL_AO_SEQUENTIALSTORAGEOBJECTS. Это вызовет передачу значения в потоке напрямую из сети без промежуточной буферизации, как показано в следующем примере:

#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"AdventureWorks";   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;}

Дополнительные сведения о том, как поставщик OLE DB для собственного клиента SQL Server представляет типы больших значений см. в разделе Большие двоичные объекты и объекты OLE.

Драйвер ODBC для собственного клиента SQL Server

Драйвер ODBC для собственного клиента SQL Server представляет типы varchar(max), varbinary(max) и nvarchar(max) как SQL_VARCHAR, SQL_VARBINARY и SQL_WVARCHAR в функциях ODBC API, принимающих и возвращающих типы данных SQL ODBC.

Сообщая максимальный размер столбца, драйвер сообщает:

  • заданный максимальный размер, например, 2000 для столбца varchar(2000) или

  • значение «неограниченный», если столбец varchar(max) равен 0.

Стандартные правила преобразования применяются к столбцу varchar(max), это значит, что любое преобразование, допустимое для столбца varchar(2000), также допустимо для столбца varchar(max). То же относится к столбцам nvarchar(max) и varbinary(max).

Ниже приведен список функций ODBC API, которые были улучшены для работы с типами больших значений: