initial commit

This commit is contained in:
2026-05-31 10:47:37 +03:00
commit 306571c4ef
11 changed files with 380 additions and 0 deletions

6
.gitignore vendored Executable file
View File

@@ -0,0 +1,6 @@
build
.tmp
.cache
subprojects/*
!subprojects/hack.wrap
!subprojects/nlohmann_json.wrap

6
README.md Executable file
View File

@@ -0,0 +1,6 @@
# Билиотека для работы с PostgreSQL
Данный продукт имеет сугубо узкое направление деятельности и предназначен для обслуживания
подключений к БД из серера
в разработке...

15
bin/main.cpp Normal file
View File

@@ -0,0 +1,15 @@
#include <string>
#include "pgxx/pgxx.hpp"
auto main(int argc, char* args[]) -> int
{
const std::string password = "zHpQW0JaULlR1PugM2SRGrszBdRYGLL4SdIxg3trl3rSeaLJLAlDnCNs33RES+CX";
const std::string main_conn = "host=localhost port=5432 dbname=main_db user=chatlanin password=" + password;
const std::string replica_conn = "host=localhost port=5433 dbname=main_db user=chatlanin password=" + password;
auto& db = pgxx::database::instance();
db.init("main", main_conn, 20);
db.init("replica", replica_conn, 10);
return 0;
}

7
bin/meson.build Normal file
View File

@@ -0,0 +1,7 @@
executable(
meson.project_name(),
'main.cpp',
dependencies : deps,
include_directories : inc,
cpp_args: args
)

50
meson.build Executable file
View File

@@ -0,0 +1,50 @@
project(
meson.current_source_dir().split('/').get(-1),
'cpp',
version : run_command('git', 'rev-parse', '--short', 'HEAD', check: false).stdout().strip(),
default_options : [
'warning_level=1',
'optimization=3',
'cpp_std=c++20'
])
cc = meson.get_compiler('cpp')
if cc.get_id() == 'gcc'
add_project_arguments(
'-Wpedantic',
'-Wno-shadow',
'-Wno-maybe-uninitialized',
'-Wno-volatile',
'-Wno-unused-header',
language: 'cpp'
)
endif
# Общие флаги для GCC и Clang
add_project_arguments(
'-Wno-unused-but-set-variable',
'-Wno-comment',
'-Wno-unused-parameter',
'-Wno-unused-value',
'-Wno-missing-field-initializers',
'-Wno-narrowing',
'-Wno-deprecated-enum-enum-conversion',
'-Wno-format-security',
'-Wno-switch',
'-Wno-ignored-attributes',
'-Wno-unused-variable',
'-Wno-unused-function',
'-Wno-unknown-pragmas',
language: 'cpp'
)
args = []
inc = []
deps = [
dependency('libpqxx'),
subproject('hack').get_variable('hack_dep'),
subproject('nlohmann_json').get_variable('nlohmann_json_dep'),
]
subdir('src')
subdir('bin')

22
run.sh Executable file
View File

@@ -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

25
src/meson.build Executable file
View File

@@ -0,0 +1,25 @@
inc += include_directories('.')
sources = [ ]
# Только если есть реальные .cpp файлы
if sources.length() > 0
lib = library(
meson.project_name(),
include_directories : inc,
sources: sources,
dependencies : deps,
cpp_args: args
)
deps += declare_dependency(
include_directories: inc,
link_with: lib,
)
else
# Header-only библиотека
deps += declare_dependency(
include_directories: inc,
dependencies: deps
)
endif

123
src/pgxx/pgxx.hpp Normal file
View File

@@ -0,0 +1,123 @@
#pragma once
#include <hack/patterns/singleton.hpp>
#include <hack/exception/exception.hpp>
#include <hack/logger/logger.hpp>
#include "pool.hpp"
namespace pgxx
{
class database : public hack::patterns::singleton<database>
{
friend hack::patterns::singleton<database>;
public:
~database() = default;
private:
database() = default;
std::map<std::string, pool> m_connections;
public:
bool ready() { return !m_connections.empty(); }
void init(const std::string& conn_name, const std::string& conn_str, std::size_t conn_count = 10, std::chrono::seconds timeout = std::chrono::seconds(30))
{
try
{
pool p;
p.init(conn_str, conn_count, timeout);
m_connections.emplace(conn_name, std::move(p));
hack::log()("make connection [", conn_name, "] completed");
}
catch(hack::exception& ex)
{
hack::error()("1");
}
catch(const std::exception& ex)
{
hack::error()("2");
}
catch(...)
{
hack::error()("3");
}
}
// Для запросов без параметров
pqxx::result execute(const std::string& connection_name, const std::string& query)
{
auto it = m_connections.find(connection_name);
if (it == m_connections.end())
{
hack::exception ex;
// ex.description("Connection pool not found");
// ex.params("connection_name", connection_name);
throw ex;
}
pool::guard conn(it->second);
pqxx::work work(*conn);
auto result = work.exec(query);
work.commit();
return result;
}
// Для запросов с параметрами (БЕЗОПАСНО!)
template<typename... Args>
pqxx::result execute(const std::string& connection_name, const std::string& query, Args&&... params)
{
auto it = m_connections.find(connection_name);
if (it == m_connections.end())
{
hack::exception ex;
ex.description("Connection pool not found");
// ex.params("connection_name", connection_name);
throw ex;
}
pool::guard conn(it->second);
pqxx::work work(*conn);
auto result = work.exec_params(query, std::forward<Args>(params)...);
work.commit();
return result;
}
// для транзакций (несколько запросов)
template<typename Func>
auto transaction(const std::string& connection_name, Func&& func)
{
auto it = m_connections.find(connection_name);
if (it == m_connections.end())
{
hack::exception ex;
ex.description("Connection pool not found");
// ex.params("connection_name", connection_name);
throw ex;
}
pool::guard conn(it->second);
pqxx::work work(*conn);
auto result = func(work);
work.commit();
return result;
}
/*
Как использовать с транзакциями.
Но по факту просто красивая обертка можно просто все и без метода transaction
db.transaction("main", [&](pqxx::work& txn) {
auto res = txn.exec_params( "SELECT balance FROM accounts WHERE id = $1 FOR UPDATE", from_id);
double balance = res[0]["balance"].as<double>();
if (balance < amount) throw std::runtime_error("Insufficient funds");
txn.exec_params( "UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, from_id);
txn.exec_params( "UPDATE accounts SET balance = balance + $1 WHERE id = $2", amount, to_id);
txn.exec_params( "INSERT INTO transfers (from_id, to_id, amount) VALUES ($1, $2, $3)", from_id, to_id, amount);
});
*/
};
}

