четверг, 23 апреля 2009 г.

Работа с PostgreSQL. Использование libpqxx

Существует, как минимум, два официальных клиентских C++ API для работы с PostgreSQL:
libpq++ [1] и libpqxx [2]. Обе библиотеки распространяются свободно, с открытым
исходным кодом и хорошо документированы.
Для себя я выбрал libpqxx: являясь очень гибкой, она остается так же достаточно простой в использовании.

Хотя, наверное, это и не самая простая библиотека, но однообразный интерфейс к данным
и привычные для C++ разработчика итераторы вместо "get_next_row" для доступа к результатам
запроса, делают ее использование интуитивно понятным. Так же поддерживаются потоки,
где это имеет смысл, а поля могут быть считаны в переменные шаблонными методами.
В общем, все, как в нормальных C++ библиотеках.

Вот несколько причин, по которым можно выбрать именно libpqxx:

  1. Согласованность с языком. В разумной мере используются фичи современного
    C++ такие, как шаблоны (templates), исключения (exceptions), различного типы итераторов.
    В библиотеке, по возможности, используются стандартные вещи из STL.
  2. Структура, позволяющая писать "хороший" код. Многие проблемы обнаруживаются
    на этапе компиляции или тестирования, а не во время работы.
  3. Мощность. Такие встроенные фичи, как автоматическое восстановление соединения и
    управление транзакциями освобождают разработчика от низкоуровневой рутинной работы.
  4. Гибкость. С учетом описанного выше, можно сделать вывод, что это не фреймворк.
    Ничто не заставляет втискивать свой код в чужие обработчики. В ней нет доморощенных
    строк и классов исключений. Код библиотеки живет в своем пространстве имен (pqxx)
    и полностью прячет нативный C API.
Остальные достопримечательности библиотеки можно почитать на официальном сайте [2].

Итак, что же необходимо для работы?

Во-первых, необходима нативная libpq.so/libpq.dll.
Во-вторых, сама libpqxx.so/libpqxx.dll.

Что касается подхода к работе с базой данных, тут стоит обратить внимание, что
доступ к базе данных выполняются через объект транзакции pqxx::transaction.
Поэтому, знание интерфейсов этих классов является фундаментальным [3].

Для работы с libpqxx™ нам необходимо, как минимум следующие классы:
  • pqxx::connection – представляет собой обвертку к соединению в программе к back-end'у PostgreSQL™.
    Можно открывать множество соединений ко многим базам данных.
  • pqxx::work – тип для шаблона pqxx::transaction, представляющий собой саму транзакцию,
    которая выполняется в контексте подключения (pqxx::connection). Если произойдет
    какая-нибудь проблема в рамках этой транзакции – выполнится полный ее откат к исходному
    состоянию.
  • pqxx::result – контейнер, содержащий результат работы запроса или команды выполненных
    в транзакции.
В качестве примера, приведу программу, которая выполняет запрос и выводит результат
на экран в виде field='value'.

#include <pqxx/connection>
#include <pqxx/transaction>
#include <iostream>
#include <string>
#include <sstream>

void help()
{
std::cout << "Usage: <host> <user> <password> <database>" << std::endl;
}

int main(int argc, char **argv)
{
// Check arguments
if (argc < 5) {
help();
return 1;
}

// Prepare connection string
std::ostringstream conn_string("");
conn_string << "host=" << argv[1]
<< " user=" << argv[2]
<< " password=" << argv[3]
<< " dbname=" << argv[4];

do {
// Create connection
try {
pqxx::connection conn(conn_string.str());
pqxx::work xact(conn, "SampleSelect");

std::string query("SELECT * from news limit 10");
// Execute query
try {
pqxx::result res = xact.exec(query);
if (!res.size()) {
std::cout << "Empty result set." << std::endl;
break;
}
// Show results
for (pqxx::result::const_iterator i = res.begin(), r_end = res.end(); i != r_end; ++i) {
// Iterate fields
for (pqxx::result::const_fielditerator f = i->begin(), f_end = i->end(); f != f_end; ++f) {
std::cout << f->name() << " = '" << f->c_str() << "'" << std::endl;
}
std::cout << std::endl;
}
} catch (...) {
std::cout << "Failed to execute query: " << query << std::endl;
break;
}
} catch (pqxx::broken_connection) {
std::cout << "Failed to establish connection." << std::endl;
break;
}
std::cout << "Query successfully executed." << std::endl;

return 0;
} while (false);

return 1;
}


Makefile выглядит следующим образом:

SRC=pqxx_sample.cpp
PROG=pqxx_sample

all:
c++ ${SRC} -o ${PROG} `pkg-config --libs --cflags libpqxx`
clean:
rm -f *.o ${PROG}

Как видно из примера, строкой подключения является стандартное перечисление ключ-значение,
которое можно собрать, например, в std::ostringstream [4].

Далее, подключение заворачивается в try-catch(pqxx::broken_connection).
После выполнения запроса выполняется двойной цикл: первый по записям, второй по полям.

Конструкция do {} while (false) здесь используется для уменьшения цикломатической сложности.

На этом возможности библиотеки libpqxx, естественно, не заканчиваются. О них можно
почитать в справочнике [3].

  1. libpq++ – http://www.postgresql.org/docs/7.2/static/libpqplusplus.html
  2. libpqxx – http://pqxx.org/development/libpqxx/
  3. Справочная информация – http://pqxx.org/devprojects/libpqxx/doc/3.0/html/Reference/
  4. std::ostringstream – http://www.cplusplus.com/reference/iostream/ostringstream/

5 комментариев:

T-Rex комментирует...

DatabaseLayer как-то привычнее :)
Но да, wx-only.

Me комментирует...

Библиотека интересная, но вот документации - кот наплакал, тем более на русском языке.
Я только начал разбираться с этой библиотекой, и Doxygen - как-то не особо помогает, а идущий в комплекте "мануал" - слишком маленький, и "водянистый".
Интересут вот такой момент - параметризованные запросы в pqxx - как писать, и как с ними работать?

Me комментирует...

В догонку к предыдущему комментарию:
http://pqxx.org/devprojects/libpqxx/doc/3.0/html/Reference/a00020.html#9f9eaad80226ad78f8edd33d8a116e6a
using namespace pqxx;
void foo(connection_base &C)
{
C.prepare("findtable",
"select * from pg_tables where name=$1")
("varchar", treat_string);
work W(C);
result R = W.prepared("findtable")("mytable").exec();
if (R.empty()) throw runtime_error("mytable not found!");
}
Несколько необычно.

Андрей Булавинов комментирует...

Для вставки можно использовать
void pqxx::tablewriter::insert(const TUPLE &)

Анонимный комментирует...

Вот еще очень симпатичная либа
http://soci.sourceforge.net/