From c20ad78d0ab66128e82c7506184ed81dd5408ffa Mon Sep 17 00:00:00 2001 From: Alex Gaetano Padula Date: Sun, 23 Mar 2025 12:38:24 -0400 Subject: [PATCH] - GetColumnFamilyStat addition - Range addition - ListColumnFamilies addition - DeleteByRange addition - Min C++11 build, support - Update read me --- CMakeLists.txt | 2 +- README.md | 163 +++++++++++++++++++++++++++++++++++-------------- tidesdb.cpp | 136 ++++++++++++++++++++++++++++++++++++++--- tidesdb.hpp | 77 +++++++++++++++++++++-- 4 files changed, 317 insertions(+), 61 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ce991f..9fef486 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.25) project(tidesdb_cpp CXX) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 11) set(PROJECT_VERSION 0.1.0) add_compile_options(-Wextra -Wall -Werror) diff --git a/README.md b/README.md index c10f2b6..9dd42bc 100644 --- a/README.md +++ b/README.md @@ -2,102 +2,171 @@ Official C++ binding for TidesDB. ## Getting Started -You must make sure you have the TidesDB shared C library installed on your system. Be sure to also compile with TIDESDB_WITH_SANITIZER and TIDESDB_BUILD_TESTS OFF. +You must make sure you have the TidesDB shared C library installed on your system. Be sure to also compile with TIDESDB_WITH_SANITIZER and TIDESDB_BUILD_TESTS OFF. You will also require a C++11 compatible compiler. ### Build and install -``` +```bash cmake -S . -B build cmake --build build cmake --install build ``` -### Create a TidesDB instance +### Linking +```cmake +# Find the TidesDB C library +find_library(LIBRARY_TIDEDB NAMES tidesdb REQUIRED) + +# Find the TidesDB C++ binding +find_library(LIBRARY_TIDEDB_CPP NAMES tidesdb_cpp REQUIRED) + +# Link with your target +target_link_libraries(your_target PRIVATE ${LIBRARY_TIDEDB_CPP} ${LIBRARY_TIDEDB}) +``` + + +### Open and Close ```cpp #include int main() { TidesDB::DB db; - db.Open("your_db_dir"); + db.Open("your_db_directory"); - // Perform db operations... + /* Database operations... */ db.Close(); return 0; } ``` -### Creating and dropping column families +### Column Family Management ```cpp -db.CreateColumnFamily("my_column_family", (1024*1024)*64, TDB_DEFAULT_SKIP_LIST_MAX_LEVEL, TDB_DEFAULT_SKIP_LIST_PROBABILITY, true, TIDESDB_COMPRESSION_LZ4, true); -db.DropColumnFamily("my_column_family"); -``` +/* Create a column family with custom parameters */ +db.CreateColumnFamily( + "users", /* Column family name */ + 64 * 1024 * 1024, /* Flush threshold (64MB) */ + TDB_DEFAULT_SKIP_LIST_MAX_LEVEL, /* Max level for skip list */ + TDB_DEFAULT_SKIP_LIST_PROBABILITY, /* Skip list probability */ + true, /* Enable compression */ + TIDESDB_COMPRESSION_LZ4, /* Use LZ4 compression */ + true /* Enable bloom filter */ +); + +/* List all column families */ +std::vector families; +db.ListColumnFamilies(&families); +for (const auto& family : families) { + std::cout << "Found column family: " << family << std::endl; +} -### CRUD operations -#### No TTL -```cpp -std::vector key = {1, 2, 3}; -std::vector value = {4, 5, 6}; -db.Put("my_column_family", &key, &value, -1); -``` +/* Get column family statistics */ +TidesDB::ColumnFamilyStat stat; +db.GetColumnFamilyStat("users", &stat); +std::cout << "Memtable size: " << stat.memtable_size << " bytes" << std::endl; +std::cout << "Number of SSTables: " << stat.num_sstables << std::endl; -#### With TTL -```cpp -std::vector key = {1, 2, 3}; -std::vector value = {4, 5, 6}; -db.Put("my_column_family", &key, &value, std::chrono::seconds(3600)); +/* Drop a column family */ +db.DropColumnFamily("users"); ``` -#### Get +### Basic Key-Value Operations ```cpp +/* Create binary key and value */ +std::vector key = {1, 2, 3, 4}; +std::vector value = {10, 20, 30, 40}; + +/* Insert with no TTL */ +db.Put("users", &key, &value, std::chrono::seconds(0)); + +/* Insert with 1 hour TTL */ +db.Put("users", &key, &value, std::chrono::seconds(3600)); + +/* Retrieve a value */ std::vector retrieved_value; -db.Get("my_column_family", &key, &retrieved_value); +db.Get("users", &key, &retrieved_value); + +/* Delete a key */ +db.Delete("users", &key); ``` -#### Deleting data +### Range Queries ```cpp -db.Delete("my_column_family", &key); +std::vector start_key = {1, 0, 0}; +std::vector end_key = {1, 255, 255}; +std::vector, std::vector>> results; + +db.Range("users", &start_key, &end_key, &results); + +for (const auto& [k, v] : results) { + /* Process key-value pairs.... */ +} + +/* Delete a range of keys */ +db.DeleteByRange("users", &start_key, &end_key); ``` -## Transactions -You can add operations to a transaction and commit them atomically. +### Transactions ```cpp TidesDB::Txn txn(&db); txn.Begin(); -txn.Put(&key, &value, std::chrono::seconds(3600)); -txn.Commit(); -``` -## Cursor -You can iterate over the keys in a column family. -```cpp -TidesDB::Cursor cursor(&db, "my_column_family"); -cursor.Init(); +/* Perform multiple operations atomically */ +std::vector key1 = {1, 1}; +std::vector value1 = {10, 10}; +txn.Put(&key1, &value1, std::chrono::seconds(0)); -std::vector key, value; -cursor.Get(key, value); -cursor.Next(); // Go forwards -cursor.Prev(); // Go backwards +std::vector key2 = {2, 2}; +std::vector value2 = {20, 20}; +txn.Put(&key2, &value2, std::chrono::seconds(0)); + +/* Read within the transaction */ +std::vector read_value; +txn.Get(&key1, &read_value); + +/* Delete within the transaction */ +txn.Delete(&key1); + +/* Commit the transaction */ +txn.Commit(); + +/* Or roll back if needed + * txn.Rollback(); */ ``` +### Cursors ```cpp -TidesDB::Cursor cursor(&db, "my_column_family"); +TidesDB::Cursor cursor(&db, "users"); cursor.Init(); std::vector key, value; while (cursor.Get(key, value) == 0) { - // Process key and value + /* Process key and value */ + + /* Move to next entry */ cursor.Next(); + + /* Or move to previous entry + * cursor.Prev(); */ } -``` +``` -## Compactions -You can manually trigger a compaction. +### Compaction Management ```cpp -db.CompactSSTables("my_column_family", 4); // Use 4 threads for compaction +/* Manual compaction with 4 threads */ +db.CompactSSTables("users", 4); + +/* Automated incremental merges (run every 60 seconds if at least 5 SSTables exist) */ +db.StartIncrementalMerges("users", std::chrono::seconds(60), 5); ``` -Or you can start incremental background merge compactions. +### Exception Handling Example ```cpp -db.StartIncrementalMerges("my_column_family", std::chrono::seconds(60), 5); // Merge every 60 seconds if there are at least 5 SSTables +try { + db.Open("non_existent_directory"); +} catch (const std::runtime_error& e) { + std::cerr << "Database error: " << e.what() << std::endl; + /* The error message will contain both the error code and description + * Format: "Error {code}: {message}" */ +} ``` \ No newline at end of file diff --git a/tidesdb.cpp b/tidesdb.cpp index 91d69b2..bd315fa 100644 --- a/tidesdb.cpp +++ b/tidesdb.cpp @@ -21,7 +21,7 @@ namespace TidesDB { -DB::DB(void) +DB::DB() { tdb = nullptr; } @@ -130,7 +130,126 @@ int DB::StartIncrementalMerges(const std::string &column_family_name, std::chron return 0; } -Txn::Txn(DB *db) +int DB::Range(const std::string &column_family_name, const std::vector *start_key, + const std::vector *end_key, std::vector, + std::vector>> *result) const +{ + size_t start_key_size = start_key->size(); + size_t end_key_size = end_key->size(); + tidesdb_key_value_pair_t **c_result = nullptr; + size_t result_size = 0; + + tidesdb_err_t *err = tidesdb_range(this->tdb, column_family_name.c_str(), + start_key->data(), start_key_size, + end_key->data(), end_key_size, + &c_result, &result_size); + + if (err == nullptr && c_result != nullptr) + { + result->clear(); + result->reserve(result_size); + + for (size_t i = 0; i < result_size; i++) + { + std::vector key(c_result[i]->key, c_result[i]->key + c_result[i]->key_size); + std::vector value(c_result[i]->value, c_result[i]->value + c_result[i]->value_size); + result->emplace_back(key, value); + + (void)_tidesdb_free_key_value_pair(c_result[i]); + } + + free(c_result); + } + + err_handler(err); + return 0; +} + +int DB::ListColumnFamilies(std::vector *families) const +{ + char *c_list = nullptr; + + tidesdb_err_t *err = tidesdb_list_column_families(this->tdb, &c_list); + + if (err == nullptr && c_list != nullptr) + { + families->clear(); + + /* we parse the comma-separated list */ + char *token = strtok(c_list, ","); + while (token != nullptr) + { + families->emplace_back(token); + token = strtok(nullptr, ","); + } + + free(c_list); + } + + err_handler(err); + return 0; +} + +int DB::DeleteByRange(const std::string &column_family_name, const std::vector *start_key, + const std::vector *end_key) const +{ + size_t start_key_size = start_key->size(); + size_t end_key_size = end_key->size(); + + tidesdb_err_t *err = tidesdb_delete_by_range(this->tdb, column_family_name.c_str(), + start_key->data(), start_key_size, + end_key->data(), end_key_size); + + err_handler(err); + return 0; +} + +int DB::GetColumnFamilyStat(const std::string &column_family_name, + ColumnFamilyStat *stat) const +{ + tidesdb_column_family_stat_t *c_stat = nullptr; + + tidesdb_err_t *err = tidesdb_get_column_family_stat(this->tdb, column_family_name.c_str(), + &c_stat); + + if (err == nullptr && c_stat != nullptr) + { + /* we convert C stat to C++ stat */ + stat->name = std::string(c_stat->cf_name); + stat->num_sstables = c_stat->num_sstables; + stat->memtable_size = c_stat->memtable_size; + stat->memtable_entries_count = c_stat->memtable_entries_count; + stat->incremental_merging = c_stat->incremental_merging; + + /* we copy configuration */ + stat->config.name = std::string(c_stat->config.name); + stat->config.flush_threshold = c_stat->config.flush_threshold; + stat->config.max_level = c_stat->config.max_level; + stat->config.probability = c_stat->config.probability; + stat->config.compressed = c_stat->config.compressed; + stat->config.compress_algo = c_stat->config.compress_algo; + stat->config.bloom_filter = c_stat->config.bloom_filter; + + /* we copy sstable stats */ + stat->sstable_stats.clear(); + for (int i = 0; i < c_stat->num_sstables; i++) + { + SSTableStat sstable_stat; + sstable_stat.path = std::string(c_stat->sstable_stats[i]->sstable_path); + sstable_stat.size = c_stat->sstable_stats[i]->size; + sstable_stat.num_blocks = c_stat->sstable_stats[i]->num_blocks; + stat->sstable_stats.push_back(sstable_stat); + } + + /* we free the memory allocated by the C API */ + (void)tidesdb_free_column_family_stat(c_stat); + } + + err_handler(err); + return 0; +} + +Txn::Txn(const DB *db) { this->tdb = db->GetTidesDB(); this->txn = nullptr; @@ -140,7 +259,7 @@ Txn::~Txn() { if (this->txn) { - tidesdb_txn_free(this->txn); + (void)tidesdb_txn_free(this->txn); } } @@ -198,13 +317,13 @@ int Txn::Rollback() const { std::string error_message = err->message; int error_code = err->code; - tidesdb_err_free(err); + (void)tidesdb_err_free(err); throw std::runtime_error("Error " + std::to_string(error_code) + ": " + error_message); } return 0; } -Cursor::Cursor(DB *db, std::string column_family_name) +Cursor::Cursor(const DB *db, std::string column_family_name) { this->tdb = db->GetTidesDB(); this->cursor = nullptr; @@ -220,10 +339,9 @@ int Cursor::Init() Cursor::~Cursor() { - // free the cursor if (this->cursor) { - tidesdb_cursor_free(this->cursor); + (void)tidesdb_cursor_free(this->cursor); } } @@ -241,7 +359,7 @@ int Cursor::Prev() const return 0; } -int Cursor::Get(std::vector &key, std::vector &value)const +int Cursor::Get(std::vector &key, std::vector &value) const { size_t key_size = key.size(); size_t value_size = value.size(); @@ -258,4 +376,4 @@ tidesdb_t *DB::GetTidesDB() const return this->tdb; } -} // namespace TidesDB \ No newline at end of file +} /* namespace TidesDB */ \ No newline at end of file diff --git a/tidesdb.hpp b/tidesdb.hpp index f6b75c7..2c66c9a 100644 --- a/tidesdb.hpp +++ b/tidesdb.hpp @@ -32,6 +32,48 @@ namespace TidesDB { +/* + * SSTableStat Struct + * represents statistics about an SSTable. + */ +struct SSTableStat +{ + std::string path; + size_t size; + size_t num_blocks; +}; + +/* + * ColumnFamilyConfig Struct + * represents configuration for a column family. + */ +struct ColumnFamilyConfig +{ + std::string name; + int32_t flush_threshold; + int32_t max_level; + float probability; + bool compressed; + tidesdb_compression_algo_t compress_algo; + bool bloom_filter; +}; + +/* + * ColumnFamilyStat Class + * represents statistics about a column family. + */ +class ColumnFamilyStat +{ + public: + std::string name; + int num_sstables; + size_t memtable_size; + size_t memtable_entries_count; + bool incremental_merging; + ColumnFamilyConfig config; + std::vector sstable_stats; +}; + /* * DB Class * represents TidesDB database. @@ -81,6 +123,33 @@ class DB int Get(const std::string &column_family_name, const std::vector *key, std::vector *value) const; + /* + * Range + * Gets a range of key-value pairs from a column family. + */ + int Range(const std::string &column_family_name, const std::vector *start_key, + const std::vector *end_key, + std::vector, std::vector>> *result) const; + + /* + * ListColumnFamilies + * Lists the column families in the database. + */ + int ListColumnFamilies(std::vector *families) const; + + /* + * DeleteByRange + * Deletes a range of key-value pairs from a column family. + */ + int DeleteByRange(const std::string &column_family_name, const std::vector *start_key, + const std::vector *end_key) const; + + /* + * GetColumnFamilyStat + * Gets statistics about a column family. + */ + int GetColumnFamilyStat(const std::string &column_family_name, ColumnFamilyStat *stat) const; + /* * Delete * Deletes a key-value pair from a column family. @@ -120,7 +189,7 @@ class Txn * Txn * creates a new transaction for a database. */ - explicit Txn(DB *db); + explicit Txn(const DB *db); ~Txn(); /* @@ -176,7 +245,7 @@ class Cursor * Cursor * creates a new cursor for a column family. */ - Cursor(DB *db, std::string column_family_name); + Cursor(const DB *db, std::string column_family_name); ~Cursor(); /* @@ -201,7 +270,7 @@ class Cursor * Get * gets the current key-value pair in the column family cursor. */ - [[nodiscard]] int Get(std::vector &key, std::vector &value)const; + [[nodiscard]] int Get(std::vector &key, std::vector &value) const; }; -}; // namespace TidesDB +}; /* namespace TidesDB */