#include "matador/sql/statement_cache.hpp" #include "matador/sql/backend_provider.hpp" #include "matador/sql/error_code.hpp" #include "matador/sql/connection_pool.hpp" #include namespace matador::sql { namespace internal { class statement_cache_proxy final : public statement_proxy { public: explicit statement_cache_proxy(std::unique_ptr&& stmt) : statement_proxy(std::move(stmt)) {} utils::result execute(interface::parameter_binder& bindings) override { if (!try_lock()) { return utils::failure(utils::error{ error_code::STATEMENT_LOCKED, "Failed to execute statement because it is already in use" }); } auto guard = statement_guard(*this); return statement_->execute(bindings); } utils::result, utils::error> fetch(interface::parameter_binder& bindings) override { if (!try_lock()) { return utils::failure(utils::error{ error_code::STATEMENT_LOCKED, "Failed to execute statement because it is already in use" }); } auto guard = statement_guard(*this); return statement_->fetch(bindings); } protected: [[nodiscard]] bool try_lock() { bool expected = false; return locked_.compare_exchange_strong(expected, true); } void unlock() { locked_.store(false); } private: struct statement_guard { explicit statement_guard(statement_cache_proxy &statement_proxy) : proxy(statement_proxy) {} ~statement_guard() { proxy.unlock(); } statement_cache_proxy &proxy; }; private: std::atomic_bool locked_{false}; }; } statement_cache::statement_cache(connection_pool &pool, const size_t max_size) : max_size_(max_size) , pool_(pool) , dialect_(backend_provider::instance().connection_dialect(pool_.info().type)) {} utils::result statement_cache::acquire(const query_context& ctx) { std::unique_lock lock(mutex_); // hash statement const auto key = std::hash{}(ctx.sql); // Found in cache. Move it to of the LRU list if (const auto it = cache_map_.find(key); it != cache_map_.end()) { usage_list_.splice(usage_list_.begin(), usage_list_, it->second.second); return utils::ok(it->second.first); } // Prepare a new statement // acquire pool connection const auto conn = pool_.acquire(); auto result = conn->perform_prepare(ctx); if (!result) { return utils::failure(utils::error{error_code::PREPARE_FAILED, std::string("Failed to prepare")}); } // If cache max size reached ensure space if (cache_map_.size() >= max_size_) { const auto& key_to_remove = usage_list_.back(); cache_map_.erase(key_to_remove); usage_list_.pop_back(); } usage_list_.push_front(key); const auto it = cache_map_.insert({ key, std::make_pair(statement{ std::make_shared(result.release())}, usage_list_.begin()) }).first; return utils::ok(it->second.first); } size_t statement_cache::size() const { return cache_map_.size(); } size_t statement_cache::capacity() const { return max_size_; } bool statement_cache::empty() const { return cache_map_.empty(); } }