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).
13 #include "table/meta_blocks.h"
14 #include "table/cuckoo_table_builder.h"
15 #include "util/file_reader_writer.h"
16 #include "util/testharness.h"
17 #include "util/testutil.h"
20 extern const uint64_t kCuckooTableMagicNumber
;
23 std::unordered_map
<std::string
, std::vector
<uint64_t>> hash_map
;
25 uint64_t GetSliceHash(const Slice
& s
, uint32_t index
,
26 uint64_t /*max_num_buckets*/) {
27 return hash_map
[s
.ToString()][index
];
31 class CuckooBuilderTest
: public testing::Test
{
34 env_
= Env::Default();
36 options
.allow_mmap_reads
= true;
37 env_options_
= EnvOptions(options
);
40 void CheckFileContents(const std::vector
<std::string
>& keys
,
41 const std::vector
<std::string
>& values
,
42 const std::vector
<uint64_t>& expected_locations
,
43 std::string expected_unused_bucket
, uint64_t expected_table_size
,
44 uint32_t expected_num_hash_func
, bool expected_is_last_level
,
45 uint32_t expected_cuckoo_block_size
= 1) {
46 uint64_t num_deletions
= 0;
47 for (const auto& key
: keys
) {
48 ParsedInternalKey parsed
;
49 if (ParseInternalKey(key
, &parsed
) && parsed
.type
== kTypeDeletion
) {
54 std::unique_ptr
<RandomAccessFile
> read_file
;
55 ASSERT_OK(env_
->NewRandomAccessFile(fname
, &read_file
, env_options_
));
56 uint64_t read_file_size
;
57 ASSERT_OK(env_
->GetFileSize(fname
, &read_file_size
));
59 // @lint-ignore TXT2 T25377293 Grandfathered in
61 options
.allow_mmap_reads
= true;
62 ImmutableCFOptions
ioptions(options
);
64 // Assert Table Properties.
65 TableProperties
* props
= nullptr;
66 std::unique_ptr
<RandomAccessFileReader
> file_reader(
67 new RandomAccessFileReader(std::move(read_file
), fname
));
68 ASSERT_OK(ReadTableProperties(file_reader
.get(), read_file_size
,
69 kCuckooTableMagicNumber
, ioptions
,
70 &props
, true /* compression_type_missing */));
71 // Check unused bucket.
72 std::string unused_key
= props
->user_collected_properties
[
73 CuckooTablePropertyNames::kEmptyKey
];
74 ASSERT_EQ(expected_unused_bucket
.substr(0,
75 props
->fixed_key_len
), unused_key
);
77 uint64_t value_len_found
=
78 *reinterpret_cast<const uint64_t*>(props
->user_collected_properties
[
79 CuckooTablePropertyNames::kValueLength
].data());
80 ASSERT_EQ(values
.empty() ? 0 : values
[0].size(), value_len_found
);
81 ASSERT_EQ(props
->raw_value_size
, values
.size()*value_len_found
);
82 const uint64_t table_size
=
83 *reinterpret_cast<const uint64_t*>(props
->user_collected_properties
[
84 CuckooTablePropertyNames::kHashTableSize
].data());
85 ASSERT_EQ(expected_table_size
, table_size
);
86 const uint32_t num_hash_func_found
=
87 *reinterpret_cast<const uint32_t*>(props
->user_collected_properties
[
88 CuckooTablePropertyNames::kNumHashFunc
].data());
89 ASSERT_EQ(expected_num_hash_func
, num_hash_func_found
);
90 const uint32_t cuckoo_block_size
=
91 *reinterpret_cast<const uint32_t*>(props
->user_collected_properties
[
92 CuckooTablePropertyNames::kCuckooBlockSize
].data());
93 ASSERT_EQ(expected_cuckoo_block_size
, cuckoo_block_size
);
94 const bool is_last_level_found
=
95 *reinterpret_cast<const bool*>(props
->user_collected_properties
[
96 CuckooTablePropertyNames::kIsLastLevel
].data());
97 ASSERT_EQ(expected_is_last_level
, is_last_level_found
);
99 ASSERT_EQ(props
->num_entries
, keys
.size());
100 ASSERT_EQ(props
->num_deletions
, num_deletions
);
101 ASSERT_EQ(props
->fixed_key_len
, keys
.empty() ? 0 : keys
[0].size());
102 ASSERT_EQ(props
->data_size
, expected_unused_bucket
.size() *
103 (expected_table_size
+ expected_cuckoo_block_size
- 1));
104 ASSERT_EQ(props
->raw_key_size
, keys
.size()*props
->fixed_key_len
);
105 ASSERT_EQ(props
->column_family_id
, 0);
106 ASSERT_EQ(props
->column_family_name
, kDefaultColumnFamilyName
);
109 // Check contents of the bucket.
110 std::vector
<bool> keys_found(keys
.size(), false);
111 size_t bucket_size
= expected_unused_bucket
.size();
112 for (uint32_t i
= 0; i
< table_size
+ cuckoo_block_size
- 1; ++i
) {
114 ASSERT_OK(file_reader
->Read(i
* bucket_size
, bucket_size
, &read_slice
,
117 std::find(expected_locations
.begin(), expected_locations
.end(), i
) -
118 expected_locations
.begin();
119 if (key_idx
== keys
.size()) {
120 // i is not one of the expected locations. Empty bucket.
121 if (read_slice
.data() == nullptr) {
122 ASSERT_EQ(0, expected_unused_bucket
.size());
124 ASSERT_EQ(read_slice
.compare(expected_unused_bucket
), 0);
127 keys_found
[key_idx
] = true;
128 ASSERT_EQ(read_slice
.compare(keys
[key_idx
] + values
[key_idx
]), 0);
131 for (auto key_found
: keys_found
) {
132 // Check that all keys wereReader found.
133 ASSERT_TRUE(key_found
);
137 std::string
GetInternalKey(Slice user_key
, bool zero_seqno
,
138 ValueType type
= kTypeValue
) {
140 ikey
.SetInternalKey(user_key
, zero_seqno
? 0 : 1000, type
);
141 return ikey
.GetInternalKey().ToString();
144 uint64_t NextPowOf2(uint64_t num
) {
152 uint64_t GetExpectedTableSize(uint64_t num
) {
153 return NextPowOf2(static_cast<uint64_t>(num
/ kHashTableRatio
));
158 EnvOptions env_options_
;
160 const double kHashTableRatio
= 0.9;
163 TEST_F(CuckooBuilderTest
, SuccessWithEmptyFile
) {
164 std::unique_ptr
<WritableFile
> writable_file
;
165 fname
= test::PerThreadDBPath("EmptyFile");
166 ASSERT_OK(env_
->NewWritableFile(fname
, &writable_file
, env_options_
));
167 std::unique_ptr
<WritableFileWriter
> file_writer(
168 new WritableFileWriter(std::move(writable_file
), fname
, EnvOptions()));
169 CuckooTableBuilder
builder(file_writer
.get(), kHashTableRatio
, 4, 100,
170 BytewiseComparator(), 1, false, false,
171 GetSliceHash
, 0 /* column_family_id */,
172 kDefaultColumnFamilyName
);
173 ASSERT_OK(builder
.status());
174 ASSERT_EQ(0UL, builder
.FileSize());
175 ASSERT_OK(builder
.Finish());
176 ASSERT_OK(file_writer
->Close());
177 CheckFileContents({}, {}, {}, "", 2, 2, false);
180 TEST_F(CuckooBuilderTest
, WriteSuccessNoCollisionFullKey
) {
181 for (auto type
: {kTypeValue
, kTypeDeletion
}) {
182 uint32_t num_hash_fun
= 4;
183 std::vector
<std::string
> user_keys
= {"key01", "key02", "key03", "key04"};
184 std::vector
<std::string
> values
;
185 if (type
== kTypeValue
) {
186 values
= {"v01", "v02", "v03", "v04"};
188 values
= {"", "", "", ""};
190 // Need to have a temporary variable here as VS compiler does not currently
191 // support operator= with initializer_list as a parameter
192 std::unordered_map
<std::string
, std::vector
<uint64_t>> hm
= {
193 {user_keys
[0], {0, 1, 2, 3}},
194 {user_keys
[1], {1, 2, 3, 4}},
195 {user_keys
[2], {2, 3, 4, 5}},
196 {user_keys
[3], {3, 4, 5, 6}}};
197 hash_map
= std::move(hm
);
199 std::vector
<uint64_t> expected_locations
= {0, 1, 2, 3};
200 std::vector
<std::string
> keys
;
201 for (auto& user_key
: user_keys
) {
202 keys
.push_back(GetInternalKey(user_key
, false, type
));
204 uint64_t expected_table_size
= GetExpectedTableSize(keys
.size());
206 std::unique_ptr
<WritableFile
> writable_file
;
207 fname
= test::PerThreadDBPath("NoCollisionFullKey");
208 ASSERT_OK(env_
->NewWritableFile(fname
, &writable_file
, env_options_
));
209 std::unique_ptr
<WritableFileWriter
> file_writer(
210 new WritableFileWriter(std::move(writable_file
), fname
, EnvOptions()));
211 CuckooTableBuilder
builder(file_writer
.get(), kHashTableRatio
, num_hash_fun
,
212 100, BytewiseComparator(), 1, false, false,
213 GetSliceHash
, 0 /* column_family_id */,
214 kDefaultColumnFamilyName
);
215 ASSERT_OK(builder
.status());
216 for (uint32_t i
= 0; i
< user_keys
.size(); i
++) {
217 builder
.Add(Slice(keys
[i
]), Slice(values
[i
]));
218 ASSERT_EQ(builder
.NumEntries(), i
+ 1);
219 ASSERT_OK(builder
.status());
221 size_t bucket_size
= keys
[0].size() + values
[0].size();
222 ASSERT_EQ(expected_table_size
* bucket_size
- 1, builder
.FileSize());
223 ASSERT_OK(builder
.Finish());
224 ASSERT_OK(file_writer
->Close());
225 ASSERT_LE(expected_table_size
* bucket_size
, builder
.FileSize());
227 std::string expected_unused_bucket
= GetInternalKey("key00", true);
228 expected_unused_bucket
+= std::string(values
[0].size(), 'a');
229 CheckFileContents(keys
, values
, expected_locations
, expected_unused_bucket
,
230 expected_table_size
, 2, false);
234 TEST_F(CuckooBuilderTest
, WriteSuccessWithCollisionFullKey
) {
235 uint32_t num_hash_fun
= 4;
236 std::vector
<std::string
> user_keys
= {"key01", "key02", "key03", "key04"};
237 std::vector
<std::string
> values
= {"v01", "v02", "v03", "v04"};
238 // Need to have a temporary variable here as VS compiler does not currently
239 // support operator= with initializer_list as a parameter
240 std::unordered_map
<std::string
, std::vector
<uint64_t>> hm
= {
241 {user_keys
[0], {0, 1, 2, 3}},
242 {user_keys
[1], {0, 1, 2, 3}},
243 {user_keys
[2], {0, 1, 2, 3}},
244 {user_keys
[3], {0, 1, 2, 3}},
246 hash_map
= std::move(hm
);
248 std::vector
<uint64_t> expected_locations
= {0, 1, 2, 3};
249 std::vector
<std::string
> keys
;
250 for (auto& user_key
: user_keys
) {
251 keys
.push_back(GetInternalKey(user_key
, false));
253 uint64_t expected_table_size
= GetExpectedTableSize(keys
.size());
255 std::unique_ptr
<WritableFile
> writable_file
;
256 fname
= test::PerThreadDBPath("WithCollisionFullKey");
257 ASSERT_OK(env_
->NewWritableFile(fname
, &writable_file
, env_options_
));
258 std::unique_ptr
<WritableFileWriter
> file_writer(
259 new WritableFileWriter(std::move(writable_file
), fname
, EnvOptions()));
260 CuckooTableBuilder
builder(file_writer
.get(), kHashTableRatio
, num_hash_fun
,
261 100, BytewiseComparator(), 1, false, false,
262 GetSliceHash
, 0 /* column_family_id */,
263 kDefaultColumnFamilyName
);
264 ASSERT_OK(builder
.status());
265 for (uint32_t i
= 0; i
< user_keys
.size(); i
++) {
266 builder
.Add(Slice(keys
[i
]), Slice(values
[i
]));
267 ASSERT_EQ(builder
.NumEntries(), i
+ 1);
268 ASSERT_OK(builder
.status());
270 size_t bucket_size
= keys
[0].size() + values
[0].size();
271 ASSERT_EQ(expected_table_size
* bucket_size
- 1, builder
.FileSize());
272 ASSERT_OK(builder
.Finish());
273 ASSERT_OK(file_writer
->Close());
274 ASSERT_LE(expected_table_size
* bucket_size
, builder
.FileSize());
276 std::string expected_unused_bucket
= GetInternalKey("key00", true);
277 expected_unused_bucket
+= std::string(values
[0].size(), 'a');
278 CheckFileContents(keys
, values
, expected_locations
,
279 expected_unused_bucket
, expected_table_size
, 4, false);
282 TEST_F(CuckooBuilderTest
, WriteSuccessWithCollisionAndCuckooBlock
) {
283 uint32_t num_hash_fun
= 4;
284 std::vector
<std::string
> user_keys
= {"key01", "key02", "key03", "key04"};
285 std::vector
<std::string
> values
= {"v01", "v02", "v03", "v04"};
286 // Need to have a temporary variable here as VS compiler does not currently
287 // support operator= with initializer_list as a parameter
288 std::unordered_map
<std::string
, std::vector
<uint64_t>> hm
= {
289 {user_keys
[0], {0, 1, 2, 3}},
290 {user_keys
[1], {0, 1, 2, 3}},
291 {user_keys
[2], {0, 1, 2, 3}},
292 {user_keys
[3], {0, 1, 2, 3}},
294 hash_map
= std::move(hm
);
296 std::vector
<uint64_t> expected_locations
= {0, 1, 2, 3};
297 std::vector
<std::string
> keys
;
298 for (auto& user_key
: user_keys
) {
299 keys
.push_back(GetInternalKey(user_key
, false));
301 uint64_t expected_table_size
= GetExpectedTableSize(keys
.size());
303 std::unique_ptr
<WritableFile
> writable_file
;
304 uint32_t cuckoo_block_size
= 2;
305 fname
= test::PerThreadDBPath("WithCollisionFullKey2");
306 ASSERT_OK(env_
->NewWritableFile(fname
, &writable_file
, env_options_
));
307 std::unique_ptr
<WritableFileWriter
> file_writer(
308 new WritableFileWriter(std::move(writable_file
), fname
, EnvOptions()));
309 CuckooTableBuilder
builder(
310 file_writer
.get(), kHashTableRatio
, num_hash_fun
, 100,
311 BytewiseComparator(), cuckoo_block_size
, false, false, GetSliceHash
,
312 0 /* column_family_id */, kDefaultColumnFamilyName
);
313 ASSERT_OK(builder
.status());
314 for (uint32_t i
= 0; i
< user_keys
.size(); i
++) {
315 builder
.Add(Slice(keys
[i
]), Slice(values
[i
]));
316 ASSERT_EQ(builder
.NumEntries(), i
+ 1);
317 ASSERT_OK(builder
.status());
319 size_t bucket_size
= keys
[0].size() + values
[0].size();
320 ASSERT_EQ(expected_table_size
* bucket_size
- 1, builder
.FileSize());
321 ASSERT_OK(builder
.Finish());
322 ASSERT_OK(file_writer
->Close());
323 ASSERT_LE(expected_table_size
* bucket_size
, builder
.FileSize());
325 std::string expected_unused_bucket
= GetInternalKey("key00", true);
326 expected_unused_bucket
+= std::string(values
[0].size(), 'a');
327 CheckFileContents(keys
, values
, expected_locations
,
328 expected_unused_bucket
, expected_table_size
, 3, false, cuckoo_block_size
);
331 TEST_F(CuckooBuilderTest
, WithCollisionPathFullKey
) {
332 // Have two hash functions. Insert elements with overlapping hashes.
333 // Finally insert an element with hash value somewhere in the middle
334 // so that it displaces all the elements after that.
335 uint32_t num_hash_fun
= 2;
336 std::vector
<std::string
> user_keys
= {"key01", "key02", "key03",
338 std::vector
<std::string
> values
= {"v01", "v02", "v03", "v04", "v05"};
339 // Need to have a temporary variable here as VS compiler does not currently
340 // support operator= with initializer_list as a parameter
341 std::unordered_map
<std::string
, std::vector
<uint64_t>> hm
= {
342 {user_keys
[0], {0, 1}},
343 {user_keys
[1], {1, 2}},
344 {user_keys
[2], {2, 3}},
345 {user_keys
[3], {3, 4}},
346 {user_keys
[4], {0, 2}},
348 hash_map
= std::move(hm
);
350 std::vector
<uint64_t> expected_locations
= {0, 1, 3, 4, 2};
351 std::vector
<std::string
> keys
;
352 for (auto& user_key
: user_keys
) {
353 keys
.push_back(GetInternalKey(user_key
, false));
355 uint64_t expected_table_size
= GetExpectedTableSize(keys
.size());
357 std::unique_ptr
<WritableFile
> writable_file
;
358 fname
= test::PerThreadDBPath("WithCollisionPathFullKey");
359 ASSERT_OK(env_
->NewWritableFile(fname
, &writable_file
, env_options_
));
360 std::unique_ptr
<WritableFileWriter
> file_writer(
361 new WritableFileWriter(std::move(writable_file
), fname
, EnvOptions()));
362 CuckooTableBuilder
builder(file_writer
.get(), kHashTableRatio
, num_hash_fun
,
363 100, BytewiseComparator(), 1, false, false,
364 GetSliceHash
, 0 /* column_family_id */,
365 kDefaultColumnFamilyName
);
366 ASSERT_OK(builder
.status());
367 for (uint32_t i
= 0; i
< user_keys
.size(); i
++) {
368 builder
.Add(Slice(keys
[i
]), Slice(values
[i
]));
369 ASSERT_EQ(builder
.NumEntries(), i
+ 1);
370 ASSERT_OK(builder
.status());
372 size_t bucket_size
= keys
[0].size() + values
[0].size();
373 ASSERT_EQ(expected_table_size
* bucket_size
- 1, builder
.FileSize());
374 ASSERT_OK(builder
.Finish());
375 ASSERT_OK(file_writer
->Close());
376 ASSERT_LE(expected_table_size
* bucket_size
, builder
.FileSize());
378 std::string expected_unused_bucket
= GetInternalKey("key00", true);
379 expected_unused_bucket
+= std::string(values
[0].size(), 'a');
380 CheckFileContents(keys
, values
, expected_locations
,
381 expected_unused_bucket
, expected_table_size
, 2, false);
384 TEST_F(CuckooBuilderTest
, WithCollisionPathFullKeyAndCuckooBlock
) {
385 uint32_t num_hash_fun
= 2;
386 std::vector
<std::string
> user_keys
= {"key01", "key02", "key03",
388 std::vector
<std::string
> values
= {"v01", "v02", "v03", "v04", "v05"};
389 // Need to have a temporary variable here as VS compiler does not currently
390 // support operator= with initializer_list as a parameter
391 std::unordered_map
<std::string
, std::vector
<uint64_t>> hm
= {
392 {user_keys
[0], {0, 1}},
393 {user_keys
[1], {1, 2}},
394 {user_keys
[2], {3, 4}},
395 {user_keys
[3], {4, 5}},
396 {user_keys
[4], {0, 3}},
398 hash_map
= std::move(hm
);
400 std::vector
<uint64_t> expected_locations
= {2, 1, 3, 4, 0};
401 std::vector
<std::string
> keys
;
402 for (auto& user_key
: user_keys
) {
403 keys
.push_back(GetInternalKey(user_key
, false));
405 uint64_t expected_table_size
= GetExpectedTableSize(keys
.size());
407 std::unique_ptr
<WritableFile
> writable_file
;
408 fname
= test::PerThreadDBPath("WithCollisionPathFullKeyAndCuckooBlock");
409 ASSERT_OK(env_
->NewWritableFile(fname
, &writable_file
, env_options_
));
410 std::unique_ptr
<WritableFileWriter
> file_writer(
411 new WritableFileWriter(std::move(writable_file
), fname
, EnvOptions()));
412 CuckooTableBuilder
builder(file_writer
.get(), kHashTableRatio
, num_hash_fun
,
413 100, BytewiseComparator(), 2, false, false,
414 GetSliceHash
, 0 /* column_family_id */,
415 kDefaultColumnFamilyName
);
416 ASSERT_OK(builder
.status());
417 for (uint32_t i
= 0; i
< user_keys
.size(); i
++) {
418 builder
.Add(Slice(keys
[i
]), Slice(values
[i
]));
419 ASSERT_EQ(builder
.NumEntries(), i
+ 1);
420 ASSERT_OK(builder
.status());
422 size_t bucket_size
= keys
[0].size() + values
[0].size();
423 ASSERT_EQ(expected_table_size
* bucket_size
- 1, builder
.FileSize());
424 ASSERT_OK(builder
.Finish());
425 ASSERT_OK(file_writer
->Close());
426 ASSERT_LE(expected_table_size
* bucket_size
, builder
.FileSize());
428 std::string expected_unused_bucket
= GetInternalKey("key00", true);
429 expected_unused_bucket
+= std::string(values
[0].size(), 'a');
430 CheckFileContents(keys
, values
, expected_locations
,
431 expected_unused_bucket
, expected_table_size
, 2, false, 2);
434 TEST_F(CuckooBuilderTest
, WriteSuccessNoCollisionUserKey
) {
435 uint32_t num_hash_fun
= 4;
436 std::vector
<std::string
> user_keys
= {"key01", "key02", "key03", "key04"};
437 std::vector
<std::string
> values
= {"v01", "v02", "v03", "v04"};
438 // Need to have a temporary variable here as VS compiler does not currently
439 // support operator= with initializer_list as a parameter
440 std::unordered_map
<std::string
, std::vector
<uint64_t>> hm
= {
441 {user_keys
[0], {0, 1, 2, 3}},
442 {user_keys
[1], {1, 2, 3, 4}},
443 {user_keys
[2], {2, 3, 4, 5}},
444 {user_keys
[3], {3, 4, 5, 6}}};
445 hash_map
= std::move(hm
);
447 std::vector
<uint64_t> expected_locations
= {0, 1, 2, 3};
448 uint64_t expected_table_size
= GetExpectedTableSize(user_keys
.size());
450 std::unique_ptr
<WritableFile
> writable_file
;
451 fname
= test::PerThreadDBPath("NoCollisionUserKey");
452 ASSERT_OK(env_
->NewWritableFile(fname
, &writable_file
, env_options_
));
453 std::unique_ptr
<WritableFileWriter
> file_writer(
454 new WritableFileWriter(std::move(writable_file
), fname
, EnvOptions()));
455 CuckooTableBuilder
builder(file_writer
.get(), kHashTableRatio
, num_hash_fun
,
456 100, BytewiseComparator(), 1, false, false,
457 GetSliceHash
, 0 /* column_family_id */,
458 kDefaultColumnFamilyName
);
459 ASSERT_OK(builder
.status());
460 for (uint32_t i
= 0; i
< user_keys
.size(); i
++) {
461 builder
.Add(Slice(GetInternalKey(user_keys
[i
], true)), Slice(values
[i
]));
462 ASSERT_EQ(builder
.NumEntries(), i
+ 1);
463 ASSERT_OK(builder
.status());
465 size_t bucket_size
= user_keys
[0].size() + values
[0].size();
466 ASSERT_EQ(expected_table_size
* bucket_size
- 1, builder
.FileSize());
467 ASSERT_OK(builder
.Finish());
468 ASSERT_OK(file_writer
->Close());
469 ASSERT_LE(expected_table_size
* bucket_size
, builder
.FileSize());
471 std::string expected_unused_bucket
= "key00";
472 expected_unused_bucket
+= std::string(values
[0].size(), 'a');
473 CheckFileContents(user_keys
, values
, expected_locations
,
474 expected_unused_bucket
, expected_table_size
, 2, true);
477 TEST_F(CuckooBuilderTest
, WriteSuccessWithCollisionUserKey
) {
478 uint32_t num_hash_fun
= 4;
479 std::vector
<std::string
> user_keys
= {"key01", "key02", "key03", "key04"};
480 std::vector
<std::string
> values
= {"v01", "v02", "v03", "v04"};
481 // Need to have a temporary variable here as VS compiler does not currently
482 // support operator= with initializer_list as a parameter
483 std::unordered_map
<std::string
, std::vector
<uint64_t>> hm
= {
484 {user_keys
[0], {0, 1, 2, 3}},
485 {user_keys
[1], {0, 1, 2, 3}},
486 {user_keys
[2], {0, 1, 2, 3}},
487 {user_keys
[3], {0, 1, 2, 3}},
489 hash_map
= std::move(hm
);
491 std::vector
<uint64_t> expected_locations
= {0, 1, 2, 3};
492 uint64_t expected_table_size
= GetExpectedTableSize(user_keys
.size());
494 std::unique_ptr
<WritableFile
> writable_file
;
495 fname
= test::PerThreadDBPath("WithCollisionUserKey");
496 ASSERT_OK(env_
->NewWritableFile(fname
, &writable_file
, env_options_
));
497 std::unique_ptr
<WritableFileWriter
> file_writer(
498 new WritableFileWriter(std::move(writable_file
), fname
, EnvOptions()));
499 CuckooTableBuilder
builder(file_writer
.get(), kHashTableRatio
, num_hash_fun
,
500 100, BytewiseComparator(), 1, false, false,
501 GetSliceHash
, 0 /* column_family_id */,
502 kDefaultColumnFamilyName
);
503 ASSERT_OK(builder
.status());
504 for (uint32_t i
= 0; i
< user_keys
.size(); i
++) {
505 builder
.Add(Slice(GetInternalKey(user_keys
[i
], true)), Slice(values
[i
]));
506 ASSERT_EQ(builder
.NumEntries(), i
+ 1);
507 ASSERT_OK(builder
.status());
509 size_t bucket_size
= user_keys
[0].size() + values
[0].size();
510 ASSERT_EQ(expected_table_size
* bucket_size
- 1, builder
.FileSize());
511 ASSERT_OK(builder
.Finish());
512 ASSERT_OK(file_writer
->Close());
513 ASSERT_LE(expected_table_size
* bucket_size
, builder
.FileSize());
515 std::string expected_unused_bucket
= "key00";
516 expected_unused_bucket
+= std::string(values
[0].size(), 'a');
517 CheckFileContents(user_keys
, values
, expected_locations
,
518 expected_unused_bucket
, expected_table_size
, 4, true);
521 TEST_F(CuckooBuilderTest
, WithCollisionPathUserKey
) {
522 uint32_t num_hash_fun
= 2;
523 std::vector
<std::string
> user_keys
= {"key01", "key02", "key03",
525 std::vector
<std::string
> values
= {"v01", "v02", "v03", "v04", "v05"};
526 // Need to have a temporary variable here as VS compiler does not currently
527 // support operator= with initializer_list as a parameter
528 std::unordered_map
<std::string
, std::vector
<uint64_t>> hm
= {
529 {user_keys
[0], {0, 1}},
530 {user_keys
[1], {1, 2}},
531 {user_keys
[2], {2, 3}},
532 {user_keys
[3], {3, 4}},
533 {user_keys
[4], {0, 2}},
535 hash_map
= std::move(hm
);
537 std::vector
<uint64_t> expected_locations
= {0, 1, 3, 4, 2};
538 uint64_t expected_table_size
= GetExpectedTableSize(user_keys
.size());
540 std::unique_ptr
<WritableFile
> writable_file
;
541 fname
= test::PerThreadDBPath("WithCollisionPathUserKey");
542 ASSERT_OK(env_
->NewWritableFile(fname
, &writable_file
, env_options_
));
543 std::unique_ptr
<WritableFileWriter
> file_writer(
544 new WritableFileWriter(std::move(writable_file
), fname
, EnvOptions()));
545 CuckooTableBuilder
builder(file_writer
.get(), kHashTableRatio
, num_hash_fun
,
546 2, BytewiseComparator(), 1, false, false,
547 GetSliceHash
, 0 /* column_family_id */,
548 kDefaultColumnFamilyName
);
549 ASSERT_OK(builder
.status());
550 for (uint32_t i
= 0; i
< user_keys
.size(); i
++) {
551 builder
.Add(Slice(GetInternalKey(user_keys
[i
], true)), Slice(values
[i
]));
552 ASSERT_EQ(builder
.NumEntries(), i
+ 1);
553 ASSERT_OK(builder
.status());
555 size_t bucket_size
= user_keys
[0].size() + values
[0].size();
556 ASSERT_EQ(expected_table_size
* bucket_size
- 1, builder
.FileSize());
557 ASSERT_OK(builder
.Finish());
558 ASSERT_OK(file_writer
->Close());
559 ASSERT_LE(expected_table_size
* bucket_size
, builder
.FileSize());
561 std::string expected_unused_bucket
= "key00";
562 expected_unused_bucket
+= std::string(values
[0].size(), 'a');
563 CheckFileContents(user_keys
, values
, expected_locations
,
564 expected_unused_bucket
, expected_table_size
, 2, true);
567 TEST_F(CuckooBuilderTest
, FailWhenCollisionPathTooLong
) {
568 // Have two hash functions. Insert elements with overlapping hashes.
569 // Finally try inserting an element with hash value somewhere in the middle
570 // and it should fail because the no. of elements to displace is too high.
571 uint32_t num_hash_fun
= 2;
572 std::vector
<std::string
> user_keys
= {"key01", "key02", "key03",
574 // Need to have a temporary variable here as VS compiler does not currently
575 // support operator= with initializer_list as a parameter
576 std::unordered_map
<std::string
, std::vector
<uint64_t>> hm
= {
577 {user_keys
[0], {0, 1}},
578 {user_keys
[1], {1, 2}},
579 {user_keys
[2], {2, 3}},
580 {user_keys
[3], {3, 4}},
581 {user_keys
[4], {0, 1}},
583 hash_map
= std::move(hm
);
585 std::unique_ptr
<WritableFile
> writable_file
;
586 fname
= test::PerThreadDBPath("WithCollisionPathUserKey");
587 ASSERT_OK(env_
->NewWritableFile(fname
, &writable_file
, env_options_
));
588 std::unique_ptr
<WritableFileWriter
> file_writer(
589 new WritableFileWriter(std::move(writable_file
), fname
, EnvOptions()));
590 CuckooTableBuilder
builder(file_writer
.get(), kHashTableRatio
, num_hash_fun
,
591 2, BytewiseComparator(), 1, false, false,
592 GetSliceHash
, 0 /* column_family_id */,
593 kDefaultColumnFamilyName
);
594 ASSERT_OK(builder
.status());
595 for (uint32_t i
= 0; i
< user_keys
.size(); i
++) {
596 builder
.Add(Slice(GetInternalKey(user_keys
[i
], false)), Slice("value"));
597 ASSERT_EQ(builder
.NumEntries(), i
+ 1);
598 ASSERT_OK(builder
.status());
600 ASSERT_TRUE(builder
.Finish().IsNotSupported());
601 ASSERT_OK(file_writer
->Close());
604 TEST_F(CuckooBuilderTest
, FailWhenSameKeyInserted
) {
605 // Need to have a temporary variable here as VS compiler does not currently
606 // support operator= with initializer_list as a parameter
607 std::unordered_map
<std::string
, std::vector
<uint64_t>> hm
= {
608 {"repeatedkey", {0, 1, 2, 3}}};
609 hash_map
= std::move(hm
);
610 uint32_t num_hash_fun
= 4;
611 std::string user_key
= "repeatedkey";
613 std::unique_ptr
<WritableFile
> writable_file
;
614 fname
= test::PerThreadDBPath("FailWhenSameKeyInserted");
615 ASSERT_OK(env_
->NewWritableFile(fname
, &writable_file
, env_options_
));
616 std::unique_ptr
<WritableFileWriter
> file_writer(
617 new WritableFileWriter(std::move(writable_file
), fname
, EnvOptions()));
618 CuckooTableBuilder
builder(file_writer
.get(), kHashTableRatio
, num_hash_fun
,
619 100, BytewiseComparator(), 1, false, false,
620 GetSliceHash
, 0 /* column_family_id */,
621 kDefaultColumnFamilyName
);
622 ASSERT_OK(builder
.status());
624 builder
.Add(Slice(GetInternalKey(user_key
, false)), Slice("value1"));
625 ASSERT_EQ(builder
.NumEntries(), 1u);
626 ASSERT_OK(builder
.status());
627 builder
.Add(Slice(GetInternalKey(user_key
, true)), Slice("value2"));
628 ASSERT_EQ(builder
.NumEntries(), 2u);
629 ASSERT_OK(builder
.status());
631 ASSERT_TRUE(builder
.Finish().IsNotSupported());
632 ASSERT_OK(file_writer
->Close());
634 } // namespace rocksdb
636 int main(int argc
, char** argv
) {
637 ::testing::InitGoogleTest(&argc
, argv
);
638 return RUN_ALL_TESTS();
644 int main(int /*argc*/, char** /*argv*/) {
645 fprintf(stderr
, "SKIPPED as Cuckoo table is not supported in ROCKSDB_LITE\n");
649 #endif // ROCKSDB_LITE