Вступление
Сама библиотека довольно таки зрелая, — первый релиз на гитхабе
датируется аж 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-запросах.
Ссылка на проект
ссылка на оригинал статьи https://habr.com/post/422881/
Добавить комментарий