Продолжается подписка на наши издания! Вы не забыли подписаться?

Microsoft OLE DB-провайдер для Oracle: хитрости, уловки и увертки

В этой статье мы рассмотрим ряд вопросов:

Введение

Microsoft OLE DB Provider для Oracle - это провайдер в чистом виде, т.е. он предоставляет только то, что Oracle может предоставить через Oracle Call Interface API.

Провайдер сам по себе не имеет такой перегруженности, как провайдер, предоставляющий все интерфейсы и модели поведения, определенные для OLE DB. Таким образом, приложения, не нуждающиеся в полном наборе функциональности, выигрывают в быстродействии. Недостатком этого является ограничение гибкости разработчика приложения.

К счастью, ряд обслуживающих компонентов предоставляют функциональность, недоступную из Microsoft OLE DB Provider для Oracle. Однако, поскольку в этом случае добавляется лишнее звено, для сохранения быстродействия, следует по мере возможности этого избегать.

Эта статья приводит ряд приемов, которые можно использовать для получения максимального быстродействия провайдера и описывает обслуживающие компоненты, поставляемые с Microsoft Data Access Components (MDAC) 2.0, помогающие получить модели поведения, не реализованные провайдером. После этого приводится ряд рекомендаций по работе с хранимыми процедурами. Наконец, мы приведем список ряда возможностей Oracle, не поддерживаемых провайдером на сегодняшний день.

Получение максимально возможного быстродействия

Приведем ряд рекомендаций по получению максимального быстродействия в Microsoft OLE DB Provider для Oracle.

Использование пула сеансов (session pooling) OLE DB

В любом Oracle-приложении, существенная часть времени уделяется достижению подключения к серверу Oracle. Обслуживающий компонент пула сеансов, оптимизирует подключения для случая с высокоскоростным приложением, управляя пулами подобных подключений. Этот компонент рекомендуется использовать во всех случаях.

Пул сеансов автоматически является доступным при использовании ADO, однако при написании приложения непосредственно под OLE DB, необходимо инициализировать свой источник данных, используя интерфейс IDataInitialize вместо CoCreateInstance. Приведем пример того, как это может быть написано в С++:

#define DBINITCONSTANTS

#include <oledb.h>
#include <msdasc.h>
#include <crtdbg.h>

static LPCWSTR   s_pwszDataSource =
                     L”Provider=MSDAORA;”
                     L”Data Source=goliath;”
                     L”User ID=scott;”
                     L”Password=tiger;”;

HRESULT UseSession (IUnknown* pUnkSession);

main ()
{
   HRESULT           hr;
   IUnknown*         pUnkDataSource = NULL;
   IDataInitialize*  pIDataInitialize = NULL;

   hr = CoInitialize (NULL);
   _ASSERT ( SUCCEEDED (hr) );

   hr = CoCreateInstance
            (
            CLSID_MSDAINITIALIZE,
            NULL,
            CLSCTX_INPROC_SERVER,
            IID_IDataInitialize,
            (LPVOID*)&pIDataInitialize
            );

   if (pIDataInitialize)
   {
      hr = pIDataInitialize->GetDataSource
               (
               NULL,
               CLSCTX_INPROC_SERVER, 
               s_pwszDataSource,
               IID_IUnknown,
               &pUnkDataSource
               );

      pIDataInitialize->Release ();
   }

   if (pUnkDataSource)
   {
      IDBInitialize*   pIDBInitialize = NULL;

      hr = pUnkDataSource->QueryInterface
                  (
                  IID_IDBInitialize,
                  (LPVOID*)&pIDBInitialize
                  );
      if (pIDBInitialize)
      {
         hr = pIDBInitialize->Initialize ();

         if ( SUCCEEDED(hr) )
         {
            IDBCreateSession*   pIDBCreateSession = NULL;

            hr = pIDBInitialize->QueryInterface
                       (
                       IID_IDBCreateSession,
                       (LPVOID*)&pIDBCreateSession
                       );

            if (pIDBCreateSession)
            {
               IUnknown*   pUnkSession = NULL;
               hr = pIDBCreateSession->CreateSession
                        (
                        NULL,
                        IID_IOpenRowset,
                        (IUnknown**)&pUnkSession
                        );

               if (pUnkSession)
               {
                  hr = UseSession (pUnkSession);
               }
               pUnkSession->Release ();
            }
         }
         pIDBInitialize->Release ();
      }
      pUnkDataSource->Release ();
   }
   return 0;
}

Избегайте использования прокрутки
или обновляемых наборов строк

Oracle не предоставляет серверных прокручиваемых курсоров. Более того, Oracle не предоставляет способа обновлять строку без использования оператора SQL UPDATE.

На момент написания этой статьи Microsoft OLE DB Provider для Oracle предоставлял наборы строк с атрибутами “только для передачи” и “только для чтения”. Поэтому сам провайдер не реализует интерфейсы IRowsetFind, IRowsetLocate, IRowsetScroll и IRowsetChange, и не поддерживает выборку или прокрутку обратных действий.

Однако, используя IDataInitialize при создании объекта источника данных, можно получить эти интерфейсы, просто присвоив их свойству DBPROPSET_ROWSET значение VARIANT_TRUE. Это приведет к тому, что обслуживающий компонент “движка” курсора клиента (Client Cursor Engine - CE) автоматически выполнит все действия, необходимые для их предоставления.

На самом деле, при использовании “движка” курсора клиента есть некоторые сложности, так что, если не планируется осуществлять обратную выборку и прокрутку или обновлять данные в наборах строк, для обеспечения оптимального быстродействия можно запрашивать наборы строк с атрибутами “только для передачи” и “только для чтения”.

