3. Лекция: Механизм выполнения SQL-операторов

Приводится схема доступа к источнику данных с использованием ODBC API.

Схема доступа к источнику данных с использованием ODBC API

Первым шагом при реализации доступа к источнику данных посредством ODBC API без применения пула соединений является создание дескриптора окружения. После выделения памяти под дескриптор окружения приложение должно вызвать функцию SQLSetEnvAttr для задания значения атрибуту дескриптора окружения SQL_ATTR_ODBC_VERSION. Если не установить номер версии ODBC API, то при создании дескриптора соединения функция SQLAllocHandle вернет код ответа SQLSTATE равным HY010, что соответствует коду произошедшей ошибки.

После создания дескриптора окружения создаются дескрипторы соединений. Каждый дескриптор соединения формируется вызовом функции SQLAllocHandle с типом дескриптора, равным SQL_HANDLE_DBC. Драйвер выделяет память для хранения информации о соединении и возвращает значение дескриптора соединения. Далее для реального соединения с источником данных вызывается функция SQLConnect или функция SQLDriverConnect.

Для выполнения SQL-операторов создаются дескрипторы операторов. Они позволяют получить доступ к информации о выполненном операторе, имени курсора и атрибутах. Дескриптор оператора формируется вызовом функции SQLAllocHandle со значением типа дескриптора, равным SQL_HANDLE_STMT.

При создании дескриптора оператора драйвер автоматически создает еще четыре дескриптора и записывает указатели на них в атрибуты дескриптора оператора SQL_ATTR_APP_ROW_DESC, SQL_ATTR_APP_PARAM_DESC, SQL_ATTR_IMP_ROW_DESC и SQL_ATTR_IMP_PARAM_DESC. Эти четыре дескриптора называются неявно размещаемыми дескрипторами.

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

Приложение может указать драйверу использовать явно размещенный дескриптор приложения вместо автоматически созданного для данного дескриптора оператора. Для этого следует вызвать функцию SQLSetStmtAttr с атрибутом SQL_ATTR_APP_ROW_DESC или SQL_ATTR_APP_PARAM_DESC.

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

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

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

Последовательность действий приложения-клиента для реализации доступа к источнику данных
Последовательность действий приложения-клиента для реализации доступа к источнику данных

Соединение с источником данных

Для непосредственного подключения к базе данных ODBC API предоставляет следующие три функции:

  • SQLConnect - соединение с источником данных по DSN, имени и паролю пользователя
  • SQLDriverConnect - соединение с источником данных по указанной строке соединения или при помощи отображаемого диалога для интерактивного ввода параметров соединения;
  • SQLBrowseConnect - соединение с источником данных с предварительным последовательным запросом атрибутов соединения.

Функция SQLConnect имеет следующее формальное описание:

SQLRETURN SQLConnect(
     SQLHDBC     ConnectionHandle,
     SQLCHAR *     ServerName,
     SQLSMALLINT     NameLength1,
     SQLCHAR *     UserName,
     SQLSMALLINT     NameLength2,
     SQLCHAR *     Authentication,
     SQLSMALLINT     NameLength3);

Параметр ConnectionHandle ([Input]) указывает используемый дескриптор соединения, параметр ServerName ([Input]) - имя источника данных. Параметры UserName ([Input]) и Authentication ([Input]) описывают имя пользователя и пароль, а параметры NameLength1 ([Input]), NameLength2 ([Input]) и NameLength3 ([Input]) определяют длину параметров *ServerName, *UserName и *Authentication соответственно.

Например:

SQLConnect(hdbc, (SQLCHAR*) "MySQLDB", SQL_NTS,
                  (SQLCHAR*) "", SQL_NTS,
                  (SQLCHAR*) "", SQL_NTS);

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

SQLRETURN SQLDriverConnect(
     SQLHDBC     ConnectionHandle,
     SQLHWND     WindowHandle,
     SQLCHAR *     InConnectionString,
     SQLSMALLINT     StringLength1,
     SQLCHAR *     OutConnectionString,
     SQLSMALLINT     BufferLength,
     SQLSMALLINT *     StringLength2Ptr,
     SQLUSMALLINT     DriverCompletion);

Параметр ConnectionHandle ([Input]) указывает дескриптор соединения; параметр WindowHandle ([Input]) - это указатель родительского окна или NULL.

Параметр InConnectionString ([Input]) задает полностью или частично строку соединения или пустую строку, а параметр StringLength1 ([Input]) - длину в байтах строки *InConnectionString. Строка соединения позволяет описывать атрибуты соединения в текстовом формате. Пары атрибут=значение разделяются между собой символом "точка с запятой".

Параметр OutConnectionString ([Output]) - это указатель на буфер, в котором после успешного подключения к источнику данных возвращается полная строка соединения. Размер этого буфера задается параметром BufferLength ([Input]) и должен быть не менее 1024 байт. Если вызывается функция SQLDriverConnectW и строка соединения указывается в символах Unicode, то размер буфера должен содержать четное число байтов.

