Summary: Major changes in this PR: * Implement CassandraCompactionFilter to remove expired columns and rows (if all column expired) * Move cassandra related code from utilities/merge_operators/cassandra to utilities/cassandra/* * Switch to use shared_ptr<> from uniqu_ptr for Column membership management in RowValue. Since columns do have multiple owners in Merge and GC process, use shared_ptr helps make RowValue immutable. * Rename cassandra_merge_test to cassandra_functional_test and add two TTL compaction related tests there. Closes https://github.com/facebook/rocksdb/pull/2588 Differential Revision: D5430010 Pulled By: wpc fbshipit-source-id: 9566c21e06de17491d486a68c70f52d501f27687main
parent
63163a8c6e
commit
534c255c7a
@ -0,0 +1,22 @@ |
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under both the GPLv2 (found in the
|
||||||
|
// COPYING file in the root directory) and Apache 2.0 License
|
||||||
|
// (found in the LICENSE.Apache file in the root directory).
|
||||||
|
|
||||||
|
#include <jni.h> |
||||||
|
|
||||||
|
#include "include/org_rocksdb_CassandraCompactionFilter.h" |
||||||
|
#include "utilities/cassandra/cassandra_compaction_filter.h" |
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: org_rocksdb_CassandraCompactionFilter |
||||||
|
* Method: createNewCassandraCompactionFilter0 |
||||||
|
* Signature: ()J |
||||||
|
*/ |
||||||
|
jlong Java_org_rocksdb_CassandraCompactionFilter_createNewCassandraCompactionFilter0( |
||||||
|
JNIEnv* env, jclass jcls, jboolean purge_ttl_on_expiration) { |
||||||
|
auto* compaction_filter = |
||||||
|
new rocksdb::cassandra::CassandraCompactionFilter(purge_ttl_on_expiration); |
||||||
|
// set the native handle to our native compaction filter
|
||||||
|
return reinterpret_cast<jlong>(compaction_filter); |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
|
||||||
|
package org.rocksdb; |
||||||
|
|
||||||
|
/** |
||||||
|
* Just a Java wrapper around CassandraCompactionFilter implemented in C++ |
||||||
|
*/ |
||||||
|
public class CassandraCompactionFilter |
||||||
|
extends AbstractCompactionFilter<Slice> { |
||||||
|
public CassandraCompactionFilter(boolean purgeTtlOnExpiration) { |
||||||
|
super(createNewCassandraCompactionFilter0(purgeTtlOnExpiration)); |
||||||
|
} |
||||||
|
|
||||||
|
private native static long createNewCassandraCompactionFilter0(boolean purgeTtlOnExpiration); |
||||||
|
} |
@ -0,0 +1,47 @@ |
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under both the GPLv2 (found in the
|
||||||
|
// COPYING file in the root directory) and Apache 2.0 License
|
||||||
|
// (found in the LICENSE.Apache file in the root directory).
|
||||||
|
|
||||||
|
#include "utilities/cassandra/cassandra_compaction_filter.h" |
||||||
|
#include <string> |
||||||
|
#include "rocksdb/slice.h" |
||||||
|
#include "utilities/cassandra/format.h" |
||||||
|
|
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
namespace cassandra { |
||||||
|
|
||||||
|
const char* CassandraCompactionFilter::Name() const { |
||||||
|
return "CassandraCompactionFilter"; |
||||||
|
} |
||||||
|
|
||||||
|
CompactionFilter::Decision CassandraCompactionFilter::FilterV2( |
||||||
|
int level, |
||||||
|
const Slice& key, |
||||||
|
ValueType value_type, |
||||||
|
const Slice& existing_value, |
||||||
|
std::string* new_value, |
||||||
|
std::string* skip_until) const { |
||||||
|
|
||||||
|
bool value_changed = false; |
||||||
|
RowValue row_value = RowValue::Deserialize( |
||||||
|
existing_value.data(), existing_value.size()); |
||||||
|
RowValue compacted = purge_ttl_on_expiration_ ? |
||||||
|
row_value.PurgeTtl(&value_changed) : |
||||||
|
row_value.ExpireTtl(&value_changed); |
||||||
|
|
||||||
|
if(compacted.Empty()) { |
||||||
|
return Decision::kRemove; |
||||||
|
} |
||||||
|
|
||||||
|
if (value_changed) { |
||||||
|
compacted.Serialize(new_value); |
||||||
|
return Decision::kChangeValue; |
||||||
|
} |
||||||
|
|
||||||
|
return Decision::kKeep; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace cassandra
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,39 @@ |
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under both the GPLv2 (found in the
|
||||||
|
// COPYING file in the root directory) and Apache 2.0 License
|
||||||
|
// (found in the LICENSE.Apache file in the root directory).
|
||||||
|
|
||||||
|
#pragma once |
||||||
|
#include <string> |
||||||
|
#include "rocksdb/compaction_filter.h" |
||||||
|
#include "rocksdb/slice.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
namespace cassandra { |
||||||
|
|
||||||
|
/**
|
||||||
|
* Compaction filter for removing expired Cassandra data with ttl. |
||||||
|
* If option `purge_ttl_on_expiration` is set to true, expired data |
||||||
|
* will be directly purged. Otherwise expired data will be converted |
||||||
|
* tombstones first, then be eventally removed after gc grace period.
|
||||||
|
* `purge_ttl_on_expiration` should only be on in the case all the
|
||||||
|
* writes have same ttl setting, otherwise it could bring old data back. |
||||||
|
*/ |
||||||
|
class CassandraCompactionFilter : public CompactionFilter { |
||||||
|
public: |
||||||
|
explicit CassandraCompactionFilter(bool purge_ttl_on_expiration) |
||||||
|
: purge_ttl_on_expiration_(purge_ttl_on_expiration) {} |
||||||
|
|
||||||
|
const char* Name() const override; |
||||||
|
virtual Decision FilterV2(int level, |
||||||
|
const Slice& key, |
||||||
|
ValueType value_type, |
||||||
|
const Slice& existing_value, |
||||||
|
std::string* new_value, |
||||||
|
std::string* skip_until) const override; |
||||||
|
|
||||||
|
private: |
||||||
|
bool purge_ttl_on_expiration_; |
||||||
|
}; |
||||||
|
} // namespace cassandra
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,251 @@ |
|||||||
|
// Copyright (c) 2017-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under both the GPLv2 (found in the
|
||||||
|
// COPYING file in the root directory) and Apache 2.0 License
|
||||||
|
// (found in the LICENSE.Apache file in the root directory).
|
||||||
|
|
||||||
|
#include <iostream> |
||||||
|
#include "rocksdb/db.h" |
||||||
|
#include "db/db_impl.h" |
||||||
|
#include "rocksdb/merge_operator.h" |
||||||
|
#include "rocksdb/utilities/db_ttl.h" |
||||||
|
#include "util/testharness.h" |
||||||
|
#include "util/random.h" |
||||||
|
#include "utilities/merge_operators.h" |
||||||
|
#include "utilities/cassandra/cassandra_compaction_filter.h" |
||||||
|
#include "utilities/cassandra/merge_operator.h" |
||||||
|
#include "utilities/cassandra/test_utils.h" |
||||||
|
|
||||||
|
using namespace rocksdb; |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
namespace cassandra { |
||||||
|
|
||||||
|
// Path to the database on file system
|
||||||
|
const std::string kDbName = test::TmpDir() + "/cassandra_functional_test"; |
||||||
|
|
||||||
|
class CassandraStore { |
||||||
|
public: |
||||||
|
explicit CassandraStore(std::shared_ptr<DB> db) |
||||||
|
: db_(db), |
||||||
|
merge_option_(), |
||||||
|
get_option_() { |
||||||
|
assert(db); |
||||||
|
} |
||||||
|
|
||||||
|
bool Append(const std::string& key, const RowValue& val){ |
||||||
|
std::string result; |
||||||
|
val.Serialize(&result); |
||||||
|
Slice valSlice(result.data(), result.size()); |
||||||
|
auto s = db_->Merge(merge_option_, key, valSlice); |
||||||
|
|
||||||
|
if (s.ok()) { |
||||||
|
return true; |
||||||
|
} else { |
||||||
|
std::cerr << "ERROR " << s.ToString() << std::endl; |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Flush() { |
||||||
|
dbfull()->TEST_FlushMemTable(); |
||||||
|
dbfull()->TEST_WaitForCompact(); |
||||||
|
} |
||||||
|
|
||||||
|
void Compact() { |
||||||
|
dbfull()->TEST_CompactRange( |
||||||
|
0, nullptr, nullptr, db_->DefaultColumnFamily()); |
||||||
|
} |
||||||
|
|
||||||
|
std::tuple<bool, RowValue> Get(const std::string& key){ |
||||||
|
std::string result; |
||||||
|
auto s = db_->Get(get_option_, key, &result); |
||||||
|
|
||||||
|
if (s.ok()) { |
||||||
|
return std::make_tuple(true, |
||||||
|
RowValue::Deserialize(result.data(), |
||||||
|
result.size())); |
||||||
|
} |
||||||
|
|
||||||
|
if (!s.IsNotFound()) { |
||||||
|
std::cerr << "ERROR " << s.ToString() << std::endl; |
||||||
|
} |
||||||
|
|
||||||
|
return std::make_tuple(false, RowValue(0, 0)); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
std::shared_ptr<DB> db_; |
||||||
|
WriteOptions merge_option_; |
||||||
|
ReadOptions get_option_; |
||||||
|
|
||||||
|
DBImpl* dbfull() { return reinterpret_cast<DBImpl*>(db_.get()); } |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
class TestCompactionFilterFactory : public CompactionFilterFactory { |
||||||
|
public: |
||||||
|
explicit TestCompactionFilterFactory(bool purge_ttl_on_expiration) |
||||||
|
: purge_ttl_on_expiration_(purge_ttl_on_expiration) {} |
||||||
|
|
||||||
|
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter( |
||||||
|
const CompactionFilter::Context& context) override { |
||||||
|
return unique_ptr<CompactionFilter>(new CassandraCompactionFilter(purge_ttl_on_expiration_)); |
||||||
|
} |
||||||
|
|
||||||
|
virtual const char* Name() const override { |
||||||
|
return "TestCompactionFilterFactory"; |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
bool purge_ttl_on_expiration_; |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
// The class for unit-testing
|
||||||
|
class CassandraFunctionalTest : public testing::Test { |
||||||
|
public: |
||||||
|
CassandraFunctionalTest() { |
||||||
|
DestroyDB(kDbName, Options()); // Start each test with a fresh DB
|
||||||
|
} |
||||||
|
|
||||||
|
std::shared_ptr<DB> OpenDb() { |
||||||
|
DB* db; |
||||||
|
Options options; |
||||||
|
options.create_if_missing = true; |
||||||
|
options.merge_operator.reset(new CassandraValueMergeOperator()); |
||||||
|
auto* cf_factory = new TestCompactionFilterFactory(purge_ttl_on_expiration_); |
||||||
|
options.compaction_filter_factory.reset(cf_factory); |
||||||
|
EXPECT_OK(DB::Open(options, kDbName, &db)); |
||||||
|
return std::shared_ptr<DB>(db); |
||||||
|
} |
||||||
|
|
||||||
|
bool purge_ttl_on_expiration_ = false; |
||||||
|
}; |
||||||
|
|
||||||
|
// THE TEST CASES BEGIN HERE
|
||||||
|
|
||||||
|
TEST_F(CassandraFunctionalTest, SimpleMergeTest) { |
||||||
|
CassandraStore store(OpenDb()); |
||||||
|
|
||||||
|
store.Append("k1", CreateTestRowValue({ |
||||||
|
std::make_tuple(kTombstone, 0, 5), |
||||||
|
std::make_tuple(kColumn, 1, 8), |
||||||
|
std::make_tuple(kExpiringColumn, 2, 5), |
||||||
|
})); |
||||||
|
store.Append("k1",CreateTestRowValue({ |
||||||
|
std::make_tuple(kColumn, 0, 2), |
||||||
|
std::make_tuple(kExpiringColumn, 1, 5), |
||||||
|
std::make_tuple(kTombstone, 2, 7), |
||||||
|
std::make_tuple(kExpiringColumn, 7, 17), |
||||||
|
})); |
||||||
|
store.Append("k1", CreateTestRowValue({ |
||||||
|
std::make_tuple(kExpiringColumn, 0, 6), |
||||||
|
std::make_tuple(kTombstone, 1, 5), |
||||||
|
std::make_tuple(kColumn, 2, 4), |
||||||
|
std::make_tuple(kTombstone, 11, 11), |
||||||
|
})); |
||||||
|
|
||||||
|
auto ret = store.Get("k1"); |
||||||
|
|
||||||
|
ASSERT_TRUE(std::get<0>(ret)); |
||||||
|
RowValue& merged = std::get<1>(ret); |
||||||
|
EXPECT_EQ(merged.columns_.size(), 5); |
||||||
|
VerifyRowValueColumns(merged.columns_, 0, kExpiringColumn, 0, 6); |
||||||
|
VerifyRowValueColumns(merged.columns_, 1, kColumn, 1, 8); |
||||||
|
VerifyRowValueColumns(merged.columns_, 2, kTombstone, 2, 7); |
||||||
|
VerifyRowValueColumns(merged.columns_, 3, kExpiringColumn, 7, 17); |
||||||
|
VerifyRowValueColumns(merged.columns_, 4, kTombstone, 11, 11); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(CassandraFunctionalTest, |
||||||
|
CompactionShouldConvertExpiredColumnsToTombstone) { |
||||||
|
CassandraStore store(OpenDb()); |
||||||
|
int64_t now= time(nullptr); |
||||||
|
|
||||||
|
store.Append("k1", CreateTestRowValue({ |
||||||
|
std::make_tuple(kExpiringColumn, 0, ToMicroSeconds(now - kTtl - 20)), //expired
|
||||||
|
std::make_tuple(kExpiringColumn, 1, ToMicroSeconds(now - kTtl + 10)), // not expired
|
||||||
|
std::make_tuple(kTombstone, 3, ToMicroSeconds(now)) |
||||||
|
})); |
||||||
|
|
||||||
|
store.Flush(); |
||||||
|
|
||||||
|
store.Append("k1",CreateTestRowValue({ |
||||||
|
std::make_tuple(kExpiringColumn, 0, ToMicroSeconds(now - kTtl - 10)), //expired
|
||||||
|
std::make_tuple(kColumn, 2, ToMicroSeconds(now)) |
||||||
|
})); |
||||||
|
|
||||||
|
store.Flush(); |
||||||
|
store.Compact(); |
||||||
|
|
||||||
|
auto ret = store.Get("k1"); |
||||||
|
ASSERT_TRUE(std::get<0>(ret)); |
||||||
|
RowValue& merged = std::get<1>(ret); |
||||||
|
EXPECT_EQ(merged.columns_.size(), 4); |
||||||
|
VerifyRowValueColumns(merged.columns_, 0, kTombstone, 0, ToMicroSeconds(now - 10)); |
||||||
|
VerifyRowValueColumns(merged.columns_, 1, kExpiringColumn, 1, ToMicroSeconds(now - kTtl + 10)); |
||||||
|
VerifyRowValueColumns(merged.columns_, 2, kColumn, 2, ToMicroSeconds(now)); |
||||||
|
VerifyRowValueColumns(merged.columns_, 3, kTombstone, 3, ToMicroSeconds(now)); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
TEST_F(CassandraFunctionalTest, |
||||||
|
CompactionShouldPurgeExpiredColumnsIfPurgeTtlIsOn) { |
||||||
|
purge_ttl_on_expiration_ = true; |
||||||
|
CassandraStore store(OpenDb()); |
||||||
|
int64_t now = time(nullptr); |
||||||
|
|
||||||
|
store.Append("k1", CreateTestRowValue({ |
||||||
|
std::make_tuple(kExpiringColumn, 0, ToMicroSeconds(now - kTtl - 20)), //expired
|
||||||
|
std::make_tuple(kExpiringColumn, 1, ToMicroSeconds(now)), // not expired
|
||||||
|
std::make_tuple(kTombstone, 3, ToMicroSeconds(now)) |
||||||
|
})); |
||||||
|
|
||||||
|
store.Flush(); |
||||||
|
|
||||||
|
store.Append("k1",CreateTestRowValue({ |
||||||
|
std::make_tuple(kExpiringColumn, 0, ToMicroSeconds(now - kTtl - 10)), //expired
|
||||||
|
std::make_tuple(kColumn, 2, ToMicroSeconds(now)) |
||||||
|
})); |
||||||
|
|
||||||
|
store.Flush(); |
||||||
|
store.Compact(); |
||||||
|
|
||||||
|
auto ret = store.Get("k1"); |
||||||
|
ASSERT_TRUE(std::get<0>(ret)); |
||||||
|
RowValue& merged = std::get<1>(ret); |
||||||
|
EXPECT_EQ(merged.columns_.size(), 3); |
||||||
|
VerifyRowValueColumns(merged.columns_, 0, kExpiringColumn, 1, ToMicroSeconds(now)); |
||||||
|
VerifyRowValueColumns(merged.columns_, 1, kColumn, 2, ToMicroSeconds(now)); |
||||||
|
VerifyRowValueColumns(merged.columns_, 2, kTombstone, 3, ToMicroSeconds(now)); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(CassandraFunctionalTest, |
||||||
|
CompactionShouldRemoveRowWhenAllColumnsExpiredIfPurgeTtlIsOn) { |
||||||
|
purge_ttl_on_expiration_ = true; |
||||||
|
CassandraStore store(OpenDb()); |
||||||
|
int64_t now = time(nullptr); |
||||||
|
|
||||||
|
store.Append("k1", CreateTestRowValue({ |
||||||
|
std::make_tuple(kExpiringColumn, 0, ToMicroSeconds(now - kTtl - 20)), |
||||||
|
std::make_tuple(kExpiringColumn, 1, ToMicroSeconds(now - kTtl - 20)), |
||||||
|
})); |
||||||
|
|
||||||
|
store.Flush(); |
||||||
|
|
||||||
|
store.Append("k1",CreateTestRowValue({ |
||||||
|
std::make_tuple(kExpiringColumn, 0, ToMicroSeconds(now - kTtl - 10)), |
||||||
|
})); |
||||||
|
|
||||||
|
store.Flush(); |
||||||
|
store.Compact(); |
||||||
|
ASSERT_FALSE(std::get<0>(store.Get("k1"))); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace cassandra
|
||||||
|
} // namespace rocksdb
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
::testing::InitGoogleTest(&argc, argv); |
||||||
|
return RUN_ALL_TESTS(); |
||||||
|
} |
@ -1,134 +0,0 @@ |
|||||||
// Copyright (c) 2017-present, Facebook, Inc. All rights reserved.
|
|
||||||
// This source code is licensed under both the GPLv2 (found in the
|
|
||||||
// COPYING file in the root directory) and Apache 2.0 License
|
|
||||||
// (found in the LICENSE.Apache file in the root directory).
|
|
||||||
// This source code is also licensed under the GPLv2 license found in the
|
|
||||||
// COPYING file in the root directory of this source tree.
|
|
||||||
|
|
||||||
#include <iostream> |
|
||||||
|
|
||||||
#include "rocksdb/db.h" |
|
||||||
#include "rocksdb/merge_operator.h" |
|
||||||
#include "rocksdb/utilities/db_ttl.h" |
|
||||||
#include "util/testharness.h" |
|
||||||
#include "util/random.h" |
|
||||||
#include "utilities/merge_operators.h" |
|
||||||
#include "utilities/merge_operators/cassandra/merge_operator.h" |
|
||||||
#include "utilities/merge_operators/cassandra/test_utils.h" |
|
||||||
|
|
||||||
using namespace rocksdb; |
|
||||||
|
|
||||||
namespace rocksdb { |
|
||||||
namespace cassandra { |
|
||||||
|
|
||||||
// Path to the database on file system
|
|
||||||
const std::string kDbName = test::TmpDir() + "/cassandramerge_test"; |
|
||||||
|
|
||||||
class CassandraStore { |
|
||||||
public: |
|
||||||
explicit CassandraStore(std::shared_ptr<DB> db) |
|
||||||
: db_(db), |
|
||||||
merge_option_(), |
|
||||||
get_option_() { |
|
||||||
assert(db); |
|
||||||
} |
|
||||||
|
|
||||||
bool Append(const std::string& key, const RowValue& val){ |
|
||||||
std::string result; |
|
||||||
val.Serialize(&result); |
|
||||||
Slice valSlice(result.data(), result.size()); |
|
||||||
auto s = db_->Merge(merge_option_, key, valSlice); |
|
||||||
|
|
||||||
if (s.ok()) { |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
std::cerr << "ERROR " << s.ToString() << std::endl; |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
std::tuple<bool, RowValue> Get(const std::string& key){ |
|
||||||
std::string result; |
|
||||||
auto s = db_->Get(get_option_, key, &result); |
|
||||||
|
|
||||||
if (s.ok()) { |
|
||||||
return std::make_tuple(true, |
|
||||||
RowValue::Deserialize(result.data(), |
|
||||||
result.size())); |
|
||||||
} |
|
||||||
|
|
||||||
if (!s.IsNotFound()) { |
|
||||||
std::cerr << "ERROR " << s.ToString() << std::endl; |
|
||||||
} |
|
||||||
|
|
||||||
return std::make_tuple(false, RowValue(0, 0)); |
|
||||||
} |
|
||||||
|
|
||||||
private: |
|
||||||
std::shared_ptr<DB> db_; |
|
||||||
WriteOptions merge_option_; |
|
||||||
ReadOptions get_option_; |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
// The class for unit-testing
|
|
||||||
class CassandraMergeTest : public testing::Test { |
|
||||||
public: |
|
||||||
CassandraMergeTest() { |
|
||||||
DestroyDB(kDbName, Options()); // Start each test with a fresh DB
|
|
||||||
} |
|
||||||
|
|
||||||
std::shared_ptr<DB> OpenDb() { |
|
||||||
DB* db; |
|
||||||
Options options; |
|
||||||
options.create_if_missing = true; |
|
||||||
options.merge_operator.reset(new CassandraValueMergeOperator()); |
|
||||||
EXPECT_OK(DB::Open(options, kDbName, &db)); |
|
||||||
return std::shared_ptr<DB>(db); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// THE TEST CASES BEGIN HERE
|
|
||||||
|
|
||||||
TEST_F(CassandraMergeTest, SimpleTest) { |
|
||||||
auto db = OpenDb(); |
|
||||||
CassandraStore store(db); |
|
||||||
|
|
||||||
store.Append("k1", CreateTestRowValue({ |
|
||||||
std::make_tuple(kTombstone, 0, 5), |
|
||||||
std::make_tuple(kColumn, 1, 8), |
|
||||||
std::make_tuple(kExpiringColumn, 2, 5), |
|
||||||
})); |
|
||||||
store.Append("k1",CreateTestRowValue({ |
|
||||||
std::make_tuple(kColumn, 0, 2), |
|
||||||
std::make_tuple(kExpiringColumn, 1, 5), |
|
||||||
std::make_tuple(kTombstone, 2, 7), |
|
||||||
std::make_tuple(kExpiringColumn, 7, 17), |
|
||||||
})); |
|
||||||
store.Append("k1", CreateTestRowValue({ |
|
||||||
std::make_tuple(kExpiringColumn, 0, 6), |
|
||||||
std::make_tuple(kTombstone, 1, 5), |
|
||||||
std::make_tuple(kColumn, 2, 4), |
|
||||||
std::make_tuple(kTombstone, 11, 11), |
|
||||||
})); |
|
||||||
|
|
||||||
auto ret = store.Get("k1"); |
|
||||||
|
|
||||||
ASSERT_TRUE(std::get<0>(ret)); |
|
||||||
RowValue& merged = std::get<1>(ret); |
|
||||||
EXPECT_EQ(merged.columns_.size(), 5); |
|
||||||
VerifyRowValueColumns(merged.columns_, 0, kExpiringColumn, 0, 6); |
|
||||||
VerifyRowValueColumns(merged.columns_, 1, kColumn, 1, 8); |
|
||||||
VerifyRowValueColumns(merged.columns_, 2, kTombstone, 2, 7); |
|
||||||
VerifyRowValueColumns(merged.columns_, 3, kExpiringColumn, 7, 17); |
|
||||||
VerifyRowValueColumns(merged.columns_, 4, kTombstone, 11, 11); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} // namespace cassandra
|
|
||||||
} // namespace rocksdb
|
|
||||||
|
|
||||||
int main(int argc, char** argv) { |
|
||||||
::testing::InitGoogleTest(&argc, argv); |
|
||||||
return RUN_ALL_TESTS(); |
|
||||||
} |
|
Loading…
Reference in new issue