Развивая наш предыдущий пример, написанный непосредственно под OLE DB в С++, можно заменить вызов UseSession вызовом следующей подпрограммы, что будет гарантировать использование наборов строк “только для передачи” и “только для чтения”:

HRESULT GetForwardOnlyRowset (IUnknown* pUnkSession)
{
   HRESULT hr = S_OK;
   LPCWSTR   pwszTable = L”EMP”;

   IOpenRowset*   pIOpenRowset = NULL;
   hr = pUnkSession->QueryInterface (IID_IOpenRowset,
                              (LPVOID*)&pIOpenRowset);

   if (pIOpenRowset)
   {
      IRowset*   pIRowset = NULL;
      DBID      dbidTemp;

      dbidTemp.eKind = DBKIND_NAME;
      dbidTemp.uGuid.guid = GUID_NULL;
      dbidTemp.uName.pwszName = (LPWSTR)pwszTable;

      hr = pIOpenRowset->OpenRowset
               (
               NULL,
               &dbidTemp,
               NULL,
               IID_IRowset,
               0,
               NULL,
               (IUnknown **)&pIRowset
               );

      if (pIRowset)
      {
         hr = FetchRowsetForward (pIRowset);
         pIRowset->Release ();
      }
      pIOpenRowset->Release ();
   }
   return hr;
}

Если при написании приложения используется ADO, у adUseServer запрашивается CursorLocation, у adOpenForwardOnly - CursorType а у adLockReadOnly - LockType. Далее приведен пример, написанный в Visual Basic:

Sub ForwardOnly()
    Dim cn As New ADODB.Connection
    Dim rs As New ADODB.Recordset
    
    cn.Open “Provider=MSDAORA;Data Source= goliath;UserID=scott;Password=tiger”
    rs.CursorLocation = adUseServer
    rs.Open “emp”, cn, adOpenForwardOnly, adLockReadOnly
       
    rs.MoveFirst
       
    While rs.EOF <> True
        Debug.Print rs(0).Value, rs(1).Value
        rs.MoveNext
    Wend
    
    rs.Close
    cn.Close
End Sub

Рассмотрение асинхронной выборки при
использовании курсоров клиентов

Если необходим прокручиваемый набор строк, сервисный компонент “движка” курсора клиента может предоставить моментальный курсор (snapshot cursor). “Движок” курсора полностью материализует набор строк на клиенте. Однако, до тех пор, пока не будет определен запрос того, что “движок” курсора клиента должен использовать асинхронную выборку, будет ожидаться окончание выборки, и только после этого управление вернется приложению. В случае большого набора строк, это займет некоторое время.

Во многих случаях приложение может запуститься для последующей выборки, но должно будет иметь возможность и осуществить обратную выборку. В этом случае программист может захотеть, чтобы материализация набора строк выполнялась асинхронно.

Если писать непосредственно для OLE DB в С++, то перед реализацией набора строк, следует в DBPROPSET_ROWSET свойству DBPROP_ROWSET_ASYNCH присвоить значение DBPROPVAL_ASYNCH_BACKGROUNDPOPULATION:

HRESULT GetScrollableRowset (IUnknown* pUnkSession)
{
   HRESULT hr = S_OK;
   LPCWSTR   pwszTable = L”EMP”;

   IOpenRowset*   pIOpenRowset = NULL;
   hr = pUnkSession->QueryInterface (IID_IOpenRowset,
                              (LPVOID*)&pIOpenRowset);

   if (pIOpenRowset)
   {
      IRowset*      pIRowset = NULL;
      DBID         dbidTemp;
      DBPROPSET      rgPropset[1];
      DBPROP         rgProps[3];

      dbidTemp.eKind = DBKIND_NAME;
      dbidTemp.uGuid.guid = GUID_NULL;
      dbidTemp.uName.pwszName = (LPWSTR)pwszTable;

      rgPropset[0].rgProperties = rgProps;
      rgPropset[0].cProperties = 3;
      rgPropset[0].guidPropertySet = DBPROPSET_ROWSET;

      rgProps[0].dwPropertyID = DBPROP_CANFETCHBACKWARDS;
      rgProps[0].dwOptions = DBPROPOPTIONS_REQUIRED;
      rgProps[0].dwStatus = 0;
      VariantInit (&rgProps[0].vValue);
      rgProps[0].vValue.vt = VT_BOOL;
      rgProps[0].vValue.boolVal = VARIANT_TRUE;

      rgProps[1].dwPropertyID = DBPROP_CANSCROLLBACKWARDS;
      rgProps[1].dwOptions = DBPROPOPTIONS_REQUIRED;
      rgProps[1].dwStatus = 0;
      VariantInit (&rgProps[1].vValue);
      rgProps[1].vValue.vt = VT_BOOL;
      rgProps[1].vValue.boolVal = VARIANT_TRUE;

      rgProps[2].dwPropertyID = DBPROP_ROWSET_ASYNCH;
      rgProps[2].dwOptions = DBPROPOPTIONS_REQUIRED;
      rgProps[2].dwStatus = 0;
      VariantInit (&rgProps[2].vValue);
      rgProps[2].vValue.vt = VT_I4;
      V_I4(&rgProps[2].vValue) = DBPROPVAL_ASYNCH_BACKGROUNDPOPULATION;

      hr = pIOpenRowset->OpenRowset
               (
               NULL,
               &dbidTemp,
               NULL,
               IID_IRowset,
               1,
               rgPropset,
               (IUnknown **)&pIRowset
               );

      if (pIRowset)
      {
         hr = FetchRowsetBackward (pIRowset);
         pIRowset->Release ();
      }
      pIOpenRowset->Release ();

      VariantClear (&rgProps[0].vValue);
      VariantClear (&rgProps[1].vValue);
      VariantClear (&rgProps[2].vValue);
   }
   return hr;
}

