Содержание


Практическое использование MySQL++

Часть 5. Работа с различными типами данных

Comments

Серия контента:

Этот контент является частью # из серии # статей: Практическое использование MySQL++

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Практическое использование MySQL++

Следите за выходом новых статей этой серии.

Обработка данных является главной задачей инструмента, обеспечивающего взаимодействие с СУБД. При этом важное значение имеет установление фактических соответствий между типами данных, применяемых в СУБД (в нашем случае - MySQL), и типами данных языка программирования C++. В предыдущих статьях цикла уже рассматривались примеры, в которых использовались числовые данные (целочисленные и с плавающей точкой) и простые строки без каких бы то ни было проблем.

Тем не менее, при работе с данными более сложных типов возникают определённые вопросы. Даже с теми же строками не всегда всё так просто и ясно. Поэтому сейчас мы рассмотрим различные методы работы со строками, предлагаемыми библиотекой MySQL++, а затем перейдём к вопросам обработки бинарных данных и изображений в BLOB-полях.

1. Различные типы строк в MySQL++

В библиотеке MySQL++ есть два класса, поведение которых в большой степени похоже на поведение std::string - стандартного класса в языке C++: String и SQLTypeAdapter. Функциональные свойства этих классов специализированы по сравнению с std::string, но они не являются производными от этого стандартного класса. Вообще говоря, в коде приложения нет особой необходимости напрямую использовать вышеуказанные классы, поскольку в качестве обобщённого средства обработки строк std::string подходит лучше, к тому же сама MySQL++ в большинстве случаев обращается именно к применению std::string. Но варианты использования специализированных строковых типов также заслуживают внимания, поскольку повышают эффективность функционирования MySQL++.

1.1. Класс SQLTypeAdapter

Начнём с класса SQLTypeAdapter, так как он более прост в применении.

По его наименованию можно понять, что данный класс предназначен для преобразования ("адаптации") других типов данных с тем, чтобы их можно было использовать в SQL. SQLTypeAdapter содержит набор конструкторов преобразования для всех типов данных, которые предположительно могут встретиться в SQL-запросе. Сами по себе SQL-запросы представляют собой строки, поэтому конструкторы преобразования в строковые типы просто копируют строку запроса, но все прочие конструкторы действительно выполняют преобразования значений из "строковой" формы в формат, требуемый для SQL.

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

Класс SQLTypeAdapter применяется там, где MySQL++ необходимо принять любой (произвольный) из нескольких типов данных для использования в SQL-запросе. В основном это работа механизма шаблонов запросов (template query) и подключение механизма заключения в кавычки и экранирования символов в потоке Query. Каждый раз, когда вы передаёте значения в методы MySQL++ для формирования SQL-запроса, эти данные проходят через объект SQLTypeAdapter. Поэтому, подводя итог, можно сказать, что класс SQLTypeAdapter является ключевым элементом библиотеки MySQL++, отвечающим за генерацию синтаксически корректных SQL-запросов.

1.2. Класс String

Класс String представляет наиболее обобщённый строковый тип внутри библиотеки MySQL++, но в текущей версии (на момент написания статьи - 3.0.9) пока ещё не обладает функциональностью, достаточной для того, чтобы рекомендовать его для повсеместного использования.

Главным "преимуществом" класса String над стандартным std::string является возможность преобразования строк, содержащих значения в SQL-форматах, в типы данных языка C++. Например, если имеется строка "2010-01-20", то вы можете преобразовать эту String-строку в тип Date; и не потому, что Date-объект знает, как использовать String для собственной инициализации, а как раз наоборот: String содержит набор операторов неявного (скрытого) преобразования, определённых для каждого типа.

С учётом того, что оператор обращения к элементу данных в строке Row::operator[] возвращает значение типа String, можно написать, например, следующую инструкцию кода:

int x = row["x"];

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

Можно сказать, что String является противоположностью SQLTypeAdapter. Если String выполняет преобразование строк SQL-значений в типы данных C++, то SQLTypeAdapter конвертирует типы данных C++ в строки, содержащие SQL-значения.

1.3. Как применять String

Чаще всего String используется для извлечения значения поля из строки Row, как это было показано выше. То есть, во всех случаях, когда MySQL++ извлекает данные из базы данных, они проходят через String-объект со всеми соответствующими преобразованиями, необходимыми для получения корректных типов C++.

