Общие замечания

Аббревиатура ODBC является сокращением для Open DataBase Connectivity, что можно перевести как «открытый интерфейс доступа к базам данных». Этот интерфейс представляет собой набор функций, которые можно использовать для доступа к любой реляционной СУБД, поддерживающей SQL. На уровне операционной системы ODBC реализуется в виде группы DLL-библиотек, состоящей из драйверов отдельных баз данных (ODBC-драйверов) и так называемого менеджера драйверов, выполняющего роль прослойки между приложением-клиентом и ODBC-драйвером; именно наличие такой прослойки и обеспечивает независимость приложения от конкретного сервера БД.

Последняя версия ODBC, выпущенная в 1997 году, имеет номер 3.51; как показывает практика, для решения большинства типичных задач бывает достаточно версий 2.x, выходивших в период с 1993 по 1995 год. Заметим, что Microsoft официально прекратила развитие ODBC, предлагая в качестве замены технологию ADO (Active Data Objects), которая представляет собой ни что иное, как построенную на базе COM оболочку ядра ODBC.

В основе ODBC лежат следующие четыре понятия:

  1. statement (оператор) — содержит произвольную инструкцию SQL и, возможно, результат ее выполнения; каждый оператор идентифицируется дескриптором, имеющим тип HSTMT, используя этот дескриптор можно получить доступ к состоянию оператора и результату его выполнения;
  2. connection (соединение) — представляет собой канал обмена данными между приложением-клиентом и сервером БД; идентифицируется дескриптором, имеющим тип HDBC; с каждым таким соединением могут быть связаны один или несколько операторов;
  3. environment (окружение) — глобальный контекст, в котором выполняется доступ к данным; идентифицируется дескриптором, имеющим тип HENV, и может содержать дескрипторы одного или нескольких соединений;
  4. data source (источник данных) — собственно база данных, с которой работает приложение.
Заголовочные файлы #include <sql.h>
#include <sqlext.h>

Типы данных, используемые в ODBC

SQL API
(родовой тип)
SQL for C
(C-псевдоним)
Тип ANSI C Префикс
в венгерской
нотации
SQLINTERGER SDWORD long sdw
SQLUINTEGER UDWORD unsigned long udw
SQLSMALLINT SWORD short sw
SQLUSMALLINT UWORD unsigned short uw
RETCODE short sw
SLONG long sn
ULONG unsigned long un
SSHORT short ss
USHORT unsigned short us
SQLDOUBLE SDOUBLE double sd
SQLREAL SFLOAT float sf
SQLCHAR UCHAR unsigned char uc
SQLSCHAR SCHAR signed char sc
SQLTCHAR unsigned char/short tc
SQLPOINTER void* pv

Примечание: фактический тип переменных, объявленных как SQLTCHAR, зависит от того, определен ли при компиляции макрос UNICODE.

Порядок работы с базой данных

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

1. Создание .dsn файла

Этот файл определяет формат базы данных и ее физическое расположение, а также параметры работы с этой БД ее ODBC-драйвера. Он является обычным текстовым файлом в формате Windows .INI и может быть создан либо вручную, либо с помощью Control Panel. Под Windows 95/98 соответствующий значок называется ODBC Data Sources (32-bit), а под Windows 2000 он «скрывается» в папке Administrative Tools под именем Data Sources (ODBC).

В окне ODBC Data Source Administrator переходим на вкладку File DSN. По умолчанию все .dsn файлы сохраняются в папке Program Files\Common Files\ODBC\Data Sources, однако их физическое местоположение никак не влияет на работу приложения. Для создания нового .dsn файла нажимаем на кнопку Add… и в появившемся диалоговом окне Create New Data Source выбираем подходящий из списка установленных ODBC-драйверов. Нажимаем на кнопку Next > и вводим имя создаваемого файла (кнопка Browse… позволяет не только обозвать файл, но и выбрать папку, в которой он будет сохранен). Еще раз нажимаем Next >, проверяем, правильно ли заданы имя файла и тип драйвера, нажимаем Finish и попадаем в окно Configure Connection, внешний вид которого зависит от выбранного типа драйвера (например, для драйвера Visual FoxPro здесь можно задать путь к файлам базы данных и параметры сортировки). Теперь окно Администратора можно закрыть.

