Глава
№13.
С
и C++
В этой книге
мы рассматриваем несколько разных языков программирования: Python, Java, Perl
и С. Из этих языков больше всего трудностей вызывают C/C++. В других языках
ваша задача состоит в формировании запроса SQL, передаче этого запроса посредством
вызова функции и обработке результирующих данных. В С добавляется очень сложная
проблема управления памятью.
Как MySQL,
так и mSQL предоставляют С-библиотеки, позволяющие создавать приложения для
работы с базами данных MySQL и mSQL. В действительности API MySQL ведет свое
происхождение от mSQL, благодаря чему опыт программирования в одном API позволяет
легко перейти к другому. Однако, как мы видели в первой части, MySQL значительно
богаче функциями, чем mSQL. Естественно, эти дополнительные функции приводят
к некоторым различиям между двумя API. В данной главе мы исследуем эти различия
и разберем детали каждого API при создании объектно-ориентированного C++ API,
который можно условно компилировать для работы с каждым из двух API.
Используете
ли вы С или C++, С API для MySQL и mSQL являются шлюзами к базе данных. Однако
их применение может очень различаться в зависимости от того, используете ли
вы С или объектно-ориентированные возможности C++. К программированию баз данных
на С нужно подходить в последовательном стиле, когда вы пошагово просматриваете
свое приложение, чтобы определить, где производятся обращения к базе данных,
а где необходимо произвести освобождение ресурсов. Напротив, объектно-ориентированный
C++ требует объектно-ориентированного интерфейса к выбранному вами API. Тогда
объекты этого API могут взять на себя ответственность за управление ресурсами
базы данных.
В таблице
13-1 сопоставлены вызовы функций каждого API. Детали использования этих функций
мы рассмотрим позднее в этой главе. Сейчас вы можете лишь бегло сравнить оба
API и отметить, какие возможности они предоставляют. Разумеется, в справочном
разделе перечислены все эти методы с подробным описанием прототипов, возвращаемых
значений и комментариями.
Таблица
13-1. С API для MySQL и mSQL
MySQL |
mSQL |
mysql_affected_rows() |
CM. msqlQuery() |
mysql_close() |
msqlClose() |
mysql_connect() |
msqlConnect() |
myql_create_db() |
|
mysql_data_seek() |
msqlDataSeek() |
mysql_drop_db() |
|
mysql_eof() |
|
mysql_error() |
|
mysql_fetch_field() |
msqlFetchField() |
mysql fetch
lengths() |
|
mysql_fetch_row() |
msqlFetchRow() |
mysql_field_count() |
|
mysql_field_seek() |
msqlFieldSeek() |
mysql_free_result() |
msqlFreeResult() |
mysql_get_client_info() |
|
mysql get host_info() |
|
mysql_get_proto_info() |
|
mysql_get_server_info() |
|
mysql_init() |
|
mysql_insert_id() |
|
mysql_list_dbs(
) |
msqlListDBs() |
mysql_list_fields() |
msqlListFields() |
|
msqlListIndex() |
mysql_list_processes() |
|
mysql_list_tables() |
msqlListTables() |
mysql_num_fields() |
msqlNumFields() |
mysql_num_rows() |
msqlNumRows() |
mysql_query() |
msqlQuery() |
mysql_real_query() |
|
mysql_reload() |
|
mysql_select_db() |
msqlSelectDB() |
mysql_shutdown() |
|
mysql_stat() |
|
mysql_store_result() |
msqiStoreResult() |
mysql_use_result() |
|
API для MySQL
значительно обширнее, чем API для mSQL, ввиду большего числа функций в MySQL.
Во многих случаях MySQL фактически только обеспечивает программный интерфейс
к функциям администрирования баз данных, которые имеются в той и другой СУБД.
Просто изучив названия функций, можно прийти к выводу, что любое разрабатываемое
вами приложение баз данных должно, как минимум, делать следующее:
В примере
13-1 показана простая команда select, извлекающая данные из базы данных MySQL
с помощью MySQL С API.
Пример
13-1. Простая программа, извлекающая все данные из тестовой базы
и отображающая их
#include <sys/time. h>
#include <stdio.h>
#include <mysql.h>
int main(char **args) {
MYSQL_RES 'result;
MYSQL_ROW row;
MYSQL 'connection, mysql;
int state;
/* соединиться
с базой данных mySQL на athens.imaginary.com */
mysql_init(&mysql);
connection =
mysql_real_connect(&mysql,
"alMens.imaginary.com",
0, 0,
"db_test",
0, 0);
/* проверить ошибки соединения */
if( connection
== NULL ) {
/* вывести сообщение
об ошибке */
printf(mysql_error(&mysql));
return 1;
}
state = mysql_query(connection,
"SELECT test_id, test_val FROM test");
if( state !=
0 ) {
printf(mysql_error(connection));
return 1; }
/* прежде чем делать другие вызовы,
* необходимо
вызвать mysql_store_result()
*/
result = mysql_store_result(connection);
printf("Строк: %d\n", mysql_num_rows(result));
/* обработать каждую строку результирующего набора */
while( ( row = mysql_fetch_row(result)) != NULL )
{
printf("id:
%s, значение: %s\n", (row[0] ? row[0] : "NULL"), (row[1] ? row[1]
: "NULL")); }
/* освободить ресурсы, использовавшиеся результирующим набором */
mysql_free_result(result); /* закрыть соединение */
mysql_close(connection);
printf("Koнец, работы.\n");
}
Назначение
файлов mysql.h и stdio.h, включаемых директивой ftinclude, должно
быть очевидно. Файл заголовков mysql.h содержит прототипы и переменные,
необходимые для MySQL, a stdio.h содержит прототип для printf (). Файл
заголовков sys/time.h приложением фактически не используется. Он нужен
из-за mysql.h, так как файл для MySQL использует определения из sys/time.h,
не включая их. Для компиляции программы с помощью компилятора GNU С используйте
командную строку:
gcc -L/usr/local/mysql/lib
-I/usr/local/mysql/include -о select
select.c\
-Imysql -Insl
-Isocket
Разумеется,
в этой строке вместо /usr/local/mysql нужно задать тот каталог, в который
вы установили MySQL.
Функция main()
выполняет те шаги, которые мы перечислили раньше: соединяется с сервером, выбирает
базу данных, выдает запрос, обрабатывает его результаты и освобождает использованные
ресурсы. По ходу главы мы подробно остановимся на каждом из этих этапов. Сейчас
посмотрите на этот код, лишь для того чтобы почувствовать, как он работает.
Кроме того, сравните этот пример с той же программой, написанной для mSQL, которая
представлена в примере 13-2.*
Пример
13-2. Простое приложение выборки данных для mSQL
#include <sys/time.h>
#include <stdio.h>
#include <msql.h>
int main(char
**args) {
int connection,
state;
m_result *result;
m_row row;
/* соединиться
с базой данных mSOL на athens.imaginary.com */
state = msqlConnect("athens.imaginary.com");
/* проверить
ошибки соединения */
if( state == -1 )
{
/* вывести сообщение об ошибке, хранящееся в MsqlErrMsg */
printf(msqlErrMsg);
return 1;
}
else
{
/* описателем
соединения является значение, возвращаемое msqlConnect() */
connection =
state; }
/* выбрать используемую базу данных */
state = msqlSelectDB(connection, "db_test");
/* опять-таки, -1 указывает на ошибку */
if( state == -1 )
{
printf(msqlErrMsg);
/* закрыть соединение
перед выходом */
msqlClose(connection);
return 1; }
state = msqlQuery(connection, "SELECT test_id, test_val FROM test");
if( state == -1 )
{
printf(msqlErrMsg);
return 1;
}
else
{
printf("Строк: %d\n", state);
}
/* прежде чем делать новый вызов Query(),
* необходимо
вызвать msqlStoreResult()
*/
result = msqlStoreResult();
/* обработать каждую строку результирующего набора */
while( ( row = msqlFetchRow(result)) != NULL )
{
printf("id:
%s, значение: %s\n",
(row[0] ? row[0]
: "NULL"),
(row[1] ? row[1] : "NULL"));
}
/* освободить ресурсы, использовавшиеся результирующим набором */
msqlFreeResult(result); /* закрыть соединение */
msqlClose(connect ion);
printf("Конец
работы.\n"); }
Эти программы
почти идентичны. Кроме разных имен функций, есть лишь несколько заметных отличий.
Сильнее всего бросается в глаза различие в соединении с базой данных, которое
проявляется в двух отношениях:
Как указывалось
ранее в этой книге, MySQL поддерживает сложную схему авторизации с именами пользователей
и паролями. Напротив, в mSQL применяется простая система, использующая ID пользователя
процесса, соединяющегося с базой данных. Более надежная схема MySQL гораздо
привлекательнее в среде клиент/сервер, но также и значительно более сложна в
администрировании. Для разработчиков приложений она означает необходимость передачи
в вызове mysql_real_connect() имени пользователя и пароля при работе с MySQL
помимо имени сервера, используемого в mSQL.
Первый аргумент API для установления соединения с MySQL может показаться необычным. По сути, это способ отслеживать все вызовы, иначе никак не связанные с соединением. Например, если вы пытаетесь установить соединение, и попытка неудачна, вам нужно получить сообщение о соответствующей ошибке. Однако функция MySQL
mysql_error()
требует задания указателя на действующее соединение с базой данных MySQL. Такое
соединение обеспечивается изначально созданным нулевым соединением. Однако у
вас должна быть действующая ссылка на это значение в течение всего времени существования
вашего приложения - вопрос большой важности в более структурированной среде,
чем простое приложение вида «соединился, сделал запрос, закрылся».
Примеры на C++ далее в этой главе подробнее рассматривают эту тему.
Два другие
различия в API относятся к тому, как производятся обработка ошибок и подсчет
числа записей в результирующем наборе. API mSQL создает глобальную переменную
для хранения сообщений об ошибках. Из-за многопоточности MySQL такая глобальная
переменная не смогла бы действовать в его API. Поэтому в нем используется функция
mysql_error() для извлечения сообщений об ошибках, связанных с последней ошибкой,
порожденной указанным соединением.
API для соединения
и обработки ошибок - два пункта, в которых MySQL отличается от mSQL для обеспечения
функциональности, отсутствующей в mSQL. Подсчет числа результирующих записей
в mSQL делается иным способом для обеспечения лучшего интерфейса, нежели предоставляемый
MySQL. А именно: при посылке SQL-запроса в msqlQuery() возвращается число задействованных
строк (или -1 в случае ошибки). Таким образом, подсчет измененных строк при
обновлении и строк в возвращаемом результирующем наборе при запросе используют
одну и ту же парадигму. В MySQL же приходится использовать различные парадигмы.
При запросе на получение данных нужно передать результирующий набор функции
mysql_nuoi_rows() , чтобы получить число строк в результирующем наборе. При
обновлении нужно вызвать другую функцию API, mysql_affected_rows() . В то время
как msqlQuery() возвращает число строк, удовлетворивших предложению WHERE при
обновлении, mysql_affected_rows() сообщает о числе фактически измененных строк.
И наконец, в mSQL есть метод msqlNumRows() , обеспечивающий тот же интерфейс
для подсчета результирующего набора, что и в MySQL, но в нем нет аналога для
mysql_affected_rows() .
Объектно-ориентированный
доступ к базам данных на C++
С API прекрасно
работают в процедурном программировании на С. Однако они не очень хорошо вписываются
в объектно-ориентированную среду C++. Чтобы показать, как реально использовать
в программе эти два API, в оставшейся части главы мы создадим с их помощью C++
API для объектно-ориентированного программирования баз данных.
Рис. 13-1.
Библиотека объектно-ориенитрованного доступа к базе данных
Поскольку
мы занимаемся освещением доступа к базам данных MySQL и mSQL, то сосредоточимся
на специфичных для MySQL и mSQL темах и не будем пытаться создать совершенный
общий C++ API. Работу с MySQL и mSQL описывают три главных понятия: соединение,
результирующий набор и строки результирующего набора. Мы будем использовать
эти понятия как ядро объектной модели, на которой будет основываться наша библиотека.
Рис. 13-1 показывает эти объекты на UML-диаграмме.*
Соединение
с базой данных
В любой среде
доступ к базе данных начинается с соединения. Как вы видели в первых двух примерах,
MySQL и mSQL по-разному представляют одно и то же понятие - соединение с базой
данных. Создание нашей объектно-ориентированной библиотеки мы начнем с абстрагирования
от этого понятия и создания объекта Connection . Объект Connection должен уметь
устанавливать соединение с сервером, выбирать нужную базу данных, посылать запросы
и возвращать результаты. Пример 13-3 показывает заголовочный файл, в котором
объявлен интерфейс к объекту Connection.
UML - это
новый Унифицированный язык моделирования, созданный Гради Бучем, Айваром Якобсоном
и Джеймсом Рамбо (Grady Booch, Ivar Jacobson, James Rumbaugh) в качестве нового
стандарта для документирования объектно-ориентированного проектирования и анализа.
Пример
13-3. Заголовок класса Connection
#ifndef l_connection_h
#define l_connection_h
#include <sys/time.h>
#if defined(HAS_MSQL)
#include <msql. h>
#lelif defined(HAS_MYSQL)
#include <mysql.h>
#endif
#include "result.h"
class Connection
{ private:
int affected_rows;
#if defined(HAS_MSQL)
int connection;
#elif defined(HAS_MYSQL)
MYSQL mysql;
MYSQL 'connection;
tfelse
#error База данных не определена,
#endif
public:
Connection(char *, char *);
Connection(char *, char *, char *, char *);
~Connection();
void Close();
void Connect(char 'host, char *db, char *uid, char *pw);
int GetAffectedRows();
char. *GetError();
int IsConnected();
Result *Query(char *);
};
#endif // l_connection_h
Методы, которые
предоставляет класс Connection, одинаковы вне зависимости от используемой СУБД.
Однако спрятанными в глубине класса окажутся закрытые члены, специфичные для
той библиотеки, с которой он будет компилироваться. При установлении соединения
единственными различными данными-членами станут те, которые представляют соединение
с базой данных. Как отмечалось, mSQL для представления соединения использует
величину типа int, a MySQL использует указатель на MYSQL и дополнительную величину
типа MYSQL для установления соединения.
Установление
соединения с базой данных
Всем приложениям,
которые мы будем создавать с использованием этого API, для соединения с базой
данных потребуется только создать новый экземпляр класса Connection с помощью
одного из его конструкторов. Аналогично, приложение может отсоединиться, уничтожив
экземпляр Connection . Оно может даже повторно использовать экземпляр Connection
с помощью прямых обращений к методам Close() и Соnnect(). Пример 13-4 показывает
реализацию конструкторов и метода Connect().
Пример
13-4. Соединение с MySQL и mSQL в классе Connection
#include "connection.h"
Connection::Connection(char *host, char *db) {
#if defined(HAS_MSQL)
connection = -1;
#elif defined(HASJIYSQL)
connection = (MYSQL *)NULL;
#else
#error Het соединения с базой данных,
#endif
Connect(host,
db, (char *)NULL, (char *)NULL); }
Connection::Connection(char 'host, char *db, char *uid, char *pw) {
#if defined(HASJISQL)
connection = -1;
#elif defined(HASJIYSQL)
connection = (MYSQL *)NULL;
#else
#error Нет соединения с базой данных,
#endif
Connect(host, db, uid, pw);
}
void Connection: :Connect(char'host, char *db, char *uid, char *pw)
{
int state;
if( IsConnected() )
{
throw "Соединение уже установлено.";
}
#if defined(HAS_MSQL)
connection = msqlConnect(host);
state = msqlSelectDB(connection, db);
#elif defined
(HAS.MYSQL) mysql_init(&mysql);
connection =
mysql_real_connect(&mysql, host,
uid, pw,
db, 0, 0); #else
#error Нет соединения с базой данных.
#endif
if( !IsConnected() )
{
throw GetError();
}
if( state < 0 )
{
throw GetError();
}
}
Оба конструктора
разработаны с учетом различия параметров, требуемых для соединений MySQL и mSQL.
Тем не менее эти API должны разрешать обоим конструкторам работать с каждой
из баз данных. Это достигается игнорированием ID пользователя и пароля при вызове
конструктора с четырьмя аргументами. Аналогично при вызове конструктора с двумя
аргументами, серверу MySQL в качестве значений ID пользователя и пароля передаются
значения null. Фактическое соединение с базой данных происходит в методе Connect
().
Метод Connect()
инкапсулирует все шаги, необходимые для соединения. Для MySQL он вызывает метод
mysql_real_connect() . Для mSQL жe сначала вызывается метод msqlConnect(), а
затем msqlSelectDB() . При неудаче на любом из этапов Connect() возбуждает исключительную
ситуацию.
Отсоединение
от базы данных
Другой логической
функцией класса Connection является отсоединение от базы данных и освобождение
скрытых от приложения ресурсов. Эту функцию осуществляет метод Close (). В примере
13-5 показано, как происходит отсоединение от MySQL и mSQL.
Пример
13-5. Освобождение ресурсов базы данных
Connection::"Connection()
{
if( IsConnected()
) {
Close();
} }
void Connection::Close()
{
if( !IsConnected() )
{
return;
}
#if defined(HAS_MSQL)
msqlClose(connection);
connection = -1;
#elif defined(HAS_MYSQL)
mysql_close(connection);
connection = (MYSQL *)NULL;
#else
#error Нет соединения
с базой данных, tfendif }
Методы mysql_close()
и msqlClose() освобождают ресурсы, используемые соединениями с MySQL и mSQL
соответственно.
Выполнение
обращений к базе данных
В промежутке
между открытием соединения и закрытием базе данных обычно посылаются команды.
Класс Connection делает это с помощью метода Query(), принимающего команду SQL
в качестве аргумента. Если команда является запросом, она возвращает экземпляр
класса Result из объектной модели, представленной на рио. 13-1. Если же команда
обновляет данные, то метод возвращает NULL и устанавливает значение affected_rows
равным количеству строк, в которых произведены изменения. В примере 13-6 показано,
как класс Connection обрабатывает запросы к базам данных MySQL и mSQL.
Пример
13-6. Обработка запроса к базе данных
Result "Connection::Query(char
*sql) { T_RESULT *res; int state;
// Если нет
соединения, делать нечего
if( !lsConnected(-)
) { throw "Соединения нет.";
}
// Выполнить запрос
#if defined(HAS_MSQL)
state = msqlQuery(connection, sql);
#elif defined(HAS_MYSQL)
state = mysql_query(connection, sql);
#else
#error Нет соединения с базой данных,
#endif
// Если произошла
ошибка
if( state <
0 ) { throw GetError();
}
// Забрать результаты, если таковые имеются
#if defined(HAS_MSQL)
res = msqlStoreResult();
#elif defined(HAS_MYSQL)
res = mysql_store_result(connection);
#else
#error Нет соединения с базой данных,
#endif
// Если результат null, это было обновление или произошла ошибка
// Примечание: mSQL не порождает ошибки в msqlStoreResult()
if( res == (T_RESULT
*)NULL ) {
// Установить значение affected_rows равным возвращенному msqlQuery()
#if defined(HAS_MSQL)
affected_rows = state;
#elif defined(HAS_MYSQL)
// field_count != 0 означает, что произошла ошибка
int field_count
= mysql_num_fields(connection);
if( field_count != 0 )
{
throw GetError();
}
else
{
// Запомнить affected_rows
affected_rows = mysql_affected_rows(connection); }
#else
#error Нет соединения с базой данных,
#endif
//Возвратить NULL в случае обновления
return (Result
*)NULL; }
// Для запроса возвратить экземпляр Result
return new Result(res);
}
В начале
обращения к базе данных делается вызов метода mysql_query() или msqlQuery()
с передачей ему команды SQL, которую нужно выполнить. В случае ошибки оба API
возвращают отличное от нуля значение. На следующем этапе вызываются mysql_store_result()
или msqlStoreResult() , чтобы проверить, получены ли результаты, и сделать эти
результаты доступными приложению. В этом месте две СУБД несколько отличаются
в деталях обработки.
В mSQL API
метод msqlStoreResult() не генерирует ошибки. Эту функцию приложение использует
для того, чтобы поместить полученный результирующий набор в хранилище, управлять
которым будет приложение, а не mSQL API. Иными словами, при вызове msqlQuery()
результаты запоминаются во временной области памяти, управляемой API. Последующие
вызовы msqlQuery() затирают эту область памяти. Чтобы сохранить результат в
области памяти вашего приложения, нужно вызвать msqlStoreResult() .
Поскольку
метод msqlStoreResult() не генерирует ошибку, при его вызове нужно рассматривать
две возможности. Если обращение к базе данных было запросом, создавшим результирующий
набор, то msqlStoreResult() возвращает указатель на структуру m_result, с которой
может работать ваше приложение. При всех других типах обращения (обновление,
вставка, удаление или создание) msqlStoreResult() возвращает
NULL. Узнать количество строк, обработанных неизвлекающим данные запросом, можно
из значения, возвращенного исходным вызовом msqlQuery() .
Подобно msqlStoreResult()
, метод mysql_store_result() используется для запоминания данных, возвращенных
запросом, в области памяти приложения, но, в отличие от версии для mSQL, необходимо
создать для mysql_store_result() некий обработчик ошибок. Именно, значение NULL,
возвращенное mysql_store_result() , может означать и то, что запрос не предполагал
возвращение результирующего набора, и ошибку при получении последнего. Вызов
метода mysql__num_f ields() позволит определить истинную причину. Отличное от
0 значение счетчика полей свидетельствует о происшедшей ошибке. Число измененных
строк можно определить при обращении к методу mysql_affected_rows() .*
Другие
методы класса Connection
По всему
классу Connection разбросаны два вспомогательных метода, IsConnected() и GetError().
Проверить состояния соединения просто — достаточно посмотреть значение атрибута
connection. Оно должно быть не NULL для MySQL или отличным от -1 для
mSQL. Напротив, сообщения об ошибках требуют некоторых пояснений.
Извлечение
сообщений об ошибках для mSQL просто и безыскусно, нужно лишь использовать значение
глобальной переменной msqlErrMsg . Ее значение точно совпадает с тем, что возвращает
от mSQL метод GetError(). С MySQL дело обстоит несколько сложнее. При обработке
любых сообщений об ошибках необходимо учитывать многопоточность. В многопоточной
среде обработка ошибок осуществляется путем получения сообщений об ошибках с
помощью функции mysql_error() . В примере 13-7 показаны обработка ошибок для
MySQL и mSQL в методе GetError(), а также проверка соединения в методе IsConnected()
.
Пример
13-7. Чтение сообщений об ошибках и другие вспомогательные задачи класса
Connection
int Connection::GetAffectedRows()
{
return affected_rows;
}
char 'Connection::GetError() {
#if defined(HAS_MSQL)
return msqlErrMsg:
#elif defined(HAS_MYSQL)
if( IsConnected()
) {
return mysql_error(connection);
}
else {
return mysql_error(&mysql); }
#else
#error Нет соединения с базой данных,
#endif }
int Connection::IsConnected() {
#if defined(HAS_MSQL)
return !(connection < 0);
#elif defined(HAS_MYSQL)
return !(iconnection);
#else
#error Нет соединения с базой данных,
#endif
)
Проблемы
при обработке ошибок
Хотя обрабатывать
ошибки, как это описано выше, несложно благодаря инкапсуляции обработки в простой
вызов API в классе Connection , следует остерегаться некоторых потенциальных
проблем. Во-первых, при работе с mSQL обработка ошибок осуществляется глобально
в пределах приложения. Если приложение поддерживает несколько соединений, значение
msqlErrMsg относится к последней ошибке последнего вызова какой-либо функции
mSQL API. Следует также учесть, что хотя mSQL - однопоточное приложение, можно
создавать многопоточные приложения, использующие mSQL, но проявлять крайнюю
осторожность при извлечении сообщений об ошибках. Именно, необходимо написать
собственный API, корректно работающий с потоками поверх mSQL С API, который
копирует сообщения об ошибках и связывает их с соответствующими соединениями.
Обе СУБД
управляют и сохраняют сообщения об ошибках внутри своих соответствующих API.
Поскольку вы не распоряжаетесь этой деятельностью, может возникнуть другая проблема,
связанная с запоминанием сообщений об ошибках. В нашем C++ API обработка ошибок
. происходит сразу после их возникновения и до того, как приложение сделает
новое обращение к базе данных. Если мы хотим продолжить обработку и лишь позднее
заняться ошибками, сообщение об ошибке следует скопировать в область памяти
нашего приложения.
Результирующие
наборы
Класс Result
абстрагируется от понятий результатов MySQL и mSQL. Он должен обеспечивать доступ
как к данным результирующего набора, так и к сопутствующим этому набору метаданным.
Согласно объектной модели на рис. 13-1, наш класс Result будет поддерживать
циклический просмотр строк результирующего набора и получение числа строк в
нем. Ниже в примере 13-8 приведен заголовочный файл класса Result.
Пример
13-8. Интерфейс класса Result в result.h
#ifndef 1_result_h
#define 1_result_h
#include <sys/time.h>
#if defined(HASJSQL)
#include <msq1.h>
#elif defined(HAS_MYSQl)
#include <mysq1.h>
#endif
#include "row.h"
class Result
{ private:
int row_count;
T_RESULT *result;
Row *current_row;
public:
Result(T_RESULT *);
~Result();
void Close();
Row *GetCurrentRow();
int GetRowCount();
int Next(); };
#endif // l_result_h
Перемещение
по результатам
Наш класс
Result позволяет работать с результирующим набором построчно. Получив экземпляр
класса Result в результате обращения к методу Query() , приложение должно последовательно
вызывать Next() и GetCurrentRow(), пока очередной Next() не возвратит 0. Пример
13-9 показывает, как выглядят эти действия для MySQL и mSQL.
Пример
13-9. Перемещение по результирующему набору
int Result::Next()
{ T_ROW row;
if( result ==
(T_RESULT *)NULL ) {
throw "Результирующий
набор закрыт.";
}
#if defined(HAS_MSQL)
row = msqlFetchRow(result);
#elif defined(HAS_MYSQL)
row = mysql_fetch_row(result);
#else
#error Нет соединения с базой данных,
#endif if( ! row )
{
current_row = (Row *)NULL;
return 0;
}
else
{
current_row = new Row(result, row);
return 1;
}
}
Row 'Result::GetCurrentRow()
{
if( result == (T_RESULT *)NULL )
{ throw "Результирующий
набор закрыт.";
}
return current_row;
}
Заголовочный
файл row.h в примере 13-11 определяет T_ROW и T_RESULT в зависимости
от того, для какого ядра базы данных компилируется приложение. Перемещение к
следующей строке в обеих базах данных осуществляется одинаково и просто. Вы
вызываете mysql_fetch_row() или msqlFetchRow() . Если вызов возвращает NULL,
значит, необработанных строк не осталось.
В объектно-ориентированной
среде это единственный тип навигации, которым вы должны пользоваться. API для
базы данных в объектно-ориентированном программировании существует лишь для
обеспечения извлечения данных, а не их обработки. Обработка данных должна быть
заключена в объектах доменов. Однако не все приложения являются объектно-ориентированными.
MySQL и mSQL предоставляют функции, позволяющие перемещаться к определенным
строкам в базе данных. Это методы mysql_data_seek() mnsqlDataSeek() соответственно.
Освобождение
ресурсов и подсчет строк
Приложения
баз данных должны освобождать после себя ресурсы. Обсуждая класс Connection,
мы отметили, как результирующие наборы, порождаемые запросом, помещаются в память,
управляемую приложением. Метод Close() класса Result освобождает память, занятую
этим результатом. Пример 13-10 показывает, как освободить ресурсы, занятые результатом,
и получить количество строк в нем.
Пример
13-10. Освобождение ресурсов и подсчет числа строк
void Result::Close()
{
if( result ==
(T_RESULT *)NULL ) { return;
}
#if defined(HAS_MSQL)
msqlFreeResult(result);
#elif defined(HAS_MYSQL)
mysql_free_result(result);
#else
#error Нет соединения
с базой данных, ftendif
result = (TJESULT *)NULL; '
}
int Result::GetRowCount()
{
if( result == (T_RESULT *)NULL )
{
throw "Результирующий набор закрыт.";
}
if( row_count > -1 )
{
return row_count;
}
else
{
#if defined(HAS_MSQL)
row_count = msqlNumRows(result);
#elif defined(HAS_MYSQL)
row_count = mysql_num_rows(result);
#else
#error Нет соединения с базой данных,
#endif
return row_count;
}
}
Строки
Отдельная
строка результирующего набора представляется в нашей объектной модели классом
Row. Класс Row позволяет приложению извлекать отдельные поля строки. В примере
13-11 показано объявление класса Row.
Пример
13-11. Объявление класса Row в row.h
#ifndef l_row_h
#define l_row_h
#include <sys/types.h>
#if defined(HAS_MSQL)
#include <msql.h>
#define T_RESULT m_result
#define T_ROW m_row
#elif defined(HAS_MYSQL)
#include <mysql.h>
#define T_RESULT MYSQL_RES
#define T_ROW MYSQL_ROW
#endif
class Row {
private:
T_RESULT 'result;
T_ROW fields;
public:
Row(T_RESULT *, T_ROW);
~Row();
char *GetField(int);
int GetFieldCount();
int IsClosed();
void Close();
};
#endif // l_row_h
В обоих API
есть макросы для типов данных, представляющие результирующий набор и строку
внутри него. В обоих API строка является массивом строк, содержащих данные этой
строки, и ничем более. Доступ к этим данным осуществляется по индексу массива
в порядке, определяемом запросом. Например, для запроса SELECT user_id , password
FROM users индекс 0 указывает на имя пользователя и индекс 1 -на пароль. Наш
C++ API делает это индексирование несколько более дружественным для пользователя.
GetField(1) возвратит первое поле, или f ields[0]. Пример 13-12 содержит полный
листинг исходного кода для класса Row.
Пример
13-12. Реализация класса Row
#include <malloc.h>
#include "row.h"
Row::Row(T_RESULT
*res, T_ROW row) {
fields = row;
result = res;
}
Row::"Row()
{
if( ! IsClosed()
) {
Close();
}
}
void Row::Close()
{
if( IsClosed()
) {
throw "Строка
освобождена.";
}
fields = (T_ROW)NULL;
result = (T_RESULT *)NULL;
}
int Row::GetFieldCount()
{
if( IsClosed() )
{
throw "Строка
освобождена.";
} #if defined(HASJISQL)
return msqlNumFields(result);
#elif defined(HAS_MYSQL)
return mysql_num_fields(result);
#else
#error Нет соединения с базой данных,
#endif }
// При вызове этого метода нужно быть готовым
// к тому, что может быть возвращен
NULL, char *Row::GetField(int field)
{
if( IsClosed() )
{
throw "Строка
освобождена.";
}
if( field < 1 || field > GetFieldCount() .)
{ throw "Индех
лежит вне допустимых значений.";}
return fields[field-1];
}
int Row::IsClosed()
{
return (fields
== (T_ROW)NULL); }
Пример приложения, использующего эти классы C++, прилагается к книге.