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).
5 
6 #include <cstdlib>
7 #include <string>
8 #include <unordered_map>
9 
10 #include "db/table_properties_collector.h"
11 #include "rocksdb/slice.h"
12 #include "table/block_based/block.h"
13 #include "table/block_based/block_based_table_reader.h"
14 #include "table/block_based/block_builder.h"
15 #include "table/block_based/data_block_hash_index.h"
16 #include "table/get_context.h"
17 #include "table/table_builder.h"
18 #include "test_util/testharness.h"
19 #include "test_util/testutil.h"
20 
21 namespace ROCKSDB_NAMESPACE {
22 
SearchForOffset(DataBlockHashIndex & index,const char * data,uint16_t map_offset,const Slice & key,uint8_t & restart_point)23 bool SearchForOffset(DataBlockHashIndex& index, const char* data,
24                      uint16_t map_offset, const Slice& key,
25                      uint8_t& restart_point) {
26   uint8_t entry = index.Lookup(data, map_offset, key);
27   if (entry == kCollision) {
28     return true;
29   }
30 
31   if (entry == kNoEntry) {
32     return false;
33   }
34 
35   return entry == restart_point;
36 }
37 
38 // Random KV generator similer to block_test
RandomString(Random * rnd,int len)39 static std::string RandomString(Random* rnd, int len) {
40   std::string r;
41   test::RandomString(rnd, len, &r);
42   return r;
43 }
GenerateKey(int primary_key,int secondary_key,int padding_size,Random * rnd)44 std::string GenerateKey(int primary_key, int secondary_key, int padding_size,
45                         Random* rnd) {
46   char buf[50];
47   char* p = &buf[0];
48   snprintf(buf, sizeof(buf), "%6d%4d", primary_key, secondary_key);
49   std::string k(p);
50   if (padding_size) {
51     k += RandomString(rnd, padding_size);
52   }
53 
54   return k;
55 }
56 
57 // Generate random key value pairs.
58 // The generated key will be sorted. You can tune the parameters to generated
59 // different kinds of test key/value pairs for different scenario.
GenerateRandomKVs(std::vector<std::string> * keys,std::vector<std::string> * values,const int from,const int len,const int step=1,const int padding_size=0,const int keys_share_prefix=1)60 void GenerateRandomKVs(std::vector<std::string>* keys,
61                        std::vector<std::string>* values, const int from,
62                        const int len, const int step = 1,
63                        const int padding_size = 0,
64                        const int keys_share_prefix = 1) {
65   Random rnd(302);
66 
67   // generate different prefix
68   for (int i = from; i < from + len; i += step) {
69     // generating keys that shares the prefix
70     for (int j = 0; j < keys_share_prefix; ++j) {
71       keys->emplace_back(GenerateKey(i, j, padding_size, &rnd));
72 
73       // 100 bytes values
74       values->emplace_back(RandomString(&rnd, 100));
75     }
76   }
77 }
78 
TEST(DataBlockHashIndex,DataBlockHashTestSmall)79 TEST(DataBlockHashIndex, DataBlockHashTestSmall) {
80   DataBlockHashIndexBuilder builder;
81   builder.Initialize(0.75 /*util_ratio*/);
82   for (int j = 0; j < 5; j++) {
83     for (uint8_t i = 0; i < 2 + j; i++) {
84       std::string key("key" + std::to_string(i));
85       uint8_t restart_point = i;
86       builder.Add(key, restart_point);
87     }
88 
89     size_t estimated_size = builder.EstimateSize();
90 
91     std::string buffer("fake"), buffer2;
92     size_t original_size = buffer.size();
93     estimated_size += original_size;
94     builder.Finish(buffer);
95 
96     ASSERT_EQ(buffer.size(), estimated_size);
97 
98     buffer2 = buffer;  // test for the correctness of relative offset
99 
100     Slice s(buffer2);
101     DataBlockHashIndex index;
102     uint16_t map_offset;
103     index.Initialize(s.data(), static_cast<uint16_t>(s.size()), &map_offset);
104 
105     // the additional hash map should start at the end of the buffer
106     ASSERT_EQ(original_size, map_offset);
107     for (uint8_t i = 0; i < 2; i++) {
108       std::string key("key" + std::to_string(i));
109       uint8_t restart_point = i;
110       ASSERT_TRUE(
111           SearchForOffset(index, s.data(), map_offset, key, restart_point));
112     }
113     builder.Reset();
114   }
115 }
116 
TEST(DataBlockHashIndex,DataBlockHashTest)117 TEST(DataBlockHashIndex, DataBlockHashTest) {
118   // bucket_num = 200, #keys = 100. 50% utilization
119   DataBlockHashIndexBuilder builder;
120   builder.Initialize(0.75 /*util_ratio*/);
121 
122   for (uint8_t i = 0; i < 100; i++) {
123     std::string key("key" + std::to_string(i));
124     uint8_t restart_point = i;
125     builder.Add(key, restart_point);
126   }
127 
128   size_t estimated_size = builder.EstimateSize();
129 
130   std::string buffer("fake content"), buffer2;
131   size_t original_size = buffer.size();
132   estimated_size += original_size;
133   builder.Finish(buffer);
134 
135   ASSERT_EQ(buffer.size(), estimated_size);
136 
137   buffer2 = buffer; // test for the correctness of relative offset
138 
139   Slice s(buffer2);
140   DataBlockHashIndex index;
141   uint16_t map_offset;
142   index.Initialize(s.data(), static_cast<uint16_t>(s.size()), &map_offset);
143 
144   // the additional hash map should start at the end of the buffer
145   ASSERT_EQ(original_size, map_offset);
146   for (uint8_t i = 0; i < 100; i++) {
147     std::string key("key" + std::to_string(i));
148     uint8_t restart_point = i;
149     ASSERT_TRUE(
150         SearchForOffset(index, s.data(), map_offset, key, restart_point));
151   }
152 }
153 
TEST(DataBlockHashIndex,DataBlockHashTestCollision)154 TEST(DataBlockHashIndex, DataBlockHashTestCollision) {
155   // bucket_num = 2. There will be intense hash collisions
156   DataBlockHashIndexBuilder builder;
157   builder.Initialize(0.75 /*util_ratio*/);
158 
159   for (uint8_t i = 0; i < 100; i++) {
160     std::string key("key" + std::to_string(i));
161     uint8_t restart_point = i;
162     builder.Add(key, restart_point);
163   }
164 
165   size_t estimated_size = builder.EstimateSize();
166 
167   std::string buffer("some other fake content to take up space"), buffer2;
168   size_t original_size = buffer.size();
169   estimated_size += original_size;
170   builder.Finish(buffer);
171 
172   ASSERT_EQ(buffer.size(), estimated_size);
173 
174   buffer2 = buffer; // test for the correctness of relative offset
175 
176   Slice s(buffer2);
177   DataBlockHashIndex index;
178   uint16_t map_offset;
179   index.Initialize(s.data(), static_cast<uint16_t>(s.size()), &map_offset);
180 
181   // the additional hash map should start at the end of the buffer
182   ASSERT_EQ(original_size, map_offset);
183   for (uint8_t i = 0; i < 100; i++) {
184     std::string key("key" + std::to_string(i));
185     uint8_t restart_point = i;
186     ASSERT_TRUE(
187         SearchForOffset(index, s.data(), map_offset, key, restart_point));
188   }
189 }
190 
TEST(DataBlockHashIndex,DataBlockHashTestLarge)191 TEST(DataBlockHashIndex, DataBlockHashTestLarge) {
192   DataBlockHashIndexBuilder builder;
193   builder.Initialize(0.75 /*util_ratio*/);
194   std::unordered_map<std::string, uint8_t> m;
195 
196   for (uint8_t i = 0; i < 100; i++) {
197     if (i % 2) {
198       continue;  // leave half of the keys out
199     }
200     std::string key = "key" + std::to_string(i);
201     uint8_t restart_point = i;
202     builder.Add(key, restart_point);
203     m[key] = restart_point;
204   }
205 
206   size_t estimated_size = builder.EstimateSize();
207 
208   std::string buffer("filling stuff"), buffer2;
209   size_t original_size = buffer.size();
210   estimated_size += original_size;
211   builder.Finish(buffer);
212 
213   ASSERT_EQ(buffer.size(), estimated_size);
214 
215   buffer2 = buffer; // test for the correctness of relative offset
216 
217   Slice s(buffer2);
218   DataBlockHashIndex index;
219   uint16_t map_offset;
220   index.Initialize(s.data(), static_cast<uint16_t>(s.size()), &map_offset);
221 
222   // the additional hash map should start at the end of the buffer
223   ASSERT_EQ(original_size, map_offset);
224   for (uint8_t i = 0; i < 100; i++) {
225     std::string key = "key" + std::to_string(i);
226     uint8_t restart_point = i;
227     if (m.count(key)) {
228       ASSERT_TRUE(m[key] == restart_point);
229       ASSERT_TRUE(
230           SearchForOffset(index, s.data(), map_offset, key, restart_point));
231     } else {
232       // we allow false positve, so don't test the nonexisting keys.
233       // when false positive happens, the search will continue to the
234       // restart intervals to see if the key really exist.
235     }
236   }
237 }
238 
TEST(DataBlockHashIndex,RestartIndexExceedMax)239 TEST(DataBlockHashIndex, RestartIndexExceedMax) {
240   DataBlockHashIndexBuilder builder;
241   builder.Initialize(0.75 /*util_ratio*/);
242   std::unordered_map<std::string, uint8_t> m;
243 
244   for (uint8_t i = 0; i <= 253; i++) {
245     std::string key = "key" + std::to_string(i);
246     uint8_t restart_point = i;
247     builder.Add(key, restart_point);
248   }
249   ASSERT_TRUE(builder.Valid());
250 
251   builder.Reset();
252 
253   for (uint8_t i = 0; i <= 254; i++) {
254     std::string key = "key" + std::to_string(i);
255     uint8_t restart_point = i;
256     builder.Add(key, restart_point);
257   }
258 
259   ASSERT_FALSE(builder.Valid());
260 
261   builder.Reset();
262   ASSERT_TRUE(builder.Valid());
263 }
264 
TEST(DataBlockHashIndex,BlockRestartIndexExceedMax)265 TEST(DataBlockHashIndex, BlockRestartIndexExceedMax) {
266   Options options = Options();
267 
268   BlockBuilder builder(1 /* block_restart_interval */,
269                        true /* use_delta_encoding */,
270                        false /* use_value_delta_encoding */,
271                        BlockBasedTableOptions::kDataBlockBinaryAndHash);
272 
273   // #restarts <= 253. HashIndex is valid
274   for (int i = 0; i <= 253; i++) {
275     std::string ukey = "key" + std::to_string(i);
276     InternalKey ikey(ukey, 0, kTypeValue);
277     builder.Add(ikey.Encode().ToString(), "value");
278   }
279 
280   {
281     // read serialized contents of the block
282     Slice rawblock = builder.Finish();
283 
284     // create block reader
285     BlockContents contents;
286     contents.data = rawblock;
287     Block reader(std::move(contents));
288 
289     ASSERT_EQ(reader.IndexType(),
290               BlockBasedTableOptions::kDataBlockBinaryAndHash);
291   }
292 
293   builder.Reset();
294 
295   // #restarts > 253. HashIndex is not used
296   for (int i = 0; i <= 254; i++) {
297     std::string ukey = "key" + std::to_string(i);
298     InternalKey ikey(ukey, 0, kTypeValue);
299     builder.Add(ikey.Encode().ToString(), "value");
300   }
301 
302   {
303     // read serialized contents of the block
304     Slice rawblock = builder.Finish();
305 
306     // create block reader
307     BlockContents contents;
308     contents.data = rawblock;
309     Block reader(std::move(contents));
310 
311     ASSERT_EQ(reader.IndexType(),
312               BlockBasedTableOptions::kDataBlockBinarySearch);
313   }
314 }
315 
TEST(DataBlockHashIndex,BlockSizeExceedMax)316 TEST(DataBlockHashIndex, BlockSizeExceedMax) {
317   Options options = Options();
318   std::string ukey(10, 'k');
319   InternalKey ikey(ukey, 0, kTypeValue);
320 
321   BlockBuilder builder(1 /* block_restart_interval */,
322                        false /* use_delta_encoding */,
323                        false /* use_value_delta_encoding */,
324                        BlockBasedTableOptions::kDataBlockBinaryAndHash);
325 
326   {
327     // insert a large value. The block size plus HashIndex is 65536.
328     std::string value(65502, 'v');
329 
330     builder.Add(ikey.Encode().ToString(), value);
331 
332     // read serialized contents of the block
333     Slice rawblock = builder.Finish();
334     ASSERT_LE(rawblock.size(), kMaxBlockSizeSupportedByHashIndex);
335     std::cerr << "block size: " << rawblock.size() << std::endl;
336 
337     // create block reader
338     BlockContents contents;
339     contents.data = rawblock;
340     Block reader(std::move(contents));
341 
342     ASSERT_EQ(reader.IndexType(),
343               BlockBasedTableOptions::kDataBlockBinaryAndHash);
344   }
345 
346   builder.Reset();
347 
348   {
349     // insert a large value. The block size plus HashIndex would be 65537.
350     // This excceed the max block size supported by HashIndex (65536).
351     // So when build finishes HashIndex will not be created for the block.
352     std::string value(65503, 'v');
353 
354     builder.Add(ikey.Encode().ToString(), value);
355 
356     // read serialized contents of the block
357     Slice rawblock = builder.Finish();
358     ASSERT_LE(rawblock.size(), kMaxBlockSizeSupportedByHashIndex);
359     std::cerr << "block size: " << rawblock.size() << std::endl;
360 
361     // create block reader
362     BlockContents contents;
363     contents.data = rawblock;
364     Block reader(std::move(contents));
365 
366     // the index type have fallen back to binary when build finish.
367     ASSERT_EQ(reader.IndexType(),
368               BlockBasedTableOptions::kDataBlockBinarySearch);
369   }
370 }
371 
TEST(DataBlockHashIndex,BlockTestSingleKey)372 TEST(DataBlockHashIndex, BlockTestSingleKey) {
373   Options options = Options();
374 
375   BlockBuilder builder(16 /* block_restart_interval */,
376                        true /* use_delta_encoding */,
377                        false /* use_value_delta_encoding */,
378                        BlockBasedTableOptions::kDataBlockBinaryAndHash);
379 
380   std::string ukey("gopher");
381   std::string value("gold");
382   InternalKey ikey(ukey, 10, kTypeValue);
383   builder.Add(ikey.Encode().ToString(), value /*value*/);
384 
385   // read serialized contents of the block
386   Slice rawblock = builder.Finish();
387 
388   // create block reader
389   BlockContents contents;
390   contents.data = rawblock;
391   Block reader(std::move(contents));
392 
393   const InternalKeyComparator icmp(BytewiseComparator());
394   auto iter = reader.NewDataIterator(&icmp, icmp.user_comparator(),
395                                      kDisableGlobalSequenceNumber);
396   bool may_exist;
397   // search in block for the key just inserted
398   {
399     InternalKey seek_ikey(ukey, 10, kValueTypeForSeek);
400     may_exist = iter->SeekForGet(seek_ikey.Encode().ToString());
401     ASSERT_TRUE(may_exist);
402     ASSERT_TRUE(iter->Valid());
403     ASSERT_EQ(
404         options.comparator->Compare(iter->key(), ikey.Encode().ToString()), 0);
405     ASSERT_EQ(iter->value(), value);
406   }
407 
408   // search in block for the existing ukey, but with higher seqno
409   {
410     InternalKey seek_ikey(ukey, 20, kValueTypeForSeek);
411 
412     // HashIndex should be able to set the iter correctly
413     may_exist = iter->SeekForGet(seek_ikey.Encode().ToString());
414     ASSERT_TRUE(may_exist);
415     ASSERT_TRUE(iter->Valid());
416 
417     // user key should match
418     ASSERT_EQ(options.comparator->Compare(ExtractUserKey(iter->key()), ukey),
419               0);
420 
421     // seek_key seqno number should be greater than that of iter result
422     ASSERT_GT(GetInternalKeySeqno(seek_ikey.Encode()),
423               GetInternalKeySeqno(iter->key()));
424 
425     ASSERT_EQ(iter->value(), value);
426   }
427 
428   // Search in block for the existing ukey, but with lower seqno
429   // in this case, hash can find the only occurrence of the user_key, but
430   // ParseNextDataKey() will skip it as it does not have a older seqno.
431   // In this case, GetForSeek() is effective to locate the user_key, and
432   // iter->Valid() == false indicates that we've reached to the end of
433   // the block and the caller should continue searching the next block.
434   {
435     InternalKey seek_ikey(ukey, 5, kValueTypeForSeek);
436     may_exist = iter->SeekForGet(seek_ikey.Encode().ToString());
437     ASSERT_TRUE(may_exist);
438     ASSERT_FALSE(iter->Valid());  // should have reached to the end of block
439   }
440 
441   delete iter;
442 }
443 
TEST(DataBlockHashIndex,BlockTestLarge)444 TEST(DataBlockHashIndex, BlockTestLarge) {
445   Random rnd(1019);
446   Options options = Options();
447   std::vector<std::string> keys;
448   std::vector<std::string> values;
449 
450   BlockBuilder builder(16 /* block_restart_interval */,
451                        true /* use_delta_encoding */,
452                        false /* use_value_delta_encoding */,
453                        BlockBasedTableOptions::kDataBlockBinaryAndHash);
454   int num_records = 500;
455 
456   GenerateRandomKVs(&keys, &values, 0, num_records);
457 
458   // Generate keys. Adding a trailing "1" to indicate existent keys.
459   // Later will Seeking for keys with a trailing "0" to test seeking
460   // non-existent keys.
461   for (int i = 0; i < num_records; i++) {
462     std::string ukey(keys[i] + "1" /* existing key marker */);
463     InternalKey ikey(ukey, 0, kTypeValue);
464     builder.Add(ikey.Encode().ToString(), values[i]);
465   }
466 
467   // read serialized contents of the block
468   Slice rawblock = builder.Finish();
469 
470   // create block reader
471   BlockContents contents;
472   contents.data = rawblock;
473   Block reader(std::move(contents));
474   const InternalKeyComparator icmp(BytewiseComparator());
475 
476   // random seek existent keys
477   for (int i = 0; i < num_records; i++) {
478     auto iter = reader.NewDataIterator(&icmp, icmp.user_comparator(),
479                                        kDisableGlobalSequenceNumber);
480     // find a random key in the lookaside array
481     int index = rnd.Uniform(num_records);
482     std::string ukey(keys[index] + "1" /* existing key marker */);
483     InternalKey ikey(ukey, 0, kTypeValue);
484 
485     // search in block for this key
486     bool may_exist = iter->SeekForGet(ikey.Encode().ToString());
487     ASSERT_TRUE(may_exist);
488     ASSERT_TRUE(iter->Valid());
489     ASSERT_EQ(values[index], iter->value());
490 
491     delete iter;
492   }
493 
494   // random seek non-existent user keys
495   // In this case A), the user_key cannot be found in HashIndex. The key may
496   // exist in the next block. So the iter is set invalidated to tell the
497   // caller to search the next block. This test case belongs to this case A).
498   //
499   // Note that for non-existent keys, there is possibility of false positive,
500   // i.e. the key is still hashed into some restart interval.
501   // Two additional possible outcome:
502   // B) linear seek the restart interval and not found, the iter stops at the
503   //    starting of the next restart interval. The key does not exist
504   //    anywhere.
505   // C) linear seek the restart interval and not found, the iter stops at the
506   //    the end of the block, i.e. restarts_. The key may exist in the next
507   //    block.
508   // So these combinations are possible when searching non-existent user_key:
509   //
510   // case#    may_exist  iter->Valid()
511   //     A         true          false
512   //     B        false           true
513   //     C         true          false
514 
515   for (int i = 0; i < num_records; i++) {
516     auto iter = reader.NewDataIterator(&icmp, icmp.user_comparator(),
517                                        kDisableGlobalSequenceNumber);
518     // find a random key in the lookaside array
519     int index = rnd.Uniform(num_records);
520     std::string ukey(keys[index] + "0" /* non-existing key marker */);
521     InternalKey ikey(ukey, 0, kTypeValue);
522 
523     // search in block for this key
524     bool may_exist = iter->SeekForGet(ikey.Encode().ToString());
525     if (!may_exist) {
526       ASSERT_TRUE(iter->Valid());
527     }
528     if (!iter->Valid()) {
529       ASSERT_TRUE(may_exist);
530     }
531 
532     delete iter;
533   }
534 }
535 
536 // helper routine for DataBlockHashIndex.BlockBoundary
TestBoundary(InternalKey & ik1,std::string & v1,InternalKey & ik2,std::string & v2,InternalKey & seek_ikey,GetContext & get_context,Options & options)537 void TestBoundary(InternalKey& ik1, std::string& v1, InternalKey& ik2,
538                   std::string& v2, InternalKey& seek_ikey,
539                   GetContext& get_context, Options& options) {
540   std::unique_ptr<WritableFileWriter> file_writer;
541   std::unique_ptr<RandomAccessFileReader> file_reader;
542   std::unique_ptr<TableReader> table_reader;
543   int level_ = -1;
544 
545   std::vector<std::string> keys;
546   const ImmutableCFOptions ioptions(options);
547   const MutableCFOptions moptions(options);
548   const InternalKeyComparator internal_comparator(options.comparator);
549 
550   EnvOptions soptions;
551 
552   soptions.use_mmap_reads = ioptions.allow_mmap_reads;
553   file_writer.reset(
554       test::GetWritableFileWriter(new test::StringSink(), "" /* don't care */));
555   std::unique_ptr<TableBuilder> builder;
556   std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
557       int_tbl_prop_collector_factories;
558   std::string column_family_name;
559   builder.reset(ioptions.table_factory->NewTableBuilder(
560       TableBuilderOptions(ioptions, moptions, internal_comparator,
561                           &int_tbl_prop_collector_factories,
562                           options.compression, options.sample_for_compression,
563                           CompressionOptions(), false /* skip_filters */,
564                           column_family_name, level_),
565       TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
566       file_writer.get()));
567 
568   builder->Add(ik1.Encode().ToString(), v1);
569   builder->Add(ik2.Encode().ToString(), v2);
570   EXPECT_TRUE(builder->status().ok());
571 
572   Status s = builder->Finish();
573   file_writer->Flush();
574   EXPECT_TRUE(s.ok()) << s.ToString();
575 
576   EXPECT_EQ(
577       test::GetStringSinkFromLegacyWriter(file_writer.get())->contents().size(),
578       builder->FileSize());
579 
580   // Open the table
581   file_reader.reset(test::GetRandomAccessFileReader(new test::StringSource(
582       test::GetStringSinkFromLegacyWriter(file_writer.get())->contents(),
583       0 /*uniq_id*/, ioptions.allow_mmap_reads)));
584   const bool kSkipFilters = true;
585   const bool kImmortal = true;
586   ioptions.table_factory->NewTableReader(
587       TableReaderOptions(ioptions, moptions.prefix_extractor.get(), soptions,
588                          internal_comparator, !kSkipFilters, !kImmortal,
589                          level_),
590       std::move(file_reader),
591       test::GetStringSinkFromLegacyWriter(file_writer.get())->contents().size(),
592       &table_reader);
593   // Search using Get()
594   ReadOptions ro;
595 
596   ASSERT_OK(table_reader->Get(ro, seek_ikey.Encode().ToString(), &get_context,
597                               moptions.prefix_extractor.get()));
598 }
599 
TEST(DataBlockHashIndex,BlockBoundary)600 TEST(DataBlockHashIndex, BlockBoundary) {
601   BlockBasedTableOptions table_options;
602   table_options.data_block_index_type =
603       BlockBasedTableOptions::kDataBlockBinaryAndHash;
604   table_options.block_restart_interval = 1;
605   table_options.block_size = 4096;
606 
607   Options options;
608   options.comparator = BytewiseComparator();
609 
610   options.table_factory.reset(NewBlockBasedTableFactory(table_options));
611 
612   // insert two large k/v pair. Given that the block_size is 4096, one k/v
613   // pair will take up one block.
614   // [    k1/v1   ][    k2/v2  ]
615   // [   Block N  ][ Block N+1 ]
616 
617   {
618     // [ "aab"@100 ][ "axy"@10  ]
619     // | Block  N  ][ Block N+1 ]
620     // seek for "axy"@60
621     std::string uk1("aab");
622     InternalKey ik1(uk1, 100, kTypeValue);
623     std::string v1(4100, '1');  // large value
624 
625     std::string uk2("axy");
626     InternalKey ik2(uk2, 10, kTypeValue);
627     std::string v2(4100, '2');  // large value
628 
629     PinnableSlice value;
630     std::string seek_ukey("axy");
631     InternalKey seek_ikey(seek_ukey, 60, kTypeValue);
632     GetContext get_context(options.comparator, nullptr, nullptr, nullptr,
633                            GetContext::kNotFound, seek_ukey, &value, nullptr,
634                            nullptr, true, nullptr, nullptr);
635 
636     TestBoundary(ik1, v1, ik2, v2, seek_ikey, get_context, options);
637     ASSERT_EQ(get_context.State(), GetContext::kFound);
638     ASSERT_EQ(value, v2);
639     value.Reset();
640   }
641 
642   {
643     // [ "axy"@100 ][ "axy"@10  ]
644     // | Block  N  ][ Block N+1 ]
645     // seek for "axy"@60
646     std::string uk1("axy");
647     InternalKey ik1(uk1, 100, kTypeValue);
648     std::string v1(4100, '1');  // large value
649 
650     std::string uk2("axy");
651     InternalKey ik2(uk2, 10, kTypeValue);
652     std::string v2(4100, '2');  // large value
653 
654     PinnableSlice value;
655     std::string seek_ukey("axy");
656     InternalKey seek_ikey(seek_ukey, 60, kTypeValue);
657     GetContext get_context(options.comparator, nullptr, nullptr, nullptr,
658                            GetContext::kNotFound, seek_ukey, &value, nullptr,
659                            nullptr, true, nullptr, nullptr);
660 
661     TestBoundary(ik1, v1, ik2, v2, seek_ikey, get_context, options);
662     ASSERT_EQ(get_context.State(), GetContext::kFound);
663     ASSERT_EQ(value, v2);
664     value.Reset();
665   }
666 
667   {
668     // [ "axy"@100 ][ "axy"@10  ]
669     // | Block  N  ][ Block N+1 ]
670     // seek for "axy"@120
671     std::string uk1("axy");
672     InternalKey ik1(uk1, 100, kTypeValue);
673     std::string v1(4100, '1');  // large value
674 
675     std::string uk2("axy");
676     InternalKey ik2(uk2, 10, kTypeValue);
677     std::string v2(4100, '2');  // large value
678 
679     PinnableSlice value;
680     std::string seek_ukey("axy");
681     InternalKey seek_ikey(seek_ukey, 120, kTypeValue);
682     GetContext get_context(options.comparator, nullptr, nullptr, nullptr,
683                            GetContext::kNotFound, seek_ukey, &value, nullptr,
684                            nullptr, true, nullptr, nullptr);
685 
686     TestBoundary(ik1, v1, ik2, v2, seek_ikey, get_context, options);
687     ASSERT_EQ(get_context.State(), GetContext::kFound);
688     ASSERT_EQ(value, v1);
689     value.Reset();
690   }
691 
692   {
693     // [ "axy"@100 ][ "axy"@10  ]
694     // | Block  N  ][ Block N+1 ]
695     // seek for "axy"@5
696     std::string uk1("axy");
697     InternalKey ik1(uk1, 100, kTypeValue);
698     std::string v1(4100, '1');  // large value
699 
700     std::string uk2("axy");
701     InternalKey ik2(uk2, 10, kTypeValue);
702     std::string v2(4100, '2');  // large value
703 
704     PinnableSlice value;
705     std::string seek_ukey("axy");
706     InternalKey seek_ikey(seek_ukey, 5, kTypeValue);
707     GetContext get_context(options.comparator, nullptr, nullptr, nullptr,
708                            GetContext::kNotFound, seek_ukey, &value, nullptr,
709                            nullptr, true, nullptr, nullptr);
710 
711     TestBoundary(ik1, v1, ik2, v2, seek_ikey, get_context, options);
712     ASSERT_EQ(get_context.State(), GetContext::kNotFound);
713     value.Reset();
714   }
715 }
716 
717 }  // namespace ROCKSDB_NAMESPACE
718 
main(int argc,char ** argv)719 int main(int argc, char** argv) {
720   ::testing::InitGoogleTest(&argc, argv);
721   return RUN_ALL_TESTS();
722 }
723