Примечание: готовое приложение должно поставляться вместе с .dsn файлом или уметь корректно генерировать его при необходимости, что предпочтительнее.

2. Создание дескриптора окружения

Для этого используется функция

RETCODE SQLAllocEnv(
   HENV* phenv
);

В переменную по адресу phenv будет записан созданный дескриптор окружения. Пример использования:

HENV hEnv = SQL_NULL_HENV;
SQLAllocEnv(&hEnv);

3. Установление соединения

Вначале необходимо создать дескриптор соединения с помощью функции

RETCODE SQLAllocConnect(
   HENV henv,
   HDBC* phdbc
);

Через параметр henv передается дескриптор окружения, в контексте которого будет выполняться соединение. Созданный дескриптор соединения будет записан в переменную по адресу phdbc. Пример использования:

HDBC hDbc = SQL_NULL_HDBC;
SQLAllocConnect(hEnv, &hDbc);

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

RETCODE SQLSetConnectOption(
   HDBC hdbc,
   SWORD swOption,
   SDWORD sdwValue
);

Через параметр hdbc передается дескриптор соединения; параметр swOption должен содержать идентификатор характеристики, а параметр sdwValue — назначаемое ей значение. Например, для того, чтобы драйвер БД самостоятельно обслуживал результаты выполнения операторов, используется вызов

SQLSetConnectOption(hDbc, SQL_ODBC_CURSORS, SQL_CUR_USE_DRIVER);

Последним шагом является собственно установление соединения, выполняемое с помощью функции

RETCODE SQLDriverConnect(
   HDBC hdbc,
   HWND hWnd,
   UCHAR* pConnStr,
   SWORD swConnStrLen,
   UCHAR* pCompStr,
   SWORD swCompStrLen,
   SWORD* pswCompStrLen,
   UWORD uwDrvCompletion
);

Описание параметров:

hdbc
Дескриптор соединения.
hWnd
Дескриптор окна, поверх которого будут выводиться диалоговые окна, если потребуется ввод дополнительной информации пользователем. Может быть равен NULL.
pConnStr
Строка, определяющая источник данных. При использовании .dsn файла она должна иметь вид FILEDSN=полное_имя_файла.
swConnStrLen
Длина строки, адресуемой параметром pConnStr. Для строк, завершающихся двоичным нулем (в частности, строк C/C++), можно указывать значение SQL_NTS.
pCompStr
Адрес буфера для записи в него completion string (заполненной строки), полностью определяющей источник данных. При использовании .dsn файла в этот буфер будет скопирован его текст.
swCompStrLen
Размер буфера, адресуемого параметром pCompStr.
pswCompStrLen
Адрес переменной, в которую будет записана реальная длина строки, скопированной по адресу pCompStr. Этот параметр можно задать равным NULL.
uwDrvCompletion
Определяет, должен ли драйвер запрашивать у пользователя ввод информации, если она необходима для установления соединения. Параметр может принимать одно из следующих значений:
SQL_DRIVER_PROMPT
всегда запрашивать ввод информации;
SQL_DRIVER_COMPLETE
запрашивать ввод информации в том случае, если ее недостаточно или она ошибочна;
SQL_DRIVER_NOPROMPT
не запрашивать ввод информации и выводить сообщение, если ее недостаточно или она ошибочна.
Как показывает практика, наиболее разумным является использование последнего значения, поскольку при указании SQL_DRIVER_COMPLETE некоторые драйвера запрашивают у пользователя подтверждение даже корректной и достаточной информации.

4. Выполнение оператора SQL

Вначале необходимо создать дескриптор оператора с помощью функции

RETCODE SQLAllocStmt(
   HDBC hdbc,
   HSTMT* phstmt
);

Через параметр hdbc необходимо передать дескриптор соединения, с которым будет ассоциирован данный оператор. Созданный дескриптор оператора будет записан в переменную по адресу phstmt.

Для выполнения оператора можно использовать функцию

