mirror of https://github.com/facebook/rocksdb.git
Make queries return the value of the default column for wide-column entities (#10483)
Summary: The patch adds support for wide-column entities to the existing query APIs (`Get`, `MultiGet`, and iterator). Namely, when during a query a wide-column entity is encountered, we will return the value of the default (anonymous) column as the result. Later, we plan to add wide-column specific query APIs which will enable retrieving entire wide-column entities or a subset of their columns. Pull Request resolved: https://github.com/facebook/rocksdb/pull/10483 Test Plan: `make check` Reviewed By: riversand963 Differential Revision: D38441881 Pulled By: ltamasi fbshipit-source-id: 6444e79a31aff2470e866698e3a97985bc2b3543
This commit is contained in:
parent
a85443c001
commit
24bcab7d5d
|
@ -17,6 +17,7 @@
|
||||||
#include "db/merge_context.h"
|
#include "db/merge_context.h"
|
||||||
#include "db/merge_helper.h"
|
#include "db/merge_helper.h"
|
||||||
#include "db/pinned_iterators_manager.h"
|
#include "db/pinned_iterators_manager.h"
|
||||||
|
#include "db/wide/wide_column_serialization.h"
|
||||||
#include "file/filename.h"
|
#include "file/filename.h"
|
||||||
#include "logging/logging.h"
|
#include "logging/logging.h"
|
||||||
#include "memory/arena.h"
|
#include "memory/arena.h"
|
||||||
|
@ -75,6 +76,7 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||||
verify_checksums_(read_options.verify_checksums),
|
verify_checksums_(read_options.verify_checksums),
|
||||||
expose_blob_index_(expose_blob_index),
|
expose_blob_index_(expose_blob_index),
|
||||||
is_blob_(false),
|
is_blob_(false),
|
||||||
|
is_wide_(false),
|
||||||
arena_mode_(arena_mode),
|
arena_mode_(arena_mode),
|
||||||
range_del_agg_(&ioptions.internal_comparator, s),
|
range_del_agg_(&ioptions.internal_comparator, s),
|
||||||
db_impl_(db_impl),
|
db_impl_(db_impl),
|
||||||
|
@ -132,6 +134,7 @@ void DBIter::Next() {
|
||||||
PERF_CPU_TIMER_GUARD(iter_next_cpu_nanos, clock_);
|
PERF_CPU_TIMER_GUARD(iter_next_cpu_nanos, clock_);
|
||||||
// Release temporarily pinned blocks from last operation
|
// Release temporarily pinned blocks from last operation
|
||||||
ReleaseTempPinnedData();
|
ReleaseTempPinnedData();
|
||||||
|
ResetWideColumnValue();
|
||||||
local_stats_.skip_count_ += num_internal_keys_skipped_;
|
local_stats_.skip_count_ += num_internal_keys_skipped_;
|
||||||
local_stats_.skip_count_--;
|
local_stats_.skip_count_--;
|
||||||
num_internal_keys_skipped_ = 0;
|
num_internal_keys_skipped_ = 0;
|
||||||
|
@ -174,6 +177,8 @@ void DBIter::Next() {
|
||||||
bool DBIter::SetBlobValueIfNeeded(const Slice& user_key,
|
bool DBIter::SetBlobValueIfNeeded(const Slice& user_key,
|
||||||
const Slice& blob_index) {
|
const Slice& blob_index) {
|
||||||
assert(!is_blob_);
|
assert(!is_blob_);
|
||||||
|
assert(!is_wide_);
|
||||||
|
assert(value_of_default_column_.empty());
|
||||||
|
|
||||||
if (expose_blob_index_) { // Stacked BlobDB implementation
|
if (expose_blob_index_) { // Stacked BlobDB implementation
|
||||||
is_blob_ = true;
|
is_blob_ = true;
|
||||||
|
@ -209,13 +214,24 @@ bool DBIter::SetBlobValueIfNeeded(const Slice& user_key,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DBIter::SetWideColumnValueIfNeeded(const Slice& /* wide_columns_slice */) {
|
bool DBIter::SetWideColumnValueIfNeeded(const Slice& wide_columns_slice) {
|
||||||
assert(!is_blob_);
|
assert(!is_blob_);
|
||||||
|
assert(!is_wide_);
|
||||||
|
assert(value_of_default_column_.empty());
|
||||||
|
|
||||||
// TODO: support wide-column entities
|
Slice wide_columns_copy = wide_columns_slice;
|
||||||
status_ = Status::NotSupported("Encountered unexpected wide-column entity");
|
|
||||||
valid_ = false;
|
const Status s = WideColumnSerialization::GetValueOfDefaultColumn(
|
||||||
return false;
|
wide_columns_copy, value_of_default_column_);
|
||||||
|
|
||||||
|
if (!s.ok()) {
|
||||||
|
status_ = s;
|
||||||
|
valid_ = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_wide_ = true;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PRE: saved_key_ has the current user key if skipping_saved_key
|
// PRE: saved_key_ has the current user key if skipping_saved_key
|
||||||
|
@ -265,6 +281,8 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||||
bool reseek_done = false;
|
bool reseek_done = false;
|
||||||
|
|
||||||
is_blob_ = false;
|
is_blob_ = false;
|
||||||
|
assert(!is_wide_);
|
||||||
|
assert(value_of_default_column_.empty());
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// Will update is_key_seqnum_zero_ as soon as we parsed the current key
|
// Will update is_key_seqnum_zero_ as soon as we parsed the current key
|
||||||
|
@ -592,7 +610,11 @@ bool DBIter::MergeValuesNewToOld() {
|
||||||
if (!s.ok()) {
|
if (!s.ok()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_blob_ = false;
|
is_blob_ = false;
|
||||||
|
assert(!is_wide_);
|
||||||
|
assert(value_of_default_column_.empty());
|
||||||
|
|
||||||
// iter_ is positioned after put
|
// iter_ is positioned after put
|
||||||
iter_.Next();
|
iter_.Next();
|
||||||
if (!iter_.status().ok()) {
|
if (!iter_.status().ok()) {
|
||||||
|
@ -638,6 +660,7 @@ void DBIter::Prev() {
|
||||||
|
|
||||||
PERF_CPU_TIMER_GUARD(iter_prev_cpu_nanos, clock_);
|
PERF_CPU_TIMER_GUARD(iter_prev_cpu_nanos, clock_);
|
||||||
ReleaseTempPinnedData();
|
ReleaseTempPinnedData();
|
||||||
|
ResetWideColumnValue();
|
||||||
ResetInternalKeysSkippedCounter();
|
ResetInternalKeysSkippedCounter();
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
if (direction_ == kForward) {
|
if (direction_ == kForward) {
|
||||||
|
@ -968,6 +991,9 @@ bool DBIter::FindValueForCurrentKey() {
|
||||||
Status s;
|
Status s;
|
||||||
s.PermitUncheckedError();
|
s.PermitUncheckedError();
|
||||||
is_blob_ = false;
|
is_blob_ = false;
|
||||||
|
assert(!is_wide_);
|
||||||
|
assert(value_of_default_column_.empty());
|
||||||
|
|
||||||
switch (last_key_entry_type) {
|
switch (last_key_entry_type) {
|
||||||
case kTypeDeletion:
|
case kTypeDeletion:
|
||||||
case kTypeDeletionWithTimestamp:
|
case kTypeDeletionWithTimestamp:
|
||||||
|
@ -1006,7 +1032,11 @@ bool DBIter::FindValueForCurrentKey() {
|
||||||
if (!s.ok()) {
|
if (!s.ok()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_blob_ = false;
|
is_blob_ = false;
|
||||||
|
assert(!is_wide_);
|
||||||
|
assert(value_of_default_column_.empty());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (last_not_merge_type == kTypeWideColumnEntity) {
|
} else if (last_not_merge_type == kTypeWideColumnEntity) {
|
||||||
// TODO: support wide-column entities
|
// TODO: support wide-column entities
|
||||||
|
@ -1082,6 +1112,9 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||||
// Find the next value that's visible.
|
// Find the next value that's visible.
|
||||||
ParsedInternalKey ikey;
|
ParsedInternalKey ikey;
|
||||||
is_blob_ = false;
|
is_blob_ = false;
|
||||||
|
assert(!is_wide_);
|
||||||
|
assert(value_of_default_column_.empty());
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!iter_.Valid()) {
|
if (!iter_.Valid()) {
|
||||||
valid_ = false;
|
valid_ = false;
|
||||||
|
@ -1216,7 +1249,11 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||||
if (!s.ok()) {
|
if (!s.ok()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_blob_ = false;
|
is_blob_ = false;
|
||||||
|
assert(!is_wide_);
|
||||||
|
assert(value_of_default_column_.empty());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (ikey.type == kTypeWideColumnEntity) {
|
} else if (ikey.type == kTypeWideColumnEntity) {
|
||||||
// TODO: support wide-column entities
|
// TODO: support wide-column entities
|
||||||
|
@ -1441,6 +1478,7 @@ void DBIter::Seek(const Slice& target) {
|
||||||
|
|
||||||
status_ = Status::OK();
|
status_ = Status::OK();
|
||||||
ReleaseTempPinnedData();
|
ReleaseTempPinnedData();
|
||||||
|
ResetWideColumnValue();
|
||||||
ResetInternalKeysSkippedCounter();
|
ResetInternalKeysSkippedCounter();
|
||||||
|
|
||||||
// Seek the inner iterator based on the target key.
|
// Seek the inner iterator based on the target key.
|
||||||
|
@ -1517,6 +1555,7 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||||
|
|
||||||
status_ = Status::OK();
|
status_ = Status::OK();
|
||||||
ReleaseTempPinnedData();
|
ReleaseTempPinnedData();
|
||||||
|
ResetWideColumnValue();
|
||||||
ResetInternalKeysSkippedCounter();
|
ResetInternalKeysSkippedCounter();
|
||||||
|
|
||||||
// Seek the inner iterator based on the target key.
|
// Seek the inner iterator based on the target key.
|
||||||
|
@ -1574,6 +1613,7 @@ void DBIter::SeekToFirst() {
|
||||||
status_ = Status::OK();
|
status_ = Status::OK();
|
||||||
direction_ = kForward;
|
direction_ = kForward;
|
||||||
ReleaseTempPinnedData();
|
ReleaseTempPinnedData();
|
||||||
|
ResetWideColumnValue();
|
||||||
ResetInternalKeysSkippedCounter();
|
ResetInternalKeysSkippedCounter();
|
||||||
ClearSavedValue();
|
ClearSavedValue();
|
||||||
is_key_seqnum_zero_ = false;
|
is_key_seqnum_zero_ = false;
|
||||||
|
@ -1621,6 +1661,7 @@ void DBIter::SeekToLast() {
|
||||||
*iterate_upper_bound_, /*a_has_ts=*/false, k,
|
*iterate_upper_bound_, /*a_has_ts=*/false, k,
|
||||||
/*b_has_ts=*/false)) {
|
/*b_has_ts=*/false)) {
|
||||||
ReleaseTempPinnedData();
|
ReleaseTempPinnedData();
|
||||||
|
ResetWideColumnValue();
|
||||||
PrevInternal(nullptr);
|
PrevInternal(nullptr);
|
||||||
|
|
||||||
k = key();
|
k = key();
|
||||||
|
@ -1640,6 +1681,7 @@ void DBIter::SeekToLast() {
|
||||||
status_ = Status::OK();
|
status_ = Status::OK();
|
||||||
direction_ = kReverse;
|
direction_ = kReverse;
|
||||||
ReleaseTempPinnedData();
|
ReleaseTempPinnedData();
|
||||||
|
ResetWideColumnValue();
|
||||||
ResetInternalKeysSkippedCounter();
|
ResetInternalKeysSkippedCounter();
|
||||||
ClearSavedValue();
|
ClearSavedValue();
|
||||||
is_key_seqnum_zero_ = false;
|
is_key_seqnum_zero_ = false;
|
||||||
|
|
10
db/db_iter.h
10
db/db_iter.h
|
@ -160,9 +160,12 @@ class DBIter final : public Iterator {
|
||||||
}
|
}
|
||||||
Slice value() const override {
|
Slice value() const override {
|
||||||
assert(valid_);
|
assert(valid_);
|
||||||
|
assert(!is_blob_ || !is_wide_);
|
||||||
|
|
||||||
if (!expose_blob_index_ && is_blob_) {
|
if (!expose_blob_index_ && is_blob_) {
|
||||||
return blob_value_;
|
return blob_value_;
|
||||||
|
} else if (is_wide_) {
|
||||||
|
return value_of_default_column_;
|
||||||
} else if (current_entry_is_merged_) {
|
} else if (current_entry_is_merged_) {
|
||||||
// If pinned_value_ is set then the result of merge operator is one of
|
// If pinned_value_ is set then the result of merge operator is one of
|
||||||
// the merge operands and we should return it.
|
// the merge operands and we should return it.
|
||||||
|
@ -302,6 +305,11 @@ class DBIter final : public Iterator {
|
||||||
|
|
||||||
bool SetWideColumnValueIfNeeded(const Slice& wide_columns_slice);
|
bool SetWideColumnValueIfNeeded(const Slice& wide_columns_slice);
|
||||||
|
|
||||||
|
void ResetWideColumnValue() {
|
||||||
|
is_wide_ = false;
|
||||||
|
value_of_default_column_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
Status Merge(const Slice* val, const Slice& user_key);
|
Status Merge(const Slice* val, const Slice& user_key);
|
||||||
|
|
||||||
const SliceTransform* prefix_extractor_;
|
const SliceTransform* prefix_extractor_;
|
||||||
|
@ -326,6 +334,7 @@ class DBIter final : public Iterator {
|
||||||
Slice pinned_value_;
|
Slice pinned_value_;
|
||||||
// for prefix seek mode to support prev()
|
// for prefix seek mode to support prev()
|
||||||
PinnableSlice blob_value_;
|
PinnableSlice blob_value_;
|
||||||
|
Slice value_of_default_column_;
|
||||||
Statistics* statistics_;
|
Statistics* statistics_;
|
||||||
uint64_t max_skip_;
|
uint64_t max_skip_;
|
||||||
uint64_t max_skippable_internal_keys_;
|
uint64_t max_skippable_internal_keys_;
|
||||||
|
@ -362,6 +371,7 @@ class DBIter final : public Iterator {
|
||||||
// the stacked BlobDB implementation is used, false otherwise.
|
// the stacked BlobDB implementation is used, false otherwise.
|
||||||
bool expose_blob_index_;
|
bool expose_blob_index_;
|
||||||
bool is_blob_;
|
bool is_blob_;
|
||||||
|
bool is_wide_;
|
||||||
bool arena_mode_;
|
bool arena_mode_;
|
||||||
// List of operands for merge operator.
|
// List of operands for merge operator.
|
||||||
MergeContext merge_context_;
|
MergeContext merge_context_;
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "db/pinned_iterators_manager.h"
|
#include "db/pinned_iterators_manager.h"
|
||||||
#include "db/range_tombstone_fragmenter.h"
|
#include "db/range_tombstone_fragmenter.h"
|
||||||
#include "db/read_callback.h"
|
#include "db/read_callback.h"
|
||||||
|
#include "db/wide/wide_column_serialization.h"
|
||||||
#include "logging/logging.h"
|
#include "logging/logging.h"
|
||||||
#include "memory/arena.h"
|
#include "memory/arena.h"
|
||||||
#include "memory/memory_usage.h"
|
#include "memory/memory_usage.h"
|
||||||
|
@ -786,12 +787,6 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||||
"Encounter unsupported blob value. Please open DB with "
|
"Encounter unsupported blob value. Please open DB with "
|
||||||
"ROCKSDB_NAMESPACE::blob_db::BlobDB instead.");
|
"ROCKSDB_NAMESPACE::blob_db::BlobDB instead.");
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
assert(type == kTypeWideColumnEntity);
|
|
||||||
|
|
||||||
// TODO: support wide-column entities
|
|
||||||
*(s->status) =
|
|
||||||
Status::NotSupported("Encountered unexpected wide-column entity");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!s->status->ok()) {
|
if (!s->status->ok()) {
|
||||||
|
@ -825,7 +820,17 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||||
merge_context->PushOperand(
|
merge_context->PushOperand(
|
||||||
v, s->inplace_update_support == false /* operand_pinned */);
|
v, s->inplace_update_support == false /* operand_pinned */);
|
||||||
} else if (s->value != nullptr) {
|
} else if (s->value != nullptr) {
|
||||||
s->value->assign(v.data(), v.size());
|
if (type != kTypeWideColumnEntity) {
|
||||||
|
assert(type == kTypeValue || type == kTypeBlobIndex);
|
||||||
|
s->value->assign(v.data(), v.size());
|
||||||
|
} else {
|
||||||
|
Slice value;
|
||||||
|
*(s->status) =
|
||||||
|
WideColumnSerialization::GetValueOfDefaultColumn(v, value);
|
||||||
|
if (s->status->ok()) {
|
||||||
|
s->value->assign(value.data(), value.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (s->inplace_update_support) {
|
if (s->inplace_update_support) {
|
||||||
s->mem->GetLock(s->key->user_key())->ReadUnlock();
|
s->mem->GetLock(s->key->user_key())->ReadUnlock();
|
||||||
|
|
|
@ -22,15 +22,90 @@ class DBWideBasicTest : public DBTestBase {
|
||||||
TEST_F(DBWideBasicTest, PutEntity) {
|
TEST_F(DBWideBasicTest, PutEntity) {
|
||||||
Options options = GetDefaultOptions();
|
Options options = GetDefaultOptions();
|
||||||
|
|
||||||
// Use the DB::PutEntity API
|
|
||||||
constexpr char first_key[] = "first";
|
constexpr char first_key[] = "first";
|
||||||
WideColumns first_columns{{"attr_name1", "foo"}, {"attr_name2", "bar"}};
|
constexpr char second_key[] = "second";
|
||||||
|
|
||||||
|
constexpr char first_value_of_default_column[] = "hello";
|
||||||
|
|
||||||
|
auto verify = [&]() {
|
||||||
|
{
|
||||||
|
PinnableSlice result;
|
||||||
|
ASSERT_OK(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), first_key,
|
||||||
|
&result));
|
||||||
|
ASSERT_EQ(result, first_value_of_default_column);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
PinnableSlice result;
|
||||||
|
ASSERT_OK(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), second_key,
|
||||||
|
&result));
|
||||||
|
ASSERT_TRUE(result.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
constexpr size_t num_keys = 2;
|
||||||
|
|
||||||
|
std::array<Slice, num_keys> keys{{first_key, second_key}};
|
||||||
|
std::array<PinnableSlice, num_keys> values;
|
||||||
|
std::array<Status, num_keys> statuses;
|
||||||
|
|
||||||
|
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
|
||||||
|
&keys[0], &values[0], &statuses[0]);
|
||||||
|
|
||||||
|
ASSERT_OK(statuses[0]);
|
||||||
|
ASSERT_EQ(values[0], first_value_of_default_column);
|
||||||
|
|
||||||
|
ASSERT_OK(statuses[1]);
|
||||||
|
ASSERT_TRUE(values[1].empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||||
|
|
||||||
|
iter->SeekToFirst();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), first_key);
|
||||||
|
ASSERT_EQ(iter->value(), first_value_of_default_column);
|
||||||
|
|
||||||
|
iter->Next();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), second_key);
|
||||||
|
ASSERT_TRUE(iter->value().empty());
|
||||||
|
|
||||||
|
iter->Next();
|
||||||
|
ASSERT_FALSE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
|
||||||
|
iter->SeekToLast();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), second_key);
|
||||||
|
ASSERT_TRUE(iter->value().empty());
|
||||||
|
|
||||||
|
iter->Prev();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), first_key);
|
||||||
|
ASSERT_EQ(iter->value(), first_value_of_default_column);
|
||||||
|
|
||||||
|
iter->Prev();
|
||||||
|
ASSERT_FALSE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use the DB::PutEntity API
|
||||||
|
WideColumns first_columns{
|
||||||
|
{kDefaultWideColumnName, first_value_of_default_column},
|
||||||
|
{"attr_name1", "foo"},
|
||||||
|
{"attr_name2", "bar"}};
|
||||||
|
|
||||||
ASSERT_OK(db_->PutEntity(WriteOptions(), db_->DefaultColumnFamily(),
|
ASSERT_OK(db_->PutEntity(WriteOptions(), db_->DefaultColumnFamily(),
|
||||||
first_key, first_columns));
|
first_key, first_columns));
|
||||||
|
|
||||||
// Use WriteBatch
|
// Use WriteBatch
|
||||||
constexpr char second_key[] = "second";
|
|
||||||
WideColumns second_columns{{"attr_one", "two"}, {"attr_three", "four"}};
|
WideColumns second_columns{{"attr_one", "two"}, {"attr_three", "four"}};
|
||||||
|
|
||||||
WriteBatch batch;
|
WriteBatch batch;
|
||||||
|
@ -38,7 +113,51 @@ TEST_F(DBWideBasicTest, PutEntity) {
|
||||||
batch.PutEntity(db_->DefaultColumnFamily(), second_key, second_columns));
|
batch.PutEntity(db_->DefaultColumnFamily(), second_key, second_columns));
|
||||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||||
|
|
||||||
// Note: currently, read APIs are supposed to return NotSupported
|
// Try reading from memtable
|
||||||
|
verify();
|
||||||
|
|
||||||
|
// Try reading after recovery
|
||||||
|
Close();
|
||||||
|
options.avoid_flush_during_recovery = true;
|
||||||
|
Reopen(options);
|
||||||
|
|
||||||
|
verify();
|
||||||
|
|
||||||
|
// Try reading from storage
|
||||||
|
ASSERT_OK(Flush());
|
||||||
|
|
||||||
|
verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DBWideBasicTest, PutEntityColumnFamily) {
|
||||||
|
Options options = GetDefaultOptions();
|
||||||
|
CreateAndReopenWithCF({"corinthian"}, options);
|
||||||
|
|
||||||
|
// Use the DB::PutEntity API
|
||||||
|
constexpr char first_key[] = "first";
|
||||||
|
WideColumns first_columns{{"attr_name1", "foo"}, {"attr_name2", "bar"}};
|
||||||
|
|
||||||
|
ASSERT_OK(
|
||||||
|
db_->PutEntity(WriteOptions(), handles_[1], first_key, first_columns));
|
||||||
|
|
||||||
|
// Use WriteBatch
|
||||||
|
constexpr char second_key[] = "second";
|
||||||
|
WideColumns second_columns{{"attr_one", "two"}, {"attr_three", "four"}};
|
||||||
|
|
||||||
|
WriteBatch batch;
|
||||||
|
ASSERT_OK(batch.PutEntity(handles_[1], second_key, second_columns));
|
||||||
|
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DBWideBasicTest, PutEntityMergeNotSupported) {
|
||||||
|
Options options = GetDefaultOptions();
|
||||||
|
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||||
|
Reopen(options);
|
||||||
|
|
||||||
|
constexpr char first_key[] = "first";
|
||||||
|
constexpr char second_key[] = "second";
|
||||||
|
|
||||||
|
// Note: Merge is currently not supported for wide-column entities
|
||||||
auto verify = [&]() {
|
auto verify = [&]() {
|
||||||
{
|
{
|
||||||
PinnableSlice result;
|
PinnableSlice result;
|
||||||
|
@ -84,26 +203,23 @@ TEST_F(DBWideBasicTest, PutEntity) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try reading from memtable
|
// Use the DB::PutEntity API
|
||||||
verify();
|
WideColumns first_columns{{"attr_name1", "foo"}, {"attr_name2", "bar"}};
|
||||||
|
|
||||||
// Try reading after recovery
|
ASSERT_OK(db_->PutEntity(WriteOptions(), db_->DefaultColumnFamily(),
|
||||||
Close();
|
first_key, first_columns));
|
||||||
options.avoid_flush_during_recovery = true;
|
|
||||||
Reopen(options);
|
|
||||||
|
|
||||||
verify();
|
// Use WriteBatch
|
||||||
|
WideColumns second_columns{{"attr_one", "two"}, {"attr_three", "four"}};
|
||||||
|
|
||||||
|
WriteBatch batch;
|
||||||
|
ASSERT_OK(
|
||||||
|
batch.PutEntity(db_->DefaultColumnFamily(), second_key, second_columns));
|
||||||
|
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||||
|
|
||||||
// Try reading from storage
|
|
||||||
ASSERT_OK(Flush());
|
ASSERT_OK(Flush());
|
||||||
|
|
||||||
verify();
|
|
||||||
|
|
||||||
// Add a couple of merge operands
|
// Add a couple of merge operands
|
||||||
Close();
|
|
||||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
|
||||||
Reopen(options);
|
|
||||||
|
|
||||||
constexpr char merge_operand[] = "bla";
|
constexpr char merge_operand[] = "bla";
|
||||||
|
|
||||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), first_key,
|
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), first_key,
|
||||||
|
@ -111,15 +227,15 @@ TEST_F(DBWideBasicTest, PutEntity) {
|
||||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), second_key,
|
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), second_key,
|
||||||
merge_operand));
|
merge_operand));
|
||||||
|
|
||||||
// Try reading from memtable
|
// Try reading when PutEntity is in storage, Merge is in memtable
|
||||||
verify();
|
verify();
|
||||||
|
|
||||||
// Try reading from storage
|
// Try reading when PutEntity and Merge are both in storage
|
||||||
ASSERT_OK(Flush());
|
ASSERT_OK(Flush());
|
||||||
|
|
||||||
verify();
|
verify();
|
||||||
|
|
||||||
// Do it again, with the Put and the Merge in the same memtable
|
// Try reading when PutEntity and Merge are both in memtable
|
||||||
ASSERT_OK(db_->PutEntity(WriteOptions(), db_->DefaultColumnFamily(),
|
ASSERT_OK(db_->PutEntity(WriteOptions(), db_->DefaultColumnFamily(),
|
||||||
first_key, first_columns));
|
first_key, first_columns));
|
||||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||||
|
@ -128,30 +244,9 @@ TEST_F(DBWideBasicTest, PutEntity) {
|
||||||
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), second_key,
|
ASSERT_OK(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), second_key,
|
||||||
merge_operand));
|
merge_operand));
|
||||||
|
|
||||||
// Try reading from memtable
|
|
||||||
verify();
|
verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DBWideBasicTest, PutEntityColumnFamily) {
|
|
||||||
Options options = GetDefaultOptions();
|
|
||||||
CreateAndReopenWithCF({"corinthian"}, options);
|
|
||||||
|
|
||||||
// Use the DB::PutEntity API
|
|
||||||
constexpr char first_key[] = "first";
|
|
||||||
WideColumns first_columns{{"attr_name1", "foo"}, {"attr_name2", "bar"}};
|
|
||||||
|
|
||||||
ASSERT_OK(
|
|
||||||
db_->PutEntity(WriteOptions(), handles_[1], first_key, first_columns));
|
|
||||||
|
|
||||||
// Use WriteBatch
|
|
||||||
constexpr char second_key[] = "second";
|
|
||||||
WideColumns second_columns{{"attr_one", "two"}, {"attr_three", "four"}};
|
|
||||||
|
|
||||||
WriteBatch batch;
|
|
||||||
ASSERT_OK(batch.PutEntity(handles_[1], second_key, second_columns));
|
|
||||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(DBWideBasicTest, PutEntityTimestampError) {
|
TEST_F(DBWideBasicTest, PutEntityTimestampError) {
|
||||||
// Note: timestamps are currently not supported
|
// Note: timestamps are currently not supported
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
namespace ROCKSDB_NAMESPACE {
|
namespace ROCKSDB_NAMESPACE {
|
||||||
|
|
||||||
|
const Slice kDefaultWideColumnName;
|
||||||
|
|
||||||
Status WideColumnSerialization::Serialize(const WideColumns& columns,
|
Status WideColumnSerialization::Serialize(const WideColumns& columns,
|
||||||
std::string& output) {
|
std::string& output) {
|
||||||
if (columns.size() >
|
if (columns.size() >
|
||||||
|
@ -137,4 +139,23 @@ WideColumns::const_iterator WideColumnSerialization::Find(
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Status WideColumnSerialization::GetValueOfDefaultColumn(Slice& input,
|
||||||
|
Slice& value) {
|
||||||
|
WideColumns columns;
|
||||||
|
|
||||||
|
const Status s = Deserialize(input, columns);
|
||||||
|
if (!s.ok()) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columns.empty() || columns[0].name() != kDefaultWideColumnName) {
|
||||||
|
value.clear();
|
||||||
|
return Status::OK();
|
||||||
|
}
|
||||||
|
|
||||||
|
value = columns[0].value();
|
||||||
|
|
||||||
|
return Status::OK();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ROCKSDB_NAMESPACE
|
} // namespace ROCKSDB_NAMESPACE
|
||||||
|
|
|
@ -48,6 +48,7 @@ class WideColumnSerialization {
|
||||||
|
|
||||||
static WideColumns::const_iterator Find(const WideColumns& columns,
|
static WideColumns::const_iterator Find(const WideColumns& columns,
|
||||||
const Slice& column_name);
|
const Slice& column_name);
|
||||||
|
static Status GetValueOfDefaultColumn(Slice& input, Slice& value);
|
||||||
|
|
||||||
static constexpr uint32_t kCurrentVersion = 1;
|
static constexpr uint32_t kCurrentVersion = 1;
|
||||||
};
|
};
|
||||||
|
|
|
@ -71,4 +71,6 @@ inline bool operator!=(const WideColumn& lhs, const WideColumn& rhs) {
|
||||||
|
|
||||||
using WideColumns = std::vector<WideColumn>;
|
using WideColumns = std::vector<WideColumn>;
|
||||||
|
|
||||||
|
extern const Slice kDefaultWideColumnName;
|
||||||
|
|
||||||
} // namespace ROCKSDB_NAMESPACE
|
} // namespace ROCKSDB_NAMESPACE
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "db/merge_helper.h"
|
#include "db/merge_helper.h"
|
||||||
#include "db/pinned_iterators_manager.h"
|
#include "db/pinned_iterators_manager.h"
|
||||||
#include "db/read_callback.h"
|
#include "db/read_callback.h"
|
||||||
|
#include "db/wide/wide_column_serialization.h"
|
||||||
#include "monitoring/file_read_sample.h"
|
#include "monitoring/file_read_sample.h"
|
||||||
#include "monitoring/perf_context_imp.h"
|
#include "monitoring/perf_context_imp.h"
|
||||||
#include "monitoring/statistics.h"
|
#include "monitoring/statistics.h"
|
||||||
|
@ -258,10 +259,6 @@ bool GetContext::SaveValue(const ParsedInternalKey& parsed_key,
|
||||||
state_ = kUnexpectedBlobIndex;
|
state_ = kUnexpectedBlobIndex;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (type == kTypeWideColumnEntity) {
|
|
||||||
// TODO: support wide-column entities
|
|
||||||
state_ = kUnexpectedWideColumnEntity;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_blob_index_ != nullptr) {
|
if (is_blob_index_ != nullptr) {
|
||||||
|
@ -272,14 +269,27 @@ bool GetContext::SaveValue(const ParsedInternalKey& parsed_key,
|
||||||
state_ = kFound;
|
state_ = kFound;
|
||||||
if (do_merge_) {
|
if (do_merge_) {
|
||||||
if (LIKELY(pinnable_val_ != nullptr)) {
|
if (LIKELY(pinnable_val_ != nullptr)) {
|
||||||
|
Slice value_to_use = value;
|
||||||
|
|
||||||
|
if (type == kTypeWideColumnEntity) {
|
||||||
|
Slice value_copy = value;
|
||||||
|
|
||||||
|
if (!WideColumnSerialization::GetValueOfDefaultColumn(
|
||||||
|
value_copy, value_to_use)
|
||||||
|
.ok()) {
|
||||||
|
state_ = kCorrupt;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (LIKELY(value_pinner != nullptr)) {
|
if (LIKELY(value_pinner != nullptr)) {
|
||||||
// If the backing resources for the value are provided, pin them
|
// If the backing resources for the value are provided, pin them
|
||||||
pinnable_val_->PinSlice(value, value_pinner);
|
pinnable_val_->PinSlice(value_to_use, value_pinner);
|
||||||
} else {
|
} else {
|
||||||
TEST_SYNC_POINT_CALLBACK("GetContext::SaveValue::PinSelf",
|
TEST_SYNC_POINT_CALLBACK("GetContext::SaveValue::PinSelf",
|
||||||
this);
|
this);
|
||||||
// Otherwise copy the value
|
// Otherwise copy the value
|
||||||
pinnable_val_->PinSelf(value);
|
pinnable_val_->PinSelf(value_to_use);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue