Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 75 additions & 1 deletion src/tidesdb.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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
158 changes: 158 additions & 0 deletions tests/test_tidesdb.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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...")
Expand Down
4 changes: 2 additions & 2 deletions tidesdb-0.5.5-1.rockspec → tidesdb-0.5.6-1.rockspec
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Loading