Access и ODBC (часть 2)

Уровни функциональных возможностей ODBC API

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

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

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

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

Уровни соответствия SQL

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

Как и в случае уровней ODBC API, существуют функции, позволяющие определить, какие операторы и типы данных поддерживает драйвер.

Более сложная задача

В предыдущей части я описал лишь основы использования интерфейса доступа к данным ODBC. Создание полнофункциональных приложений, основанных непосредственно на этом API, по сравнению с разработкой на MS Access, представляет собой исключительно трудную задачу.

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

Перво-наперво, откажемся от использования классов для «обёртывания» функций, отвечающих за выделение и возврат ресурсов. Это не прибавит нашей программе надёжности, но зато значительно сократит её объём и улучшит читаемость кода. А функций таких будет много - полный комплект для всех трёх уровней структуры API (окружение-соединение-оператор).

Далее, дополним модуль ODBC_API новыми объявлениями констант и функций:

'Константы ODBC
'Константы для SQLDriverConnect
Global Const SQL_DRIVER_NOPROMPT = 0
Global Const SQL_DRIVER_COMPLETE = 1
Global Const SQL_DRIVER_PROMPT = 2
Global Const SQL_DRIVER_COMPLETE_REQUIRED = 3

'Константы для FreeStmt
Global Const SQL_CLOSE = 0
Global Const SQL_DROP = 1
Global Const SQL_UNBIND = 2
Global Const SQL_RESET_PARAMS = 3

'Типы данных
Global Const SQL_C_CHAR = 1

'Декларации функций ODBC
Declare Function SQLDriverConnect Lib "odbc32.dll" (ByVal HDBC As Long, _
                                                    ByVal hWnd As Long, _
                                                    ByVal ConStrIn As String, _
                                                    ByVal ConStrInMax As Integer, _
                                                    ByVal ConStrOut As String, _
                                                    ByVal ConStrOutMax As Integer, _
                                                    ByRef ConStrOutLen As Long, _
                                                    ByVal DriverCompleation As Integer) As Integer
                                                    
Declare Function SQLDisconnect Lib "odbc32.dll" (ByVal HDBC As Long) As Integer

Declare Function SQLTables Lib "odbc32.dll" (ByVal HSTMT As Long, _
                                             ByVal TableQualifier As String, _
                                             ByVal TableQualifierLen As Integer, _
                                             ByVal TableOwner As String, _
                                             ByVal TableOwnerLen As Integer, _
                                             ByVal TableName As String, _
                                             ByVal TableNameLen As Integer, _
                                             ByVal TableType As String, _
                                             ByVal TableTypeLen As Integer) As Integer

Declare Function SQLFetch Lib "odbc32.dll" (ByVal HSTMT As Long) As Integer

Declare Function SQLGetData Lib "odbc32.dll" (ByVal HSTMT As Long, _
                                              ByVal ColNumber As Long, _
                                              ByVal DataType As Integer, _
                                              ByVal DataValue As String, _
                                              ByVal DataValueMax As Integer, _
                                              ByRef DataValueLen As Long) As Integer

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

Список драйверов заполним по аналогии с предыдущим примером, только не будем создавать и наполнять таблицу. Вместо этого, в качестве типа источника строк выберем список значений, а при загрузке формы запишем в переменную наименования всех найденных драйверов и присвоим её свойству «Источник строк» списка.

Следующий важный шаг - установить соединение с сервером. Информация, которая должна понадобиться - это, кроме имени драйвера, имена сервера и БД, а также логин и пароль пользователя, от лица которого будет осуществляться подключение. Вся вместе она образует так называемую строку подключения (Connection String), где перечисляются соответствующие ключи и их значения:

  strConStr = "DRIVER=" & Me.lbxDrivers & _
              ";SERVER=" & Me.txtServer & _
              ";DATABASE=" & Me.txtDataBase & _
              ";UID=" & Me.txtLogin & _
              ";PWD=" & Me.txtPassword & ";"

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