Если используется ADO, при открытии набора записей следует запросить опцию adAsynchFetch. Приведем пример в Visual Basic:

Sub ScrollableAsync()   
    Dim cn As New ADODB.Connection
    Dim rs As New ADODB.Recordset
    
    cn.Open “Provider=MSDAORA;Data Source= goliath;UserID=scott;Password=tiger”
    rs.CursorLocation = adUseClient
    rs.Open “emp”, cn, adOpenStatic, adLockReadOnly, adAsyncFetch

    rs.MoveFirst
       
    While rs.EOF <> True
        Debug.Print rs(0).Value, rs(1).Value
        rs.MoveNext
    Wend
    
    rs.Close
    cn.Close
End Sub

Использование одноточечной выборки для получения данных LONG и LONG RAW

Провайдер позволяет выбирать в наборе данных данные LONG и LONG RAW. Поскольку Oracle предоставляет наборы строк “только для передачи”, а OLE DB необходима возможность получения данных из набора данных в любое время, отложенная выборка (т.е. выборка, предоставляющие данные только после из запроса), невозможна.

Провайдер восстанавливает все данные для столбцов LONG и LONG RAW для каждой строки выборки. В случае, когда данные из столбца LONG или LONG RAW необходимы изредка, более целесообразным было бы использование запроса столбцов LONG и LONG RAW во втором операторе запроса.

При написании на С++ непосредственно под OLE DB, рекомендуется воспользоваться следующим кодом:

HRESULT GetDeferredBLOBRowset (IUnknown* pUnkSession)
{
   HRESULT hr = S_OK;
   LPCWSTR pwszStmt1 = L”select owner, view_name from all_views”;
   LPCWSTR pwszStmt2 = L”select text from all_views where owner = ? and view_name = ?”;

   IDBCreateCommand*   pIDBCreateCommand = NULL;
   ICommandText*      pICommand1 = NULL;
   ICommandText*      pICommand2 = NULL;
   hr = pUnkSession->QueryInterface (IID_IDBCreateCommand,
                              (LPVOID*)&pIDBCreateCommand);

   if (pIDBCreateCommand)
   {
      hr = pIDBCreateCommand->CreateCommand (NULL, IID_ICommandText,
                                    (IUnknown **)&pICommand1);
      _ASSERT ( SUCCEEDED(hr) );
      if (pICommand1)
      {
         hr = pICommand1->SetCommandText(DBGUID_SQL, pwszStmt1);
         _ASSERT ( SUCCEEDED(hr) );
      }

      hr = pIDBCreateCommand->CreateCommand (NULL, IID_ICommandText,
      (IUnknown **)&pICommand2);
      _ASSERT ( SUCCEEDED(hr) );
      if (pICommand2)
      {
         hr = pICommand2->SetCommandText(DBGUID_SQL, pwszStmt2);
         _ASSERT ( SUCCEEDED(hr) );
      }
   }

   if (pICommand1 && pICommand2)
   {
      IRowset*   pIRowset = NULL;
   
      hr = pICommand1->Execute
               (
               NULL,
               IID_IRowset,
               NULL,
               NULL,
               (IUnknown **)&pIRowset
               );

      if (pIRowset)
      {
         hr = FetchRowsetDeferredBLOB (pIRowset, pICommand2);
         pIRowset->Release ();
      }
   }
   if (pICommand1)
   {
      pICommand1->Release ();
   }
   if (pICommand2)
   {
      pICommand2->Release ();
   }
   return hr;
}

