Ferenc Szontágh
2024-06-27 097a09efd9556b22917990ed37fbff3265b24260
initial
1 files modified
7 files added
513 ■■■■■ changed files
.gitignore 3 ●●●●● patch | view | raw | blame | history
.gitmodules 6 ●●●●● patch | view | raw | blame | history
CMakeLists.txt 44 ●●●●● patch | view | raw | blame | history
RocksDBWrapper.cpp 73 ●●●●● patch | view | raw | blame | history
RocksDBWrapper.h 225 ●●●●● patch | view | raw | blame | history
external/tser @ 9f4423 1 ●●●● patch | view | raw | blame | history
main.cpp 47 ●●●●● patch | view | raw | blame | history
test_RocksDBWrapper.cpp 114 ●●●●● patch | view | raw | blame | history
.gitignore
@@ -19,3 +19,6 @@
*.exe
*.out
*.app
build
.vscode
.gitmodules
New file
@@ -0,0 +1,6 @@
[submodule "external/tser"]
    path = external/tser
    url = https://github.com/KonanM/tser.git
[submodule "external/wildcards"]
    path = external/wildcards
    url = https://github.com/zemasoft/wildcards.git
CMakeLists.txt
New file
@@ -0,0 +1,44 @@
cmake_minimum_required(VERSION 3.10)
project(RocksDBWrapper)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
include_directories(${CMAKE_SOURCE_DIR}/external/tser/include)
# Add the source files
set(SOURCES
    RocksDBWrapper.cpp
)
# Find RocksDB
find_package(RocksDB REQUIRED)
# Create the library target
add_library(RocksDBWrapper STATIC ${SOURCES})
# Link the RocksDB library
target_link_libraries(RocksDBWrapper RocksDB::rocksdb)
# Add the executable for testing
add_executable(RocksDBWrapperExecutable main.cpp)
target_link_libraries(RocksDBWrapperExecutable RocksDBWrapper)
# Optionally add tests
option(BUILD_TESTS "Build tests" ON)
if (BUILD_TESTS)
    include(FetchContent)
    FetchContent_Declare(
        googletest
        URL https://github.com/google/googletest/archive/refs/tags/release-1.11.0.zip
    )
    FetchContent_MakeAvailable(googletest)
    enable_testing()
    add_executable(RocksDBWrapperTest test_RocksDBWrapper.cpp)
    target_link_libraries(RocksDBWrapperTest RocksDBWrapper gtest gtest_main)
    add_test(NAME RocksDBWrapperTest COMMAND RocksDBWrapperTest)
