Книга: Мир InterBase. Архитектура, администрирование и разработка приложений баз данных в InterBase/FireBird/Yaffil

Пример 2. Запрос с параметрами

Пример 2. Запрос с параметрами

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

Вот текст этой ХП.

create procedure InsertData (b_index char(16),

b_name varchar(80),

b_author varchar(80),

b_theme varchar(60))

returns (result_code integer)

as

begin

insert into books (

B_ID,B_INDEX,B_NAME, B_AUTHOR,B_ADDED,BJTHEME)

values(0,.b_index, :b_name, :b_author, 'now', :b_theme);

result_code = 0;

when any

do begin

result_code=-l;

end

end

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

Текст скрипта второго примера выглядит так :

#include <ibase.h>

#include <stdio.h>

#include <stdlib.h>

#include <scring.h>

#include <time.h>

#include "cgic.h"

#define SQL_VARCHAR(len) struct {short vary_length; char

vary_string[(len)+1];}

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

int cgiMain (void){ '

char *dbname = "localhost:/var/db/demo.gdb";

char *uname = "sysdba";

char "upass = "masterkey";

char *qaery = "select b_id, b_index, b_name, b_author, b_added,

b_theme from books";

На месте неизвестных входящих параметров - знаки вопроса:

char *SPCall = "execute procedure insertdata (?,?,?,?)";

isc_db_handle db_handle = NULL;

isc_tr_handle transaction_handle = NULL;

isc_stmt_handle statement_handle=NULL;

char dpb_buffer[256], *dpb, *p;

short dpb_length;

ISC_STATUS status_vector[20];

XSQLDA *isqlda, *osqlda;

Long fetch_code;

Short

o_ind[20]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);

int i = 0;

int hDisplayed=0;

int formVarErr;

int ExecSP=l;

long res_code;

long b_id;

char b_index[17];

SQL_VARCHAR(100) b_name;

SQL_VARCHAR(100) b_author;

SQL_VARCHAR (100) b_theme;

ISC_TIMESTAMP b_added;

struct tm added_time;

char decodedTime[100];

char form_b_index[17],

form_b_name[81],

form_b_author[81],

form_b_theme[61];

В этой части происходит анализ переменных, полученных скриптом, и в зависимости от метода вызова принимается решение, исполнять ли ХП или нет.

printf("Content-type:

text/plainnn<htmlxbody><center><b>Example Nr 2</b><hr

width=%></centerxbr>&nbsp;&nbsp;&nbsp;Part 1: Executable SP

demo with both types of parameters.<br>");

if(strcmp(cgiRequestMethod,"POST")==0){

formVarErr =

cgiFormStringNoNewlines("b_index",form_b_index,17);

if (formVarErr!=cgiFormSuccess){

printf("<br><b>Error: Book index missed or too long</b>");

ExecSP=0,

}

formVarErr = cgiFormStringNoNewlines("b_name",form_b_name,81);

if {formVarErr!=cgiFormSuccess){

printf ("<brxb>Error: Book name missed or too long</b>");

ExecSP=0;

}

formVarErr =

cgiFormStringNoNewlines("b_author",form_b_author,81);

if (formVarErr!=cgiFormSuccess){

printf ("<br><b>Error: Book author missed or too long</b>");

ExecSP=0;

}

formVarErr =

cgiFormStringNoNewlines("b_theme",form_b_theme,61);

if (formVarErr!=cgiFormSuccess){

printf("<br><b>Error: Book theme missed or too long</b>");

ExecSP=0;

}

}

else{

ExecSP=0;

printf ("<brxi>Procedure execution skipped</i> - REQUEST_METHOD

must be POST");

}

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

После разбора переменных идет непосредственно работа с базой данных:

dpb=dpb_buffеr;

*dpb++ = isc_dpb_versionl;

*dpb++ = isc_dpb_user_name;

*dpb++ = strlen(uname);

for(p = uname; *p;)

*dpb++ = *p++;

*dpb++ = isc_dpb_password;

*dpb++ = strlen(upass);

for (p=upass; *p;)

*dpb++ = *p++;

dpb_length = dpb- dpb_buffer;

isc_attach_database(

status_vector,

strlen(dbname),

dbname,

&db_handle,

dpb_length,

dpb_buffer);

if (status_vector[0] == 1 && status_vector[1]){

isc_print_status(status_vector);

return(1};

}

if (db_handle){

isc_start_transaction(

status_vector,

&transaction_handle,

1,

&db_handle,

0,

NULL);

if (status_vector[0] == 1 && status_vector[1]){

isc_print_status(status_vector);

return(l);

}

}

Если были получены данные и они корректны, происходит вызов хранимой процедуры:

if(ExecSP){

printf("<br><i>Attempt to call SP with the following

parameters:

' %s' , '%s' , ' %s' , '%s'</i>.....",form_b_index,form_b_name,form_b_a

uthor,form_b_theme);

Как можно видеть, принципиально инициализация структур для входящих параметров не сильно отличается от инициализации исходящих параметров из первого примера:

isqlda = (XSQLDA *)malloc(XSQLDA_LENGTH(4));

isqlda->version = SQLDA_VERSION1;

isqlda->sqln = 4;

isqlda->sqld = 4;

isqlda->sqlvar[0] .sqldata = (char *)&form_b_index;

isqlda->sqlvar[0].sqltype = SQL_TEXT;

isqlda->sqlvar[0].sqllen = strlen(form_b_index);

isqlda->sqlvar [1] . sqldata = (char * ) &f orm_b_name,

isqlda->sqlvar[1].sqltype = SQL_TEXT;

isqlda->sqlvar[1].sqllen = strlen(form_b_name);

isqlda->sqlvar[2].sqldata = (char *)&form_b_author;

isqlda->sqlvar[2].sqltype = SQL_TEXT;

isqlda->sqlvar[2].sqllen = strlen(form_b_author);

isqlda->sqlvar[3].sqldata = (char *)&form_b_theme;

isqlda->sqlvar[3].sqltype = SQL_TEXT;

isqlda->sqlvar[3].sqllen = strlen(form_b_theme);

osqlda = (XSQLDA *)malloc(XSQLDA_LENGTH(1));

osqlda -> version = SQLDA_VERSION1;

osqlda -> sqln = 1; osqlda -> sqld = 1;

osqlda->sqlvar[0].sqldata = (char *)&res_code;

osqlda->sqlvar[0].sqltype = SQL_LONG;

osqlda->sqlvar[0].sqllen = sizeof(long);

osqlda->sqlvar[0].sqlind = &o_ind[0];

Вызов ХП происходит сразу, без предварительной подготовки (подробнее об этом будет рассказано несколько ниже):

isc_dsql_exec_immed2(

status_vector,

&db_handle,

&transaction_handle,

0,

SPCall,

SQL_DIALECT_V6,

isqlda,

osqlda};

if (status_vector[0] == 1 && status_vector[1]){

isc_print_status(status_vector) ;

return(1);

}

if(res_code==0){

printf(" <t»successfly</b>.");

}

else{

printf(" <b>failed with result_code=%i</b>.",res_code);

}

free(isqlda);

free(osqlda);

}

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

printf ("<brxbr>&nbsp; &nbsp; &nbsp; Part 2: Select without input

parameters<br>");

osqlda = (XSQLDA *)malloc(XSQLDA_LENGTH(6));

osqlda -> version = SQLDA_VERSION1;

osqlda -> sqln = 6;

osqlda->sqlvar[0].sqldata = (char *)&b_id;

osqlda->sqlvar[0].sqltype = SQL_LONG;

osqlda->sqlvar[0].sqlind = &o_ind[0];

osqlda->sqlvar[1].sqldata = (char *)&b_index;

osqlda->sqlvar[1].sqltype = SQL_TEXT;

osqlda->sqlvar[1].sqlind = &o_ind[l];

osqlda->sqlvar[2].sqldata = (char *)&b_name;

osqlda->sqlvar[2].sqltype = SQLJVARYING;

osqlda->sqlvar[2].sqlind = &o_ind[2];

osqlda->sqlvar[3].sqldata = (char *)&b_author;

osqlda->sqlvar[3].sqltype = SQL_VARYING;

osqlda->sqlvar[3].sqlind = &o_ind[3];

osqlda->sqlvar[4].sqldata = (char *)&b_added;

osqlda->sqlvar[4].sqltype = SQL_TIMESTAMP;

osqlda->sqlvar[4].sqlind = &o_ind[4];

osqlda->sqlvar[5].sqldata = (char *)&b_theme;

osqlda->sqlvar[5].sqltype = SQL_VARYING;

osqlda->sqlvar[5].sqlind = &o_ind[5];

isc_dsql_allocate_statement(

status_vector,

&db_handle,

&statement_handle);

if (stacus_vector[0] == 1 && status_vector[1]){

isc_print_status(status_vector);

return{1);

}

isc_dsql_prepare(

status_vector,

&transaction_handle,

&statement_handle,

0,

query,

SQL_DIAL,ECT_Y6,

osqlda);

if (status_vector [ 0] == 1 && status_vector[1]) {

isc_print_status(status_vector) ;

return(1);

}

Заметьте, что если бы запрос гарантированно возвращал одну запись, то надобность в вызове функции isc_dsql_fetch()oтпaлa бы, а на месте последних двух пустых значений в вызове нижеследующей функции были бы соответственно isqlda и osqlda:

isc_dsql__execute2 (

status_vector,

&transaction_handle,

&statement_handle,

1,

NULL,

NULL);

if (status_vector[0] == 1 && status_vector[1]){

isc_print_status (status_vector);

return(1);

}

while((fetch_code = isc_dsql_fetch(

status_vector,

&staternent .handle,

1,

osqlda))==0)

{

Вот здесь и производится проверка на существование хотя бы одной записи в таблице:

if (!hDisplayed){

hDisplayed=l;

printf ("<brxbr><center><table border=0 bgcolor=black

cellpadding=l cellspacing=l><tr align=center bgcolor=#999999>

<td>Book ID</tr> <td>CODE</tr> <td>TITLE</tr> <td>AUTHOR</tr>

<td>ADDED</tr> <cd>THEME</tr> </tr>");

}

Далее идет уже знакомая по предыдущему примеру обработка текстовых переменных:

b_index[osqlda->sqlvar[1].sqllen]='';

b_name.vary_string[b_name.vary_length]='';

b_author.vary_string[b_author.vary_length]='';

b_theme.vary_string[b_theme.vary_length]='';

и преобразование TIMESTAMP в удобочитаемое выражение:

isc_decode_timestamp(&b_added,&added_time);

strftime(decodedTime,sizeof(decodedTime),"%d-%b-%Y

%H: %M", &added_t ime) ;

printf("<tr bgcolor=white><td>%i</td> <td>%s</td> <td>%s</td>

<td>%s</td> <td>%s</td> <td>%s</td> </tr>",

b_id,

b_index,

b_name.vary_string,

b_author.vary_string,

decodedTime,

b_theme.vary_string);

}

if (status_vector[0] == 1 && status_vector[1]){

isc_print_status(status_vector);

return(1);

}

free(osqlda);

isc_dsql_free_statement(

status_vector,

&statement_handle,

DSQL_drop);

if (scacus_vector[0] == 1 && status_vector[1]){

isc_print_status(status_vector) ;

return(1);

}

Завершаем транзакцию и отключаемся от базы данных:

if (transaction_handle){

isc_commit_transaction(status_vector, &transaction_handle); }

if (status_vector[0] == 1 && status_vector[1]){

isc_pnnt_status (status_vector) ;

return(1);

}

if (db_handle) isc_detach_database(status_vector, &db_handle);

if (status_vector[0] == 1 && status_vector[1]){

isc_print_status(status_vector);

return(1);

}

Завершаем документ HTML-формой для ввода данных:

if(hDisplayed){

printf("</table></center><br>");

}

else{

printf ("<br><brxbr><center><b>Table is

empty</b></center><brxbr><br>") ;

}

printf ( "<centerxtable border=0 width=%><tr

align=right><td><form name ='demo ' method=POST

action= ' example2 ' xtable border=0>") ;

printf("<tr><td align=left>Book index</td><td>

<input type=text name='b_index' size=16 maxlength=16></td></tr>") ;

printf("<tr><td align=left>Book name</td><td>

<input type=text name= ' b_name ' size=16 maxlength=80x/tdx/tr>");

printf ("<tr><td align=lef t>Book author</td><td>

<input type=text name='b_author' size=16 maxlength=80></td></tr>");

printf("<tr><td align=left>Book theme</td><td>

<input type=text name='b_theme' size=16 maxlength=60></td></tr>");

printf ("<trxtd colspan=2 align=right>

<input type= ' submit' value='Add data'></td></tr>"};

printf ("</table></form></td></tr></table></center></body></html>");

return(0);

}// end of main

Вот что мы получим при первом запуске приложения при пустой таблице данных (см. рис 3.2):


Рис 3.2. Результат вставки записи

После вставки какой-нибудь записи в таблицу при помощи формы справа снизу результат будет следующим (см. рис 3.3).


Рис 3.3. Результаты выполнения CCI-приложения

В этом приложении читается несколько переменных www-окружения, которые проверяются на ненулевую длину и передаются в качестве входящего параметра запроса. Переменные www-окружения читаются при помощи функций библиотеки CGIC (ее можно загрузить с сайта http://www.boutell.com/cgic), однако вы можете воспользоваться любой удобной вам библиотекой. В качестве результата анализируется переменная res_code, и в зависимости от ее значения сообщается результат вызова процедуры. Неизвестные входные параметры, участвующие в запросе, передаются следующим образом: на местах неизвестных параметров ставится знак вопроса (?), а в соответствующей этому параметру переменной XSQLDA определяется тип и данные для передаваемой переменной, причем допускается смешивать родственные типы (int-smallint, char-varchar и т д ) При внимательном изучении примера видно, что запрос, выбирающий данные из таблицы, вызывается несколько отличным методом, нежели запрос вызывающий ХП. Первый запрос, состоит из следующих этапов:

* Резервирование ресурсов, требуемых для запроса библиотекой доступа к InterBase, - вызов функции isc_dsql_allocate_statement().

* Подготовка запроса к исполнению сервером - именно на этом этапе выдаются сообщения об ошибках в синтаксисе - вызов функции isc_dsql_prepare().

* Исполнение запроса сервером - вызов функции isc_dsql_execute2() (или isc_dsql_execute(), отличие этих функций в том, что вызов isc_dsql_execute() непременно требует вызова функции isc_dsql_fetch(), если существуют исходящие параметры). На этом этапе могут быть выданы сообщения о несоответствии типов и/или количества элементов XSQLDA.

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

* Освобождение ресурсов, занятых на этапе 1, - вызов isc_dsql_free_statement(). Инициализация структур XSQLDA обычно происходит до п. 2.

Второй запрос исполняется сразу, минуя все вышеперечисленные пункты, кроме п. 3. В данном случае используется функция isc_dsql_exec_immed2(). Отличие данных методов заключается в том, что в случае первого запроса можно воспользоваться пп. 3 и 4 внутри цикла - это дает преимущество в скорости, при исполнении одного и того же запроса, но с разными значениями параметров, так как синтаксис уже проверен и запрос сразу исполняется. Метод, реализованный в исполнении второго запроса, обычно применяется при однократном вызове хранимых процедур или при исполнении команд, которые разрешается исполнять только в этих функциях (например, CREATE DATABASE разрешается использовать только в вызове функции isc_dsql_execute_immediate()).

Так как в случае "немедленного" исполнения запроса не происходит его предварительного анализа, то необходимо инициализировать переменную sqld структуры XSQLDA, которая используется для получения результата (в данном случае это делает строка osqlda -> sqld =1). Если не проинициализировать эту переменную, то в качестве результата вызова функции isc_dsql_exec_immed2 будет получена ошибка Message length error... По той же причине, необходимо явно указывать размер памяти, который будет занимать переменная, хранящая результат, - в данном случае это sizeof(long). Если этого не сделать, то последствия будут непредсказуемы, - в лучшем случае вы не получите ничего на выходе.

При разработке CGI-приложений, работающих с InterBase, следует придерживаться следующих правил:

* Все переменные www-окружения должны быть проанализированы до подключения к базе данных - может случиться, что нужных данных нет или они не удовлетворяют каким-либо требованиям и работа с базой данных заведомо теряет смысл. В этом случае следует проинформировать пользователя о неверно введенных данных.

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

* Запускайте, если это возможно, сервер от имени пользователя с ограниченными правами.

* Создайте клиента базы данных (см. главу "Безопасность в InterBase: пользователи, роли и права" (ч. 4)), которого будут использовать все www-приложения для доступа к базе данных, и назначьте ему минимально необходимые права.

Маленький совет напоследок (применимый для InterBase с архитектурой SuperServer): если прописать вызов сервера в inittab с параметром respawn, то система сама перезапустит сервер в случае его падения надежнее, чем это сделает guardian Таким образом, получится некий аналог птицы Феникс - сервер базы данных возродится сразу же после фатальной ошибки (допущенной, например, при разработке приложений на API).

Оглавление книги


Генерация: 1.201. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз