From 23c870c7fca17d6e2ed7203e9edbdd35861bb3f9 Mon Sep 17 00:00:00 2001 From: Ramesh Chander Date: Mon, 13 Jun 2016 01:22:14 -0700 Subject: [PATCH] kv: In memory keyvalue db implementation Signed-off-by: Ramesh Chander --- src/include/encoding.h | 28 ++ src/kv/KeyValueDB.cc | 10 + src/kv/KeyValueDB.h | 1 + src/kv/Makefile.am | 4 + src/kv/MemDBStore.cc | 521 ++++++++++++++++++++++++++++++++ src/kv/MemDBStore.h | 208 +++++++++++++ src/test/objectstore/test_kv.cc | 36 ++- 7 files changed, 806 insertions(+), 2 deletions(-) create mode 100644 src/kv/MemDBStore.cc create mode 100644 src/kv/MemDBStore.h diff --git a/src/include/encoding.h b/src/include/encoding.h index 693b2193cce7ef..2825144c1fa266 100644 --- a/src/include/encoding.h +++ b/src/include/encoding.h @@ -1018,4 +1018,32 @@ inline void decode(std::deque& ls, bufferlist::iterator& p) bl.advance(struct_end - bl.get_off()); \ } +/* + * Encoders/decoders to read from current offset in a file handle and + * encode/decode the data according to argument types. + */ +inline ssize_t decode_file(int fd, std::string &str) +{ + bufferlist bl; + __u32 len = 0; + bl.read_fd(fd, sizeof(len)); + decode(len, bl); + bl.read_fd(fd, len); + decode(str, bl); + return bl.length(); +} + +inline ssize_t decode_file(int fd, bufferptr &bp) +{ + bufferlist bl; + __u32 len = 0; + bl.read_fd(fd, sizeof(len)); + decode(len, bl); + bl.read_fd(fd, len); + bufferlist::iterator bli = bl.begin(); + + decode(bp, bli); + return bl.length(); +} + #endif diff --git a/src/kv/KeyValueDB.cc b/src/kv/KeyValueDB.cc index 9437958739ae8a..4e99461053c76e 100644 --- a/src/kv/KeyValueDB.cc +++ b/src/kv/KeyValueDB.cc @@ -3,6 +3,7 @@ #include "KeyValueDB.h" #include "LevelDBStore.h" +#include "MemDBStore.h" #ifdef HAVE_LIBROCKSDB #include "RocksDBStore.h" #endif @@ -29,6 +30,11 @@ KeyValueDB *KeyValueDB::create(CephContext *cct, const string& type, return new RocksDBStore(cct, dir, p); } #endif + + if ((type == "memdb") && + cct->check_experimental_feature_enabled("memdb")) { + return new MSStore(cct, dir, p); + } return NULL; } @@ -47,5 +53,9 @@ int KeyValueDB::test_init(const string& type, const string& dir) return RocksDBStore::_test_init(dir); } #endif + + if (type == "memdb") { + return MSStore::_test_init(dir); + } return -EINVAL; } diff --git a/src/kv/KeyValueDB.h b/src/kv/KeyValueDB.h index 91718ab4d82146..11942c9e08d29c 100644 --- a/src/kv/KeyValueDB.h +++ b/src/kv/KeyValueDB.h @@ -124,6 +124,7 @@ class KeyValueDB { virtual int init(string option_str="") = 0; virtual int open(std::ostream &out) = 0; virtual int create_and_open(std::ostream &out) = 0; + virtual void close() { } virtual Transaction get_transaction() = 0; virtual int submit_transaction(Transaction) = 0; diff --git a/src/kv/Makefile.am b/src/kv/Makefile.am index 789703472d9b46..e01fbb26d5ec9a 100644 --- a/src/kv/Makefile.am +++ b/src/kv/Makefile.am @@ -51,4 +51,8 @@ libkv_a_LIBADD += -lkinetic_client -lprotobuf -lglog -lgflags libcrypto.a noinst_HEADERS += kv/KineticStore.h endif +libkv_a_SOURCES += kv/MemDBStore.cc +noinst_HEADERS += kv/MemDBStore.h + + endif # ENABLE_SERVER diff --git a/src/kv/MemDBStore.cc b/src/kv/MemDBStore.cc new file mode 100644 index 00000000000000..2da259d584c3a4 --- /dev/null +++ b/src/kv/MemDBStore.cc @@ -0,0 +1,521 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * In-memory crash non-safe keyvalue db + * Author: Ramesh Chander, Ramesh.Chander@sandisk.com + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +using std::string; +#include "common/perf_counters.h" +#include "common/debug.h" +#include "include/str_list.h" +#include "include/str_map.h" +#include "KeyValueDB.h" +#include "MemDBStore.h" + +#include "include/assert.h" +#include "common/debug.h" +#include "common/errno.h" + +#define dout_subsys ceph_subsys_ms +#undef dout_prefix +#define dout_prefix *_dout << "ms: " +#define dtrace dout(30) +#define dwarn dout(0) +#define dinfo dout(0) + +void MSStore::_encode(btree::btree_map:: iterator iter, bufferlist &bl) +{ + ::encode(iter->first, bl); + ::encode(iter->second, bl); +} + +std::string MSStore::_get_data_fn() +{ + string fn = m_db_path+"//"+"MemDB.db"; + return fn; +} + +void MSStore::_save() +{ + std::lock_guard l(m_lock); + int mode = 0644; + int fd = TEMP_FAILURE_RETRY(::open(_get_data_fn().c_str(), + O_WRONLY|O_CREAT|O_TRUNC, mode)); + if (fd < 0) { + int err = errno; + cerr << "write_file(" << _get_data_fn().c_str() << "): failed to open file: " + << cpp_strerror(err) << std::endl; + return; + } + btree::btree_map::iterator iter = m_btree.begin(); + while (iter != m_btree.end()) { + bufferlist bl; + dout(10) << __func__ << " Key:"<< iter->first << dendl; + _encode(iter, bl); + bl.write_fd(fd); + iter++; + } + + ::close(fd); +} + +void MSStore::_load() +{ + std::lock_guard l(m_lock); + /* + * Open file and read it in single shot. + */ + int fd = TEMP_FAILURE_RETRY(::open(_get_data_fn().c_str(), O_RDONLY)); + if (fd < 0) { + std::ostringstream oss; + oss << "can't open " << _get_data_fn().c_str() << ": " << std::endl; + return; + } + + struct stat st; + memset(&st, 0, sizeof(st)); + if (::fstat(fd, &st) < 0) { + std::ostringstream oss; + oss << "can't stat file " << _get_data_fn().c_str() << ": " << std::endl; + return; + } + + ssize_t file_size = st.st_size; + ssize_t bytes_done = 0; + while (bytes_done < file_size) { + string key; + bufferptr datap; + + bytes_done += ::decode_file(fd, key); + bytes_done += ::decode_file(fd, datap); + + dout(10) << __func__ << " Key:"<< key << dendl; + m_btree[key] = datap; + } + ::close(fd); +} + +int MSStore::_init(bool format) +{ + if (!format) { + _load(); + } + + return 0; +} + +int MSStore::set_merge_operator( + const string& prefix, + std::shared_ptr mop) +{ + merge_ops.push_back(std::make_pair(prefix, mop)); + return 0; +} + +int MSStore::do_open(ostream &out, bool create) +{ + m_total_bytes = 0; + m_allocated_bytes = 0; + + return _init(create); +} + +MSStore::~MSStore() +{ + close(); +} + +void MSStore::close() +{ + /* + * Save whatever in memory btree. + */ + _save(); +} + +int MSStore::submit_transaction(KeyValueDB::Transaction t) +{ + MSTransactionImpl* mt = static_cast(t.get()); + + dtrace << __func__ << " " << mt->get_ops().size() << dendl; + for(auto& op : mt->get_ops()) { + if(op.first == MSTransactionImpl::WRITE) { + ms_op_t set_op = op.second; + _setkey(set_op); + } else if (op.first == MSTransactionImpl::MERGE) { + ms_op_t merge_op = op.second; + _merge(merge_op); + } else { + ms_op_t rm_op = op.second; + assert(op.first == MSTransactionImpl::DELETE); + _rmkey(rm_op); + } + } + + return 0; +} + +int MSStore::submit_transaction_sync(KeyValueDB::Transaction tsync) +{ + dtrace << __func__ << " " << dendl; + submit_transaction(tsync); + return 0; +} + +int MSStore::transaction_rollback(KeyValueDB::Transaction t) +{ + MSTransactionImpl* mt = static_cast(t.get()); + mt->clear(); + return 0; +} + +void MSStore::MSTransactionImpl::set( + const string &prefix, const string &k, const bufferlist &to_set_bl) +{ + dtrace << __func__ << " " << prefix << " " << k << dendl; + ops.push_back(make_pair(WRITE, std::make_pair(std::make_pair(prefix, k), + to_set_bl))); +} + +void MSStore::MSTransactionImpl::rmkey(const string &prefix, + const string &k) +{ + dtrace << __func__ << " " << prefix << " " << k << dendl; + ops.push_back(make_pair(DELETE, + std::make_pair(std::make_pair(prefix, k), + NULL))); +} + +void MSStore::MSTransactionImpl::rmkeys_by_prefix(const string &prefix) +{ + KeyValueDB::Iterator it = m_db->get_iterator(prefix); + for (it->seek_to_first(); it->valid(); it->next()) { + rmkey(prefix, it->key()); + } +} + +void MSStore::MSTransactionImpl::merge( + const std::string &prefix, const std::string &key, const bufferlist &value) +{ + + dtrace << __func__ << " " << prefix << " " << key << dendl; + ops.push_back(make_pair(MERGE, make_pair(std::make_pair(prefix, key), value))); + return; +} + +int MSStore::_setkey(ms_op_t &op) +{ + std::lock_guard l(m_lock); + std::string key = combine_strings(op.first.first, op.first.second); + bufferlist bl = op.second; + + m_total_bytes += bl.length(); + + bufferlist bl_old; + if (_get(op.first.first, op.first.second, &bl_old)) { + /* + * delete and free existing key. + */ + m_total_bytes -= bl_old.length(); + m_btree.erase(key); + } + + m_btree[key] = bufferptr((char *) bl.c_str(), bl.length()); + + return 0; +} + +int MSStore::_rmkey(ms_op_t &op) +{ + std::lock_guard l(m_lock); + std::string key = combine_strings(op.first.first, op.first.second); + + bufferlist bl_old; + if (_get(op.first.first, op.first.second, &bl_old)) { + m_total_bytes -= bl_old.length(); + } + /* + * Erase will call the destructor for bufferptr. + */ + return m_btree.erase(key); +} + +std::shared_ptr MSStore::_find_merge_op(std::string prefix) +{ + for (const auto& i : merge_ops) { + if (i.first == prefix) { + return i.second; + } + } + + dtrace << __func__ << " No merge op for " << prefix << dendl; + return NULL; +} + + +int MSStore::_merge(ms_op_t &op) +{ + std::lock_guard l(m_lock); + std::string prefix = op.first.first; + std::string key = combine_strings(op.first.first, op.first.second); + bufferlist bl = op.second; + int64_t bytes_adjusted = bl.length(); + + /* + * find the operator for this prefix + */ + std::shared_ptr mop = _find_merge_op(prefix); + assert(mop); + + /* + * call the merge operator with value and non value + */ + bufferlist bl_old; + if (_get(op.first.first, op.first.second, &bl_old) == false) { + std::string new_val; + /* + * Merge non existent. + */ + mop->merge_nonexistent(bl.c_str(), bl.length(), &new_val); + m_btree[key] = bufferptr(new_val.c_str(), new_val.length()); + } else { + /* + * Merge existing. + */ + std::string new_val; + mop->merge(bl_old.c_str(), bl_old.length(), bl.c_str(), bl.length(), &new_val); + m_btree[key] = bufferptr(new_val.c_str(), new_val.length()); + bytes_adjusted -= bl_old.length(); + bl_old.clear(); + } + + m_total_bytes += bytes_adjusted; + return 0; +} + +/* + * Caller take btree lock. + */ +bool MSStore::_get(const string &prefix, const string &k, bufferlist *out) +{ + string key = combine_strings(prefix, k); + + btree::btree_map::iterator iter = m_btree.find(key); + if (iter == m_btree.end()) { + return false; + } + + out->push_back((m_btree[key].clone())); + return true; +} + +bool MSStore::_get_locked(const string &prefix, const string &k, bufferlist *out) +{ + std::lock_guard l(m_lock); + return _get(prefix, k, out); +} + +int MSStore::get(const string &prefix, const std::set &keys, + std::map *out) +{ + for (const auto& i : keys) { + bufferlist bl; + if (_get_locked(prefix, i, &bl)) + out->insert(make_pair(i, bl)); + } + + return 0; +} + +void MSStore::MSWholeSpaceIteratorImpl::tokenize(const std::string *s, + char space, std::vector *tokens) +{ + size_t i1 = 0; + + i1 = s->find(space, 0); + tokens->push_back(s->substr(0, i1)); + tokens->push_back(s->substr(i1 + 1, s->length())); +} + + +int MSStore::MSWholeSpaceIteratorImpl::split_key(const string *pkey, + string *prefix, string *key) +{ + + char delim = KEY_DELIM; + std::vector tokens; + tokenize(pkey, delim, &tokens); + + assert(tokens.size() == 2); + *prefix = tokens[0]; + *key = tokens[1]; + + return 0; +} + +void MSStore::MSWholeSpaceIteratorImpl::fill_current() +{ + bufferlist bl; + bl.append(m_iter->second.clone()); + m_key_value = std::make_pair(m_iter->first, bl); +} + +bool MSStore::MSWholeSpaceIteratorImpl::valid() +{ + if (m_key_value.first.empty()) { + return false; + } + return true; +} + +void +MSStore::MSWholeSpaceIteratorImpl::free_last() +{ + m_key_value.first.clear(); + assert(m_key_value.first.empty()); + m_key_value.second.clear(); +} + +string MSStore::MSWholeSpaceIteratorImpl::key() +{ + dtrace << __func__ << " " << m_key_value.first << dendl; + string prefix, key; + split_key(&m_key_value.first, &prefix, &key); + return key; +} + +pair MSStore::MSWholeSpaceIteratorImpl::raw_key() +{ + string prefix, key; + split_key(&m_key_value.first, &prefix, &key); + return make_pair(prefix, key); +} + +bool MSStore::MSWholeSpaceIteratorImpl::raw_key_is_prefixed( + const string &prefix) +{ + std::vector tokens; + tokenize(&m_key_value.first, KEY_DELIM, &tokens); + if (tokens[0] == prefix) { + return true; + } + return false; +} + +bufferlist MSStore::MSWholeSpaceIteratorImpl::value() +{ + dtrace << __func__ << " " << m_key_value << dendl; + return m_key_value.second; +} + +int MSStore::MSWholeSpaceIteratorImpl::next() +{ + std::lock_guard l(*m_btree_lock_p); + free_last(); + m_iter++; + if (m_iter != m_btree_p->end()) { + fill_current(); + return 0; + } else { + return -1; + } +} + +int MSStore::MSWholeSpaceIteratorImpl:: prev() +{ + std::lock_guard l(*m_btree_lock_p); + free_last(); + m_iter--; + if (m_iter != m_btree_p->end()) { + fill_current(); + return 0; + } else { + return -1; + } +} + +/* + * First key >= to given key, if key is null then first key in btree. + */ +int MSStore::MSWholeSpaceIteratorImpl::seek_to_first(const std::string &k) +{ + std::lock_guard l(*m_btree_lock_p); + free_last(); + if (k.empty()) { + m_iter = m_btree_p->begin(); + } else { + m_iter = m_btree_p->lower_bound(k); + } + + if (m_iter == m_btree_p->end()) { + return -1; + } + return 0; +} + +int MSStore::MSWholeSpaceIteratorImpl::seek_to_last(const std::string &k) +{ + std::lock_guard l(*m_btree_lock_p); + + free_last(); + if (k.empty()) { + m_iter = m_btree_p->end(); + m_iter--; + } else { + m_iter = m_btree_p->lower_bound(k); + } + + if (m_iter == m_btree_p->end()) { + return -1; + } + return 0; +} + +MSStore::MSWholeSpaceIteratorImpl::~MSWholeSpaceIteratorImpl() +{ + free_last(); +} + +KeyValueDB::WholeSpaceIterator MSStore::_get_snapshot_iterator() +{ + assert(0); +} + +int MSStore::MSWholeSpaceIteratorImpl::upper_bound(const std::string &prefix, + const std::string &after) { + + std::lock_guard l(*m_btree_lock_p); + + dtrace << "upper_bound " << prefix.c_str() << after.c_str() << dendl; + string k = combine_strings(prefix, after); + m_iter = m_btree_p->upper_bound(k); + if (m_iter != m_btree_p->end()) { + fill_current(); + return 0; + } + return -1; +} + +int MSStore::MSWholeSpaceIteratorImpl::lower_bound(const std::string &prefix, + const std::string &to) { + std::lock_guard l(*m_btree_lock_p); + dtrace << "lower_bound " << prefix.c_str() << to.c_str() << dendl; + string k = combine_strings(prefix, to); + m_iter = m_btree_p->lower_bound(k); + if (m_iter != m_btree_p->end()) { + fill_current(); + return 0; + } + return -1; +} diff --git a/src/kv/MemDBStore.h b/src/kv/MemDBStore.h new file mode 100644 index 00000000000000..022b972ec38586 --- /dev/null +++ b/src/kv/MemDBStore.h @@ -0,0 +1,208 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * In-memory crash non-safe keyvalue db + * Author: Ramesh Chander, Ramesh.Chander@sandisk.com + */ + +#ifndef CEPH_OS_BLUESTORE_MEMDBDB_H +#define CEPH_OS_BLUESTORE_MEMDBDB_H + +#include "include/buffer.h" +#include +#include +#include +#include +#include +#include "include/memory.h" +#include +#include "include/encoding.h" +#include "include/cpp-btree/btree.h" +#include "include/cpp-btree/btree_map.h" +#include "include/encoding_btree.h" +#include "KeyValueDB.h" +#include "osd/osd_types.h" + +using std::string; +#define KEY_DELIM 'X' + +static inline +string combine_strings(const string &prefix, const string &value) +{ + string out = prefix; + out.push_back(KEY_DELIM); + out.append(value); + return out; +} + +#define CEPH_MS_CONTAINER_NAME "ceph_kv_store" + +class MSStore : public KeyValueDB +{ + typedef std::pair, bufferlist> ms_op_t; + std::mutex m_lock; + uint64_t m_total_bytes; + uint64_t m_allocated_bytes; + + btree::btree_map m_btree; + CephContext *m_cct; + void* m_priv; + string m_options; + string m_db_path; + + int transaction_rollback(KeyValueDB::Transaction t); + int _open(ostream &out); + void close(); + bool _get(const string &prefix, const string &k, bufferlist *out); + bool _get_locked(const string &prefix, const string &k, bufferlist *out); + std::string _get_data_fn(); + void _encode(btree::btree_map:: iterator iter, bufferlist &bl); + void _save(); + void _load(); + +public: + MSStore(CephContext *c, const string &path, void *p) : + m_cct(c), m_priv(p), m_db_path(path) + { + //Nothing as of now + } + + ~MSStore(); + virtual int set_merge_operator(const std::string& prefix, + std::shared_ptr mop); + + std::shared_ptr _find_merge_op(std::string prefix); + + static + int _test_init(const string& dir) { return 0; }; + + class MSTransactionImpl : public KeyValueDB::TransactionImpl { + public: + enum op_type { WRITE = 1, MERGE = 2, DELETE = 3}; + private: + + std::vector> ops; + MSStore *m_db; + + bool key_is_prefixed(const string &prefix, const string& full_key); + public: + const std::vector>& + get_ops() { return ops; }; + + void set(const std::string &prefix, const std::string &key, + const bufferlist &val); + void rmkey(const std::string &prefix, const std::string &k); + void rmkeys_by_prefix(const std::string &prefix); + + void merge(const std::string &prefix, const std::string &key, const bufferlist &value); + void clear() { + ops.clear(); + } + MSTransactionImpl(MSStore* _db) :m_db(_db) + { + ops.clear(); + } + ~MSTransactionImpl() {}; + }; + +private: + + /* + * Transaction states. + */ + int _merge(const std::string &k, bufferptr &bl); + int _merge(ms_op_t &op); + int _setkey(ms_op_t &op); + int _rmkey(ms_op_t &op); + +public: + + int init(string option_str="") { m_options = option_str; return 0; } + int _init(bool format); + + int do_open(ostream &out, bool create); + int open(ostream &out) { return do_open(out, false); } + int create_and_open(ostream &out) { return do_open(out, true); } + + KeyValueDB::Transaction get_transaction() { + return std::shared_ptr(new MSTransactionImpl(this)); + } + + int submit_transaction(Transaction); + int submit_transaction_sync(Transaction); + + int get(const std::string &prefix, const std::set &key, + std::map *out); + + class MSWholeSpaceIteratorImpl : public KeyValueDB::WholeSpaceIteratorImpl { + + btree::btree_map::iterator m_iter; + std::pair m_key_value; + btree::btree_map *m_btree_p; + std::mutex *m_btree_lock_p; + + int split_key(const string *pkey, string *prefix, string *key); + void tokenize(const std::string *s, char space, std::vector *tokens); + + public: + MSWholeSpaceIteratorImpl(btree::btree_map *btree_p, + std::mutex *btree_lock_p) { + m_btree_p = btree_p; + m_btree_lock_p = btree_lock_p; + } + + void fill_current(); + void free_last(); + + + int seek_to_first(const std::string &k); + int seek_to_last(const std::string &k); + + int seek_to_first() { return seek_to_first(NULL); }; + int seek_to_last() { return seek_to_last(NULL); }; + + int upper_bound(const std::string &prefix, const std::string &after); + int lower_bound(const std::string &prefix, const std::string &to); + bool valid(); + + int next(); + int prev(); + int status() { return 0; }; + + std::string key(); + std::pair raw_key(); + bool raw_key_is_prefixed(const std::string &prefix); + bufferlist value(); + ~MSWholeSpaceIteratorImpl(); + }; + + uint64_t get_estimated_size(std::map &extra) { + std::lock_guard l(m_lock); + return m_allocated_bytes; + }; + + int get_statfs(struct store_statfs_t *buf) { + std::lock_guard l(m_lock); + store_statfs_t s; + s.total = m_total_bytes; + s.allocated = m_allocated_bytes; + s.stored = m_total_bytes; + s.compressed = 0; + s.compressed_allocated = 0; + s.compressed_original = 0; + *buf = s; + return 0; + } + +protected: + + WholeSpaceIterator _get_iterator() { + return std::shared_ptr( + new MSWholeSpaceIteratorImpl(&m_btree, &m_lock)); + } + + WholeSpaceIterator _get_snapshot_iterator(); +}; + +#endif + diff --git a/src/test/objectstore/test_kv.cc b/src/test/objectstore/test_kv.cc index 57d2d48056f68b..8aed8ade72a3bd 100644 --- a/src/test/objectstore/test_kv.cc +++ b/src/test/objectstore/test_kv.cc @@ -83,6 +83,37 @@ TEST_P(KVTest, OpenCloseReopenClose) { fini(); } +/* + * Basic write and read test case in same database session. + */ +TEST_P(KVTest, OpenWriteRead) { + ASSERT_EQ(0, db->create_and_open(cout)); + { + KeyValueDB::Transaction t = db->get_transaction(); + bufferlist value; + value.append("value"); + t->set("prefix", "key", value); + value.clear(); + value.append("value2"); + t->set("prefix", "key2", value); + value.clear(); + value.append("value3"); + t->set("prefix", "key3", value); + db->submit_transaction_sync(t); + + bufferlist v1, v2; + ASSERT_EQ(0, db->get("prefix", "key", &v1)); + ASSERT_EQ(v1.length(), 5u); + (v1.c_str())[v1.length()] = 0x0; + ASSERT_EQ(std::string(v1.c_str()), std::string("value")); + ASSERT_EQ(0, db->get("prefix", "key2", &v2)); + ASSERT_EQ(v2.length(), 6u); + (v2.c_str())[v2.length()] = 0x0; + ASSERT_EQ(std::string(v2.c_str()), std::string("value2")); + } + fini(); +} + TEST_P(KVTest, PutReopen) { ASSERT_EQ(0, db->create_and_open(cout)); { @@ -168,6 +199,7 @@ struct AppendMOP : public KeyValueDB::MergeOperator { const char *ldata, size_t llen, const char *rdata, size_t rlen, std::string *new_value) { + *new_value = std::string(ldata, llen) + std::string(rdata, rlen); } // We use each operator name and each prefix to construct the @@ -227,7 +259,7 @@ TEST_P(KVTest, Merge) { INSTANTIATE_TEST_CASE_P( KeyValueDB, KVTest, - ::testing::Values("leveldb", "rocksdb")); + ::testing::Values("leveldb", "rocksdb", "memdb")); #else @@ -250,7 +282,7 @@ int main(int argc, char **argv) { common_init_finish(g_ceph_context); g_ceph_context->_conf->set_val( "enable_experimental_unrecoverable_data_corrupting_features", - "rocksdb"); + "rocksdb, memdb"); g_ceph_context->_conf->apply_changes(NULL); ::testing::InitGoogleTest(&argc, argv);