RETCODE SQLExecDirect(
   HSTMT hstmt,
   UCHAR* pStmtText,
   SDWORD sdwStmtLen
);

Через параметр hstmt передается дескриптор оператора, а через параметр pStmtText — текст оператора на SQL. Параметр sdwStmtLen должен содержать длину этого текста; для строк, завершающихся двоичным нулем, указывается значение SQL_NTS.

Частным случаем оператора SQL является SQL-запрос, пример выполнения которого приведен ниже:

HSTMT hStmt = SQL_NULL_HSTMT;
SQLAllocStmt(hDbc, &hStmt);
SQLExecDirect(hStmt, "SELECT * FROM Customers", SQL_NTS);

В результате выполнения SQL-запроса в памяти создается таблица, называемая в терминах ODBC курсором, которая содержит все отобранные данные. Работа с данными этой таблицы рассматривается ниже.

5. Получение информации о результатах выполнения оператора

Для получения количества колонок (полей) в результирующей таблице используется функция

RETCODE SQLNumResultCols(
   HSTMT hstmt,
   SWORD* pswNumCols
);

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

Для получения атрибутов заданной колонки можно воспользоваться функцией

RETCODE SQLColAttributes(
   HSTMT hstmt,
   UWORD uwColNumber,
   UWORD uwAttrID,
   void* pCharAttr,
   SWORD swMaxCharAttr,
   SWORD* pswCharAttrLen,
   void* pNumAttr
);

Описание параметров:

hstmt
Дескриптор оператора.
uwColNumber
Порядковый номер столбца, начиная с 1.
uwAttrID
Идентификатор запрашиваемого атрибута. Можно задавать одно из следующих значений:
SQL_COLUMN_NAME
имя колонки (поля) в БД;
SQL_COLUMN_LENGTH
размер данных в колонке (размер поля) в символах;
SQL_COLUMN_TYPE
тип данных в колонке (тип поля).
pCharAttr
Адрес буфера, в который будет записано значение атрибута, если этот атрибут строковый (например, SQL_COLUMN_NAME).
swMaxCharAttr
Размер буфера, адресуемого параметром pCharAttr.
pswCharAttrLen
Адрес переменной, в которую будет записана реальная длина строки, скопированной по адресу pCharAttr.
pNumAttr
Адрес переменной, в которую будет записано значение атрибута, если этот атрибут числовой (например, SQL_COLUMN_LENGTH). Как правило, такая переменная должна иметь тип SDWORD.

При получении значений строковых атрибутов параметр pNumAttr можно задавать равным NULL, а при получении значений числовых атрибутов можно задавать нулевыми параметры pCharAttr, swMaxCharAttr и pswCharAttrLen.

ODBC предусматривает следующие типы полей:

название описание
SQL_UNKNOWN_TYPEнеизвестный тип
SQL_CHARстрока
SQL_INTERGERдлинное целое
SQL_SMALLINTкороткое целое
SQL_FLOAT4-байтовое вещественное
SQL_DOUBLE8-байтовое вещественное
SQL_DATETIMEдата/время
SQL_NUMERICчисловой тип
SQL_DECIMALдесятичный тип

6. Доступ к результатам выполнения оператора

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

RETCODE SQLBindCol(
   HSTMT hstmt,
   UWORD uwColNumber,
   SWORD swColType,
   void* pDest,
   SDWORD sdwDestSize,
   SDWORD* psdwInd
);

Описание параметров:

hstmt
Дескриптор оператора.
uwColNumber
Порядковый номер колонки, начиная с 1.
swColType
Тип данных в колонке. Бывает, что его приходится подбирать опытным путем.
pDest
Адрес буфера, в который будет записываться значение поля.
sdwDestSize
Размер буфера, адресуемого параметром pDest, в байтах.
psdwInd
Адрес переменной, в которую будет записываться информация о результатах получения данных.

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

После связывания всех необходимых колонок выполняется построчное чтение данных. В терминах ODBC эта операция называется fetching и выполняется с помощью функции

RETCODE SQLFetch(
   HSTMT hstmt
);

