From 4a4fabac62262c1d63e99a6effc120743d509676 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 01:49:11 +0000 Subject: [PATCH] Add Redis cache implementation - Implement RedisCache class for Redis storage backend - Add unit tests for Redis cache functionality - Update CMake configuration for Redis support Fixes #484 --- omnn/storage/RedisCache.cpp | 92 +++++++++++++++++++++++++ omnn/storage/RedisCache.h | 41 +++++++++++ omnn/storage/tests/CMakeLists.txt | 5 +- omnn/storage/tests/redis_cache_test.cpp | 33 +++++++++ 4 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 omnn/storage/RedisCache.cpp create mode 100644 omnn/storage/RedisCache.h create mode 100644 omnn/storage/tests/redis_cache_test.cpp diff --git a/omnn/storage/RedisCache.cpp b/omnn/storage/RedisCache.cpp new file mode 100644 index 0000000000..fd25dd4e62 --- /dev/null +++ b/omnn/storage/RedisCache.cpp @@ -0,0 +1,92 @@ +#include "RedisCache.h" + +#ifdef OPENMIND_STORAGE_REDIS + +#include +#include +#include + +namespace omnn::rt::storage { + +RedisCache::RedisCache(const std::string_view& host, int port, int timeout_ms) + : _context(nullptr, redisFree) + , _host(host) + , _port(port) + , _timeout_ms(timeout_ms) +{ + ensureConnection(); +} + +RedisCache::~RedisCache() = default; + +void RedisCache::ensureConnection() { + if (_context) return; + + struct timeval timeout = { 0, _timeout_ms * 1000 }; + redisContext* c = redisConnectWithTimeout(_host.c_str(), _port, timeout); + if (c == nullptr || c->err) { + if (c) { + std::string error = c->errstr; + redisFree(c); + throw std::runtime_error("Redis connection error: " + error); + } else { + throw std::runtime_error("Redis connection error: can't allocate redis context"); + } + } + _context.reset(c); +} + +std::unique_ptr RedisCache::executeCommand(const char* format, ...) { + ensureConnection(); + + va_list ap; + va_start(ap, format); + redisReply* reply = (redisReply*)redisvCommand(_context.get(), format, ap); + va_end(ap); + + if (!reply) { + throw std::runtime_error("Redis command failed"); + } + + // Cast freeReplyObject to the correct function pointer type + auto deleter = reinterpret_cast(freeReplyObject); + return std::unique_ptr(reply, deleter); +} + +std::string RedisCache::GetOne(const std::string_view& key) { + auto reply = executeCommand("GET %b", key.data(), key.size()); + + if (reply->type == REDIS_REPLY_NIL) { + return ""; + } + + if (reply->type != REDIS_REPLY_STRING) { + throw std::runtime_error("Unexpected Redis reply type for GET"); + } + + return std::string(reply->str, reply->len); +} + +bool RedisCache::Set(const std::string_view& key, const std::string_view& v) { + auto reply = executeCommand("SET %b %b", key.data(), key.size(), v.data(), v.size()); + + return reply->type == REDIS_REPLY_STATUS && + std::string_view(reply->str, reply->len) == "OK"; +} + +bool RedisCache::Clear(const std::string_view& key) { + auto reply = executeCommand("DEL %b", key.data(), key.size()); + + return reply->type == REDIS_REPLY_INTEGER && reply->integer > 0; +} + +bool RedisCache::ResetAllDB(const path_str_t& path) { + auto reply = executeCommand("FLUSHDB"); + + return reply->type == REDIS_REPLY_STATUS && + std::string_view(reply->str, reply->len) == "OK"; +} + +} // namespace omnn::rt::storage + +#endif // OPENMIND_STORAGE_REDIS diff --git a/omnn/storage/RedisCache.h b/omnn/storage/RedisCache.h new file mode 100644 index 0000000000..36533342d4 --- /dev/null +++ b/omnn/storage/RedisCache.h @@ -0,0 +1,41 @@ +#pragma once +#include "CacheBase.h" + +#ifdef OPENMIND_STORAGE_REDIS + +#include +#include +#include + +struct redisContext; // Forward declaration +struct redisReply; // Forward declaration + +namespace omnn::rt::storage { + +using path_str_t = boost::filesystem::path; + +class RedisCache : public CacheBase { + std::unique_ptr _context; + const std::string _host; + const int _port; + const int _timeout_ms; + +public: + RedisCache(const std::string_view& host = "localhost", + int port = 6379, + int timeout_ms = 1000); + ~RedisCache() override; + + std::string GetOne(const std::string_view& key) override; + bool Set(const std::string_view& key, const std::string_view& v) override; + bool Clear(const std::string_view& key) override; + bool ResetAllDB(const path_str_t& path) override; + +private: + void ensureConnection(); + std::unique_ptr executeCommand(const char* format, ...); +}; + +} // namespace omnn::rt::storage + +#endif // OPENMIND_STORAGE_REDIS diff --git a/omnn/storage/tests/CMakeLists.txt b/omnn/storage/tests/CMakeLists.txt index d08d7ffda7..abed5db9d5 100644 --- a/omnn/storage/tests/CMakeLists.txt +++ b/omnn/storage/tests/CMakeLists.txt @@ -1 +1,4 @@ -test() \ No newline at end of file +test() + +# Redis cache tests are automatically included by the test() macro +# when OPENMIND_STORAGE_REDIS is defined diff --git a/omnn/storage/tests/redis_cache_test.cpp b/omnn/storage/tests/redis_cache_test.cpp new file mode 100644 index 0000000000..306b91ed73 --- /dev/null +++ b/omnn/storage/tests/redis_cache_test.cpp @@ -0,0 +1,33 @@ +#define BOOST_TEST_MODULE redis_cache_test +#include +#include "../RedisCache.h" + +#ifdef OPENMIND_STORAGE_REDIS + +using namespace omnn::rt::storage; + +BOOST_AUTO_TEST_CASE(redis_cache_basic_operations) { + RedisCache cache("localhost", 6379); + + // Test Set and GetOne + BOOST_CHECK(cache.Set("test_key", "test_value")); + BOOST_CHECK_EQUAL(cache.GetOne("test_key"), "test_value"); + + // Test Clear + BOOST_CHECK(cache.Clear("test_key")); + BOOST_CHECK_EQUAL(cache.GetOne("test_key"), ""); + + // Test ResetAllDB + BOOST_CHECK(cache.Set("key1", "value1")); + BOOST_CHECK(cache.Set("key2", "value2")); + BOOST_CHECK(cache.ResetAllDB("")); + BOOST_CHECK_EQUAL(cache.GetOne("key1"), ""); + BOOST_CHECK_EQUAL(cache.GetOne("key2"), ""); +} + +BOOST_AUTO_TEST_CASE(redis_cache_error_handling) { + // Test connection to non-existent Redis server + BOOST_CHECK_THROW(RedisCache("nonexistent", 6379), std::runtime_error); +} + +#endif // OPENMIND_STORAGE_REDIS