110
src/pgxx/pool.hpp Normal file
View File

@@ -0,0 +1,110 @@
#pragma once
#include <chrono>
#include <mutex>
#include <string>
#include <queue>
#include <condition_variable>
#include <pqxx/pqxx>
#include <hack/patterns/singleton.hpp>
namespace pgxx
{
class pool
{
public:
pool() = default;
pool(pool&& other) noexcept : m_conn_str(std::move(other.m_conn_str)) , m_timeout(other.m_timeout) , m_pool(std::move(other.m_pool)) {}
pool(const pool&) = delete;
pool& operator=(const pool&) = delete;
pool& operator=(pool&&) = delete;
public:
void init(const std::string& conn_str, std::size_t pool_size = 10, std::chrono::seconds timeout = std::chrono::seconds(30))
{
m_conn_str = conn_str;
m_timeout = timeout;
for (size_t i = 0; i < pool_size; i++)
m_pool.push(create_connection());
}
class guard
{
public:
guard(pool& pool) : m_pool(pool), m_conn(pool.acquire())
{
ensure_valid();
}
~guard()
{
if (m_conn) m_pool.release(std::move(m_conn));
}
pqxx::connection* operator->() { return m_conn.get(); }
pqxx::connection& operator*() { return *m_conn.get(); }
private:
void ensure_valid()
{
if (!m_conn->is_open())
{
m_conn = m_pool.recreate_connection();
if (!m_conn || !m_conn->is_open())
throw std::runtime_error("Failed to recreate database connection");
}
}
private:
pool& m_pool;
std::unique_ptr<pqxx::connection> m_conn;
};
private:
std::unique_ptr<pqxx::connection> acquire()
{
std::unique_lock lock(m_mutex);
if (!m_cond.wait_for(lock, m_timeout, [this] { return !m_pool.empty(); }))
throw std::runtime_error("Connection pool timeout");
auto conn = std::move(m_pool.front());
m_pool.pop();
return conn;
}
void release(std::unique_ptr<pqxx::connection> conn)
{
try
{
if (!conn->is_open()) conn = create_connection();
std::lock_guard lock(m_mutex);
m_pool.push(std::move(conn));
m_cond.notify_one();
}
catch (...)
{
// Подавляем исключения, чтобы не потерять соединение
std::lock_guard lock(m_mutex);
m_pool.push(create_connection());
m_cond.notify_one();
}
}
std::unique_ptr<pqxx::connection> create_connection()
{
return std::make_unique<pqxx::connection>(m_conn_str);
}
std::unique_ptr<pqxx::connection> recreate_connection()
{
return create_connection();
}
private:
std::string m_conn_str;
std::chrono::seconds m_timeout;
std::queue<std::unique_ptr<pqxx::connection>> m_pool;
std::mutex m_mutex;
std::condition_variable m_cond;
};
}

6
subprojects/hack.wrap Executable file
View File

@@ -0,0 +1,6 @@
[wrap-git]
url = https://gitcast.ru/chatlanin/hack.git
revision = master
[provide]
hack = hack_dep

View File

@@ -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