From 9254ad7b5b8c84fafc3b3e83c153407bca172fa7 Mon Sep 17 00:00:00 2001 From: Alex Gaetano Padula Date: Fri, 13 Mar 2026 11:37:57 -0400 Subject: [PATCH] addition of db stats, purge, and sync wal methods with tests; bumped patch --- src/tidesdb.lua | 76 ++++++++- tests/test_tidesdb.lua | 158 ++++++++++++++++++ ...5.5-1.rockspec => tidesdb-0.5.6-1.rockspec | 4 +- 3 files changed, 235 insertions(+), 3 deletions(-) rename tidesdb-0.5.5-1.rockspec => tidesdb-0.5.6-1.rockspec (95%) diff --git a/src/tidesdb.lua b/src/tidesdb.lua index 2a2eb20..e0923dc 100644 --- a/src/tidesdb.lua +++ b/src/tidesdb.lua @@ -180,6 +180,33 @@ ffi.cdef[[ // Commit hook operations int tidesdb_cf_set_commit_hook(void* cf, tidesdb_commit_hook_fn fn, void* ctx); + // Sync WAL + int tidesdb_sync_wal(void* cf); + + // Purge operations + int tidesdb_purge_cf(void* cf); + int tidesdb_purge(void* db); + + // Database-level statistics + typedef struct { + int num_column_families; + uint64_t total_memory; + uint64_t available_memory; + size_t resolved_memory_limit; + int memory_pressure_level; + int flush_pending_count; + int64_t total_memtable_bytes; + int total_immutable_count; + int total_sstable_count; + uint64_t total_data_size_bytes; + int num_open_sstables; + uint64_t global_seq; + int64_t txn_memory_bytes; + size_t compaction_queue_size; + size_t flush_queue_size; + } tidesdb_db_stats_t; + int tidesdb_get_db_stats(void* db, tidesdb_db_stats_t* stats); + // Backup operations int tidesdb_backup(void* db, const char* dir); int tidesdb_checkpoint(void* db, const char* checkpoint_dir); @@ -583,6 +610,16 @@ function ColumnFamily:clear_commit_hook() check_result(result, "failed to clear commit hook") end +function ColumnFamily:sync_wal() + local result = lib.tidesdb_sync_wal(self._cf) + check_result(result, "failed to sync WAL") +end + +function ColumnFamily:purge() + local result = lib.tidesdb_purge_cf(self._cf) + check_result(result, "failed to purge column family") +end + function ColumnFamily:range_cost(key_a, key_b) local cost = ffi.new("double[1]") local result = lib.tidesdb_range_cost(self._cf, key_a, #key_a, key_b, #key_b, cost) @@ -1022,6 +1059,43 @@ function TidesDB:get_cache_stats() } end +function TidesDB:purge() + if self._closed then + error(TidesDBError.new("Database is closed")) + end + + local result = lib.tidesdb_purge(self._db) + check_result(result, "failed to purge database") +end + +function TidesDB:get_db_stats() + if self._closed then + error(TidesDBError.new("Database is closed")) + end + + local c_stats = ffi.new("tidesdb_db_stats_t") + local result = lib.tidesdb_get_db_stats(self._db, c_stats) + check_result(result, "failed to get database stats") + + return { + num_column_families = c_stats.num_column_families, + total_memory = tonumber(c_stats.total_memory), + available_memory = tonumber(c_stats.available_memory), + resolved_memory_limit = tonumber(c_stats.resolved_memory_limit), + memory_pressure_level = c_stats.memory_pressure_level, + flush_pending_count = c_stats.flush_pending_count, + total_memtable_bytes = tonumber(c_stats.total_memtable_bytes), + total_immutable_count = c_stats.total_immutable_count, + total_sstable_count = c_stats.total_sstable_count, + total_data_size_bytes = tonumber(c_stats.total_data_size_bytes), + num_open_sstables = c_stats.num_open_sstables, + global_seq = tonumber(c_stats.global_seq), + txn_memory_bytes = tonumber(c_stats.txn_memory_bytes), + compaction_queue_size = tonumber(c_stats.compaction_queue_size), + flush_queue_size = tonumber(c_stats.flush_queue_size), + } +end + function TidesDB:backup(dir) if self._closed then error(TidesDBError.new("Database is closed")) @@ -1102,6 +1176,6 @@ function tidesdb.save_config_to_ini(ini_file, section_name, config) end -- Version -tidesdb._VERSION = "0.5.5" +tidesdb._VERSION = "0.5.6" return tidesdb diff --git a/tests/test_tidesdb.lua b/tests/test_tidesdb.lua index 17f8b2b..eb642b2 100644 --- a/tests/test_tidesdb.lua +++ b/tests/test_tidesdb.lua @@ -910,6 +910,164 @@ function tests.test_max_memory_usage() print("PASS: test_max_memory_usage") end +function tests.test_sync_wal() + local path = "./test_db_sync_wal" + cleanup_db(path) + + local db = tidesdb.TidesDB.open(path, { + log_level = tidesdb.LogLevel.LOG_WARN, + }) + local cf_config = tidesdb.default_column_family_config() + cf_config.sync_mode = tidesdb.SyncMode.SYNC_NONE + db:create_column_family("test_cf", cf_config) + local cf = db:get_column_family("test_cf") + + -- Write some data + local txn = db:begin_txn() + txn:put(cf, "key1", "value1") + txn:put(cf, "key2", "value2") + txn:commit() + txn:free() + + -- Manually sync WAL + cf:sync_wal() + + -- Verify data is still readable after sync + local read_txn = db:begin_txn() + local v1 = read_txn:get(cf, "key1") + local v2 = read_txn:get(cf, "key2") + assert_eq(v1, "value1", "key1 should be readable after sync_wal") + assert_eq(v2, "value2", "key2 should be readable after sync_wal") + read_txn:free() + + db:drop_column_family("test_cf") + db:close() + cleanup_db(path) + print("PASS: test_sync_wal") +end + +function tests.test_purge_cf() + local path = "./test_db_purge_cf" + cleanup_db(path) + + local db = tidesdb.TidesDB.open(path, { + log_level = tidesdb.LogLevel.LOG_WARN, + }) + db:create_column_family("test_cf") + local cf = db:get_column_family("test_cf") + + -- Write some data + local txn = db:begin_txn() + for i = 1, 10 do + txn:put(cf, string.format("key:%04d", i), string.format("value:%04d", i)) + end + txn:commit() + txn:free() + + -- Purge column family (synchronous flush + compaction) + cf:purge() + + -- Verify data is still readable after purge + local read_txn = db:begin_txn() + local v1 = read_txn:get(cf, "key:0001") + local v10 = read_txn:get(cf, "key:0010") + assert_eq(v1, "value:0001", "key:0001 should be readable after purge_cf") + assert_eq(v10, "value:0010", "key:0010 should be readable after purge_cf") + read_txn:free() + + -- After purge, flushing and compacting should be done + assert_eq(cf:is_flushing(), false, "should not be flushing after purge") + assert_eq(cf:is_compacting(), false, "should not be compacting after purge") + + db:drop_column_family("test_cf") + db:close() + cleanup_db(path) + print("PASS: test_purge_cf") +end + +function tests.test_purge_db() + local path = "./test_db_purge_db" + cleanup_db(path) + + local db = tidesdb.TidesDB.open(path, { + log_level = tidesdb.LogLevel.LOG_WARN, + }) + db:create_column_family("cf_a") + db:create_column_family("cf_b") + local cf_a = db:get_column_family("cf_a") + local cf_b = db:get_column_family("cf_b") + + -- Write data to both CFs + local txn = db:begin_txn() + txn:put(cf_a, "a_key1", "a_value1") + txn:put(cf_b, "b_key1", "b_value1") + txn:commit() + txn:free() + + -- Purge entire database + db:purge() + + -- Verify data is still readable + local read_txn = db:begin_txn() + local va = read_txn:get(cf_a, "a_key1") + local vb = read_txn:get(cf_b, "b_key1") + assert_eq(va, "a_value1", "cf_a key should be readable after db purge") + assert_eq(vb, "b_value1", "cf_b key should be readable after db purge") + read_txn:free() + + db:drop_column_family("cf_a") + db:drop_column_family("cf_b") + db:close() + cleanup_db(path) + print("PASS: test_purge_db") +end + +function tests.test_get_db_stats() + local path = "./test_db_db_stats" + cleanup_db(path) + + local db = tidesdb.TidesDB.open(path, { + log_level = tidesdb.LogLevel.LOG_WARN, + }) + db:create_column_family("cf_a") + db:create_column_family("cf_b") + local cf_a = db:get_column_family("cf_a") + local cf_b = db:get_column_family("cf_b") + + -- Write some data + local txn = db:begin_txn() + txn:put(cf_a, "key1", "value1") + txn:put(cf_b, "key2", "value2") + txn:commit() + txn:free() + + -- Get database-level stats + local db_stats = db:get_db_stats() + + -- Verify fields exist and have sensible values + assert_true(db_stats.num_column_families >= 2, "should have at least 2 column families") + assert_true(db_stats.total_memory > 0, "total_memory should be > 0") + assert_true(db_stats.resolved_memory_limit > 0, "resolved_memory_limit should be > 0") + assert_true(db_stats.memory_pressure_level >= 0, "memory_pressure_level should be >= 0") + assert_true(db_stats.global_seq >= 0, "global_seq should be >= 0") + assert_true(db_stats.flush_queue_size >= 0, "flush_queue_size should be >= 0") + assert_true(db_stats.compaction_queue_size >= 0, "compaction_queue_size should be >= 0") + assert_true(db_stats.total_sstable_count >= 0, "total_sstable_count should be >= 0") + assert_true(db_stats.total_data_size_bytes >= 0, "total_data_size_bytes should be >= 0") + assert_true(db_stats.num_open_sstables >= 0, "num_open_sstables should be >= 0") + assert_true(db_stats.txn_memory_bytes ~= nil, "txn_memory_bytes should exist") + assert_true(db_stats.total_memtable_bytes ~= nil, "total_memtable_bytes should exist") + assert_true(db_stats.total_immutable_count >= 0, "total_immutable_count should be >= 0") + assert_true(db_stats.flush_pending_count >= 0, "flush_pending_count should be >= 0") + assert_true(db_stats.available_memory ~= nil, "available_memory should exist") + + db:drop_column_family("cf_a") + db:drop_column_family("cf_b") + db:close() + cleanup_db(path) + print("PASS: test_get_db_stats") +end + -- Run all tests local function run_tests() print("Running TidesDB Lua tests...") diff --git a/tidesdb-0.5.5-1.rockspec b/tidesdb-0.5.6-1.rockspec similarity index 95% rename from tidesdb-0.5.5-1.rockspec rename to tidesdb-0.5.6-1.rockspec index cb1a1bd..0e2fae1 100644 --- a/tidesdb-0.5.5-1.rockspec +++ b/tidesdb-0.5.6-1.rockspec @@ -1,8 +1,8 @@ package = "tidesdb" -version = "0.5.5-1" +version = "0.5.6-1" source = { url = "git://github.com/tidesdb/tidesdb-lua.git", - tag = "v0.5.5" + tag = "v0.5.6" } description = { summary = "Official Lua bindings for TidesDB - A high-performance embedded key-value storage engine",