Через параметр hstmt передается дескриптор оператора. Каждый вызов этой функции записывает в переменные, связанные с колонками, значения соответствующих полей текущей записи, после чего делает текущей следующую запись. Функция должна циклически вызываться до тех пор, пока не будет достигнут конец результирующей таблицы (то есть, пока она возвращает значение SQL_SUCCESS или SQL_SUCCESS_WITH_INFO). Для проверки возвращаемого значения можно воспользоваться макросом

SQL_SUCCEEDED(возвр_знач)

и организовать, например, следующий цикл:

while (SQL_SUCCEEDED(SQLFetch(hStmt)))
{
   // копируем данные из переменных
   // в соответствующие элементы управления
}

7. Освобождение системных ресурсов

После завершения работы с базой данных необходимо «удалить» каждый созданный оператор с помощью функции

RETCODE SQLFreeStmt(
   HSTMT hstmt,
   UWORD uwOption
);

Через параметр hstmt передается дескриптор удаляемого оператора, а через параметр uwOption — характер удаления. Для полного освобождения системных ресурсов, занятых оператором и результатами его выполнения, необходимо сделать два последовательных вызова:

SQLFreeStmt(hStmt, SQL_CLOSE);
SQLFreeStmt(hStmt, SQL_DROP);

После удаления всех операторов необходимо разорвать соединение с сервером БД вызовом функции

RETCODE SQLDisconnect(
   HDBC hdbc
);

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

RETCODE SQLFreeConnect(
   HDBC hdbc
);

В обоих случаях, через параметр hdbc передается дескриптор разрываемого соединения.

Последним шагом является удаление дескриптора окружения, выполняемое при помощи функции

RETCODE SQLFreeEnv(
   HENV henv
);

которой через параметр henv нужно передать подлежащий удалению дескриптор.

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

Определение количества строк в результирующей таблице

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

RETCODE SQLSetStmtOption(
   HSTMT hstmt,
   UWORD uwOption,
   UDWORD udwValue
);

Она позволяет для оператора hstmt назначить значение атрибута с идентификатором uwOption равным udwValue. В нашем случае необходимо в качестве идентификатора атрибута указать значение SQL_CURSOR_TYPE (тип курсора), а в качестве назначаемого значения этого атрибута — SQL_CURSOR_STATIC (статический курсор).

Начиная с ODBC версии 3.0 можно также воспользоваться функцией

   RETCODE SQLSetStmtAttr(
   HSTMT hstmt,
   SDWORD sdwAttr,
   void* pValue,
   SDWORD sdwStrLen
);

Параметр hstmt, как и в предыдущем случае, определяет оператор, заданный атрибут которого необходимо изменить. Идентификатор изменяемого атрибута передается через параметр sdwAttr и в нашем случае должен быть задан равным SQL_ATTR_CURSOR_TYPE. Назначаемое значение необходимо передать через параметр pValue, причем для строковых атрибутов он должен содержать адрес строки (SQLCHAR*), а для числовых — величину типа UDWORD. Еще раз подчеркнем, что в последнем случае необходимо передать значение атрибута, приведенное к типу void*. Для создания статического курсора, как и в предыдущем случае, используется значение SQL_CURSOR_STATIC. Через параметр sdwStrLen необходимо передать длину строки, адресуемой параметром pValue; для числовых атрибутов значение этого параметра игнорируется и может быть задано равным нулю.

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

Для перемещения по строкам статического курсора можно воспользоваться универсальной функцией

RETCODE SQLFetchScroll(
   HSTMT hstmt,
   SWORD swType,
   SDWORD sdwOffset
);

Через параметр hstmt передается дескриптор оператора, параметр swType задает тип перемещения, а параметр sdwOffset — требуемое смещение в результирующей таблице (курсоре). Возможные значения последнего параметра зависят от типа перемещения и приведены ниже:

swType sdwOffset
значениеописание
SQL_FETCH_FIRST перемещение к первой строке игнорируется
SQL_FETCH_LAST перемещение к последней строке игнорируется
SQL_FETCH_NEXT перемещение к следующей строке игнорируется
SQL_FETCH_PRIOR перемещение к предыдущей строке игнорируется
SQL_FETCH_ABSOLUTE перемещение к строке с номером sdwOffset номер строки
SQL_FETCH_RELATIVE перемещение на sdwOffset строк от текущей смещение

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

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