HRESULT FetchRowsetDeferredBLOB (IRowset *   pIRowset, ICommandText * pICommand)
{
   HRESULT      hr = S_OK;
   HACCESSOR   hAccessor;
   HACCESSOR   hAccessorBLOB;
   HACCESSOR   hAccessorParam;
   ULONG      cbData;
   BYTE *      pData;
   BYTE *      pBLOB;
   HROW      hrow;
   HROW *      phrow = &hrow;
   ULONG      cFetched;
   ULONG      cColumns;
   DBBINDING *   rgBinding;

   
   hr = CreateFastAccessor
         (
         pIRowset,
         &hAccessor,
         &cColumns,
         &rgBinding,
         &cbData
         );

   if (pICommand)
   {
      // Get an accessor for the BLOB rowset
      IAccessor *   pIAccessor;
      DBBINDING    BLOBBinding;
      DBBINDING   ParamBinding[2];
      ULONG      i;

      hr = pICommand->QueryInterface (IID_IAccessor, (LPVOID*)&pIAccessor);
      _ASSERT ( pIAccessor );

      memset ((LPVOID)&BLOBBinding, 0, sizeof(BLOBBinding));

      BLOBBinding.iOrdinal   = 1;

      BLOBBinding.obValue      = 0;
      BLOBBinding.obStatus   = 4000;
      BLOBBinding.obLength   = BLOBBinding.obStatus + sizeof (DBSTATUS);
      BLOBBinding.dwPart      = DBPART_STATUS | DBPART_LENGTH | DBPART_VALUE;
      
      BLOBBinding.dwMemOwner   = DBMEMOWNER_CLIENTOWNED;
      BLOBBinding.eParamIO   = DBPARAMIO_NOTPARAM;
      BLOBBinding.wType      = DBTYPE_STR | DBTYPE_BYREF;
      BLOBBinding.cbMaxLen   = 4000;

      hr = pIAccessor->CreateAccessor
               (
               DBACCESSOR_ROWDATA,
               1,
               &BLOBBinding,
               0,
               &hAccessorBLOB,
               NULL
               );
      _ASSERT ( SUCCEEDED(hr) );
      // Get a Parameter Accessor for the two parameters
      memset ((LPVOID)&ParamBinding, 0, sizeof(ParamBinding));
      for (i = 0; i < 2; i++)
      {
         ParamBinding[i] = rgBinding[i];
         ParamBinding[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
         ParamBinding[i].eParamIO = DBPARAMIO_INPUT;
      }
      hr = pIAccessor->CreateAccessor
               (
               DBACCESSOR_PARAMETERDATA,
               2,
               ParamBinding,
               0,
               &hAccessorParam,
               NULL
               );
      _ASSERT ( SUCCEEDED(hr) );
   }
   if ( SUCCEEDED (hr) )
   {
      pData = (BYTE *)malloc (cbData);
      pBLOB = (BYTE *)malloc (4000 + sizeof (ULONG) + sizeof(DBSTATUS));
      if (pData)
      {
         while ( hr == S_OK )
         {
            hr = pIRowset->GetNextRows (NULL, 0, 1, &cFetched, &phrow );

            if ( SUCCEEDED(hr) && cFetched )
            {
               hr = pIRowset->GetData ( hrow, hAccessor, pData );

               if ( SUCCEEDED(hr) )
               {
                  CHAR *   pszOwner;
                  CHAR *   pszView;
	
                  pszOwner   = (CHAR *)*( (BYTE **)(pData + rgBinding[0].obValue) );
                  pszView      = (CHAR *)*( (BYTE **)(pData + rgBinding[1].obValue) );

                  printf (“OWNER=%-30.30s VIEW_NAME=%-30.30s “, pszOwner, pszView);
                  if ( strcmp (pszOwner, “SYS”) == 0)
                  {
                     printf (“(BLOB Skipped...)\n”);
                  }
                  else
                  {
                     hr = FetchBLOB (pICommand, hAccessorParam, pData, hAccessorBLOB, pBLOB);
                  }
               }
               pIRowset->ReleaseRows  (1, &hrow, NULL, NULL, NULL);
            }
         }
      }
      free (rgBinding);
      free (pData);
      free (pBLOB);
   }
   return hr;
}

HRESULT FetchBLOB (ICommandText * pICommand, HACCESSOR hAccessorParam, 
                   BYTE *pData, HACCESSOR hAccessorBLOB, BYTE *pBLOB)
{
   HRESULT      hr = S_OK;
   HROW      hrow;
   HROW *      phrow = &hrow;
   ULONG      cFetched;
   IRowset*   pIRowset = NULL;
   DBPARAMS   dbParams;

   dbParams.pData = pData;
   dbParams.cParamSets = 1;
   dbParams.hAccessor = hAccessorParam;

   hr = pICommand->Execute
            (
            NULL,
            IID_IRowset,
            &dbParams,
            NULL,
            (IUnknown **)&pIRowset
            );

   if (pIRowset)
   {
      if ( SUCCEEDED (hr) )
      {
         hr = pIRowset->GetNextRows (NULL, 0, 1, &cFetched, &phrow );

         if ( SUCCEEDED(hr) && cFetched )
         {
            hr = pIRowset->GetData ( hrow, hAccessorBLOB, pBLOB );

            if ( SUCCEEDED(hr) )
            {
               printf (“VIEW_TEXT=%s\n\n”, *(CHAR **)pBLOB);
            }
            pIRowset->ReleaseRows  (1, &hrow, NULL, NULL, NULL);
         }
      }
      pIRowset->Release ();
   }
   return hr;
}

Здесь приведен другой пример, использующий ADO и написанный в Visual Basic. Обратите внимание на использование параметризованного запроса и на то, что параметры определяются, а не вызываются:

Sub DeferredBLOBs()
    Dim cn As New ADODB.Connection
    Dim rs As New ADODB.Recordset
    Dim cmd As New ADODB.Command
    Dim rs2 As New ADODB.Recordset
    
    cn.Open “Provider=MSDAORA;Data Source= goliath;UserID=scott;Password=tiger”
    rs.CursorLocation = adUseServer
    rs.Open “select owner, view_name from all_views”, cn, adOpenForwardOnly, adLockReadOnly
    
    cmd.ActiveConnection = cn
    cmd.CommandText = “select text from all_views where owner = ? and view_name = ?”
    cmd.Parameters.Append cmd.CreateParameter(“owner”, adVarChar, adParamInput, 30)
    cmd.Parameters.Append cmd.CreateParameter(“viewname”, adVarChar, adParamInput, 30)
       
    rs.MoveFirst
       
    While rs.EOF <> True
        Debug.Print rs(0).Value, rs(1).Value,
        
        If (rs(0).Value <> “SYS”) Then
            cmd(0) = rs(0).Value
            cmd(1) = rs(1).Value
            Set rs2 = cmd.Execute
        
            Debug.Print rs2(0).Value
        Else
            Debug.Print “Skipped...”
        End If
        rs.MoveNext
    Wend
    
    rs.Close
    cn.Close
End Sub

Создание собственной
информации о параметрах

Oracle не предоставляет способа определения информации о параметрах для команд Языка Манипуляции SQL-Данными (SQL Data Manipulation Language - DML), а именно SELECT, INSERT, UPDATE и DELETE, и, таким образом, провайдер ее получить не может. Если для объекта Command с помощью оператора SELECT, INSERT, UPDATE или DELETE вызывается ICommandWithParameters::GetParameterInfo, то мы получим значение DB_E_PARAMUNAVAILABLE.

Как говорилось, Oracle не предоставляет способа получения информации о параметрах для хранимых процедур и функций PL/SQL. Для того, чтобы сделать это используется функция Oracle Call Interface, однако, чтобы избежать значительных усложнений, рекомендуется не запрашивать у провайдера получение этой информации.

Если приложение было написано для ADO, следует вместо вызова Command.Parameter.Refresh просто применить определения параметров. Как это сделать, описано в примере DefferedBLOB.

Если приложение написано непосредственно под OLE DB, перед вызовом ICommandWithParameters::GetParameterInfo, следует вызвать ICommandWithParameters::SetParameterInfo.

Использование связок BYREF, PROVIDEROWNED

Несмотря на рекомендацию, имеющуюся в OLE DB Provider для Oracle, всегда лучше создавать аксессор связки DBTYPE_BYREF и использовать DBMEMOWNER_PROVIDEROWNED. Это приведет к тому, что провайдер будет работать с указателем в своем буфере строк вместо того, чтобы копировать данные в буфер строк приложения.

Замечание. На данный момент, сервисный компонент “движка” курсора клиента не поддерживает связку данных с DBMEMOWNER_PROVIDEROWNED. Однако, как показано в этой статье, это не самый лучший сценарий с точки зрения быстродействия.

Нижеприведенный код на С++ показывает, как, используя информацию возвращенную интерфейсом IColumnsInfo, создавать аксессор со связками BYREF, PROVIDEROWNED. Функция FetchRowsetForward сделает выборку всех данных для наборов строк, созданных нашей подпрограммой GetForwardOnlyRowset:

HRESULT CreateFastAccessor
   (
   IRowset*      pIRowset,
   HACCESSOR *      phAccessor,
   ULONG *         pcColumns,
   DBBINDING **   prgBinding,
   ULONG *         pcbData
   )
{
   HRESULT         hr = S_OK;
   IAccessor *      pIAccessor;
   IColumnsInfo *   pIColumnsInfo;
   ULONG         cColumns;
   DBCOLUMNINFO *   rgInfo;
   OLECHAR *      pStringsBuffer;
   ULONG         i;

   DBBINDING *      rgBinding;

   *pcColumns = 0;
   *prgBinding = NULL;
   *pcbData = 0;

   hr = pIRowset->QueryInterface (IID_IAccessor, (LPVOID*)&pIAccessor);
   _ASSERT ( pIAccessor );

   hr = pIRowset->QueryInterface (IID_IColumnsInfo, (LPVOID*)&pIColumnsInfo);
   _ASSERT ( pIColumnsInfo );

   hr = pIColumnsInfo->GetColumnInfo
            (
            &cColumns,
            &rgInfo,
            &pStringsBuffer
            );

   if ( SUCCEEDED(hr) )
   {
      rgBinding = (DBBINDING *)malloc (cColumns * sizeof(DBBINDING));
      
      if (rgBinding)
      {
         memset (rgBinding, 0, (cColumns * sizeof(DBBINDING)) );

         for (i = 0; i < cColumns; i++)
         {
            rgBinding[i].iOrdinal   = rgInfo[i].iOrdinal;

            rgBinding[i].obValue   = *pcbData;
            (*pcbData) += sizeof (void *);

            rgBinding[i].obLength   = *pcbData;
            (*pcbData) += sizeof (void *);

            rgBinding[i].obStatus   = *pcbData;
            (*pcbData) += sizeof (void *);

            rgBinding[i].dwPart   = (DBPART_VALUE | DBPART_LENGTH | DBPART_STATUS);
            rgBinding[i].dwMemOwner = DBMEMOWNER_PROVIDEROWNED;
            rgBinding[i].eParamIO   = DBPARAMIO_NOTPARAM;
            rgBinding[i].wType      = rgInfo[i].wType | DBTYPE_BYREF;
         }

         hr = pIAccessor->CreateAccessor
                  (
                  DBACCESSOR_ROWDATA,
                  cColumns,
                  rgBinding,
                  0,
                  phAccessor,
                  NULL
                  );
         
         if ( FAILED(hr) )
         {
            free (rgBinding);
            rgBinding = NULL;
            cColumns = 0;
         }
      }
   }

   g_pIMalloc->Free (rgInfo);
   g_pIMalloc->Free (pStringsBuffer);
   pIColumnsInfo->Release ();
   pIAccessor->Release ();

   *prgBinding = rgBinding;
   *pcColumns = cColumns;
   return hr;
}

HRESULT FetchRowsetForward (IRowset* pIRowset)
{
   HRESULT      hr = S_OK;
   HACCESSOR   hAccessor;
   ULONG      cbData;
   BYTE *      pData;
   HROW      hrow;
   HROW *      phrow = &hrow;
   ULONG      cFetched;
   ULONG      cColumns;
   DBBINDING *   rgBinding;

   hr = CreateFastAccessor
         (
         pIRowset,
         &hAccessor,
         &cColumns,
         &rgBinding,
         &cbData
         );

   if ( SUCCEEDED (hr) )
   {
      pData = (BYTE *)malloc (cbData);
      if (pData)
      {
         while ( hr == S_OK )
         {
            hr = pIRowset->GetNextRows (NULL, 0, 1, &cFetched, &phrow );

            if ( SUCCEEDED(hr) && cFetched )
            {
               hr = pIRowset->GetData ( hrow, hAccessor, pData );

               if ( SUCCEEDED(hr) )
               {
                  hr = PrintColumn (“ENAME”, pData, &rgBinding[1]);
               }
               pIRowset->ReleaseRows  (1, &hrow, NULL, NULL, NULL);
            }
         }
      }
      free (rgBinding);
      free (pData);
   }
   return hr;
}

Избегайте ненужных преобразований в тип данных WIDE CHAR

В интерфейсе вызова Oracle 7 (Oracle Version 7 Call Interface) нет поддержки Unicode. Поэтому все данные возвращаются в виде MultiByte. При выборке символьных данных, которые не должны быть в Unicode, можно увеличить быстродействие, просто выбрав DBTYPE_STR вместо DBTYPE_WSTR. Информация, возвращаемая IColumnsInfo::GetColumnInfo отражает оптимальность связки.

Замечание. Поскольку символьные данные в схеме наборов строк определены как DBTYPE_WSTR, они будут преобразованы автоматически. Связка с ними как с DBTYPE_STR приведет к обратному преобразованию в строку MultiByte.

Использование ICommandPrepare

При каждом выполнении оператора, провайдер проделывает некоторую работу по подготовке к его выполнению, такую как определение столбцов набора строк и подготовка к связке параметров. Если оператор будет выполняться более одного раза, для того, чтобы подготовка к выполнению команды выполнялась только один раз, используется ICommandPrepare. Если ICommandPrepare использоваться не будет, это приведет к повторному выполнению одной и той же работы.

Получение дополнительных моделей поведения

Теперь, провайдер реализует функциональность базового уровня и позволяет для получения дополнительной модели поведения использовать обслуживающие компоненты.

Приведенные ниже интерфейсы OLE DB реализуются самим провайдером:

Data Source Objects:
   CoType TDataSource {
      interface IDBCreateSession;
      interface IDBInfo;
      interface IDBInitialize;
      interface IDBProperties;
      interface IPersist;
      interface ISupportErrorInfo;
   }
Sessions:
   CoType TSession {
      interface IDBCreateCommand;
      interface IDBSchemaRowset;
      interface IGetDataSource;
      interface IOpenRowset;
      interface ISessionProperties;
      interface ISupportErrorInfo;
      interface ITransaction;
      interface ITransactionJoin;
      interface ITransactionLocal;
   }
Commands:
   CoType TCommand {
      interface IAccessor;
      interface IColumnsInfo;
      interface ICommand;
      interface ICommandPrepare;
      interface ICommandProperties;
      interface ICommandText;
      interface ICommandWithParameters;
      interface IConvertType;
      interface ISupportErrorInfo;
   }
Rowsets:
   CoType TRowset {
      interface IAccessor;
      interface IColumnsInfo;
      interface IConnectionPointContainer;
      interface IConvertType;
      interface IRowset;
      interface IRowsetIdentity;
      interface IRowsetInfo;
      interface ISupportErrorInfo;
   }
Transaction Options:
   CoType TTransactionOptions {
      interface ISupportErrorInfo;
      interface ITransactionOptions;
   };

Это - дополнительные интерфейсы OLE DB, реализуемые обслуживающими компонентами:

Rowsets:
   CoType TRowset {
      interface IRowsetChange;
      interface IRowsetFind;
      interface IRowsetLocate;
      interface IRowsetScroll;
   };
};