Public Function OpenConnection(HENV As Long, ConnectionString As String) As Long
Dim intStatus As Integer                      'Для кодов возврата функций ODBC.
Dim lngHDBC As Long                           'Идентификатор соединения.
Dim strConStrIn As String * MAX_DATA_BUFFER   'Строковый буфер для передачи данных.
Dim strConStrOut As String * MAX_DATA_BUFFER  'Строковый буфер для получения данных.
Dim lngConStrOutLen As Long                   'Реальная длина данных в буфере.

  OpenConnection = 0

  If Len(ConnectionString) < MAX_DATA_BUFFER Then 'Строка не должна быть слишком длинной.
    strConStrIn = ConnectionString & Chr(0) 'Добавим нулевой символ как знак завершения строки.
    intStatus = SQLAllocConnect(HENV, lngHDBC)  'Получим идентификатор соединения.
    If intStatus = SQL_SUCCESS Then  'Если соединение успешно создано, пытаемся открыть его.
      intStatus = SQLDriverConnect(lngHDBC, _
                                   0, _
                                   strConStrIn, _
                                   MAX_DATA_BUFFER, _
                                   strConStrOut, _
                                   MAX_DATA_BUFFER, _
                                   lngConStrOutLen, _
                                   SQL_DRIVER_NOPROMPT)
      If intStatus = SQL_SUCCESS Then  'Если соединение успешно открыто, возвращаем его идентификатор.
        OpenConnection = lngHDBC
      Else 'Иначе - освободим ресурсы, выделенные соединению, а функция вернёт 0.
        intStatus = SQLFreeConnect(lngHDBC)
      End If
    End If
  End If

End Function

Основа нашей функции - функция ODBC API SQLDriverConnect. Первым параметром в ней идёт уже знакомый нам идентификатор соединения, который надо предварительно получить с помощью функции SQLAllocConnect. Второй параметр - идентификатор окна, которое будет являться родительским для окна диалога ввода параметров соединения. Этот диалог обеспечивается используемым драйвером ODBC, если мы того захотим и укажем в последнем параметре вызова функции соответствующий режим. В нашем случае диалог не требуется и поэтому мы передаём нулевой идентификатор окна (в противном случае можно передать, например, hWndAccessApp) и SQL_DRIVER_NOPROMPT соответственно.

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

После того, как соединение установлено, подготовим идентификатор оператора с помощью функции SQLAllocStmt и вызовем функцию SQLTables. Эта функция возвращает набор сведений о таблицах (TABLE), представлениях (VIEW) и других аналогичных объектах базы данных. Использовать её нужно также, как и все функции, работающие с результирующим множеством, каким является, например, результат выполнения запроса на выборку.

В двух словах, порядок действий такой: сначала нужно определить количество столбцов в результирующем множестве (SQLNumResultCols), затем их имена (SQLColumns) и типы данных (SQLDescribeCol). Для нашего примера всё это не нужно, так как точно известно, что имена таблиц содержатся в третьем столбце, имеют символьный тип данных, а название столбца не имеет значения. Теперь можно выделить буферы для данных каждого из столбцов с помощью функции SQLBindCol. Мы этого делать не будем, а воспользуемся другим способом извлечения данных (SQLGetData). Далее необходимо, устанавливая указатель на нужную запись с помощью функции SQLFetch или SQLExtendedFetch (последняя поддерживается не всеми драйверами), считывать данные столбца в подготовленную для них переменную.

Как видите, всё это очень сложно и громоздко. Поэтому, многими фирмами разработаны различные программные компоненты более высокого уровня, существенно облегчающие использование таких интерфейсов, как ODBC. В MS Access для этого можно использовать механизмы присоединённых таблиц и запросов к серверу. В программах на VBA удобно использовать объекты доступа к данным DAO. Все перечисленные механизмы имеют ряд особенностей, которые необходимо знать, учитывать и использовать при работе с данными через интерфейс ODBC. Однако, это уже тема для отдельного разговора.

Смирнов Сергей Юрьевич (SSY) ssmirnov@fromru.com



Опубликовал admin
16 Сен, Четверг 2004г.



Программирование для чайников.