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

от автора

Вступление

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

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

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

  • Firebird
  • MySQL
  • Oracle
  • PostgreSQL
  • SQLite

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

Установка

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

В Windows

$ mkdir build && cd build && cmake -G»Visual Studio 15 2017 Win64” ..

открываем получившийся проект в Visual Studio и собираем.

В nix

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

Пишем пул для соединений с базой данных (БД)

#ifndef db_pool_hpp #define db_pool_hpp  // да простят меня пользователи НЕ GCC, но я не знаю как отключить // ворнинги для других компиляторов, о deprecated auto_ptr #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> /// \note замените "postgresql" на "mysql" или "sqlite3" - для вашего бэкенда #include <soci/postgresql/soci-postgresql.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(); }     bool ret = false;     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

Определяем структуру таблицы в классе user_info

#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; tm = {0}; firstname = lastname = ""; friends.clear(); }  };  // для работы со своими типами, в 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);         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);        std::cout << "id нового пользователя: " << id << std::endl;        // очищаем перед выборкой из БД       info.clear();        // делаем выборку нашей записи в очищенную структуру       sql << "SELECT * FROM users WHERE id=:userid;", soci::use(id, "userid"), soci::into(info, ind);        // выводим в консоль полученные данные       info.print();        // удаляем таблицу       sql << "DROP TABLE IF EXISTS users;";      } catch (std::exception const & e) { std::cerr << e.what() << std::endl; }   }    return 0; }  #endif

Заключение

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

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

Ссылка на проект

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


ссылка на оригинал статьи https://habr.com/post/422881/


Комментарии

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

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