From 4ff7f0eee93680416a36d1fcd6da9715aa9fb7b6 Mon Sep 17 00:00:00 2001 From: chatlanin Date: Thu, 18 Jul 2024 09:58:33 +0300 Subject: [PATCH] initial commit --- .gitignore | 5 ++ README.md | 6 ++ meson.build | 51 +++++++++++++++ run.sh | 22 +++++++ src/meson.build | 26 ++++++++ src/pgxx/pgxx.hpp | 115 +++++++++++++++++++++++++++++++++ src/pgxx/pool_connection.hpp | 66 +++++++++++++++++++ src/pgxx/query_builder.hpp | 96 +++++++++++++++++++++++++++ src/pgxx/utils/define.hpp | 3 + src/pgxx/utils/using.hpp | 8 +++ src/pgxx/utils/var.hpp | 13 ++++ subprojects/hack.wrap | 6 ++ subprojects/nlohmann_json.wrap | 10 +++ tests/main.cpp | 47 ++++++++++++++ tests/meson.build | 8 +++ 15 files changed, 482 insertions(+) create mode 100755 .gitignore create mode 100755 README.md create mode 100755 meson.build create mode 100755 run.sh create mode 100755 src/meson.build create mode 100644 src/pgxx/pgxx.hpp create mode 100644 src/pgxx/pool_connection.hpp create mode 100644 src/pgxx/query_builder.hpp create mode 100644 src/pgxx/utils/define.hpp create mode 100644 src/pgxx/utils/using.hpp create mode 100644 src/pgxx/utils/var.hpp create mode 100755 subprojects/hack.wrap create mode 100644 subprojects/nlohmann_json.wrap create mode 100644 tests/main.cpp create mode 100644 tests/meson.build diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..9730439 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build +.cache +subprojects/* +!subprojects/hack.wrap +!subprojects/nlohmann_json.wrap diff --git a/README.md b/README.md new file mode 100755 index 0000000..e940e8c --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Билиотека для работы с PostgreSQL + +Данный продукт имеет сугубо узкое направление деятельности и предназначен для обслуживания +подключений к БД из серера + +Пример реализации расположен в папке test. diff --git a/meson.build b/meson.build new file mode 100755 index 0000000..9cfa452 --- /dev/null +++ b/meson.build @@ -0,0 +1,51 @@ +project( + meson.current_source_dir().split('/').get(-1), + 'cpp', + version : '1.0.0', + default_options : [ + 'warning_level=1', + 'optimization=3', + 'cpp_std=c++20', +]) + +add_project_arguments ( + '-Wpedantic', + '-Wno-shadow', + '-Wno-unused-but-set-variable', + '-Wno-comment', + '-Wno-unused-parameter', + '-Wno-unused-value', + '-Wno-unused-header', + '-Wno-missing-field-initializers', + '-Wno-narrowing', + '-Wno-deprecated-enum-enum-conversion', + '-Wno-volatile', + '-Wno-format-security', + '-Wno-switch', + '-Wno-ignored-attributes', + '-Wno-unused-variable', + '-Wno-deprecated-enum-enum-conversion', + language: 'cpp' +) + +############################################################# + +#args = ['-lglfw', '-ldl', '-lGL', '-lpthread', '-lX11', '-lXxf86vm', '-lXrandr', '-lXi'] +args = [] +deps = [] +inc = [] + +deps = [ + dependency('uuid'), + dependency('threads'), + dependency('libpqxx'), + subproject('hack').get_variable('hack_dep'), + subproject('nlohmann_json').get_variable('nlohmann_json_dep'), +] + +subdir('src') +subdir('tests') + +message('==================================================================================================================') +message('=== т.к. мы не включаем зависимости в проект, котрый пушм на гит, то их нужно все подключить в deps (см. выше) ===') +message('==================================================================================================================') diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..47ec7fa --- /dev/null +++ b/run.sh @@ -0,0 +1,22 @@ +#!/bin/zsh + +PROJECT_NAME=$(basename $PWD) + +run() { + command meson compile -C build + if [[ -z "$1" ]]; then + cd build + ./tests/$PROJECT_NAME + cd .. + else + meson test $1 -C build + fi +} + +if [ -d "build" ]; then + run +else + command meson setup build + run +fi + diff --git a/src/meson.build b/src/meson.build new file mode 100755 index 0000000..de75d65 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,26 @@ +inc += include_directories('.') +headers = [ + 'pgxx/pgxx.hpp', + 'pgxx/query_builder.hpp', + + 'pgxx/utils/define.hpp', + 'pgxx/utils/using.hpp', + 'pgxx/utils/var.hpp', +] + +sources = [ ] + +lib = library( + meson.project_name(), + include_directories : inc, + sources: [headers, sources], + dependencies : deps, + cpp_args: args +) + +pgxx_dep = declare_dependency( + include_directories: inc, + link_with: lib, +) + +deps += pgxx_dep diff --git a/src/pgxx/pgxx.hpp b/src/pgxx/pgxx.hpp new file mode 100644 index 0000000..9e56717 --- /dev/null +++ b/src/pgxx/pgxx.hpp @@ -0,0 +1,115 @@ +#pragma once + +#include "hack/utils/singleton.hpp" +#include "hack/exception/exception.hpp" +#include "hack/logger/logger.hpp" + +#include "pgxx/utils/define.hpp" +#include "pgxx/utils/using.hpp" +#include "pgxx/utils/var.hpp" +#include "pgxx/query_builder.hpp" +#include "pgxx/pool_connection.hpp" + +namespace pgxx +{ + class manager : public hack::utils::singleton + { + friend hack::utils::singleton; + + public: + ~manager() = default; + + private: + manager() = default; + std::map m_data_connections; + + public: + void init(std::string connection_name, int connection_count, std::string connection_url) + { + m_data_connections[connection_name] = pool_connection { connection_count, connection_url }; + hack::log("")("make connection [", connection_name, "] completed"); + } + + template + std::string prepare(const std::string func_name, const Args&... args) + { + std::string query; + + try + { + query = builder::make_query(func_name, args...); + } + catch (const std::exception& e) + { + hack::exception ex; + ex.description("database dont create query from args"); + ex.system_error(e); + ex.params("query", query); + ex.variadic_params(args...); + throw ex; + } + + return query; + } + + JSON execute(const std::string connection_name, std::string query) + { + JSON result; + + try + { + auto c = m_data_connections[connection_name].get(); + + pqxx::result r; + pqxx::work work { *c }; + r = work.exec(query); + + std::string r_str; + for (auto row : r) r_str = row.at(0).c_str(); + + work.commit(); + m_data_connections[connection_name].release(c); + + result = JSON::parse(r_str); + } + catch (const std::exception& e) + { + hack::exception ex; + ex.description("database dont execute query"); + ex.system_error(e); + ex.params("connection_name", connection_name); + ex.params("query", query); + ex.params("result", result); + + if (connection_name == var::LOG_CONNECTION) + { + hack::error()("WARNING!!! ERROR LOG TO DATABASE"); + hack::error()("query", query); + hack::error()(e.what()); + std::terminate(); + } + throw ex; + } + catch (...) + { + hack::exception ex; + ex.description(var::ALIEN_SYSTEM_ERROR); + ex.message(var::EXECUTE_ERROR); + ex.params("connection_name", connection_name); + ex.params("query", query); + ex.params("result", result); + + if (connection_name == var::LOG_CONNECTION) + { + hack::error()("WARNING!!! ERROR LOG TO DATABASE"); + hack::error()("query", query); + std::terminate(); + } + throw ex; + } + + return result; + } + }; +} + diff --git a/src/pgxx/pool_connection.hpp b/src/pgxx/pool_connection.hpp new file mode 100644 index 0000000..c8114c0 --- /dev/null +++ b/src/pgxx/pool_connection.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include +#include + +namespace pgxx +{ + class pool_connection + { + using connection = std::unique_ptr; + + public: + pool_connection() = default; + pool_connection(int connection_count, std::string connection_url) + { + for (int i = 0; i < connection_count; ++i) + { + auto c = std::make_unique(connection_url); + m_connections.push(std::move(c)); + } + } + + ~pool_connection() + { + while (!m_connections.empty()) + { + auto c = std::move(m_connections.front()); + m_connections.pop(); + c->close(); + } + } + + public: + pool_connection& operator=(pool_connection&& pc) noexcept + { + if (this != &pc) + m_connections = std::move(pc.m_connections); + return *this; + } + + public: + connection get() + { + std::unique_lock l { m_connections_mutex }; + m_connections_cond.wait(l, [this]() { return !m_connections.empty(); }); + + auto manager = std::move(m_connections.front()); + m_connections.pop(); + return manager; + } + + void release(connection& manager) + { + std::scoped_lock lock(m_connections_mutex); + m_connections.push(std::move(manager)); + m_connections_cond.notify_one(); + } + + private: + std::mutex m_connections_mutex; + std::condition_variable m_connections_cond; + std::queue m_connections; + }; +} diff --git a/src/pgxx/query_builder.hpp b/src/pgxx/query_builder.hpp new file mode 100644 index 0000000..c54d6fa --- /dev/null +++ b/src/pgxx/query_builder.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include + +#include "hack/string/string_concat_helper.hpp" +#include "hack/concepts/concepts.hpp" + +#include "utils/using.hpp" + +namespace pgxx::builder +{ + template + std::string make_one(First f) + { + f = std::regex_replace(f, std::regex("'"), "[quote]"); + return std::string("'") + f + std::string("',"); + } + + inline std::string make_one(const char* f) + { + auto f_str = std::string(f); + f_str = std::regex_replace(f_str, std::regex("'"), "[quote]"); + return std::string("'") + f_str + std::string("',"); + } + + inline std::string make_one(char f) + { + return std::string("'") + f + std::string("',"); + } + + template + requires std::integral + std::string make_one(First f) + { + auto f_str = std::to_string(f); + f_str = std::regex_replace(f_str, std::regex("'"), "[quote]"); + return std::string("'") + f_str + std::string("',"); + } + + inline std::string make_one(const float f) + { + auto f_str = std::to_string(f); + f_str = std::regex_replace(f_str, std::regex("'"), "[quote]"); + return f_str + std::string(","); + } + + inline std::string make_one(int f) + { + auto f_str = std::to_string(f); + f_str = std::regex_replace(f_str, std::regex("'"), "[quote]"); + return f_str + std::string(","); + } + + inline std::string make_one(const std::string& f) + { + auto f_str = f; + f_str = std::regex_replace(f_str, std::regex("'"), "[quote]"); + return hack::string::str_concat + "'" + f_str + "'::jsonb,"; + } + + inline std::string make_one(const JSON& f) + { + auto f_str = f.dump(); + f_str = std::regex_replace(f_str, std::regex("'"), "[quote]"); + return hack::string::str_concat + "'" + f_str + "'::jsonb,"; + } + + // это заглушкa при компиляции пустых данных + template + std::string make() { return ""; } + + template + std::string make(const First f, const Args... args) + { + auto param = make_one(f); + param += make(args...); + return param; + } + + template + std::string make_query(const std::string func, const Args... args) + { + std::string query = "SELECT s_func." + func + "("; + query += make(args...); + query.replace(query.find_last_of(','), 1, ""); + query += ");"; + return query; + } + + inline std::string make_query(const std::string func) + { + std::string query = "SELECT s_func." + func + "();"; + return query; + } +} + diff --git a/src/pgxx/utils/define.hpp b/src/pgxx/utils/define.hpp new file mode 100644 index 0000000..f293701 --- /dev/null +++ b/src/pgxx/utils/define.hpp @@ -0,0 +1,3 @@ +#pragma once + +#define PGXX() pgxx::manager::instance() diff --git a/src/pgxx/utils/using.hpp b/src/pgxx/utils/using.hpp new file mode 100644 index 0000000..8bf172e --- /dev/null +++ b/src/pgxx/utils/using.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "nlohmann/json.hpp" + +namespace pgxx +{ + using JSON = nlohmann::json; +} diff --git a/src/pgxx/utils/var.hpp b/src/pgxx/utils/var.hpp new file mode 100644 index 0000000..63a5f7b --- /dev/null +++ b/src/pgxx/utils/var.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace pgxx::var +{ + inline const std::string ALIEN_SYSTEM_ERROR = "is alien system error it can be very dangerous!!! good luck my friend!"; + inline const std::string NO_VALID_DATA = "no valid data"; + inline const std::string EXECUTE_ERROR = "execute error"; + inline const std::string HEADER_FLAG_JSON = "application/json"; + inline const std::string FUNC_LOGGER = "logger"; + inline const std::string LOG_CONNECTION = "log_db"; +} diff --git a/subprojects/hack.wrap b/subprojects/hack.wrap new file mode 100755 index 0000000..8b65299 --- /dev/null +++ b/subprojects/hack.wrap @@ -0,0 +1,6 @@ +[wrap-git] +url = https://gitcast.ru/chatlanin/hack.git +revision = master + +[provide] +hack = hack_dep diff --git a/subprojects/nlohmann_json.wrap b/subprojects/nlohmann_json.wrap new file mode 100644 index 0000000..bf5d700 --- /dev/null +++ b/subprojects/nlohmann_json.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = nlohmann_json-3.11.2 +lead_directory_missing = true +source_url = https://github.com/nlohmann/json/releases/download/v3.11.2/include.zip +source_filename = nlohmann_json-3.11.2.zip +source_hash = e5c7a9f49a16814be27e4ed0ee900ecd0092bfb7dbfca65b5a421b774dccaaed +wrapdb_version = 3.11.2-1 + +[provide] +nlohmann_json = nlohmann_json_dep diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..296fe92 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,47 @@ +#include +#include + +#include "pgxx/pgxx.hpp" + +auto main(int argc, char* args[]) -> int +{ + const std::string con = ""; + + try + { + PGXX().init("con_1", 300, con); + PGXX().init("con_2", 300, con); + } + catch(hack::exception& ex) + { + ex.log(); + throw; + } + + // pgxx::JSON j { + // { + // "params", { { "key_1", 1 }, { "key2", "value" } } + // } + // }; + // + // for (auto i = 0; i < 1'000; ++i) + // { + // std::thread th([&j](){ + // auto query = PGXX().prepare("read_and_write", j); + // auto r = PGXX().execute("con_1", query); + // }); + // th.detach(); + // } + // + // + // for (auto i = 0; i < 1'000; ++i) + // { + // std::thread th([&j](){ + // auto query = PGXX().prepare("read_and_write", j); + // auto r = PGXX().execute("con_2", query); + // }); + // th.detach(); + // } + + hack::log()("ok"); +} diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..8d55723 --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,8 @@ +executable( + meson.project_name(), + 'main.cpp', + dependencies : deps, + cpp_args: args +) + +