Поскольку String является конечной формой данных в наборе результатов перед тем, как они попадут в пользовательский код, возможно возникновение ситуаций, в которых будут создаваться копии этих наборов данных. Если приложение обращается к серверу базы данных в сетевой среде, и в таблицах БД содержатся BLOB-объекты, то подобное копирование приведёт к снижению эффективности работы, а в некоторых случаях просто недопустимо. Здесь на помощь приходит замена копий ссылками.

1.4. Механизм учёта ссылок

Для того, чтобы избежать нерационального копирования буфера данных, и SQLTypeAdapter, и String реализованы с использованием механизма учёта ссылок (reference counting) по схеме "copy-on-write". Оба класса совместно используют один и тот же внутренний механизм, поэтому взаимодействие между ними вполне возможно. Таким образом, если вы создаёте один из этих объектов, как производный от другого, то данные не копируются в полном объёме, а в новом объекте создаётся указатель на буфер данных, и счётчик ссылок на этот буфер увеличивается на единицу. Если данные в производном объекте обновляются или ещё как-либо модифицируются, то счётчик ссылок уменьшается на единицу, и создаётся собственный, независимый экземпляр буфера с изменёнными данными. Это позволяет свести к минимуму количество операций копирования данных.

2. Обработка бинарных данных

Главная особенность работы с бинарными данными при помощи библиотеки MySQL++ состоит в том, что эти данные практически не поддаются преобразованию в формат C-строки. Любой нулевой байт (а нулевые байты достаточно часто встречаются в массиве бинарных данных) интерпретируется, как символ конца строки. Поэтому обработку больших массивов бинарных данных (BLOB - Binary Large OBject) приходится производить по особой методике.

2.1. Загрузка бинарного файла в поле типа BLOB

Рассмотрим работу с бинарными данными на практическом примере. Предположим, что в дополнение к таблице хранимых товаров (с которой мы работали в предыдущей статье) в нашей базе данных имеется справочная таблица, содержащая фотографии каждого товара, определяемого уникальным идентификатором. Эта таблица с именем photos имеет следующую структуру:

id    INT NOT NULL PRIMARY KEY AUTO_INCREMENT
image MEDIUMBLOB

В поле image могут содержаться двоичные данные с максимальным размером 16 Мбайт. Для упрощения примера допустим, что требуемый файл с именем comp_table.jpeg находится в текущем рабочем каталоге программы, исходный код которой приведён ниже (сохраните его в файле с именем load_jpeg.cpp):

#include <mysql++.h>
#include <fstream>
#include <string.h>

using namespace std;
using namespace mysqlpp;

static bool is_jpeg( const unsigned char *img_data )
{
  return (img_data[0] == 0xFF) && (img_data[1] == 0xD8) &&
         ((memcmp( img_data+6, "JFIF", 4 ) == 0) ||
          (memcmp( img_data+6, "Exif", 4 ) == 0));
}

