1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
9 #include "db/db_test_util.h"
10 #include "db/memtable.h"
11 #include "port/stack_trace.h"
12 #include "rocksdb/memtablerep.h"
13 #include "rocksdb/slice_transform.h"
17 class DBMemTableTest
: public DBTestBase
{
19 DBMemTableTest() : DBTestBase("/db_memtable_test") {}
22 class MockMemTableRep
: public MemTableRep
{
24 explicit MockMemTableRep(Allocator
* allocator
, MemTableRep
* rep
)
25 : MemTableRep(allocator
), rep_(rep
), num_insert_with_hint_(0) {}
27 virtual KeyHandle
Allocate(const size_t len
, char** buf
) override
{
28 return rep_
->Allocate(len
, buf
);
31 virtual void Insert(KeyHandle handle
) override
{ rep_
->Insert(handle
); }
33 virtual void InsertWithHint(KeyHandle handle
, void** hint
) override
{
34 num_insert_with_hint_
++;
35 EXPECT_NE(nullptr, hint
);
36 last_hint_in_
= *hint
;
37 rep_
->InsertWithHint(handle
, hint
);
38 last_hint_out_
= *hint
;
41 virtual bool Contains(const char* key
) const override
{
42 return rep_
->Contains(key
);
45 virtual void Get(const LookupKey
& k
, void* callback_args
,
46 bool (*callback_func
)(void* arg
,
47 const char* entry
)) override
{
48 rep_
->Get(k
, callback_args
, callback_func
);
51 virtual size_t ApproximateMemoryUsage() override
{
52 return rep_
->ApproximateMemoryUsage();
55 virtual Iterator
* GetIterator(Arena
* arena
) override
{
56 return rep_
->GetIterator(arena
);
59 void* last_hint_in() { return last_hint_in_
; }
60 void* last_hint_out() { return last_hint_out_
; }
61 int num_insert_with_hint() { return num_insert_with_hint_
; }
64 std::unique_ptr
<MemTableRep
> rep_
;
67 int num_insert_with_hint_
;
70 class MockMemTableRepFactory
: public MemTableRepFactory
{
72 virtual MemTableRep
* CreateMemTableRep(const MemTableRep::KeyComparator
& cmp
,
74 const SliceTransform
* transform
,
75 Logger
* logger
) override
{
76 SkipListFactory factory
;
77 MemTableRep
* skiplist_rep
=
78 factory
.CreateMemTableRep(cmp
, allocator
, transform
, logger
);
79 mock_rep_
= new MockMemTableRep(allocator
, skiplist_rep
);
83 virtual MemTableRep
* CreateMemTableRep(const MemTableRep::KeyComparator
& cmp
,
85 const SliceTransform
* transform
,
87 uint32_t column_family_id
) override
{
88 last_column_family_id_
= column_family_id
;
89 return CreateMemTableRep(cmp
, allocator
, transform
, logger
);
92 virtual const char* Name() const override
{ return "MockMemTableRepFactory"; }
94 MockMemTableRep
* rep() { return mock_rep_
; }
96 bool IsInsertConcurrentlySupported() const override
{ return false; }
98 uint32_t GetLastColumnFamilyId() { return last_column_family_id_
; }
101 MockMemTableRep
* mock_rep_
;
102 // workaround since there's no port::kMaxUint32 yet.
103 uint32_t last_column_family_id_
= static_cast<uint32_t>(-1);
106 class TestPrefixExtractor
: public SliceTransform
{
108 virtual const char* Name() const override
{ return "TestPrefixExtractor"; }
110 virtual Slice
Transform(const Slice
& key
) const override
{
111 const char* p
= separator(key
);
115 return Slice(key
.data(), p
- key
.data() + 1);
118 virtual bool InDomain(const Slice
& key
) const override
{
119 return separator(key
) != nullptr;
122 virtual bool InRange(const Slice
& /*key*/) const override
{ return false; }
125 const char* separator(const Slice
& key
) const {
126 return reinterpret_cast<const char*>(memchr(key
.data(), '_', key
.size()));
130 // Test that ::Add properly returns false when inserting duplicate keys
131 TEST_F(DBMemTableTest
, DuplicateSeq
) {
132 SequenceNumber seq
= 123;
135 MergeContext merge_context
;
137 InternalKeyComparator
ikey_cmp(options
.comparator
);
138 RangeDelAggregator
range_del_agg(ikey_cmp
, {} /* snapshots */);
141 InternalKeyComparator
cmp(BytewiseComparator());
142 auto factory
= std::make_shared
<SkipListFactory
>();
143 options
.memtable_factory
= factory
;
144 ImmutableCFOptions
ioptions(options
);
145 WriteBufferManager
wb(options
.db_write_buffer_size
);
146 MemTable
* mem
= new MemTable(cmp
, ioptions
, MutableCFOptions(options
), &wb
,
147 kMaxSequenceNumber
, 0 /* column_family_id */);
149 // Write some keys and make sure it returns false on duplicates
151 res
= mem
->Add(seq
, kTypeValue
, "key", "value2");
153 res
= mem
->Add(seq
, kTypeValue
, "key", "value2");
155 // Changing the type should still cause the duplicatae key
156 res
= mem
->Add(seq
, kTypeMerge
, "key", "value2");
158 // Changing the seq number will make the key fresh
159 res
= mem
->Add(seq
+ 1, kTypeMerge
, "key", "value2");
161 // Test with different types for duplicate keys
162 res
= mem
->Add(seq
, kTypeDeletion
, "key", "");
164 res
= mem
->Add(seq
, kTypeSingleDeletion
, "key", "");
167 // Test the duplicate keys under stress
168 for (int i
= 0; i
< 10000; i
++) {
169 bool insert_dup
= i
% 10 == 1;
173 res
= mem
->Add(seq
, kTypeValue
, "foo", "value" + ToString(seq
));
182 // Test with InsertWithHint
183 options
.memtable_insert_with_hint_prefix_extractor
.reset(
184 new TestPrefixExtractor()); // which uses _ to extract the prefix
185 ioptions
= ImmutableCFOptions(options
);
186 mem
= new MemTable(cmp
, ioptions
, MutableCFOptions(options
), &wb
,
187 kMaxSequenceNumber
, 0 /* column_family_id */);
188 // Insert a duplicate key with _ in it
189 res
= mem
->Add(seq
, kTypeValue
, "key_1", "value");
191 res
= mem
->Add(seq
, kTypeValue
, "key_1", "value");
195 // Test when InsertConcurrently will be invoked
196 options
.allow_concurrent_memtable_write
= true;
197 ioptions
= ImmutableCFOptions(options
);
198 mem
= new MemTable(cmp
, ioptions
, MutableCFOptions(options
), &wb
,
199 kMaxSequenceNumber
, 0 /* column_family_id */);
200 MemTablePostProcessInfo post_process_info
;
201 res
= mem
->Add(seq
, kTypeValue
, "key", "value", true, &post_process_info
);
203 res
= mem
->Add(seq
, kTypeValue
, "key", "value", true, &post_process_info
);
208 TEST_F(DBMemTableTest
, InsertWithHint
) {
210 options
.allow_concurrent_memtable_write
= false;
211 options
.create_if_missing
= true;
212 options
.memtable_factory
.reset(new MockMemTableRepFactory());
213 options
.memtable_insert_with_hint_prefix_extractor
.reset(
214 new TestPrefixExtractor());
217 MockMemTableRep
* rep
=
218 reinterpret_cast<MockMemTableRepFactory
*>(options
.memtable_factory
.get())
220 ASSERT_OK(Put("foo_k1", "foo_v1"));
221 ASSERT_EQ(nullptr, rep
->last_hint_in());
222 void* hint_foo
= rep
->last_hint_out();
223 ASSERT_OK(Put("foo_k2", "foo_v2"));
224 ASSERT_EQ(hint_foo
, rep
->last_hint_in());
225 ASSERT_EQ(hint_foo
, rep
->last_hint_out());
226 ASSERT_OK(Put("foo_k3", "foo_v3"));
227 ASSERT_EQ(hint_foo
, rep
->last_hint_in());
228 ASSERT_EQ(hint_foo
, rep
->last_hint_out());
229 ASSERT_OK(Put("bar_k1", "bar_v1"));
230 ASSERT_EQ(nullptr, rep
->last_hint_in());
231 void* hint_bar
= rep
->last_hint_out();
232 ASSERT_NE(hint_foo
, hint_bar
);
233 ASSERT_OK(Put("bar_k2", "bar_v2"));
234 ASSERT_EQ(hint_bar
, rep
->last_hint_in());
235 ASSERT_EQ(hint_bar
, rep
->last_hint_out());
236 ASSERT_EQ(5, rep
->num_insert_with_hint());
237 ASSERT_OK(Put("whitelisted", "vvv"));
238 ASSERT_EQ(5, rep
->num_insert_with_hint());
239 ASSERT_EQ("foo_v1", Get("foo_k1"));
240 ASSERT_EQ("foo_v2", Get("foo_k2"));
241 ASSERT_EQ("foo_v3", Get("foo_k3"));
242 ASSERT_EQ("bar_v1", Get("bar_k1"));
243 ASSERT_EQ("bar_v2", Get("bar_k2"));
244 ASSERT_EQ("vvv", Get("whitelisted"));
247 TEST_F(DBMemTableTest
, ColumnFamilyId
) {
248 // Verifies MemTableRepFactory is told the right column family id.
250 options
.allow_concurrent_memtable_write
= false;
251 options
.create_if_missing
= true;
252 options
.memtable_factory
.reset(new MockMemTableRepFactory());
253 DestroyAndReopen(options
);
254 CreateAndReopenWithCF({"pikachu"}, options
);
256 for (int cf
= 0; cf
< 2; ++cf
) {
257 ASSERT_OK(Put(cf
, "key", "val"));
258 ASSERT_OK(Flush(cf
));
260 cf
, static_cast<MockMemTableRepFactory
*>(options
.memtable_factory
.get())
261 ->GetLastColumnFamilyId());
265 } // namespace rocksdb
267 int main(int argc
, char** argv
) {
268 rocksdb::port::InstallStackTraceHandler();
269 ::testing::InitGoogleTest(&argc
, argv
);
270 return RUN_ALL_TESTS();