Summary: Add WriteBatchWithIndex so that a user can query data out of a WriteBatch, to support MongoDB's read-its-own-write. WriteBatchWithIndex uses a skiplist to store the binary index. The index stores the offset of the entry in the write batch. When searching for a key, the key for the entry is read by read the entry from the write batch from the offset. Define a new iterator class for querying data out of WriteBatchWithIndex. A user can create an iterator of the write batch for one column family, seek to a key and keep calling Next() to see next entries. I will add more unit tests if people are OK about this API. Test Plan: make all check Add unit tests. Reviewers: yhchiang, igor, MarkCallaghan, ljin Reviewed By: ljin Subscribers: dhruba, leveldb, xjin Differential Revision: https://reviews.facebook.net/D21381main
parent
5585e00279
commit
28b5c76004
@ -0,0 +1,102 @@ |
||||
// Copyright (c) 2013, 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.
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
//
|
||||
// A WriteBatchWithIndex with a binary searchable index built for all the keys
|
||||
// inserted.
|
||||
|
||||
#pragma once |
||||
|
||||
#include "rocksdb/status.h" |
||||
#include "rocksdb/slice.h" |
||||
#include "rocksdb/write_batch.h" |
||||
|
||||
namespace rocksdb { |
||||
|
||||
class ColumnFamilyHandle; |
||||
struct SliceParts; |
||||
class Comparator; |
||||
|
||||
enum WriteType { kPutRecord, kMergeRecord, kDeleteRecord, kLogDataRecord }; |
||||
|
||||
// an entry for Put, Merge or Delete entry for write batches. Used in
|
||||
// WBWIIterator.
|
||||
struct WriteEntry { |
||||
WriteType type; |
||||
Slice key; |
||||
Slice value; |
||||
}; |
||||
|
||||
// Iterator of one column family out of a WriteBatchWithIndex.
|
||||
class WBWIIterator { |
||||
public: |
||||
virtual ~WBWIIterator() {} |
||||
|
||||
virtual bool Valid() const = 0; |
||||
|
||||
virtual void Seek(const Slice& key) = 0; |
||||
|
||||
virtual void Next() = 0; |
||||
|
||||
virtual const WriteEntry& Entry() const = 0; |
||||
|
||||
virtual Status status() const = 0; |
||||
}; |
||||
|
||||
// A WriteBatchWithIndex with a binary searchable index built for all the keys
|
||||
// inserted.
|
||||
// In Put(), Merge() or Delete(), the same function of the wrapped will be
|
||||
// called. At the same time, indexes will be built.
|
||||
// By calling GetWriteBatch(), a user will get the WriteBatch for the data
|
||||
// they inserted, which can be used for DB::Write().
|
||||
// A user can call NewIterator() to create an iterator.
|
||||
class WriteBatchWithIndex { |
||||
public: |
||||
// index_comparator indicates the order when iterating data in the write
|
||||
// batch. Technically, it doesn't have to be the same as the one used in
|
||||
// the DB.
|
||||
// reserved_bytes: reserved bytes in underlying WriteBatch
|
||||
explicit WriteBatchWithIndex(const Comparator* index_comparator, |
||||
size_t reserved_bytes = 0); |
||||
virtual ~WriteBatchWithIndex(); |
||||
|
||||
WriteBatch* GetWriteBatch(); |
||||
|
||||
virtual void Put(ColumnFamilyHandle* column_family, const Slice& key, |
||||
const Slice& value); |
||||
|
||||
virtual void Put(const Slice& key, const Slice& value); |
||||
|
||||
virtual void Merge(ColumnFamilyHandle* column_family, const Slice& key, |
||||
const Slice& value); |
||||
|
||||
virtual void Merge(const Slice& key, const Slice& value); |
||||
|
||||
virtual void PutLogData(const Slice& blob); |
||||
|
||||
virtual void Delete(ColumnFamilyHandle* column_family, const Slice& key); |
||||
virtual void Delete(const Slice& key); |
||||
|
||||
virtual void Delete(ColumnFamilyHandle* column_family, const SliceParts& key); |
||||
|
||||
virtual void Delete(const SliceParts& key); |
||||
|
||||
// Create an iterator of a column family. User can call iterator.Seek() to
|
||||
// search to the next entry of or after a key. Keys will be iterated in the
|
||||
// order given by index_comparator. For multiple updates on the same key,
|
||||
// each update will be returned as a separate entry, in the order of update
|
||||
// time.
|
||||
virtual WBWIIterator* NewIterator(ColumnFamilyHandle* column_family); |
||||
// Create an iterator of the default column family.
|
||||
virtual WBWIIterator* NewIterator(); |
||||
|
||||
private: |
||||
struct Rep; |
||||
Rep* rep; |
||||
}; |
||||
|
||||
} // namespace rocksdb
|
@ -0,0 +1,301 @@ |
||||
// Copyright (c) 2013, 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.
|
||||
|
||||
#include "rocksdb/utilities/write_batch_with_index.h" |
||||
#include "rocksdb/comparator.h" |
||||
#include "db/column_family.h" |
||||
#include "db/skiplist.h" |
||||
#include "util/arena.h" |
||||
|
||||
namespace rocksdb { |
||||
namespace { |
||||
class ReadableWriteBatch : public WriteBatch { |
||||
public: |
||||
explicit ReadableWriteBatch(size_t reserved_bytes = 0) |
||||
: WriteBatch(reserved_bytes) {} |
||||
// Retrieve some information from a write entry in the write batch, given
|
||||
// the start offset of the write entry.
|
||||
Status GetEntryFromDataOffset(size_t data_offset, WriteType* type, Slice* Key, |
||||
Slice* value, Slice* blob) const; |
||||
}; |
||||
} // namespace
|
||||
|
||||
// Key used by skip list, as the binary searchable index of WriteBatchWithIndex.
|
||||
struct WriteBatchIndexEntry { |
||||
WriteBatchIndexEntry(size_t o, uint32_t c) |
||||
: offset(o), column_family(c), search_key(nullptr) {} |
||||
WriteBatchIndexEntry(const Slice* sk, uint32_t c) |
||||
: offset(0), column_family(c), search_key(sk) {} |
||||
|
||||
size_t offset; // offset of an entry in write batch's string buffer.
|
||||
uint32_t column_family; // column family of the entry
|
||||
const Slice* search_key; // if not null, instead of reading keys from
|
||||
// write batch, use it to compare. This is used
|
||||
// for lookup key.
|
||||
}; |
||||
|
||||
class WriteBatchEntryComparator { |
||||
public: |
||||
WriteBatchEntryComparator(const Comparator* comparator, |
||||
const ReadableWriteBatch* write_batch) |
||||
: comparator_(comparator), write_batch_(write_batch) {} |
||||
// Compare a and b. Return a negative value if a is less than b, 0 if they
|
||||
// are equal, and a positive value if a is greater than b
|
||||
int operator()(const WriteBatchIndexEntry* entry1, |
||||
const WriteBatchIndexEntry* entry2) const; |
||||
|
||||
private: |
||||
const Comparator* comparator_; |
||||
const ReadableWriteBatch* write_batch_; |
||||
}; |
||||
|
||||
typedef SkipList<WriteBatchIndexEntry*, const WriteBatchEntryComparator&> |
||||
WriteBatchEntrySkipList; |
||||
|
||||
struct WriteBatchWithIndex::Rep { |
||||
Rep(const Comparator* index_comparator, size_t reserved_bytes = 0) |
||||
: write_batch(reserved_bytes), |
||||
comparator(index_comparator, &write_batch), |
||||
skip_list(comparator, &arena) {} |
||||
ReadableWriteBatch write_batch; |
||||
WriteBatchEntryComparator comparator; |
||||
Arena arena; |
||||
WriteBatchEntrySkipList skip_list; |
||||
|
||||
WriteBatchIndexEntry* GetEntry(ColumnFamilyHandle* column_family) { |
||||
return GetEntryWithCfId(GetColumnFamilyID(column_family)); |
||||
} |
||||
|
||||
WriteBatchIndexEntry* GetEntryWithCfId(uint32_t column_family_id) { |
||||
auto* mem = arena.Allocate(sizeof(WriteBatchIndexEntry)); |
||||
auto* index_entry = new (mem) |
||||
WriteBatchIndexEntry(write_batch.GetDataSize(), column_family_id); |
||||
return index_entry; |
||||
} |
||||
}; |
||||
|
||||
class WBWIIteratorImpl : public WBWIIterator { |
||||
public: |
||||
WBWIIteratorImpl(uint32_t column_family_id, |
||||
WriteBatchEntrySkipList* skip_list, |
||||
const ReadableWriteBatch* write_batch) |
||||
: column_family_id_(column_family_id), |
||||
skip_list_iter_(skip_list), |
||||
write_batch_(write_batch), |
||||
valid_(false) {} |
||||
|
||||
virtual ~WBWIIteratorImpl() {} |
||||
|
||||
virtual bool Valid() const override { return valid_; } |
||||
|
||||
virtual void Seek(const Slice& key) override { |
||||
valid_ = true; |
||||
WriteBatchIndexEntry search_entry(&key, column_family_id_); |
||||
skip_list_iter_.Seek(&search_entry); |
||||
ReadEntry(); |
||||
} |
||||
|
||||
virtual void Next() override { |
||||
skip_list_iter_.Next(); |
||||
ReadEntry(); |
||||
} |
||||
|
||||
virtual const WriteEntry& Entry() const override { return current_; } |
||||
|
||||
virtual Status status() const override { return status_; } |
||||
|
||||
private: |
||||
uint32_t column_family_id_; |
||||
WriteBatchEntrySkipList::Iterator skip_list_iter_; |
||||
const ReadableWriteBatch* write_batch_; |
||||
Status status_; |
||||
bool valid_; |
||||
WriteEntry current_; |
||||
|
||||
void ReadEntry() { |
||||
if (!status_.ok() || !skip_list_iter_.Valid()) { |
||||
valid_ = false; |
||||
return; |
||||
} |
||||
const WriteBatchIndexEntry* iter_entry = skip_list_iter_.key(); |
||||
if (iter_entry == nullptr || |
||||
iter_entry->column_family != column_family_id_) { |
||||
valid_ = false; |
||||
return; |
||||
} |
||||
Slice blob; |
||||
status_ = write_batch_->GetEntryFromDataOffset( |
||||
iter_entry->offset, ¤t_.type, ¤t_.key, ¤t_.value, |
||||
&blob); |
||||
if (!status_.ok()) { |
||||
valid_ = false; |
||||
} else if (current_.type != kPutRecord && current_.type != kDeleteRecord && |
||||
current_.type != kMergeRecord) { |
||||
valid_ = false; |
||||
status_ = Status::Corruption("write batch index is corrupted"); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
Status ReadableWriteBatch::GetEntryFromDataOffset(size_t data_offset, |
||||
WriteType* type, Slice* Key, |
||||
Slice* value, |
||||
Slice* blob) const { |
||||
if (type == nullptr || Key == nullptr || value == nullptr || |
||||
blob == nullptr) { |
||||
return Status::InvalidArgument("Output parameters cannot be null"); |
||||
} |
||||
|
||||
if (data_offset >= GetDataSize()) { |
||||
return Status::InvalidArgument("data offset exceed write batch size"); |
||||
} |
||||
Slice input = Slice(rep_.data() + data_offset, rep_.size() - data_offset); |
||||
char tag; |
||||
uint32_t column_family; |
||||
Status s = |
||||
ReadRecordFromWriteBatch(&input, &tag, &column_family, Key, value, blob); |
||||
|
||||
switch (tag) { |
||||
case kTypeColumnFamilyValue: |
||||
case kTypeValue: |
||||
*type = kPutRecord; |
||||
break; |
||||
case kTypeColumnFamilyDeletion: |
||||
case kTypeDeletion: |
||||
*type = kDeleteRecord; |
||||
break; |
||||
case kTypeColumnFamilyMerge: |
||||
case kTypeMerge: |
||||
*type = kMergeRecord; |
||||
break; |
||||
case kTypeLogData: |
||||
*type = kLogDataRecord; |
||||
break; |
||||
default: |
||||
return Status::Corruption("unknown WriteBatch tag"); |
||||
} |
||||
return Status::OK(); |
||||
} |
||||
|
||||
WriteBatchWithIndex::WriteBatchWithIndex(const Comparator* index_comparator, |
||||
size_t reserved_bytes) |
||||
: rep(new Rep(index_comparator, reserved_bytes)) {} |
||||
|
||||
WriteBatchWithIndex::~WriteBatchWithIndex() { delete rep; } |
||||
|
||||
WriteBatch* WriteBatchWithIndex::GetWriteBatch() { return &rep->write_batch; } |
||||
|
||||
WBWIIterator* WriteBatchWithIndex::NewIterator() { |
||||
return new WBWIIteratorImpl(0, &(rep->skip_list), &rep->write_batch); |
||||
} |
||||
|
||||
WBWIIterator* WriteBatchWithIndex::NewIterator( |
||||
ColumnFamilyHandle* column_family) { |
||||
return new WBWIIteratorImpl(GetColumnFamilyID(column_family), |
||||
&(rep->skip_list), &rep->write_batch); |
||||
} |
||||
|
||||
void WriteBatchWithIndex::Put(ColumnFamilyHandle* column_family, |
||||
const Slice& key, const Slice& value) { |
||||
auto* index_entry = rep->GetEntry(column_family); |
||||
rep->write_batch.Put(column_family, key, value); |
||||
rep->skip_list.Insert(index_entry); |
||||
} |
||||
|
||||
void WriteBatchWithIndex::Put(const Slice& key, const Slice& value) { |
||||
auto* index_entry = rep->GetEntryWithCfId(0); |
||||
rep->write_batch.Put(key, value); |
||||
rep->skip_list.Insert(index_entry); |
||||
} |
||||
|
||||
void WriteBatchWithIndex::Merge(ColumnFamilyHandle* column_family, |
||||
const Slice& key, const Slice& value) { |
||||
auto* index_entry = rep->GetEntry(column_family); |
||||
rep->write_batch.Merge(column_family, key, value); |
||||
rep->skip_list.Insert(index_entry); |
||||
} |
||||
|
||||
void WriteBatchWithIndex::Merge(const Slice& key, const Slice& value) { |
||||
auto* index_entry = rep->GetEntryWithCfId(0); |
||||
rep->write_batch.Merge(key, value); |
||||
rep->skip_list.Insert(index_entry); |
||||
} |
||||
|
||||
void WriteBatchWithIndex::PutLogData(const Slice& blob) { |
||||
rep->write_batch.PutLogData(blob); |
||||
} |
||||
|
||||
void WriteBatchWithIndex::Delete(ColumnFamilyHandle* column_family, |
||||
const Slice& key) { |
||||
auto* index_entry = rep->GetEntry(column_family); |
||||
rep->write_batch.Delete(column_family, key); |
||||
rep->skip_list.Insert(index_entry); |
||||
} |
||||
|
||||
void WriteBatchWithIndex::Delete(const Slice& key) { |
||||
auto* index_entry = rep->GetEntryWithCfId(0); |
||||
rep->write_batch.Delete(key); |
||||
rep->skip_list.Insert(index_entry); |
||||
} |
||||
|
||||
void WriteBatchWithIndex::Delete(ColumnFamilyHandle* column_family, |
||||
const SliceParts& key) { |
||||
auto* index_entry = rep->GetEntry(column_family); |
||||
rep->write_batch.Delete(column_family, key); |
||||
rep->skip_list.Insert(index_entry); |
||||
} |
||||
|
||||
void WriteBatchWithIndex::Delete(const SliceParts& key) { |
||||
auto* index_entry = rep->GetEntryWithCfId(0); |
||||
rep->write_batch.Delete(key); |
||||
rep->skip_list.Insert(index_entry); |
||||
} |
||||
|
||||
int WriteBatchEntryComparator::operator()( |
||||
const WriteBatchIndexEntry* entry1, |
||||
const WriteBatchIndexEntry* entry2) const { |
||||
if (entry1->column_family > entry2->column_family) { |
||||
return 1; |
||||
} else if (entry1->column_family < entry2->column_family) { |
||||
return -1; |
||||
} |
||||
|
||||
Status s; |
||||
Slice key1, key2; |
||||
if (entry1->search_key == nullptr) { |
||||
Slice value, blob; |
||||
WriteType write_type; |
||||
s = write_batch_->GetEntryFromDataOffset(entry1->offset, &write_type, &key1, |
||||
&value, &blob); |
||||
if (!s.ok()) { |
||||
return 1; |
||||
} |
||||
} else { |
||||
key1 = *(entry1->search_key); |
||||
} |
||||
if (entry2->search_key == nullptr) { |
||||
Slice value, blob; |
||||
WriteType write_type; |
||||
s = write_batch_->GetEntryFromDataOffset(entry2->offset, &write_type, &key2, |
||||
&value, &blob); |
||||
if (!s.ok()) { |
||||
return -1; |
||||
} |
||||
} else { |
||||
key2 = *(entry2->search_key); |
||||
} |
||||
|
||||
int cmp = comparator_->Compare(key1, key2); |
||||
if (cmp != 0) { |
||||
return cmp; |
||||
} else if (entry1->offset > entry2->offset) { |
||||
return 1; |
||||
} else if (entry1->offset < entry2->offset) { |
||||
return -1; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
} // namespace rocksdb
|
@ -0,0 +1,235 @@ |
||||
// Copyright (c) 2013, 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.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
|
||||
#include <memory> |
||||
#include <map> |
||||
#include "db/column_family.h" |
||||
#include "rocksdb/utilities/write_batch_with_index.h" |
||||
#include "util/testharness.h" |
||||
|
||||
namespace rocksdb { |
||||
|
||||
namespace { |
||||
class ColumnFamilyHandleImplDummy : public ColumnFamilyHandleImpl { |
||||
public: |
||||
explicit ColumnFamilyHandleImplDummy(int id) |
||||
: ColumnFamilyHandleImpl(nullptr, nullptr, nullptr), id_(id) {} |
||||
uint32_t GetID() const override { return id_; } |
||||
|
||||
private: |
||||
uint32_t id_; |
||||
}; |
||||
|
||||
struct Entry { |
||||
std::string key; |
||||
std::string value; |
||||
WriteType type; |
||||
}; |
||||
|
||||
struct TestHandler : public WriteBatch::Handler { |
||||
std::map<uint32_t, std::vector<Entry>> seen; |
||||
virtual Status PutCF(uint32_t column_family_id, const Slice& key, |
||||
const Slice& value) { |
||||
Entry e; |
||||
e.key = key.ToString(); |
||||
e.value = value.ToString(); |
||||
e.type = kPutRecord; |
||||
seen[column_family_id].push_back(e); |
||||
return Status::OK(); |
||||
} |
||||
virtual Status MergeCF(uint32_t column_family_id, const Slice& key, |
||||
const Slice& value) { |
||||
Entry e; |
||||
e.key = key.ToString(); |
||||
e.value = value.ToString(); |
||||
e.type = kMergeRecord; |
||||
seen[column_family_id].push_back(e); |
||||
return Status::OK(); |
||||
} |
||||
virtual void LogData(const Slice& blob) {} |
||||
virtual Status DeleteCF(uint32_t column_family_id, const Slice& key) { |
||||
Entry e; |
||||
e.key = key.ToString(); |
||||
e.value = ""; |
||||
e.type = kDeleteRecord; |
||||
seen[column_family_id].push_back(e); |
||||
return Status::OK(); |
||||
} |
||||
}; |
||||
} // namespace anonymous
|
||||
|
||||
class WriteBatchWithIndexTest {}; |
||||
|
||||
TEST(WriteBatchWithIndexTest, TestValueAsSecondaryIndex) { |
||||
Entry entries[] = {{"aaa", "0005", kPutRecord}, |
||||
{"b", "0002", kPutRecord}, |
||||
{"cdd", "0002", kMergeRecord}, |
||||
{"aab", "00001", kPutRecord}, |
||||
{"cc", "00005", kPutRecord}, |
||||
{"cdd", "0002", kPutRecord}, |
||||
{"aab", "0003", kPutRecord}, |
||||
{"cc", "00005", kDeleteRecord}, }; |
||||
|
||||
// In this test, we insert <key, value> to column family `data`, and
|
||||
// <value, key> to column family `index`. Then iterator them in order
|
||||
// and seek them by key.
|
||||
|
||||
// Sort entries by key
|
||||
std::map<std::string, std::vector<Entry*>> data_map; |
||||
// Sort entries by value
|
||||
std::map<std::string, std::vector<Entry*>> index_map; |
||||
for (auto& e : entries) { |
||||
data_map[e.key].push_back(&e); |
||||
index_map[e.value].push_back(&e); |
||||
} |
||||
|
||||
WriteBatchWithIndex batch(BytewiseComparator(), 20); |
||||
ColumnFamilyHandleImplDummy data(6), index(8); |
||||
for (auto& e : entries) { |
||||
if (e.type == kPutRecord) { |
||||
batch.Put(&data, e.key, e.value); |
||||
batch.Put(&index, e.value, e.key); |
||||
} else if (e.type == kMergeRecord) { |
||||
batch.Merge(&data, e.key, e.value); |
||||
batch.Put(&index, e.value, e.key); |
||||
} else { |
||||
assert(e.type == kDeleteRecord); |
||||
std::unique_ptr<WBWIIterator> iter(batch.NewIterator(&data)); |
||||
iter->Seek(e.key); |
||||
ASSERT_OK(iter->status()); |
||||
auto& write_entry = iter->Entry(); |
||||
ASSERT_EQ(e.key, write_entry.key.ToString()); |
||||
ASSERT_EQ(e.value, write_entry.value.ToString()); |
||||
batch.Delete(&data, e.key); |
||||
batch.Put(&index, e.value, ""); |
||||
} |
||||
} |
||||
|
||||
// Iterator all keys
|
||||
{ |
||||
std::unique_ptr<WBWIIterator> iter(batch.NewIterator(&data)); |
||||
iter->Seek(""); |
||||
for (auto pair : data_map) { |
||||
for (auto v : pair.second) { |
||||
ASSERT_OK(iter->status()); |
||||
ASSERT_TRUE(iter->Valid()); |
||||
auto& write_entry = iter->Entry(); |
||||
ASSERT_EQ(pair.first, write_entry.key.ToString()); |
||||
ASSERT_EQ(v->type, write_entry.type); |
||||
if (write_entry.type != kDeleteRecord) { |
||||
ASSERT_EQ(v->value, write_entry.value.ToString()); |
||||
} |
||||
iter->Next(); |
||||
} |
||||
} |
||||
ASSERT_TRUE(!iter->Valid()); |
||||
} |
||||
|
||||
// Iterator all indexes
|
||||
{ |
||||
std::unique_ptr<WBWIIterator> iter(batch.NewIterator(&index)); |
||||
iter->Seek(""); |
||||
for (auto pair : index_map) { |
||||
for (auto v : pair.second) { |
||||
ASSERT_OK(iter->status()); |
||||
ASSERT_TRUE(iter->Valid()); |
||||
auto& write_entry = iter->Entry(); |
||||
ASSERT_EQ(pair.first, write_entry.key.ToString()); |
||||
if (v->type != kDeleteRecord) { |
||||
ASSERT_EQ(v->key, write_entry.value.ToString()); |
||||
ASSERT_EQ(v->value, write_entry.key.ToString()); |
||||
} |
||||
iter->Next(); |
||||
} |
||||
} |
||||
ASSERT_TRUE(!iter->Valid()); |
||||
} |
||||
|
||||
// Seek to every key
|
||||
{ |
||||
std::unique_ptr<WBWIIterator> iter(batch.NewIterator(&data)); |
||||
|
||||
// Seek the keys one by one in reverse order
|
||||
for (auto pair = data_map.rbegin(); pair != data_map.rend(); ++pair) { |
||||
iter->Seek(pair->first); |
||||
ASSERT_OK(iter->status()); |
||||
for (auto v : pair->second) { |
||||
ASSERT_TRUE(iter->Valid()); |
||||
auto& write_entry = iter->Entry(); |
||||
ASSERT_EQ(pair->first, write_entry.key.ToString()); |
||||
ASSERT_EQ(v->type, write_entry.type); |
||||
if (write_entry.type != kDeleteRecord) { |
||||
ASSERT_EQ(v->value, write_entry.value.ToString()); |
||||
} |
||||
iter->Next(); |
||||
ASSERT_OK(iter->status()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Seek to every index
|
||||
{ |
||||
std::unique_ptr<WBWIIterator> iter(batch.NewIterator(&index)); |
||||
|
||||
// Seek the keys one by one in reverse order
|
||||
for (auto pair = index_map.rbegin(); pair != index_map.rend(); ++pair) { |
||||
iter->Seek(pair->first); |
||||
ASSERT_OK(iter->status()); |
||||
for (auto v : pair->second) { |
||||
ASSERT_TRUE(iter->Valid()); |
||||
auto& write_entry = iter->Entry(); |
||||
ASSERT_EQ(pair->first, write_entry.key.ToString()); |
||||
ASSERT_EQ(v->value, write_entry.key.ToString()); |
||||
if (v->type != kDeleteRecord) { |
||||
ASSERT_EQ(v->key, write_entry.value.ToString()); |
||||
} |
||||
iter->Next(); |
||||
ASSERT_OK(iter->status()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Verify WriteBatch can be iterated
|
||||
TestHandler handler; |
||||
batch.GetWriteBatch()->Iterate(&handler); |
||||
|
||||
// Verify data column family
|
||||
{ |
||||
ASSERT_EQ(sizeof(entries) / sizeof(Entry), |
||||
handler.seen[data.GetID()].size()); |
||||
size_t i = 0; |
||||
for (auto e : handler.seen[data.GetID()]) { |
||||
auto write_entry = entries[i++]; |
||||
ASSERT_EQ(e.type, write_entry.type); |
||||
ASSERT_EQ(e.key, write_entry.key); |
||||
if (e.type != kDeleteRecord) { |
||||
ASSERT_EQ(e.value, write_entry.value); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Verify index column family
|
||||
{ |
||||
ASSERT_EQ(sizeof(entries) / sizeof(Entry), |
||||
handler.seen[index.GetID()].size()); |
||||
size_t i = 0; |
||||
for (auto e : handler.seen[index.GetID()]) { |
||||
auto write_entry = entries[i++]; |
||||
ASSERT_EQ(e.key, write_entry.value); |
||||
if (write_entry.type != kDeleteRecord) { |
||||
ASSERT_EQ(e.value, write_entry.key); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) { return rocksdb::test::RunAllTests(); } |
Loading…
Reference in new issue