initial commit
This commit is contained in:
6
.gitignore
vendored
Executable file
6
.gitignore
vendored
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
build
|
||||||
|
.tmp
|
||||||
|
.cache
|
||||||
|
subprojects/*
|
||||||
|
!subprojects/hack.wrap
|
||||||
|
!subprojects/nlohmann_json.wrap
|
||||||
6
README.md
Executable file
6
README.md
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
# Билиотека для работы с PostgreSQL
|
||||||
|
|
||||||
|
Данный продукт имеет сугубо узкое направление деятельности и предназначен для обслуживания
|
||||||
|
подключений к БД из серера
|
||||||
|
|
||||||
|
в разработке...
|
||||||
15
bin/main.cpp
Normal file
15
bin/main.cpp
Normal 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
7
bin/meson.build
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
executable(
|
||||||
|
meson.project_name(),
|
||||||
|
'main.cpp',
|
||||||
|
dependencies : deps,
|
||||||
|
include_directories : inc,
|
||||||
|
cpp_args: args
|
||||||
|
)
|
||||||
50
meson.build
Executable file
50
meson.build
Executable 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
22
run.sh
Executable 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
25
src/meson.build
Executable 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
123
src/pgxx/pgxx.hpp
Normal 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
110
src/pgxx/pool.hpp
Normal 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
6
subprojects/hack.wrap
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
[wrap-git]
|
||||||
|
url = https://gitcast.ru/chatlanin/hack.git
|
||||||
|
revision = master
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
hack = hack_dep
|
||||||
10
subprojects/nlohmann_json.wrap
Normal file
10
subprojects/nlohmann_json.wrap
Normal 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
|
||||||
Reference in New Issue
Block a user