RETCODE SQLGetStmtOption(
   HSTMT hstmt,
   UWORD uwOption,
   void* pValue
);

Она позволяет для оператора hstmt получить значение атрибута с идентификатором uwOption. В нашем случае в качестве идентификатора необходимо указать значение SQL_ROW_NUMBER (номер строки). Через параметр pValue необходимо передать адрес переменной, в которую будет записано значение атрибута; при получении номера текущей строки эта переменная должна иметь тип SDWORD.

Начиная с ODBC версии 3.0 для получения атрибута оператора можно использовать функцию

RETCODE SQLGetStmtAttr(
   HSTMT hstmt,
   SDWORD sdwAttr,
   void* pValue,
   SDWORD sdwSize,
   SDWORD* psdwStrLen
);

Параметр hstmt, как и в предыдущем случае, определяет оператор, для которого требуется получить значение атрибута. Идентификатор атрибута необходимо передать через параметр sdwAttr, в нашем случае он задается равным SQL_ATTR_ROW_NUMBER. Через параметр pValue передается адрес переменной, в которую будет записано значение атрибута. Параметр sdwSize должен содержать размер буфера, адресуемого pValue; для числовых атрибутов этот параметр игнорируется, а для строк в кодировке Unicode он должен быть четным числом. И наконец, через параметр psdwStrLen передается адрес переменной, в которую будет записана длина возвращенной строки; для числовых атрибутов его можно задавать равным NULL.

Примером получения количества строк в результирующей таблице может служить следующий код:

// создаем дескриптор оператора
HSTMT hStmt = SQL_NULL_HSTMT;
SQLAllocStmt(hDbc, &hStmt);

// назначаем требуемый тип курсора
SQLSetStmtOption(hStmt, SQL_CURSOR_TYPE, SQL_CURSOR_STATIC);

// выполняем запрос
SQLExecDirect(hStmt, "SELECT * FROM Customers", SQL_NTS);

// перемещаемся к последней строке курсора
SQLFetchScroll(hStmt, SQL_FETCH_LAST, 0);

// запрашиваем номер текущей строки
SDWORD sdwNumRows = 0;
SQLGetStmtOption(hStmt, SQL_ROW_NUMBER, &sdwNumRows);

// теперь sdwNumRows == количество строк

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

Получение информации об ошибках

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

RETCODE SQLError(
   HENV henv,
   HDBC hdbc,
   HSTMT hstmt,
   UCHAR* pStateStr,
   SDWORD* psdwNativeCode,
   UCHAR* pErrorMsg,
   SDWORD sdwMaxErrMsg,
   SDWORD* psdwErrMsgLen
);

Описание параметров:

henv
Дескриптор окружения, в контексте которого произошла ошибка. Если он недоступен, то указывается значение SQL_NULL_HENV.
hdbc
Дескриптор соединения, в контексте которого произошла ошибка. Если он недоступен, то указывается значение SQL_NULL_HDBC.
hstmt
Дескриптор оператора, при обращении к которому произошла ошибка. Если он недоступен, то указывается значение SQL_NULL_HSTMT.
pStateStr
Адрес буфера, в который будет записан строковый код состояния ODBC-драйвера. Этот код всегда имеет длину 5 символов, поэтому размер буфера, адресуемого данным параметром, должен быть не менее 6 байт для строк ANSI.
psdwNativeCode
Адрес переменной, в которую будет записан численный код ошибки. Этот код специфичен для каждого ODBC-драйвера; некоторые из них вообще его не поддерживают.
pErrorMsg
Адрес буфера, в который будет записана строка с описанием возникшей ошибки. Размер этого буфера должен быть как минимум SQL_MAX_MESSAGE_LENGTH символов.
sdwMaxErrMsg
Длина буфера, адресуемого параметром pErrorMsg без учета завершающего нуля.
psdwErrMsgLen
Адрес переменной, в которую будет записана реальная длина сообщения, скопированного по адресу pErrorMsg.

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

обновлено
29.03.2006
 
Проверка PR и ТИЦ