Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЛР7.doc
Скачиваний:
16
Добавлен:
05.05.2019
Размер:
851.46 Кб
Скачать
    1. Программный доступ посредством odbc

Об установке интерфейса для доступа к SQL Server посредством ODBC было сказано в предыдущем разделе. В пользу применения именно этого интерфейса для доступа к базам данных говорит его широчайшее распространение. Практически все системы разработки поддерживают его, добавляя свои компоненты для доступа к API-функциям. С другой стороны, ODBC с точки зрения программирования— это набор функций и структур, которые используются для доступа к реляционным базам данных. Для применения этого инструмента в современных системах программирования, исповедующих объектный подход, придется все равно создавать для него некоторую объектную оболочку. Поэтому в чистом виде ODBC в настоящее время уже почти не используется.

Классификация api-функций odbc

Функции ODBC можно отнести к одной из перечисленных далее групп. Рассмотрим эти группы.

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

  • Идентификатор соединения — определяет соединение с базой данных. Этот идентификатор указывает на область памяти, где содержится информация о конкретном соединении. Каждый идентификатор соединения ассоциируется с конкретным идентификатором окружения.

  • Идентификатор окружения — определяет базу данных. Указывает на область памяти для глобальной информации. Один идентификатор окружения может быть связан с несколькими идентификаторами соединения.

  • Идентификатор оператора— определяет отдельный SQL-оператор. Указывает на область, где хранится информация о конкретном операторе. Прикладная программа должна запрашивать этот идентификатор прежде, чем отправлять SQL-запрос. Каждый идентификатор оператора связывается с единственным идентификатором соединения.

  • Идентификатор приложения — используется вместо идентификатора оператора для упрощения некоторых операций.

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

  2. Выполнение операторов SQL. Есть две возможности выполнения SQL-операторов: непосредственное выполнение и отложенное выполнениеВ первом случае оператор выполняется сразу с возвращением результата выполнения. При отложенном выполнении оператор компилируется на сервере, а затем может быть выполнен произвольное количество раз. Под оператором понимается не только непосредственный оператор SQL, но и хранимая процедура или функция.

  3. Получение результатов. Функции этой группы извлекают результаты из набора строк, полученных при выполнении операторов SQL, реализуя механизм курсоров.

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

Пример программирования на основе odbc

Рассмотрим полный пример клиентского приложения, взаимодействующего с SQL Server по протоколу ODBC и написанного на языке С. Пример представлен в листинге 5.1 и может быть набран и откомпилирован Visual Studio .NET. Для этого необходимо сделать следующие шаги:

  1. Создать проект, имеющий тип Visual C++ | Win32, и выбрать шаблон Win32 Console Application.

  1. В окне мастера создания проектов (Win32 Application Wizard) на вкладке Application Settings следует указать, что требуется создать пустой проект (установить флажок Empty Project).

  1. После создания проекта следует перейти к окну Solution Explorer и, щелкнув по имени проекта правой кнопкой мыши, выбрать в контекстном меню пункт Properties. На вкладке General нужно обратиться к комбинированному списку Character Set (Набор символов) и выбрать в нем вариант Not Set. Это важный момент, поскольку по умолчанию устанавливается набор символов Unicode, а наша программа рассчитана на работу с ASCII-кодировкой.

  2. Теперь, обратившись снова к окну Solution Explorer и вызвав контекстное меню, выберите пункт меню Add | New Item. В появившемся окне Add New Item следует выбрать категорию добавляемого модуля (список Categories) — Code, и шаблон модуля (Templates) — C++ File (.cpp).

Таким образом, мы получили пустой текстовый модуль в нашем проекте, куда следует поместить текст из листинга 5.1.

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

Рис. 5.7. Выбор типа проекта для ODBC-клиента

листинг 5.1

#include <stdio.h>

#include <windows.h>

#include <sql.h>

#include <sqlext.h>

#include <sqltypes.h>

#include <odbcss.h>

// объявления функций

