9 Программирование на unixODBC

Цель этого учебника состоит в том, чтобы научить программиста на языке C обращению с ODBC. В качестве примера будет рассматриваться простая программа, которая соединяется с базой данных через ODBC и читает некоторые данные. Программа, показанная в качестве примера, была первоначально написана под WinNT и позже перенесена без каких-либо корректировок в Linux и unixODBC. Вот она, настоящая совместимость!

Требования

Я предполагаю, что у Вас есть:

Компиляция

Если установлен gcc, введите: gcc odbc.c -o odbc -lodbc В результате будет создан исполняемый модуль с именем odbc.

База данных

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

tkeyuser
idusersequence
dtnamechar(40)
dtmaxSizeInteger

Наш источник данных будет называться web, а доступ предоставляется пользователю christa без пароля.

Связь с источником данных

Первая вещь, в которой Вы будете заинтересованы, это переменная типа SQLHENV. Это дескриптор (указатель) на внутреннюю структуру ODBC, которая хранит всю информацию относительно ODBC-среды. Без дескриптора этого вида Вы не сможете почти ничего сделать. Чтобы получить этот дескриптор, Вы вызываете функцию SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &V_OD_Env). Здесь V_OD_Erg является переменной типа SQLHENV, которая как раз и хранит распределенный дескриптор среды.

Если Вы распределили дескриптор, Вы должны определить, которую версию ODBC надлежит использовать. Следовательно, Вы должны вызвать SQLSetEnvAttr(V_OD_Env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0) . Константа SQL_ATTR_ODBC_VERSION определяет то, какая версия ODBC будет использована, а SQL_OV_ODBC3 говорит, что программа будет нуждаться В ODBC 3.0.

Теперь Вы должны создать дескриптор для соединения с базой данных, который имеет тип SQLHDBC. Вы еще раз вызываете SQLAllocHandle, но на этот раз с SQL_HANDLE_DBC и дескриптором среды, возвращенным первым обращением к SQLAllocHandle.

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

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

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

Теперь рассмотрим код примера: /* odbc.c testing unixODBC */ #include <stdlib.h> #include <stdio.h> #include <odbc/sql.h> #include <odbc/sqlext.h> #include <odbc/sqltypes.h> SQLHENV V_OD_Env; // Handle ODBC environment long V_OD_erg; // result of functions SQLHDBC V_OD_hdbc; // Handle connection char V_OD_stat[10]; // Status SQL SQLINTEGER V_OD_err,V_OD_rowanz,V_OD_id; SQLSMALLINT V_OD_mlen; char V_OD_msg[200],V_OD_buffer[200]; int main(int argc,char *argv[]) { // 1. allocate Environment handle and register version V_OD_erg=SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&V_OD_Env); if ((V_OD_erg != SQL_SUCCESS) && (V_OD_erg != SQL_SUCCESS_WITH_INFO)) { printf("Error AllocHandle\n"); exit(0); } V_OD_erg=SQLSetEnvAttr(V_OD_Env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); if ((V_OD_erg != SQL_SUCCESS) && (V_OD_erg != SQL_SUCCESS_WITH_INFO)) { printf("Error SetEnv\n"); SQLFreeHandle(SQL_HANDLE_ENV, V_OD_Env); exit(0); } // 2. allocate connection handle, set timeout V_OD_erg = SQLAllocHandle(SQL_HANDLE_DBC, V_OD_Env, &V_OD_hdbc); if ((V_OD_erg != SQL_SUCCESS) && (V_OD_erg != SQL_SUCCESS_WITH_INFO)) { printf("Error AllocHDB %d\n",V_OD_erg); SQLFreeHandle(SQL_HANDLE_ENV, V_OD_Env); exit(0); } SQLSetConnectAttr(V_OD_hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0); // 3. Connect to the datasource "web" V_OD_erg = SQLConnect(V_OD_hdbc, (SQLCHAR*) "web", SQL_NTS, (SQLCHAR*) "christa", SQL_NTS, (SQLCHAR*) "", SQL_NTS); if ((V_OD_erg != SQL_SUCCESS) && (V_OD_erg != SQL_SUCCESS_WITH_INFO)) { printf("Error SQLConnect %d\n",V_OD_erg); SQLGetDiagRec(SQL_HANDLE_DBC, V_OD_hdbc,1, V_OD_stat, &V_OD_err, V_OD_msg,100,&V_OD_mlen); printf("%s (%d)\n",V_OD_msg,V_OD_err); SQLFreeHandle(SQL_HANDLE_DBC, V_OD_hdbc); SQLFreeHandle(SQL_HANDLE_ENV, V_OD_Env); exit(0); } printf("Connected !\n"); /* Продолжение чуть ниже... */

