Skip to content

Commit a3e01a9

Browse files
authored
Merge pull request #41 from tidesdb/updates100
Updates100
2 parents d22483b + 72faa59 commit a3e01a9

4 files changed

Lines changed: 232 additions & 1 deletion

File tree

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
cmake_minimum_required(VERSION 3.16)
2-
project(tidesdb_cpp VERSION 2.3.2 LANGUAGES CXX)
2+
project(tidesdb_cpp VERSION 2.3.3 LANGUAGES CXX)
33

44
set(CMAKE_CXX_STANDARD 17)
55
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include/tidesdb/tidesdb.hpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ struct ColumnFamilyConfig
189189
int l1FileCountTrigger = 4;
190190
int l0QueueStallThreshold = 20;
191191
bool useBtree = false; ///< Use B+tree format for klog (default: false = block-based)
192+
tidesdb_commit_hook_fn commitHookFn =
193+
nullptr; ///< Optional commit hook callback (runtime-only)
194+
void* commitHookCtx = nullptr; ///< Optional user context for commit hook (runtime-only)
192195

193196
/**
194197
* @brief Get default column family configuration from TidesDB
@@ -322,6 +325,18 @@ class ColumnFamily
322325
[[nodiscard]] double rangeCost(const std::vector<std::uint8_t>& keyA,
323326
const std::vector<std::uint8_t>& keyB) const;
324327

328+
/**
329+
* @brief Set or replace the commit hook for this column family
330+
* @param fn Commit hook callback
331+
* @param ctx User-provided context passed to the callback
332+
*/
333+
void setCommitHook(tidesdb_commit_hook_fn fn, void* ctx);
334+
335+
/**
336+
* @brief Clear (disable) the commit hook for this column family
337+
*/
338+
void clearCommitHook();
339+
325340
/**
326341
* @brief Update runtime-safe configuration settings
327342
* @param config New configuration (only runtime-safe fields are applied)

src/tidesdb.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ void ColumnFamilyConfig::saveToIni(const std::string& iniFile, const std::string
143143
std::memset(cConfig.comparator_ctx_str, 0, TDB_MAX_COMPARATOR_CTX);
144144
cConfig.comparator_fn_cached = nullptr;
145145
cConfig.comparator_ctx_cached = nullptr;
146+
cConfig.commit_hook_fn = nullptr;
147+
cConfig.commit_hook_ctx = nullptr;
146148

147149
int result = tidesdb_cf_config_save_to_ini(iniFile.c_str(), sectionName.c_str(), &cConfig);
148150
checkResult(result, "failed to save config to INI");
@@ -292,6 +294,18 @@ double ColumnFamily::rangeCost(const std::vector<std::uint8_t>& keyA,
292294
return cost;
293295
}
294296

297+
void ColumnFamily::setCommitHook(tidesdb_commit_hook_fn fn, void* ctx)
298+
{
299+
int result = tidesdb_cf_set_commit_hook(cf_, fn, ctx);
300+
checkResult(result, "failed to set commit hook");
301+
}
302+
303+
void ColumnFamily::clearCommitHook()
304+
{
305+
int result = tidesdb_cf_set_commit_hook(cf_, nullptr, nullptr);
306+
checkResult(result, "failed to clear commit hook");
307+
}
308+
295309
void ColumnFamily::updateRuntimeConfig(const ColumnFamilyConfig& config, bool persistToDisk)
296310
{
297311
tidesdb_column_family_config_t cConfig;
@@ -327,6 +341,8 @@ void ColumnFamily::updateRuntimeConfig(const ColumnFamilyConfig& config, bool pe
327341
std::memset(cConfig.comparator_ctx_str, 0, TDB_MAX_COMPARATOR_CTX);
328342
cConfig.comparator_fn_cached = nullptr;
329343
cConfig.comparator_ctx_cached = nullptr;
344+
cConfig.commit_hook_fn = nullptr;
345+
cConfig.commit_hook_ctx = nullptr;
330346

331347
int result = tidesdb_cf_update_runtime_config(cf_, &cConfig, persistToDisk ? 1 : 0);
332348
checkResult(result, "failed to update runtime config");
@@ -654,6 +670,8 @@ void TidesDB::createColumnFamily(const std::string& name, const ColumnFamilyConf
654670
std::memset(cConfig.comparator_ctx_str, 0, TDB_MAX_COMPARATOR_CTX);
655671
cConfig.comparator_fn_cached = nullptr;
656672
cConfig.comparator_ctx_cached = nullptr;
673+
cConfig.commit_hook_fn = config.commitHookFn;
674+
cConfig.commit_hook_ctx = config.commitHookCtx;
657675

658676
int result = tidesdb_create_column_family(db_, name.c_str(), &cConfig);
659677
checkResult(result, "failed to create column family");

tests/tidesdb_test.cpp

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919

2020
#include <gtest/gtest.h>
2121

22+
#include <atomic>
2223
#include <chrono>
2324
#include <cstdlib>
2425
#include <filesystem>
26+
#include <mutex>
2527
#include <string>
2628
#include <thread>
2729
#include <vector>
@@ -1139,6 +1141,202 @@ TEST_F(TidesDBTest, TransactionResetAfterRollback)
11391141
}
11401142
}
11411143

1144+
// Commit hook test helpers
1145+
struct HookTestCtx
1146+
{
1147+
std::atomic<int> callCount{0};
1148+
std::atomic<int> totalOps{0};
1149+
std::atomic<uint64_t> lastCommitSeq{0};
1150+
std::mutex mu;
1151+
std::vector<std::string> capturedKeys;
1152+
};
1153+
1154+
static int testCommitHook(const tidesdb_commit_op_t* ops, int num_ops, uint64_t commit_seq,
1155+
void* ctx)
1156+
{
1157+
auto* hctx = static_cast<HookTestCtx*>(ctx);
1158+
hctx->callCount.fetch_add(1);
1159+
hctx->totalOps.fetch_add(num_ops);
1160+
hctx->lastCommitSeq.store(commit_seq);
1161+
1162+
std::lock_guard<std::mutex> lock(hctx->mu);
1163+
for (int i = 0; i < num_ops; ++i)
1164+
{
1165+
hctx->capturedKeys.emplace_back(reinterpret_cast<const char*>(ops[i].key), ops[i].key_size);
1166+
}
1167+
return 0;
1168+
}
1169+
1170+
TEST_F(TidesDBTest, CommitHookBasic)
1171+
{
1172+
tidesdb::TidesDB db(getConfig());
1173+
1174+
auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig();
1175+
db.createColumnFamily("test_cf", cfConfig);
1176+
1177+
auto cf = db.getColumnFamily("test_cf");
1178+
1179+
HookTestCtx hookCtx;
1180+
cf.setCommitHook(testCommitHook, &hookCtx);
1181+
1182+
// Commit a transaction -- hook should fire
1183+
{
1184+
auto txn = db.beginTransaction();
1185+
txn.put(cf, "key1", "value1", -1);
1186+
txn.put(cf, "key2", "value2", -1);
1187+
txn.commit();
1188+
}
1189+
1190+
ASSERT_GE(hookCtx.callCount.load(), 1);
1191+
ASSERT_GE(hookCtx.totalOps.load(), 2);
1192+
ASSERT_GT(hookCtx.lastCommitSeq.load(), 0u);
1193+
1194+
// Verify captured keys
1195+
{
1196+
std::lock_guard<std::mutex> lock(hookCtx.mu);
1197+
bool foundKey1 = false, foundKey2 = false;
1198+
for (const auto& k : hookCtx.capturedKeys)
1199+
{
1200+
if (k == "key1") foundKey1 = true;
1201+
if (k == "key2") foundKey2 = true;
1202+
}
1203+
ASSERT_TRUE(foundKey1);
1204+
ASSERT_TRUE(foundKey2);
1205+
}
1206+
}
1207+
1208+
TEST_F(TidesDBTest, CommitHookClear)
1209+
{
1210+
tidesdb::TidesDB db(getConfig());
1211+
1212+
auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig();
1213+
db.createColumnFamily("test_cf", cfConfig);
1214+
1215+
auto cf = db.getColumnFamily("test_cf");
1216+
1217+
HookTestCtx hookCtx;
1218+
cf.setCommitHook(testCommitHook, &hookCtx);
1219+
1220+
// First commit -- hook fires
1221+
{
1222+
auto txn = db.beginTransaction();
1223+
txn.put(cf, "key1", "value1", -1);
1224+
txn.commit();
1225+
}
1226+
1227+
int countAfterFirst = hookCtx.callCount.load();
1228+
ASSERT_GE(countAfterFirst, 1);
1229+
1230+
// Clear the hook
1231+
cf.clearCommitHook();
1232+
1233+
// Second commit -- hook should NOT fire
1234+
{
1235+
auto txn = db.beginTransaction();
1236+
txn.put(cf, "key2", "value2", -1);
1237+
txn.commit();
1238+
}
1239+
1240+
ASSERT_EQ(hookCtx.callCount.load(), countAfterFirst);
1241+
}
1242+
1243+
TEST_F(TidesDBTest, CommitHookDeleteOps)
1244+
{
1245+
tidesdb::TidesDB db(getConfig());
1246+
1247+
auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig();
1248+
db.createColumnFamily("test_cf", cfConfig);
1249+
1250+
auto cf = db.getColumnFamily("test_cf");
1251+
1252+
// Insert data first
1253+
{
1254+
auto txn = db.beginTransaction();
1255+
txn.put(cf, "del_key", "del_value", -1);
1256+
txn.commit();
1257+
}
1258+
1259+
HookTestCtx hookCtx;
1260+
cf.setCommitHook(testCommitHook, &hookCtx);
1261+
1262+
// Delete the key -- hook should capture the delete
1263+
{
1264+
auto txn = db.beginTransaction();
1265+
txn.del(cf, "del_key");
1266+
txn.commit();
1267+
}
1268+
1269+
ASSERT_GE(hookCtx.callCount.load(), 1);
1270+
ASSERT_GE(hookCtx.totalOps.load(), 1);
1271+
}
1272+
1273+
TEST_F(TidesDBTest, CommitHookMonotonicSeq)
1274+
{
1275+
tidesdb::TidesDB db(getConfig());
1276+
1277+
auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig();
1278+
db.createColumnFamily("test_cf", cfConfig);
1279+
1280+
auto cf = db.getColumnFamily("test_cf");
1281+
1282+
// Custom hook that records all commit sequences
1283+
struct SeqCtx
1284+
{
1285+
std::mutex mu;
1286+
std::vector<uint64_t> seqs;
1287+
} seqCtx;
1288+
1289+
auto seqHook = [](const tidesdb_commit_op_t*, int, uint64_t commit_seq, void* ctx) -> int
1290+
{
1291+
auto* sctx = static_cast<SeqCtx*>(ctx);
1292+
std::lock_guard<std::mutex> lock(sctx->mu);
1293+
sctx->seqs.push_back(commit_seq);
1294+
return 0;
1295+
};
1296+
1297+
cf.setCommitHook(seqHook, &seqCtx);
1298+
1299+
// Multiple commits
1300+
for (int i = 0; i < 5; ++i)
1301+
{
1302+
auto txn = db.beginTransaction();
1303+
txn.put(cf, "key" + std::to_string(i), "value" + std::to_string(i), -1);
1304+
txn.commit();
1305+
}
1306+
1307+
// Verify monotonically increasing sequence numbers
1308+
std::lock_guard<std::mutex> lock(seqCtx.mu);
1309+
ASSERT_GE(seqCtx.seqs.size(), 5u);
1310+
for (size_t i = 1; i < seqCtx.seqs.size(); ++i)
1311+
{
1312+
ASSERT_GT(seqCtx.seqs[i], seqCtx.seqs[i - 1]);
1313+
}
1314+
}
1315+
1316+
TEST_F(TidesDBTest, CommitHookViaConfig)
1317+
{
1318+
tidesdb::TidesDB db(getConfig());
1319+
1320+
HookTestCtx hookCtx;
1321+
1322+
auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig();
1323+
cfConfig.commitHookFn = testCommitHook;
1324+
cfConfig.commitHookCtx = &hookCtx;
1325+
db.createColumnFamily("test_cf", cfConfig);
1326+
1327+
auto cf = db.getColumnFamily("test_cf");
1328+
1329+
// Commit -- hook should already be active from config
1330+
{
1331+
auto txn = db.beginTransaction();
1332+
txn.put(cf, "key1", "value1", -1);
1333+
txn.commit();
1334+
}
1335+
1336+
ASSERT_GE(hookCtx.callCount.load(), 1);
1337+
ASSERT_GE(hookCtx.totalOps.load(), 1);
1338+
}
1339+
11421340
int main(int argc, char** argv)
11431341
{
11441342
::testing::InitGoogleTest(&argc, argv);

0 commit comments

Comments
 (0)