int execom(SQLHSTMT, SQLCHAR *,int );

void get_err(SQLHSTMT);

// главная функция

void main()

{

SQLHENV henv = SQL_NULL_HENV;

SQLHDBC hdbc = SQL_NULL_HDBC;

SQLHSTMT hstmt1 = SQL_NULL_HSTMT;

RETCODE retcode;

char datab[SQL_MAX_DSN_LENGTH+1] = "sql";

char user[256] = "";

char passw[256] = "";

SQLCHAR command1[256]="use institute";

SQLCHAR command2[256]="execute proc1 ? ";

SQLCHAR command3[256]="{call proc2(?)}";

SQLCHAR command4[256]="select dbo.fuct(?)";

SQLCHAR command5[256]="select * FROM dbo.func(?)";

SQLINTEGER count=0;

char par1[256],par2[256];

ULONG param;

ULONG id;

id=5;

param = 6;

// назначить окружение прикладной программе

retcode = SQLAllocHandle (

SQL_HANDLE_ENV,

NULL,

&henv);

if (retcode == SQL_ERROR)

{

printf("Error!\n");

goto exit1;

}

// известить ODBC, что приложение будет работать

// в стандарте ODBC 3.0

retcode = SQLSetEnvAttr(

henv,

SQL_ATTR_ODBC_VERSION,

(SQLPOINTER)SQL_OV_ODBC3,

SQL_IS_INTEGER);

// получить идентификатор соединения

retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);

if (retcode == SQL_ERROR)

{

printf("Error!\n");

goto exit2;

}

// осуществить соединение

retcode=SQLConnect(

hdbc,

(SQLCHAR*) datab,

SQL_NTS,

(SQLCHAR*) user,

SQL_NTS,

(SQLCHAR*) passw,

SQL_NTS);

if (retcode == SQL_ERROR)

{

printf("Error!\n");

goto exit3;

}

// получить идентификатор оператора

retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt1);

if (retcode == SQL_ERROR)

{

printf("Error!\n");

goto exit3;

}

// установить текущую базу данных (см. command1)

if(execom(hstmt1,command1,0))goto exit3;

retcode = SQLPrepare(

hstmt1,

command2,

(int)strlen((char*)command2));

count=8;

// связать маркер с реальной переменной

retcode = SQLBindParameter(

hstmt1,

1,

SQL_PARAM_INPUT,

SQL_C_ULONG,

SQL_BIGINT,

8,

0,

&id,

8,

&count);

// выполнить команду command2

if(execom(hstmt1,command2,1))goto exit3;

// цикл выборки результатов курсора

more1:

do {

par1[0]=0; par2[0]=0;

retcode=SQLFetch(hstmt1);

if(retcode==SQL_ERROR)

{

printf("Error!\n");

goto exit3;

}

SQLGetData(hstmt1,1,SQL_C_CHAR,par1,256,&count);

SQLGetData(hstmt1,2,SQL_C_CHAR,par2,256,&count);

CharToOem(par1,par1);

CharToOem(par2,par2);

printf("%s %s \n",par1,par2);

} while(retcode!=SQL_NO_DATA);

// перейти к следующему курсору (набору данных)

retcode=SQLMoreResults(hstmt1);

if(retcode==SQL_ERROR)

{

printf("Error!\n");

goto exit3;

}

if(retcode!=SQL_NO_DATA)goto more1;

// закрыть курсор

SQLCloseCursor(hstmt1);

// вызов процедуры с возвращением параметра

count=8;

retcode = SQLPrepare(

hstmt1,

command3,

(int)strlen((char*)command3));

// связать маркер с реальной переменной

retcode = SQLBindParameter(

hstmt1,

1,

SQL_PARAM_INPUT_OUTPUT,

SQL_C_ULONG,

SQL_BIGINT,

8,

0,

&id,

8,

&count);

// выполнить команду command3

if(execom(hstmt1,command3,1)) goto exit3;

more2:

// перейти к следующему курсору (набору данных);

