implemented the first version of statement_cache

This commit is contained in:
2025-07-30 10:34:33 +02:00
parent f90a670fb3
commit ebd8bccfb3
31 changed files with 575 additions and 278 deletions
+6 -3
View File
@@ -1,21 +1,22 @@
#ifndef MATADOR_BASIC_SQL_LOGGER_HPP
#define MATADOR_BASIC_SQL_LOGGER_HPP
#include <memory>
#include <string>
namespace matador::sql {
/**
* @brief Base class for sql logging
* @brief Base class for SQL logging
*
* This class acts as a base class to
* implement a concrete logger for sql
* implement a concrete logger for SQL
* statements.
*
* It provides interfaces to handle
* the establishing and closing of
* a database connection as well as
* when a sql statement is about to
* when a SQL statement is about to
* execute or going to be prepared.
*/
class abstract_sql_logger
@@ -94,6 +95,8 @@ public:
void on_prepare(const std::string &) override { }
};
const auto null_logger = std::make_shared<null_sql_logger>();
}
#endif //MATADOR_BASIC_SQL_LOGGER_HPP
+6 -4
View File
@@ -13,12 +13,10 @@
namespace matador::sql {
class connection_impl;
const auto null_logger = std::make_shared<null_sql_logger>();
/**
* @brief The connection class represents a connection to a database.
*/
class connection final : public executor {
class connection final : public executor, public prepared_executor {
public:
using logger_ptr = std::shared_ptr<abstract_sql_logger>;
/**
@@ -136,14 +134,18 @@ public:
[[nodiscard]] utils::result<std::unique_ptr<query_result_impl>, utils::error> fetch(const query_context &ctx) const override;
[[nodiscard]] utils::result<size_t, utils::error> execute(const query_context &ctx) const override;
[[nodiscard]] utils::result<statement, utils::error> prepare(const query_context &ctx) const override;
[[nodiscard]] utils::result<statement, utils::error> prepare(const query_context &ctx) override;
[[nodiscard]] std::string str( const query_context& ctx ) const override;
[[nodiscard]] const class dialect &dialect() const override;
private:
[[nodiscard]] utils::result<std::unique_ptr<statement_impl>, utils::error> perform_prepare(const query_context &ctx) const;
private:
friend class fetchable_query;
friend class session;
friend class statement_cache;
connection_info connection_info_;
std::unique_ptr<connection_impl> connection_;
+33 -145
View File
@@ -1,180 +1,73 @@
#ifndef QUERY_CONNECTION_POOL_HPP
#define QUERY_CONNECTION_POOL_HPP
#include "matador/sql/connection.hpp"
#include "matador/sql/connection_info.hpp"
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <string>
#include <optional>
#include <condition_variable>
#include <thread>
#include <unordered_map>
namespace matador::sql {
template < class Connection >
class connection_pool;
template < class Connection >
using IdConnection = std::pair<size_t, Connection>;
struct identifiable_connection {
identifiable_connection(size_t id, connection conn);
size_t id{};
connection conn;
};
template < class Connection >
class connection_ptr
{
class connection_ptr {
public:
connection_ptr(IdConnection<Connection> *c, connection_pool<Connection> *pool)
: connection_(c), pool_(pool) {}
connection_ptr(identifiable_connection *c, connection_pool *pool);
~connection_ptr();
connection_ptr(const connection_ptr &) = delete;
connection_ptr& operator=(const connection_ptr &) = delete;
connection_ptr(connection_ptr &&x) noexcept
: connection_(x.connection_)
, pool_(x.pool_)
{
x.connection_ = nullptr;
x.pool_ = nullptr;
}
connection_ptr& operator=(connection_ptr &&x) noexcept
{
if (this == &x) {
return *this;
}
connection_ptr(connection_ptr &&x) noexcept;
std::swap(connection_, x.connection_);
std::swap(pool_, x.pool_);
connection_ptr& operator=(connection_ptr &&x) noexcept;
return *this;
}
connection* operator->() const;
connection& operator*() const;
Connection* operator->() { return &connection_->second; }
Connection& operator*() { return connection_->second; }
[[nodiscard]] std::optional<size_t> id() const
{
if (connection_) {
return connection_->first;
} else {
return std::nullopt;
}
}
[[nodiscard]] bool valid() const { return connection_ != nullptr; }
[[nodiscard]] std::optional<size_t> id() const;
[[nodiscard]] bool valid() const;
private:
friend class connection_pool<Connection>;
friend class connection_pool;
IdConnection<Connection> *connection_{};
connection_pool<Connection> *pool_{};
identifiable_connection *connection_{};
connection_pool *pool_{};
};
template < class Connection >
class connection_pool
{
class connection_pool {
public:
using connection_pointer = connection_ptr<Connection>;
connection_pool(const std::string &dns, size_t count);
public:
connection_pool(const std::string &dns, size_t count)
: info_(connection_info::parse(dns)) {
connection_repo_.reserve(count);
while (count) {
connection_repo_.emplace_back(count, info_);
auto &conn = connection_repo_.back();
idle_connections_.emplace(conn.first, &conn);
// Todo: handle result
const auto result = conn.second.open();
if (!result) {
throw std::runtime_error("Failed to open connection");
}
--count;
}
}
connection_ptr acquire();
connection_ptr try_acquire();
connection_ptr acquire(size_t id);
connection_pointer acquire() {
std::unique_lock<std::mutex> lock(mutex_);
while (idle_connections_.empty()) {
cv.wait(lock);
}
void release(identifiable_connection *c);
void release(connection_ptr &c);
return get_next_connection();
}
std::size_t size() const;
std::size_t idle() const;
std::size_t inuse() const;
connection_pointer try_acquire() {
std::unique_lock<std::mutex> lock(mutex_);
if (idle_connections_.empty()) {
return {nullptr, this};
}
return get_next_connection();
}
connection_pointer acquire(size_t id) {
using namespace std::chrono_literals;
pointer next_connection{nullptr};
auto try_count{0};
std::unique_lock<std::mutex> lock(mutex_);
do {
if (auto it = idle_connections_.find(id); it != idle_connections_.end()) {
next_connection = it->second;
auto node = idle_connections_.extract(it);
inuse_connections_.insert(std::move(node));
} else {
lock.unlock();
std::this_thread::sleep_for(100ms);
lock.lock();
}
} while(try_count++ < 5);
return {next_connection, this};
}
void release(IdConnection<Connection> *c) {
if (c == nullptr) {
return;
}
std::unique_lock<std::mutex> lock(mutex_);
if (auto it = inuse_connections_.find(c->first); it != inuse_connections_.end()) {
auto node = inuse_connections_.extract(it);
idle_connections_.insert(std::move(node));
}
}
void release(connection_ptr<Connection> &c) {
release(c.connection_);
c.connection_ = nullptr;
}
std::size_t size() const { return connection_repo_.size(); }
std::size_t idle() const {
std::lock_guard<std::mutex> guard(mutex_);
return idle_connections_.size();
}
std::size_t inuse() const {
std::lock_guard<std::mutex> guard(mutex_);
return inuse_connections_.size();
}
const connection_info &info() const {
return info_;
}
const connection_info &info() const;
private:
connection_pointer get_next_connection() {
pointer next_connection{nullptr};
for (auto &item : idle_connections_) {
next_connection = item.second;
auto node = idle_connections_.extract(item.first);
inuse_connections_.insert(std::move(node));
break;
}
return {next_connection, this};
}
connection_ptr get_next_connection();
private:
mutable std::mutex mutex_;
std::condition_variable cv;
std::vector<IdConnection<Connection>> connection_repo_;
using pointer = IdConnection<Connection>*;
std::vector<identifiable_connection> connection_repo_;
using pointer = identifiable_connection*;
using connection_map = std::unordered_map<size_t, pointer>;
connection_map inuse_connections_;
connection_map idle_connections_;
@@ -182,11 +75,6 @@ private:
const connection_info info_;
};
template<class Connection>
connection_ptr<Connection>::~connection_ptr() {
pool_->release(connection_);
}
}
#endif //QUERY_CONNECTION_POOL_HPP
+12 -4
View File
@@ -12,14 +12,22 @@ struct query_context;
class query_result_impl;
class statement;
class executor {
class dialect_executor_mixin {
public:
virtual ~executor();
virtual ~dialect_executor_mixin() = default;
[[nodiscard]] virtual const class dialect& dialect() const = 0;
};
class prepared_executor : public dialect_executor_mixin {
public:
[[nodiscard]] virtual utils::result<statement, utils::error> prepare(const query_context &ctx) = 0;
};
class executor : public dialect_executor_mixin {
public:
[[nodiscard]] virtual utils::result<size_t, utils::error> execute(const query_context &ctx) const = 0;
[[nodiscard]] virtual utils::result<std::unique_ptr<query_result_impl>, utils::error> fetch(const query_context &ctx) const = 0;
[[nodiscard]] virtual utils::result<statement, utils::error> prepare(const query_context &ctx) const = 0;
[[nodiscard]] virtual std::string str(const query_context &ctx) const = 0;
};
@@ -0,0 +1,37 @@
#ifndef STATEMENT_PROXY_HPP
#define STATEMENT_PROXY_HPP
#include "matador/sql/interface/statement_impl.hpp"
namespace matador::sql {
class statement_proxy {
protected:
explicit statement_proxy(std::unique_ptr<statement_impl>&& stmt);
public:
virtual ~statement_proxy() = default;
virtual utils::result<size_t, utils::error> execute() = 0;
virtual utils::result<std::unique_ptr<query_result_impl>, utils::error> fetch() = 0;
template<class Type>
void bind(const Type &obj) {
statement_->bind_object(obj);
}
template<typename Type>
void bind(size_t pos, Type &value) {
statement_->bind(pos, value);
}
void bind(size_t pos, const char *value, size_t size) const;
void bind(size_t pos, std::string &val, size_t size) const;
void reset() const;
protected:
std::unique_ptr<statement_impl> statement_;
};
}
#endif //STATEMENT_PROXY_HPP
+27 -22
View File
@@ -4,7 +4,7 @@
#include "matador/sql/abstract_sql_logger.hpp"
#include "matador/sql/query_result.hpp"
#include "matador/sql/interface/statement_impl.hpp"
#include "matador/sql/interface/statement_proxy.hpp"
#include "matador/utils/error.hpp"
#include "matador/utils/result.hpp"
@@ -18,26 +18,28 @@ class identifier_binder;
}
class statement_impl;
class statement_proxy;
class statement {
public:
using logger_ptr = std::shared_ptr<abstract_sql_logger>;
/**
* Creates a statement initialized from the
* given statement implementation object holding
* the implementation for the selected database
*
* @param impl The statement implementation object
* @param logger The logger handler to write sql log messages to
* @param proxy The statement proxy implementation object
* @param logger The logger handler to write SQL log messages to
*/
explicit statement(std::unique_ptr<statement_impl> impl,
const std::shared_ptr<abstract_sql_logger> &logger = std::make_shared<null_sql_logger>());
explicit statement(const std::shared_ptr<statement_proxy>& proxy, logger_ptr logger = null_logger);
/**
* Copy move constructor for statement
*
* @param x The statement to move from
*/
statement(statement &&x) noexcept
: statement_(std::move(x.statement_))
: statement_proxy_(std::move(x.statement_proxy_))
, logger_(std::move(x.logger_)) {
}
@@ -48,11 +50,14 @@ public:
* @return Reference to this
*/
statement &operator=(statement &&x) noexcept {
statement_ = std::move(x.statement_);
statement_proxy_ = std::move(x.statement_proxy_);
logger_ = std::move(x.logger_);
return *this;
}
statement(const statement &x) = default;
statement &operator=(const statement &x) = default;
statement &bind(size_t pos, const char *value);
statement &bind(size_t pos, std::string &val, size_t size);
/**
@@ -77,8 +82,8 @@ public:
/**
* Fetches the result of the prepared
* statement. If prepared statement was not
* a SELECT statement an empty query result set
* statement. If a prepared statement was not
* a SELECT statement, an empty query result set
* is returned.
*
* @tparam Type Type of the fetched result
@@ -88,10 +93,10 @@ public:
utils::result<query_result<Type>, utils::error> fetch();
/**
* Fetches the result of the prepared
* statement. The type is record representing an
* statement. The type is the record representing an
* unknown variable type.
* If prepared statement was not
* a SELECT statement an empty query result set
* If the prepared statement was not
* a SELECT statement, an empty query result set
* is returned.
*
* @return The query result set
@@ -99,8 +104,8 @@ public:
[[nodiscard]] utils::result<query_result<record>, utils::error> fetch() const;
/**
* Fetches the first result of a prepared statement.
* If prepared statement is empty or not
* a SELECT statement a nullptr is returned.
* If a prepared statement is empty or not
* a SELECT statement, a nullptr is returned.
*
* @tparam Type Type of the fetched result
* @return The query result set
@@ -109,9 +114,9 @@ public:
utils::result<std::unique_ptr<Type>, utils::error> fetch_one();
/**
* Fetches the first result of a prepared statement.
* The type is record representing an unknown variable type.
* If prepared statement is empty or not
* a SELECT statement a nullptr is returned.
* The type is a record representing an unknown variable type.
* If a prepared statement is empty or not
* a SELECT statement, a nullptr is returned.
*
* @return The query result set
*/
@@ -128,25 +133,25 @@ private:
friend class detail::identifier_binder;
private:
std::unique_ptr<statement_impl> statement_;
std::shared_ptr<statement_proxy> statement_proxy_;
std::shared_ptr<abstract_sql_logger> logger_;
};
template<typename Type>
statement &statement::bind(size_t pos, Type &value) {
statement_->bind(pos, value);
statement_proxy_->bind(pos, value);
return *this;
}
template<class Type>
statement &statement::bind(const Type &obj) {
statement_->bind_object(obj);
statement_proxy_->bind(obj);
return *this;
}
template<class Type>
utils::result<query_result<Type>, utils::error> statement::fetch() {
return statement_->fetch().and_then([](std::unique_ptr<query_result_impl> &&value) {
return statement_proxy_->fetch().and_then([](std::unique_ptr<query_result_impl> &&value) {
return utils::ok(query_result<Type>(std::forward<decltype(value)>(value)));
});
// if (!result.is_ok()) {
@@ -157,7 +162,7 @@ utils::result<query_result<Type>, utils::error> statement::fetch() {
template<class Type>
utils::result<std::unique_ptr<Type>, utils::error> statement::fetch_one() {
auto result = statement_->fetch();
auto result = statement_proxy_->fetch();
if (!result.is_ok()) {
return utils::failure(result.err());
}
+38
View File
@@ -0,0 +1,38 @@
#ifndef STATEMENT_CACHE_HPP
#define STATEMENT_CACHE_HPP
#include "matador/sql/executor.hpp"
#include "matador/sql/interface/statement_proxy.hpp"
#include <list>
#include <mutex>
#include <unordered_map>
namespace matador::sql {
class connection_pool;
class statement_cache final {
public:
explicit statement_cache(connection_pool &pool, size_t max_size = 50);
[[nodiscard]] utils::result<statement, utils::error> acquire(const query_context &ctx);
[[nodiscard]] size_t size() const;
[[nodiscard]] size_t capacity() const;
[[nodiscard]] bool empty() const;
private:
using list_iterator = std::list<size_t>::iterator;
size_t max_size_{};
std::list<size_t> usage_list_; // LRU: front = most recent, back = least recent
std::unordered_map<size_t, std::pair<statement, list_iterator>> cache_map_;
std::mutex mutex_;
connection_pool &pool_;
const sql::dialect &dialect_;
};
}
#endif //STATEMENT_CACHE_HPP