Для использования любого из этих дополнительных интерфейсов, следует создать экземпляр IDataInstance.

Получение прокручиваемых наборов строк

Провайдер, как таковой, не реализует обратной выборки, обратной прокрутки или интерфейсов IRowsetFind, IRowsetLocate или IRowsetScroll. Поэтому приложения, которым необходимы прокручиваемые наборы строк, должны использовать для их конструирования “движок” курсора клиента. Для этого, перед созданием набора строк, следует DBPROP_CANFETCHBACKWARDS и DBPROP_CANSCROLLBACKWARDS следует присвоить значение VARIANT_TRUE.

В нашем примере на С++ вызов UseSession заменяется вызовом функции GetScrollableRowset:

HRESULT GetScrollableRowset (IUnknown* pUnkSession)
{
   HRESULT hr = S_OK;
   LPCWSTR   pwszTable = L”EMP”;

   IOpenRowset*   pIOpenRowset = NULL;
   hr = pUnkSession->QueryInterface (IID_IOpenRowset,
                              (LPVOID*)&pIOpenRowset);

   if (pIOpenRowset)
   {
      IRowset*      pIRowset = NULL;
      DBID         dbidTemp;
      DBPROPSET      rgPropset[1];
      DBPROP         rgProps[3];

      dbidTemp.eKind = DBKIND_NAME;
      dbidTemp.uGuid.guid = GUID_NULL;
      dbidTemp.uName.pwszName = (LPWSTR)pwszTable;

      rgPropset[0].rgProperties = rgProps;
      rgPropset[0].cProperties = 3;
      rgPropset[0].guidPropertySet = DBPROPSET_ROWSET;

      rgProps[0].dwPropertyID = DBPROP_CANFETCHBACKWARDS;
      rgProps[0].dwOptions = DBPROPOPTIONS_REQUIRED;
      rgProps[0].dwStatus = 0;
      VariantInit (&rgProps[0].vValue);
      rgProps[0].vValue.vt = VT_BOOL;
      rgProps[0].vValue.boolVal = VARIANT_TRUE;

      rgProps[1].dwPropertyID = DBPROP_CANSCROLLBACKWARDS;
      rgProps[1].dwOptions = DBPROPOPTIONS_REQUIRED;
      rgProps[1].dwStatus = 0;
      VariantInit (&rgProps[1].vValue);
      rgProps[1].vValue.vt = VT_BOOL;
      rgProps[1].vValue.boolVal = VARIANT_TRUE;

      rgProps[2].dwPropertyID = DBPROP_ROWSET_ASYNCH;
      rgProps[2].dwOptions = DBPROPOPTIONS_REQUIRED;
      rgProps[2].dwStatus = 0;
      VariantInit (&rgProps[2].vValue);
      rgProps[2].vValue.vt = VT_I4;
      V_I4(&rgProps[2].vValue) = DBPROPVAL_ASYNCH_BACKGROUNDPOPULATION;

      hr = pIOpenRowset->OpenRowset
               (
               NULL,
               &dbidTemp,
               NULL,
               IID_IRowset,
               1,
               rgPropset,
               (IUnknown **)&pIRowset
               );

      if (pIRowset)
      {
         hr = FetchRowsetBackward (pIRowset);
         pIRowset->Release ();
      }
      pIOpenRowset->Release ();

      VariantClear (&rgProps[0].vValue);
      VariantClear (&rgProps[1].vValue);
      VariantClear (&rgProps[2].vValue);
   }
   return hr;
}