// сами курсоры нас в данном случае интересовать не будут

retcode=SQLMoreResults(hstmt1);

if(retcode==SQL_ERROR)

{

printf("Error!\n");

goto exit3;

}

if(retcode!=SQL_NO_DATA)goto more2;

// вывести значение возвращенного параметра

printf("%d\n",id);

// закрыть курсор

SQLCloseCursor(hstmt1);

// вызвать скалярную функцию

retcode = SQLPrepare(hstmt1,command4,SQL_NTS);

count=40;

// связать маркер с реальной переменной,

// значение которой будет отправлено

// в скалярную функцию в качестве параметра

retcode = SQLBindParameter(

hstmt1,

1,

SQL_PARAM_INPUT,

SQL_C_ULONG,

SQL_BIGINT,

8,

0,

&param,

8,

&count);

// выполнить команду command4

if(execom(hstmt1,command4,1)) goto exit3;

// цикл выборки результатов курсора

more4:

do {

par1[0]=0;

retcode=SQLFetch(hstmt1);

if(retcode==SQL_ERROR)

{

printf("Error!\n");

goto exit3;

}

SQLGetData(hstmt1,1,SQL_C_CHAR,par1,256,&count);

CharToOem(par1,par1);

printf("%s \n",par1);

} while(retcode!=SQL_NO_DATA);

// перейти к следующему курсору (набору данных)

retcode=SQLMoreResults(hstmt1);

if(retcode==SQL_ERROR)

{

printf("Error!\n");

goto exit3;

}

if(retcode!=SQL_NO_DATA)goto more4;

// вызов табличной функции

retcode = SQLPrepare(hstmt1,command5,SQL_NTS);

count=8;

id=2003;

// связать маркер с реальной переменной

retcode = SQLBindParameter(

hstmt1,

1,

SQL_PARAM_INPUT,

SQL_C_ULONG,

SQL_BIGINT,

8,

0,

&id,

0,

&count);

// выполнить команду command5

if(execom(hstmt1,command5,1)) goto exit3;

// цикл выборки результатов курсора

more5:

do {

par1[0]=0;

par2[0]=0;

retcode=SQLFetch(hstmt1);

if(retcode==SQL_ERROR)

{

printf("Error!\n");

goto exit3;

}

SQLGetData(hstmt1,1,SQL_C_CHAR,par1,256,&count);

CharToOem(par1,par1);

SQLGetData(hstmt1,2,SQL_C_CHAR,par2,256,&count);

CharToOem(par2,par2);

printf("%s %s \n",par1,par2);

} while(retcode!=SQL_NO_DATA);

// перейти к следующему курсору (набору данных)

retcode=SQLMoreResults(hstmt1);

if(retcode==SQL_ERROR)

{

printf("Error!\n");

goto exit3;

}

if(retcode!=SQL_NO_DATA)goto more5;

// освободить идентификатор оператора

SQLFreeHandle(SQL_HANDLE_STMT, hstmt1);

// разорвать соединение

SQLDisconnect(hdbc);

exit3:

// освободить идентификатор соединения

SQLFreeHandle(SQL_HANDLE_DBC, hdbc);

exit2:

// осводить идентификатор окружения

SQLFreeHandle(SQL_HANDLE_ENV, henv);

exit1:

return;

}

// функция, осуществляющая выполнение SQL-оператора

int execom(SQLHSTMT hstmt, SQLCHAR * com, int tip)

{

RETCODE ret;

if(!tip)

ret = SQLExecDirect(hstmt,com,SQL_NTS);

else

ret = SQLExecute(hstmt);

if (ret == SQL_ERROR)

{

get_err(hstmt);

return 1;

}

return 0;

}

// функция вывода сообщения и номера ошибки

void get_err(SQLHSTMT hstmt)

{

int i=1;

UCHAR st[6];

SDWORD fne;

UCHAR ser[SQL_MAX_MESSAGE_LENGTH+1];

SWORD cber;

RETCODE ret=SQLGetDiagRec(SQL_HANDLE_STMT,

hstmt,i,st,&fne,ser,sizeof(ser),&cber);

while(ret==SQL_SUCCESS)

{

i++;

CharToOem((char*)ser,(char*)ser);

printf("%s %s\n",ser,st);

ret=SQLGetDiagRec(SQL_HANDLE_DBC,hstmt,i,st,

&fne,ser,sizeof(ser),&cber);

};

}

Комментарий к листингу 5.1

Обратите ваше внимание на заголовочные файлы в начале программы и, в частности, на те, названия которых начинаются с префикса sql. В них содержатся прообразы API-функций ODBC, определения констант и типов данных, используемых при программировании ODBC. Что касается подключения динамических библиотек в период времени исполнения (run-time period), содержащих API-функции ODBC, то Visual Studio .NET обеспечивает такое подключение автоматически, и нам не следует об этом беспокоиться.

Кто знаком с принципами программирования в операционной системе Windows, знает, что доступ к API-функциям можно получить, обратившись напрямую к соответствующей динамической библиотеке. В данном случае такой библиотекой является odbc32.dll, которая хранится в каталоге System32

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

Вместо этого можно использовать константу sql_nts (см. листинг 5.1), означающую, что строка должна заканчиваться символом с кодом 0 (стиль языка С).

Функция API SQLAllocHandle. Данная функция универсальна, она позволяет выделять память для идентификатора:

  • окружения;

  • соединения;

  • оператора.

Все три операции представлены в тексте программы (см. листинг 5.1). Рассмотрим назначение параметров функции.

  • Первым параметром функции определяется тип идентификатора, для которого выделяются ресурсы.

  • Второй параметр — входной идентификатор. При выделении памяти для идентификатора окружения следует использовать константу sql_null_handle или просто null. В случае выделения памяти для идентификатора соединения нужно использовать идентификатор окружения. В случае выделения памяти для идентификатора оператора следует указывать идентификатор соединения.

  • Третий параметр — указатель на переменную, которая получит значение идентификатора.

Функция SQLSetEnvAttr. Она предназначена для задания атрибута окружения. В частности, мы указываем, что далее будем использовать версию ODBC 3.0.

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

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

Обратим внимание на функцию execom. Она предназначена для выполнения команды SQL на стороне сервера. Внимательно посмотрим на третий параметр. В зависимости от значения параметра для выполнения команды на стороне сервера используется либо функция SQLExecDirect, либо функция SQLExecute. Первая функция предназначена для непосредственного выполнения команды на стороне сервера, вторая выполняет команду, предварительно подготовленную функцией SQLPrepare (см. листинг 5.1).

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

Функция SQLPrepare. Осуществляет подготовку строки с командой SQL для выполнения на SQL Server. Строка может содержать так называемые маркеры. Они обозначаются символами вопроса (?) и могут в дальнейшем быть связаны с переменными (см. функцию SQLBindParameter). Маркеры позволяют не только передавать параметры на SQL Server, но и возвращать параметры. Пример строки, содержащей команду SQL, с маркером таков:

insert into names (name) values (?)