Использование источников данных

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

Тут Вы должны использовать SQLDataSources(). После распределения дескриптора среды, Вы можете использовать его, чтобы выяснить все относительно DSN и описания для источника данных.

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

SQL_FETCH_FIRST Устанавливает SQLDataSources() в первый из всех доступных источников данных (системный или пользовательский).
SQL_FETCH_FIRST_USER Устанавливает SQLDataSources() в первый из всех доступных источников данных (только пользовательский).
SQL_FETCH_FIRST_SYSTEM Устанавливает SQLDataSources() в первый из всех доступных источников данных (только системный).
SQL_FETCH_FIRST_NEXT Выбирает следующий источник данных. В зависимости от SQL_FETCH_FIRST_USER, SQL_FETCH_FIRST_SYSTEM или SQL_FETCH_FIRST, это может быть пользовательский, системный или любой источник данных.

Рассмотрим маленькую функцию, которая возвратит все доступные имена источников данных. Вы можете вставлять этот код в программу, которую Вы сформировали прежде, и вызывать его где-нибудь после того, как получили дескриптор среды: void OD_ListDSN(void) { char l_dsn[100],l_desc[100]; short int l_len1,l_len2,l_next; l_next=SQL_FETCH_FIRST; while(SQLDataSources(V_OD_Env,l_next,l_dsn, sizeof(l_dsn), &l_len1, l_desc, sizeof(l_desc), &l_len2) == SQL_SUCCESS) { printf("Server=(%s) Beschreibung=(%s)\n",l_dsn,l_desc); l_next=SQL_FETCH_NEXT; } }

Выполнение запросов

Если Вы хотите выполнить запрос, Вы должны будете определить дескриптор (SQL_HANDLE_STMT) для SQL-оператора. Чтобы получить его, Вы должны распределить память с помощью функции SQLAllocHandle.

Затем Вы должны подготовить запрос для выполнения. Как я писал ранее, я предполагаю использование таблицы tkeyuser, которая содержит следующие данные:

iduserdtname dtmaxSize
1Christa10000
2Nicole9000

В этом примере мы хотим выполнить запрос, который возвращает все строки для полей iduser и dtname в этой таблице, упорядоченные соответственно полю iduser. Так что команда SQL будет примерно такой: SELECT iduser,dtname FROM tkeydata ORDER BY iduser

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

Так что надо привязать первый столбец к переменной типа SQLINTEGER, а второй к переменной типа char. Это делается вызовом функции SQLBindCol. Следовательно, добавляем в программу следующие переменные: SQLHSTMT V_OD_hstmt; // Handle for a statement SQLINTEGER V_OD_err,V_OD_id; char V_OD_buffer[200];

Теперь Вы можете связывать переменные: SQLBindCol(V_OD_hstmt,1,SQL_C_CHAR, &V_OD_buffer,200,&V_OD_err); SQLBindCol(V_OD_hstmt,2,SQL_C_ULONG,&V_OD_id,sizeof(V_OD_id),&V_OD_err);

Вы должны проверить код возврата из обращения к функции. Теперь Вы можете выполнять запрос, вызывая функцию SQLExecDirect: V_OD_erg=SQLExecDirect(V_OD_hstmt, "SELECT dtname,iduser FROM tkeyuser order by iduser",SQL_NTS); if ((V_OD_erg != SQL_SUCCESS) && (V_OD_erg != SQL_SUCCESS_WITH_INFO)) { printf("Error Select %d\n",V_OD_erg); SQLGetDiagRec(SQL_HANDLE_DBC, V_OD_hdbc,1, V_OD_stat, &V_OD_err, V_OD_msg,100,&V_OD_mlen); printf("%s (%d)\n",V_OD_msg,V_OD_err); SQLFreeHandle(SQL_HANDLE_STMT,V_OD_hstmt); SQLDisconnect(V_OD_hdbc); SQLFreeHandle(SQL_HANDLE_DBC,V_OD_hdbc); SQLFreeHandle(SQL_HANDLE_ENV, V_OD_Env); exit(0); }

Получение данных из набора результатов

