Хабрахабр

Знакомство с SOCI — C++ библиотекой доступа к базам данных

Я был удивлён когда Хабр в поисковике не выдал мне ни одной ссылки на статьи, в которых бы упоминалось об этой замечательной библиотеке. Сама библиотека довольно таки зрелая, — первый релиз на гитхабе датируется аж 2004 годом.

SOCI поддерживает ORM, через специализацию soci::type_conversion.

В SOCI имеются бэкенды для:

  • DB2
  • Firebird
  • MySQL
  • ODBC with specific database driver
  • Oracle
  • PostgreSQL
  • SQLite

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

Качаем сырцы из ветки master, распаковываем, и внутри директории выполняем команду:

В Windows

--config Release $ mkdir build && cd build && cmake -G"Visual Studio 15 2017 Win64”… && cmake --build.

или вместо последней команды, можно открыть получившийся проект в Visual Studio и собрать.
(о сборке при помощи cmake в командной строке подсказал Wilk)

В nix

$ mkdir build && cd build && cmake… && sudo make install

#ifndef db_pool_hpp
#define db_pool_hpp // да простят меня пользователи НЕ GCC, но я не знаю как отключить
// ворнинги для других компиляторов, о deprecated auto_ptr (если версия ниже 4)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#include <soci/soci.h>
#include <soci/connection-pool.h>
#pragma GCC diagnostic pop #include <iostream>
#include <string> class db_pool { soci::connection_pool* pool_; std::size_t pool_size_;
public: db_pool():pool_(nullptr),pool_size_(0) ~db_pool() { close(); } soci::connection_pool* get_pool() { return pool_; } bool connect(const std::string& conn_str, std::size_t n = 5) { if (pool_ != nullptr) { close(); } int is_connected = 0; if (!(pool_ = new soci::connection_pool((pool_size_ = n)))) return false; try { soci::indicator ind; for (std::size_t _i = 0; _i < pool_size_; _i++) { soci::session& sql = pool_->at(_i); // для каждой сессии открываем соединение с БД sql.open(conn_str); // и проверяем простым запросом sql << "SELECT 1;", soci::into(is_connected, ind); if (!is_connected) break; else if (_i+1 < pool_size_) is_connected = 0; } } catch (std::exception const & e) { std::cerr << e.what() << std::endl; } if (!is_connected) close(); return (pool_ != nullptr); } void close () { if (pool_ != nullptr) { try { for (std::size_t _i = 0; _i < pool_size_; _i++) { soci::session& sql = pool_->at(_i); sql.close(); } delete pool_; pool_ = nullptr; } catch (std::exception const & e) { std::cerr << e.what() << std::endl; } pool_size_ = 0; } }
}; #endif

#ifndef user_info_hpp
#define user_info_hpp #include "db_pool.hpp"
#include <ctime>
#include <vector>
#include <regex>
#include <numeric>
#include <algorithm>
#include <iomanip> // некоторые вспомогательные ф-ии для преобразования массивов в векторы и обратно
template<typename T>
static void extract_integers(const std::string& str, std::vector<T>& result ) { result.clear(); using re_iterator = std::regex_iterator<std::string::const_iterator>; using re_iterated = re_iterator::value_type; std::regex re("(\\d+)"); re_iterator rit(str.begin(), str.end(), re); re_iterator rend; std::transform(rit, rend, std::back_inserter(result), [](const re_iterated& it){return std::stoul(it[1]); });
} template<typename T>
static void split_integers(std::string& str, const std::vector<T>& arr) { str.clear(); str = "{"; if (arr.size()) { str += std::accumulate(arr.begin()+1, arr.end(), std::to_string(arr[0]), [](const std::string& a, int b){return a + ',' + std::to_string(b);}); } str += "}";
} // структура таблицы `users'
class user_info {
public: int id; // айди пользователя std::tm birthday; // день рождения std::string firstname, lastname; // имя и фамилия std::vector<int> friends; // айдишники друзей user_info():id(0),birthday(0),firstname(),lastname(),friends() {} void print() { std::cout.imbue(std::locale("ru_RU.utf8")); std::cout << "id: " << id << std::endl; std::cout << "birthday: " << std::put_time(&birthday, "%c %Z") << std::endl; std::cout << "firstname: " << firstname << std::endl; std::cout << "lastname: " << lastname << std::endl; std::string arr_str; split_integers(arr_str, friends); std::cout << "friends: " << arr_str << std::endl; } void clear() { id = 0; firstname = lastname = ""; friends.clear(); } user_info& operator=(const user_info& rhs) { if (this != &rhs) { id = rhs.id; birthday = rhs.birthday; firstname = rhs.firstname; lastname = rhs.lastname; friends = rhs.friends; } return *this; } }; // для работы со своими типами, в SOCI имеются конвертеры
namespace soci { template<> struct type_conversion<user_info> { typedef values base_type; static void from_base(values const& v, indicator ind, user_info& p) { if (ind == i_null) return; try { p.id = v.get<int>("id", 0); p.birthday = v.get<std::tm>("birthday", {}); p.firstname = v.get<std::string>("firstname", {}); p.lastname = v.get<std::string>("lastname", {}); std::string arr_str = v.get<std::string>("friends", {}); extract_integers(arr_str, p.friends); } catch (std::exception const & e) { std::cerr << e.what() << std::endl; } } static void to_base(const user_info& p, values& v, indicator& ind) { try { v.set("id", p.id); v.set("birthday", p.birthday); v.set("firstname", p.firstname); v.set("lastname", p.lastname); std::string arr_str; split_integers(arr_str, p.friends); v.set("friends", arr_str); ind = i_ok; return; } catch (std::exception const & e) { std::cerr << e.what() << std::endl; } ind = i_null; } }; } #endif

#ifndef test_cxx
#define test_cxx #include "user_info.hpp" // g++ -std=c++11 test.cxx -o test -lsoci_core -lsoci_postgresql && ./test
int main() { db_pool db; /// \note замените "postgresql" на свой бэкенд, также измените имя БД и пользователя с паролем if (db.connect("postgresql://host='localhost' dbname='test' user='test' password='test'")) { try { soci::session sql(*db.get_pool()); // создаём таблицу если не существует sql << "CREATE TABLE IF NOT EXISTS users(id serial PRIMARY KEY, birthday timestamp(6) without time zone DEFAULT now(), firstname text DEFAULT NULL, lastname text DEFAULT NULL, friends integer[] DEFAULT NULL);"; // заполняем поля user_info info; std::time_t t = std::time(nullptr); info.birthday = *std::localtime(&t); info.firstname = "Dmitrij"; info.lastname = "Volin"; info.friends = {1,2,3,4,5,6,7,8,9}; int id = 0; // id новой записи (поле id пользователя) soci::indicator ind; // делаем запись в БД sql << "INSERT INTO users(birthday, firstname, lastname, friends) VALUES(:birthday, :firstname, :lastname, :friends) RETURNING id;", soci::use(info), soci::into(id, ind); if (ind != soci::i_ok) std::cout << "не удалось записать данные в БД ..." << std::endl; t = std::time(nullptr); info.birthday = *std::localtime(&t); info.firstname = "Vasy"; info.lastname = "Pupkin"; info.friends = {11,22,33,44,55,66,77,88,99}; // делаем ещё одну запись в БД sql << "INSERT INTO users(birthday, firstname, lastname, friends) VALUES(:birthday, :firstname, :lastname, :friends) RETURNING id;", soci::use(info), soci::into(id, ind); if (ind != soci::i_ok) std::cout << "не удалось записать данные в БД ..." << std::endl; std::cout << "id нового пользователя: " << id << std::endl; // очищаем перед выборкой из БД info.clear(); // делаем выборку нашей записи в очищенную структуру sql << "SELECT * FROM users WHERE id=:userid;", soci::use(id, "userid"), soci::into(info, ind); if (ind == soci::i_null) std::cout << "не удалось выбрать данные из БД ..." << std::endl; // выводим в консоль полученные данные info.print(); std::cout << "++++++++++++++++++++++++++++++++++++++" << std::endl; // сейчас сделаем полную выборку soci::rowset<user_info> rs = (sql.prepare << "SELECT * FROM users"); for (auto it = rs.begin(); it != rs.end(); it++) { user_info & i = *it; i.print(); } // удаляем таблицу sql << "DROP TABLE IF EXISTS users;"; } catch (std::exception const & e) { std::cerr << e.what() << std::endl; } } return 0;
} #endif

В этой статье мы расмотрели малую часть возможностей библиотеки.

В следующей статье (если у читателей будет интерес), могу написать о работе с полями типа BLOB — для хранения файлов и картинок (в postgresql это поля типа OID), а также о транзакциях и prepared-запросах.

SOCI на github
SOCI домашняя страница

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»