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
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16)
project(tidesdb_cpp VERSION 2.2.0 LANGUAGES CXX)
project(tidesdb_cpp VERSION 2.3.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Expand Down
12 changes: 9 additions & 3 deletions code_formatter.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
#!/bin/bash
set -euo pipefail

# Before submitting a PR, run this script to format the source code.

EXCLUDE_DIRS="external\|cmake-build-debug\|.idea|build|cmake"

find . -type f -name "*.cpp" -o -name "*.hpp" | grep -v "$EXCLUDE_DIRS" | xargs clang-format -i
find . \
\( -path "./external" \
-o -path "./cmake-build-debug" \
-o -path "./.idea" \
-o -path "./build" \
-o -path "./cmake" \) -prune \
-o -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 \
| xargs -0 clang-format -i
13 changes: 13 additions & 0 deletions include/tidesdb/tidesdb.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,12 @@ class Transaction
*/
[[nodiscard]] Iterator newIterator(ColumnFamily& cf);

/**
* @brief Reset a committed or aborted transaction for reuse
* @param isolation New isolation level for the reset transaction
*/
void reset(IsolationLevel isolation);

private:
friend class TidesDB;
explicit Transaction(tidesdb_txn_t* txn) : txn_(txn)
Expand Down Expand Up @@ -579,6 +585,13 @@ class TidesDB
*/
void renameColumnFamily(const std::string& oldName, const std::string& newName);

/**
* @brief Clone a column family
* @param srcName Source column family name
* @param dstName Destination column family name
*/
void cloneColumnFamily(const std::string& srcName, const std::string& dstName);

/**
* @brief Create a backup of the database
* @param dir Backup directory (must be empty or non-existent)
Expand Down
12 changes: 12 additions & 0 deletions src/tidesdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,12 @@ Iterator Transaction::newIterator(ColumnFamily& cf)
return Iterator(iter);
}

void Transaction::reset(IsolationLevel isolation)
{
int result = tidesdb_txn_reset(txn_, static_cast<tidesdb_isolation_level_t>(isolation));
checkResult(result, "failed to reset transaction");
}

//-----------------------------------------------------------------------------
// TidesDB
//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -726,6 +732,12 @@ void TidesDB::renameColumnFamily(const std::string& oldName, const std::string&
checkResult(result, "failed to rename column family");
}

void TidesDB::cloneColumnFamily(const std::string& srcName, const std::string& dstName)
{
int result = tidesdb_clone_column_family(db_, srcName.c_str(), dstName.c_str());
checkResult(result, "failed to clone column family");
}

void TidesDB::backup(const std::string& dir)
{
int result = tidesdb_backup(db_, const_cast<char*>(dir.c_str()));
Expand Down
183 changes: 183 additions & 0 deletions tests/tidesdb_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,189 @@ TEST_F(TidesDBTest, BtreeStats)
ASSERT_GE(stats.btreeAvgHeight, 0.0);
}

TEST_F(TidesDBTest, CloneColumnFamily)
{
tidesdb::TidesDB db(getConfig());

auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig();
db.createColumnFamily("source_cf", cfConfig);

auto cf = db.getColumnFamily("source_cf");

// Write data to source
{
auto txn = db.beginTransaction();
txn.put(cf, "key1", "value1", -1);
txn.put(cf, "key2", "value2", -1);
txn.put(cf, "key3", "value3", -1);
txn.commit();
}

// Clone the column family
db.cloneColumnFamily("source_cf", "cloned_cf");

// Verify cloned CF exists and has the same data
auto clonedCf = db.getColumnFamily("cloned_cf");
{
auto txn = db.beginTransaction();
for (int i = 1; i <= 3; ++i)
{
std::string key = "key" + std::to_string(i);
std::string expectedValue = "value" + std::to_string(i);

auto value = txn.get(clonedCf, key);
std::string valueStr(value.begin(), value.end());
ASSERT_EQ(valueStr, expectedValue);
}
}

// Verify source CF still exists and is independent
auto sourceCf = db.getColumnFamily("source_cf");
{
auto txn = db.beginTransaction();
auto value = txn.get(sourceCf, "key1");
std::string valueStr(value.begin(), value.end());
ASSERT_EQ(valueStr, "value1");
}

// Verify independence: write to clone doesn't affect source
{
auto txn = db.beginTransaction();
txn.put(clonedCf, "clone_only_key", "clone_only_value", -1);
txn.commit();
}

{
auto txn = db.beginTransaction();
EXPECT_THROW(txn.get(sourceCf, "clone_only_key"), tidesdb::Exception);
}
}

TEST_F(TidesDBTest, CloneColumnFamilyErrors)
{
tidesdb::TidesDB db(getConfig());

auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig();
db.createColumnFamily("existing_cf", cfConfig);

// Clone non-existent source
EXPECT_THROW(db.cloneColumnFamily("nonexistent_cf", "new_cf"), tidesdb::Exception);

// Clone to existing destination
EXPECT_THROW(db.cloneColumnFamily("existing_cf", "existing_cf"), tidesdb::Exception);
}

TEST_F(TidesDBTest, TransactionReset)
{
tidesdb::TidesDB db(getConfig());

auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig();
db.createColumnFamily("test_cf", cfConfig);

auto cf = db.getColumnFamily("test_cf");

// Begin, use, commit, then reset and reuse
auto txn = db.beginTransaction();

// First batch of work
txn.put(cf, "key1", "value1", -1);
txn.commit();

// Reset instead of free + begin
txn.reset(tidesdb::IsolationLevel::ReadCommitted);

// Second batch of work using the same transaction
txn.put(cf, "key2", "value2", -1);
txn.commit();

// Verify both batches were committed
{
auto readTxn = db.beginTransaction();
auto value1 = readTxn.get(cf, "key1");
std::string value1Str(value1.begin(), value1.end());
ASSERT_EQ(value1Str, "value1");

auto value2 = readTxn.get(cf, "key2");
std::string value2Str(value2.begin(), value2.end());
ASSERT_EQ(value2Str, "value2");
}
}

TEST_F(TidesDBTest, TransactionResetWithDifferentIsolation)
{
tidesdb::TidesDB db(getConfig());

auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig();
db.createColumnFamily("test_cf", cfConfig);

auto cf = db.getColumnFamily("test_cf");

// Start with ReadCommitted
auto txn = db.beginTransaction(tidesdb::IsolationLevel::ReadCommitted);
txn.put(cf, "key1", "value1", -1);
txn.commit();

// Reset to RepeatableRead
txn.reset(tidesdb::IsolationLevel::RepeatableRead);
txn.put(cf, "key2", "value2", -1);
txn.commit();

// Reset to Snapshot
txn.reset(tidesdb::IsolationLevel::Snapshot);
txn.put(cf, "key3", "value3", -1);
txn.commit();

// Verify all writes succeeded
{
auto readTxn = db.beginTransaction();
for (int i = 1; i <= 3; ++i)
{
std::string key = "key" + std::to_string(i);
std::string expectedValue = "value" + std::to_string(i);

auto value = readTxn.get(cf, key);
std::string valueStr(value.begin(), value.end());
ASSERT_EQ(valueStr, expectedValue);
}
}
}

TEST_F(TidesDBTest, TransactionResetAfterRollback)
{
tidesdb::TidesDB db(getConfig());

auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig();
db.createColumnFamily("test_cf", cfConfig);

auto cf = db.getColumnFamily("test_cf");

auto txn = db.beginTransaction();

// Put and rollback
txn.put(cf, "rolled_back_key", "rolled_back_value", -1);
txn.rollback();

// Reset after rollback
txn.reset(tidesdb::IsolationLevel::ReadCommitted);

// New work after reset
txn.put(cf, "new_key", "new_value", -1);
txn.commit();

// Verify rolled back key doesn't exist, new key does
{
auto readTxn = db.beginTransaction();
EXPECT_THROW(readTxn.get(cf, "rolled_back_key"), tidesdb::Exception);
}

{
auto readTxn = db.beginTransaction();
auto value = readTxn.get(cf, "new_key");
std::string valueStr(value.begin(), value.end());
ASSERT_EQ(valueStr, "new_value");
}
}

int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
Expand Down
Loading