Обратите внимание, что набор строк реализован при присвоенном DBPROP_ROWSET_ASYNCH значении DBPROPVAL_ASYNCH_BACKGROUNDPOPULATION. Это показывает “движку” курсора клиента, что необходимо материализовать набор строк асинхронно, что позволяет приложению гораздо быстрее получать управление в случае больших наборов строк.

Если приложение написано в ADO, у adUseClient запрашивается CursorLocation, а у adOpenStatic - CursorType. Ниже приведен пример, написанный на Visual Basic:

Sub Scrollable()
    Dim cn As New ADODB.Connection
    Dim rs As New ADODB.Recordset
    
    cn.Open “Provider=MSDAORA;Data Source= goliath;UserID=scott;Password=tiger”
    rs.CursorLocation = adUseClient
    rs.Open “emp”, cn, adOpenStatic, adLockReadOnly, adAsyncFetch
       
    rs.MoveLast
       
    While rs.BOF <> True
        Debug.Print rs(0).Value, rs(1).Value
        rs.MovePrevious
    Wend
    
    rs.Close
    cn.Close
End Sub

В этом примере использование adAsyncFetch показывает “движку” курсора клиента, что необходимо материализовать набор строк асинхронно, что позволяет приложению гораздо быстрее возвращать управление в случае больших наборов строк.

