mirror of https://github.com/facebook/rocksdb.git
Call PrepareValue on the base iterator in BaseDeltaIterator (#13105)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/13105 The `WriteBatchWithIndex::NewIteratorWithBase` interface enables creating a `BaseDeltaIterator` with an arbitrary base iterator passed in by the client, which has potentially been created with the `allow_unprepared_value` read option set. Because of this, `BaseDeltaIterator` has to call `PrepareValue` before using the `value()` or `columns()` from the base iterator. This includes both the case when `BaseDeltaIterator` exposes the `value()` and `columns()` of the base iterator as is and the case when the final `value()` / `columns()` is a result of merging key-values across the base and delta iterators. Note that `BaseDeltaIterator` itself does not support `allow_unprepared_value` yet; this will be implemented in an upcoming patch. Reviewed By: jowlyzhang Differential Revision: D65249643 fbshipit-source-id: b0a1ccc0dfd31105b2eef167b463ed15a8bb83b7
This commit is contained in:
parent
1987313a94
commit
ef535039f3
|
@ -0,0 +1 @@
|
||||||
|
`BaseDeltaIterator` now calls `PrepareValue` on the base iterator in case it has been created with the `allow_unprepared_value` read option set. Earlier, such base iterators could lead to incorrect values being exposed from `BaseDeltaIterator`.
|
|
@ -262,6 +262,17 @@ void BaseDeltaIterator::SetValueAndColumnsFromBase() {
|
||||||
assert(value_.empty());
|
assert(value_.empty());
|
||||||
assert(columns_.empty());
|
assert(columns_.empty());
|
||||||
|
|
||||||
|
if (!base_iterator_->PrepareValue()) {
|
||||||
|
assert(!BaseValid());
|
||||||
|
assert(!base_iterator_->status().ok());
|
||||||
|
|
||||||
|
Invalidate(base_iterator_->status());
|
||||||
|
|
||||||
|
assert(!Valid());
|
||||||
|
assert(!status().ok());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
value_ = base_iterator_->value();
|
value_ = base_iterator_->value();
|
||||||
columns_ = base_iterator_->columns();
|
columns_ = base_iterator_->columns();
|
||||||
}
|
}
|
||||||
|
@ -314,6 +325,17 @@ void BaseDeltaIterator::SetValueAndColumnsFromDelta() {
|
||||||
/* result_operand */ nullptr, &result_type);
|
/* result_operand */ nullptr, &result_type);
|
||||||
} else if (delta_entry.type == kMergeRecord) {
|
} else if (delta_entry.type == kMergeRecord) {
|
||||||
if (equal_keys_) {
|
if (equal_keys_) {
|
||||||
|
if (!base_iterator_->PrepareValue()) {
|
||||||
|
assert(!BaseValid());
|
||||||
|
assert(!base_iterator_->status().ok());
|
||||||
|
|
||||||
|
Invalidate(base_iterator_->status());
|
||||||
|
|
||||||
|
assert(!Valid());
|
||||||
|
assert(!status().ok());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (WideColumnsHelper::HasDefaultColumnOnly(base_iterator_->columns())) {
|
if (WideColumnsHelper::HasDefaultColumnOnly(base_iterator_->columns())) {
|
||||||
status_ = WriteBatchWithIndexInternal::MergeKeyWithBaseValue(
|
status_ = WriteBatchWithIndexInternal::MergeKeyWithBaseValue(
|
||||||
column_family_, delta_entry.key, MergeHelper::kPlainBaseValue,
|
column_family_, delta_entry.key, MergeHelper::kPlainBaseValue,
|
||||||
|
|
|
@ -81,73 +81,116 @@ using KVMap = std::map<std::string, std::string>;
|
||||||
|
|
||||||
class KVIter : public Iterator {
|
class KVIter : public Iterator {
|
||||||
public:
|
public:
|
||||||
explicit KVIter(const KVMap* map) : map_(map), iter_(map_->end()) {}
|
explicit KVIter(const KVMap* map, bool allow_unprepared_value = false,
|
||||||
|
bool fail_prepare_value = false)
|
||||||
|
: map_(map),
|
||||||
|
iter_(map_->end()),
|
||||||
|
allow_unprepared_value_(allow_unprepared_value),
|
||||||
|
fail_prepare_value_(fail_prepare_value) {}
|
||||||
|
|
||||||
bool Valid() const override { return iter_ != map_->end(); }
|
bool Valid() const override { return status_.ok() && iter_ != map_->end(); }
|
||||||
|
|
||||||
void SeekToFirst() override {
|
void SeekToFirst() override {
|
||||||
|
status_ = Status::OK();
|
||||||
|
Reset();
|
||||||
|
|
||||||
iter_ = map_->begin();
|
iter_ = map_->begin();
|
||||||
|
|
||||||
if (Valid()) {
|
if (Valid() && !allow_unprepared_value_) {
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SeekToLast() override {
|
void SeekToLast() override {
|
||||||
|
status_ = Status::OK();
|
||||||
|
Reset();
|
||||||
|
|
||||||
if (map_->empty()) {
|
if (map_->empty()) {
|
||||||
iter_ = map_->end();
|
iter_ = map_->end();
|
||||||
} else {
|
} else {
|
||||||
iter_ = map_->find(map_->rbegin()->first);
|
iter_ = map_->find(map_->rbegin()->first);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Valid()) {
|
if (Valid() && !allow_unprepared_value_) {
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Seek(const Slice& k) override {
|
void Seek(const Slice& k) override {
|
||||||
|
status_ = Status::OK();
|
||||||
|
Reset();
|
||||||
|
|
||||||
iter_ = map_->lower_bound(k.ToString());
|
iter_ = map_->lower_bound(k.ToString());
|
||||||
|
|
||||||
if (Valid()) {
|
if (Valid() && !allow_unprepared_value_) {
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SeekForPrev(const Slice& k) override {
|
void SeekForPrev(const Slice& k) override {
|
||||||
|
status_ = Status::OK();
|
||||||
|
Reset();
|
||||||
|
|
||||||
iter_ = map_->upper_bound(k.ToString());
|
iter_ = map_->upper_bound(k.ToString());
|
||||||
Prev();
|
Prev();
|
||||||
|
|
||||||
if (Valid()) {
|
if (Valid() && !allow_unprepared_value_) {
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Next() override {
|
void Next() override {
|
||||||
|
Reset();
|
||||||
|
|
||||||
++iter_;
|
++iter_;
|
||||||
|
|
||||||
if (Valid()) {
|
if (Valid() && !allow_unprepared_value_) {
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Prev() override {
|
void Prev() override {
|
||||||
|
Reset();
|
||||||
|
|
||||||
if (iter_ == map_->begin()) {
|
if (iter_ == map_->begin()) {
|
||||||
iter_ = map_->end();
|
iter_ = map_->end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
--iter_;
|
--iter_;
|
||||||
|
|
||||||
if (Valid()) {
|
if (Valid() && !allow_unprepared_value_) {
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PrepareValue() override {
|
||||||
|
assert(Valid());
|
||||||
|
|
||||||
|
if (!allow_unprepared_value_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fail_prepare_value_) {
|
||||||
|
status_ = Status::Corruption("PrepareValue() failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Update();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Slice key() const override { return iter_->first; }
|
Slice key() const override { return iter_->first; }
|
||||||
Slice value() const override { return value_; }
|
Slice value() const override { return value_; }
|
||||||
const WideColumns& columns() const override { return columns_; }
|
const WideColumns& columns() const override { return columns_; }
|
||||||
Status status() const override { return Status::OK(); }
|
Status status() const override { return status_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void Reset() {
|
||||||
|
value_.clear();
|
||||||
|
columns_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void Update() {
|
void Update() {
|
||||||
assert(Valid());
|
assert(Valid());
|
||||||
|
|
||||||
|
@ -157,8 +200,11 @@ class KVIter : public Iterator {
|
||||||
|
|
||||||
const KVMap* const map_;
|
const KVMap* const map_;
|
||||||
KVMap::const_iterator iter_;
|
KVMap::const_iterator iter_;
|
||||||
|
Status status_;
|
||||||
Slice value_;
|
Slice value_;
|
||||||
WideColumns columns_;
|
WideColumns columns_;
|
||||||
|
bool allow_unprepared_value_;
|
||||||
|
bool fail_prepare_value_;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::string PrintContents(WriteBatchWithIndex* batch,
|
static std::string PrintContents(WriteBatchWithIndex* batch,
|
||||||
|
@ -1693,6 +1739,182 @@ TEST_P(WriteBatchWithIndexTest, TestNewIteratorWithBaseFromWbwi) {
|
||||||
ASSERT_OK(iter->status());
|
ASSERT_OK(iter->status());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_P(WriteBatchWithIndexTest, NewIteratorWithBasePrepareValue) {
|
||||||
|
// BaseDeltaIterator by default should call PrepareValue if it lands on the
|
||||||
|
// base iterator in case it was created with allow_unprepared_value=true.
|
||||||
|
// (Note that BaseDeltaIterator itself does not support allow_unprepared_value
|
||||||
|
// yet but the base iterator might have been created with the flag set.)
|
||||||
|
ColumnFamilyHandleImplDummy cf1(1, BytewiseComparator());
|
||||||
|
KVMap map{{"a", "aa"}, {"c", "cc"}, {"e", "ee"}};
|
||||||
|
|
||||||
|
ASSERT_OK(batch_->Put(&cf1, "c", "cc1"));
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_ptr<Iterator> iter(batch_->NewIteratorWithBase(
|
||||||
|
&cf1, new KVIter(&map, /* allow_unprepared_value */ true)));
|
||||||
|
|
||||||
|
iter->SeekToFirst();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), "a");
|
||||||
|
ASSERT_EQ(iter->value(), "aa");
|
||||||
|
|
||||||
|
iter->Next();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), "c");
|
||||||
|
ASSERT_EQ(iter->value(), "cc1");
|
||||||
|
|
||||||
|
iter->Next();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), "e");
|
||||||
|
ASSERT_EQ(iter->value(), "ee");
|
||||||
|
|
||||||
|
iter->Next();
|
||||||
|
ASSERT_FALSE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
|
||||||
|
iter->SeekToLast();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), "e");
|
||||||
|
ASSERT_EQ(iter->value(), "ee");
|
||||||
|
|
||||||
|
iter->Prev();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), "c");
|
||||||
|
ASSERT_EQ(iter->value(), "cc1");
|
||||||
|
|
||||||
|
iter->Prev();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), "a");
|
||||||
|
ASSERT_EQ(iter->value(), "aa");
|
||||||
|
|
||||||
|
iter->Prev();
|
||||||
|
ASSERT_FALSE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareValue failures from the base iterator should be propagated
|
||||||
|
{
|
||||||
|
std::unique_ptr<Iterator> iter(batch_->NewIteratorWithBase(
|
||||||
|
&cf1, new KVIter(&map, /* allow_unprepared_value */ true,
|
||||||
|
/* fail_prepare_value */ true)));
|
||||||
|
|
||||||
|
iter->SeekToFirst();
|
||||||
|
ASSERT_FALSE(iter->Valid());
|
||||||
|
ASSERT_TRUE(iter->status().IsCorruption());
|
||||||
|
|
||||||
|
iter->SeekToLast();
|
||||||
|
ASSERT_FALSE(iter->Valid());
|
||||||
|
ASSERT_TRUE(iter->status().IsCorruption());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(WriteBatchWithIndexTest, NewIteratorWithBaseMergePrepareValue) {
|
||||||
|
// When performing a merge across the base and delta iterators,
|
||||||
|
// BaseDeltaIterator should call PrepareValue on the base iterator in case it
|
||||||
|
// was created with allow_unprepared_value=true. (Note: we use BlobDB here
|
||||||
|
// to ensure PrepareValue is not a no-op.)
|
||||||
|
options_.enable_blob_files = true;
|
||||||
|
|
||||||
|
ASSERT_OK(OpenDB());
|
||||||
|
|
||||||
|
ASSERT_OK(db_->Put(write_opts_, db_->DefaultColumnFamily(), "a", "aa"));
|
||||||
|
ASSERT_OK(db_->Put(write_opts_, db_->DefaultColumnFamily(), "c", "cc"));
|
||||||
|
ASSERT_OK(db_->Put(write_opts_, db_->DefaultColumnFamily(), "e", "ee"));
|
||||||
|
ASSERT_OK(db_->Flush(FlushOptions(), db_->DefaultColumnFamily()));
|
||||||
|
|
||||||
|
ASSERT_OK(batch_->Merge(db_->DefaultColumnFamily(), "a", "aa1"));
|
||||||
|
ASSERT_OK(batch_->Merge(db_->DefaultColumnFamily(), "c", "cc1"));
|
||||||
|
ASSERT_OK(batch_->Merge(db_->DefaultColumnFamily(), "e", "ee1"));
|
||||||
|
|
||||||
|
ReadOptions db_read_options = read_opts_;
|
||||||
|
db_read_options.allow_unprepared_value = true;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_ptr<Iterator> iter(batch_->NewIteratorWithBase(
|
||||||
|
db_->DefaultColumnFamily(),
|
||||||
|
db_->NewIterator(db_read_options, db_->DefaultColumnFamily()),
|
||||||
|
&read_opts_));
|
||||||
|
|
||||||
|
iter->SeekToFirst();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), "a");
|
||||||
|
ASSERT_EQ(iter->value(), "aa,aa1");
|
||||||
|
|
||||||
|
iter->Next();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), "c");
|
||||||
|
ASSERT_EQ(iter->value(), "cc,cc1");
|
||||||
|
|
||||||
|
iter->Next();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), "e");
|
||||||
|
ASSERT_EQ(iter->value(), "ee,ee1");
|
||||||
|
|
||||||
|
iter->Next();
|
||||||
|
ASSERT_FALSE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
|
||||||
|
iter->SeekToLast();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), "e");
|
||||||
|
ASSERT_EQ(iter->value(), "ee,ee1");
|
||||||
|
|
||||||
|
iter->Prev();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), "c");
|
||||||
|
ASSERT_EQ(iter->value(), "cc,cc1");
|
||||||
|
|
||||||
|
iter->Prev();
|
||||||
|
ASSERT_TRUE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
ASSERT_EQ(iter->key(), "a");
|
||||||
|
ASSERT_EQ(iter->value(), "aa,aa1");
|
||||||
|
|
||||||
|
iter->Prev();
|
||||||
|
ASSERT_FALSE(iter->Valid());
|
||||||
|
ASSERT_OK(iter->status());
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncPoint::GetInstance()->SetCallBack(
|
||||||
|
"BlobFileReader::GetBlob:TamperWithResult", [](void* arg) {
|
||||||
|
Slice* const blob_index = static_cast<Slice*>(arg);
|
||||||
|
assert(blob_index);
|
||||||
|
assert(!blob_index->empty());
|
||||||
|
blob_index->remove_prefix(1);
|
||||||
|
});
|
||||||
|
SyncPoint::GetInstance()->EnableProcessing();
|
||||||
|
|
||||||
|
// PrepareValue failures from the base iterator should be propagated
|
||||||
|
{
|
||||||
|
std::unique_ptr<Iterator> iter(batch_->NewIteratorWithBase(
|
||||||
|
db_->DefaultColumnFamily(),
|
||||||
|
db_->NewIterator(db_read_options, db_->DefaultColumnFamily()),
|
||||||
|
&read_opts_));
|
||||||
|
|
||||||
|
iter->SeekToFirst();
|
||||||
|
ASSERT_FALSE(iter->Valid());
|
||||||
|
ASSERT_TRUE(iter->status().IsCorruption());
|
||||||
|
|
||||||
|
iter->SeekToLast();
|
||||||
|
ASSERT_FALSE(iter->Valid());
|
||||||
|
ASSERT_TRUE(iter->status().IsCorruption());
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncPoint::GetInstance()->DisableProcessing();
|
||||||
|
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||||
|
}
|
||||||
|
|
||||||
TEST_P(WriteBatchWithIndexTest, TestBoundsCheckingInDeltaIterator) {
|
TEST_P(WriteBatchWithIndexTest, TestBoundsCheckingInDeltaIterator) {
|
||||||
Status s = OpenDB();
|
Status s = OpenDB();
|
||||||
ASSERT_OK(s);
|
ASSERT_OK(s);
|
||||||
|
|
Loading…
Reference in New Issue