Функция SQLBindParameter. Осуществляет связывание маркеров, содержащихся в строке с конкретными переменными.

  • 1-й параметр — идентификатор оператора;

  • 2-й параметр— номер маркера в строке, представляющей команду SQL. Параметры нумеруются слева направо;

  • 3-й параметр— тип параметра: sqlparaminput— параметр ввода, sqlparaminputoutput— параметры ввода/вывода в хранимой процедуре, sql_param_output — значение возврата или параметр вывода в процедуре;

  • 4-й параметр — тип данных (в С-нотации):

    SQL_C_BINARY

    SQL_C_NUMERIC

    SQL_C_TYPE_TIMESTAMP

    SQL_C_BIT

    SQL_C_SBIGINT

    SQL_C_UBIGINT

    SQL_C_BOOKMARK

    SQL_C_SLONG

    SQL_C_ULONG

    SQL_C_CHAR

    SQL_C_SSHORT

    SQL_C_USHORT

    SQL_C_DOUBLE

    SQL_C_STINYINT

    SQL_C_UTINYINT

    SQL C_FLOAT

    SQL_C_TYPE_DATE

    SQL_C_VARBOOKMARK

    SQL_C_GUID

    SQL_C_TYPE_TIME

  • 5-й параметр — SQL-тип данных:

    SQL_BIGINT

    SQL_INTERVAL_MONTH

    SQL_BINARY

    SQL_INTERVAL_SECOND

    SQL_BIT

    SQL_INTERVAL_YEAR

    SQL_CHAR

    SQL_INTERVAL_YEAR_TO_MONTH

    SQL_DECIMAL

    SQL_LONGVARBINARY

    SQL_DOUBLE

    SQL_LONGVARCHAR

    SQL_FLOAT

    SQL_NUMERIC

    SQL_GUID

    SQL_REAL

    SQL_INTEGER

    SQL_SMALLINT

    SQL_INTERVAL_DAY

    SQLJFINYINT

    SQL_INTERVAL_DAY_TO_HOUR

    SQL_TYPE_DATE

    SQL_INTERVAL_DAY_TO

    SQL_TYPE_TIME

    SQL_INTERVAL_DAY_TO_SECOND_MINUTE

    SQL_TYPE_TIMESTAMP

    SQL_INTERVAL_HOUR

    SQL_VARBINARY

    SQL_INTERVAL_HOUR_TO_MINUTE

    SQL_VARCHAR

    SQL_INTERVAL_HOUR_TO_SECOND

    SQL_WCHAR

    SQL_INTERVAL_MINUTE

    SQL_WLONGVARCHAR

    SQL_INTERVAL_MINUTE_TO_SECOND

    SQL_WVARCHAR

  • 6-й параметр — точность соответствующего маркера (параметра);

  • 7-й параметр — размер соответствующего маркера (параметра);

  • 8-й параметр— указатель на буфер, где содержатся данные параметра;

  • 9-й параметр — размер буфера;

  • 10-й параметр — указатель на буфер, где будет помещено данное, возвращенное с сервера: длина значения параметра, sqlnts (строка заканчивается Нулем), SQL_NULL_DATA– данные ОТСУТСТВУЮТ, SQL_DATA_AT_exec, sql_len_data_at_exec— значения параметров определяются во время выполнения.

Если при выполнении команды SQL на стороне сервера возвращается определенный набор строк, то эти данные размещаются в некоторых буферах на стороне клиента, доступ к которым можно получить посредством идентификатора оператора. Структура полученных данных является структурой курсора, т. е. по этому набору можно осуществлять передвижение от одной строки к другой и получать доступ к данным текущей строки. Для передвижения по курсору используется функция SQLFetch. Переходя к очередной строке, функция помещает ее содержимое в специальный буфер. Извлечение данных из этого буфера осуществляется посредством функции SQLGetData. За один раз функция возвращает данные только из одного столбца. Номер столбца указывается вторым параметром функции. Третьим параметром задается тип данных в столбце. Далее идет указатель на буфер, куда будет помещено значение, длина буфера и указатель на переменную, куда будет помещена реальная длина возвращаемых данных.

Реально, на одну команду SQL может быть возвращено несколько результирующих наборов. Для того чтобы перебрать все возвращенные наборы, используется функция SQLMoreResults (СМ. ЛИСТИНГ 5.1).

Функции SQLDisconnect И SQLFreeHandle, соответственно, отсоединяют клиента от сервера и освобождают идентификатор.

И, наконец, последнее, что требует пояснений при разборе программы из листинга 5.1. Текстовые данные, которые мы получаем с сервера, имеют Windows-кодировку. Если в данных есть строки, написанные на национальном языке (например, русском), то следует произвести перекодировку для вывода в консольное окно. Это осуществляется функцией СharToOem.