Если выполнение инструкции прошло нормально, Вы можете выбрать столбец данных. Может быть Вы сочтете нужным узнать, сколько столбцов находится в наборе результатов (если Вы используете SELECT * FROM tkeyuser в программе-примере, Вы не знаете этого). Обращение к функции SQLNumResultCols решает проблему. Эта функция берет операторный дескриптор и указатель на целую переменную, которая будет хранить число столбцов после обращения. Вы можете добавить это к Вашей программе: // At the beginning add: SQLSMALLINT V_OD_colanz; // Num of columns // At the end add: V_OD_erg=SQLNumResultCols(V_OD_hstmt,&V_OD_colanz); if ((V_OD_erg != SQL_SUCCESS) && (V_OD_erg != SQL_SUCCESS_WITH_INFO)) { printf("Fehler im ResultCols %d\n",V_OD_erg); SQLFreeHandle(SQL_HANDLE_STMT,V_OD_hstmt); SQLFreeHandle(SQL_HANDLE_DBC,V_OD_hdbc); SQLFreeHandle(SQL_HANDLE_ENV, V_OD_Env); exit(0); } printf("Number of Columns %d\n",V_OD_colanz);

Следующая вещь, которую Вы должны знать: сколько строк было возвращено запросом. Обращение к функции SQLRowCount должно подавить Вашу жажду знаний.

Последнее действие должно собственно выбрать данные из набора результатов. Вы должны вызвать SQLFetch с операторным дескриптором (который был распределен, и SQLBind вызван для каждого отдельного столбца). SQLFetch возвращает SQL_NO_DATA, если не имеется больше данных в наборе результатов.

