Контакты

Для связи с нами можно использовать:
irc://irc.chatnet.ru:#gfs
icq://546460
email://cobalt[@]gfs-team.ru

Все материалы предоставлены только с ознакомительной целью
ГлавнаяСтатьиКодингС++. Работа с MySQL
© Guru 05.06.2008

Данная статья не претендует на звание "учебник года" или "самый лучший пример", но необходимые знания для связи и работы с MySQL из С++ - извлечь можно.

Текст статьи разбит на три части - теория, реализация, примеры. К сожалению без теории никуда... однако Я постараюсь представить все самое необходимое, в кратце, доступно и понятно. Приступим...

Что необходимо для того, чтобы разработать приложение, которое будет взаимодействовать с базой данных mysql? Все очень просто - вам необходимо иметь сам mysql, а точнее статическую библиотеку libmysql.lib, которая поставляется вместе с самой бд. На самом деле существует две библиотеки libmysql.lib - одна для отладки, другая для релиза. В винде они находятся в mysql/lib/opt и mysql/lib/debug. Кроме библиотеки понадобятся заголовочные файлы, которые находятся в mysql/include. Приложение будем разрабатывать в MS VS, как не странно - на С++. На самом деле не важно где разрабатывать, главное знать - для того, что бы приложение могло работать с базой данных mysql нужно следующее:

1) подключить дополнительные инклюды - mysql/include
2) подключить дополнительную статическую библиотеку - 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,'�',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,'�',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,'�',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("okn");
		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("okn");
			else{
				// Запрос не выполнился
				printf("errorn");
				// Освобождаем место, занятое объектом 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("%st",rows[i].row[j].data);
			printf("n");
		}
		// Результат выборки (rows) более нам не нужен - освобождаем занятое место в памяти
		CPPMySQL::free_rows(rows);
		printf("drop table test...");
		// Выполняем запрос - дропнуть таблицу test
		if (!mysql->query("drop table test")) printf("okn");
		else printf("errorn");
		// Закрываем соединение, освобождаем место...
		delete mysql;
	// словили нашу (CPPMySQLError) ошибку где-то
	}catch (CPPMySQLError error){
		// печатаем ошибку
		printf("MySQL error: %sn", error.GetErrorTrace());
		// если было установленно соединение - закрываем
		if (mysql != NULL) delete mysql;
		return;
	}
}

Вот вообщем и все. Теперь Вы должны иметь представление на сколько легко связать C++ с MySQL. Если Вас интересует более углубленное познание связки C++ и MySQL'а - прошу, manual.chm в папке MySQLDocs всегда рад видеть Вас.

P.s А для того чтобы получилось нужно правильно питаться


© Guru 05.06.2008

e-Commerce Partners Network
Guru написал:

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

Guru написал:

если чё - тут спецом допущено пару ошибок :))

Cobalt написал:

lol =)

lol написал:

чё за фигня?

фигня написал:

искали?

Guru написал:

норке, не норкоманте (:

ssv написал:

Ну код как то не очень хорошо написан. Не нужно мешать С и С++, это не есть айс.

Ник:

Текст:
P Br B I Qute



Код: обновить
Последние комментарии
19.11.2017 07:57:37 Yakunmip написал:
tilaDatt ImmenseZinna IroriaShoobbog ...
Пишем guestbook
19.11.2017 05:42:41 YakunKeway написал:
enusbansiddign COoroTeatroff Byday ...
Пишем guestbook
19.11.2017 01:19:37 Dmitriyvah написал:
byncecopesy SlaltPog Kt ...
Пишем guestbook
Реклама

Тут должна была быть ваша реклама, но мы потеряли глиняную табличку с ее текстом. SapeId: 665044

Rambler's Top100