endif()
RocksDBWrapper.cpp
New file
@@ -0,0 +1,73 @@
#include "RocksDBWrapper.h"
RocksDBWrapper::RocksDBWrapper(const std::string &db_path, const std::string &index_path)
{
    rocksdb::Options options;
    options.create_if_missing = true;
    // Open the main RocksDB database
    rocksdb::Status status = rocksdb::DB::Open(options, db_path, &db_);
    if (!status.ok())
    {
        std::cerr << "Error opening RocksDB database: " << status.ToString() << std::endl;
    }
    // Open the index RocksDB database
    status = rocksdb::DB::Open(options, index_path, &index_db_);
    if (!status.ok())
    {
        std::cerr << "Error opening RocksDB index database: " << status.ToString() << std::endl;
    }
}
RocksDBWrapper::~RocksDBWrapper()
{
    delete db_;
    delete index_db_;
}
bool RocksDBWrapper::keyExists(const std::string &key)
{
    std::string value;
    rocksdb::Status status = db_->Get(rocksdb::ReadOptions(), serializeKey(key), &value);
    return status.ok();
}
bool RocksDBWrapper::StringMatch(const std::string &text, const std::string &pattern)
{
    int n = text.length();
    int m = pattern.length();
    int i = 0, j = 0, startIndex = -1, match = 0;
    while (i < n)
    {
        if (j < m && (pattern[j] == '?' || pattern[j] == text[i]))
        {
            i++;
            j++;
        }
        else if (j < m && pattern[j] == '*')
        {
            startIndex = j;
            match = i;
            j++;
        }
        else if (startIndex != -1)
        {
            j = startIndex + 1;
            match++;
            i = match;
        }
        else
        {
            return false;
        }
    }
    while (j < m && pattern[j] == '*')
    {
        j++;
    }
    return j == m;
}
RocksDBWrapper.h
New file
@@ -0,0 +1,225 @@
#include <rocksdb/db.h>
#include <rocksdb/options.h>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <regex>
#include "tser/tser.hpp"
// Wrapper class for RocksDB
class RocksDBWrapper
{
public:
    RocksDBWrapper(const std::string &db_path, const std::string &index_path);
    ~RocksDBWrapper();
    template <typename T>
    void store(const std::string &key, const T &value)
    {
        tser::BinaryArchive archive(0);
        archive.save(value);
        rocksdb::Status status = db_->Put(rocksdb::WriteOptions(), serializeKey(key), archive.get_buffer().data());
        if (!status.ok())
        {
            std::cerr << "Error storing value in RocksDB database: " << status.ToString() << std::endl;
        }
        // Index the struct's members
        index_members(serializeKey(key), value);
    }
    template <typename T>
    bool get(const std::string &key, T &value)
    {
        std::string serialized_value;
        rocksdb::Status status = db_->Get(rocksdb::ReadOptions(), serializeKey(key), &serialized_value);
        if (!status.ok())
        {
            std::cerr << "Error getting value from RocksDB database: " << status.ToString() << std::endl;
            return false;
        }
        tser::BinaryArchive archive(0);
        archive.initialize(serialized_value);
        value = archive.load<T>();
        return true;
    }
    template <typename T>
    std::vector<std::string> search(const std::string &member_name, const std::string &member_value)
    {
        std::vector<std::string> keys;
        std::string prefix = member_name + ":" + serializeKey(member_value) + ":";
        rocksdb::Iterator *it = index_db_->NewIterator(rocksdb::ReadOptions());
        for (it->Seek(prefix); it->Valid() && it->key().ToString().find(prefix) == 0; it->Next())
        {
            keys.push_back(it->value().ToString());
        }
        delete it;
        return keys;
    }
    inline std::vector<std::string> split(const std::string &s, const std::string &delimiter)
    {
        size_t last = 0;
        size_t next = 0;
        std::vector<std::string> results;
        while ((next = s.find(delimiter, last)) != std::string::npos)
        {
            results.emplace_back(s.substr(last, next - last));
            last = next + delimiter.length();
        }
        results.emplace_back(s.substr(last));
        return results;
    }
    template <typename T>
    void search_text(const std::string &search_value, std::vector<T> &res)
    {
        std::vector<std::string> keys;
        std::vector<std::string> tmp;
        rocksdb::Iterator *it = index_db_->NewIterator(rocksdb::ReadOptions());
        for (it->SeekToFirst(); it->Valid(); it->Next())
        {
            std::string key = it->key().ToString();
            auto _split = this->split(key, ":");
            if (this->StringMatch(_split[1], search_value))
            {
                keys.push_back(_split[2]);
            }
        }
        std::vector<rocksdb::Slice> slices;
        for (const auto &key : keys)
        {
            slices.push_back(rocksdb::Slice(key));
        }
        std::vector<rocksdb::Status> statuses = db_->MultiGet(rocksdb::ReadOptions(), slices, &tmp);
        for (const auto &_v : tmp)
        {
            T _val;
            this->DeSerialize(_v, _val);
            res.emplace_back(_val);
        }
        delete it;
    }
    // New keyExists method
    bool keyExists(const std::string &key);
    // New conditional search method
    template <typename T>
    void search_conditional(std::vector<T> &res, std::function<bool(const T &)> condition)
    {
        rocksdb::Iterator *it = db_->NewIterator(rocksdb::ReadOptions());
        for (it->SeekToFirst(); it->Valid(); it->Next())
        {
            std::string key = it->key().ToString();
            std::string serialized_value = it->value().ToString();
            T _val;
            this->DeSerialize(serialized_value, _val);
            if (condition(_val))
            {
                res.push_back(_val);
            }
        }
        delete it;
    }
private:
    rocksdb::DB *db_;
    rocksdb::DB *index_db_;
    bool StringMatch(const std::string &text, const std::string &pattern);
    template <typename T>
    inline void DeSerialize(const std::string &data, T &value)
    {
        tser::BinaryArchive archive(0);
        archive.initialize(data);
        value = archive.load<T>();
    }
    template <typename T>
    inline void Serialize(const T &value, std::string &data)
    {
        tser::BinaryArchive archive(0);
        archive.save(value);
        data = archive.get_buffer().data();
    }
    template <typename T>
    void index_single_member(const std::string &key, const std::string &member_name, const T &member_value)
    {
        if constexpr (std::is_arithmetic_v<std::decay_t<T>>)
        {
            std::string member_value_str = std::to_string(member_value);
            std::string index_key = member_name + ":" + serializeKey(member_value_str) + ":" + key;
            rocksdb::Status status = index_db_->Put(rocksdb::WriteOptions(), index_key, key);
            if (!status.ok())
            {
                std::cerr << "Error indexing member in RocksDB index database: " << status.ToString() << std::endl;
            }
        }
        else if constexpr (std::is_same_v<std::decay_t<T>, std::string>)
        {
            std::string index_key = member_name + ":" + serializeKey(member_value) + ":" + key;
            rocksdb::Status status = index_db_->Put(rocksdb::WriteOptions(), index_key, key);
            if (!status.ok())
            {
                std::cerr << "Error indexing member in RocksDB index database: " << status.ToString() << std::endl;
            }
        }
        else if constexpr (tser::is_container_v<std::decay_t<T>>)
        {
            for (const auto &elem : member_value)
            {
                index_single_member(key, member_name, elem);
            }
        }
        else
        {
            // Handle other types if necessary
        }
    }
    template <typename T>
    void index_members(const std::string &key, const T &value)
    {
        if constexpr (tser::is_tser_t_v<T>)
        {
            auto member_names = T::_memberNames;
            size_t index = 0;
            std::apply([&](auto &&...args)
                       { ((index_single_member(key, member_names[index++], args)), ...); },
                       value.members());
        }
    }
    std::string serializeKey(const std::string &key)
    {
        // Implement a method to serialize keys to handle special characters
        std::string serialized_key;
        for (const char c : key)
        {
            if (c == ':')
            {
                serialized_key += "%3A"; // Percent-encoding for ':'
            }
            else
            {
                serialized_key += c;
            }
        }
        return serialized_key;
    }
};
external/tser
New file
@@ -0,0 +1 @@
Subproject commit 9f44236de8fc9e1af5bbea443c8fe8b5a4952ec5
main.cpp
New file
@@ -0,0 +1,47 @@
#include <rocksdb/db.h>
#include <rocksdb/options.h>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <regex>
#include "tser/tser.hpp"
#include "RocksDBWrapper.h"
// Define a sample struct with tser serialization
struct Book
{
    std::string title;
    int pages;
    // Define the members for serialization
    DEFINE_SERIALIZABLE(Book, title, pages)
};
int main()
{
    RocksDBWrapper db("testdb", "indexdb");
    // Create some sample books
    Book book1{"Book One", 30};
    Book book2{"Book Two", 15};
    Book book3{"Book Three", 50};
    // Store the books in the database
    db.store("book_1", book1);
    db.store("book_2", book2);
    db.store("book_3", book3);
    std::vector<Book> res;
    db.search_text("*One", res);
    for (const auto &_l : res)
    {
        std::cout << _l << std::endl;
    }
    Book _res;
    db.get("book_1", _res);
    std::cout << _res << std::endl;
}
test_RocksDBWrapper.cpp
New file
@@ -0,0 +1,114 @@
#include "RocksDBWrapper.h"
#include <gtest/gtest.h>
#include <string>
#include <filesystem>
struct TestStruct
{
    int id;
    std::string name;
    double value;
    DEFINE_SERIALIZABLE(TestStruct, id, name, value);
};
class RocksDBWrapperTest : public ::testing::Test
{
protected:
    RocksDBWrapper *db;
    std::string db_path = "/tmp/testdb";
    std::string index_path = "/tmp/testindex";
    void SetUp() override
    {
        db = new RocksDBWrapper(db_path, index_path);
    }
    void TearDown() override
    {
        delete db;
        // Clean up the database files after each test
        std::filesystem::remove_all(db_path);
        std::filesystem::remove_all(index_path);
    }
};
TEST_F(RocksDBWrapperTest, StoreAndRetrieve)
{
    TestStruct original{1, "Test", 3.14};
    db->store("test_key", original);
    TestStruct retrieved;
    bool found = db->get("test_key", retrieved);
    ASSERT_TRUE(found);
    EXPECT_EQ(original.id, retrieved.id);
    EXPECT_EQ(original.name, retrieved.name);
    EXPECT_DOUBLE_EQ(original.value, retrieved.value);
}
TEST_F(RocksDBWrapperTest, KeyExists)
{
    TestStruct original{2, "AnotherTest", 2.718};
    db->store("another_key", original);
    ASSERT_TRUE(db->keyExists("another_key"));
    ASSERT_FALSE(db->keyExists("nonexistent_key"));
}
TEST_F(RocksDBWrapperTest, SearchByMember)
{
    TestStruct ts1{1, "Alpha", 1.1};
    TestStruct ts2{2, "Beta", 2.2};
    TestStruct ts3{3, "Gamma", 3.3};
    db->store("key1", ts1);
    db->store("key2", ts2);
    db->store("key3", ts3);
    auto keys = db->search<TestStruct>("name", "Beta");
    ASSERT_EQ(keys.size(), 1);
    EXPECT_EQ(keys[0], "key2");
}
TEST_F(RocksDBWrapperTest, SearchText)
{
    TestStruct ts1{1, "Alpha", 1.1};
    TestStruct ts2{2, "Beta", 2.2};
    TestStruct ts3{3, "Gamma", 3.3};
    db->store("key1", ts1);
    db->store("key2", ts2);
    db->store("key3", ts3);
    std::vector<TestStruct> results;
    db->search_text("Bet*", results);
    ASSERT_EQ(results.size(), 1);
    EXPECT_EQ(results[0].name, "Beta");
}
TEST_F(RocksDBWrapperTest, ConditionalSearch)
{
    TestStruct ts1{1, "Alpha", 1.1};
    TestStruct ts2{2, "Beta", 2.2};
    TestStruct ts3{3, "Gamma", 3.3};
    db->store("key1", ts1);
    db->store("key2", ts2);
    db->store("key3", ts3);
    std::vector<TestStruct> results;
    db->search_conditional<TestStruct>(results, [](const TestStruct &s)
                                       { return s.value > 2.0; });
    ASSERT_EQ(results.size(), 2);
    EXPECT_EQ(results[0].name, "Beta");
    EXPECT_EQ(results[1].name, "Gamma");
}
int main(int argc, char **argv)
{
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}