Имеется полный исходный текст. Это только пример Вашей работы с ODBC, программа-пример не оптимизирована. /* odbc.c testing unixODBC */ #include <stdlib.h> #include <stdio.h> #include <odbc/sql.h> #include <odbc/sqlext.h> #include <odbc/sqltypes.h> SQLHENV V_OD_Env; // Handle ODBC environment long V_OD_erg; // result of functions SQLHDBC V_OD_hdbc; // Handle connection char V_OD_stat[10]; // Status SQL SQLINTEGER V_OD_err,V_OD_rowanz,V_OD_id; SQLSMALLINT V_OD_mlen,V_OD_colanz; char V_OD_msg[200],V_OD_buffer[200]; int main(int argc,char *argv[]) { // 1. allocate Environment handle and register version V_OD_erg=SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&V_OD_Env); if ((V_OD_erg != SQL_SUCCESS) && (V_OD_erg != SQL_SUCCESS_WITH_INFO)) { printf("Error AllocHandle\n"); exit(0); } V_OD_erg=SQLSetEnvAttr(V_OD_Env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); if ((V_OD_erg != SQL_SUCCESS) && (V_OD_erg != SQL_SUCCESS_WITH_INFO)) { printf("Error SetEnv\n"); SQLFreeHandle(SQL_HANDLE_ENV, V_OD_Env); exit(0); } // 2. allocate connection handle, set timeout V_OD_erg = SQLAllocHandle(SQL_HANDLE_DBC, V_OD_Env, &V_OD_hdbc); if ((V_OD_erg != SQL_SUCCESS) && (V_OD_erg != SQL_SUCCESS_WITH_INFO)) { printf("Error AllocHDB %d\n",V_OD_erg); SQLFreeHandle(SQL_HANDLE_ENV, V_OD_Env); exit(0); } SQLSetConnectAttr(V_OD_hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0); // 3. Connect to the datasource "web" V_OD_erg = SQLConnect(V_OD_hdbc, (SQLCHAR*) "web", SQL_NTS, (SQLCHAR*) "christa", SQL_NTS, (SQLCHAR*) "", SQL_NTS); if ((V_OD_erg != SQL_SUCCESS) && (V_OD_erg != SQL_SUCCESS_WITH_INFO)) { printf("Error SQLConnect %d\n",V_OD_erg); SQLGetDiagRec(SQL_HANDLE_DBC, V_OD_hdbc,1, V_OD_stat, &V_OD_err, V_OD_msg,100,&V_OD_mlen); printf("%s (%d)\n",V_OD_msg,V_OD_err); SQLFreeHandle(SQL_HANDLE_ENV, V_OD_Env); exit(0); } printf("Connected !\n"); V_OD_erg=SQLAllocHandle(SQL_HANDLE_STMT, V_OD_hdbc, &V_OD_hstmt); if ((V_OD_erg != SQL_SUCCESS) && (V_OD_erg != SQL_SUCCESS_WITH_INFO)) { printf("Fehler im AllocStatement %d\n",V_OD_erg); SQLGetDiagRec(SQL_HANDLE_DBC, V_OD_hdbc,1, V_OD_stat, &V_OD_err,V_OD_msg,100,&V_OD_mlen); printf("%s (%d)\n",V_OD_msg,V_OD_err); SQLDisconnect(V_OD_hdbc); SQLFreeHandle(SQL_HANDLE_DBC,V_OD_hdbc); SQLFreeHandle(SQL_HANDLE_ENV, V_OD_Env); exit(0); } SQLBindCol(V_OD_hstmt,1,SQL_C_CHAR, &V_OD_buffer,150,&V_OD_err); SQLBindCol(V_OD_hstmt,2,SQL_C_ULONG,&V_OD_id,150,&V_OD_err); V_OD_erg=SQLExecDirect(V_OD_hstmt,"SELECT dtname,iduser FROM tkeyuser order by iduser",SQL_NTS); if ((V_OD_erg != SQL_SUCCESS) && (V_OD_erg != SQL_SUCCESS_WITH_INFO)) { printf("Error in Select %d\n",V_OD_erg); SQLGetDiagRec(SQL_HANDLE_DBC, V_OD_hdbc,1, V_OD_stat,&V_OD_err, V_OD_msg,100,&V_OD_mlen); printf("%s (%d)\n",V_OD_msg,V_OD_err); SQLFreeHandle(SQL_HANDLE_STMT,V_OD_hstmt); SQLDisconnect(V_OD_hdbc); SQLFreeHandle(SQL_HANDLE_DBC,V_OD_hdbc); SQLFreeHandle(SQL_HANDLE_ENV, V_OD_Env); exit(0); } V_OD_erg=SQLNumResultCols(V_OD_hstmt,&V_OD_colanz); if ((V_OD_erg != SQL_SUCCESS) && (V_OD_erg != SQL_SUCCESS_WITH_INFO)) { SQLFreeHandle(SQL_HANDLE_STMT,V_OD_hstmt); SQLDisconnect(V_OD_hdbc); SQLFreeHandle(SQL_HANDLE_DBC,V_OD_hdbc); SQLFreeHandle(SQL_HANDLE_ENV, V_OD_Env); exit(0); } printf("Number of Columns %d\n",V_OD_colanz); V_OD_erg=SQLRowCount(V_OD_hstmt,&V_OD_rowanz); if ((V_OD_erg != SQL_SUCCESS) && (V_OD_erg != SQL_SUCCESS_WITH_INFO)) { printf("Number ofRowCount %d\n",V_OD_erg); SQLFreeHandle(SQL_HANDLE_STMT,V_OD_hstmt); SQLDisconnect(V_OD_hdbc); SQLFreeHandle(SQL_HANDLE_DBC,V_OD_hdbc); SQLFreeHandle(SQL_HANDLE_ENV, V_OD_Env); exit(0); } printf("Number of Rows %d\n",V_OD_rowanz); V_OD_erg=SQLFetch(V_OD_hstmt); while(V_OD_erg != SQL_NO_DATA) { printf("Result: %d %s\n",V_OD_id,V_OD_buffer); V_OD_erg=SQLFetch(V_OD_hstmt); }; SQLFreeHandle(SQL_HANDLE_STMT,V_OD_hstmt); SQLDisconnect(V_OD_hdbc); SQLFreeHandle(SQL_HANDLE_DBC,V_OD_hdbc); SQLFreeHandle(SQL_HANDLE_ENV, V_OD_Env); return(0); }

Закрытие соединения

Прежде, чем Ваша программа завершит работу, Вы должны освободить все ресурсы, которые распределили. Функция SQLFreeHandle должна использоваться, чтобы освободить каждый распределенный дескриптор. Она ожидает параметр, который заявляет тип дескриптора, который будет освобожден, и непосредственно дескриптор. Так, если Вы хотите освободить дескриптор среды, Вы должны вызвать (в нашем примере программы): SQLFreeHandle(SQL_HANDLE_ENV, V_OD_Env);

Прежде, чем Вы освобождаете любой дескриптор, удостоверьтесь, что он впредь не понадобится. Наиболее распространенная ошибка: освобождение дескриптора соединения ДО того, как соединение закрыто.

Если Вы хотите закрыть соединение, Вы нуждаетесь в функции SQLDisconnect. Она закрывает соединение, связанное с дескриптором соединения, передаваемым как параметр вызова SQLDisconnect. В нашей программе мы должны вызвать: SQLDisconnect(V_OD_hdbc);