int main( int argc, char *argv[] )
{
  try
  {
    // установление соединения с тестовой базой данных
    mysqlpp::Connection con( "test_db", "localhost", "tdb_user", "tdb_password" );

    string img_name, img_data;
    img_name = "comp_table.jpeg";
    ifstream img_file( img_name.c_str(), ios::ate );
    if( img_file )
    {
      size_t img_size = img_file.tellg();
      if( img_size > 10 )
      {
        img_file.seekg( 0, ios::beg );
        unsigned char *img_buffer = new unsigned char[img_size];
        img_file.read( reinterpret_cast<char*>(img_buffer), img_size );
        if( is_jpeg( img_buffer )
          img_data.assign( reinterpret_cast<char*>(img_buffer), img_size );
        else
          cerr << "Файл " << img_file << " не содержит JPEG-изображение" << endl;
        delete[] img_buffer;
      }
      else
        cerr << "JPEG-файл должен иметь больший размер" << endl;
    }
    if( img_data.empty() )
    {
      cerr << "Указанный файл не содержит данных" << endl;
      return -1;
    }

    // Вставка данных в поле image (тип MEDIUMBLOB) таблицы photos.
    Query query = con.query();
    query << "INSERT INTO photos (image) VALUES (\"" <<
             mysqlpp::escape << img_data << "\")";
    SimpleResult res = query.execute();

    // Данные вставлены в таблицу успешно
    cout << "Файл " << img_name << " добавлен в таблицу photos. Размер в байтах: " <<
            img_data.size() << ", идентификатор " << res.insert_id() << endl;
  }
  catch( const BadQuery &erq )
  {
    cerr << "Ошибка при выполнении запроса: " << erq.what() << endl;
    return -1;
  }
  catch( const BadConversion &erc )
  {
    cerr << "Ошибка преобразования данных: " << erc.what() << endl <<
            "\tразмер обработанных данных: " << erc.retrieved << endl <<
            "\tдействительный размер данных: " << erc.actual_size << endl;
    return -1;
  }
  catch( const Exception &ex )
  {
    cerr << "Ошибка: " ex.what() << endl;
    return -1;
  }
  return 0;
}

При формировании запроса INSERT был использован манипулятор escape, так как для обрабатываемых в данном случае типов MySQL++ не выполняется автоматическое экранирование и взятие в кавычки специальных символов.

3. Работа с изображениями в BLOB-полях

В следующем примере демонстрируется извлечение бинарных данных, записанных в таблицу БД с помощью предыдущей программы load_jpeg, с использованием CGI на локальном web-сервере. Исходный код размещается в файле с именем cgi_jpeg.cpp:

#include <mysql++.h>
#include <ssqls.h>
#include <string.h>
#include <stdlib.h>

sql_create_2( photos, 1, 2,
  mysqlpp::sql_int_unsigned, id,
  mysqlpp::sql_mediumblob, image )

using namespace std;

int main()
{
  unsigned int img_id = 0;
  char *cgi_query = getenv( "QUERY_STRING" );

  if( cgi_query )
  {
    if( (strlen(cgi_query) < 4) || memcmp( cgi_query, "id=", 3 ) )
    {
      cout << "Content-type: text/plain" << endl << endl;
      cout << "ERROR: Bad query string" << endl;
      return -1;
    }
    else
      img_id = atoi( cgi_query + 3 );
  }
  else
  {
    cerr << "Поместите эту программу в каталог cgi-bin локального web-сервера." << endl;
    cerr << "Обратитесь к ней с помощью URL, например:" << endl;
    cerr << "http://localhost/cgi-bin/cgi_jpeg?id=1" << endl;
    cerr << "При этом будет извлечено изображение с идентификатором 1" << endl;
    return -1;
  }

  try
  {
    mysqlpp::Connection con( "test_db", "localhost", "root", "hardtocrack" );
    // Пароль следует заменить на реальный пароль администратора СУБД
    mysqlpp::Query query = con.query();
    query << "SELECT * FROM photos WHERE id = " << img_id;
    mysqlpp::UseQueryResult res = query.use();
    if( res )
    {
      photos pht = res.fetch_row();
      cout << "Content-type: image/jpeg" endl;
      cout << "Content-length: " << pht.data.length() << "\n\n";
      cout << pht.data;
    }
    else
    {
      cout << "Content-type: text/plain" << endl << endl;
      cout << "ERROR: No such image with ID = " << img_id << endl;
    }
  }
  catch( const mysqlpp::BadQuery &erq )
  {
    cout << "Content-type: text/plain" << endl << endl;
    cout << "QUERY ERROR: " << erq.what() << endl;
    return -1;
  }
  catch( const mysqlpp::Exception &ex )
  {
    cout << "Content-type: text/plain" << endl << endl;
    cout << "GENERAL ERROR: " << ex.what() << endl;
    return -1;
  }
  return 0;
}

Скомпилированную программу cgi_jpeg следует поместить в специальный каталог для CGI-модулей вашего локального web-сервера. Затем с помощью любого браузера вызовите её через соотвествующий URL, например, указав в поле адреса http://localhost/cgi_jpeg?id=1. После этого из таблицы photos базы данных test_db будет извлечено JPEG-изображение с идентификатором, равным 1, и выведено в вашем браузере.

4. Заключение

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

В данной статье рассматривались различные типы данных и работа с ними. Специализированным формам запросов уделяется особое внимание в следующей, шестой статье. В заключительной, седьмой статье цикла будет продемонстрировано практическое применение библиотеки MySQL++ в многопоточных приложениях.


Ресурсы для скачивания


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=514242
ArticleTitle=Практическое использование MySQL++: Часть 5. Работа с различными типами данных
publish-date=08312010