---------------------------------------------
#!/usr/share/doc/defaced/3/tandp/mysqlapi.txt
---------------------------------------------
/////////////////////////////////////////////////////////////////////////////////
// Тема.......Работа с БД MySQL при помощи C / C++. MySQL API и libmysql.
// Автор......black c0de
// Группа.....[the nobodies] //[tN]
// [email protected]
// HTTP.......http://nteam.ru/
// DATE.......21.09.03
//
//
// # Все права принадлежат [the nobodies]
// # любое распространение возможно только с сохранением копирайтов и тд и тп
//
////////////////////////////////////////////////////////////////////////////////
Эта статья посвящена теме базы данных MySQL, а именно использования всей
мощности этой БД в своих приложениях, написанных на языках С/C++. В статье будут
описаны некоторые интересные хинты, которые были освоены в процессе работы с
mysql-API.
Статья пишется на основании личного опыта работы, потому если у кого будет что
сказать нового по-поводу работы с libmysql и MYSQL API - пишите.
//===( 0x0 :: Содержание )======================================================
[0x0] Содержание
[0x1] Предисловие
[0x2] Библиотека libmysql
[0x3] Что необходимо для работы
[0x4] Перечень основных функций и типов данных
[0x5] Логика работы с mysql-api
[0x6] Хинты
[0x7] Линки на полезные русурсы
[0x8] greetZ
//===( 0x1 :: Предисловие )=====================================================
Сейчас сложно представить себе наш мир без компутеров, без интернета. Век
информации. Кто владеет информацией - тот владеет миром. Сейчас это справедливо
как никогда. Итак, о наших баранах, а точнее баране - mysql это реляционная база
данных. Реляционная от слова "реляции", то есть таблицы. Это значит что вся
информация в БД представлена в виде таблиц. mysql считается самой
распространенной БД. Сложно найти хостера, который не поддерживает mysql. Почти
каждый нормальный движок для сайта пользует для своих нужд БД, зачастую это
mysql. Но так повелось что услугами этой мощнейшей БД пользуются или при
разработке web-интерфейсов и приложений или же в клиент-серверных приложениях,
используя интерфейс ODBC и всякие ацтойные ODBC-драйвера. Но мы пойдем другим
путем. Мы будем работать с данной БД на самом низком уровне. Что, интересно
стало? ;)
Так вот, все mysql client's, интерфейсы для администрирования и прочая лабуда,
включая интерфейс для тех же PHP и Perl пользуются так называемым MYSQL API, это
самый низкий уровень, на который мы можем опуститься, программируя свою тулзу
для работы с БД.
//===( 0x2 :: Библиотека libmysql )=============================================
В предыдущем разделе я упомянул про MySQL API. Итак, что такое MySQL API? API -
application programming interface. То есть это набор функций, которые
предоставлены самими разработчиками данной БД. MYSQL API содержат большое
количество функций и встроенных типов данных. С помощью этих функций мы можем
делать все что только прийдет в голову. Подавать любые запросы, выставлять права
доступа к БД, создавать и удалять БД, таблицы, в общем ВСЕ! Все эти функции
находятся в библиотеке libmysql. Можете ковырнуть их при помощи dumpbin/etc и
заглянуть в истоки mysql API, посмотреть на их сигнатуры 8) Есть две библиотеки:
динамическая libmysql.dll и статическая libmysql.lib Данные библиотеки доступны
с сервера mysql.com.
//===( 0x3 :: Что необходимо для работы )======================================
Для того чтобы приступить к написанию своей программы необходимо следующее:
[x] динамическая библиотека libmysql.dll
[x] статическая библиотека libmysql.lib для статической линковки
[x] заголовочные файлы, в стандартной поставке есть следующее файло:
- DBUG.H
- ERRMSG.H
- Libmysql.def
- M_CTYPE.H
- M_STRING.H
- MY_LIST.H
- my_pthread.h
- MY_SYS.H
- Mysql.h
- mysql_com.h
- mysql_version.h
- mysqld_error.h
- RAID.H
[x] ну и конечно же С-компилятор, я пользую Visual C++ 6.0
//===( 0x4 :: Перечень основных функций и типов данных )========================
Перечислять все функции с сигнатурами и все типы данных в рамках одной статьи
просто нереально. Я приведу список ОСНОВНЫХ функций, которыми приходится
пользоваться при написании ПО и основных встроенных типов данных и структур.
Если кто заинтересуется темой статьи и решит серьезно заняться разработкой
программ с использованием MySQL API - смотрите в разделе [0x7] перечень линков,
по которым можно найти массу инфы по данной теме.
Итак. Есть три основных типа данных:
MYSQL
Эта структура является своеобразным дескриптором базы данных для текущего
соединения. Ей приходится пользоваться почти во всех функциях.
MYSQL_RES
Эта структура содержит результат запроса, который возвращает строки (SELECT,
SHOW, DESCRIBE, EXPLAIN). То есть если вы запросили select * from mytable -
структура будет содержать таблицу с данными, которые вернул сервер mysql. ну и
т.п.
MYSQL_ROW
Этот тип используется для доступа к отдельной строке таблицы, которую мы
получили от сервера в ответ на запрос. Этот тип реализован как массив строк с
фиксированным количеством байтов (их нельзя трактовать как строки с нулевым
символом в конце, если величины полей могут содержать двоичные данные, поскольку
они могут содержать ноль байтов). Строки можно получить вызовом функции
mysql_fetch_row().
Ну и вот список функций, сигнатуры и более детальная информация есть в хедерах и
мануале, которые доступны с mysql.com.
mysql_affected_rows()
mysql_change_user()
mysql_character_set_name()
mysql_close()
mysql_connect()
mysql_create_db()
mysql_data_seek()
mysql_debug()
mysql_drop_db()
mysql_dump_debug_info()
mysql_eof()
mysql_errno()
mysql_error()
mysql_escape_string()
mysql_fetch_field()
mysql_fetch_field_direct()
mysql_fetch_fields()
mysql_fetch_lengths()
mysql_fetch_row()
mysql_field_count()
mysql_field_seek()
mysql_field_tell()
mysql_free_result()
mysql_get_client_info()
mysql_get_host_info()
mysql_get_proto_info()
mysql_get_server_info()
mysql_info()
mysql_init()
mysql_insert_id()
mysql_kill()
mysql_list_dbs()
mysql_list_fields()
mysql_list_processes()
mysql_list_tables()
mysql_num_fields()
mysql_num_rows()
mysql_options()
mysql_ping()
mysql_query()
mysql_real_connect()
mysql_real_escape_string()
mysql_real_query()
mysql_reload()
mysql_row_seek()
mysql_row_tell()
mysql_select_db()
mysql_shutdown()
mysql_stat()
mysql_store_result()
mysql_thread_id()
mysql_use_result()
как видите, достаточно широкий спектр разнообразных функций 8)
//===( 0x5 :: Логика работы с mysql-api)========================================
Теперь стоит рассмотреть алгоритм работы. Так как функций достаточно много - не
сразу становится понятным что как и когда вызывать для получения нужного
результата. Для начала объявим переменные, без которых никак не обойтись:
MYSQL mysql;
MYSQL_ROW row;
MYSQL_RES *res;
// перед коннектом к серверу устанавливайте reconnect=true. если программе не
// удастся соединится с сервером, она продолжит попытки подключения, избавляя
// вас тем самым, от лишних телодвижений. ессно, иногда это не нужно, так что
// каждый сам для себя решает как ему лучше.
mysql.reconnect = true;
// дальше инициализируем объект типа MYSQL. это ОБЯЗАТЕЛЬНО!
mysql_init( &mysql );
// следующий шаг - непосредственное соединение с сервером. для этого пользуем
// mysql_real_connect(). о том, почему нужно использовать именно эту функцию
// смотрите в разделе 0x6 Хинты
// сигнатура функции такова:
MYSQL *mysql_real_connect(
MYSQL *mysql,
const char *host,
const char *user,
const char *passwd,
const char *db,
unsigned int port,
const char *unix_socket, // при написании софтины под винду установите в NULL
unsigned int client_flag); // дополнительные флаги. подробнее в разделе 0x6
// тут все предельно ясно. имена переменных в параметрах говорят об их
// предназначении
// итак, запрос на соединение мы подали. что дальше? дальше - !!! возьмите себе
// за правило проверять коды возвращаемых ошибок, это вас избавит от
// преждевременного геморроя 8)
// результат выполнения любой функции можно проверить следующим образом:
// есть две функции:
unsigned int mysql_errno(MYSQL *mysql);
char *mysql_error(MYSQL *mysql);
// первая возвращает числовое значение кода ошибки. если ошибок не было -
// возвращает 0. вторая, mysql_error(MYSQL*) возвращает текстовое описание
// ошибки. это оч удобно с точки зрения отладки и диагностики ошибок. то есть
// если mysql_errno()!=0 значит выводим сообщение об ошибке при помощи
// mysql_error()
if( mysql_errno( &mysql ) ) // если не равно 0...
fprintf(stderr, "[err#%u] %s\r\n",mysql_errno(&mysql), mysql_error(&mysql) );
// ...выводим мессадж об ошибке
// ну а если все ок - продолжаем дальше.
// итак. к серверу мы законектились. теперь приступаем к делу, ведь вы
// конектились с какой-то целью? к примеру вы хотите получить значения таблицы
// passwordz, которая находится в базе данных cool_dialup_provider. ок, не вопрос.
// оформляем запрос. думаю вы знакомы с языком SQL 8)
char query_ptr[1024]; //отхватим побольше памяти 8)
sprintf(query_ptr, "use cool_dialup_provider");
// дальше необходимо подать серверу запрос, для этого была придумана следующая
// функция:
int mysql_query(MYSQL *mysql, const char *query) ;
// надеюсь вы уже все поняли, *mysql - это указатель на дескриптор соединения,
// который мы ранее использовали в mysql_init() и mysql_real_connect()
if( mysql_query( &mysql, query_ptr ) )
fprintf(stderr, "[err#%u] %s\r\n",mysql_errno(&mysql), mysql_error(&mysql) );
// если в процессе выполнения функции возникла ошибка - возвращаемое значение
// НЕ равно 0 вот возможные возвращаемые ошибки:
// CR_COMMANDS_OUT_OF_SYNC Команды были выполнены в ненадлежащем порядке
// CR_SERVER_GONE_ERROR Сервер MySQL неожиданно завершил работу
// CR_SERVER_LOST Соединение с сервером прервалось в процессе данного запроса
// CR_UNKNOWN_ERROR Произошла неизвесная ошибка
// итак, запрос выполнили. к базе присоединились. теперь приступим к следующему
// шагу - получение информации из таблицы. первые шаги абсолютно аналогичны -
// оформляем sql-запрос, подаем запрос серверу, если ошибок не возникло - получаем
// результат запроса.
sprintf(query_ptr, "select * from passwordz");
// вывести все данные из таблицы passwordz
if( mysql_query( &mysql, query_ptr ) || mysql_field_count()==0 )
fprintf(stderr, "[err#%u] %s\r\n",mysql_errno(&mysql), mysql_error(&mysql) );
// наш запрос должен вернуть данные из таблицы. итак, мы подошли к оч интересному
// моменту. бывают случаи, когда в процессе возникли ошибки, или же вы подали
// кривой запрос. чтобы работу нашей программы сделать более надежной после
// запросов, которые должны вернуть данные (SELECT, SHOW, DESCRIBE, EXPLAIN) вместе
// с функцией mysql_errno() проверяйте вернул ли сервер вообще какую-то таблицу
// данных:
unsigned int mysql_field_count(MYSQL *mysql)
// эта функция возвращает количество полей таблицы, которую вернул сервер.
// если возникла ошибка это значение будет равно 0
// если ошибок нет - работаем дальше
// итак, мы подали запрос, который должен вернуть нам данные. в случае таких
// запросов результирующие данные помещаются в объект типа MYSQL_RES *res и дальше
// уже спецовыми функциями обрабатывается и получаются сырые данные, то есть то что
// нужно - plain text.
res = mysql_store_result( &mysql ); //загоняем в MYSQL_RES данные
// будте внимательны с функцией mysql_store_result(), подробнее читайте в 0x6.
// бывают случаи, когда в таблице нет данных. обработать эту ситуацию можно при
// помощи следующей функции:
my_ulonglong mysql_num_rows(MYSQL_RES *result);
// my_ulonglong объявлен так: typedef unsigned long my_ulonglong;
// то есть понятно - функция вернет количество строк в таблице, которую вернул
// сервер. если это значение равно 0 - переходим к обработке этой ситуации, а не
// тратим время и силы на непонятно что.
// допустим сервер вернул нам данные. бывают случаи когда вы не знаете структуры
// таблицы, поэтому минимум что нужно - знать количество столбцов для вывода
// таблицы в нормальном виде, для этого была введена функция:
unsigned int mysql_field_count(MYSQL *mysql);
unsigned int colnum = mysql_field_count( &mysql );
// опять же, можете проверять не равно ли значение нулю перед обработкой
// результата
// допустим количество столбцов равно 3, теперь приступим непосредственно к
// извлечению данных, !данные считываются ПОСТРОЧНО! для этого используем
// функцию:
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);
// MYSQL_ROW это своеобразный массив строк
// итак у нас таблица с тремя столбцами больше нам ничего не известно.
// конечно, есть функции для извлечения имен столбцов и прочих радостей, но это
// уже вы сами решите нужно это вам или нет
unsigned int rnum=0;
while ( row = mysql_fetch_row( res ) ){
printf("[%u]---| ", rnum++); // ну эт уже чтобы красиво табличку вывести
for(unsigned int cnt=0; cnt<colnum; cnt++)
printf(" | %s |",(((row[cnt]==NULL)||(!lstrlen(row[cnt])))?"NULL":row[cnt]));
printf("\r\n");
}
// то есть в цикле выводим все строки таблицы пока функция mysql_fetch_row()
// что-то возвращает
// можете выводить строки в свои переменные и потом их обработать, можете в файл.
// как хотите так и делайте. значительно удобнее когда нам известна структура
// таблицы.
// после того как была извлечена последняя строка необходимо "очистить"
// результирующий набор данных, который вернул нам сервер. пользуем спецовую
// функцию:
void mysql_free_result(MYSQL_RES *result);
// и отсоединяемся от сервера:
void mysql_close(MYSQL *mysql);
//===( 0x6 :: Хинты )============================================================
[--[0x6 - 0]---
Первый и основной хинт - проверяйте всегда возвращаемые значения функций:
mysql_errno() и mysql_error(). Напомню еще раз:
unsigned int mysql_errno(MYSQL *mysql);
если возникла ошибка - возвращает значение, отличное от нуля
char *mysql_error(MYSQL *mysql)
если возникла ошибка - возвращает значение, отличное от NULL
[--[0x6 - 1]---
Всегда используйте mysql_real_connect() всесто mysql_connect(). Причин тому
несколько: самая основная - эта функция возвращает значение ошибок в случае
потери соединения с сервером, mysql_connect() этого не делает.
вот коды ошибок для mysql_real_connect()
CR_CONN_HOST_ERROR Не удалось соединиться с сервером MySQL.
CR_CONNECTION_ERROR Не удалось соединиться с локальным сервером MySQL.
CR_IPSOCK_ERROR Не удалось создать IP-сокет.
CR_OUT_OF_MEMORY Недостаток памяти.
CR_SOCKET_CREATE_ERROR Не удалось создать Unix сокет.
CR_UNKNOWN_HOST Не удалось найти IP-адрес для данного имени хоста.
CR_VERSION_ERROR Несоответствие протокола, что явилось результатом попытки
соединения с сервером с клиентской библиотекой, использующей иную версию
протокола. Это может произойти при использовании очень старой клиентской
библиотеки для подключения к новому серверу, при запуске которого не была
установлена опция --old-protocol
CR_NAMEDPIPEOPEN_ERROR Не удалось создать именованный канал на Windows.
CR_NAMEDPIPEWAIT_ERROR Не удалось дождаться именованного канала на Windows.
CR_NAMEDPIPESETSTATE_ERROR Не удалось получить обработчик канала на Windows.
CR_SERVER_LOST Если connect_timeout > 0 и требовалось больше, чем
connect_timeout секунд для соединения с сервером или если сервер прекратил
работу во время выполнения init-command.
Дальше. Взгляните на сигнатуры этих функций и вы заметите что
mysql_real_connect() позволяет более тонко указать параметры соединения:
MYSQL *mysql_real_connect(
MYSQL *mysql,
const char *host,
const char *user,
const char *passwd,
const char *db,
unsigned int port,
const char *unix_socket,
unsigned int client_flag)
MYSQL *mysql_connect(
MYSQL *mysql,
const char *host,
const char *user,
const char *passwd)
что мы имеем? имеем 4 дополнительных параметра! давайте подробнее разберем их:
const char *db - вы можете задать имя базы данных, используемой по-умолчанию
следует заметить что по-умолчанию только для данного соединения, то есть с
данным дескриптором MYSQL
unsigned int port - также мы можем указать порт, на котором следует искать
сервер БД mysql
const char *unix_socket - сокет или именованный канал, который следует
использовать
unsigned int client_flag:
Можно задать дополнительные флаги:
CLIENT_COMPRESS Использовать сжатие в протоколе.
CLIENT_FOUND_ROWS Возвращать количество найденных (совпавших) строк, а не
количество строк, подвергшихся воздействию.
CLIENT_IGNORE_SPACE Допускать пробелы после имен функций. Сделать имена всех
функций зарезервированными словами.
CLIENT_INTERACTIVE Допускать простой длительностью interactive_timeout секунд
(вместо wait_timeout секунд) перед закрытием данного соединения.
CLIENT_NO_SCHEMA Запретить использование формы db_name.tbl_name.col_name. Это
делается для ODBC и заставляет синтаксический анализатор генерировать ошибку при
использовании данного синтаксиса, который полезен для выявления ошибок в
некоторых программах ODBC.
CLIENT_ODBC Клиентом является клиент ODBC. Настраивает mysqld для большей
совместимости с ODBC.
CLIENT_SSL Использовать SSL (протокол шифрования).
как видите, существенные преемущества 8)
[--[0x6 - 2]---
следующий хинт - использование функции mysql_store_result(). это опять таки
очень важный момент, поэтому советую запомнить его и если не до конца поняли
суть - перечитать еще раз. так вот, функция mysql_store_result() вызывается для
получения результирующего набора данных, то есть загрузкой объекта MYSQL_RES.
итак, постараюсь максимально понятно объяснить в чем хинт:
стандартная последовательность:
1) подаем запрос при помощи mysql_query()
2) если нет ошибок - получаем результат в MYSQL_RES при помощи
mysql_store_result()
3) освобождаем выделенную память при помощи mysql_free_result()
итак, если вы забудете вызвать mysql_store_result() после успешно выполненной
mysql_query() вас ожидает НЕприятный сюрприз - после следующего запроса вы
получите данные, которые вернул сервер для предыдущего запроса! потому после
КАЖДОГО запроса, который должен вернуть данные вызывайте ВСЕГДА
mysql_store_result() ну и возьмите за правило хорошего тона -освобождение
ненужной памяти, в данном случае - mysql_free_result() этим и займеться.
[--[0x6 - 3]---
библиотеки и инклуды найти очень легко, они есть в каждом полноценном
дистрибутиве mysql ;)
[--[0x6 - 4]---
очень важный момент - использовать версии libmysql.lib и libmysql.dll с одной и
той же версии дистрибутива mysql! оказывается, иногда бывают нереальные глюки 8)
[--[0x6 - 5]---
если вы собираетесь использовать Builder C++, этот хинт именно для вас!
(+) чтобы получить корректную статическую библиотеку пользуйте implib.exe:
implib.exe libmysql.lib libmysql.dll
(+)
Если компилятор ругается на переменную my_socket, (см. mysql.h) - нужно
заменить ее переопределение с #define my_socket SOCKET на
#define my_socket UINT_PTR
//===( 0x7 :: Линки на полезные русурсы )=======================================
(+) оффициальный мануал на русском языке в zip:
http://www.mysql.com/downloads/download.php?file=Downloads%2FManual%2Fmanual.ru.zip&pick=mirror
(+) дистрибутивы mysql под разные ОС и прочее для скачки:
http://www.mysql.com/downloads/index.html
(+) библиотеки и инклуды в одном архиве:
http://ssz.by.ru/apach_sql/mysqlapi.zip
//===( 0x8 :: greetZ )==========================================================
greetz2:
--------
firew0rker, [R00T], S`QuaD, bio3k, OxiD, Xternal, euronymous, Over_G, sp0raw, tyuba,
Kabuto, XPYD3X
&&
F0kp, ZUD Team, DWC Security Group, m00security, cyberinfo
respect2:
---------
security.nnov.ru, securityfocus.org, void.ru, wasm.ru
#
# all rights reserved (c) 2003 black c0de //tN [the nobodies] :: [www.nteam.ru]
#
################################################################################