Данная статья не претендует на звание "учебник года" или "самый лучший пример", но необходимые знания для связи и работы с MySQL из С++ - извлечь можно.
Текст статьи разбит на три части - теория, реализация, примеры. К сожалению без теории никуда... однако Я постараюсь представить все самое необходимое, в кратце, доступно и понятно. Приступим...
Что необходимо для того, чтобы разработать приложение, которое будет взаимодействовать с базой данных mysql? Все очень просто - вам необходимо иметь сам mysql, а точнее статическую библиотеку libmysql.lib, которая поставляется вместе с самой бд. На самом деле существует две библиотеки libmysql.lib - одна для отладки, другая для релиза. В винде они находятся в mysql/lib/opt и mysql/lib/debug. Кроме библиотеки понадобятся заголовочные файлы, которые находятся в mysql/include. Приложение будем разрабатывать в MS VS, как не странно - на С++. На самом деле не важно где разрабатывать, главное знать - для того, что бы приложение могло работать с базой данных mysql нужно следующее:
1) подключить дополнительные инклюды - mysql/include2) подключить дополнительную статическую библиотеку - mysql/lib/opt/libmysql.lib
Другими словами - компилируете приложение с инклюдами mysql/include , линкуете с mysql/lib/opt/libmysql.lib . Существует ещё один факт, который Вам необходимо знать. Если Вы установили MySQL с инклюдами для девелоперов и документацией, то у Вас открывается незабываемая возможность стать читателем одного из важных разделов в мануале по MySQL - manual.chm -> API and Libraries -> MySQL C API.
Раздел MySQL C++ API представлен очень смутно, а точнее - он ссылается на готовое решение MySQL++. Если хотите - можете порыться в Интернете на эту тему. Но нас интересует другое - мы хотим изобрести свой велосипед, по этому приступаем к MySQL C API. Коротко и ясно...
Самая главная структура - MYSQL. Как пишут в мануале - структура представляет хэндл соединения с базой данных; другими словами - это то, без чего вы не сделаете ни одного SQL запроса и соответственно не получите результат. Запомните следующее: для одной базы данных (одного соединения) - своя копия структуры MYSQL, в единственном экземпляре - это важно. По большому счёту - эта структура используется практически во всех MySQL Си-шных функциях (так пишут в мане и я им верю, советую верить и Вам :).
Не менее важная структура - MYSQL_RES. Если Вы хоть раз работали с MySQL и (по идее... я надеюсь) делали запросы типа SELECT, SHOW, DESC, то наверняка у Вас был некий результат в виде колонок и ячеек. Предназначение структуры - хранение результата выполненого запроса, для дальнейшей его обработки.
MYSQL_ROW. Результат запроса SELECT, SHOW, DESC представен в виде полей с ячейками. В ячейках находятся некоторые значения. Вот представление значений этих ячеек, если быть точным - самих ячеек - можно изобразить в виде массива MYSQL_ROW. Если верить (а мы заранее верим) мануалу, то это безопасный массив типа строки (не заоверфлоувите). Все данные, которые были извлечены запросом - представленны в виде массива строк MYSQL_ROW.
MYSQL_FIELD. Так как ячейки мы получаем в виде строк, хотелось бы знать что они из себя представляют - это цифра? а может быть это файл? или булево значение... Если Вы не в курсе, из каких типов полей состоит запрашиваемая таблица - эта структура для Вас. Более точное представление структуры Вы можете прочесть в мане. Я отмечу только самые интересные поля: char * name - имя поля; unsigned long length - размер поля (пр. varchar(102) - length = 102); enum enum_field_types type - тип поля (MYSQL_TYPE_LONG - INTEGER, MYSQL_TYPE_VAR_STRING - VARCHAR, и т.д. по ману).
Основные структуры - есть. Дело остается только за основными функциями (основные, но не все... необходимый рацион для работы нижеприведенных примеров). Коротко и ясно...
MYSQL *mysql_init(MYSQL *mysql) - Инициализация. Закидываете в качестве параметра ссылку на вашу структуру MYSQL и получаете её же на выходе. Эту функцию необходимо запускать самой первой, прежде чем работать с остальными функциями mysql-а. Если инициализация прошла неудачно - вы получите на выходе NULL, соответственно дальше нет смысла жить (приложению ;).
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 long client_flag) - соединяет Вас с MySQL сервером. Думаю назначение параметров можно понять по их именам. Некоторые особенности: в качестве host'а Вы можете использовать пайпы (pipes/каналы), если сервер разрешает и поддерживает такие соединения; пароль шифровать нельзя, это делает функция.
void mysql_close(MYSQL *mysql) - тут нечего объяснять. Закрываем соединение. Освобождаем mysql.
int mysql_real_query(MYSQL *mysql, const char *query, unsigned long length) - выполнение SQL запроса на сервере. query - запрос в виде строки, length - длина строки запроса в байтах. Функция выполнилась успешно - результат выполнения == 0.
MYSQL_RES *mysql_store_result(MYSQL *mysql) - Эту функцию необходимо запускать после каждого удачного выполнения SQL запроса выборок (SELECT, SHOW, DESCRIBE, т.д.). Напомню, SQL запросы мы выполняем с помощью функции mysql_real_query. Запускать функцию mysql_store_result необходимо для того, чтобы извлечь данные, которые были получены после выполнения запроса. Существует так же и другая функция с аналогичной задачей - mysql_use_result. Разница заключается в следующем: store_result считывает результат выборки в клиент (тоесть, нам становится известно полное содержание результата); use_result работает наоборот - полный размер результата не известен (как пишут в мане - эта функция работает быстрее mysql_store_result). Существуют так же и другие отличия, которые Вы сможете отметить для себя, когда попробуете заменить store_result на use_result.
void mysql_free_result(MYSQL_RES *result) - освобождает память, выделенную под результат с помощью функций mysql_store_result(), mysql_use_result().
my_ulonglong mysql_num_rows(MYSQL_RES *result) - возвращает количество строк в полученном результате.
unsigned int mysql_num_fields(MYSQL_RES *result) - возвращает количество столбцов в полученном результате.
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result). С помощью этой функции мы извлекаем из нашего результата result строки, поочередно - в цикле. Как видно из описания функции мы получаем данные в виде структуры MYSQL_ROW. Возвращаемая структура представляет из себя массив строк. Каждый элемент массива это ячейка, представленная в текущей строке выборки (результата).
unsigned long *mysql_fetch_lengths(MYSQL_RES *result). Данная функция необходима для того, чтобы иметь представление о длине строки каждой ячейки массива выборки (о как). Другими словами... результат выполнения этой функции - это массив чисел, элементы которого содержат длины строки в каждой ячейке текущего элемента выборки (строки в таблице). Реально функция делает следующее: создает массив, в который запихивает strlen (длину) каждой ячейки. Предназначение поймете из примера.
Вот вроде бы и все, что необходимо иметь у себя в арсенале для реализации простого приложения, которое будет работать с MySQL. Ещё немного теории и приступим к примерам...
Принцип работы MySQL клиента заключается в следующем:
1) инициализируем структуру MYSQL для работы с базой
2) соединяемся с MySQL сервером (выбираем базу данных)
3) выполняем запрос (например SHOW TABLES)
4) выделяем место для результата всей выборки (все поля (строки))
5) обрабатываем полученные данные
6) освобаждаем выделенное место результата
7) закрываем соединение с сервером
Приступим к практике. В качестве примера я предлагаю создать некоторый класс, который будет работать с базой данных. Объект такого класса сможет выполнять любой запрос, получать результат в зависимости от посланного запроса, а так же выделять и освобождать выделенное в процессе работы место в памяти. Так как моя гордость не позволяет мне выкладывать в паблик свежии версии своих исходных кодов, Я поделюсь самой первой версией такого класса... код будет немного смешным (: но работает как надо.
CPPMySQL.h
#include <my_global.h> #include <mysql.h> #include <stdarg.h> #define MYSQLE_INITIALIZATION_ERROR 0x001 #define MYSQLE_CONNECT_ERROR 0x002 #define MYSQLE_UNKNOWN_ERROR 0x003 class CPPMySQLError{ public: // constructor , first param - error code CPPMySQLError(DWORD code); // get error code DWORD GetErrorCode(); // get some text about error CHAR * GetErrorTrace(); private: // error code DWORD ErrorCode; // error text CHAR ErrorTrace[1024]; }; struct mysql_row{ // fields count int fields; // columns count int cols; // row representation struct _row{ // data in row char * data; // length of data int length; }*row; // convert row data in integer int asInt(int index){return atoi(row[index].data);}; }; class CPPMySQL{ public: // constructor - connect to server CPPMySQL(const char * host, const char * user, const char * password) throw (CPPMySQLError); // constructor - connect to server, select db CPPMySQL(const char * host, const char * user, const char * password, const char * dbname) throw (CPPMySQLError); // destructor - destroy connection ~CPPMySQL(); // what was last sql? char * get_last_sql(){return this->last_sql;}; // sql query to server int query(const char * sql,...); // fetch rows from last sql query mysql_row * fetch(int query_result) throw (CPPMySQLError); // free result rows memory (static member) static void free_rows(mysql_row * rows); private: // MYSQL structure MYSQL mysql; // last sql char last_sql[512]; // make char escape, example: ' to \' char * escape(const char *statement); };
CPPMySQL.cpp
#include "CPPMysql.h" /* Constructor initialization */ CPPMySQLError::CPPMySQLError(DWORD code){ this->ErrorCode = code; memset(this->ErrorTrace,0,sizeof this->ErrorTrace); sprintf(this->ErrorTrace,"MySQL error (0x%X)",code); } /* Return error code value */ DWORD CPPMySQLError::GetErrorCode(){ return this->ErrorCode; } /* Return error information */ CHAR * CPPMySQLError::GetErrorTrace(){ return this->ErrorTrace; } /* Constructors */ CPPMySQL::CPPMySQL(const char * host, const char * user, const char * password) throw (CPPMySQLError){ // init this->mysql struct // if failed - throw error with code MYSQLE_INITIALIZATION_ERROR if (!mysql_init(&this->mysql)) throw CPPMySQLError(MYSQLE_INITIALIZATION_ERROR); // connect to MySQL server // if failed - throw error with code MYSQLE_CONNECT_ERROR if (!mysql_real_connect(&this->mysql,host,user,password,NULL,0,NULL,0)) throw CPPMySQLError(MYSQLE_CONNECT_ERROR); } CPPMySQL::CPPMySQL(const char * host, const char * user, const char * password, const char * dbname) throw (CPPMySQLError){ if (!mysql_init(&this->mysql)) throw CPPMySQLError(MYSQLE_INITIALIZATION_ERROR); // connect to MySQL server, select db if (!mysql_real_connect(&this->mysql,host,user,password,dbname,0,NULL,0)) throw CPPMySQLError(MYSQLE_CONNECT_ERROR); } /* Destructor */ CPPMySQL::~CPPMySQL(){ // close connection // free this->mysql mysql_close(&this->mysql); } /* Sends sql query to the server If there is any parameters after sql statement: - check out this params and make some chages in sql query - change %d to digit parameter, %s to string, %c to char, %f to float Stupid code (: TODO: if there is no params after sql - just do the fucking query Return value: not null - all ok */ int CPPMySQL::query(const char *sql,...){ va_list vl; va_start(vl,sql); int sql_len = strlen(sql), i = 0, j = 0, a = 0, len = 0; int new_len = sql_len; char temp[64]; char * string; /* count new length */ while (i < sql_len){ if (sql[i++] == '%' && i != sql_len){ memset(temp,'\0',64); switch (sql[i]){ case 's' : new_len += strlen(this->escape(va_arg(vl, char*))) - 2; break; case 'd' : sprintf(temp,"%d",va_arg(vl,int)); new_len += strlen(temp) - 2; break; case 'c' : sprintf(temp,"%c",va_arg(vl,char)); string = this->escape(temp); new_len += strlen(string) - 2; break; case 'f' : sprintf(temp,"%f",va_arg(vl,float)); new_len += strlen(temp) - 2; break; } } } va_end(vl); char * new_sql = new char[new_len + 1]; memset(new_sql,'\0',new_len + 1); va_start(vl,sql); i = 0; /* make new sql with changes */ while (i < sql_len){ if (sql[i++] == '%' && i != sql_len){ memset(temp,'\0',64); switch (sql[i++]){ case 's' : string = this->escape(va_arg(vl,char*)); len = strlen(string); for (a=0; a<len; a++) new_sql[j++] = string[a]; break; case 'd' : sprintf(temp,"%d",va_arg(vl,int)); len = strlen(temp); for (a=0; a<len; a++) new_sql[j++] = temp[a]; break; case 'c' : sprintf(temp,"%c",va_arg(vl,char)); string = this->escape(temp); len = strlen(string); for (a=0; a<len; a++) new_sql[j++] = string[a]; break; case 'f' : sprintf(temp,"%f",va_arg(vl,float)); len = strlen(temp); for (a=0; a<len; a++) new_sql[j++] = temp[a]; break; default: new_sql[j++] = sql[i - 1]; } }else new_sql[j++] = sql[i - 1]; } va_end(vl); memset(this->last_sql,0,sizeof this->last_sql); memcpy(this->last_sql,new_sql,new_len); // go query, go! int result = mysql_real_query(&this->mysql,new_sql,new_len); // we don't need you any more... delete new_sql; return result; } /* We have result from query method We want to get our fields and rows Return value: all ok - mysql_row array , or null if there was error/no rows There is exceptions when something goes wrong... */ mysql_row * CPPMySQL::fetch(int query_result) throw (CPPMySQLError){ if (query_result) return NULL; MYSQL_RES * result = mysql_store_result(&this->mysql); if (result == NULL) throw CPPMySQLError(MYSQLE_UNKNOWN_ERROR); int num_rows = mysql_num_rows(result); if (!num_rows){ mysql_free_result(result); return NULL; } MYSQL_ROW real_row; int num_cols = mysql_num_fields(result); unsigned long * lengths; // our new mysql_row array mysql_row * rows = new mysql_row[num_rows]; /* copy result from MYSQL_ROW to our mysql_row */ for (int i=0; i<num_rows; i++){ rows[i].fields = num_rows; rows[i].cols = num_cols; rows[i].row = new mysql_row::_row[num_cols]; real_row = mysql_fetch_row(result); if (real_row == NULL){ mysql_free_result(result); throw CPPMySQLError(MYSQLE_UNKNOWN_ERROR); } lengths = mysql_fetch_lengths(result); for (int j=0; j<num_cols; j++){ rows[i].row[j].length = (int)lengths[j]; rows[i].row[j].data = new char[(int)lengths[j] + 1]; memset(rows[i].row[j].data,0,(int)lengths[j] + 1); memcpy(rows[i].row[j].data,real_row[j],(int)lengths[j]); } } mysql_free_result(result); return rows; } /* When we don't need mysql_row array result any more: drop result, by memory free (: */ void CPPMySQL::free_rows(mysql_row * rows){ if (rows == NULL) return; for (int i=0; i<rows->fields; i++){ for (int j=0; j<rows->cols; j++) delete rows[i].row[j].data; delete [] rows[i].row; } delete [] rows; } /* SQL-Injection... go away? :D */ char * CPPMySQL::escape(const char *statement){ char temp[1024]; mysql_real_escape_string(&this->mysql,temp,statement,strlen(statement)); return temp; }
Пример:
#include <windows.h> #include "CPPMySQL.h" void main(int argc, char * args[]){ // Объявляем объект mysql, с помощью которого будем работать с базой // Инициализируем как NULL CPPMySQL * mysql = NULL; // пробуем запуститься try{ printf("connect to server..."); // Выделяем место для нашего объекта mysql // Соединяемся с localhost под логином tester, без пароля // Выбираем базу данных test mysql = new MySQL("localhost","tester","","test"); printf("ok\n"); printf("create table test..."); // Выполняем запрос - создаем таблицу test if (!mysql->query("create table test(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, tkey VARCHAR(128), tvalue VARCHAR(255))")){ // Запрос не выполнился printf("error"); // Освобождаем место, занятое объектом mysql delete mysql; return; } printf("do next queries:\n"); // Выполняем запросы в цикле (10 раз) for (int i=0; i<10; i++){ printf("insert into test values(NULL,\"%s\",\"value-%d\")...","key",i*2); /* Выполняем запрос, метод mysql->query совершит замену в запросе "insert into test values(NULL,\"%s\",\"value-%d\")" следующим образом: %s = "key" %d = i*2 */ if (!mysql->query("insert into test values(NULL,\"%s\",\"value-%d\")","key",i*2)) printf("ok\n"); else{ // Запрос не выполнился printf("error\n"); // Освобождаем место, занятое объектом mysql delete mysql; return; } } printf("do select:\n"); // Выполняем запрос выборки в базе // Передаем результат запроса mysql->query в mysql->fetch // mysql->fetch возвращает массив ячеек, который присвается в rows mysql_row * rows = mysql->fetch(mysql->query("select id, tvalue from test order by id desc limit 0,5")); if (rows == NULL){ // Либо ячеек не было, либо запрос был не успешным printf("wtf? no rows?"); delete mysql; return; } // Делаем распечатку полученных ячеек // rows[i] - строка // rows[i].row[j] - столбец for (int i=0; i<rows[0].fields; i++){ for (int j=0; j<rows[0].cols; j++) printf("%s\t",rows[i].row[j].data); printf("\n"); } // Результат выборки (rows) более нам не нужен - освобождаем занятое место в памяти CPPMySQL::free_rows(rows); printf("drop table test..."); // Выполняем запрос - дропнуть таблицу test if (!mysql->query("drop table test")) printf("ok\n"); else printf("error\n"); // Закрываем соединение, освобождаем место... delete mysql; // словили нашу (CPPMySQLError) ошибку где-то }catch (CPPMySQLError error){ // печатаем ошибку printf("MySQL error: %s\n", error.GetErrorTrace()); // если было установленно соединение - закрываем if (mysql != NULL) delete mysql; return; } }
Вот вообщем и все. Теперь Вы должны иметь представление на сколько легко связать C++ с MySQL. Если Вас интересует более углубленное познание связки C++ и MySQL'а - прошу, manual.chm в папке MySQL\Docs всегда рад видеть Вас.
© Guru 05.06.2008

хе-хе (:
только заметил что пропали в sql запросах и какое-то "..." в запросе с insert появилось :D