Получение обновляемого набора строк

Провайдер не реализует интерфейс IRowsetChange. Поэтому приложение, которое должно осуществлять обновление и не использует операторы безопасного обновления, должно запрашивать у “движка” курсора клиента конструирование обновляемого набора строк. Для этого, перед созданием набора строк, необходимо присвоить DBPROP_IROWSETCHANGE значение VARIANT_TRUE.

В нашем примере на С++ обращение к UseSession заменяется на обращение к функции GetUpdatableRowset:

HRESULT GetUpdatableRowset (IUnknown* pUnkSession)
{
   HRESULT hr = S_OK;
   LPCWSTR   pwszTable = L”EMP”;

   IOpenRowset*   pIOpenRowset = NULL;
   hr = pUnkSession->QueryInterface (IID_IOpenRowset,
                              (LPVOID*)&pIOpenRowset);

   if (pIOpenRowset)
   {
      IRowset *      pIRowset = NULL;
      DBID         dbidTemp;
      DBPROPSET      rgPropset[1];
      DBPROP         rgProps[2];

      dbidTemp.eKind = DBKIND_NAME;
      dbidTemp.uGuid.guid = GUID_NULL;
      dbidTemp.uName.pwszName = (LPWSTR)pwszTable;

      rgPropset[0].rgProperties = rgProps;
      rgPropset[0].cProperties = 2;
      rgPropset[0].guidPropertySet = DBPROPSET_ROWSET;
      rgProps[0].dwPropertyID = DBPROP_IRowsetChange;
      rgProps[0].dwOptions = DBPROPOPTIONS_REQUIRED;
      rgProps[0].dwStatus = 0;
      VariantInit (&rgProps[0].vValue);
      rgProps[0].vValue.vt = VT_BOOL;
      rgProps[0].vValue.boolVal = VARIANT_TRUE;
      rgProps[1].dwPropertyID = DBPROP_ROWSET_ASYNCH;
      rgProps[1].dwOptions = DBPROPOPTIONS_REQUIRED;
      rgProps[1].dwStatus = 0;
      VariantInit (&rgProps[1].vValue);
      rgProps[1].vValue.vt = VT_I4;
      V_I4(&rgProps[1].vValue) = DBPROPVAL_ASYNCH_BACKGROUNDPOPULATION;
      hr = pIOpenRowset->OpenRowset
               (
               NULL,
               &dbidTemp,
               NULL,
               IID_IRowset,
               1,
               rgPropset,
               (IUnknown **)&pIRowset
               );
      if (pIRowset)
      {
         hr = FetchRowsetForward (pIRowset);
         pIRowset->Release ();
      }
      pIOpenRowset->Release ();
      VariantClear (&rgProps[0].vValue);
      VariantClear (&rgProps[1].vValue);
   }
   return hr;
}

Обратите внимание, что набор строк реализован при присвоенном DBPROP_ROWSET_ASYNCH значении DBPROPVAL_ASYNCH_BACKGROUNDPOPULATION. Это показывает “движку” курсора клиента, что необходимо материализовать набор строк асинхронно, что позволяет приложению гораздо быстрее получать управление в случае больших наборов строк.

Если приложение написано в ADO, у adUseClient запрашивается CursorLocation, у adOpenStatic — CursorType, а у adLockOptimistic - LockType. Ниже приведен пример, написанный на Visual Basic:

	Sub Updatable()
    Dim cn As New ADODB.Connection
    Dim rs As New ADODB.Recordset
    
    cn.Open “Provider=MSDAORA;Data Source= goliath;UserID=scott;Password=tiger”
    rs.CursorLocation = adUseClient
    rs.Open “emp”, cn, adOpenStatic, adLockOptimistic, adAsyncFetch
       
    rs.MoveFirst
       
    While rs.EOF <> True
        Debug.Print rs(0).Value, rs(1).Value
        rs(0).Value = rs(0).Value
        rs.Update
        rs.MoveNext
    Wend
    
    rs.Close
    cn.Close
End Sub

В этом примере использование adAsyncFetch показывает “движку” курсора клиента, что необходимо материализовать набор строк асинхронно, что позволит приложению гораздо быстрее возвращать управление в случае больших наборов строк.

Работа с хранимыми процедурами Oracle

Использование синтаксиса Oracle для вызова хранимых процедур

Провайдер поддерживает два метода вызова хранимой процедуры или функции: канонический синтаксис, определенный в спецификации ODBC и синтаксис самого Oracle — PL/SQL.

Канонический синтаксис:

{call spfoo [(arg...)]}

Синтаксис Oracle (PL/SQL):

begin
   spfoo [{arg...)]
end;

При использовании канонического синтаксиса, параметры могут вызываться. Однако, при использовании синтаксиса Oracle, как такового, вызов информации о параметрах не практикуется, поскольку это приводит к необходимости осуществления провайдером грамматического разбора PL/SQL.

Также, при использовании канонического синтаксиса, можно определить умолчальные значения параметров, придав им статус DBSTATUS_S_DEFAULT. При работе с PL/SQL-синтаксисом, необходимо использовать поименованные нотации, а использовать связки с параметрами, которые должны быть умолчальными, не следует.

Ограничения LONG/LONG RAW

Типы данных Oracle PL/SQL LONG и LONG RAW не могут содержать более 32760 байтов данных, даже если их название подразумевает возможность поддержки до 2 Гигабайт. Эти ограничения хорошо документированы Oracle, но общей ошибкой является попытка посредством хранимой процедуры вставить данные большого двоичного объекта (BLOB) в базу данных, где они обрезаются до 32760 байтов.

Если необходимо работать со значениями LONG и LONG RAW, превышающими эти ограничения, вместо хранимой процедуры PL/SQL следует воспользоваться операторами SQL SELECT, INSERT или UPDATE.

Возможности, не поддерживаемые в данный момент

Есть ряд возможностей, которые в данный момент не поддерживаются Microsoft OLE DB Provider для Oracle. Это — возможности Oracle 8, которые не могут работать с Oracle 7 Call Interface, используемым провайдером.

NCHAR, NVARCHAR

Работать с NCHAR и NVARCHAR, используя Oracle 7 Call Interface невозможно. Поэтому, не следует производить операции вставки и обновления с полями, определенными как NCHAR и NVARCHAR. Однако, поскольку Oracle будет производить необходимые преобразования, операция SELECT работать будет.

VARCHAR более 2000 байтов

В Oracle 8 максимальный размер столбца VARCHAR был увеличен с 2000 до 4000 байтов. Однако, клиентское приложение Oracle 7 не имеет способа работать со значениями параметров более 2000 байтов. При создании таблицы со столбцом VARCHAR более 2000 байтов, возможности выполнять параметризованные вставки, обновления, удаления и запросы с данными, превышающими по своему размеру предел в 2000 байтов, нет. Поскольку драйвер ODBC для Oracle, как и OLE DB Provider для Oracle поддерживают параметризованные вставки, обновления, удаления и запросы, в этом случае они создают отчет об ошибках ORA-01026. Однако, данные приложения клиента, которые отвечают требованиям, предъявляемым Oracle, работать будут.

Способом избежать этого является обновление клиентского приложения под Oracle 8 (8.0.4.1.1с или более поздний).

Параметры REF CURSOR

Oracle предоставляет метод передачи ссылок курсора в качестве параметров ввода-вывода хранимым процедурам PL/SQL. При создании хранимой процедуры и определении параметра с типом данных REF CURSOR, можно привязать OCI-курсор к параметру.

OLE DB Provider для Oracle позволит определить их как DBTYPE_UNKNOWN, а затем появится возможность связать их, причем элемент pObject, входящий в структуру DBBINDING, должен быть IID_IRowset. Однако, поскольку некоторые характеристики еще должны быть доработаны, эта версия провайдера пока не поддерживает такую возможность.

{resultset...} Отказ от драйвера ODBC

Для получения этой модели поведения используйте OLE DB Provider для драйверов ODBC и Microsoft ODBC Driver для Oracle.

Точки защиты

На текущий момент, провайдер не поддерживает транзакций, с помощью которых он мог бы предоставить точки защиты.


Copyright © 1994-2016 ООО "К-Пресс"