Параметр StringLength2Ptr ([Output]) возвращает указатель на буфер, в котором размещается общее число символов полной строки соединения. Если размер буфера *OutConnectionString, указанный параметром BufferLength, меньше, чем требуется, то возвращаемая строка соединения усекается до размера буфера минус длина null-символа.

Параметр DriverCompletion ([Input]) - это флажок, указывающий, будет ли менеджер драйверов и драйвер предлагать диалоги для формирования завершенной строки соединения. Этот параметр определяется следующими значениями:

  • SQL_DRIVER_PROMPT - подсказка предлагается и учитывается даже в том случае, если значение атрибута уже задано в строке соединения. Первоначально отображается окно с доступными источниками данных (для атрибута DSN);
  • SQL_DRIVER_COMPLETE - подсказка предлагается только в том случае, если требуемый для подключения атрибут не задан в строке соединения;
  • SQL_DRIVER_COMPLETE_REQUIRED - подсказка предлагается только в том случае, если требуемый для подключения атрибут не задан в строке соединения и при этом запрашиваются только необходимые значения атрибутов;
  • SQL_DRIVER_NOPROMPT - подсказки не предлагаются.

Строка соединения, передаваемая в качестве параметра, имеет следующее формальное описание:

строка_соединения ::=   пустая_строка[;] | 
                        атрибут[;] | 
                        атрибут; строка_соединения		
атрибут ::= 		ключевое_слово =значение  | 
                        DRIVER=[{]значение [}]	
ключевое слово ::= DSN | UID | PWD	
                   | идентификатор_используемый_драйвером

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

  • DSN - имя источника данных (функция SQLDataSources возвращает список доступных источников данных);
  • FILEDSN - имя .dsn файла, из которого будет прочитана строка соединения;
  • DRIVER - описание драйвера (список доступных драйверов возвращается функцией SQLDrivers);
  • UID - идентификатор пользователя;
  • PWD - для указанного идентификатора пользователя или при отсутствии пароля пустая строка (PWD=;);
  • SAVEFILE - имя .dsn файла, в который будет записана строка соединения, используемая для данного успешного подключения к источнику данных.

Ключевые слова DSN и FILEDSN являются в строке соединения взаимоисключающими: будет использовано первое из указанных. С остальными ключевыми словами FILEDSN не является взаимоисключающим: приоритет имеет значение, указанное непосредственно в строке состояния. Очевидно, что значение ключевого слова PWD не сохраняется в .dsn файле.

По умолчанию, каталогом для сохранения и загрузки .dsn файла будет комбинация пути, указанного как CommonFileDir в разделе реестра Windows HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ Windows\CurrentVersion, и пути ODBC\DataSources.

На рис. 3.1 отображен соответствующий раздел реестра Windows (в данном примере .dsn файлы будут сохраняться в каталоге C:\Program Files\Common Files\ODBC\Data Sources).

Параметр CommonFileDir из реестра Windows
Рис. 3.1.  Параметр CommonFileDir из реестра Windows

Функция SQLBrowseConnect реализует итерационный метод запроса значений атрибутов, требуемых для подключения к базе данных, возвращая каждый раз код ответа SQL_NEED_DATA и идентификатор очередного запрашиваемого атрибута. После определения значений всех необходимых атрибутов функция устанавливает соединение с базой данных и при успешном завершении операции возвращает код ответа, равный SQL_SUCCESS или SQL_SUCCESS_WITH_INFO.

Функция SQLBrowseConnect имеет следующее формальное описание:

SQLRETURN SQLBrowseConnect(
     SQLHDBC     ConnectionHandle,
     SQLCHAR *     InConnectionString,
     SQLSMALLINT     StringLength1,
     SQLCHAR *     OutConnectionString,
     SQLSMALLINT     BufferLength,
     SQLSMALLINT *     StringLength2Ptr);

Параметр ConnectionHandle ([Input]) определяет дескриптор соединения, параметр InConnectionString ([Input]) описывает строку подключения или ее часть, указанную при предыдущем вызове функции.

Параметр StringLength1 ([Input]) задает длину буфера*InConnectionString.

Параметр OutConnectionString ([Output]) определяет указатель на буфер, содержащий информацию о недостающем атрибуте строки соединения (например, в буфере может быть возвращено следующее значение: "HOST:Server={MySR1,S2,S3};UID:ID=?;PWD:Password=?").

Параметр BufferLength ([Input]) задает длину буфера*OutConnectionString.

Параметр StringLength2Ptr ([Output]) указывает общее число байтов, которое должно быть возвращено в буфере *OutConnectionString. Если размер этого буфера меньше, чем требуется, то возвращаемое значение будет усечено.

Например:

retcode = SQLAllocHandle(SQL_HANDLE_ENV, 
      SQL_NULL_HANDLE, &henv)
if (retcode == SQL_SUCCESS || 
    retcode == SQL_SUCCESS_WITH_INFO) 
 {
   retcode = SQLSetEnvAttr(henv,
      SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3, 0);
   if (retcode == SQL_SUCCESS || 
       retcode == SQL_SUCCESS_WITH_INFO) 
     {
      /* Создание дескриптора соединения */
      retcode = SQLAllocHandle(SQL_HANDLE_DBC, 
                               henv, &hdbc);
      if (retcode == SQL_SUCCESS || 
          retcode == SQL_SUCCESS_WITH_INFO) 
        {
         /* Вызов SQLBrowseConnect до тех пор пока*/
          /*код ответа будет  SQL_NEED_DATA  */

         lstrcpy(szConnStrIn, "DSN=MeDB");
         do {  retcode = SQLBrowseConnect(hdbc,
                           szConnStrIn, SQL_NTS,
                         szConnStrOut, BRWS_LEN,
                         &cbConnStrOut);
               if (retcode == SQL_NEED_DATA)
               /* Вызов функции пользователя, 
                  возвращающей запрашиваемое 
                  значение атрибута*/
                 GetValueAttr(szConnStrOut, 
                              szConnStrIn);
             } while (retcode == SQL_NEED_DATA);

         if (retcode == SQL_SUCCESS || 
         retcode == SQL_SUCCESS_WITH_INFO){

             /* Соединение установлено и можно
            формировать дескриптор оператора. */
            retcode = SQLAllocHandle(SQL_HANDLE_STMT,
                                      hdbc, &hstmt);
             }
            // ...
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
            }
SQLDisconnect(hdbc);
         }
      }
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
   }
}
SQLFreeHandle(SQL_HANDLE_ENV, henv);

Пул соединений

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

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

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

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

Пул соединений управляется менеджером драйверов. Соединение выбирается из пула при вызове приложением функции SQLConnect или функции SQLDriverConnect, а возвращается в пул при выполнении функции SQLDisconnect. Размер пула изменяется динамически: если соединение не было использовано в течение определенного периода времени, то оно удаляется из пула.

Используя атрибут SQL_ATTR_CONNECTION_DEAD в версии ODBC API 3.х, менеджер драйверов может определить состояние соединения из пула: значение SQL_CD_TRUE определяет, что соединение разорвано, а значение SQL_CD_FALSE означает, что соединение пока еще активно. При этом обычно для получения значения данного атрибута драйвер не обращается к серверу, а возвращает результат, основываясь на состоянии соединения при его последнем использовании.

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

HKEY_LOCAL_MACHINE\
  Software\Odbc\Odbcinst.ini\
   ODBC Connection Pooling\Retry Wait

Для использования приложением пула соединений следует:

  1. Для включения режима пула соединений вызвать функцию SQLSetEnvAttr с атрибутом среды SQL_ATTR_CONNECTION_POOLING, равным значению SQL_CP_ONE_PER_DRIVER или значению SQL_CP_ONE_PER_HENV. При вызове функции SQLSetEnvAttr дескриптор среды указывается равным значению NULL, что определяет атрибут SQL_ATTR_CONNECTION_POOLING как атрибут уровня процесса. Значение SQL_CP_ONE_PER_DRIVER определяет, что отдельный пул соединений поддерживается для каждого драйвера. При необходимости иметь один пул для различных драйверов указывается значение SQL_CP_ONE_PER_HENV (отдельный пул соединений поддерживается для каждой среды).
  2. Создать дескриптор среды, вызвав функцию SQLAllocHandle со значением параметра типа дескриптора равным SQL_HANDLE_ENV. Созданная среда будет неявно разделяемой средой.
  3. Создать дескриптор соединения, вызвав функцию SQLAllocHandle со значением параметра типа дескриптора, равным SQL_HANDLE_DBC. Менеджер драйверов будет искать существующую разделяемую среду с соответствующими атрибутами среды (при нахождении требуемой среды она возвращается приложению и менеджер драйверов увеличивает значение счетчика на 1).Если такой среды нет, то менеджер драйверов создает ее и устанавливает значение счетчика равным 1.
  4. Для получения соединения из пула вызвать функцию SQLConnect или SQLDriverConnect. Менеджер драйверов использует значения параметров и значения атрибутов соединения для определения требуемого соединения из пула соединений. При этом учитывается значение атрибута SQL_ATTR_CP_MATCH (соответствие требуемого соединения соединению из пула).
  5. Для разрыва соединения вызвать функцию SQLDisconnect. При этом соединение возвращается обратно в пул и делается доступным для дальнейшего использования.

Для отключения режима пула соединений следует установить значение атрибута SQL_ATTR_CONNECTION_POOLING равным SQL_CP_OFF.