2013-10-16 23:59:46 +02:00
|
|
|
// 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.
|
|
|
|
//
|
2011-03-18 23:37:00 +01:00
|
|
|
// 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 "db/db_iter.h"
|
2013-05-28 23:00:10 +02:00
|
|
|
#include <stdexcept>
|
[RocksDB] [MergeOperator] The new Merge Interface! Uses merge sequences.
Summary:
Here are the major changes to the Merge Interface. It has been expanded
to handle cases where the MergeOperator is not associative. It does so by stacking
up merge operations while scanning through the key history (i.e.: during Get() or
Compaction), until a valid Put/Delete/end-of-history is encountered; it then
applies all of the merge operations in the correct sequence starting with the
base/sentinel value.
I have also introduced an "AssociativeMerge" function which allows the user to
take advantage of associative merge operations (such as in the case of counters).
The implementation will always attempt to merge the operations/operands themselves
together when they are encountered, and will resort to the "stacking" method if
and only if the "associative-merge" fails.
This implementation is conjectured to allow MergeOperator to handle the general
case, while still providing the user with the ability to take advantage of certain
efficiencies in their own merge-operator / data-structure.
NOTE: This is a preliminary diff. This must still go through a lot of review,
revision, and testing. Feedback welcome!
Test Plan:
-This is a preliminary diff. I have only just begun testing/debugging it.
-I will be testing this with the existing MergeOperator use-cases and unit-tests
(counters, string-append, and redis-lists)
-I will be "desk-checking" and walking through the code with the help gdb.
-I will find a way of stress-testing the new interface / implementation using
db_bench, db_test, merge_test, and/or db_stress.
-I will ensure that my tests cover all cases: Get-Memtable,
Get-Immutable-Memtable, Get-from-Disk, Iterator-Range-Scan, Flush-Memtable-to-L0,
Compaction-L0-L1, Compaction-Ln-L(n+1), Put/Delete found, Put/Delete not-found,
end-of-history, end-of-file, etc.
-A lot of feedback from the reviewers.
Reviewers: haobo, dhruba, zshao, emayanke
Reviewed By: haobo
CC: leveldb
Differential Revision: https://reviews.facebook.net/D11499
2013-08-06 05:14:32 +02:00
|
|
|
#include <deque>
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
#include "db/filename.h"
|
|
|
|
#include "db/dbformat.h"
|
2013-08-23 17:38:13 +02:00
|
|
|
#include "rocksdb/env.h"
|
|
|
|
#include "rocksdb/options.h"
|
|
|
|
#include "rocksdb/iterator.h"
|
|
|
|
#include "rocksdb/merge_operator.h"
|
2011-03-18 23:37:00 +01:00
|
|
|
#include "port/port.h"
|
|
|
|
#include "util/logging.h"
|
|
|
|
#include "util/mutexlock.h"
|
2013-10-02 19:28:25 +02:00
|
|
|
#include "util/perf_context_imp.h"
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2013-10-04 06:49:15 +02:00
|
|
|
namespace rocksdb {
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
#if 0
|
|
|
|
static void DumpInternalIter(Iterator* iter) {
|
|
|
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
|
|
|
ParsedInternalKey k;
|
|
|
|
if (!ParseInternalKey(iter->key(), &k)) {
|
|
|
|
fprintf(stderr, "Corrupt '%s'\n", EscapeString(iter->key()).c_str());
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "@ '%s'\n", k.DebugString().c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
2014-04-01 23:45:30 +02:00
|
|
|
class IterLookupKey {
|
|
|
|
public:
|
|
|
|
IterLookupKey() : key_(space_), buf_size_(sizeof(space_)), key_size_(0) {}
|
|
|
|
|
|
|
|
~IterLookupKey() { Clear(); }
|
|
|
|
|
|
|
|
Slice GetKey() const {
|
|
|
|
if (key_ != nullptr) {
|
|
|
|
return Slice(key_, key_size_);
|
|
|
|
} else {
|
|
|
|
return Slice();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Valid() const { return key_ != nullptr; }
|
|
|
|
|
|
|
|
void Clear() {
|
|
|
|
if (key_ != nullptr && key_ != space_) {
|
|
|
|
delete[] key_;
|
|
|
|
}
|
|
|
|
key_ = space_;
|
|
|
|
buf_size_ = sizeof(buf_size_);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Enlarge the buffer size if needed based on key_size.
|
|
|
|
// By default, static allocated buffer is used. Once there is a key
|
|
|
|
// larger than the static allocated buffer, another buffer is dynamically
|
|
|
|
// allocated, until a larger key buffer is requested. In that case, we
|
|
|
|
// reallocate buffer and delete the old one.
|
|
|
|
void EnlargeBufferIfNeeded(size_t key_size) {
|
|
|
|
// If size is smaller than buffer size, continue using current buffer,
|
|
|
|
// or the static allocated one, as default
|
|
|
|
if (key_size > buf_size_) {
|
|
|
|
// Need to enlarge the buffer.
|
|
|
|
Clear();
|
|
|
|
key_ = new char[key_size];
|
|
|
|
buf_size_ = key_size;
|
|
|
|
}
|
|
|
|
key_size_ = key_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetUserKey(const Slice& user_key) {
|
|
|
|
size_t size = user_key.size();
|
|
|
|
EnlargeBufferIfNeeded(size);
|
|
|
|
memcpy(key_, user_key.data(), size);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetInternalKey(const Slice& user_key, SequenceNumber s) {
|
|
|
|
size_t usize = user_key.size();
|
|
|
|
EnlargeBufferIfNeeded(usize + sizeof(uint64_t));
|
|
|
|
memcpy(key_, user_key.data(), usize);
|
|
|
|
EncodeFixed64(key_ + usize, PackSequenceAndType(s, kValueTypeForSeek));
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
char* key_;
|
|
|
|
size_t buf_size_;
|
|
|
|
size_t key_size_;
|
|
|
|
char space_[32]; // Avoid allocation for short keys
|
|
|
|
|
|
|
|
// No copying allowed
|
|
|
|
IterLookupKey(const IterLookupKey&) = delete;
|
|
|
|
void operator=(const LookupKey&) = delete;
|
|
|
|
};
|
|
|
|
|
2011-03-18 23:37:00 +01:00
|
|
|
// Memtables and sstables that make the DB representation contain
|
|
|
|
// (userkey,seq,type) => uservalue entries. DBIter
|
|
|
|
// combines multiple entries for the same userkey found in the DB
|
|
|
|
// representation into a single entry while accounting for sequence
|
|
|
|
// numbers, deletion markers, overwrites, etc.
|
|
|
|
class DBIter: public Iterator {
|
|
|
|
public:
|
2013-03-21 23:59:47 +01:00
|
|
|
// The following is grossly complicated. TODO: clean it up
|
2011-03-25 21:27:43 +01:00
|
|
|
// Which direction is the iterator currently moving?
|
|
|
|
// (1) When moving forward, the internal iterator is positioned at
|
|
|
|
// the exact entry that yields this->key(), this->value()
|
|
|
|
// (2) When moving backwards, the internal iterator is positioned
|
|
|
|
// just before all entries whose user key == this->key().
|
|
|
|
enum Direction {
|
|
|
|
kForward,
|
|
|
|
kReverse
|
|
|
|
};
|
|
|
|
|
2013-03-21 23:59:47 +01:00
|
|
|
DBIter(const std::string* dbname, Env* env, const Options& options,
|
2011-03-18 23:37:00 +01:00
|
|
|
const Comparator* cmp, Iterator* iter, SequenceNumber s)
|
|
|
|
: dbname_(dbname),
|
|
|
|
env_(env),
|
2013-12-03 20:17:58 +01:00
|
|
|
logger_(options.info_log.get()),
|
2011-03-18 23:37:00 +01:00
|
|
|
user_comparator_(cmp),
|
2013-08-20 22:35:28 +02:00
|
|
|
user_merge_operator_(options.merge_operator.get()),
|
2011-03-18 23:37:00 +01:00
|
|
|
iter_(iter),
|
|
|
|
sequence_(s),
|
2011-03-25 21:27:43 +01:00
|
|
|
direction_(kForward),
|
2013-03-21 23:59:47 +01:00
|
|
|
valid_(false),
|
2013-05-28 23:00:10 +02:00
|
|
|
current_entry_is_merged_(false),
|
2013-11-22 23:14:05 +01:00
|
|
|
statistics_(options.statistics.get()) {
|
2013-05-28 23:00:10 +02:00
|
|
|
RecordTick(statistics_, NO_ITERATORS, 1);
|
2013-07-28 20:53:08 +02:00
|
|
|
max_skip_ = options.max_sequential_skip_in_iterations;
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
virtual ~DBIter() {
|
2013-05-28 23:00:10 +02:00
|
|
|
RecordTick(statistics_, NO_ITERATORS, -1);
|
2011-03-18 23:37:00 +01:00
|
|
|
delete iter_;
|
|
|
|
}
|
|
|
|
virtual bool Valid() const { return valid_; }
|
|
|
|
virtual Slice key() const {
|
|
|
|
assert(valid_);
|
2014-04-01 23:45:30 +02:00
|
|
|
return saved_key_.GetKey();
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
virtual Slice value() const {
|
|
|
|
assert(valid_);
|
2013-03-21 23:59:47 +01:00
|
|
|
return (direction_ == kForward && !current_entry_is_merged_) ?
|
|
|
|
iter_->value() : saved_value_;
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
virtual Status status() const {
|
|
|
|
if (status_.ok()) {
|
|
|
|
return iter_->status();
|
|
|
|
} else {
|
|
|
|
return status_;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-25 21:27:43 +01:00
|
|
|
virtual void Next();
|
|
|
|
virtual void Prev();
|
|
|
|
virtual void Seek(const Slice& target);
|
|
|
|
virtual void SeekToFirst();
|
|
|
|
virtual void SeekToLast();
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2011-03-25 21:27:43 +01:00
|
|
|
private:
|
2013-11-18 20:32:54 +01:00
|
|
|
inline void FindNextUserEntry(bool skipping);
|
|
|
|
void FindNextUserEntryInternal(bool skipping);
|
2011-03-25 21:27:43 +01:00
|
|
|
void FindPrevUserEntry();
|
|
|
|
bool ParseKey(ParsedInternalKey* key);
|
2013-03-21 23:59:47 +01:00
|
|
|
void MergeValuesNewToOld();
|
2011-03-25 21:27:43 +01:00
|
|
|
|
|
|
|
inline void ClearSavedValue() {
|
|
|
|
if (saved_value_.capacity() > 1048576) {
|
|
|
|
std::string empty;
|
|
|
|
swap(empty, saved_value_);
|
|
|
|
} else {
|
|
|
|
saved_value_.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-18 23:37:00 +01:00
|
|
|
const std::string* const dbname_;
|
|
|
|
Env* const env_;
|
2013-12-03 20:17:58 +01:00
|
|
|
Logger* logger_;
|
2011-03-18 23:37:00 +01:00
|
|
|
const Comparator* const user_comparator_;
|
2013-03-21 23:59:47 +01:00
|
|
|
const MergeOperator* const user_merge_operator_;
|
2011-03-18 23:37:00 +01:00
|
|
|
Iterator* const iter_;
|
|
|
|
SequenceNumber const sequence_;
|
2011-03-25 21:27:43 +01:00
|
|
|
|
2011-03-18 23:37:00 +01:00
|
|
|
Status status_;
|
2014-04-01 23:45:30 +02:00
|
|
|
IterLookupKey saved_key_; // == current key when direction_==kReverse
|
2011-03-25 21:27:43 +01:00
|
|
|
std::string saved_value_; // == current raw value when direction_==kReverse
|
2013-03-21 23:59:47 +01:00
|
|
|
std::string skip_key_;
|
2011-03-25 21:27:43 +01:00
|
|
|
Direction direction_;
|
2011-03-18 23:37:00 +01:00
|
|
|
bool valid_;
|
2013-03-21 23:59:47 +01:00
|
|
|
bool current_entry_is_merged_;
|
2013-11-22 23:14:05 +01:00
|
|
|
Statistics* statistics_;
|
2013-07-28 20:53:08 +02:00
|
|
|
uint64_t max_skip_;
|
2011-03-18 23:37:00 +01:00
|
|
|
|
|
|
|
// No copying allowed
|
|
|
|
DBIter(const DBIter&);
|
|
|
|
void operator=(const DBIter&);
|
|
|
|
};
|
|
|
|
|
|
|
|
inline bool DBIter::ParseKey(ParsedInternalKey* ikey) {
|
|
|
|
if (!ParseInternalKey(iter_->key(), ikey)) {
|
|
|
|
status_ = Status::Corruption("corrupted internal key in DBIter");
|
2013-03-21 23:59:47 +01:00
|
|
|
Log(logger_, "corrupted internal key in DBIter: %s",
|
|
|
|
iter_->key().ToString(true).c_str());
|
2011-03-18 23:37:00 +01:00
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-25 21:27:43 +01:00
|
|
|
void DBIter::Next() {
|
|
|
|
assert(valid_);
|
|
|
|
|
|
|
|
if (direction_ == kReverse) { // Switch directions?
|
|
|
|
direction_ = kForward;
|
|
|
|
// iter_ is pointing just before the entries for this->key(),
|
|
|
|
// so advance into the range of entries for this->key() and then
|
|
|
|
// use the normal skipping code below.
|
|
|
|
if (!iter_->Valid()) {
|
|
|
|
iter_->SeekToFirst();
|
|
|
|
} else {
|
2011-03-18 23:37:00 +01:00
|
|
|
iter_->Next();
|
|
|
|
}
|
2011-03-25 21:27:43 +01:00
|
|
|
if (!iter_->Valid()) {
|
|
|
|
valid_ = false;
|
2014-04-01 23:45:30 +02:00
|
|
|
saved_key_.Clear();
|
2011-03-25 21:27:43 +01:00
|
|
|
return;
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
}
|
2011-03-25 21:27:43 +01:00
|
|
|
|
2013-03-21 23:59:47 +01:00
|
|
|
// If the current value is merged, we might already hit end of iter_
|
|
|
|
if (!iter_->Valid()) {
|
|
|
|
valid_ = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
FindNextUserEntry(true /* skipping the current user key */);
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2013-03-21 23:59:47 +01:00
|
|
|
|
|
|
|
// PRE: saved_key_ has the current user key if skipping
|
|
|
|
// POST: saved_key_ should have the next user key if valid_,
|
|
|
|
// if the current entry is a result of merge
|
|
|
|
// current_entry_is_merged_ => true
|
|
|
|
// saved_value_ => the merged value
|
|
|
|
//
|
|
|
|
// NOTE: In between, saved_key_ can point to a user key that has
|
|
|
|
// a delete marker
|
2013-11-18 20:32:54 +01:00
|
|
|
inline void DBIter::FindNextUserEntry(bool skipping) {
|
|
|
|
StopWatchNano timer(env_, false);
|
|
|
|
StartPerfTimer(&timer);
|
|
|
|
FindNextUserEntryInternal(skipping);
|
|
|
|
BumpPerfTime(&perf_context.find_next_user_entry_time, &timer);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Actual implementation of DBIter::FindNextUserEntry()
|
|
|
|
void DBIter::FindNextUserEntryInternal(bool skipping) {
|
2011-03-25 21:27:43 +01:00
|
|
|
// Loop until we hit an acceptable entry to yield
|
|
|
|
assert(iter_->Valid());
|
|
|
|
assert(direction_ == kForward);
|
2013-03-21 23:59:47 +01:00
|
|
|
current_entry_is_merged_ = false;
|
2013-07-28 20:53:08 +02:00
|
|
|
uint64_t num_skipped = 0;
|
2011-03-25 21:27:43 +01:00
|
|
|
do {
|
2011-03-18 23:37:00 +01:00
|
|
|
ParsedInternalKey ikey;
|
2011-03-25 21:27:43 +01:00
|
|
|
if (ParseKey(&ikey) && ikey.sequence <= sequence_) {
|
2013-03-21 23:59:47 +01:00
|
|
|
if (skipping &&
|
2014-04-01 23:45:30 +02:00
|
|
|
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) <= 0) {
|
2013-07-28 20:53:08 +02:00
|
|
|
num_skipped++; // skip this entry
|
2013-10-02 19:28:25 +02:00
|
|
|
BumpPerfCount(&perf_context.internal_key_skipped_count);
|
2013-03-21 23:59:47 +01:00
|
|
|
} else {
|
|
|
|
skipping = false;
|
|
|
|
switch (ikey.type) {
|
|
|
|
case kTypeDeletion:
|
|
|
|
// Arrange to skip all upcoming entries for this key since
|
|
|
|
// they are hidden by this deletion.
|
2014-04-01 23:45:30 +02:00
|
|
|
saved_key_.SetUserKey(ikey.user_key);
|
2013-03-21 23:59:47 +01:00
|
|
|
skipping = true;
|
2013-07-28 20:53:08 +02:00
|
|
|
num_skipped = 0;
|
2013-10-02 19:28:25 +02:00
|
|
|
BumpPerfCount(&perf_context.internal_delete_skipped_count);
|
2013-03-21 23:59:47 +01:00
|
|
|
break;
|
|
|
|
case kTypeValue:
|
2011-03-25 21:27:43 +01:00
|
|
|
valid_ = true;
|
2014-04-01 23:45:30 +02:00
|
|
|
saved_key_.SetUserKey(ikey.user_key);
|
2011-03-25 21:27:43 +01:00
|
|
|
return;
|
2013-03-21 23:59:47 +01:00
|
|
|
case kTypeMerge:
|
|
|
|
// By now, we are sure the current ikey is going to yield a value
|
2014-04-01 23:45:30 +02:00
|
|
|
saved_key_.SetUserKey(ikey.user_key);
|
2013-03-21 23:59:47 +01:00
|
|
|
current_entry_is_merged_ = true;
|
|
|
|
valid_ = true;
|
2013-08-19 20:42:47 +02:00
|
|
|
MergeValuesNewToOld(); // Go to a different state machine
|
2013-03-21 23:59:47 +01:00
|
|
|
return;
|
2014-01-27 22:53:22 +01:00
|
|
|
default:
|
2013-08-15 01:32:46 +02:00
|
|
|
assert(false);
|
|
|
|
break;
|
2013-03-21 23:59:47 +01:00
|
|
|
}
|
2011-03-25 21:27:43 +01:00
|
|
|
}
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
2013-07-28 20:53:08 +02:00
|
|
|
// If we have sequentially iterated via numerous keys and still not
|
|
|
|
// found the next user-key, then it is better to seek so that we can
|
|
|
|
// avoid too many key comparisons. We seek to the last occurence of
|
|
|
|
// our current key by looking for sequence number 0.
|
|
|
|
if (skipping && num_skipped > max_skip_) {
|
|
|
|
num_skipped = 0;
|
|
|
|
std::string last_key;
|
2014-04-01 23:45:30 +02:00
|
|
|
AppendInternalKey(&last_key, ParsedInternalKey(saved_key_.GetKey(), 0,
|
|
|
|
kValueTypeForSeek));
|
2013-07-28 20:53:08 +02:00
|
|
|
iter_->Seek(last_key);
|
|
|
|
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
|
|
|
} else {
|
|
|
|
iter_->Next();
|
|
|
|
}
|
2011-03-25 21:27:43 +01:00
|
|
|
} while (iter_->Valid());
|
|
|
|
valid_ = false;
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2013-03-21 23:59:47 +01:00
|
|
|
// Merge values of the same user key starting from the current iter_ position
|
|
|
|
// Scan from the newer entries to older entries.
|
|
|
|
// PRE: iter_->key() points to the first merge type entry
|
|
|
|
// saved_key_ stores the user key
|
|
|
|
// POST: saved_value_ has the merged value for the user key
|
|
|
|
// iter_ points to the next entry (or invalid)
|
|
|
|
void DBIter::MergeValuesNewToOld() {
|
2013-08-19 20:42:47 +02:00
|
|
|
if (!user_merge_operator_) {
|
|
|
|
Log(logger_, "Options::merge_operator is null.");
|
|
|
|
throw std::logic_error("DBIter::MergeValuesNewToOld() with"
|
|
|
|
" Options::merge_operator null");
|
|
|
|
}
|
2013-03-21 23:59:47 +01:00
|
|
|
|
[RocksDB] [MergeOperator] The new Merge Interface! Uses merge sequences.
Summary:
Here are the major changes to the Merge Interface. It has been expanded
to handle cases where the MergeOperator is not associative. It does so by stacking
up merge operations while scanning through the key history (i.e.: during Get() or
Compaction), until a valid Put/Delete/end-of-history is encountered; it then
applies all of the merge operations in the correct sequence starting with the
base/sentinel value.
I have also introduced an "AssociativeMerge" function which allows the user to
take advantage of associative merge operations (such as in the case of counters).
The implementation will always attempt to merge the operations/operands themselves
together when they are encountered, and will resort to the "stacking" method if
and only if the "associative-merge" fails.
This implementation is conjectured to allow MergeOperator to handle the general
case, while still providing the user with the ability to take advantage of certain
efficiencies in their own merge-operator / data-structure.
NOTE: This is a preliminary diff. This must still go through a lot of review,
revision, and testing. Feedback welcome!
Test Plan:
-This is a preliminary diff. I have only just begun testing/debugging it.
-I will be testing this with the existing MergeOperator use-cases and unit-tests
(counters, string-append, and redis-lists)
-I will be "desk-checking" and walking through the code with the help gdb.
-I will find a way of stress-testing the new interface / implementation using
db_bench, db_test, merge_test, and/or db_stress.
-I will ensure that my tests cover all cases: Get-Memtable,
Get-Immutable-Memtable, Get-from-Disk, Iterator-Range-Scan, Flush-Memtable-to-L0,
Compaction-L0-L1, Compaction-Ln-L(n+1), Put/Delete found, Put/Delete not-found,
end-of-history, end-of-file, etc.
-A lot of feedback from the reviewers.
Reviewers: haobo, dhruba, zshao, emayanke
Reviewed By: haobo
CC: leveldb
Differential Revision: https://reviews.facebook.net/D11499
2013-08-06 05:14:32 +02:00
|
|
|
// Start the merge process by pushing the first operand
|
|
|
|
std::deque<std::string> operands;
|
|
|
|
operands.push_front(iter_->value().ToString());
|
2013-03-21 23:59:47 +01:00
|
|
|
|
[RocksDB] [MergeOperator] The new Merge Interface! Uses merge sequences.
Summary:
Here are the major changes to the Merge Interface. It has been expanded
to handle cases where the MergeOperator is not associative. It does so by stacking
up merge operations while scanning through the key history (i.e.: during Get() or
Compaction), until a valid Put/Delete/end-of-history is encountered; it then
applies all of the merge operations in the correct sequence starting with the
base/sentinel value.
I have also introduced an "AssociativeMerge" function which allows the user to
take advantage of associative merge operations (such as in the case of counters).
The implementation will always attempt to merge the operations/operands themselves
together when they are encountered, and will resort to the "stacking" method if
and only if the "associative-merge" fails.
This implementation is conjectured to allow MergeOperator to handle the general
case, while still providing the user with the ability to take advantage of certain
efficiencies in their own merge-operator / data-structure.
NOTE: This is a preliminary diff. This must still go through a lot of review,
revision, and testing. Feedback welcome!
Test Plan:
-This is a preliminary diff. I have only just begun testing/debugging it.
-I will be testing this with the existing MergeOperator use-cases and unit-tests
(counters, string-append, and redis-lists)
-I will be "desk-checking" and walking through the code with the help gdb.
-I will find a way of stress-testing the new interface / implementation using
db_bench, db_test, merge_test, and/or db_stress.
-I will ensure that my tests cover all cases: Get-Memtable,
Get-Immutable-Memtable, Get-from-Disk, Iterator-Range-Scan, Flush-Memtable-to-L0,
Compaction-L0-L1, Compaction-Ln-L(n+1), Put/Delete found, Put/Delete not-found,
end-of-history, end-of-file, etc.
-A lot of feedback from the reviewers.
Reviewers: haobo, dhruba, zshao, emayanke
Reviewed By: haobo
CC: leveldb
Differential Revision: https://reviews.facebook.net/D11499
2013-08-06 05:14:32 +02:00
|
|
|
std::string merge_result; // Temporary string to hold merge result later
|
2013-03-21 23:59:47 +01:00
|
|
|
ParsedInternalKey ikey;
|
|
|
|
for (iter_->Next(); iter_->Valid(); iter_->Next()) {
|
|
|
|
if (!ParseKey(&ikey)) {
|
|
|
|
// skip corrupted key
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-04-01 23:45:30 +02:00
|
|
|
if (user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) != 0) {
|
2013-03-21 23:59:47 +01:00
|
|
|
// hit the next user key, stop right here
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (kTypeDeletion == ikey.type) {
|
|
|
|
// hit a delete with the same user key, stop right here
|
|
|
|
// iter_ is positioned after delete
|
|
|
|
iter_->Next();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (kTypeValue == ikey.type) {
|
[RocksDB] [MergeOperator] The new Merge Interface! Uses merge sequences.
Summary:
Here are the major changes to the Merge Interface. It has been expanded
to handle cases where the MergeOperator is not associative. It does so by stacking
up merge operations while scanning through the key history (i.e.: during Get() or
Compaction), until a valid Put/Delete/end-of-history is encountered; it then
applies all of the merge operations in the correct sequence starting with the
base/sentinel value.
I have also introduced an "AssociativeMerge" function which allows the user to
take advantage of associative merge operations (such as in the case of counters).
The implementation will always attempt to merge the operations/operands themselves
together when they are encountered, and will resort to the "stacking" method if
and only if the "associative-merge" fails.
This implementation is conjectured to allow MergeOperator to handle the general
case, while still providing the user with the ability to take advantage of certain
efficiencies in their own merge-operator / data-structure.
NOTE: This is a preliminary diff. This must still go through a lot of review,
revision, and testing. Feedback welcome!
Test Plan:
-This is a preliminary diff. I have only just begun testing/debugging it.
-I will be testing this with the existing MergeOperator use-cases and unit-tests
(counters, string-append, and redis-lists)
-I will be "desk-checking" and walking through the code with the help gdb.
-I will find a way of stress-testing the new interface / implementation using
db_bench, db_test, merge_test, and/or db_stress.
-I will ensure that my tests cover all cases: Get-Memtable,
Get-Immutable-Memtable, Get-from-Disk, Iterator-Range-Scan, Flush-Memtable-to-L0,
Compaction-L0-L1, Compaction-Ln-L(n+1), Put/Delete found, Put/Delete not-found,
end-of-history, end-of-file, etc.
-A lot of feedback from the reviewers.
Reviewers: haobo, dhruba, zshao, emayanke
Reviewed By: haobo
CC: leveldb
Differential Revision: https://reviews.facebook.net/D11499
2013-08-06 05:14:32 +02:00
|
|
|
// hit a put, merge the put value with operands and store the
|
|
|
|
// final result in saved_value_. We are done!
|
|
|
|
// ignore corruption if there is any.
|
2013-03-21 23:59:47 +01:00
|
|
|
const Slice value = iter_->value();
|
2013-08-19 20:42:47 +02:00
|
|
|
user_merge_operator_->FullMerge(ikey.user_key, &value, operands,
|
2013-12-03 20:17:58 +01:00
|
|
|
&saved_value_, logger_);
|
2013-03-21 23:59:47 +01:00
|
|
|
// iter_ is positioned after put
|
|
|
|
iter_->Next();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (kTypeMerge == ikey.type) {
|
[RocksDB] [MergeOperator] The new Merge Interface! Uses merge sequences.
Summary:
Here are the major changes to the Merge Interface. It has been expanded
to handle cases where the MergeOperator is not associative. It does so by stacking
up merge operations while scanning through the key history (i.e.: during Get() or
Compaction), until a valid Put/Delete/end-of-history is encountered; it then
applies all of the merge operations in the correct sequence starting with the
base/sentinel value.
I have also introduced an "AssociativeMerge" function which allows the user to
take advantage of associative merge operations (such as in the case of counters).
The implementation will always attempt to merge the operations/operands themselves
together when they are encountered, and will resort to the "stacking" method if
and only if the "associative-merge" fails.
This implementation is conjectured to allow MergeOperator to handle the general
case, while still providing the user with the ability to take advantage of certain
efficiencies in their own merge-operator / data-structure.
NOTE: This is a preliminary diff. This must still go through a lot of review,
revision, and testing. Feedback welcome!
Test Plan:
-This is a preliminary diff. I have only just begun testing/debugging it.
-I will be testing this with the existing MergeOperator use-cases and unit-tests
(counters, string-append, and redis-lists)
-I will be "desk-checking" and walking through the code with the help gdb.
-I will find a way of stress-testing the new interface / implementation using
db_bench, db_test, merge_test, and/or db_stress.
-I will ensure that my tests cover all cases: Get-Memtable,
Get-Immutable-Memtable, Get-from-Disk, Iterator-Range-Scan, Flush-Memtable-to-L0,
Compaction-L0-L1, Compaction-Ln-L(n+1), Put/Delete found, Put/Delete not-found,
end-of-history, end-of-file, etc.
-A lot of feedback from the reviewers.
Reviewers: haobo, dhruba, zshao, emayanke
Reviewed By: haobo
CC: leveldb
Differential Revision: https://reviews.facebook.net/D11499
2013-08-06 05:14:32 +02:00
|
|
|
// hit a merge, add the value as an operand and run associative merge.
|
|
|
|
// when complete, add result to operands and continue.
|
|
|
|
const Slice& value = iter_->value();
|
|
|
|
operands.push_front(value.ToString());
|
2013-03-21 23:59:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// we either exhausted all internal keys under this user key, or hit
|
|
|
|
// a deletion marker.
|
[RocksDB] [MergeOperator] The new Merge Interface! Uses merge sequences.
Summary:
Here are the major changes to the Merge Interface. It has been expanded
to handle cases where the MergeOperator is not associative. It does so by stacking
up merge operations while scanning through the key history (i.e.: during Get() or
Compaction), until a valid Put/Delete/end-of-history is encountered; it then
applies all of the merge operations in the correct sequence starting with the
base/sentinel value.
I have also introduced an "AssociativeMerge" function which allows the user to
take advantage of associative merge operations (such as in the case of counters).
The implementation will always attempt to merge the operations/operands themselves
together when they are encountered, and will resort to the "stacking" method if
and only if the "associative-merge" fails.
This implementation is conjectured to allow MergeOperator to handle the general
case, while still providing the user with the ability to take advantage of certain
efficiencies in their own merge-operator / data-structure.
NOTE: This is a preliminary diff. This must still go through a lot of review,
revision, and testing. Feedback welcome!
Test Plan:
-This is a preliminary diff. I have only just begun testing/debugging it.
-I will be testing this with the existing MergeOperator use-cases and unit-tests
(counters, string-append, and redis-lists)
-I will be "desk-checking" and walking through the code with the help gdb.
-I will find a way of stress-testing the new interface / implementation using
db_bench, db_test, merge_test, and/or db_stress.
-I will ensure that my tests cover all cases: Get-Memtable,
Get-Immutable-Memtable, Get-from-Disk, Iterator-Range-Scan, Flush-Memtable-to-L0,
Compaction-L0-L1, Compaction-Ln-L(n+1), Put/Delete found, Put/Delete not-found,
end-of-history, end-of-file, etc.
-A lot of feedback from the reviewers.
Reviewers: haobo, dhruba, zshao, emayanke
Reviewed By: haobo
CC: leveldb
Differential Revision: https://reviews.facebook.net/D11499
2013-08-06 05:14:32 +02:00
|
|
|
// feed null as the existing value to the merge operator, such that
|
2013-03-21 23:59:47 +01:00
|
|
|
// client can differentiate this scenario and do things accordingly.
|
2014-04-01 23:45:30 +02:00
|
|
|
user_merge_operator_->FullMerge(saved_key_.GetKey(), nullptr, operands,
|
2013-12-03 20:17:58 +01:00
|
|
|
&saved_value_, logger_);
|
2013-03-21 23:59:47 +01:00
|
|
|
}
|
|
|
|
|
2011-03-25 21:27:43 +01:00
|
|
|
void DBIter::Prev() {
|
|
|
|
assert(valid_);
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2013-03-21 23:59:47 +01:00
|
|
|
// Throw an exception now if merge_operator is provided
|
[RocksDB] [MergeOperator] The new Merge Interface! Uses merge sequences.
Summary:
Here are the major changes to the Merge Interface. It has been expanded
to handle cases where the MergeOperator is not associative. It does so by stacking
up merge operations while scanning through the key history (i.e.: during Get() or
Compaction), until a valid Put/Delete/end-of-history is encountered; it then
applies all of the merge operations in the correct sequence starting with the
base/sentinel value.
I have also introduced an "AssociativeMerge" function which allows the user to
take advantage of associative merge operations (such as in the case of counters).
The implementation will always attempt to merge the operations/operands themselves
together when they are encountered, and will resort to the "stacking" method if
and only if the "associative-merge" fails.
This implementation is conjectured to allow MergeOperator to handle the general
case, while still providing the user with the ability to take advantage of certain
efficiencies in their own merge-operator / data-structure.
NOTE: This is a preliminary diff. This must still go through a lot of review,
revision, and testing. Feedback welcome!
Test Plan:
-This is a preliminary diff. I have only just begun testing/debugging it.
-I will be testing this with the existing MergeOperator use-cases and unit-tests
(counters, string-append, and redis-lists)
-I will be "desk-checking" and walking through the code with the help gdb.
-I will find a way of stress-testing the new interface / implementation using
db_bench, db_test, merge_test, and/or db_stress.
-I will ensure that my tests cover all cases: Get-Memtable,
Get-Immutable-Memtable, Get-from-Disk, Iterator-Range-Scan, Flush-Memtable-to-L0,
Compaction-L0-L1, Compaction-Ln-L(n+1), Put/Delete found, Put/Delete not-found,
end-of-history, end-of-file, etc.
-A lot of feedback from the reviewers.
Reviewers: haobo, dhruba, zshao, emayanke
Reviewed By: haobo
CC: leveldb
Differential Revision: https://reviews.facebook.net/D11499
2013-08-06 05:14:32 +02:00
|
|
|
// TODO: support backward iteration
|
2013-03-21 23:59:47 +01:00
|
|
|
if (user_merge_operator_) {
|
|
|
|
Log(logger_, "Prev not supported yet if merge_operator is provided");
|
|
|
|
throw std::logic_error("DBIter::Prev backward iteration not supported"
|
|
|
|
" if merge_operator is provided");
|
|
|
|
}
|
|
|
|
|
2011-03-25 21:27:43 +01:00
|
|
|
if (direction_ == kForward) { // Switch directions?
|
|
|
|
// iter_ is pointing at the current entry. Scan backwards until
|
|
|
|
// the key changes so we can use the normal reverse scanning code.
|
|
|
|
assert(iter_->Valid()); // Otherwise valid_ would have been false
|
2014-04-01 23:45:30 +02:00
|
|
|
saved_key_.SetUserKey(ExtractUserKey(iter_->key()));
|
2011-03-25 21:27:43 +01:00
|
|
|
while (true) {
|
|
|
|
iter_->Prev();
|
2011-03-18 23:37:00 +01:00
|
|
|
if (!iter_->Valid()) {
|
2011-03-25 21:27:43 +01:00
|
|
|
valid_ = false;
|
2014-04-01 23:45:30 +02:00
|
|
|
saved_key_.Clear();
|
2011-03-25 21:27:43 +01:00
|
|
|
ClearSavedValue();
|
2011-03-18 23:37:00 +01:00
|
|
|
return;
|
|
|
|
}
|
2011-03-25 21:27:43 +01:00
|
|
|
if (user_comparator_->Compare(ExtractUserKey(iter_->key()),
|
2014-04-01 23:45:30 +02:00
|
|
|
saved_key_.GetKey()) < 0) {
|
2011-03-25 21:27:43 +01:00
|
|
|
break;
|
|
|
|
}
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
2011-03-25 21:27:43 +01:00
|
|
|
direction_ = kReverse;
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
2011-03-25 21:27:43 +01:00
|
|
|
|
|
|
|
FindPrevUserEntry();
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2011-03-25 21:27:43 +01:00
|
|
|
void DBIter::FindPrevUserEntry() {
|
|
|
|
assert(direction_ == kReverse);
|
2013-07-28 20:53:08 +02:00
|
|
|
uint64_t num_skipped = 0;
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2011-03-25 21:27:43 +01:00
|
|
|
ValueType value_type = kTypeDeletion;
|
2014-03-15 00:06:38 +01:00
|
|
|
bool saved_key_valid = true;
|
2011-03-25 21:27:43 +01:00
|
|
|
if (iter_->Valid()) {
|
|
|
|
do {
|
|
|
|
ParsedInternalKey ikey;
|
|
|
|
if (ParseKey(&ikey) && ikey.sequence <= sequence_) {
|
|
|
|
if ((value_type != kTypeDeletion) &&
|
2014-04-01 23:45:30 +02:00
|
|
|
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) < 0) {
|
2011-03-25 21:27:43 +01:00
|
|
|
// We encountered a non-deleted value in entries for previous keys,
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
value_type = ikey.type;
|
|
|
|
if (value_type == kTypeDeletion) {
|
2014-04-01 23:45:30 +02:00
|
|
|
saved_key_.Clear();
|
2011-03-25 21:27:43 +01:00
|
|
|
ClearSavedValue();
|
2014-03-15 00:06:38 +01:00
|
|
|
saved_key_valid = false;
|
2011-03-25 21:27:43 +01:00
|
|
|
} else {
|
|
|
|
Slice raw_value = iter_->value();
|
|
|
|
if (saved_value_.capacity() > raw_value.size() + 1048576) {
|
|
|
|
std::string empty;
|
|
|
|
swap(empty, saved_value_);
|
|
|
|
}
|
2014-04-01 23:45:30 +02:00
|
|
|
saved_key_.SetUserKey(ExtractUserKey(iter_->key()));
|
2011-03-25 21:27:43 +01:00
|
|
|
saved_value_.assign(raw_value.data(), raw_value.size());
|
|
|
|
}
|
2014-03-15 00:06:38 +01:00
|
|
|
} else {
|
|
|
|
// In the case of ikey.sequence > sequence_, we might have already
|
|
|
|
// iterated to a different user key.
|
|
|
|
saved_key_valid = false;
|
2011-03-25 21:27:43 +01:00
|
|
|
}
|
2013-07-28 20:53:08 +02:00
|
|
|
num_skipped++;
|
|
|
|
// If we have sequentially iterated via numerous keys and still not
|
|
|
|
// found the prev user-key, then it is better to seek so that we can
|
|
|
|
// avoid too many key comparisons. We seek to the first occurence of
|
|
|
|
// our current key by looking for max sequence number.
|
2014-03-15 00:06:38 +01:00
|
|
|
if (saved_key_valid && num_skipped > max_skip_) {
|
2013-07-28 20:53:08 +02:00
|
|
|
num_skipped = 0;
|
|
|
|
std::string last_key;
|
2014-04-01 23:45:30 +02:00
|
|
|
AppendInternalKey(&last_key, ParsedInternalKey(saved_key_.GetKey(),
|
|
|
|
kMaxSequenceNumber,
|
|
|
|
kValueTypeForSeek));
|
2013-07-28 20:53:08 +02:00
|
|
|
iter_->Seek(last_key);
|
|
|
|
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
|
|
|
} else {
|
|
|
|
iter_->Prev();
|
|
|
|
}
|
2011-03-25 21:27:43 +01:00
|
|
|
} while (iter_->Valid());
|
|
|
|
}
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2011-03-25 21:27:43 +01:00
|
|
|
if (value_type == kTypeDeletion) {
|
|
|
|
// End
|
|
|
|
valid_ = false;
|
2014-04-01 23:45:30 +02:00
|
|
|
saved_key_.Clear();
|
2011-03-25 21:27:43 +01:00
|
|
|
ClearSavedValue();
|
|
|
|
direction_ = kForward;
|
|
|
|
} else {
|
|
|
|
valid_ = true;
|
|
|
|
}
|
|
|
|
}
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2011-03-25 21:27:43 +01:00
|
|
|
void DBIter::Seek(const Slice& target) {
|
2014-04-01 23:45:30 +02:00
|
|
|
saved_key_.Clear();
|
|
|
|
// now savved_key is used to store internal key.
|
|
|
|
saved_key_.SetInternalKey(target, sequence_);
|
2013-11-18 20:32:54 +01:00
|
|
|
StopWatchNano internal_seek_timer(env_, false);
|
|
|
|
StartPerfTimer(&internal_seek_timer);
|
2014-04-01 23:45:30 +02:00
|
|
|
iter_->Seek(saved_key_.GetKey());
|
2013-11-18 20:32:54 +01:00
|
|
|
BumpPerfTime(&perf_context.seek_internal_seek_time, &internal_seek_timer);
|
2011-03-25 21:27:43 +01:00
|
|
|
if (iter_->Valid()) {
|
2013-11-20 01:37:34 +01:00
|
|
|
direction_ = kForward;
|
|
|
|
ClearSavedValue();
|
2013-03-21 23:59:47 +01:00
|
|
|
FindNextUserEntry(false /*not skipping */);
|
2011-03-25 21:27:43 +01:00
|
|
|
} else {
|
|
|
|
valid_ = false;
|
|
|
|
}
|
|
|
|
}
|
2011-03-18 23:37:00 +01:00
|
|
|
|
2011-03-25 21:27:43 +01:00
|
|
|
void DBIter::SeekToFirst() {
|
|
|
|
direction_ = kForward;
|
|
|
|
ClearSavedValue();
|
2013-11-18 20:32:54 +01:00
|
|
|
StopWatchNano internal_seek_timer(env_, false);
|
|
|
|
StartPerfTimer(&internal_seek_timer);
|
2011-03-25 21:27:43 +01:00
|
|
|
iter_->SeekToFirst();
|
2013-11-18 20:32:54 +01:00
|
|
|
BumpPerfTime(&perf_context.seek_internal_seek_time, &internal_seek_timer);
|
2011-03-25 21:27:43 +01:00
|
|
|
if (iter_->Valid()) {
|
2013-03-21 23:59:47 +01:00
|
|
|
FindNextUserEntry(false /* not skipping */);
|
2011-03-25 21:27:43 +01:00
|
|
|
} else {
|
|
|
|
valid_ = false;
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-25 21:27:43 +01:00
|
|
|
void DBIter::SeekToLast() {
|
[RocksDB] [MergeOperator] The new Merge Interface! Uses merge sequences.
Summary:
Here are the major changes to the Merge Interface. It has been expanded
to handle cases where the MergeOperator is not associative. It does so by stacking
up merge operations while scanning through the key history (i.e.: during Get() or
Compaction), until a valid Put/Delete/end-of-history is encountered; it then
applies all of the merge operations in the correct sequence starting with the
base/sentinel value.
I have also introduced an "AssociativeMerge" function which allows the user to
take advantage of associative merge operations (such as in the case of counters).
The implementation will always attempt to merge the operations/operands themselves
together when they are encountered, and will resort to the "stacking" method if
and only if the "associative-merge" fails.
This implementation is conjectured to allow MergeOperator to handle the general
case, while still providing the user with the ability to take advantage of certain
efficiencies in their own merge-operator / data-structure.
NOTE: This is a preliminary diff. This must still go through a lot of review,
revision, and testing. Feedback welcome!
Test Plan:
-This is a preliminary diff. I have only just begun testing/debugging it.
-I will be testing this with the existing MergeOperator use-cases and unit-tests
(counters, string-append, and redis-lists)
-I will be "desk-checking" and walking through the code with the help gdb.
-I will find a way of stress-testing the new interface / implementation using
db_bench, db_test, merge_test, and/or db_stress.
-I will ensure that my tests cover all cases: Get-Memtable,
Get-Immutable-Memtable, Get-from-Disk, Iterator-Range-Scan, Flush-Memtable-to-L0,
Compaction-L0-L1, Compaction-Ln-L(n+1), Put/Delete found, Put/Delete not-found,
end-of-history, end-of-file, etc.
-A lot of feedback from the reviewers.
Reviewers: haobo, dhruba, zshao, emayanke
Reviewed By: haobo
CC: leveldb
Differential Revision: https://reviews.facebook.net/D11499
2013-08-06 05:14:32 +02:00
|
|
|
// Throw an exception for now if merge_operator is provided
|
2013-03-21 23:59:47 +01:00
|
|
|
// TODO: support backward iteration
|
|
|
|
if (user_merge_operator_) {
|
|
|
|
Log(logger_, "SeekToLast not supported yet if merge_operator is provided");
|
|
|
|
throw std::logic_error("DBIter::SeekToLast: backward iteration not"
|
|
|
|
" supported if merge_operator is provided");
|
|
|
|
}
|
|
|
|
|
2011-03-25 21:27:43 +01:00
|
|
|
direction_ = kReverse;
|
|
|
|
ClearSavedValue();
|
2013-11-18 20:32:54 +01:00
|
|
|
StopWatchNano internal_seek_timer(env_, false);
|
|
|
|
StartPerfTimer(&internal_seek_timer);
|
2011-03-25 21:27:43 +01:00
|
|
|
iter_->SeekToLast();
|
2013-11-18 20:32:54 +01:00
|
|
|
BumpPerfTime(&perf_context.seek_internal_seek_time, &internal_seek_timer);
|
2011-03-25 21:27:43 +01:00
|
|
|
FindPrevUserEntry();
|
|
|
|
}
|
|
|
|
|
2011-03-18 23:37:00 +01:00
|
|
|
} // anonymous namespace
|
|
|
|
|
|
|
|
Iterator* NewDBIterator(
|
|
|
|
const std::string* dbname,
|
|
|
|
Env* env,
|
2013-03-21 23:59:47 +01:00
|
|
|
const Options& options,
|
|
|
|
const Comparator *user_key_comparator,
|
2011-03-18 23:37:00 +01:00
|
|
|
Iterator* internal_iter,
|
|
|
|
const SequenceNumber& sequence) {
|
2013-03-21 23:59:47 +01:00
|
|
|
return new DBIter(dbname, env, options, user_key_comparator,
|
|
|
|
internal_iter, sequence);
|
2011-03-18 23:37:00 +01:00
|
|
|
}
|
|
|
|
|
2013-10-04 06:49:15 +02:00
|
|
|
} // namespace rocksdb
|