diff --git a/CMakeLists.txt b/CMakeLists.txt index 31992b7..9410171 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(tidesdb_cpp VERSION 2.1.1 LANGUAGES CXX) +project(tidesdb_cpp VERSION 2.2.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/include/tidesdb/tidesdb.hpp b/include/tidesdb/tidesdb.hpp index 19842bc..15e0380 100644 --- a/include/tidesdb/tidesdb.hpp +++ b/include/tidesdb/tidesdb.hpp @@ -188,6 +188,7 @@ struct ColumnFamilyConfig std::uint64_t minDiskSpace = 100 * 1024 * 1024; int l1FileCountTrigger = 4; int l0QueueStallThreshold = 20; + bool useBtree = false; ///< Use B+tree format for klog (default: false = block-based) /** * @brief Get default column family configuration from TidesDB @@ -245,6 +246,10 @@ struct Stats std::vector levelKeyCounts; double readAmp = 0.0; double hitRate = 0.0; + bool useBtree = false; ///< Whether column family uses B+tree format + std::uint64_t btreeTotalNodes = 0; ///< Total B+tree nodes across all SSTables + std::uint32_t btreeMaxHeight = 0; ///< Maximum tree height across all SSTables + double btreeAvgHeight = 0.0; ///< Average tree height across all SSTables }; /** diff --git a/src/tidesdb.cpp b/src/tidesdb.cpp index b25a39a..aa5c9b2 100644 --- a/src/tidesdb.cpp +++ b/src/tidesdb.cpp @@ -68,6 +68,7 @@ ColumnFamilyConfig ColumnFamilyConfig::defaultConfig() config.minDiskSpace = cConfig.min_disk_space; config.l1FileCountTrigger = cConfig.l1_file_count_trigger; config.l0QueueStallThreshold = cConfig.l0_queue_stall_threshold; + config.useBtree = cConfig.use_btree != 0; return config; } @@ -101,6 +102,7 @@ ColumnFamilyConfig ColumnFamilyConfig::loadFromIni(const std::string& iniFile, config.minDiskSpace = cConfig.min_disk_space; config.l1FileCountTrigger = cConfig.l1_file_count_trigger; config.l0QueueStallThreshold = cConfig.l0_queue_stall_threshold; + config.useBtree = cConfig.use_btree != 0; return config; } @@ -130,6 +132,7 @@ void ColumnFamilyConfig::saveToIni(const std::string& iniFile, const std::string cConfig.min_disk_space = config.minDiskSpace; cConfig.l1_file_count_trigger = config.l1FileCountTrigger; cConfig.l0_queue_stall_threshold = config.l0QueueStallThreshold; + cConfig.use_btree = config.useBtree ? 1 : 0; std::memset(cConfig.comparator_name, 0, TDB_MAX_COMPARATOR_NAME); if (!config.comparatorName.empty()) @@ -200,6 +203,12 @@ Stats ColumnFamily::getStats() const stats.readAmp = cStats->read_amp; stats.hitRate = cStats->hit_rate; + // B+tree stats + stats.useBtree = cStats->use_btree != 0; + stats.btreeTotalNodes = cStats->btree_total_nodes; + stats.btreeMaxHeight = cStats->btree_max_height; + stats.btreeAvgHeight = cStats->btree_avg_height; + if (cStats->num_levels > 0 && cStats->level_key_counts != nullptr) { stats.levelKeyCounts.resize(cStats->num_levels); @@ -234,6 +243,7 @@ Stats ColumnFamily::getStats() const cfConfig.minDiskSpace = cStats->config->min_disk_space; cfConfig.l1FileCountTrigger = cStats->config->l1_file_count_trigger; cfConfig.l0QueueStallThreshold = cStats->config->l0_queue_stall_threshold; + cfConfig.useBtree = cStats->config->use_btree != 0; stats.config = cfConfig; } @@ -287,6 +297,7 @@ void ColumnFamily::updateRuntimeConfig(const ColumnFamilyConfig& config, bool pe cConfig.min_disk_space = config.minDiskSpace; cConfig.l1_file_count_trigger = config.l1FileCountTrigger; cConfig.l0_queue_stall_threshold = config.l0QueueStallThreshold; + cConfig.use_btree = config.useBtree ? 1 : 0; std::memset(cConfig.comparator_name, 0, TDB_MAX_COMPARATOR_NAME); if (!config.comparatorName.empty()) @@ -607,6 +618,7 @@ void TidesDB::createColumnFamily(const std::string& name, const ColumnFamilyConf cConfig.min_disk_space = config.minDiskSpace; cConfig.l1_file_count_trigger = config.l1FileCountTrigger; cConfig.l0_queue_stall_threshold = config.l0QueueStallThreshold; + cConfig.use_btree = config.useBtree ? 1 : 0; std::memset(cConfig.comparator_name, 0, TDB_MAX_COMPARATOR_NAME); if (!config.comparatorName.empty()) diff --git a/tests/tidesdb_test.cpp b/tests/tidesdb_test.cpp index 60251aa..664a11f 100644 --- a/tests/tidesdb_test.cpp +++ b/tests/tidesdb_test.cpp @@ -749,6 +749,76 @@ TEST_F(TidesDBTest, DefaultConfig) ASSERT_GE(defaultConfig.maxOpenSSTables, 0u); } +TEST_F(TidesDBTest, UseBtreeConfig) +{ + tidesdb::TidesDB db(getConfig()); + + // Test with useBtree = false (default, block-based format) + { + auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig(); + ASSERT_FALSE(cfConfig.useBtree); // Default should be false + db.createColumnFamily("block_cf", cfConfig); + + auto cf = db.getColumnFamily("block_cf"); + auto stats = cf.getStats(); + ASSERT_FALSE(stats.useBtree); + if (stats.config.has_value()) + { + ASSERT_FALSE(stats.config->useBtree); + } + } + + // Test with useBtree = true (B+tree format) + { + auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig(); + cfConfig.useBtree = true; + db.createColumnFamily("btree_cf", cfConfig); + + auto cf = db.getColumnFamily("btree_cf"); + auto stats = cf.getStats(); + ASSERT_TRUE(stats.useBtree); + if (stats.config.has_value()) + { + ASSERT_TRUE(stats.config->useBtree); + } + } +} + +TEST_F(TidesDBTest, BtreeStats) +{ + tidesdb::TidesDB db(getConfig()); + + auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig(); + cfConfig.useBtree = true; + cfConfig.writeBufferSize = 1024; // Small buffer to trigger flush + db.createColumnFamily("btree_cf", cfConfig); + + auto cf = db.getColumnFamily("btree_cf"); + + // Insert data to populate B+tree structures + { + auto txn = db.beginTransaction(); + for (int i = 0; i < 50; ++i) + { + std::string key = "key" + std::to_string(i); + std::string value = "value" + std::to_string(i); + txn.put(cf, key, value, -1); + } + txn.commit(); + } + + // Force flush to create SSTables with B+tree format + cf.flushMemtable(); + + auto stats = cf.getStats(); + + // Verify B+tree stats fields exist and are valid + ASSERT_TRUE(stats.useBtree); + ASSERT_GE(stats.btreeTotalNodes, 0u); + ASSERT_GE(stats.btreeMaxHeight, 0u); + ASSERT_GE(stats.btreeAvgHeight, 0.0); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv);