]>
Commit | Line | Data |
---|---|---|
7c673cae | 1 | // Copyright (c) 2011-present, Facebook, Inc. All rights reserved. |
11fdf7f2 TL |
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). | |
7c673cae FG |
5 | // |
6 | // Copyright (c) 2011 The LevelDB Authors. All rights reserved. | |
7 | // Use of this source code is governed by a BSD-style license that can be | |
8 | // found in the LICENSE file. See the AUTHORS file for names of contributors. | |
9 | ||
7c673cae | 10 | #include <stdio.h> |
7c673cae FG |
11 | #include <algorithm> |
12 | #include <iostream> | |
13 | #include <map> | |
14 | #include <memory> | |
15 | #include <string> | |
16 | #include <vector> | |
17 | ||
f67539c2 | 18 | #include "block_fetcher.h" |
11fdf7f2 | 19 | #include "cache/lru_cache.h" |
7c673cae FG |
20 | #include "db/dbformat.h" |
21 | #include "db/memtable.h" | |
22 | #include "db/write_batch_internal.h" | |
23 | #include "memtable/stl_wrappers.h" | |
f67539c2 | 24 | #include "meta_blocks.h" |
7c673cae FG |
25 | #include "monitoring/statistics.h" |
26 | #include "port/port.h" | |
27 | #include "rocksdb/cache.h" | |
28 | #include "rocksdb/db.h" | |
29 | #include "rocksdb/env.h" | |
f67539c2 TL |
30 | #include "rocksdb/file_checksum.h" |
31 | #include "rocksdb/file_system.h" | |
7c673cae FG |
32 | #include "rocksdb/iterator.h" |
33 | #include "rocksdb/memtablerep.h" | |
34 | #include "rocksdb/perf_context.h" | |
35 | #include "rocksdb/slice_transform.h" | |
36 | #include "rocksdb/statistics.h" | |
37 | #include "rocksdb/write_buffer_manager.h" | |
f67539c2 TL |
38 | #include "table/block_based/block.h" |
39 | #include "table/block_based/block_based_table_builder.h" | |
40 | #include "table/block_based/block_based_table_factory.h" | |
41 | #include "table/block_based/block_based_table_reader.h" | |
42 | #include "table/block_based/block_builder.h" | |
43 | #include "table/block_based/flush_block_policy.h" | |
7c673cae FG |
44 | #include "table/format.h" |
45 | #include "table/get_context.h" | |
46 | #include "table/internal_iterator.h" | |
f67539c2 | 47 | #include "table/plain/plain_table_factory.h" |
7c673cae FG |
48 | #include "table/scoped_arena_iterator.h" |
49 | #include "table/sst_file_writer_collectors.h" | |
f67539c2 TL |
50 | #include "test_util/sync_point.h" |
51 | #include "test_util/testharness.h" | |
52 | #include "test_util/testutil.h" | |
7c673cae | 53 | #include "util/compression.h" |
f67539c2 | 54 | #include "util/file_checksum_helper.h" |
7c673cae FG |
55 | #include "util/random.h" |
56 | #include "util/string_util.h" | |
7c673cae FG |
57 | #include "utilities/merge_operators.h" |
58 | ||
f67539c2 | 59 | namespace ROCKSDB_NAMESPACE { |
7c673cae FG |
60 | |
61 | extern const uint64_t kLegacyBlockBasedTableMagicNumber; | |
62 | extern const uint64_t kLegacyPlainTableMagicNumber; | |
63 | extern const uint64_t kBlockBasedTableMagicNumber; | |
64 | extern const uint64_t kPlainTableMagicNumber; | |
65 | ||
66 | namespace { | |
67 | ||
f67539c2 TL |
68 | const std::string kDummyValue(10000, 'o'); |
69 | ||
7c673cae FG |
70 | // DummyPropertiesCollector used to test BlockBasedTableProperties |
71 | class DummyPropertiesCollector : public TablePropertiesCollector { | |
72 | public: | |
20effc67 | 73 | const char* Name() const override { return "DummyPropertiesCollector"; } |
7c673cae | 74 | |
494da23a | 75 | Status Finish(UserCollectedProperties* /*properties*/) override { |
11fdf7f2 TL |
76 | return Status::OK(); |
77 | } | |
7c673cae | 78 | |
494da23a | 79 | Status Add(const Slice& /*user_key*/, const Slice& /*value*/) override { |
11fdf7f2 TL |
80 | return Status::OK(); |
81 | } | |
7c673cae | 82 | |
494da23a | 83 | UserCollectedProperties GetReadableProperties() const override { |
7c673cae FG |
84 | return UserCollectedProperties{}; |
85 | } | |
86 | }; | |
87 | ||
88 | class DummyPropertiesCollectorFactory1 | |
89 | : public TablePropertiesCollectorFactory { | |
90 | public: | |
494da23a TL |
91 | TablePropertiesCollector* CreateTablePropertiesCollector( |
92 | TablePropertiesCollectorFactory::Context /*context*/) override { | |
7c673cae FG |
93 | return new DummyPropertiesCollector(); |
94 | } | |
20effc67 TL |
95 | const char* Name() const override { |
96 | return "DummyPropertiesCollectorFactory1"; | |
97 | } | |
7c673cae FG |
98 | }; |
99 | ||
100 | class DummyPropertiesCollectorFactory2 | |
101 | : public TablePropertiesCollectorFactory { | |
102 | public: | |
494da23a TL |
103 | TablePropertiesCollector* CreateTablePropertiesCollector( |
104 | TablePropertiesCollectorFactory::Context /*context*/) override { | |
7c673cae FG |
105 | return new DummyPropertiesCollector(); |
106 | } | |
20effc67 TL |
107 | const char* Name() const override { |
108 | return "DummyPropertiesCollectorFactory2"; | |
109 | } | |
7c673cae FG |
110 | }; |
111 | ||
112 | // Return reverse of "key". | |
113 | // Used to test non-lexicographic comparators. | |
114 | std::string Reverse(const Slice& key) { | |
115 | auto rev = key.ToString(); | |
116 | std::reverse(rev.begin(), rev.end()); | |
117 | return rev; | |
118 | } | |
119 | ||
120 | class ReverseKeyComparator : public Comparator { | |
121 | public: | |
494da23a | 122 | const char* Name() const override { |
7c673cae FG |
123 | return "rocksdb.ReverseBytewiseComparator"; |
124 | } | |
125 | ||
494da23a | 126 | int Compare(const Slice& a, const Slice& b) const override { |
7c673cae FG |
127 | return BytewiseComparator()->Compare(Reverse(a), Reverse(b)); |
128 | } | |
129 | ||
494da23a TL |
130 | void FindShortestSeparator(std::string* start, |
131 | const Slice& limit) const override { | |
7c673cae FG |
132 | std::string s = Reverse(*start); |
133 | std::string l = Reverse(limit); | |
134 | BytewiseComparator()->FindShortestSeparator(&s, l); | |
135 | *start = Reverse(s); | |
136 | } | |
137 | ||
494da23a | 138 | void FindShortSuccessor(std::string* key) const override { |
7c673cae FG |
139 | std::string s = Reverse(*key); |
140 | BytewiseComparator()->FindShortSuccessor(&s); | |
141 | *key = Reverse(s); | |
142 | } | |
143 | }; | |
144 | ||
145 | ReverseKeyComparator reverse_key_comparator; | |
146 | ||
147 | void Increment(const Comparator* cmp, std::string* key) { | |
148 | if (cmp == BytewiseComparator()) { | |
149 | key->push_back('\0'); | |
150 | } else { | |
151 | assert(cmp == &reverse_key_comparator); | |
152 | std::string rev = Reverse(*key); | |
153 | rev.push_back('\0'); | |
154 | *key = Reverse(rev); | |
155 | } | |
156 | } | |
157 | ||
158 | } // namespace | |
159 | ||
160 | // Helper class for tests to unify the interface between | |
161 | // BlockBuilder/TableBuilder and Block/Table. | |
162 | class Constructor { | |
163 | public: | |
164 | explicit Constructor(const Comparator* cmp) | |
165 | : data_(stl_wrappers::LessOfComparator(cmp)) {} | |
166 | virtual ~Constructor() { } | |
167 | ||
168 | void Add(const std::string& key, const Slice& value) { | |
169 | data_[key] = value.ToString(); | |
170 | } | |
171 | ||
172 | // Finish constructing the data structure with all the keys that have | |
173 | // been added so far. Returns the keys in sorted order in "*keys" | |
174 | // and stores the key/value pairs in "*kvmap" | |
175 | void Finish(const Options& options, const ImmutableCFOptions& ioptions, | |
11fdf7f2 | 176 | const MutableCFOptions& moptions, |
7c673cae FG |
177 | const BlockBasedTableOptions& table_options, |
178 | const InternalKeyComparator& internal_comparator, | |
179 | std::vector<std::string>* keys, stl_wrappers::KVMap* kvmap) { | |
180 | last_internal_key_ = &internal_comparator; | |
181 | *kvmap = data_; | |
182 | keys->clear(); | |
183 | for (const auto& kv : data_) { | |
184 | keys->push_back(kv.first); | |
185 | } | |
186 | data_.clear(); | |
11fdf7f2 | 187 | Status s = FinishImpl(options, ioptions, moptions, table_options, |
7c673cae FG |
188 | internal_comparator, *kvmap); |
189 | ASSERT_TRUE(s.ok()) << s.ToString(); | |
190 | } | |
191 | ||
192 | // Construct the data structure from the data in "data" | |
193 | virtual Status FinishImpl(const Options& options, | |
194 | const ImmutableCFOptions& ioptions, | |
11fdf7f2 | 195 | const MutableCFOptions& moptions, |
7c673cae FG |
196 | const BlockBasedTableOptions& table_options, |
197 | const InternalKeyComparator& internal_comparator, | |
198 | const stl_wrappers::KVMap& data) = 0; | |
199 | ||
11fdf7f2 TL |
200 | virtual InternalIterator* NewIterator( |
201 | const SliceTransform* prefix_extractor = nullptr) const = 0; | |
7c673cae FG |
202 | |
203 | virtual const stl_wrappers::KVMap& data() { return data_; } | |
204 | ||
205 | virtual bool IsArenaMode() const { return false; } | |
206 | ||
207 | virtual DB* db() const { return nullptr; } // Overridden in DBConstructor | |
208 | ||
209 | virtual bool AnywayDeleteIterator() const { return false; } | |
210 | ||
211 | protected: | |
212 | const InternalKeyComparator* last_internal_key_; | |
213 | ||
214 | private: | |
215 | stl_wrappers::KVMap data_; | |
216 | }; | |
217 | ||
7c673cae FG |
218 | // A helper class that converts internal format keys into user keys |
219 | class KeyConvertingIterator : public InternalIterator { | |
220 | public: | |
221 | explicit KeyConvertingIterator(InternalIterator* iter, | |
222 | bool arena_mode = false) | |
223 | : iter_(iter), arena_mode_(arena_mode) {} | |
494da23a | 224 | ~KeyConvertingIterator() override { |
7c673cae FG |
225 | if (arena_mode_) { |
226 | iter_->~InternalIterator(); | |
227 | } else { | |
228 | delete iter_; | |
229 | } | |
230 | } | |
494da23a TL |
231 | bool Valid() const override { return iter_->Valid() && status_.ok(); } |
232 | void Seek(const Slice& target) override { | |
7c673cae FG |
233 | ParsedInternalKey ikey(target, kMaxSequenceNumber, kTypeValue); |
234 | std::string encoded; | |
235 | AppendInternalKey(&encoded, ikey); | |
236 | iter_->Seek(encoded); | |
237 | } | |
494da23a | 238 | void SeekForPrev(const Slice& target) override { |
7c673cae FG |
239 | ParsedInternalKey ikey(target, kMaxSequenceNumber, kTypeValue); |
240 | std::string encoded; | |
241 | AppendInternalKey(&encoded, ikey); | |
242 | iter_->SeekForPrev(encoded); | |
243 | } | |
494da23a TL |
244 | void SeekToFirst() override { iter_->SeekToFirst(); } |
245 | void SeekToLast() override { iter_->SeekToLast(); } | |
246 | void Next() override { iter_->Next(); } | |
247 | void Prev() override { iter_->Prev(); } | |
20effc67 TL |
248 | IterBoundCheck UpperBoundCheckResult() override { |
249 | return iter_->UpperBoundCheckResult(); | |
250 | } | |
7c673cae | 251 | |
494da23a | 252 | Slice key() const override { |
7c673cae FG |
253 | assert(Valid()); |
254 | ParsedInternalKey parsed_key; | |
20effc67 TL |
255 | Status pik_status = |
256 | ParseInternalKey(iter_->key(), &parsed_key, true /* log_err_key */); | |
257 | if (!pik_status.ok()) { | |
258 | status_ = pik_status; | |
259 | return Slice(status_.getState()); | |
7c673cae FG |
260 | } |
261 | return parsed_key.user_key; | |
262 | } | |
263 | ||
494da23a TL |
264 | Slice value() const override { return iter_->value(); } |
265 | Status status() const override { | |
7c673cae FG |
266 | return status_.ok() ? iter_->status() : status_; |
267 | } | |
268 | ||
269 | private: | |
270 | mutable Status status_; | |
271 | InternalIterator* iter_; | |
272 | bool arena_mode_; | |
273 | ||
274 | // No copying allowed | |
275 | KeyConvertingIterator(const KeyConvertingIterator&); | |
276 | void operator=(const KeyConvertingIterator&); | |
277 | }; | |
278 | ||
20effc67 TL |
279 | // `BlockConstructor` APIs always accept/return user keys. |
280 | class BlockConstructor : public Constructor { | |
281 | public: | |
282 | explicit BlockConstructor(const Comparator* cmp) | |
283 | : Constructor(cmp), comparator_(cmp), block_(nullptr) {} | |
284 | ~BlockConstructor() override { delete block_; } | |
285 | Status FinishImpl(const Options& /*options*/, | |
286 | const ImmutableCFOptions& /*ioptions*/, | |
287 | const MutableCFOptions& /*moptions*/, | |
288 | const BlockBasedTableOptions& table_options, | |
289 | const InternalKeyComparator& /*internal_comparator*/, | |
290 | const stl_wrappers::KVMap& kv_map) override { | |
291 | delete block_; | |
292 | block_ = nullptr; | |
293 | BlockBuilder builder(table_options.block_restart_interval); | |
294 | ||
295 | for (const auto& kv : kv_map) { | |
296 | // `DataBlockIter` assumes it reads only internal keys. `BlockConstructor` | |
297 | // clients provide user keys, so we need to convert to internal key format | |
298 | // before writing the data block. | |
299 | ParsedInternalKey ikey(kv.first, kMaxSequenceNumber, kTypeValue); | |
300 | std::string encoded; | |
301 | AppendInternalKey(&encoded, ikey); | |
302 | builder.Add(encoded, kv.second); | |
303 | } | |
304 | // Open the block | |
305 | data_ = builder.Finish().ToString(); | |
306 | BlockContents contents; | |
307 | contents.data = data_; | |
308 | block_ = new Block(std::move(contents)); | |
309 | return Status::OK(); | |
310 | } | |
311 | InternalIterator* NewIterator( | |
312 | const SliceTransform* /*prefix_extractor*/) const override { | |
313 | // `DataBlockIter` returns the internal keys it reads. | |
314 | // `KeyConvertingIterator` converts them to user keys before they are | |
315 | // exposed to the `BlockConstructor` clients. | |
316 | return new KeyConvertingIterator( | |
317 | block_->NewDataIterator(comparator_, kDisableGlobalSequenceNumber)); | |
318 | } | |
319 | ||
320 | private: | |
321 | const Comparator* comparator_; | |
322 | std::string data_; | |
323 | Block* block_; | |
324 | ||
325 | BlockConstructor(); | |
326 | }; | |
327 | ||
328 | class TableConstructor : public Constructor { | |
7c673cae FG |
329 | public: |
330 | explicit TableConstructor(const Comparator* cmp, | |
11fdf7f2 | 331 | bool convert_to_internal_key = false, |
f67539c2 | 332 | int level = -1, SequenceNumber largest_seqno = 0) |
7c673cae | 333 | : Constructor(cmp), |
f67539c2 | 334 | largest_seqno_(largest_seqno), |
11fdf7f2 | 335 | convert_to_internal_key_(convert_to_internal_key), |
f67539c2 TL |
336 | level_(level) { |
337 | env_ = ROCKSDB_NAMESPACE::Env::Default(); | |
338 | } | |
494da23a | 339 | ~TableConstructor() override { Reset(); } |
7c673cae | 340 | |
494da23a TL |
341 | Status FinishImpl(const Options& options, const ImmutableCFOptions& ioptions, |
342 | const MutableCFOptions& moptions, | |
343 | const BlockBasedTableOptions& /*table_options*/, | |
344 | const InternalKeyComparator& internal_comparator, | |
345 | const stl_wrappers::KVMap& kv_map) override { | |
7c673cae FG |
346 | Reset(); |
347 | soptions.use_mmap_reads = ioptions.allow_mmap_reads; | |
11fdf7f2 TL |
348 | file_writer_.reset(test::GetWritableFileWriter(new test::StringSink(), |
349 | "" /* don't care */)); | |
494da23a | 350 | std::unique_ptr<TableBuilder> builder; |
7c673cae FG |
351 | std::vector<std::unique_ptr<IntTblPropCollectorFactory>> |
352 | int_tbl_prop_collector_factories; | |
f67539c2 TL |
353 | |
354 | if (largest_seqno_ != 0) { | |
355 | // Pretend that it's an external file written by SstFileWriter. | |
356 | int_tbl_prop_collector_factories.emplace_back( | |
357 | new SstFileWriterPropertiesCollectorFactory(2 /* version */, | |
358 | 0 /* global_seqno*/)); | |
359 | } | |
360 | ||
7c673cae | 361 | std::string column_family_name; |
7c673cae | 362 | builder.reset(ioptions.table_factory->NewTableBuilder( |
494da23a TL |
363 | TableBuilderOptions(ioptions, moptions, internal_comparator, |
364 | &int_tbl_prop_collector_factories, | |
365 | options.compression, options.sample_for_compression, | |
366 | options.compression_opts, false /* skip_filters */, | |
367 | column_family_name, level_), | |
7c673cae FG |
368 | TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, |
369 | file_writer_.get())); | |
370 | ||
20effc67 | 371 | for (const auto& kv : kv_map) { |
7c673cae FG |
372 | if (convert_to_internal_key_) { |
373 | ParsedInternalKey ikey(kv.first, kMaxSequenceNumber, kTypeValue); | |
374 | std::string encoded; | |
375 | AppendInternalKey(&encoded, ikey); | |
376 | builder->Add(encoded, kv.second); | |
377 | } else { | |
378 | builder->Add(kv.first, kv.second); | |
379 | } | |
380 | EXPECT_TRUE(builder->status().ok()); | |
381 | } | |
382 | Status s = builder->Finish(); | |
383 | file_writer_->Flush(); | |
384 | EXPECT_TRUE(s.ok()) << s.ToString(); | |
385 | ||
11fdf7f2 | 386 | EXPECT_EQ(TEST_GetSink()->contents().size(), builder->FileSize()); |
7c673cae FG |
387 | |
388 | // Open the table | |
389 | uniq_id_ = cur_uniq_id_++; | |
390 | file_reader_.reset(test::GetRandomAccessFileReader(new test::StringSource( | |
11fdf7f2 TL |
391 | TEST_GetSink()->contents(), uniq_id_, ioptions.allow_mmap_reads))); |
392 | const bool kSkipFilters = true; | |
393 | const bool kImmortal = true; | |
7c673cae | 394 | return ioptions.table_factory->NewTableReader( |
11fdf7f2 TL |
395 | TableReaderOptions(ioptions, moptions.prefix_extractor.get(), soptions, |
396 | internal_comparator, !kSkipFilters, !kImmortal, | |
20effc67 TL |
397 | false, level_, largest_seqno_, &block_cache_tracer_, |
398 | moptions.write_buffer_size), | |
11fdf7f2 TL |
399 | std::move(file_reader_), TEST_GetSink()->contents().size(), |
400 | &table_reader_); | |
7c673cae FG |
401 | } |
402 | ||
494da23a | 403 | InternalIterator* NewIterator( |
11fdf7f2 | 404 | const SliceTransform* prefix_extractor) const override { |
f67539c2 | 405 | InternalIterator* iter = table_reader_->NewIterator( |
20effc67 TL |
406 | read_options_, prefix_extractor, /*arena=*/nullptr, |
407 | /*skip_filters=*/false, TableReaderCaller::kUncategorized); | |
7c673cae FG |
408 | if (convert_to_internal_key_) { |
409 | return new KeyConvertingIterator(iter); | |
410 | } else { | |
411 | return iter; | |
412 | } | |
413 | } | |
414 | ||
415 | uint64_t ApproximateOffsetOf(const Slice& key) const { | |
416 | if (convert_to_internal_key_) { | |
417 | InternalKey ikey(key, kMaxSequenceNumber, kTypeValue); | |
418 | const Slice skey = ikey.Encode(); | |
f67539c2 TL |
419 | return table_reader_->ApproximateOffsetOf( |
420 | skey, TableReaderCaller::kUncategorized); | |
7c673cae | 421 | } |
f67539c2 TL |
422 | return table_reader_->ApproximateOffsetOf( |
423 | key, TableReaderCaller::kUncategorized); | |
7c673cae FG |
424 | } |
425 | ||
11fdf7f2 TL |
426 | virtual Status Reopen(const ImmutableCFOptions& ioptions, |
427 | const MutableCFOptions& moptions) { | |
7c673cae | 428 | file_reader_.reset(test::GetRandomAccessFileReader(new test::StringSource( |
11fdf7f2 | 429 | TEST_GetSink()->contents(), uniq_id_, ioptions.allow_mmap_reads))); |
7c673cae | 430 | return ioptions.table_factory->NewTableReader( |
11fdf7f2 TL |
431 | TableReaderOptions(ioptions, moptions.prefix_extractor.get(), soptions, |
432 | *last_internal_key_), | |
433 | std::move(file_reader_), TEST_GetSink()->contents().size(), | |
434 | &table_reader_); | |
7c673cae FG |
435 | } |
436 | ||
11fdf7f2 | 437 | virtual TableReader* GetTableReader() { return table_reader_.get(); } |
7c673cae | 438 | |
494da23a | 439 | bool AnywayDeleteIterator() const override { |
7c673cae FG |
440 | return convert_to_internal_key_; |
441 | } | |
442 | ||
443 | void ResetTableReader() { table_reader_.reset(); } | |
444 | ||
445 | bool ConvertToInternalKey() { return convert_to_internal_key_; } | |
446 | ||
11fdf7f2 | 447 | test::StringSink* TEST_GetSink() { |
f67539c2 TL |
448 | return ROCKSDB_NAMESPACE::test::GetStringSinkFromLegacyWriter( |
449 | file_writer_.get()); | |
11fdf7f2 TL |
450 | } |
451 | ||
f67539c2 TL |
452 | BlockCacheTracer block_cache_tracer_; |
453 | ||
7c673cae FG |
454 | private: |
455 | void Reset() { | |
456 | uniq_id_ = 0; | |
457 | table_reader_.reset(); | |
458 | file_writer_.reset(); | |
459 | file_reader_.reset(); | |
460 | } | |
461 | ||
20effc67 | 462 | const ReadOptions read_options_; |
7c673cae | 463 | uint64_t uniq_id_; |
494da23a TL |
464 | std::unique_ptr<WritableFileWriter> file_writer_; |
465 | std::unique_ptr<RandomAccessFileReader> file_reader_; | |
466 | std::unique_ptr<TableReader> table_reader_; | |
f67539c2 | 467 | SequenceNumber largest_seqno_; |
7c673cae | 468 | bool convert_to_internal_key_; |
11fdf7f2 | 469 | int level_; |
7c673cae FG |
470 | |
471 | TableConstructor(); | |
472 | ||
473 | static uint64_t cur_uniq_id_; | |
474 | EnvOptions soptions; | |
f67539c2 | 475 | Env* env_; |
7c673cae FG |
476 | }; |
477 | uint64_t TableConstructor::cur_uniq_id_ = 1; | |
478 | ||
479 | class MemTableConstructor: public Constructor { | |
480 | public: | |
481 | explicit MemTableConstructor(const Comparator* cmp, WriteBufferManager* wb) | |
482 | : Constructor(cmp), | |
483 | internal_comparator_(cmp), | |
484 | write_buffer_manager_(wb), | |
485 | table_factory_(new SkipListFactory) { | |
486 | options_.memtable_factory = table_factory_; | |
487 | ImmutableCFOptions ioptions(options_); | |
488 | memtable_ = | |
489 | new MemTable(internal_comparator_, ioptions, MutableCFOptions(options_), | |
11fdf7f2 | 490 | wb, kMaxSequenceNumber, 0 /* column_family_id */); |
7c673cae FG |
491 | memtable_->Ref(); |
492 | } | |
494da23a TL |
493 | ~MemTableConstructor() override { delete memtable_->Unref(); } |
494 | Status FinishImpl(const Options&, const ImmutableCFOptions& ioptions, | |
495 | const MutableCFOptions& /*moptions*/, | |
496 | const BlockBasedTableOptions& /*table_options*/, | |
497 | const InternalKeyComparator& /*internal_comparator*/, | |
498 | const stl_wrappers::KVMap& kv_map) override { | |
7c673cae FG |
499 | delete memtable_->Unref(); |
500 | ImmutableCFOptions mem_ioptions(ioptions); | |
501 | memtable_ = new MemTable(internal_comparator_, mem_ioptions, | |
502 | MutableCFOptions(options_), write_buffer_manager_, | |
11fdf7f2 | 503 | kMaxSequenceNumber, 0 /* column_family_id */); |
7c673cae FG |
504 | memtable_->Ref(); |
505 | int seq = 1; | |
20effc67 | 506 | for (const auto& kv : kv_map) { |
7c673cae FG |
507 | memtable_->Add(seq, kTypeValue, kv.first, kv.second); |
508 | seq++; | |
509 | } | |
510 | return Status::OK(); | |
511 | } | |
494da23a | 512 | InternalIterator* NewIterator( |
11fdf7f2 | 513 | const SliceTransform* /*prefix_extractor*/) const override { |
7c673cae FG |
514 | return new KeyConvertingIterator( |
515 | memtable_->NewIterator(ReadOptions(), &arena_), true); | |
516 | } | |
517 | ||
494da23a | 518 | bool AnywayDeleteIterator() const override { return true; } |
7c673cae | 519 | |
494da23a | 520 | bool IsArenaMode() const override { return true; } |
7c673cae FG |
521 | |
522 | private: | |
523 | mutable Arena arena_; | |
524 | InternalKeyComparator internal_comparator_; | |
525 | Options options_; | |
526 | WriteBufferManager* write_buffer_manager_; | |
527 | MemTable* memtable_; | |
528 | std::shared_ptr<SkipListFactory> table_factory_; | |
529 | }; | |
530 | ||
531 | class InternalIteratorFromIterator : public InternalIterator { | |
532 | public: | |
533 | explicit InternalIteratorFromIterator(Iterator* it) : it_(it) {} | |
494da23a TL |
534 | bool Valid() const override { return it_->Valid(); } |
535 | void Seek(const Slice& target) override { it_->Seek(target); } | |
536 | void SeekForPrev(const Slice& target) override { it_->SeekForPrev(target); } | |
537 | void SeekToFirst() override { it_->SeekToFirst(); } | |
538 | void SeekToLast() override { it_->SeekToLast(); } | |
539 | void Next() override { it_->Next(); } | |
540 | void Prev() override { it_->Prev(); } | |
7c673cae FG |
541 | Slice key() const override { return it_->key(); } |
542 | Slice value() const override { return it_->value(); } | |
494da23a | 543 | Status status() const override { return it_->status(); } |
7c673cae FG |
544 | |
545 | private: | |
494da23a | 546 | std::unique_ptr<Iterator> it_; |
7c673cae FG |
547 | }; |
548 | ||
549 | class DBConstructor: public Constructor { | |
550 | public: | |
551 | explicit DBConstructor(const Comparator* cmp) | |
552 | : Constructor(cmp), | |
553 | comparator_(cmp) { | |
554 | db_ = nullptr; | |
555 | NewDB(); | |
556 | } | |
494da23a TL |
557 | ~DBConstructor() override { delete db_; } |
558 | Status FinishImpl(const Options& /*options*/, | |
559 | const ImmutableCFOptions& /*ioptions*/, | |
560 | const MutableCFOptions& /*moptions*/, | |
561 | const BlockBasedTableOptions& /*table_options*/, | |
562 | const InternalKeyComparator& /*internal_comparator*/, | |
563 | const stl_wrappers::KVMap& kv_map) override { | |
7c673cae FG |
564 | delete db_; |
565 | db_ = nullptr; | |
566 | NewDB(); | |
20effc67 | 567 | for (const auto& kv : kv_map) { |
7c673cae | 568 | WriteBatch batch; |
20effc67 | 569 | EXPECT_OK(batch.Put(kv.first, kv.second)); |
7c673cae FG |
570 | EXPECT_TRUE(db_->Write(WriteOptions(), &batch).ok()); |
571 | } | |
572 | return Status::OK(); | |
573 | } | |
574 | ||
494da23a | 575 | InternalIterator* NewIterator( |
11fdf7f2 | 576 | const SliceTransform* /*prefix_extractor*/) const override { |
7c673cae FG |
577 | return new InternalIteratorFromIterator(db_->NewIterator(ReadOptions())); |
578 | } | |
579 | ||
494da23a | 580 | DB* db() const override { return db_; } |
7c673cae FG |
581 | |
582 | private: | |
583 | void NewDB() { | |
11fdf7f2 | 584 | std::string name = test::PerThreadDBPath("table_testdb"); |
7c673cae FG |
585 | |
586 | Options options; | |
587 | options.comparator = comparator_; | |
588 | Status status = DestroyDB(name, options); | |
589 | ASSERT_TRUE(status.ok()) << status.ToString(); | |
590 | ||
591 | options.create_if_missing = true; | |
592 | options.error_if_exists = true; | |
593 | options.write_buffer_size = 10000; // Something small to force merging | |
594 | status = DB::Open(options, name, &db_); | |
595 | ASSERT_TRUE(status.ok()) << status.ToString(); | |
596 | } | |
597 | ||
598 | const Comparator* comparator_; | |
599 | DB* db_; | |
600 | }; | |
601 | ||
602 | enum TestType { | |
603 | BLOCK_BASED_TABLE_TEST, | |
604 | #ifndef ROCKSDB_LITE | |
605 | PLAIN_TABLE_SEMI_FIXED_PREFIX, | |
606 | PLAIN_TABLE_FULL_STR_PREFIX, | |
607 | PLAIN_TABLE_TOTAL_ORDER, | |
608 | #endif // !ROCKSDB_LITE | |
609 | BLOCK_TEST, | |
610 | MEMTABLE_TEST, | |
611 | DB_TEST | |
612 | }; | |
613 | ||
614 | struct TestArgs { | |
615 | TestType type; | |
616 | bool reverse_compare; | |
617 | int restart_interval; | |
618 | CompressionType compression; | |
20effc67 | 619 | uint32_t compression_parallel_threads; |
7c673cae FG |
620 | uint32_t format_version; |
621 | bool use_mmap; | |
622 | }; | |
623 | ||
20effc67 TL |
624 | std::ostream& operator<<(std::ostream& os, const TestArgs& args) { |
625 | os << "type: " << args.type << " reverse_compare: " << args.reverse_compare | |
626 | << " restart_interval: " << args.restart_interval | |
627 | << " compression: " << args.compression | |
628 | << " compression_parallel_threads: " << args.compression_parallel_threads | |
629 | << " format_version: " << args.format_version | |
630 | << " use_mmap: " << args.use_mmap; | |
631 | ||
632 | return os; | |
633 | } | |
634 | ||
7c673cae FG |
635 | static std::vector<TestArgs> GenerateArgList() { |
636 | std::vector<TestArgs> test_args; | |
637 | std::vector<TestType> test_types = { | |
638 | BLOCK_BASED_TABLE_TEST, | |
639 | #ifndef ROCKSDB_LITE | |
640 | PLAIN_TABLE_SEMI_FIXED_PREFIX, | |
641 | PLAIN_TABLE_FULL_STR_PREFIX, | |
642 | PLAIN_TABLE_TOTAL_ORDER, | |
643 | #endif // !ROCKSDB_LITE | |
644 | BLOCK_TEST, | |
645 | MEMTABLE_TEST, DB_TEST}; | |
646 | std::vector<bool> reverse_compare_types = {false, true}; | |
647 | std::vector<int> restart_intervals = {16, 1, 1024}; | |
20effc67 | 648 | std::vector<uint32_t> compression_parallel_threads = {1, 4}; |
7c673cae FG |
649 | |
650 | // Only add compression if it is supported | |
651 | std::vector<std::pair<CompressionType, bool>> compression_types; | |
652 | compression_types.emplace_back(kNoCompression, false); | |
653 | if (Snappy_Supported()) { | |
654 | compression_types.emplace_back(kSnappyCompression, false); | |
655 | } | |
656 | if (Zlib_Supported()) { | |
657 | compression_types.emplace_back(kZlibCompression, false); | |
658 | compression_types.emplace_back(kZlibCompression, true); | |
659 | } | |
660 | if (BZip2_Supported()) { | |
661 | compression_types.emplace_back(kBZip2Compression, false); | |
662 | compression_types.emplace_back(kBZip2Compression, true); | |
663 | } | |
664 | if (LZ4_Supported()) { | |
665 | compression_types.emplace_back(kLZ4Compression, false); | |
666 | compression_types.emplace_back(kLZ4Compression, true); | |
667 | compression_types.emplace_back(kLZ4HCCompression, false); | |
668 | compression_types.emplace_back(kLZ4HCCompression, true); | |
669 | } | |
670 | if (XPRESS_Supported()) { | |
671 | compression_types.emplace_back(kXpressCompression, false); | |
672 | compression_types.emplace_back(kXpressCompression, true); | |
673 | } | |
674 | if (ZSTD_Supported()) { | |
675 | compression_types.emplace_back(kZSTD, false); | |
676 | compression_types.emplace_back(kZSTD, true); | |
677 | } | |
678 | ||
679 | for (auto test_type : test_types) { | |
680 | for (auto reverse_compare : reverse_compare_types) { | |
681 | #ifndef ROCKSDB_LITE | |
682 | if (test_type == PLAIN_TABLE_SEMI_FIXED_PREFIX || | |
683 | test_type == PLAIN_TABLE_FULL_STR_PREFIX || | |
684 | test_type == PLAIN_TABLE_TOTAL_ORDER) { | |
685 | // Plain table doesn't use restart index or compression. | |
686 | TestArgs one_arg; | |
687 | one_arg.type = test_type; | |
688 | one_arg.reverse_compare = reverse_compare; | |
689 | one_arg.restart_interval = restart_intervals[0]; | |
690 | one_arg.compression = compression_types[0].first; | |
20effc67 TL |
691 | one_arg.compression_parallel_threads = 1; |
692 | one_arg.format_version = 0; | |
7c673cae FG |
693 | one_arg.use_mmap = true; |
694 | test_args.push_back(one_arg); | |
695 | one_arg.use_mmap = false; | |
696 | test_args.push_back(one_arg); | |
697 | continue; | |
698 | } | |
699 | #endif // !ROCKSDB_LITE | |
700 | ||
701 | for (auto restart_interval : restart_intervals) { | |
702 | for (auto compression_type : compression_types) { | |
20effc67 TL |
703 | for (auto num_threads : compression_parallel_threads) { |
704 | TestArgs one_arg; | |
705 | one_arg.type = test_type; | |
706 | one_arg.reverse_compare = reverse_compare; | |
707 | one_arg.restart_interval = restart_interval; | |
708 | one_arg.compression = compression_type.first; | |
709 | one_arg.compression_parallel_threads = num_threads; | |
710 | one_arg.format_version = compression_type.second ? 2 : 1; | |
711 | one_arg.use_mmap = false; | |
712 | test_args.push_back(one_arg); | |
713 | } | |
7c673cae FG |
714 | } |
715 | } | |
716 | } | |
717 | } | |
718 | return test_args; | |
719 | } | |
720 | ||
721 | // In order to make all tests run for plain table format, including | |
722 | // those operating on empty keys, create a new prefix transformer which | |
723 | // return fixed prefix if the slice is not shorter than the prefix length, | |
724 | // and the full slice if it is shorter. | |
725 | class FixedOrLessPrefixTransform : public SliceTransform { | |
726 | private: | |
727 | const size_t prefix_len_; | |
728 | ||
729 | public: | |
730 | explicit FixedOrLessPrefixTransform(size_t prefix_len) : | |
731 | prefix_len_(prefix_len) { | |
732 | } | |
733 | ||
494da23a | 734 | const char* Name() const override { return "rocksdb.FixedPrefix"; } |
7c673cae | 735 | |
494da23a | 736 | Slice Transform(const Slice& src) const override { |
7c673cae FG |
737 | assert(InDomain(src)); |
738 | if (src.size() < prefix_len_) { | |
739 | return src; | |
740 | } | |
741 | return Slice(src.data(), prefix_len_); | |
742 | } | |
743 | ||
494da23a | 744 | bool InDomain(const Slice& /*src*/) const override { return true; } |
7c673cae | 745 | |
494da23a | 746 | bool InRange(const Slice& dst) const override { |
7c673cae FG |
747 | return (dst.size() <= prefix_len_); |
748 | } | |
494da23a | 749 | bool FullLengthEnabled(size_t* /*len*/) const override { return false; } |
7c673cae FG |
750 | }; |
751 | ||
752 | class HarnessTest : public testing::Test { | |
753 | public: | |
20effc67 TL |
754 | explicit HarnessTest(const TestArgs& args) |
755 | : args_(args), | |
756 | ioptions_(options_), | |
11fdf7f2 | 757 | moptions_(options_), |
20effc67 TL |
758 | write_buffer_(options_.db_write_buffer_size), |
759 | support_prev_(true), | |
760 | only_support_prefix_seek_(false) { | |
761 | options_.compression = args_.compression; | |
762 | options_.compression_opts.parallel_threads = | |
763 | args_.compression_parallel_threads; | |
7c673cae FG |
764 | // Use shorter block size for tests to exercise block boundary |
765 | // conditions more. | |
20effc67 | 766 | if (args_.reverse_compare) { |
7c673cae FG |
767 | options_.comparator = &reverse_key_comparator; |
768 | } | |
769 | ||
770 | internal_comparator_.reset( | |
771 | new test::PlainInternalKeyComparator(options_.comparator)); | |
772 | ||
20effc67 TL |
773 | options_.allow_mmap_reads = args_.use_mmap; |
774 | switch (args_.type) { | |
7c673cae FG |
775 | case BLOCK_BASED_TABLE_TEST: |
776 | table_options_.flush_block_policy_factory.reset( | |
777 | new FlushBlockBySizePolicyFactory()); | |
778 | table_options_.block_size = 256; | |
20effc67 TL |
779 | table_options_.block_restart_interval = args_.restart_interval; |
780 | table_options_.index_block_restart_interval = args_.restart_interval; | |
781 | table_options_.format_version = args_.format_version; | |
7c673cae FG |
782 | options_.table_factory.reset( |
783 | new BlockBasedTableFactory(table_options_)); | |
20effc67 TL |
784 | constructor_.reset(new TableConstructor( |
785 | options_.comparator, true /* convert_to_internal_key_ */)); | |
7c673cae FG |
786 | internal_comparator_.reset( |
787 | new InternalKeyComparator(options_.comparator)); | |
788 | break; | |
789 | // Plain table is not supported in ROCKSDB_LITE | |
790 | #ifndef ROCKSDB_LITE | |
791 | case PLAIN_TABLE_SEMI_FIXED_PREFIX: | |
792 | support_prev_ = false; | |
793 | only_support_prefix_seek_ = true; | |
794 | options_.prefix_extractor.reset(new FixedOrLessPrefixTransform(2)); | |
795 | options_.table_factory.reset(NewPlainTableFactory()); | |
20effc67 TL |
796 | constructor_.reset(new TableConstructor( |
797 | options_.comparator, true /* convert_to_internal_key_ */)); | |
7c673cae FG |
798 | internal_comparator_.reset( |
799 | new InternalKeyComparator(options_.comparator)); | |
800 | break; | |
801 | case PLAIN_TABLE_FULL_STR_PREFIX: | |
802 | support_prev_ = false; | |
803 | only_support_prefix_seek_ = true; | |
804 | options_.prefix_extractor.reset(NewNoopTransform()); | |
805 | options_.table_factory.reset(NewPlainTableFactory()); | |
20effc67 TL |
806 | constructor_.reset(new TableConstructor( |
807 | options_.comparator, true /* convert_to_internal_key_ */)); | |
7c673cae FG |
808 | internal_comparator_.reset( |
809 | new InternalKeyComparator(options_.comparator)); | |
810 | break; | |
811 | case PLAIN_TABLE_TOTAL_ORDER: | |
812 | support_prev_ = false; | |
813 | only_support_prefix_seek_ = false; | |
814 | options_.prefix_extractor = nullptr; | |
815 | ||
816 | { | |
817 | PlainTableOptions plain_table_options; | |
818 | plain_table_options.user_key_len = kPlainTableVariableLength; | |
819 | plain_table_options.bloom_bits_per_key = 0; | |
820 | plain_table_options.hash_table_ratio = 0; | |
821 | ||
822 | options_.table_factory.reset( | |
823 | NewPlainTableFactory(plain_table_options)); | |
824 | } | |
20effc67 TL |
825 | constructor_.reset(new TableConstructor( |
826 | options_.comparator, true /* convert_to_internal_key_ */)); | |
7c673cae FG |
827 | internal_comparator_.reset( |
828 | new InternalKeyComparator(options_.comparator)); | |
829 | break; | |
830 | #endif // !ROCKSDB_LITE | |
831 | case BLOCK_TEST: | |
832 | table_options_.block_size = 256; | |
833 | options_.table_factory.reset( | |
834 | new BlockBasedTableFactory(table_options_)); | |
20effc67 | 835 | constructor_.reset(new BlockConstructor(options_.comparator)); |
7c673cae FG |
836 | break; |
837 | case MEMTABLE_TEST: | |
838 | table_options_.block_size = 256; | |
839 | options_.table_factory.reset( | |
840 | new BlockBasedTableFactory(table_options_)); | |
20effc67 TL |
841 | constructor_.reset( |
842 | new MemTableConstructor(options_.comparator, &write_buffer_)); | |
7c673cae FG |
843 | break; |
844 | case DB_TEST: | |
845 | table_options_.block_size = 256; | |
846 | options_.table_factory.reset( | |
847 | new BlockBasedTableFactory(table_options_)); | |
20effc67 | 848 | constructor_.reset(new DBConstructor(options_.comparator)); |
7c673cae FG |
849 | break; |
850 | } | |
851 | ioptions_ = ImmutableCFOptions(options_); | |
11fdf7f2 | 852 | moptions_ = MutableCFOptions(options_); |
7c673cae FG |
853 | } |
854 | ||
7c673cae FG |
855 | void Add(const std::string& key, const std::string& value) { |
856 | constructor_->Add(key, value); | |
857 | } | |
858 | ||
859 | void Test(Random* rnd) { | |
860 | std::vector<std::string> keys; | |
861 | stl_wrappers::KVMap data; | |
11fdf7f2 | 862 | constructor_->Finish(options_, ioptions_, moptions_, table_options_, |
7c673cae FG |
863 | *internal_comparator_, &keys, &data); |
864 | ||
865 | TestForwardScan(keys, data); | |
866 | if (support_prev_) { | |
867 | TestBackwardScan(keys, data); | |
868 | } | |
869 | TestRandomAccess(rnd, keys, data); | |
870 | } | |
871 | ||
11fdf7f2 | 872 | void TestForwardScan(const std::vector<std::string>& /*keys*/, |
7c673cae FG |
873 | const stl_wrappers::KVMap& data) { |
874 | InternalIterator* iter = constructor_->NewIterator(); | |
875 | ASSERT_TRUE(!iter->Valid()); | |
876 | iter->SeekToFirst(); | |
20effc67 | 877 | ASSERT_OK(iter->status()); |
7c673cae FG |
878 | for (stl_wrappers::KVMap::const_iterator model_iter = data.begin(); |
879 | model_iter != data.end(); ++model_iter) { | |
880 | ASSERT_EQ(ToString(data, model_iter), ToString(iter)); | |
881 | iter->Next(); | |
20effc67 | 882 | ASSERT_OK(iter->status()); |
7c673cae FG |
883 | } |
884 | ASSERT_TRUE(!iter->Valid()); | |
20effc67 | 885 | ASSERT_OK(iter->status()); |
7c673cae FG |
886 | if (constructor_->IsArenaMode() && !constructor_->AnywayDeleteIterator()) { |
887 | iter->~InternalIterator(); | |
888 | } else { | |
889 | delete iter; | |
890 | } | |
891 | } | |
892 | ||
11fdf7f2 | 893 | void TestBackwardScan(const std::vector<std::string>& /*keys*/, |
7c673cae FG |
894 | const stl_wrappers::KVMap& data) { |
895 | InternalIterator* iter = constructor_->NewIterator(); | |
896 | ASSERT_TRUE(!iter->Valid()); | |
897 | iter->SeekToLast(); | |
20effc67 | 898 | ASSERT_OK(iter->status()); |
7c673cae FG |
899 | for (stl_wrappers::KVMap::const_reverse_iterator model_iter = data.rbegin(); |
900 | model_iter != data.rend(); ++model_iter) { | |
901 | ASSERT_EQ(ToString(data, model_iter), ToString(iter)); | |
902 | iter->Prev(); | |
20effc67 | 903 | ASSERT_OK(iter->status()); |
7c673cae FG |
904 | } |
905 | ASSERT_TRUE(!iter->Valid()); | |
20effc67 | 906 | ASSERT_OK(iter->status()); |
7c673cae FG |
907 | if (constructor_->IsArenaMode() && !constructor_->AnywayDeleteIterator()) { |
908 | iter->~InternalIterator(); | |
909 | } else { | |
910 | delete iter; | |
911 | } | |
912 | } | |
913 | ||
914 | void TestRandomAccess(Random* rnd, const std::vector<std::string>& keys, | |
915 | const stl_wrappers::KVMap& data) { | |
916 | static const bool kVerbose = false; | |
917 | InternalIterator* iter = constructor_->NewIterator(); | |
918 | ASSERT_TRUE(!iter->Valid()); | |
919 | stl_wrappers::KVMap::const_iterator model_iter = data.begin(); | |
920 | if (kVerbose) fprintf(stderr, "---\n"); | |
921 | for (int i = 0; i < 200; i++) { | |
922 | const int toss = rnd->Uniform(support_prev_ ? 5 : 3); | |
923 | switch (toss) { | |
924 | case 0: { | |
925 | if (iter->Valid()) { | |
926 | if (kVerbose) fprintf(stderr, "Next\n"); | |
927 | iter->Next(); | |
20effc67 | 928 | ASSERT_OK(iter->status()); |
7c673cae FG |
929 | ++model_iter; |
930 | ASSERT_EQ(ToString(data, model_iter), ToString(iter)); | |
931 | } | |
932 | break; | |
933 | } | |
934 | ||
935 | case 1: { | |
936 | if (kVerbose) fprintf(stderr, "SeekToFirst\n"); | |
937 | iter->SeekToFirst(); | |
20effc67 | 938 | ASSERT_OK(iter->status()); |
7c673cae FG |
939 | model_iter = data.begin(); |
940 | ASSERT_EQ(ToString(data, model_iter), ToString(iter)); | |
941 | break; | |
942 | } | |
943 | ||
944 | case 2: { | |
945 | std::string key = PickRandomKey(rnd, keys); | |
946 | model_iter = data.lower_bound(key); | |
947 | if (kVerbose) fprintf(stderr, "Seek '%s'\n", | |
948 | EscapeString(key).c_str()); | |
949 | iter->Seek(Slice(key)); | |
20effc67 | 950 | ASSERT_OK(iter->status()); |
7c673cae FG |
951 | ASSERT_EQ(ToString(data, model_iter), ToString(iter)); |
952 | break; | |
953 | } | |
954 | ||
955 | case 3: { | |
956 | if (iter->Valid()) { | |
957 | if (kVerbose) fprintf(stderr, "Prev\n"); | |
958 | iter->Prev(); | |
20effc67 | 959 | ASSERT_OK(iter->status()); |
7c673cae FG |
960 | if (model_iter == data.begin()) { |
961 | model_iter = data.end(); // Wrap around to invalid value | |
962 | } else { | |
963 | --model_iter; | |
964 | } | |
965 | ASSERT_EQ(ToString(data, model_iter), ToString(iter)); | |
966 | } | |
967 | break; | |
968 | } | |
969 | ||
970 | case 4: { | |
971 | if (kVerbose) fprintf(stderr, "SeekToLast\n"); | |
972 | iter->SeekToLast(); | |
20effc67 | 973 | ASSERT_OK(iter->status()); |
7c673cae FG |
974 | if (keys.empty()) { |
975 | model_iter = data.end(); | |
976 | } else { | |
977 | std::string last = data.rbegin()->first; | |
978 | model_iter = data.lower_bound(last); | |
979 | } | |
980 | ASSERT_EQ(ToString(data, model_iter), ToString(iter)); | |
981 | break; | |
982 | } | |
983 | } | |
984 | } | |
985 | if (constructor_->IsArenaMode() && !constructor_->AnywayDeleteIterator()) { | |
986 | iter->~InternalIterator(); | |
987 | } else { | |
988 | delete iter; | |
989 | } | |
990 | } | |
991 | ||
992 | std::string ToString(const stl_wrappers::KVMap& data, | |
993 | const stl_wrappers::KVMap::const_iterator& it) { | |
994 | if (it == data.end()) { | |
995 | return "END"; | |
996 | } else { | |
997 | return "'" + it->first + "->" + it->second + "'"; | |
998 | } | |
999 | } | |
1000 | ||
1001 | std::string ToString(const stl_wrappers::KVMap& data, | |
1002 | const stl_wrappers::KVMap::const_reverse_iterator& it) { | |
1003 | if (it == data.rend()) { | |
1004 | return "END"; | |
1005 | } else { | |
1006 | return "'" + it->first + "->" + it->second + "'"; | |
1007 | } | |
1008 | } | |
1009 | ||
1010 | std::string ToString(const InternalIterator* it) { | |
1011 | if (!it->Valid()) { | |
1012 | return "END"; | |
1013 | } else { | |
1014 | return "'" + it->key().ToString() + "->" + it->value().ToString() + "'"; | |
1015 | } | |
1016 | } | |
1017 | ||
1018 | std::string PickRandomKey(Random* rnd, const std::vector<std::string>& keys) { | |
1019 | if (keys.empty()) { | |
1020 | return "foo"; | |
1021 | } else { | |
1022 | const int index = rnd->Uniform(static_cast<int>(keys.size())); | |
1023 | std::string result = keys[index]; | |
1024 | switch (rnd->Uniform(support_prev_ ? 3 : 1)) { | |
1025 | case 0: | |
1026 | // Return an existing key | |
1027 | break; | |
1028 | case 1: { | |
1029 | // Attempt to return something smaller than an existing key | |
1030 | if (result.size() > 0 && result[result.size() - 1] > '\0' | |
1031 | && (!only_support_prefix_seek_ | |
1032 | || options_.prefix_extractor->Transform(result).size() | |
1033 | < result.size())) { | |
1034 | result[result.size() - 1]--; | |
1035 | } | |
1036 | break; | |
1037 | } | |
1038 | case 2: { | |
1039 | // Return something larger than an existing key | |
1040 | Increment(options_.comparator, &result); | |
1041 | break; | |
1042 | } | |
1043 | } | |
1044 | return result; | |
1045 | } | |
1046 | } | |
1047 | ||
1048 | // Returns nullptr if not running against a DB | |
1049 | DB* db() const { return constructor_->db(); } | |
1050 | ||
1051 | private: | |
20effc67 TL |
1052 | TestArgs args_; |
1053 | Options options_; | |
7c673cae | 1054 | ImmutableCFOptions ioptions_; |
11fdf7f2 | 1055 | MutableCFOptions moptions_; |
20effc67 TL |
1056 | BlockBasedTableOptions table_options_; |
1057 | std::unique_ptr<Constructor> constructor_; | |
7c673cae FG |
1058 | WriteBufferManager write_buffer_; |
1059 | bool support_prev_; | |
1060 | bool only_support_prefix_seek_; | |
494da23a | 1061 | std::shared_ptr<InternalKeyComparator> internal_comparator_; |
7c673cae FG |
1062 | }; |
1063 | ||
20effc67 TL |
1064 | class ParameterizedHarnessTest : public HarnessTest, |
1065 | public testing::WithParamInterface<TestArgs> { | |
1066 | public: | |
1067 | ParameterizedHarnessTest() : HarnessTest(GetParam()) {} | |
1068 | }; | |
1069 | ||
1070 | INSTANTIATE_TEST_CASE_P(TableTest, ParameterizedHarnessTest, | |
1071 | ::testing::ValuesIn(GenerateArgList())); | |
1072 | ||
1073 | class DBHarnessTest : public HarnessTest { | |
1074 | public: | |
1075 | DBHarnessTest() | |
1076 | : HarnessTest(TestArgs{DB_TEST, /* reverse_compare */ false, | |
1077 | /* restart_interval */ 16, kNoCompression, | |
1078 | /* compression_parallel_threads */ 1, | |
1079 | /* format_version */ 0, /* use_mmap */ false}) {} | |
1080 | }; | |
1081 | ||
7c673cae FG |
1082 | static bool Between(uint64_t val, uint64_t low, uint64_t high) { |
1083 | bool result = (val >= low) && (val <= high); | |
1084 | if (!result) { | |
1085 | fprintf(stderr, "Value %llu is not in range [%llu, %llu]\n", | |
1086 | (unsigned long long)(val), | |
1087 | (unsigned long long)(low), | |
1088 | (unsigned long long)(high)); | |
1089 | } | |
1090 | return result; | |
1091 | } | |
1092 | ||
1093 | // Tests against all kinds of tables | |
1094 | class TableTest : public testing::Test { | |
1095 | public: | |
1096 | const InternalKeyComparator& GetPlainInternalComparator( | |
1097 | const Comparator* comp) { | |
1098 | if (!plain_internal_comparator) { | |
1099 | plain_internal_comparator.reset( | |
1100 | new test::PlainInternalKeyComparator(comp)); | |
1101 | } | |
1102 | return *plain_internal_comparator; | |
1103 | } | |
1104 | void IndexTest(BlockBasedTableOptions table_options); | |
1105 | ||
1106 | private: | |
1107 | std::unique_ptr<InternalKeyComparator> plain_internal_comparator; | |
1108 | }; | |
1109 | ||
1110 | class GeneralTableTest : public TableTest {}; | |
11fdf7f2 TL |
1111 | class BlockBasedTableTest |
1112 | : public TableTest, | |
1113 | virtual public ::testing::WithParamInterface<uint32_t> { | |
1114 | public: | |
f67539c2 TL |
1115 | BlockBasedTableTest() : format_(GetParam()) { |
1116 | env_ = ROCKSDB_NAMESPACE::Env::Default(); | |
1117 | } | |
11fdf7f2 TL |
1118 | |
1119 | BlockBasedTableOptions GetBlockBasedTableOptions() { | |
1120 | BlockBasedTableOptions options; | |
1121 | options.format_version = format_; | |
1122 | return options; | |
1123 | } | |
1124 | ||
f67539c2 TL |
1125 | void SetupTracingTest(TableConstructor* c) { |
1126 | test_path_ = test::PerThreadDBPath("block_based_table_tracing_test"); | |
1127 | EXPECT_OK(env_->CreateDir(test_path_)); | |
1128 | trace_file_path_ = test_path_ + "/block_cache_trace_file"; | |
1129 | TraceOptions trace_opt; | |
1130 | std::unique_ptr<TraceWriter> trace_writer; | |
1131 | EXPECT_OK(NewFileTraceWriter(env_, EnvOptions(), trace_file_path_, | |
1132 | &trace_writer)); | |
20effc67 TL |
1133 | // Always return Status::OK(). |
1134 | assert(c->block_cache_tracer_ | |
1135 | .StartTrace(env_, trace_opt, std::move(trace_writer)) | |
1136 | .ok()); | |
f67539c2 TL |
1137 | { |
1138 | std::string user_key = "k01"; | |
1139 | InternalKey internal_key(user_key, 0, kTypeValue); | |
1140 | std::string encoded_key = internal_key.Encode().ToString(); | |
1141 | c->Add(encoded_key, kDummyValue); | |
1142 | } | |
1143 | { | |
1144 | std::string user_key = "k02"; | |
1145 | InternalKey internal_key(user_key, 0, kTypeValue); | |
1146 | std::string encoded_key = internal_key.Encode().ToString(); | |
1147 | c->Add(encoded_key, kDummyValue); | |
1148 | } | |
1149 | } | |
1150 | ||
1151 | void VerifyBlockAccessTrace( | |
1152 | TableConstructor* c, | |
1153 | const std::vector<BlockCacheTraceRecord>& expected_records) { | |
1154 | c->block_cache_tracer_.EndTrace(); | |
1155 | ||
20effc67 TL |
1156 | { |
1157 | std::unique_ptr<TraceReader> trace_reader; | |
1158 | Status s = | |
1159 | NewFileTraceReader(env_, EnvOptions(), trace_file_path_, &trace_reader); | |
1160 | EXPECT_OK(s); | |
1161 | BlockCacheTraceReader reader(std::move(trace_reader)); | |
1162 | BlockCacheTraceHeader header; | |
1163 | EXPECT_OK(reader.ReadHeader(&header)); | |
1164 | uint32_t index = 0; | |
1165 | while (s.ok()) { | |
1166 | BlockCacheTraceRecord access; | |
1167 | s = reader.ReadAccess(&access); | |
1168 | if (!s.ok()) { | |
1169 | break; | |
f67539c2 | 1170 | } |
20effc67 TL |
1171 | ASSERT_LT(index, expected_records.size()); |
1172 | EXPECT_NE("", access.block_key); | |
1173 | EXPECT_EQ(access.block_type, expected_records[index].block_type); | |
1174 | EXPECT_GT(access.block_size, 0); | |
1175 | EXPECT_EQ(access.caller, expected_records[index].caller); | |
1176 | EXPECT_EQ(access.no_insert, expected_records[index].no_insert); | |
1177 | EXPECT_EQ(access.is_cache_hit, expected_records[index].is_cache_hit); | |
1178 | // Get | |
1179 | if (access.caller == TableReaderCaller::kUserGet) { | |
1180 | EXPECT_EQ(access.referenced_key, | |
1181 | expected_records[index].referenced_key); | |
1182 | EXPECT_EQ(access.get_id, expected_records[index].get_id); | |
1183 | EXPECT_EQ(access.get_from_user_specified_snapshot, | |
1184 | expected_records[index].get_from_user_specified_snapshot); | |
1185 | if (access.block_type == TraceType::kBlockTraceDataBlock) { | |
1186 | EXPECT_GT(access.referenced_data_size, 0); | |
1187 | EXPECT_GT(access.num_keys_in_block, 0); | |
1188 | EXPECT_EQ(access.referenced_key_exist_in_block, | |
1189 | expected_records[index].referenced_key_exist_in_block); | |
1190 | } | |
1191 | } else { | |
1192 | EXPECT_EQ(access.referenced_key, ""); | |
1193 | EXPECT_EQ(access.get_id, 0); | |
1194 | EXPECT_TRUE(access.get_from_user_specified_snapshot == Boolean::kFalse); | |
1195 | EXPECT_EQ(access.referenced_data_size, 0); | |
1196 | EXPECT_EQ(access.num_keys_in_block, 0); | |
1197 | EXPECT_TRUE(access.referenced_key_exist_in_block == Boolean::kFalse); | |
1198 | } | |
1199 | index++; | |
f67539c2 | 1200 | } |
20effc67 | 1201 | EXPECT_EQ(index, expected_records.size()); |
f67539c2 | 1202 | } |
f67539c2 TL |
1203 | EXPECT_OK(env_->DeleteFile(trace_file_path_)); |
1204 | EXPECT_OK(env_->DeleteDir(test_path_)); | |
1205 | } | |
1206 | ||
11fdf7f2 TL |
1207 | protected: |
1208 | uint64_t IndexUncompressedHelper(bool indexCompress); | |
1209 | ||
1210 | private: | |
1211 | uint32_t format_; | |
f67539c2 TL |
1212 | Env* env_; |
1213 | std::string trace_file_path_; | |
1214 | std::string test_path_; | |
11fdf7f2 | 1215 | }; |
7c673cae FG |
1216 | class PlainTableTest : public TableTest {}; |
1217 | class TablePropertyTest : public testing::Test {}; | |
11fdf7f2 TL |
1218 | class BBTTailPrefetchTest : public TableTest {}; |
1219 | ||
f67539c2 TL |
1220 | // The helper class to test the file checksum |
1221 | class FileChecksumTestHelper { | |
1222 | public: | |
1223 | FileChecksumTestHelper(bool convert_to_internal_key = false) | |
1224 | : convert_to_internal_key_(convert_to_internal_key) { | |
f67539c2 TL |
1225 | } |
1226 | ~FileChecksumTestHelper() {} | |
1227 | ||
1228 | void CreateWriteableFile() { | |
20effc67 | 1229 | sink_ = new test::StringSink(); |
f67539c2 TL |
1230 | file_writer_.reset(test::GetWritableFileWriter(sink_, "" /* don't care */)); |
1231 | } | |
1232 | ||
20effc67 | 1233 | void SetFileChecksumGenerator(FileChecksumGenerator* checksum_generator) { |
f67539c2 | 1234 | if (file_writer_ != nullptr) { |
20effc67 TL |
1235 | file_writer_->TEST_SetFileChecksumGenerator(checksum_generator); |
1236 | } else { | |
1237 | delete checksum_generator; | |
f67539c2 TL |
1238 | } |
1239 | } | |
1240 | ||
1241 | WritableFileWriter* GetFileWriter() { return file_writer_.get(); } | |
1242 | ||
1243 | Status ResetTableBuilder(std::unique_ptr<TableBuilder>&& builder) { | |
1244 | assert(builder != nullptr); | |
1245 | table_builder_ = std::move(builder); | |
1246 | return Status::OK(); | |
1247 | } | |
1248 | ||
1249 | void AddKVtoKVMap(int num_entries) { | |
1250 | Random rnd(test::RandomSeed()); | |
1251 | for (int i = 0; i < num_entries; i++) { | |
20effc67 | 1252 | std::string v = rnd.RandomString(100); |
f67539c2 TL |
1253 | kv_map_[test::RandomKey(&rnd, 20)] = v; |
1254 | } | |
1255 | } | |
1256 | ||
1257 | Status WriteKVAndFlushTable() { | |
20effc67 | 1258 | for (const auto& kv : kv_map_) { |
f67539c2 TL |
1259 | if (convert_to_internal_key_) { |
1260 | ParsedInternalKey ikey(kv.first, kMaxSequenceNumber, kTypeValue); | |
1261 | std::string encoded; | |
1262 | AppendInternalKey(&encoded, ikey); | |
1263 | table_builder_->Add(encoded, kv.second); | |
1264 | } else { | |
1265 | table_builder_->Add(kv.first, kv.second); | |
1266 | } | |
1267 | EXPECT_TRUE(table_builder_->status().ok()); | |
1268 | } | |
1269 | Status s = table_builder_->Finish(); | |
1270 | file_writer_->Flush(); | |
1271 | EXPECT_TRUE(s.ok()); | |
1272 | ||
1273 | EXPECT_EQ(sink_->contents().size(), table_builder_->FileSize()); | |
1274 | return s; | |
1275 | } | |
1276 | ||
20effc67 TL |
1277 | std::string GetFileChecksum() { |
1278 | file_writer_->Close(); | |
1279 | return table_builder_->GetFileChecksum(); | |
1280 | } | |
f67539c2 TL |
1281 | |
1282 | const char* GetFileChecksumFuncName() { | |
1283 | return table_builder_->GetFileChecksumFuncName(); | |
1284 | } | |
1285 | ||
20effc67 | 1286 | Status CalculateFileChecksum(FileChecksumGenerator* file_checksum_generator, |
f67539c2 | 1287 | std::string* checksum) { |
20effc67 | 1288 | assert(file_checksum_generator != nullptr); |
f67539c2 TL |
1289 | cur_uniq_id_ = checksum_uniq_id_++; |
1290 | test::StringSink* ss_rw = | |
1291 | ROCKSDB_NAMESPACE::test::GetStringSinkFromLegacyWriter( | |
1292 | file_writer_.get()); | |
1293 | file_reader_.reset(test::GetRandomAccessFileReader( | |
1294 | new test::StringSource(ss_rw->contents()))); | |
1295 | std::unique_ptr<char[]> scratch(new char[2048]); | |
1296 | Slice result; | |
1297 | uint64_t offset = 0; | |
f67539c2 | 1298 | Status s; |
20effc67 TL |
1299 | s = file_reader_->Read(IOOptions(), offset, 2048, &result, scratch.get(), |
1300 | nullptr, false); | |
f67539c2 TL |
1301 | if (!s.ok()) { |
1302 | return s; | |
1303 | } | |
1304 | while (result.size() != 0) { | |
20effc67 | 1305 | file_checksum_generator->Update(scratch.get(), result.size()); |
f67539c2 | 1306 | offset += static_cast<uint64_t>(result.size()); |
20effc67 TL |
1307 | s = file_reader_->Read(IOOptions(), offset, 2048, &result, scratch.get(), |
1308 | nullptr, false); | |
f67539c2 TL |
1309 | if (!s.ok()) { |
1310 | return s; | |
1311 | } | |
1312 | } | |
1313 | EXPECT_EQ(offset, static_cast<uint64_t>(table_builder_->FileSize())); | |
20effc67 TL |
1314 | file_checksum_generator->Finalize(); |
1315 | *checksum = file_checksum_generator->GetChecksum(); | |
f67539c2 TL |
1316 | return Status::OK(); |
1317 | } | |
1318 | ||
1319 | private: | |
1320 | bool convert_to_internal_key_; | |
1321 | uint64_t cur_uniq_id_; | |
1322 | std::unique_ptr<WritableFileWriter> file_writer_; | |
1323 | std::unique_ptr<RandomAccessFileReader> file_reader_; | |
1324 | std::unique_ptr<TableBuilder> table_builder_; | |
1325 | stl_wrappers::KVMap kv_map_; | |
20effc67 | 1326 | test::StringSink* sink_ = nullptr; |
f67539c2 TL |
1327 | |
1328 | static uint64_t checksum_uniq_id_; | |
1329 | }; | |
1330 | ||
1331 | uint64_t FileChecksumTestHelper::checksum_uniq_id_ = 1; | |
1332 | ||
11fdf7f2 TL |
1333 | INSTANTIATE_TEST_CASE_P(FormatDef, BlockBasedTableTest, |
1334 | testing::Values(test::kDefaultFormatVersion)); | |
1335 | INSTANTIATE_TEST_CASE_P(FormatLatest, BlockBasedTableTest, | |
1336 | testing::Values(test::kLatestFormatVersion)); | |
7c673cae FG |
1337 | |
1338 | // This test serves as the living tutorial for the prefix scan of user collected | |
1339 | // properties. | |
1340 | TEST_F(TablePropertyTest, PrefixScanTest) { | |
1341 | UserCollectedProperties props{{"num.111.1", "1"}, | |
1342 | {"num.111.2", "2"}, | |
1343 | {"num.111.3", "3"}, | |
1344 | {"num.333.1", "1"}, | |
1345 | {"num.333.2", "2"}, | |
1346 | {"num.333.3", "3"}, | |
1347 | {"num.555.1", "1"}, | |
1348 | {"num.555.2", "2"}, | |
1349 | {"num.555.3", "3"}, }; | |
1350 | ||
1351 | // prefixes that exist | |
1352 | for (const std::string& prefix : {"num.111", "num.333", "num.555"}) { | |
1353 | int num = 0; | |
1354 | for (auto pos = props.lower_bound(prefix); | |
1355 | pos != props.end() && | |
1356 | pos->first.compare(0, prefix.size(), prefix) == 0; | |
1357 | ++pos) { | |
1358 | ++num; | |
1359 | auto key = prefix + "." + ToString(num); | |
1360 | ASSERT_EQ(key, pos->first); | |
1361 | ASSERT_EQ(ToString(num), pos->second); | |
1362 | } | |
1363 | ASSERT_EQ(3, num); | |
1364 | } | |
1365 | ||
1366 | // prefixes that don't exist | |
1367 | for (const std::string& prefix : | |
1368 | {"num.000", "num.222", "num.444", "num.666"}) { | |
1369 | auto pos = props.lower_bound(prefix); | |
1370 | ASSERT_TRUE(pos == props.end() || | |
1371 | pos->first.compare(0, prefix.size(), prefix) != 0); | |
1372 | } | |
1373 | } | |
1374 | ||
1375 | // This test include all the basic checks except those for index size and block | |
1376 | // size, which will be conducted in separated unit tests. | |
11fdf7f2 | 1377 | TEST_P(BlockBasedTableTest, BasicBlockBasedTableProperties) { |
7c673cae FG |
1378 | TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); |
1379 | ||
1380 | c.Add("a1", "val1"); | |
1381 | c.Add("b2", "val2"); | |
1382 | c.Add("c3", "val3"); | |
1383 | c.Add("d4", "val4"); | |
1384 | c.Add("e5", "val5"); | |
1385 | c.Add("f6", "val6"); | |
1386 | c.Add("g7", "val7"); | |
1387 | c.Add("h8", "val8"); | |
1388 | c.Add("j9", "val9"); | |
1389 | uint64_t diff_internal_user_bytes = 9 * 8; // 8 is seq size, 9 k-v totally | |
1390 | ||
1391 | std::vector<std::string> keys; | |
1392 | stl_wrappers::KVMap kvmap; | |
1393 | Options options; | |
1394 | options.compression = kNoCompression; | |
11fdf7f2 | 1395 | options.statistics = CreateDBStatistics(); |
494da23a | 1396 | options.statistics->set_stats_level(StatsLevel::kAll); |
11fdf7f2 | 1397 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
7c673cae FG |
1398 | table_options.block_restart_interval = 1; |
1399 | options.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
1400 | ||
11fdf7f2 TL |
1401 | ImmutableCFOptions ioptions(options); |
1402 | MutableCFOptions moptions(options); | |
1403 | ioptions.statistics = options.statistics.get(); | |
1404 | c.Finish(options, ioptions, moptions, table_options, | |
7c673cae | 1405 | GetPlainInternalComparator(options.comparator), &keys, &kvmap); |
11fdf7f2 | 1406 | ASSERT_EQ(options.statistics->getTickerCount(NUMBER_BLOCK_NOT_COMPRESSED), 0); |
7c673cae FG |
1407 | |
1408 | auto& props = *c.GetTableReader()->GetTableProperties(); | |
1409 | ASSERT_EQ(kvmap.size(), props.num_entries); | |
1410 | ||
1411 | auto raw_key_size = kvmap.size() * 2ul; | |
1412 | auto raw_value_size = kvmap.size() * 4ul; | |
1413 | ||
1414 | ASSERT_EQ(raw_key_size + diff_internal_user_bytes, props.raw_key_size); | |
1415 | ASSERT_EQ(raw_value_size, props.raw_value_size); | |
1416 | ASSERT_EQ(1ul, props.num_data_blocks); | |
1417 | ASSERT_EQ("", props.filter_policy_name); // no filter policy is used | |
1418 | ||
1419 | // Verify data size. | |
1420 | BlockBuilder block_builder(1); | |
1421 | for (const auto& item : kvmap) { | |
1422 | block_builder.Add(item.first, item.second); | |
1423 | } | |
1424 | Slice content = block_builder.Finish(); | |
1425 | ASSERT_EQ(content.size() + kBlockTrailerSize + diff_internal_user_bytes, | |
1426 | props.data_size); | |
1427 | c.ResetTableReader(); | |
1428 | } | |
1429 | ||
11fdf7f2 TL |
1430 | #ifdef SNAPPY |
1431 | uint64_t BlockBasedTableTest::IndexUncompressedHelper(bool compressed) { | |
1432 | TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); | |
1433 | constexpr size_t kNumKeys = 10000; | |
1434 | ||
1435 | for (size_t k = 0; k < kNumKeys; ++k) { | |
1436 | c.Add("key" + ToString(k), "val" + ToString(k)); | |
1437 | } | |
1438 | ||
1439 | std::vector<std::string> keys; | |
1440 | stl_wrappers::KVMap kvmap; | |
1441 | Options options; | |
1442 | options.compression = kSnappyCompression; | |
1443 | options.statistics = CreateDBStatistics(); | |
494da23a | 1444 | options.statistics->set_stats_level(StatsLevel::kAll); |
11fdf7f2 TL |
1445 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
1446 | table_options.block_restart_interval = 1; | |
1447 | table_options.enable_index_compression = compressed; | |
1448 | options.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
1449 | ||
1450 | ImmutableCFOptions ioptions(options); | |
1451 | MutableCFOptions moptions(options); | |
1452 | ioptions.statistics = options.statistics.get(); | |
1453 | c.Finish(options, ioptions, moptions, table_options, | |
1454 | GetPlainInternalComparator(options.comparator), &keys, &kvmap); | |
1455 | c.ResetTableReader(); | |
1456 | return options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED); | |
1457 | } | |
1458 | TEST_P(BlockBasedTableTest, IndexUncompressed) { | |
1459 | uint64_t tbl1_compressed_cnt = IndexUncompressedHelper(true); | |
1460 | uint64_t tbl2_compressed_cnt = IndexUncompressedHelper(false); | |
1461 | // tbl1_compressed_cnt should include 1 index block | |
1462 | EXPECT_EQ(tbl2_compressed_cnt + 1, tbl1_compressed_cnt); | |
1463 | } | |
1464 | #endif // SNAPPY | |
1465 | ||
1466 | TEST_P(BlockBasedTableTest, BlockBasedTableProperties2) { | |
7c673cae FG |
1467 | TableConstructor c(&reverse_key_comparator); |
1468 | std::vector<std::string> keys; | |
1469 | stl_wrappers::KVMap kvmap; | |
1470 | ||
1471 | { | |
1472 | Options options; | |
1473 | options.compression = CompressionType::kNoCompression; | |
11fdf7f2 | 1474 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
7c673cae FG |
1475 | options.table_factory.reset(NewBlockBasedTableFactory(table_options)); |
1476 | ||
1477 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 TL |
1478 | const MutableCFOptions moptions(options); |
1479 | c.Finish(options, ioptions, moptions, table_options, | |
7c673cae FG |
1480 | GetPlainInternalComparator(options.comparator), &keys, &kvmap); |
1481 | ||
1482 | auto& props = *c.GetTableReader()->GetTableProperties(); | |
1483 | ||
1484 | // Default comparator | |
1485 | ASSERT_EQ("leveldb.BytewiseComparator", props.comparator_name); | |
1486 | // No merge operator | |
1487 | ASSERT_EQ("nullptr", props.merge_operator_name); | |
1488 | // No prefix extractor | |
1489 | ASSERT_EQ("nullptr", props.prefix_extractor_name); | |
1490 | // No property collectors | |
1491 | ASSERT_EQ("[]", props.property_collectors_names); | |
1492 | // No filter policy is used | |
1493 | ASSERT_EQ("", props.filter_policy_name); | |
1494 | // Compression type == that set: | |
1495 | ASSERT_EQ("NoCompression", props.compression_name); | |
1496 | c.ResetTableReader(); | |
1497 | } | |
1498 | ||
1499 | { | |
1500 | Options options; | |
11fdf7f2 | 1501 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
7c673cae FG |
1502 | options.table_factory.reset(NewBlockBasedTableFactory(table_options)); |
1503 | options.comparator = &reverse_key_comparator; | |
1504 | options.merge_operator = MergeOperators::CreateUInt64AddOperator(); | |
1505 | options.prefix_extractor.reset(NewNoopTransform()); | |
1506 | options.table_properties_collector_factories.emplace_back( | |
1507 | new DummyPropertiesCollectorFactory1()); | |
1508 | options.table_properties_collector_factories.emplace_back( | |
1509 | new DummyPropertiesCollectorFactory2()); | |
1510 | ||
1511 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 TL |
1512 | const MutableCFOptions moptions(options); |
1513 | c.Finish(options, ioptions, moptions, table_options, | |
7c673cae FG |
1514 | GetPlainInternalComparator(options.comparator), &keys, &kvmap); |
1515 | ||
1516 | auto& props = *c.GetTableReader()->GetTableProperties(); | |
1517 | ||
1518 | ASSERT_EQ("rocksdb.ReverseBytewiseComparator", props.comparator_name); | |
1519 | ASSERT_EQ("UInt64AddOperator", props.merge_operator_name); | |
1520 | ASSERT_EQ("rocksdb.Noop", props.prefix_extractor_name); | |
20effc67 TL |
1521 | ASSERT_EQ( |
1522 | "[DummyPropertiesCollectorFactory1,DummyPropertiesCollectorFactory2]", | |
1523 | props.property_collectors_names); | |
7c673cae FG |
1524 | ASSERT_EQ("", props.filter_policy_name); // no filter policy is used |
1525 | c.ResetTableReader(); | |
1526 | } | |
1527 | } | |
1528 | ||
11fdf7f2 | 1529 | TEST_P(BlockBasedTableTest, RangeDelBlock) { |
7c673cae FG |
1530 | TableConstructor c(BytewiseComparator()); |
1531 | std::vector<std::string> keys = {"1pika", "2chu"}; | |
1532 | std::vector<std::string> vals = {"p", "c"}; | |
1533 | ||
494da23a TL |
1534 | std::vector<RangeTombstone> expected_tombstones = { |
1535 | {"1pika", "2chu", 0}, | |
1536 | {"2chu", "c", 1}, | |
1537 | {"2chu", "c", 0}, | |
1538 | {"c", "p", 0}, | |
1539 | }; | |
1540 | ||
7c673cae FG |
1541 | for (int i = 0; i < 2; i++) { |
1542 | RangeTombstone t(keys[i], vals[i], i); | |
1543 | std::pair<InternalKey, Slice> p = t.Serialize(); | |
1544 | c.Add(p.first.Encode().ToString(), p.second); | |
1545 | } | |
1546 | ||
1547 | std::vector<std::string> sorted_keys; | |
1548 | stl_wrappers::KVMap kvmap; | |
1549 | Options options; | |
1550 | options.compression = kNoCompression; | |
11fdf7f2 | 1551 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
7c673cae FG |
1552 | table_options.block_restart_interval = 1; |
1553 | options.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
1554 | ||
1555 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 | 1556 | const MutableCFOptions moptions(options); |
7c673cae FG |
1557 | std::unique_ptr<InternalKeyComparator> internal_cmp( |
1558 | new InternalKeyComparator(options.comparator)); | |
11fdf7f2 TL |
1559 | c.Finish(options, ioptions, moptions, table_options, *internal_cmp, |
1560 | &sorted_keys, &kvmap); | |
7c673cae FG |
1561 | |
1562 | for (int j = 0; j < 2; ++j) { | |
1563 | std::unique_ptr<InternalIterator> iter( | |
1564 | c.GetTableReader()->NewRangeTombstoneIterator(ReadOptions())); | |
1565 | if (j > 0) { | |
1566 | // For second iteration, delete the table reader object and verify the | |
1567 | // iterator can still access its metablock's range tombstones. | |
1568 | c.ResetTableReader(); | |
1569 | } | |
1570 | ASSERT_FALSE(iter->Valid()); | |
1571 | iter->SeekToFirst(); | |
1572 | ASSERT_TRUE(iter->Valid()); | |
494da23a | 1573 | for (size_t i = 0; i < expected_tombstones.size(); i++) { |
7c673cae FG |
1574 | ASSERT_TRUE(iter->Valid()); |
1575 | ParsedInternalKey parsed_key; | |
20effc67 TL |
1576 | ASSERT_OK( |
1577 | ParseInternalKey(iter->key(), &parsed_key, true /* log_err_key */)); | |
7c673cae | 1578 | RangeTombstone t(parsed_key, iter->value()); |
494da23a TL |
1579 | const auto& expected_t = expected_tombstones[i]; |
1580 | ASSERT_EQ(t.start_key_, expected_t.start_key_); | |
1581 | ASSERT_EQ(t.end_key_, expected_t.end_key_); | |
1582 | ASSERT_EQ(t.seq_, expected_t.seq_); | |
7c673cae FG |
1583 | iter->Next(); |
1584 | } | |
1585 | ASSERT_TRUE(!iter->Valid()); | |
1586 | } | |
1587 | } | |
1588 | ||
11fdf7f2 | 1589 | TEST_P(BlockBasedTableTest, FilterPolicyNameProperties) { |
7c673cae FG |
1590 | TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); |
1591 | c.Add("a1", "val1"); | |
1592 | std::vector<std::string> keys; | |
1593 | stl_wrappers::KVMap kvmap; | |
11fdf7f2 | 1594 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
7c673cae FG |
1595 | table_options.filter_policy.reset(NewBloomFilterPolicy(10)); |
1596 | Options options; | |
1597 | options.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
1598 | ||
1599 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 TL |
1600 | const MutableCFOptions moptions(options); |
1601 | c.Finish(options, ioptions, moptions, table_options, | |
7c673cae FG |
1602 | GetPlainInternalComparator(options.comparator), &keys, &kvmap); |
1603 | auto& props = *c.GetTableReader()->GetTableProperties(); | |
1604 | ASSERT_EQ("rocksdb.BuiltinBloomFilter", props.filter_policy_name); | |
1605 | c.ResetTableReader(); | |
1606 | } | |
1607 | ||
1608 | // | |
1609 | // BlockBasedTableTest::PrefetchTest | |
1610 | // | |
1611 | void AssertKeysInCache(BlockBasedTable* table_reader, | |
1612 | const std::vector<std::string>& keys_in_cache, | |
1613 | const std::vector<std::string>& keys_not_in_cache, | |
1614 | bool convert = false) { | |
1615 | if (convert) { | |
1616 | for (auto key : keys_in_cache) { | |
1617 | InternalKey ikey(key, kMaxSequenceNumber, kTypeValue); | |
1618 | ASSERT_TRUE(table_reader->TEST_KeyInCache(ReadOptions(), ikey.Encode())); | |
1619 | } | |
1620 | for (auto key : keys_not_in_cache) { | |
1621 | InternalKey ikey(key, kMaxSequenceNumber, kTypeValue); | |
1622 | ASSERT_TRUE(!table_reader->TEST_KeyInCache(ReadOptions(), ikey.Encode())); | |
1623 | } | |
1624 | } else { | |
1625 | for (auto key : keys_in_cache) { | |
1626 | ASSERT_TRUE(table_reader->TEST_KeyInCache(ReadOptions(), key)); | |
1627 | } | |
1628 | for (auto key : keys_not_in_cache) { | |
1629 | ASSERT_TRUE(!table_reader->TEST_KeyInCache(ReadOptions(), key)); | |
1630 | } | |
1631 | } | |
1632 | } | |
1633 | ||
1634 | void PrefetchRange(TableConstructor* c, Options* opt, | |
1635 | BlockBasedTableOptions* table_options, const char* key_begin, | |
1636 | const char* key_end, | |
1637 | const std::vector<std::string>& keys_in_cache, | |
1638 | const std::vector<std::string>& keys_not_in_cache, | |
1639 | const Status expected_status = Status::OK()) { | |
1640 | // reset the cache and reopen the table | |
1641 | table_options->block_cache = NewLRUCache(16 * 1024 * 1024, 4); | |
1642 | opt->table_factory.reset(NewBlockBasedTableFactory(*table_options)); | |
1643 | const ImmutableCFOptions ioptions2(*opt); | |
11fdf7f2 TL |
1644 | const MutableCFOptions moptions(*opt); |
1645 | ASSERT_OK(c->Reopen(ioptions2, moptions)); | |
7c673cae FG |
1646 | |
1647 | // prefetch | |
1648 | auto* table_reader = dynamic_cast<BlockBasedTable*>(c->GetTableReader()); | |
1649 | Status s; | |
494da23a TL |
1650 | std::unique_ptr<Slice> begin, end; |
1651 | std::unique_ptr<InternalKey> i_begin, i_end; | |
7c673cae FG |
1652 | if (key_begin != nullptr) { |
1653 | if (c->ConvertToInternalKey()) { | |
1654 | i_begin.reset(new InternalKey(key_begin, kMaxSequenceNumber, kTypeValue)); | |
1655 | begin.reset(new Slice(i_begin->Encode())); | |
1656 | } else { | |
1657 | begin.reset(new Slice(key_begin)); | |
1658 | } | |
1659 | } | |
1660 | if (key_end != nullptr) { | |
1661 | if (c->ConvertToInternalKey()) { | |
1662 | i_end.reset(new InternalKey(key_end, kMaxSequenceNumber, kTypeValue)); | |
1663 | end.reset(new Slice(i_end->Encode())); | |
1664 | } else { | |
1665 | end.reset(new Slice(key_end)); | |
1666 | } | |
1667 | } | |
1668 | s = table_reader->Prefetch(begin.get(), end.get()); | |
1669 | ||
1670 | ASSERT_TRUE(s.code() == expected_status.code()); | |
1671 | ||
1672 | // assert our expectation in cache warmup | |
1673 | AssertKeysInCache(table_reader, keys_in_cache, keys_not_in_cache, | |
1674 | c->ConvertToInternalKey()); | |
1675 | c->ResetTableReader(); | |
1676 | } | |
1677 | ||
11fdf7f2 | 1678 | TEST_P(BlockBasedTableTest, PrefetchTest) { |
7c673cae FG |
1679 | // The purpose of this test is to test the prefetching operation built into |
1680 | // BlockBasedTable. | |
1681 | Options opt; | |
494da23a | 1682 | std::unique_ptr<InternalKeyComparator> ikc; |
7c673cae FG |
1683 | ikc.reset(new test::PlainInternalKeyComparator(opt.comparator)); |
1684 | opt.compression = kNoCompression; | |
11fdf7f2 | 1685 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
7c673cae FG |
1686 | table_options.block_size = 1024; |
1687 | // big enough so we don't ever lose cached values. | |
1688 | table_options.block_cache = NewLRUCache(16 * 1024 * 1024, 4); | |
1689 | opt.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
1690 | ||
1691 | TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); | |
1692 | c.Add("k01", "hello"); | |
1693 | c.Add("k02", "hello2"); | |
1694 | c.Add("k03", std::string(10000, 'x')); | |
1695 | c.Add("k04", std::string(200000, 'x')); | |
1696 | c.Add("k05", std::string(300000, 'x')); | |
1697 | c.Add("k06", "hello3"); | |
1698 | c.Add("k07", std::string(100000, 'x')); | |
1699 | std::vector<std::string> keys; | |
1700 | stl_wrappers::KVMap kvmap; | |
1701 | const ImmutableCFOptions ioptions(opt); | |
11fdf7f2 TL |
1702 | const MutableCFOptions moptions(opt); |
1703 | c.Finish(opt, ioptions, moptions, table_options, *ikc, &keys, &kvmap); | |
7c673cae FG |
1704 | c.ResetTableReader(); |
1705 | ||
1706 | // We get the following data spread : | |
1707 | // | |
1708 | // Data block Index | |
1709 | // ======================== | |
1710 | // [ k01 k02 k03 ] k03 | |
1711 | // [ k04 ] k04 | |
1712 | // [ k05 ] k05 | |
1713 | // [ k06 k07 ] k07 | |
1714 | ||
1715 | ||
1716 | // Simple | |
1717 | PrefetchRange(&c, &opt, &table_options, | |
1718 | /*key_range=*/"k01", "k05", | |
1719 | /*keys_in_cache=*/{"k01", "k02", "k03", "k04", "k05"}, | |
1720 | /*keys_not_in_cache=*/{"k06", "k07"}); | |
1721 | PrefetchRange(&c, &opt, &table_options, "k01", "k01", {"k01", "k02", "k03"}, | |
1722 | {"k04", "k05", "k06", "k07"}); | |
1723 | // odd | |
1724 | PrefetchRange(&c, &opt, &table_options, "a", "z", | |
1725 | {"k01", "k02", "k03", "k04", "k05", "k06", "k07"}, {}); | |
1726 | PrefetchRange(&c, &opt, &table_options, "k00", "k00", {"k01", "k02", "k03"}, | |
1727 | {"k04", "k05", "k06", "k07"}); | |
1728 | // Edge cases | |
1729 | PrefetchRange(&c, &opt, &table_options, "k00", "k06", | |
1730 | {"k01", "k02", "k03", "k04", "k05", "k06", "k07"}, {}); | |
1731 | PrefetchRange(&c, &opt, &table_options, "k00", "zzz", | |
1732 | {"k01", "k02", "k03", "k04", "k05", "k06", "k07"}, {}); | |
1733 | // null keys | |
1734 | PrefetchRange(&c, &opt, &table_options, nullptr, nullptr, | |
1735 | {"k01", "k02", "k03", "k04", "k05", "k06", "k07"}, {}); | |
1736 | PrefetchRange(&c, &opt, &table_options, "k04", nullptr, | |
1737 | {"k04", "k05", "k06", "k07"}, {"k01", "k02", "k03"}); | |
1738 | PrefetchRange(&c, &opt, &table_options, nullptr, "k05", | |
1739 | {"k01", "k02", "k03", "k04", "k05"}, {"k06", "k07"}); | |
1740 | // invalid | |
1741 | PrefetchRange(&c, &opt, &table_options, "k06", "k00", {}, {}, | |
1742 | Status::InvalidArgument(Slice("k06 "), Slice("k07"))); | |
1743 | c.ResetTableReader(); | |
1744 | } | |
1745 | ||
11fdf7f2 TL |
1746 | TEST_P(BlockBasedTableTest, TotalOrderSeekOnHashIndex) { |
1747 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); | |
f67539c2 | 1748 | for (int i = 0; i <= 5; ++i) { |
7c673cae FG |
1749 | Options options; |
1750 | // Make each key/value an individual block | |
1751 | table_options.block_size = 64; | |
1752 | switch (i) { | |
1753 | case 0: | |
1754 | // Binary search index | |
1755 | table_options.index_type = BlockBasedTableOptions::kBinarySearch; | |
1756 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
1757 | break; | |
1758 | case 1: | |
1759 | // Hash search index | |
1760 | table_options.index_type = BlockBasedTableOptions::kHashSearch; | |
1761 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
1762 | options.prefix_extractor.reset(NewFixedPrefixTransform(4)); | |
1763 | break; | |
1764 | case 2: | |
1765 | // Hash search index with hash_index_allow_collision | |
1766 | table_options.index_type = BlockBasedTableOptions::kHashSearch; | |
1767 | table_options.hash_index_allow_collision = true; | |
1768 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
1769 | options.prefix_extractor.reset(NewFixedPrefixTransform(4)); | |
1770 | break; | |
1771 | case 3: | |
1772 | // Hash search index with filter policy | |
1773 | table_options.index_type = BlockBasedTableOptions::kHashSearch; | |
1774 | table_options.filter_policy.reset(NewBloomFilterPolicy(10)); | |
1775 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
1776 | options.prefix_extractor.reset(NewFixedPrefixTransform(4)); | |
1777 | break; | |
1778 | case 4: | |
f67539c2 | 1779 | // Two-level index |
7c673cae FG |
1780 | table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch; |
1781 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
1782 | break; | |
f67539c2 TL |
1783 | case 5: |
1784 | // Binary search with first key | |
1785 | table_options.index_type = | |
1786 | BlockBasedTableOptions::kBinarySearchWithFirstKey; | |
1787 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
1788 | break; | |
7c673cae FG |
1789 | } |
1790 | ||
1791 | TableConstructor c(BytewiseComparator(), | |
1792 | true /* convert_to_internal_key_ */); | |
1793 | c.Add("aaaa1", std::string('a', 56)); | |
1794 | c.Add("bbaa1", std::string('a', 56)); | |
1795 | c.Add("cccc1", std::string('a', 56)); | |
1796 | c.Add("bbbb1", std::string('a', 56)); | |
1797 | c.Add("baaa1", std::string('a', 56)); | |
1798 | c.Add("abbb1", std::string('a', 56)); | |
1799 | c.Add("cccc2", std::string('a', 56)); | |
1800 | std::vector<std::string> keys; | |
1801 | stl_wrappers::KVMap kvmap; | |
1802 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 TL |
1803 | const MutableCFOptions moptions(options); |
1804 | c.Finish(options, ioptions, moptions, table_options, | |
7c673cae FG |
1805 | GetPlainInternalComparator(options.comparator), &keys, &kvmap); |
1806 | auto props = c.GetTableReader()->GetTableProperties(); | |
1807 | ASSERT_EQ(7u, props->num_data_blocks); | |
1808 | auto* reader = c.GetTableReader(); | |
1809 | ReadOptions ro; | |
1810 | ro.total_order_seek = true; | |
f67539c2 TL |
1811 | std::unique_ptr<InternalIterator> iter(reader->NewIterator( |
1812 | ro, moptions.prefix_extractor.get(), /*arena=*/nullptr, | |
1813 | /*skip_filters=*/false, TableReaderCaller::kUncategorized)); | |
7c673cae FG |
1814 | |
1815 | iter->Seek(InternalKey("b", 0, kTypeValue).Encode()); | |
1816 | ASSERT_OK(iter->status()); | |
1817 | ASSERT_TRUE(iter->Valid()); | |
1818 | ASSERT_EQ("baaa1", ExtractUserKey(iter->key()).ToString()); | |
1819 | iter->Next(); | |
1820 | ASSERT_OK(iter->status()); | |
1821 | ASSERT_TRUE(iter->Valid()); | |
1822 | ASSERT_EQ("bbaa1", ExtractUserKey(iter->key()).ToString()); | |
1823 | ||
1824 | iter->Seek(InternalKey("bb", 0, kTypeValue).Encode()); | |
1825 | ASSERT_OK(iter->status()); | |
1826 | ASSERT_TRUE(iter->Valid()); | |
1827 | ASSERT_EQ("bbaa1", ExtractUserKey(iter->key()).ToString()); | |
1828 | iter->Next(); | |
1829 | ASSERT_OK(iter->status()); | |
1830 | ASSERT_TRUE(iter->Valid()); | |
1831 | ASSERT_EQ("bbbb1", ExtractUserKey(iter->key()).ToString()); | |
1832 | ||
1833 | iter->Seek(InternalKey("bbb", 0, kTypeValue).Encode()); | |
1834 | ASSERT_OK(iter->status()); | |
1835 | ASSERT_TRUE(iter->Valid()); | |
1836 | ASSERT_EQ("bbbb1", ExtractUserKey(iter->key()).ToString()); | |
1837 | iter->Next(); | |
1838 | ASSERT_OK(iter->status()); | |
1839 | ASSERT_TRUE(iter->Valid()); | |
1840 | ASSERT_EQ("cccc1", ExtractUserKey(iter->key()).ToString()); | |
1841 | } | |
1842 | } | |
1843 | ||
11fdf7f2 TL |
1844 | TEST_P(BlockBasedTableTest, NoopTransformSeek) { |
1845 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); | |
7c673cae FG |
1846 | table_options.filter_policy.reset(NewBloomFilterPolicy(10)); |
1847 | ||
1848 | Options options; | |
1849 | options.comparator = BytewiseComparator(); | |
1850 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
1851 | options.prefix_extractor.reset(NewNoopTransform()); | |
1852 | ||
1853 | TableConstructor c(options.comparator); | |
1854 | // To tickle the PrefixMayMatch bug it is important that the | |
1855 | // user-key is a single byte so that the index key exactly matches | |
1856 | // the user-key. | |
1857 | InternalKey key("a", 1, kTypeValue); | |
1858 | c.Add(key.Encode().ToString(), "b"); | |
1859 | std::vector<std::string> keys; | |
1860 | stl_wrappers::KVMap kvmap; | |
1861 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 | 1862 | const MutableCFOptions moptions(options); |
7c673cae | 1863 | const InternalKeyComparator internal_comparator(options.comparator); |
11fdf7f2 TL |
1864 | c.Finish(options, ioptions, moptions, table_options, internal_comparator, |
1865 | &keys, &kvmap); | |
7c673cae FG |
1866 | |
1867 | auto* reader = c.GetTableReader(); | |
1868 | for (int i = 0; i < 2; ++i) { | |
1869 | ReadOptions ro; | |
1870 | ro.total_order_seek = (i == 0); | |
f67539c2 TL |
1871 | std::unique_ptr<InternalIterator> iter(reader->NewIterator( |
1872 | ro, moptions.prefix_extractor.get(), /*arena=*/nullptr, | |
1873 | /*skip_filters=*/false, TableReaderCaller::kUncategorized)); | |
7c673cae FG |
1874 | |
1875 | iter->Seek(key.Encode()); | |
1876 | ASSERT_OK(iter->status()); | |
1877 | ASSERT_TRUE(iter->Valid()); | |
1878 | ASSERT_EQ("a", ExtractUserKey(iter->key()).ToString()); | |
1879 | } | |
1880 | } | |
1881 | ||
11fdf7f2 | 1882 | TEST_P(BlockBasedTableTest, SkipPrefixBloomFilter) { |
7c673cae FG |
1883 | // if DB is opened with a prefix extractor of a different name, |
1884 | // prefix bloom is skipped when read the file | |
11fdf7f2 | 1885 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
7c673cae FG |
1886 | table_options.filter_policy.reset(NewBloomFilterPolicy(2)); |
1887 | table_options.whole_key_filtering = false; | |
1888 | ||
1889 | Options options; | |
1890 | options.comparator = BytewiseComparator(); | |
1891 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
1892 | options.prefix_extractor.reset(NewFixedPrefixTransform(1)); | |
1893 | ||
1894 | TableConstructor c(options.comparator); | |
1895 | InternalKey key("abcdefghijk", 1, kTypeValue); | |
1896 | c.Add(key.Encode().ToString(), "test"); | |
1897 | std::vector<std::string> keys; | |
1898 | stl_wrappers::KVMap kvmap; | |
1899 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 | 1900 | const MutableCFOptions moptions(options); |
7c673cae | 1901 | const InternalKeyComparator internal_comparator(options.comparator); |
11fdf7f2 TL |
1902 | c.Finish(options, ioptions, moptions, table_options, internal_comparator, |
1903 | &keys, &kvmap); | |
1904 | // TODO(Zhongyi): update test to use MutableCFOptions | |
7c673cae FG |
1905 | options.prefix_extractor.reset(NewFixedPrefixTransform(9)); |
1906 | const ImmutableCFOptions new_ioptions(options); | |
11fdf7f2 | 1907 | const MutableCFOptions new_moptions(options); |
20effc67 | 1908 | ASSERT_OK(c.Reopen(new_ioptions, new_moptions)); |
7c673cae | 1909 | auto reader = c.GetTableReader(); |
20effc67 | 1910 | ReadOptions read_options; |
f67539c2 | 1911 | std::unique_ptr<InternalIterator> db_iter(reader->NewIterator( |
20effc67 | 1912 | read_options, new_moptions.prefix_extractor.get(), /*arena=*/nullptr, |
f67539c2 | 1913 | /*skip_filters=*/false, TableReaderCaller::kUncategorized)); |
7c673cae FG |
1914 | |
1915 | // Test point lookup | |
1916 | // only one kv | |
1917 | for (auto& kv : kvmap) { | |
1918 | db_iter->Seek(kv.first); | |
1919 | ASSERT_TRUE(db_iter->Valid()); | |
1920 | ASSERT_OK(db_iter->status()); | |
1921 | ASSERT_EQ(db_iter->key(), kv.first); | |
1922 | ASSERT_EQ(db_iter->value(), kv.second); | |
1923 | } | |
1924 | } | |
1925 | ||
7c673cae | 1926 | void AddInternalKey(TableConstructor* c, const std::string& prefix, |
f67539c2 | 1927 | std::string value = "v", int /*suffix_len*/ = 800) { |
7c673cae | 1928 | static Random rnd(1023); |
20effc67 | 1929 | InternalKey k(prefix + rnd.RandomString(800), 0, kTypeValue); |
f67539c2 | 1930 | c->Add(k.Encode().ToString(), value); |
7c673cae FG |
1931 | } |
1932 | ||
1933 | void TableTest::IndexTest(BlockBasedTableOptions table_options) { | |
1934 | TableConstructor c(BytewiseComparator()); | |
1935 | ||
1936 | // keys with prefix length 3, make sure the key/value is big enough to fill | |
1937 | // one block | |
1938 | AddInternalKey(&c, "0015"); | |
1939 | AddInternalKey(&c, "0035"); | |
1940 | ||
1941 | AddInternalKey(&c, "0054"); | |
1942 | AddInternalKey(&c, "0055"); | |
1943 | ||
1944 | AddInternalKey(&c, "0056"); | |
1945 | AddInternalKey(&c, "0057"); | |
1946 | ||
1947 | AddInternalKey(&c, "0058"); | |
1948 | AddInternalKey(&c, "0075"); | |
1949 | ||
1950 | AddInternalKey(&c, "0076"); | |
1951 | AddInternalKey(&c, "0095"); | |
1952 | ||
1953 | std::vector<std::string> keys; | |
1954 | stl_wrappers::KVMap kvmap; | |
1955 | Options options; | |
1956 | options.prefix_extractor.reset(NewFixedPrefixTransform(3)); | |
1957 | table_options.block_size = 1700; | |
1958 | table_options.block_cache = NewLRUCache(1024, 4); | |
1959 | options.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
1960 | ||
1961 | std::unique_ptr<InternalKeyComparator> comparator( | |
1962 | new InternalKeyComparator(BytewiseComparator())); | |
1963 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 TL |
1964 | const MutableCFOptions moptions(options); |
1965 | c.Finish(options, ioptions, moptions, table_options, *comparator, &keys, | |
1966 | &kvmap); | |
7c673cae FG |
1967 | auto reader = c.GetTableReader(); |
1968 | ||
1969 | auto props = reader->GetTableProperties(); | |
1970 | ASSERT_EQ(5u, props->num_data_blocks); | |
1971 | ||
11fdf7f2 | 1972 | // TODO(Zhongyi): update test to use MutableCFOptions |
20effc67 | 1973 | ReadOptions read_options; |
f67539c2 | 1974 | std::unique_ptr<InternalIterator> index_iter(reader->NewIterator( |
20effc67 | 1975 | read_options, moptions.prefix_extractor.get(), /*arena=*/nullptr, |
f67539c2 | 1976 | /*skip_filters=*/false, TableReaderCaller::kUncategorized)); |
7c673cae FG |
1977 | |
1978 | // -- Find keys do not exist, but have common prefix. | |
1979 | std::vector<std::string> prefixes = {"001", "003", "005", "007", "009"}; | |
20effc67 TL |
1980 | std::vector<std::string> lower_bound = { |
1981 | keys[0], keys[1], keys[2], keys[7], keys[9], | |
1982 | }; | |
7c673cae FG |
1983 | |
1984 | // find the lower bound of the prefix | |
1985 | for (size_t i = 0; i < prefixes.size(); ++i) { | |
1986 | index_iter->Seek(InternalKey(prefixes[i], 0, kTypeValue).Encode()); | |
1987 | ASSERT_OK(index_iter->status()); | |
1988 | ASSERT_TRUE(index_iter->Valid()); | |
1989 | ||
1990 | // seek the first element in the block | |
1991 | ASSERT_EQ(lower_bound[i], index_iter->key().ToString()); | |
1992 | ASSERT_EQ("v", index_iter->value().ToString()); | |
1993 | } | |
1994 | ||
1995 | // find the upper bound of prefixes | |
1996 | std::vector<std::string> upper_bound = {keys[1], keys[2], keys[7], keys[9], }; | |
1997 | ||
1998 | // find existing keys | |
1999 | for (const auto& item : kvmap) { | |
2000 | auto ukey = ExtractUserKey(item.first).ToString(); | |
2001 | index_iter->Seek(ukey); | |
2002 | ||
2003 | // ASSERT_OK(regular_iter->status()); | |
2004 | ASSERT_OK(index_iter->status()); | |
2005 | ||
2006 | // ASSERT_TRUE(regular_iter->Valid()); | |
2007 | ASSERT_TRUE(index_iter->Valid()); | |
2008 | ||
2009 | ASSERT_EQ(item.first, index_iter->key().ToString()); | |
2010 | ASSERT_EQ(item.second, index_iter->value().ToString()); | |
2011 | } | |
2012 | ||
2013 | for (size_t i = 0; i < prefixes.size(); ++i) { | |
2014 | // the key is greater than any existing keys. | |
2015 | auto key = prefixes[i] + "9"; | |
2016 | index_iter->Seek(InternalKey(key, 0, kTypeValue).Encode()); | |
2017 | ||
f67539c2 TL |
2018 | ASSERT_TRUE(index_iter->status().ok() || index_iter->status().IsNotFound()); |
2019 | ASSERT_TRUE(!index_iter->status().IsNotFound() || !index_iter->Valid()); | |
7c673cae FG |
2020 | if (i == prefixes.size() - 1) { |
2021 | // last key | |
2022 | ASSERT_TRUE(!index_iter->Valid()); | |
2023 | } else { | |
2024 | ASSERT_TRUE(index_iter->Valid()); | |
2025 | // seek the first element in the block | |
2026 | ASSERT_EQ(upper_bound[i], index_iter->key().ToString()); | |
2027 | ASSERT_EQ("v", index_iter->value().ToString()); | |
2028 | } | |
2029 | } | |
2030 | ||
2031 | // find keys with prefix that don't match any of the existing prefixes. | |
2032 | std::vector<std::string> non_exist_prefixes = {"002", "004", "006", "008"}; | |
2033 | for (const auto& prefix : non_exist_prefixes) { | |
2034 | index_iter->Seek(InternalKey(prefix, 0, kTypeValue).Encode()); | |
2035 | // regular_iter->Seek(prefix); | |
2036 | ||
2037 | ASSERT_OK(index_iter->status()); | |
2038 | // Seek to non-existing prefixes should yield either invalid, or a | |
2039 | // key with prefix greater than the target. | |
2040 | if (index_iter->Valid()) { | |
2041 | Slice ukey = ExtractUserKey(index_iter->key()); | |
2042 | Slice ukey_prefix = options.prefix_extractor->Transform(ukey); | |
2043 | ASSERT_TRUE(BytewiseComparator()->Compare(prefix, ukey_prefix) < 0); | |
2044 | } | |
2045 | } | |
f67539c2 TL |
2046 | for (const auto& prefix : non_exist_prefixes) { |
2047 | index_iter->SeekForPrev(InternalKey(prefix, 0, kTypeValue).Encode()); | |
2048 | // regular_iter->Seek(prefix); | |
2049 | ||
2050 | ASSERT_OK(index_iter->status()); | |
2051 | // Seek to non-existing prefixes should yield either invalid, or a | |
2052 | // key with prefix greater than the target. | |
2053 | if (index_iter->Valid()) { | |
2054 | Slice ukey = ExtractUserKey(index_iter->key()); | |
2055 | Slice ukey_prefix = options.prefix_extractor->Transform(ukey); | |
2056 | ASSERT_TRUE(BytewiseComparator()->Compare(prefix, ukey_prefix) > 0); | |
2057 | } | |
2058 | } | |
20effc67 TL |
2059 | |
2060 | { | |
2061 | // Test reseek case. It should impact partitioned index more. | |
2062 | ReadOptions ro; | |
2063 | ro.total_order_seek = true; | |
2064 | std::unique_ptr<InternalIterator> index_iter2(reader->NewIterator( | |
2065 | ro, moptions.prefix_extractor.get(), /*arena=*/nullptr, | |
2066 | /*skip_filters=*/false, TableReaderCaller::kUncategorized)); | |
2067 | ||
2068 | // Things to cover in partitioned index: | |
2069 | // 1. Both of Seek() and SeekToLast() has optimization to prevent | |
2070 | // rereek leaf index block if it remains to the same one, and | |
2071 | // they reuse the same variable. | |
2072 | // 2. When Next() or Prev() is called, the block moves, so the | |
2073 | // optimization should kick in only with the current one. | |
2074 | index_iter2->Seek(InternalKey("0055", 0, kTypeValue).Encode()); | |
2075 | ASSERT_TRUE(index_iter2->Valid()); | |
2076 | ASSERT_EQ("0055", index_iter2->key().ToString().substr(0, 4)); | |
2077 | ||
2078 | index_iter2->SeekToLast(); | |
2079 | ASSERT_TRUE(index_iter2->Valid()); | |
2080 | ASSERT_EQ("0095", index_iter2->key().ToString().substr(0, 4)); | |
2081 | ||
2082 | index_iter2->Seek(InternalKey("0055", 0, kTypeValue).Encode()); | |
2083 | ASSERT_TRUE(index_iter2->Valid()); | |
2084 | ASSERT_EQ("0055", index_iter2->key().ToString().substr(0, 4)); | |
2085 | ||
2086 | index_iter2->SeekToLast(); | |
2087 | ASSERT_TRUE(index_iter2->Valid()); | |
2088 | ASSERT_EQ("0095", index_iter2->key().ToString().substr(0, 4)); | |
2089 | index_iter2->Prev(); | |
2090 | ASSERT_TRUE(index_iter2->Valid()); | |
2091 | index_iter2->Prev(); | |
2092 | ASSERT_TRUE(index_iter2->Valid()); | |
2093 | ASSERT_EQ("0075", index_iter2->key().ToString().substr(0, 4)); | |
2094 | ||
2095 | index_iter2->Seek(InternalKey("0095", 0, kTypeValue).Encode()); | |
2096 | ASSERT_TRUE(index_iter2->Valid()); | |
2097 | ASSERT_EQ("0095", index_iter2->key().ToString().substr(0, 4)); | |
2098 | index_iter2->Prev(); | |
2099 | ASSERT_TRUE(index_iter2->Valid()); | |
2100 | index_iter2->Prev(); | |
2101 | ASSERT_TRUE(index_iter2->Valid()); | |
2102 | ASSERT_EQ("0075", index_iter2->key().ToString().substr(0, 4)); | |
2103 | ||
2104 | index_iter2->SeekToLast(); | |
2105 | ASSERT_TRUE(index_iter2->Valid()); | |
2106 | ASSERT_EQ("0095", index_iter2->key().ToString().substr(0, 4)); | |
2107 | ||
2108 | index_iter2->Seek(InternalKey("0095", 0, kTypeValue).Encode()); | |
2109 | ASSERT_TRUE(index_iter2->Valid()); | |
2110 | ASSERT_EQ("0095", index_iter2->key().ToString().substr(0, 4)); | |
2111 | ||
2112 | index_iter2->Prev(); | |
2113 | ASSERT_TRUE(index_iter2->Valid()); | |
2114 | index_iter2->Prev(); | |
2115 | ASSERT_TRUE(index_iter2->Valid()); | |
2116 | ASSERT_EQ("0075", index_iter2->key().ToString().substr(0, 4)); | |
2117 | ||
2118 | index_iter2->Seek(InternalKey("0075", 0, kTypeValue).Encode()); | |
2119 | ASSERT_TRUE(index_iter2->Valid()); | |
2120 | ASSERT_EQ("0075", index_iter2->key().ToString().substr(0, 4)); | |
2121 | ||
2122 | index_iter2->Next(); | |
2123 | ASSERT_TRUE(index_iter2->Valid()); | |
2124 | index_iter2->Next(); | |
2125 | ASSERT_TRUE(index_iter2->Valid()); | |
2126 | ASSERT_EQ("0095", index_iter2->key().ToString().substr(0, 4)); | |
2127 | ||
2128 | index_iter2->SeekToLast(); | |
2129 | ASSERT_TRUE(index_iter2->Valid()); | |
2130 | ASSERT_EQ("0095", index_iter2->key().ToString().substr(0, 4)); | |
2131 | } | |
2132 | ||
7c673cae FG |
2133 | c.ResetTableReader(); |
2134 | } | |
2135 | ||
11fdf7f2 TL |
2136 | TEST_P(BlockBasedTableTest, BinaryIndexTest) { |
2137 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); | |
7c673cae FG |
2138 | table_options.index_type = BlockBasedTableOptions::kBinarySearch; |
2139 | IndexTest(table_options); | |
2140 | } | |
2141 | ||
11fdf7f2 TL |
2142 | TEST_P(BlockBasedTableTest, HashIndexTest) { |
2143 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); | |
7c673cae FG |
2144 | table_options.index_type = BlockBasedTableOptions::kHashSearch; |
2145 | IndexTest(table_options); | |
2146 | } | |
2147 | ||
11fdf7f2 | 2148 | TEST_P(BlockBasedTableTest, PartitionIndexTest) { |
7c673cae FG |
2149 | const int max_index_keys = 5; |
2150 | const int est_max_index_key_value_size = 32; | |
2151 | const int est_max_index_size = max_index_keys * est_max_index_key_value_size; | |
2152 | for (int i = 1; i <= est_max_index_size + 1; i++) { | |
11fdf7f2 | 2153 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
7c673cae FG |
2154 | table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch; |
2155 | table_options.metadata_block_size = i; | |
2156 | IndexTest(table_options); | |
2157 | } | |
2158 | } | |
2159 | ||
f67539c2 TL |
2160 | TEST_P(BlockBasedTableTest, IndexSeekOptimizationIncomplete) { |
2161 | std::unique_ptr<InternalKeyComparator> comparator( | |
2162 | new InternalKeyComparator(BytewiseComparator())); | |
2163 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); | |
2164 | Options options; | |
2165 | options.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
2166 | const ImmutableCFOptions ioptions(options); | |
2167 | const MutableCFOptions moptions(options); | |
2168 | ||
2169 | TableConstructor c(BytewiseComparator()); | |
2170 | AddInternalKey(&c, "pika"); | |
2171 | ||
2172 | std::vector<std::string> keys; | |
2173 | stl_wrappers::KVMap kvmap; | |
2174 | c.Finish(options, ioptions, moptions, table_options, *comparator, &keys, | |
2175 | &kvmap); | |
2176 | ASSERT_EQ(1, keys.size()); | |
2177 | ||
2178 | auto reader = c.GetTableReader(); | |
2179 | ReadOptions ropt; | |
2180 | ropt.read_tier = ReadTier::kBlockCacheTier; | |
2181 | std::unique_ptr<InternalIterator> iter(reader->NewIterator( | |
2182 | ropt, /*prefix_extractor=*/nullptr, /*arena=*/nullptr, | |
2183 | /*skip_filters=*/false, TableReaderCaller::kUncategorized)); | |
2184 | ||
2185 | auto ikey = [](Slice user_key) { | |
2186 | return InternalKey(user_key, 0, kTypeValue).Encode().ToString(); | |
2187 | }; | |
2188 | ||
2189 | iter->Seek(ikey("pika")); | |
2190 | ASSERT_FALSE(iter->Valid()); | |
2191 | ASSERT_TRUE(iter->status().IsIncomplete()); | |
2192 | ||
2193 | // This used to crash at some point. | |
2194 | iter->Seek(ikey("pika")); | |
2195 | ASSERT_FALSE(iter->Valid()); | |
2196 | ASSERT_TRUE(iter->status().IsIncomplete()); | |
2197 | } | |
2198 | ||
2199 | TEST_P(BlockBasedTableTest, BinaryIndexWithFirstKey1) { | |
2200 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); | |
2201 | table_options.index_type = BlockBasedTableOptions::kBinarySearchWithFirstKey; | |
2202 | IndexTest(table_options); | |
2203 | } | |
2204 | ||
2205 | class CustomFlushBlockPolicy : public FlushBlockPolicyFactory, | |
2206 | public FlushBlockPolicy { | |
2207 | public: | |
2208 | explicit CustomFlushBlockPolicy(std::vector<int> keys_per_block) | |
2209 | : keys_per_block_(keys_per_block) {} | |
2210 | ||
20effc67 TL |
2211 | const char* Name() const override { return "CustomFlushBlockPolicy"; } |
2212 | ||
f67539c2 TL |
2213 | FlushBlockPolicy* NewFlushBlockPolicy(const BlockBasedTableOptions&, |
2214 | const BlockBuilder&) const override { | |
2215 | return new CustomFlushBlockPolicy(keys_per_block_); | |
2216 | } | |
2217 | ||
2218 | bool Update(const Slice&, const Slice&) override { | |
2219 | if (keys_in_current_block_ >= keys_per_block_.at(current_block_idx_)) { | |
2220 | ++current_block_idx_; | |
2221 | keys_in_current_block_ = 1; | |
2222 | return true; | |
2223 | } | |
2224 | ||
2225 | ++keys_in_current_block_; | |
2226 | return false; | |
2227 | } | |
2228 | ||
2229 | std::vector<int> keys_per_block_; | |
2230 | ||
2231 | int current_block_idx_ = 0; | |
2232 | int keys_in_current_block_ = 0; | |
2233 | }; | |
2234 | ||
2235 | TEST_P(BlockBasedTableTest, BinaryIndexWithFirstKey2) { | |
2236 | for (int use_first_key = 0; use_first_key < 2; ++use_first_key) { | |
2237 | SCOPED_TRACE("use_first_key = " + std::to_string(use_first_key)); | |
2238 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); | |
2239 | table_options.index_type = | |
2240 | use_first_key ? BlockBasedTableOptions::kBinarySearchWithFirstKey | |
2241 | : BlockBasedTableOptions::kBinarySearch; | |
2242 | table_options.block_cache = NewLRUCache(10000); // fits all blocks | |
2243 | table_options.index_shortening = | |
2244 | BlockBasedTableOptions::IndexShorteningMode::kNoShortening; | |
2245 | table_options.flush_block_policy_factory = | |
2246 | std::make_shared<CustomFlushBlockPolicy>(std::vector<int>{2, 1, 3, 2}); | |
2247 | Options options; | |
2248 | options.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
2249 | options.statistics = CreateDBStatistics(); | |
2250 | Statistics* stats = options.statistics.get(); | |
2251 | std::unique_ptr<InternalKeyComparator> comparator( | |
2252 | new InternalKeyComparator(BytewiseComparator())); | |
2253 | const ImmutableCFOptions ioptions(options); | |
2254 | const MutableCFOptions moptions(options); | |
2255 | ||
2256 | TableConstructor c(BytewiseComparator()); | |
2257 | ||
2258 | // Block 0. | |
2259 | AddInternalKey(&c, "aaaa", "v0"); | |
2260 | AddInternalKey(&c, "aaac", "v1"); | |
2261 | ||
2262 | // Block 1. | |
2263 | AddInternalKey(&c, "aaca", "v2"); | |
2264 | ||
2265 | // Block 2. | |
2266 | AddInternalKey(&c, "caaa", "v3"); | |
2267 | AddInternalKey(&c, "caac", "v4"); | |
2268 | AddInternalKey(&c, "caae", "v5"); | |
2269 | ||
2270 | // Block 3. | |
2271 | AddInternalKey(&c, "ccaa", "v6"); | |
2272 | AddInternalKey(&c, "ccac", "v7"); | |
2273 | ||
2274 | // Write the file. | |
2275 | std::vector<std::string> keys; | |
2276 | stl_wrappers::KVMap kvmap; | |
2277 | c.Finish(options, ioptions, moptions, table_options, *comparator, &keys, | |
2278 | &kvmap); | |
2279 | ASSERT_EQ(8, keys.size()); | |
2280 | ||
2281 | auto reader = c.GetTableReader(); | |
2282 | auto props = reader->GetTableProperties(); | |
2283 | ASSERT_EQ(4u, props->num_data_blocks); | |
20effc67 | 2284 | ReadOptions read_options; |
f67539c2 | 2285 | std::unique_ptr<InternalIterator> iter(reader->NewIterator( |
20effc67 TL |
2286 | read_options, /*prefix_extractor=*/nullptr, /*arena=*/nullptr, |
2287 | /*skip_filters=*/false, TableReaderCaller::kUncategorized, | |
2288 | /*compaction_readahead_size=*/0, /*allow_unprepared_value=*/true)); | |
f67539c2 TL |
2289 | |
2290 | // Shouldn't have read data blocks before iterator is seeked. | |
2291 | EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); | |
2292 | EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2293 | ||
2294 | auto ikey = [](Slice user_key) { | |
2295 | return InternalKey(user_key, 0, kTypeValue).Encode().ToString(); | |
2296 | }; | |
2297 | ||
2298 | // Seek to a key between blocks. If index contains first key, we shouldn't | |
2299 | // read any data blocks until value is requested. | |
2300 | iter->Seek(ikey("aaba")); | |
2301 | ASSERT_TRUE(iter->Valid()); | |
2302 | EXPECT_EQ(keys[2], iter->key().ToString()); | |
2303 | EXPECT_EQ(use_first_key ? 0 : 1, | |
2304 | stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); | |
20effc67 | 2305 | ASSERT_TRUE(iter->PrepareValue()); |
f67539c2 TL |
2306 | EXPECT_EQ("v2", iter->value().ToString()); |
2307 | EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); | |
2308 | EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2309 | ||
2310 | // Seek to the middle of a block. The block should be read right away. | |
2311 | iter->Seek(ikey("caab")); | |
2312 | ASSERT_TRUE(iter->Valid()); | |
2313 | EXPECT_EQ(keys[4], iter->key().ToString()); | |
2314 | EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); | |
2315 | EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
20effc67 | 2316 | ASSERT_TRUE(iter->PrepareValue()); |
f67539c2 TL |
2317 | EXPECT_EQ("v4", iter->value().ToString()); |
2318 | EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2319 | ||
2320 | // Seek to just before the same block and don't access value. | |
2321 | // The iterator should keep pinning the block contents. | |
2322 | iter->Seek(ikey("baaa")); | |
2323 | ASSERT_TRUE(iter->Valid()); | |
2324 | EXPECT_EQ(keys[3], iter->key().ToString()); | |
2325 | EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2326 | ||
2327 | // Seek to the same block again to check that the block is still pinned. | |
2328 | iter->Seek(ikey("caae")); | |
2329 | ASSERT_TRUE(iter->Valid()); | |
2330 | EXPECT_EQ(keys[5], iter->key().ToString()); | |
2331 | EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
20effc67 | 2332 | ASSERT_TRUE(iter->PrepareValue()); |
f67539c2 TL |
2333 | EXPECT_EQ("v5", iter->value().ToString()); |
2334 | EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); | |
2335 | EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2336 | ||
2337 | // Step forward and fall through to the next block. Don't access value. | |
2338 | iter->Next(); | |
2339 | ASSERT_TRUE(iter->Valid()); | |
2340 | EXPECT_EQ(keys[6], iter->key().ToString()); | |
2341 | EXPECT_EQ(use_first_key ? 2 : 3, | |
2342 | stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); | |
2343 | EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2344 | ||
2345 | // Step forward again. Block should be read. | |
2346 | iter->Next(); | |
2347 | ASSERT_TRUE(iter->Valid()); | |
2348 | EXPECT_EQ(keys[7], iter->key().ToString()); | |
2349 | EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); | |
20effc67 | 2350 | ASSERT_TRUE(iter->PrepareValue()); |
f67539c2 TL |
2351 | EXPECT_EQ("v7", iter->value().ToString()); |
2352 | EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2353 | ||
2354 | // Step forward and reach the end. | |
2355 | iter->Next(); | |
2356 | EXPECT_FALSE(iter->Valid()); | |
2357 | EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); | |
2358 | EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2359 | ||
2360 | // Seek to a single-key block and step forward without accessing value. | |
2361 | iter->Seek(ikey("aaca")); | |
2362 | ASSERT_TRUE(iter->Valid()); | |
2363 | EXPECT_EQ(keys[2], iter->key().ToString()); | |
2364 | EXPECT_EQ(use_first_key ? 0 : 1, | |
2365 | stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2366 | ||
2367 | iter->Next(); | |
2368 | ASSERT_TRUE(iter->Valid()); | |
2369 | EXPECT_EQ(keys[3], iter->key().ToString()); | |
2370 | EXPECT_EQ(use_first_key ? 1 : 2, | |
2371 | stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
20effc67 | 2372 | ASSERT_TRUE(iter->PrepareValue()); |
f67539c2 TL |
2373 | EXPECT_EQ("v3", iter->value().ToString()); |
2374 | EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2375 | EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); | |
2376 | ||
2377 | // Seek between blocks and step back without accessing value. | |
2378 | iter->Seek(ikey("aaca")); | |
2379 | ASSERT_TRUE(iter->Valid()); | |
2380 | EXPECT_EQ(keys[2], iter->key().ToString()); | |
2381 | EXPECT_EQ(use_first_key ? 2 : 3, | |
2382 | stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2383 | EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); | |
2384 | ||
2385 | iter->Prev(); | |
2386 | ASSERT_TRUE(iter->Valid()); | |
2387 | EXPECT_EQ(keys[1], iter->key().ToString()); | |
2388 | EXPECT_EQ(use_first_key ? 2 : 3, | |
2389 | stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2390 | // All blocks are in cache now, there'll be no more misses ever. | |
2391 | EXPECT_EQ(4, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); | |
20effc67 | 2392 | ASSERT_TRUE(iter->PrepareValue()); |
f67539c2 TL |
2393 | EXPECT_EQ("v1", iter->value().ToString()); |
2394 | ||
2395 | // Next into the next block again. | |
2396 | iter->Next(); | |
2397 | ASSERT_TRUE(iter->Valid()); | |
2398 | EXPECT_EQ(keys[2], iter->key().ToString()); | |
2399 | EXPECT_EQ(use_first_key ? 2 : 4, | |
2400 | stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2401 | ||
2402 | // Seek to first and step back without accessing value. | |
2403 | iter->SeekToFirst(); | |
2404 | ASSERT_TRUE(iter->Valid()); | |
2405 | EXPECT_EQ(keys[0], iter->key().ToString()); | |
2406 | EXPECT_EQ(use_first_key ? 2 : 5, | |
2407 | stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2408 | ||
2409 | iter->Prev(); | |
2410 | EXPECT_FALSE(iter->Valid()); | |
2411 | EXPECT_EQ(use_first_key ? 2 : 5, | |
2412 | stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2413 | ||
2414 | // Do some SeekForPrev() and SeekToLast() just to cover all methods. | |
2415 | iter->SeekForPrev(ikey("caad")); | |
2416 | ASSERT_TRUE(iter->Valid()); | |
2417 | EXPECT_EQ(keys[4], iter->key().ToString()); | |
2418 | EXPECT_EQ(use_first_key ? 3 : 6, | |
2419 | stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
20effc67 | 2420 | ASSERT_TRUE(iter->PrepareValue()); |
f67539c2 TL |
2421 | EXPECT_EQ("v4", iter->value().ToString()); |
2422 | EXPECT_EQ(use_first_key ? 3 : 6, | |
2423 | stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2424 | ||
2425 | iter->SeekToLast(); | |
2426 | ASSERT_TRUE(iter->Valid()); | |
2427 | EXPECT_EQ(keys[7], iter->key().ToString()); | |
2428 | EXPECT_EQ(use_first_key ? 4 : 7, | |
2429 | stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
20effc67 | 2430 | ASSERT_TRUE(iter->PrepareValue()); |
f67539c2 TL |
2431 | EXPECT_EQ("v7", iter->value().ToString()); |
2432 | EXPECT_EQ(use_first_key ? 4 : 7, | |
2433 | stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2434 | ||
2435 | EXPECT_EQ(4, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); | |
2436 | ||
2437 | c.ResetTableReader(); | |
2438 | } | |
2439 | } | |
2440 | ||
2441 | TEST_P(BlockBasedTableTest, BinaryIndexWithFirstKeyGlobalSeqno) { | |
2442 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); | |
2443 | table_options.index_type = BlockBasedTableOptions::kBinarySearchWithFirstKey; | |
2444 | table_options.block_cache = NewLRUCache(10000); | |
2445 | Options options; | |
2446 | options.statistics = CreateDBStatistics(); | |
2447 | Statistics* stats = options.statistics.get(); | |
2448 | options.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
2449 | std::unique_ptr<InternalKeyComparator> comparator( | |
2450 | new InternalKeyComparator(BytewiseComparator())); | |
2451 | const ImmutableCFOptions ioptions(options); | |
2452 | const MutableCFOptions moptions(options); | |
2453 | ||
2454 | TableConstructor c(BytewiseComparator(), /* convert_to_internal_key */ false, | |
2455 | /* level */ -1, /* largest_seqno */ 42); | |
2456 | ||
2457 | c.Add(InternalKey("b", 0, kTypeValue).Encode().ToString(), "x"); | |
2458 | c.Add(InternalKey("c", 0, kTypeValue).Encode().ToString(), "y"); | |
2459 | ||
2460 | std::vector<std::string> keys; | |
2461 | stl_wrappers::KVMap kvmap; | |
2462 | c.Finish(options, ioptions, moptions, table_options, *comparator, &keys, | |
2463 | &kvmap); | |
2464 | ASSERT_EQ(2, keys.size()); | |
2465 | ||
2466 | auto reader = c.GetTableReader(); | |
2467 | auto props = reader->GetTableProperties(); | |
2468 | ASSERT_EQ(1u, props->num_data_blocks); | |
20effc67 | 2469 | ReadOptions read_options; |
f67539c2 | 2470 | std::unique_ptr<InternalIterator> iter(reader->NewIterator( |
20effc67 TL |
2471 | read_options, /*prefix_extractor=*/nullptr, /*arena=*/nullptr, |
2472 | /*skip_filters=*/false, TableReaderCaller::kUncategorized, | |
2473 | /*compaction_readahead_size=*/0, /*allow_unprepared_value=*/true)); | |
f67539c2 TL |
2474 | |
2475 | iter->Seek(InternalKey("a", 0, kTypeValue).Encode().ToString()); | |
2476 | ASSERT_TRUE(iter->Valid()); | |
2477 | EXPECT_EQ(InternalKey("b", 42, kTypeValue).Encode().ToString(), | |
2478 | iter->key().ToString()); | |
2479 | EXPECT_NE(keys[0], iter->key().ToString()); | |
2480 | // Key should have been served from index, without reading data blocks. | |
2481 | EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); | |
2482 | ||
20effc67 | 2483 | ASSERT_TRUE(iter->PrepareValue()); |
f67539c2 TL |
2484 | EXPECT_EQ("x", iter->value().ToString()); |
2485 | EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); | |
2486 | EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); | |
2487 | EXPECT_EQ(InternalKey("b", 42, kTypeValue).Encode().ToString(), | |
2488 | iter->key().ToString()); | |
2489 | ||
2490 | c.ResetTableReader(); | |
2491 | } | |
2492 | ||
7c673cae FG |
2493 | // It's very hard to figure out the index block size of a block accurately. |
2494 | // To make sure we get the index size, we just make sure as key number | |
2495 | // grows, the filter block size also grows. | |
11fdf7f2 | 2496 | TEST_P(BlockBasedTableTest, IndexSizeStat) { |
7c673cae FG |
2497 | uint64_t last_index_size = 0; |
2498 | ||
2499 | // we need to use random keys since the pure human readable texts | |
2500 | // may be well compressed, resulting insignifcant change of index | |
2501 | // block size. | |
2502 | Random rnd(test::RandomSeed()); | |
2503 | std::vector<std::string> keys; | |
2504 | ||
2505 | for (int i = 0; i < 100; ++i) { | |
20effc67 | 2506 | keys.push_back(rnd.RandomString(10000)); |
7c673cae FG |
2507 | } |
2508 | ||
2509 | // Each time we load one more key to the table. the table index block | |
2510 | // size is expected to be larger than last time's. | |
2511 | for (size_t i = 1; i < keys.size(); ++i) { | |
2512 | TableConstructor c(BytewiseComparator(), | |
2513 | true /* convert_to_internal_key_ */); | |
2514 | for (size_t j = 0; j < i; ++j) { | |
2515 | c.Add(keys[j], "val"); | |
2516 | } | |
2517 | ||
2518 | std::vector<std::string> ks; | |
2519 | stl_wrappers::KVMap kvmap; | |
2520 | Options options; | |
2521 | options.compression = kNoCompression; | |
11fdf7f2 | 2522 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
7c673cae FG |
2523 | table_options.block_restart_interval = 1; |
2524 | options.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
2525 | ||
2526 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 TL |
2527 | const MutableCFOptions moptions(options); |
2528 | c.Finish(options, ioptions, moptions, table_options, | |
7c673cae FG |
2529 | GetPlainInternalComparator(options.comparator), &ks, &kvmap); |
2530 | auto index_size = c.GetTableReader()->GetTableProperties()->index_size; | |
2531 | ASSERT_GT(index_size, last_index_size); | |
2532 | last_index_size = index_size; | |
2533 | c.ResetTableReader(); | |
2534 | } | |
2535 | } | |
2536 | ||
11fdf7f2 | 2537 | TEST_P(BlockBasedTableTest, NumBlockStat) { |
7c673cae FG |
2538 | Random rnd(test::RandomSeed()); |
2539 | TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); | |
2540 | Options options; | |
2541 | options.compression = kNoCompression; | |
11fdf7f2 | 2542 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
7c673cae FG |
2543 | table_options.block_restart_interval = 1; |
2544 | table_options.block_size = 1000; | |
2545 | options.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
2546 | ||
2547 | for (int i = 0; i < 10; ++i) { | |
2548 | // the key/val are slightly smaller than block size, so that each block | |
2549 | // holds roughly one key/value pair. | |
20effc67 | 2550 | c.Add(rnd.RandomString(900), "val"); |
7c673cae FG |
2551 | } |
2552 | ||
2553 | std::vector<std::string> ks; | |
2554 | stl_wrappers::KVMap kvmap; | |
2555 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 TL |
2556 | const MutableCFOptions moptions(options); |
2557 | c.Finish(options, ioptions, moptions, table_options, | |
7c673cae FG |
2558 | GetPlainInternalComparator(options.comparator), &ks, &kvmap); |
2559 | ASSERT_EQ(kvmap.size(), | |
2560 | c.GetTableReader()->GetTableProperties()->num_data_blocks); | |
2561 | c.ResetTableReader(); | |
2562 | } | |
2563 | ||
f67539c2 TL |
2564 | TEST_P(BlockBasedTableTest, TracingGetTest) { |
2565 | TableConstructor c(BytewiseComparator()); | |
2566 | Options options; | |
2567 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); | |
2568 | options.create_if_missing = true; | |
2569 | table_options.block_cache = NewLRUCache(1024 * 1024, 0); | |
2570 | table_options.cache_index_and_filter_blocks = true; | |
2571 | table_options.filter_policy.reset(NewBloomFilterPolicy(10, true)); | |
2572 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
2573 | SetupTracingTest(&c); | |
2574 | std::vector<std::string> keys; | |
2575 | stl_wrappers::KVMap kvmap; | |
2576 | ImmutableCFOptions ioptions(options); | |
2577 | MutableCFOptions moptions(options); | |
2578 | c.Finish(options, ioptions, moptions, table_options, | |
2579 | GetPlainInternalComparator(options.comparator), &keys, &kvmap); | |
2580 | std::string user_key = "k01"; | |
2581 | InternalKey internal_key(user_key, 0, kTypeValue); | |
2582 | std::string encoded_key = internal_key.Encode().ToString(); | |
2583 | for (uint32_t i = 1; i <= 2; i++) { | |
2584 | PinnableSlice value; | |
2585 | GetContext get_context(options.comparator, nullptr, nullptr, nullptr, | |
2586 | GetContext::kNotFound, user_key, &value, nullptr, | |
2587 | nullptr, true, nullptr, nullptr, nullptr, nullptr, | |
2588 | nullptr, nullptr, /*tracing_get_id=*/i); | |
2589 | get_perf_context()->Reset(); | |
2590 | ASSERT_OK(c.GetTableReader()->Get(ReadOptions(), encoded_key, &get_context, | |
2591 | moptions.prefix_extractor.get())); | |
2592 | ASSERT_EQ(get_context.State(), GetContext::kFound); | |
2593 | ASSERT_EQ(value.ToString(), kDummyValue); | |
2594 | } | |
2595 | ||
2596 | // Verify traces. | |
2597 | std::vector<BlockCacheTraceRecord> expected_records; | |
2598 | // The first two records should be prefetching index and filter blocks. | |
2599 | BlockCacheTraceRecord record; | |
2600 | record.block_type = TraceType::kBlockTraceIndexBlock; | |
2601 | record.caller = TableReaderCaller::kPrefetch; | |
2602 | record.is_cache_hit = Boolean::kFalse; | |
2603 | record.no_insert = Boolean::kFalse; | |
2604 | expected_records.push_back(record); | |
2605 | record.block_type = TraceType::kBlockTraceFilterBlock; | |
2606 | expected_records.push_back(record); | |
2607 | // Then we should have three records for one index, one filter, and one data | |
2608 | // block access. | |
2609 | record.get_id = 1; | |
2610 | record.block_type = TraceType::kBlockTraceIndexBlock; | |
2611 | record.caller = TableReaderCaller::kUserGet; | |
2612 | record.get_from_user_specified_snapshot = Boolean::kFalse; | |
2613 | record.referenced_key = encoded_key; | |
2614 | record.referenced_key_exist_in_block = Boolean::kTrue; | |
2615 | record.is_cache_hit = Boolean::kTrue; | |
2616 | expected_records.push_back(record); | |
2617 | record.block_type = TraceType::kBlockTraceFilterBlock; | |
2618 | expected_records.push_back(record); | |
2619 | record.is_cache_hit = Boolean::kFalse; | |
2620 | record.block_type = TraceType::kBlockTraceDataBlock; | |
2621 | expected_records.push_back(record); | |
2622 | // The second get should all observe cache hits. | |
2623 | record.is_cache_hit = Boolean::kTrue; | |
2624 | record.get_id = 2; | |
2625 | record.block_type = TraceType::kBlockTraceIndexBlock; | |
2626 | record.caller = TableReaderCaller::kUserGet; | |
2627 | record.get_from_user_specified_snapshot = Boolean::kFalse; | |
2628 | record.referenced_key = encoded_key; | |
2629 | expected_records.push_back(record); | |
2630 | record.block_type = TraceType::kBlockTraceFilterBlock; | |
2631 | expected_records.push_back(record); | |
2632 | record.block_type = TraceType::kBlockTraceDataBlock; | |
2633 | expected_records.push_back(record); | |
2634 | VerifyBlockAccessTrace(&c, expected_records); | |
2635 | c.ResetTableReader(); | |
2636 | } | |
2637 | ||
2638 | TEST_P(BlockBasedTableTest, TracingApproximateOffsetOfTest) { | |
2639 | TableConstructor c(BytewiseComparator()); | |
2640 | Options options; | |
2641 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); | |
2642 | options.create_if_missing = true; | |
2643 | table_options.block_cache = NewLRUCache(1024 * 1024, 0); | |
2644 | table_options.cache_index_and_filter_blocks = true; | |
2645 | table_options.filter_policy.reset(NewBloomFilterPolicy(10, true)); | |
2646 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
2647 | SetupTracingTest(&c); | |
2648 | std::vector<std::string> keys; | |
2649 | stl_wrappers::KVMap kvmap; | |
2650 | ImmutableCFOptions ioptions(options); | |
2651 | MutableCFOptions moptions(options); | |
2652 | c.Finish(options, ioptions, moptions, table_options, | |
2653 | GetPlainInternalComparator(options.comparator), &keys, &kvmap); | |
2654 | for (uint32_t i = 1; i <= 2; i++) { | |
2655 | std::string user_key = "k01"; | |
2656 | InternalKey internal_key(user_key, 0, kTypeValue); | |
2657 | std::string encoded_key = internal_key.Encode().ToString(); | |
2658 | c.GetTableReader()->ApproximateOffsetOf( | |
2659 | encoded_key, TableReaderCaller::kUserApproximateSize); | |
2660 | } | |
2661 | // Verify traces. | |
2662 | std::vector<BlockCacheTraceRecord> expected_records; | |
2663 | // The first two records should be prefetching index and filter blocks. | |
2664 | BlockCacheTraceRecord record; | |
2665 | record.block_type = TraceType::kBlockTraceIndexBlock; | |
2666 | record.caller = TableReaderCaller::kPrefetch; | |
2667 | record.is_cache_hit = Boolean::kFalse; | |
2668 | record.no_insert = Boolean::kFalse; | |
2669 | expected_records.push_back(record); | |
2670 | record.block_type = TraceType::kBlockTraceFilterBlock; | |
2671 | expected_records.push_back(record); | |
2672 | // Then we should have two records for only index blocks. | |
2673 | record.block_type = TraceType::kBlockTraceIndexBlock; | |
2674 | record.caller = TableReaderCaller::kUserApproximateSize; | |
2675 | record.is_cache_hit = Boolean::kTrue; | |
2676 | expected_records.push_back(record); | |
2677 | expected_records.push_back(record); | |
2678 | VerifyBlockAccessTrace(&c, expected_records); | |
2679 | c.ResetTableReader(); | |
2680 | } | |
2681 | ||
2682 | TEST_P(BlockBasedTableTest, TracingIterator) { | |
2683 | TableConstructor c(BytewiseComparator()); | |
2684 | Options options; | |
2685 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); | |
2686 | options.create_if_missing = true; | |
2687 | table_options.block_cache = NewLRUCache(1024 * 1024, 0); | |
2688 | table_options.cache_index_and_filter_blocks = true; | |
2689 | table_options.filter_policy.reset(NewBloomFilterPolicy(10, true)); | |
2690 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
2691 | SetupTracingTest(&c); | |
2692 | std::vector<std::string> keys; | |
2693 | stl_wrappers::KVMap kvmap; | |
2694 | ImmutableCFOptions ioptions(options); | |
2695 | MutableCFOptions moptions(options); | |
2696 | c.Finish(options, ioptions, moptions, table_options, | |
2697 | GetPlainInternalComparator(options.comparator), &keys, &kvmap); | |
2698 | ||
2699 | for (uint32_t i = 1; i <= 2; i++) { | |
20effc67 | 2700 | ReadOptions read_options; |
f67539c2 | 2701 | std::unique_ptr<InternalIterator> iter(c.GetTableReader()->NewIterator( |
20effc67 | 2702 | read_options, moptions.prefix_extractor.get(), /*arena=*/nullptr, |
f67539c2 TL |
2703 | /*skip_filters=*/false, TableReaderCaller::kUserIterator)); |
2704 | iter->SeekToFirst(); | |
2705 | while (iter->Valid()) { | |
2706 | iter->key(); | |
2707 | iter->value(); | |
2708 | iter->Next(); | |
2709 | } | |
2710 | ASSERT_OK(iter->status()); | |
2711 | iter.reset(); | |
2712 | } | |
2713 | ||
2714 | // Verify traces. | |
2715 | std::vector<BlockCacheTraceRecord> expected_records; | |
2716 | // The first two records should be prefetching index and filter blocks. | |
2717 | BlockCacheTraceRecord record; | |
2718 | record.block_type = TraceType::kBlockTraceIndexBlock; | |
2719 | record.caller = TableReaderCaller::kPrefetch; | |
2720 | record.is_cache_hit = Boolean::kFalse; | |
2721 | record.no_insert = Boolean::kFalse; | |
2722 | expected_records.push_back(record); | |
2723 | record.block_type = TraceType::kBlockTraceFilterBlock; | |
2724 | expected_records.push_back(record); | |
2725 | // Then we should have three records for index and two data block access. | |
2726 | record.block_type = TraceType::kBlockTraceIndexBlock; | |
2727 | record.caller = TableReaderCaller::kUserIterator; | |
2728 | record.is_cache_hit = Boolean::kTrue; | |
2729 | expected_records.push_back(record); | |
2730 | record.block_type = TraceType::kBlockTraceDataBlock; | |
2731 | record.is_cache_hit = Boolean::kFalse; | |
2732 | expected_records.push_back(record); | |
2733 | expected_records.push_back(record); | |
2734 | // When we iterate this file for the second time, we should observe all cache | |
2735 | // hits. | |
2736 | record.block_type = TraceType::kBlockTraceIndexBlock; | |
2737 | record.is_cache_hit = Boolean::kTrue; | |
2738 | expected_records.push_back(record); | |
2739 | record.block_type = TraceType::kBlockTraceDataBlock; | |
2740 | expected_records.push_back(record); | |
2741 | expected_records.push_back(record); | |
2742 | VerifyBlockAccessTrace(&c, expected_records); | |
2743 | c.ResetTableReader(); | |
2744 | } | |
2745 | ||
7c673cae FG |
2746 | // A simple tool that takes the snapshot of block cache statistics. |
2747 | class BlockCachePropertiesSnapshot { | |
2748 | public: | |
2749 | explicit BlockCachePropertiesSnapshot(Statistics* statistics) { | |
2750 | block_cache_miss = statistics->getTickerCount(BLOCK_CACHE_MISS); | |
2751 | block_cache_hit = statistics->getTickerCount(BLOCK_CACHE_HIT); | |
2752 | index_block_cache_miss = statistics->getTickerCount(BLOCK_CACHE_INDEX_MISS); | |
2753 | index_block_cache_hit = statistics->getTickerCount(BLOCK_CACHE_INDEX_HIT); | |
2754 | data_block_cache_miss = statistics->getTickerCount(BLOCK_CACHE_DATA_MISS); | |
2755 | data_block_cache_hit = statistics->getTickerCount(BLOCK_CACHE_DATA_HIT); | |
2756 | filter_block_cache_miss = | |
2757 | statistics->getTickerCount(BLOCK_CACHE_FILTER_MISS); | |
2758 | filter_block_cache_hit = statistics->getTickerCount(BLOCK_CACHE_FILTER_HIT); | |
2759 | block_cache_bytes_read = statistics->getTickerCount(BLOCK_CACHE_BYTES_READ); | |
2760 | block_cache_bytes_write = | |
2761 | statistics->getTickerCount(BLOCK_CACHE_BYTES_WRITE); | |
2762 | } | |
2763 | ||
2764 | void AssertIndexBlockStat(int64_t expected_index_block_cache_miss, | |
2765 | int64_t expected_index_block_cache_hit) { | |
2766 | ASSERT_EQ(expected_index_block_cache_miss, index_block_cache_miss); | |
2767 | ASSERT_EQ(expected_index_block_cache_hit, index_block_cache_hit); | |
2768 | } | |
2769 | ||
2770 | void AssertFilterBlockStat(int64_t expected_filter_block_cache_miss, | |
2771 | int64_t expected_filter_block_cache_hit) { | |
2772 | ASSERT_EQ(expected_filter_block_cache_miss, filter_block_cache_miss); | |
2773 | ASSERT_EQ(expected_filter_block_cache_hit, filter_block_cache_hit); | |
2774 | } | |
2775 | ||
2776 | // Check if the fetched props matches the expected ones. | |
2777 | // TODO(kailiu) Use this only when you disabled filter policy! | |
2778 | void AssertEqual(int64_t expected_index_block_cache_miss, | |
2779 | int64_t expected_index_block_cache_hit, | |
2780 | int64_t expected_data_block_cache_miss, | |
2781 | int64_t expected_data_block_cache_hit) const { | |
2782 | ASSERT_EQ(expected_index_block_cache_miss, index_block_cache_miss); | |
2783 | ASSERT_EQ(expected_index_block_cache_hit, index_block_cache_hit); | |
2784 | ASSERT_EQ(expected_data_block_cache_miss, data_block_cache_miss); | |
2785 | ASSERT_EQ(expected_data_block_cache_hit, data_block_cache_hit); | |
2786 | ASSERT_EQ(expected_index_block_cache_miss + expected_data_block_cache_miss, | |
2787 | block_cache_miss); | |
2788 | ASSERT_EQ(expected_index_block_cache_hit + expected_data_block_cache_hit, | |
2789 | block_cache_hit); | |
2790 | } | |
2791 | ||
2792 | int64_t GetCacheBytesRead() { return block_cache_bytes_read; } | |
2793 | ||
2794 | int64_t GetCacheBytesWrite() { return block_cache_bytes_write; } | |
2795 | ||
2796 | private: | |
2797 | int64_t block_cache_miss = 0; | |
2798 | int64_t block_cache_hit = 0; | |
2799 | int64_t index_block_cache_miss = 0; | |
2800 | int64_t index_block_cache_hit = 0; | |
2801 | int64_t data_block_cache_miss = 0; | |
2802 | int64_t data_block_cache_hit = 0; | |
2803 | int64_t filter_block_cache_miss = 0; | |
2804 | int64_t filter_block_cache_hit = 0; | |
2805 | int64_t block_cache_bytes_read = 0; | |
2806 | int64_t block_cache_bytes_write = 0; | |
2807 | }; | |
2808 | ||
2809 | // Make sure, by default, index/filter blocks were pre-loaded (meaning we won't | |
2810 | // use block cache to store them). | |
11fdf7f2 | 2811 | TEST_P(BlockBasedTableTest, BlockCacheDisabledTest) { |
7c673cae FG |
2812 | Options options; |
2813 | options.create_if_missing = true; | |
2814 | options.statistics = CreateDBStatistics(); | |
11fdf7f2 | 2815 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
7c673cae FG |
2816 | table_options.block_cache = NewLRUCache(1024, 4); |
2817 | table_options.filter_policy.reset(NewBloomFilterPolicy(10)); | |
2818 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
2819 | std::vector<std::string> keys; | |
2820 | stl_wrappers::KVMap kvmap; | |
2821 | ||
2822 | TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); | |
2823 | c.Add("key", "value"); | |
2824 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 TL |
2825 | const MutableCFOptions moptions(options); |
2826 | c.Finish(options, ioptions, moptions, table_options, | |
7c673cae FG |
2827 | GetPlainInternalComparator(options.comparator), &keys, &kvmap); |
2828 | ||
2829 | // preloading filter/index blocks is enabled. | |
2830 | auto reader = dynamic_cast<BlockBasedTable*>(c.GetTableReader()); | |
f67539c2 TL |
2831 | ASSERT_FALSE(reader->TEST_FilterBlockInCache()); |
2832 | ASSERT_FALSE(reader->TEST_IndexBlockInCache()); | |
7c673cae FG |
2833 | |
2834 | { | |
2835 | // nothing happens in the beginning | |
2836 | BlockCachePropertiesSnapshot props(options.statistics.get()); | |
2837 | props.AssertIndexBlockStat(0, 0); | |
2838 | props.AssertFilterBlockStat(0, 0); | |
2839 | } | |
2840 | ||
2841 | { | |
2842 | GetContext get_context(options.comparator, nullptr, nullptr, nullptr, | |
2843 | GetContext::kNotFound, Slice(), nullptr, nullptr, | |
f67539c2 | 2844 | nullptr, true, nullptr, nullptr); |
7c673cae | 2845 | // a hack that just to trigger BlockBasedTable::GetFilter. |
20effc67 TL |
2846 | ASSERT_OK(reader->Get(ReadOptions(), "non-exist-key", &get_context, |
2847 | moptions.prefix_extractor.get())); | |
7c673cae FG |
2848 | BlockCachePropertiesSnapshot props(options.statistics.get()); |
2849 | props.AssertIndexBlockStat(0, 0); | |
2850 | props.AssertFilterBlockStat(0, 0); | |
2851 | } | |
2852 | } | |
2853 | ||
2854 | // Due to the difficulities of the intersaction between statistics, this test | |
2855 | // only tests the case when "index block is put to block cache" | |
11fdf7f2 | 2856 | TEST_P(BlockBasedTableTest, FilterBlockInBlockCache) { |
7c673cae FG |
2857 | // -- Table construction |
2858 | Options options; | |
2859 | options.create_if_missing = true; | |
2860 | options.statistics = CreateDBStatistics(); | |
2861 | ||
2862 | // Enable the cache for index/filter blocks | |
11fdf7f2 | 2863 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
f67539c2 TL |
2864 | LRUCacheOptions co; |
2865 | co.capacity = 2048; | |
2866 | co.num_shard_bits = 2; | |
2867 | co.metadata_charge_policy = kDontChargeCacheMetadata; | |
2868 | table_options.block_cache = NewLRUCache(co); | |
7c673cae FG |
2869 | table_options.cache_index_and_filter_blocks = true; |
2870 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
2871 | std::vector<std::string> keys; | |
2872 | stl_wrappers::KVMap kvmap; | |
2873 | ||
2874 | TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); | |
2875 | c.Add("key", "value"); | |
2876 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 TL |
2877 | const MutableCFOptions moptions(options); |
2878 | c.Finish(options, ioptions, moptions, table_options, | |
7c673cae FG |
2879 | GetPlainInternalComparator(options.comparator), &keys, &kvmap); |
2880 | // preloading filter/index blocks is prohibited. | |
2881 | auto* reader = dynamic_cast<BlockBasedTable*>(c.GetTableReader()); | |
f67539c2 TL |
2882 | ASSERT_FALSE(reader->TEST_FilterBlockInCache()); |
2883 | ASSERT_TRUE(reader->TEST_IndexBlockInCache()); | |
7c673cae FG |
2884 | |
2885 | // -- PART 1: Open with regular block cache. | |
2886 | // Since block_cache is disabled, no cache activities will be involved. | |
494da23a | 2887 | std::unique_ptr<InternalIterator> iter; |
7c673cae FG |
2888 | |
2889 | int64_t last_cache_bytes_read = 0; | |
2890 | // At first, no block will be accessed. | |
2891 | { | |
2892 | BlockCachePropertiesSnapshot props(options.statistics.get()); | |
2893 | // index will be added to block cache. | |
2894 | props.AssertEqual(1, // index block miss | |
2895 | 0, 0, 0); | |
2896 | ASSERT_EQ(props.GetCacheBytesRead(), 0); | |
2897 | ASSERT_EQ(props.GetCacheBytesWrite(), | |
f67539c2 | 2898 | static_cast<int64_t>(table_options.block_cache->GetUsage())); |
7c673cae FG |
2899 | last_cache_bytes_read = props.GetCacheBytesRead(); |
2900 | } | |
2901 | ||
2902 | // Only index block will be accessed | |
2903 | { | |
11fdf7f2 | 2904 | iter.reset(c.NewIterator(moptions.prefix_extractor.get())); |
7c673cae FG |
2905 | BlockCachePropertiesSnapshot props(options.statistics.get()); |
2906 | // NOTE: to help better highlight the "detla" of each ticker, I use | |
2907 | // <last_value> + <added_value> to indicate the increment of changed | |
2908 | // value; other numbers remain the same. | |
2909 | props.AssertEqual(1, 0 + 1, // index block hit | |
2910 | 0, 0); | |
2911 | // Cache hit, bytes read from cache should increase | |
2912 | ASSERT_GT(props.GetCacheBytesRead(), last_cache_bytes_read); | |
2913 | ASSERT_EQ(props.GetCacheBytesWrite(), | |
f67539c2 | 2914 | static_cast<int64_t>(table_options.block_cache->GetUsage())); |
7c673cae FG |
2915 | last_cache_bytes_read = props.GetCacheBytesRead(); |
2916 | } | |
2917 | ||
2918 | // Only data block will be accessed | |
2919 | { | |
2920 | iter->SeekToFirst(); | |
20effc67 | 2921 | ASSERT_OK(iter->status()); |
7c673cae FG |
2922 | BlockCachePropertiesSnapshot props(options.statistics.get()); |
2923 | props.AssertEqual(1, 1, 0 + 1, // data block miss | |
2924 | 0); | |
2925 | // Cache miss, Bytes read from cache should not change | |
2926 | ASSERT_EQ(props.GetCacheBytesRead(), last_cache_bytes_read); | |
2927 | ASSERT_EQ(props.GetCacheBytesWrite(), | |
f67539c2 | 2928 | static_cast<int64_t>(table_options.block_cache->GetUsage())); |
7c673cae FG |
2929 | last_cache_bytes_read = props.GetCacheBytesRead(); |
2930 | } | |
2931 | ||
2932 | // Data block will be in cache | |
2933 | { | |
11fdf7f2 | 2934 | iter.reset(c.NewIterator(moptions.prefix_extractor.get())); |
7c673cae | 2935 | iter->SeekToFirst(); |
20effc67 | 2936 | ASSERT_OK(iter->status()); |
7c673cae FG |
2937 | BlockCachePropertiesSnapshot props(options.statistics.get()); |
2938 | props.AssertEqual(1, 1 + 1, /* index block hit */ | |
2939 | 1, 0 + 1 /* data block hit */); | |
2940 | // Cache hit, bytes read from cache should increase | |
2941 | ASSERT_GT(props.GetCacheBytesRead(), last_cache_bytes_read); | |
2942 | ASSERT_EQ(props.GetCacheBytesWrite(), | |
f67539c2 | 2943 | static_cast<int64_t>(table_options.block_cache->GetUsage())); |
7c673cae FG |
2944 | } |
2945 | // release the iterator so that the block cache can reset correctly. | |
2946 | iter.reset(); | |
2947 | ||
2948 | c.ResetTableReader(); | |
2949 | ||
2950 | // -- PART 2: Open with very small block cache | |
2951 | // In this test, no block will ever get hit since the block cache is | |
2952 | // too small to fit even one entry. | |
2953 | table_options.block_cache = NewLRUCache(1, 4); | |
2954 | options.statistics = CreateDBStatistics(); | |
2955 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
2956 | const ImmutableCFOptions ioptions2(options); | |
11fdf7f2 | 2957 | const MutableCFOptions moptions2(options); |
20effc67 | 2958 | ASSERT_OK(c.Reopen(ioptions2, moptions2)); |
7c673cae FG |
2959 | { |
2960 | BlockCachePropertiesSnapshot props(options.statistics.get()); | |
2961 | props.AssertEqual(1, // index block miss | |
2962 | 0, 0, 0); | |
2963 | // Cache miss, Bytes read from cache should not change | |
2964 | ASSERT_EQ(props.GetCacheBytesRead(), 0); | |
2965 | } | |
2966 | ||
2967 | { | |
2968 | // Both index and data block get accessed. | |
2969 | // It first cache index block then data block. But since the cache size | |
2970 | // is only 1, index block will be purged after data block is inserted. | |
11fdf7f2 | 2971 | iter.reset(c.NewIterator(moptions2.prefix_extractor.get())); |
7c673cae FG |
2972 | BlockCachePropertiesSnapshot props(options.statistics.get()); |
2973 | props.AssertEqual(1 + 1, // index block miss | |
2974 | 0, 0, // data block miss | |
2975 | 0); | |
2976 | // Cache hit, bytes read from cache should increase | |
2977 | ASSERT_EQ(props.GetCacheBytesRead(), 0); | |
2978 | } | |
2979 | ||
2980 | { | |
2981 | // SeekToFirst() accesses data block. With similar reason, we expect data | |
2982 | // block's cache miss. | |
2983 | iter->SeekToFirst(); | |
20effc67 | 2984 | ASSERT_OK(iter->status()); |
7c673cae FG |
2985 | BlockCachePropertiesSnapshot props(options.statistics.get()); |
2986 | props.AssertEqual(2, 0, 0 + 1, // data block miss | |
2987 | 0); | |
2988 | // Cache miss, Bytes read from cache should not change | |
2989 | ASSERT_EQ(props.GetCacheBytesRead(), 0); | |
2990 | } | |
2991 | iter.reset(); | |
2992 | c.ResetTableReader(); | |
2993 | ||
2994 | // -- PART 3: Open table with bloom filter enabled but not in SST file | |
2995 | table_options.block_cache = NewLRUCache(4096, 4); | |
2996 | table_options.cache_index_and_filter_blocks = false; | |
2997 | options.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
2998 | ||
2999 | TableConstructor c3(BytewiseComparator()); | |
3000 | std::string user_key = "k01"; | |
3001 | InternalKey internal_key(user_key, 0, kTypeValue); | |
3002 | c3.Add(internal_key.Encode().ToString(), "hello"); | |
3003 | ImmutableCFOptions ioptions3(options); | |
11fdf7f2 | 3004 | MutableCFOptions moptions3(options); |
7c673cae | 3005 | // Generate table without filter policy |
11fdf7f2 | 3006 | c3.Finish(options, ioptions3, moptions3, table_options, |
7c673cae FG |
3007 | GetPlainInternalComparator(options.comparator), &keys, &kvmap); |
3008 | c3.ResetTableReader(); | |
3009 | ||
3010 | // Open table with filter policy | |
3011 | table_options.filter_policy.reset(NewBloomFilterPolicy(1)); | |
3012 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
3013 | options.statistics = CreateDBStatistics(); | |
3014 | ImmutableCFOptions ioptions4(options); | |
11fdf7f2 TL |
3015 | MutableCFOptions moptions4(options); |
3016 | ASSERT_OK(c3.Reopen(ioptions4, moptions4)); | |
7c673cae | 3017 | reader = dynamic_cast<BlockBasedTable*>(c3.GetTableReader()); |
f67539c2 | 3018 | ASSERT_FALSE(reader->TEST_FilterBlockInCache()); |
7c673cae FG |
3019 | PinnableSlice value; |
3020 | GetContext get_context(options.comparator, nullptr, nullptr, nullptr, | |
3021 | GetContext::kNotFound, user_key, &value, nullptr, | |
f67539c2 | 3022 | nullptr, true, nullptr, nullptr); |
11fdf7f2 TL |
3023 | ASSERT_OK(reader->Get(ReadOptions(), internal_key.Encode(), &get_context, |
3024 | moptions4.prefix_extractor.get())); | |
7c673cae FG |
3025 | ASSERT_STREQ(value.data(), "hello"); |
3026 | BlockCachePropertiesSnapshot props(options.statistics.get()); | |
3027 | props.AssertFilterBlockStat(0, 0); | |
3028 | c3.ResetTableReader(); | |
3029 | } | |
3030 | ||
3031 | void ValidateBlockSizeDeviation(int value, int expected) { | |
3032 | BlockBasedTableOptions table_options; | |
3033 | table_options.block_size_deviation = value; | |
3034 | BlockBasedTableFactory* factory = new BlockBasedTableFactory(table_options); | |
3035 | ||
3036 | const BlockBasedTableOptions* normalized_table_options = | |
20effc67 | 3037 | factory->GetOptions<BlockBasedTableOptions>(); |
7c673cae FG |
3038 | ASSERT_EQ(normalized_table_options->block_size_deviation, expected); |
3039 | ||
3040 | delete factory; | |
3041 | } | |
3042 | ||
3043 | void ValidateBlockRestartInterval(int value, int expected) { | |
3044 | BlockBasedTableOptions table_options; | |
3045 | table_options.block_restart_interval = value; | |
3046 | BlockBasedTableFactory* factory = new BlockBasedTableFactory(table_options); | |
3047 | ||
3048 | const BlockBasedTableOptions* normalized_table_options = | |
20effc67 | 3049 | factory->GetOptions<BlockBasedTableOptions>(); |
7c673cae FG |
3050 | ASSERT_EQ(normalized_table_options->block_restart_interval, expected); |
3051 | ||
3052 | delete factory; | |
3053 | } | |
3054 | ||
11fdf7f2 | 3055 | TEST_P(BlockBasedTableTest, InvalidOptions) { |
7c673cae FG |
3056 | // invalid values for block_size_deviation (<0 or >100) are silently set to 0 |
3057 | ValidateBlockSizeDeviation(-10, 0); | |
3058 | ValidateBlockSizeDeviation(-1, 0); | |
3059 | ValidateBlockSizeDeviation(0, 0); | |
3060 | ValidateBlockSizeDeviation(1, 1); | |
3061 | ValidateBlockSizeDeviation(99, 99); | |
3062 | ValidateBlockSizeDeviation(100, 100); | |
3063 | ValidateBlockSizeDeviation(101, 0); | |
3064 | ValidateBlockSizeDeviation(1000, 0); | |
3065 | ||
3066 | // invalid values for block_restart_interval (<1) are silently set to 1 | |
3067 | ValidateBlockRestartInterval(-10, 1); | |
3068 | ValidateBlockRestartInterval(-1, 1); | |
3069 | ValidateBlockRestartInterval(0, 1); | |
3070 | ValidateBlockRestartInterval(1, 1); | |
3071 | ValidateBlockRestartInterval(2, 2); | |
3072 | ValidateBlockRestartInterval(1000, 1000); | |
3073 | } | |
3074 | ||
11fdf7f2 | 3075 | TEST_P(BlockBasedTableTest, BlockReadCountTest) { |
7c673cae FG |
3076 | // bloom_filter_type = 0 -- block-based filter |
3077 | // bloom_filter_type = 0 -- full filter | |
3078 | for (int bloom_filter_type = 0; bloom_filter_type < 2; ++bloom_filter_type) { | |
3079 | for (int index_and_filter_in_cache = 0; index_and_filter_in_cache < 2; | |
3080 | ++index_and_filter_in_cache) { | |
3081 | Options options; | |
3082 | options.create_if_missing = true; | |
3083 | ||
11fdf7f2 | 3084 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
7c673cae FG |
3085 | table_options.block_cache = NewLRUCache(1, 0); |
3086 | table_options.cache_index_and_filter_blocks = index_and_filter_in_cache; | |
3087 | table_options.filter_policy.reset( | |
3088 | NewBloomFilterPolicy(10, bloom_filter_type == 0)); | |
3089 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
3090 | std::vector<std::string> keys; | |
3091 | stl_wrappers::KVMap kvmap; | |
3092 | ||
3093 | TableConstructor c(BytewiseComparator()); | |
3094 | std::string user_key = "k04"; | |
3095 | InternalKey internal_key(user_key, 0, kTypeValue); | |
3096 | std::string encoded_key = internal_key.Encode().ToString(); | |
3097 | c.Add(encoded_key, "hello"); | |
3098 | ImmutableCFOptions ioptions(options); | |
11fdf7f2 | 3099 | MutableCFOptions moptions(options); |
7c673cae | 3100 | // Generate table with filter policy |
11fdf7f2 | 3101 | c.Finish(options, ioptions, moptions, table_options, |
7c673cae FG |
3102 | GetPlainInternalComparator(options.comparator), &keys, &kvmap); |
3103 | auto reader = c.GetTableReader(); | |
3104 | PinnableSlice value; | |
f67539c2 TL |
3105 | { |
3106 | GetContext get_context(options.comparator, nullptr, nullptr, nullptr, | |
3107 | GetContext::kNotFound, user_key, &value, nullptr, | |
3108 | nullptr, true, nullptr, nullptr); | |
3109 | get_perf_context()->Reset(); | |
3110 | ASSERT_OK(reader->Get(ReadOptions(), encoded_key, &get_context, | |
3111 | moptions.prefix_extractor.get())); | |
3112 | if (index_and_filter_in_cache) { | |
3113 | // data, index and filter block | |
3114 | ASSERT_EQ(get_perf_context()->block_read_count, 3); | |
3115 | ASSERT_EQ(get_perf_context()->index_block_read_count, 1); | |
3116 | ASSERT_EQ(get_perf_context()->filter_block_read_count, 1); | |
3117 | } else { | |
3118 | // just the data block | |
3119 | ASSERT_EQ(get_perf_context()->block_read_count, 1); | |
3120 | } | |
3121 | ASSERT_EQ(get_context.State(), GetContext::kFound); | |
3122 | ASSERT_STREQ(value.data(), "hello"); | |
7c673cae | 3123 | } |
7c673cae FG |
3124 | |
3125 | // Get non-existing key | |
3126 | user_key = "does-not-exist"; | |
3127 | internal_key = InternalKey(user_key, 0, kTypeValue); | |
3128 | encoded_key = internal_key.Encode().ToString(); | |
3129 | ||
3130 | value.Reset(); | |
f67539c2 TL |
3131 | { |
3132 | GetContext get_context(options.comparator, nullptr, nullptr, nullptr, | |
7c673cae | 3133 | GetContext::kNotFound, user_key, &value, nullptr, |
f67539c2 TL |
3134 | nullptr, true, nullptr, nullptr); |
3135 | get_perf_context()->Reset(); | |
3136 | ASSERT_OK(reader->Get(ReadOptions(), encoded_key, &get_context, | |
3137 | moptions.prefix_extractor.get())); | |
3138 | ASSERT_EQ(get_context.State(), GetContext::kNotFound); | |
3139 | } | |
7c673cae FG |
3140 | |
3141 | if (index_and_filter_in_cache) { | |
3142 | if (bloom_filter_type == 0) { | |
3143 | // with block-based, we read index and then the filter | |
11fdf7f2 | 3144 | ASSERT_EQ(get_perf_context()->block_read_count, 2); |
f67539c2 TL |
3145 | ASSERT_EQ(get_perf_context()->index_block_read_count, 1); |
3146 | ASSERT_EQ(get_perf_context()->filter_block_read_count, 1); | |
7c673cae FG |
3147 | } else { |
3148 | // with full-filter, we read filter first and then we stop | |
11fdf7f2 | 3149 | ASSERT_EQ(get_perf_context()->block_read_count, 1); |
f67539c2 | 3150 | ASSERT_EQ(get_perf_context()->filter_block_read_count, 1); |
7c673cae FG |
3151 | } |
3152 | } else { | |
3153 | // filter is already in memory and it figures out that the key doesn't | |
3154 | // exist | |
11fdf7f2 | 3155 | ASSERT_EQ(get_perf_context()->block_read_count, 0); |
7c673cae FG |
3156 | } |
3157 | } | |
3158 | } | |
3159 | } | |
3160 | ||
11fdf7f2 | 3161 | TEST_P(BlockBasedTableTest, BlockCacheLeak) { |
7c673cae FG |
3162 | // Check that when we reopen a table we don't lose access to blocks already |
3163 | // in the cache. This test checks whether the Table actually makes use of the | |
3164 | // unique ID from the file. | |
3165 | ||
3166 | Options opt; | |
494da23a | 3167 | std::unique_ptr<InternalKeyComparator> ikc; |
7c673cae FG |
3168 | ikc.reset(new test::PlainInternalKeyComparator(opt.comparator)); |
3169 | opt.compression = kNoCompression; | |
11fdf7f2 | 3170 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
7c673cae FG |
3171 | table_options.block_size = 1024; |
3172 | // big enough so we don't ever lose cached values. | |
3173 | table_options.block_cache = NewLRUCache(16 * 1024 * 1024, 4); | |
3174 | opt.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
3175 | ||
3176 | TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); | |
3177 | c.Add("k01", "hello"); | |
3178 | c.Add("k02", "hello2"); | |
3179 | c.Add("k03", std::string(10000, 'x')); | |
3180 | c.Add("k04", std::string(200000, 'x')); | |
3181 | c.Add("k05", std::string(300000, 'x')); | |
3182 | c.Add("k06", "hello3"); | |
3183 | c.Add("k07", std::string(100000, 'x')); | |
3184 | std::vector<std::string> keys; | |
3185 | stl_wrappers::KVMap kvmap; | |
3186 | const ImmutableCFOptions ioptions(opt); | |
11fdf7f2 TL |
3187 | const MutableCFOptions moptions(opt); |
3188 | c.Finish(opt, ioptions, moptions, table_options, *ikc, &keys, &kvmap); | |
7c673cae | 3189 | |
494da23a | 3190 | std::unique_ptr<InternalIterator> iter( |
11fdf7f2 | 3191 | c.NewIterator(moptions.prefix_extractor.get())); |
7c673cae FG |
3192 | iter->SeekToFirst(); |
3193 | while (iter->Valid()) { | |
3194 | iter->key(); | |
3195 | iter->value(); | |
3196 | iter->Next(); | |
3197 | } | |
3198 | ASSERT_OK(iter->status()); | |
11fdf7f2 | 3199 | iter.reset(); |
7c673cae FG |
3200 | |
3201 | const ImmutableCFOptions ioptions1(opt); | |
11fdf7f2 TL |
3202 | const MutableCFOptions moptions1(opt); |
3203 | ASSERT_OK(c.Reopen(ioptions1, moptions1)); | |
7c673cae FG |
3204 | auto table_reader = dynamic_cast<BlockBasedTable*>(c.GetTableReader()); |
3205 | for (const std::string& key : keys) { | |
11fdf7f2 TL |
3206 | InternalKey ikey(key, kMaxSequenceNumber, kTypeValue); |
3207 | ASSERT_TRUE(table_reader->TEST_KeyInCache(ReadOptions(), ikey.Encode())); | |
7c673cae FG |
3208 | } |
3209 | c.ResetTableReader(); | |
3210 | ||
3211 | // rerun with different block cache | |
3212 | table_options.block_cache = NewLRUCache(16 * 1024 * 1024, 4); | |
3213 | opt.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
3214 | const ImmutableCFOptions ioptions2(opt); | |
11fdf7f2 TL |
3215 | const MutableCFOptions moptions2(opt); |
3216 | ASSERT_OK(c.Reopen(ioptions2, moptions2)); | |
7c673cae FG |
3217 | table_reader = dynamic_cast<BlockBasedTable*>(c.GetTableReader()); |
3218 | for (const std::string& key : keys) { | |
11fdf7f2 TL |
3219 | InternalKey ikey(key, kMaxSequenceNumber, kTypeValue); |
3220 | ASSERT_TRUE(!table_reader->TEST_KeyInCache(ReadOptions(), ikey.Encode())); | |
7c673cae FG |
3221 | } |
3222 | c.ResetTableReader(); | |
3223 | } | |
3224 | ||
494da23a TL |
3225 | namespace { |
3226 | class CustomMemoryAllocator : public MemoryAllocator { | |
3227 | public: | |
3228 | const char* Name() const override { return "CustomMemoryAllocator"; } | |
3229 | ||
3230 | void* Allocate(size_t size) override { | |
3231 | ++numAllocations; | |
3232 | auto ptr = new char[size + 16]; | |
3233 | memcpy(ptr, "memory_allocator_", 16); // mangle first 16 bytes | |
3234 | return reinterpret_cast<void*>(ptr + 16); | |
3235 | } | |
3236 | void Deallocate(void* p) override { | |
3237 | ++numDeallocations; | |
3238 | char* ptr = reinterpret_cast<char*>(p) - 16; | |
3239 | delete[] ptr; | |
3240 | } | |
3241 | ||
3242 | std::atomic<int> numAllocations; | |
3243 | std::atomic<int> numDeallocations; | |
3244 | }; | |
3245 | } // namespace | |
3246 | ||
3247 | TEST_P(BlockBasedTableTest, MemoryAllocator) { | |
3248 | auto custom_memory_allocator = std::make_shared<CustomMemoryAllocator>(); | |
3249 | { | |
3250 | Options opt; | |
3251 | std::unique_ptr<InternalKeyComparator> ikc; | |
3252 | ikc.reset(new test::PlainInternalKeyComparator(opt.comparator)); | |
3253 | opt.compression = kNoCompression; | |
3254 | BlockBasedTableOptions table_options; | |
3255 | table_options.block_size = 1024; | |
3256 | LRUCacheOptions lruOptions; | |
3257 | lruOptions.memory_allocator = custom_memory_allocator; | |
3258 | lruOptions.capacity = 16 * 1024 * 1024; | |
3259 | lruOptions.num_shard_bits = 4; | |
3260 | table_options.block_cache = NewLRUCache(std::move(lruOptions)); | |
3261 | opt.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
3262 | ||
3263 | TableConstructor c(BytewiseComparator(), | |
3264 | true /* convert_to_internal_key_ */); | |
3265 | c.Add("k01", "hello"); | |
3266 | c.Add("k02", "hello2"); | |
3267 | c.Add("k03", std::string(10000, 'x')); | |
3268 | c.Add("k04", std::string(200000, 'x')); | |
3269 | c.Add("k05", std::string(300000, 'x')); | |
3270 | c.Add("k06", "hello3"); | |
3271 | c.Add("k07", std::string(100000, 'x')); | |
3272 | std::vector<std::string> keys; | |
3273 | stl_wrappers::KVMap kvmap; | |
3274 | const ImmutableCFOptions ioptions(opt); | |
3275 | const MutableCFOptions moptions(opt); | |
3276 | c.Finish(opt, ioptions, moptions, table_options, *ikc, &keys, &kvmap); | |
3277 | ||
3278 | std::unique_ptr<InternalIterator> iter( | |
3279 | c.NewIterator(moptions.prefix_extractor.get())); | |
3280 | iter->SeekToFirst(); | |
3281 | while (iter->Valid()) { | |
3282 | iter->key(); | |
3283 | iter->value(); | |
3284 | iter->Next(); | |
3285 | } | |
3286 | ASSERT_OK(iter->status()); | |
3287 | } | |
3288 | ||
3289 | // out of scope, block cache should have been deleted, all allocations | |
3290 | // deallocated | |
3291 | EXPECT_EQ(custom_memory_allocator->numAllocations.load(), | |
3292 | custom_memory_allocator->numDeallocations.load()); | |
3293 | // make sure that allocations actually happened through the cache allocator | |
3294 | EXPECT_GT(custom_memory_allocator->numAllocations.load(), 0); | |
3295 | } | |
3296 | ||
f67539c2 TL |
3297 | // Test the file checksum of block based table |
3298 | TEST_P(BlockBasedTableTest, NoFileChecksum) { | |
7c673cae | 3299 | Options options; |
f67539c2 TL |
3300 | ImmutableCFOptions ioptions(options); |
3301 | MutableCFOptions moptions(options); | |
11fdf7f2 | 3302 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); |
f67539c2 TL |
3303 | std::unique_ptr<InternalKeyComparator> comparator( |
3304 | new InternalKeyComparator(BytewiseComparator())); | |
f67539c2 TL |
3305 | int level = 0; |
3306 | std::vector<std::unique_ptr<IntTblPropCollectorFactory>> | |
3307 | int_tbl_prop_collector_factories; | |
f67539c2 | 3308 | std::string column_family_name; |
7c673cae | 3309 | |
f67539c2 TL |
3310 | FileChecksumTestHelper f(true); |
3311 | f.CreateWriteableFile(); | |
3312 | std::unique_ptr<TableBuilder> builder; | |
3313 | builder.reset(ioptions.table_factory->NewTableBuilder( | |
3314 | TableBuilderOptions(ioptions, moptions, *comparator, | |
3315 | &int_tbl_prop_collector_factories, | |
3316 | options.compression, options.sample_for_compression, | |
3317 | options.compression_opts, false /* skip_filters */, | |
3318 | column_family_name, level), | |
3319 | TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, | |
3320 | f.GetFileWriter())); | |
20effc67 | 3321 | ASSERT_OK(f.ResetTableBuilder(std::move(builder))); |
f67539c2 TL |
3322 | f.AddKVtoKVMap(1000); |
3323 | f.WriteKVAndFlushTable(); | |
20effc67 TL |
3324 | ASSERT_STREQ(f.GetFileChecksumFuncName(), kUnknownFileChecksumFuncName); |
3325 | ASSERT_STREQ(f.GetFileChecksum().c_str(), kUnknownFileChecksum); | |
f67539c2 | 3326 | } |
7c673cae | 3327 | |
20effc67 TL |
3328 | TEST_P(BlockBasedTableTest, Crc32cFileChecksum) { |
3329 | FileChecksumGenCrc32cFactory* file_checksum_gen_factory = | |
3330 | new FileChecksumGenCrc32cFactory(); | |
f67539c2 | 3331 | Options options; |
20effc67 | 3332 | options.file_checksum_gen_factory.reset(file_checksum_gen_factory); |
f67539c2 TL |
3333 | ImmutableCFOptions ioptions(options); |
3334 | MutableCFOptions moptions(options); | |
3335 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); | |
3336 | std::unique_ptr<InternalKeyComparator> comparator( | |
3337 | new InternalKeyComparator(BytewiseComparator())); | |
f67539c2 TL |
3338 | int level = 0; |
3339 | std::vector<std::unique_ptr<IntTblPropCollectorFactory>> | |
3340 | int_tbl_prop_collector_factories; | |
f67539c2 TL |
3341 | std::string column_family_name; |
3342 | ||
20effc67 TL |
3343 | FileChecksumGenContext gen_context; |
3344 | gen_context.file_name = "db/tmp"; | |
3345 | std::unique_ptr<FileChecksumGenerator> checksum_crc32c_gen1 = | |
3346 | options.file_checksum_gen_factory->CreateFileChecksumGenerator( | |
3347 | gen_context); | |
f67539c2 TL |
3348 | FileChecksumTestHelper f(true); |
3349 | f.CreateWriteableFile(); | |
20effc67 | 3350 | f.SetFileChecksumGenerator(checksum_crc32c_gen1.release()); |
f67539c2 TL |
3351 | std::unique_ptr<TableBuilder> builder; |
3352 | builder.reset(ioptions.table_factory->NewTableBuilder( | |
3353 | TableBuilderOptions(ioptions, moptions, *comparator, | |
3354 | &int_tbl_prop_collector_factories, | |
3355 | options.compression, options.sample_for_compression, | |
3356 | options.compression_opts, false /* skip_filters */, | |
3357 | column_family_name, level), | |
3358 | TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, | |
3359 | f.GetFileWriter())); | |
20effc67 | 3360 | ASSERT_OK(f.ResetTableBuilder(std::move(builder))); |
f67539c2 TL |
3361 | f.AddKVtoKVMap(1000); |
3362 | f.WriteKVAndFlushTable(); | |
3363 | ASSERT_STREQ(f.GetFileChecksumFuncName(), "FileChecksumCrc32c"); | |
20effc67 TL |
3364 | |
3365 | std::unique_ptr<FileChecksumGenerator> checksum_crc32c_gen2 = | |
3366 | options.file_checksum_gen_factory->CreateFileChecksumGenerator( | |
3367 | gen_context); | |
f67539c2 | 3368 | std::string checksum; |
20effc67 | 3369 | ASSERT_OK(f.CalculateFileChecksum(checksum_crc32c_gen2.get(), &checksum)); |
f67539c2 | 3370 | ASSERT_STREQ(f.GetFileChecksum().c_str(), checksum.c_str()); |
20effc67 TL |
3371 | |
3372 | // Unit test the generator itself for schema stability | |
3373 | std::unique_ptr<FileChecksumGenerator> checksum_crc32c_gen3 = | |
3374 | options.file_checksum_gen_factory->CreateFileChecksumGenerator( | |
3375 | gen_context); | |
3376 | const char data[] = "here is some data"; | |
3377 | checksum_crc32c_gen3->Update(data, sizeof(data)); | |
3378 | checksum_crc32c_gen3->Finalize(); | |
3379 | checksum = checksum_crc32c_gen3->GetChecksum(); | |
3380 | ASSERT_STREQ(checksum.c_str(), "\345\245\277\110"); | |
7c673cae FG |
3381 | } |
3382 | ||
3383 | // Plain table is not supported in ROCKSDB_LITE | |
3384 | #ifndef ROCKSDB_LITE | |
3385 | TEST_F(PlainTableTest, BasicPlainTableProperties) { | |
3386 | PlainTableOptions plain_table_options; | |
3387 | plain_table_options.user_key_len = 8; | |
3388 | plain_table_options.bloom_bits_per_key = 8; | |
3389 | plain_table_options.hash_table_ratio = 0; | |
3390 | ||
3391 | PlainTableFactory factory(plain_table_options); | |
3392 | test::StringSink sink; | |
494da23a | 3393 | std::unique_ptr<WritableFileWriter> file_writer( |
11fdf7f2 | 3394 | test::GetWritableFileWriter(new test::StringSink(), "" /* don't care */)); |
7c673cae FG |
3395 | Options options; |
3396 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 | 3397 | const MutableCFOptions moptions(options); |
7c673cae FG |
3398 | InternalKeyComparator ikc(options.comparator); |
3399 | std::vector<std::unique_ptr<IntTblPropCollectorFactory>> | |
3400 | int_tbl_prop_collector_factories; | |
3401 | std::string column_family_name; | |
3402 | int unknown_level = -1; | |
3403 | std::unique_ptr<TableBuilder> builder(factory.NewTableBuilder( | |
11fdf7f2 TL |
3404 | TableBuilderOptions( |
3405 | ioptions, moptions, ikc, &int_tbl_prop_collector_factories, | |
494da23a | 3406 | kNoCompression, 0 /* sample_for_compression */, CompressionOptions(), |
11fdf7f2 | 3407 | false /* skip_filters */, column_family_name, unknown_level), |
7c673cae FG |
3408 | TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, |
3409 | file_writer.get())); | |
3410 | ||
3411 | for (char c = 'a'; c <= 'z'; ++c) { | |
3412 | std::string key(8, c); | |
3413 | key.append("\1 "); // PlainTable expects internal key structure | |
3414 | std::string value(28, c + 42); | |
3415 | builder->Add(key, value); | |
3416 | } | |
3417 | ASSERT_OK(builder->Finish()); | |
3418 | file_writer->Flush(); | |
3419 | ||
3420 | test::StringSink* ss = | |
f67539c2 | 3421 | ROCKSDB_NAMESPACE::test::GetStringSinkFromLegacyWriter(file_writer.get()); |
494da23a | 3422 | std::unique_ptr<RandomAccessFileReader> file_reader( |
7c673cae FG |
3423 | test::GetRandomAccessFileReader( |
3424 | new test::StringSource(ss->contents(), 72242, true))); | |
3425 | ||
3426 | TableProperties* props = nullptr; | |
3427 | auto s = ReadTableProperties(file_reader.get(), ss->contents().size(), | |
3428 | kPlainTableMagicNumber, ioptions, | |
11fdf7f2 | 3429 | &props, true /* compression_type_missing */); |
7c673cae FG |
3430 | std::unique_ptr<TableProperties> props_guard(props); |
3431 | ASSERT_OK(s); | |
3432 | ||
3433 | ASSERT_EQ(0ul, props->index_size); | |
3434 | ASSERT_EQ(0ul, props->filter_size); | |
3435 | ASSERT_EQ(16ul * 26, props->raw_key_size); | |
3436 | ASSERT_EQ(28ul * 26, props->raw_value_size); | |
3437 | ASSERT_EQ(26ul, props->num_entries); | |
3438 | ASSERT_EQ(1ul, props->num_data_blocks); | |
3439 | } | |
f67539c2 TL |
3440 | |
3441 | TEST_F(PlainTableTest, NoFileChecksum) { | |
3442 | PlainTableOptions plain_table_options; | |
3443 | plain_table_options.user_key_len = 20; | |
3444 | plain_table_options.bloom_bits_per_key = 8; | |
3445 | plain_table_options.hash_table_ratio = 0; | |
3446 | PlainTableFactory factory(plain_table_options); | |
3447 | ||
3448 | Options options; | |
3449 | const ImmutableCFOptions ioptions(options); | |
3450 | const MutableCFOptions moptions(options); | |
3451 | InternalKeyComparator ikc(options.comparator); | |
3452 | std::vector<std::unique_ptr<IntTblPropCollectorFactory>> | |
3453 | int_tbl_prop_collector_factories; | |
3454 | std::string column_family_name; | |
3455 | int unknown_level = -1; | |
3456 | FileChecksumTestHelper f(true); | |
3457 | f.CreateWriteableFile(); | |
3458 | ||
3459 | std::unique_ptr<TableBuilder> builder(factory.NewTableBuilder( | |
3460 | TableBuilderOptions( | |
3461 | ioptions, moptions, ikc, &int_tbl_prop_collector_factories, | |
3462 | kNoCompression, 0 /* sample_for_compression */, CompressionOptions(), | |
3463 | false /* skip_filters */, column_family_name, unknown_level), | |
3464 | TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, | |
3465 | f.GetFileWriter())); | |
20effc67 | 3466 | ASSERT_OK(f.ResetTableBuilder(std::move(builder))); |
f67539c2 TL |
3467 | f.AddKVtoKVMap(1000); |
3468 | f.WriteKVAndFlushTable(); | |
20effc67 TL |
3469 | ASSERT_STREQ(f.GetFileChecksumFuncName(), kUnknownFileChecksumFuncName); |
3470 | EXPECT_EQ(f.GetFileChecksum(), kUnknownFileChecksum); | |
f67539c2 TL |
3471 | } |
3472 | ||
20effc67 | 3473 | TEST_F(PlainTableTest, Crc32cFileChecksum) { |
f67539c2 TL |
3474 | PlainTableOptions plain_table_options; |
3475 | plain_table_options.user_key_len = 20; | |
3476 | plain_table_options.bloom_bits_per_key = 8; | |
3477 | plain_table_options.hash_table_ratio = 0; | |
3478 | PlainTableFactory factory(plain_table_options); | |
3479 | ||
20effc67 TL |
3480 | FileChecksumGenCrc32cFactory* file_checksum_gen_factory = |
3481 | new FileChecksumGenCrc32cFactory(); | |
f67539c2 | 3482 | Options options; |
20effc67 | 3483 | options.file_checksum_gen_factory.reset(file_checksum_gen_factory); |
f67539c2 TL |
3484 | const ImmutableCFOptions ioptions(options); |
3485 | const MutableCFOptions moptions(options); | |
3486 | InternalKeyComparator ikc(options.comparator); | |
3487 | std::vector<std::unique_ptr<IntTblPropCollectorFactory>> | |
3488 | int_tbl_prop_collector_factories; | |
3489 | std::string column_family_name; | |
3490 | int unknown_level = -1; | |
20effc67 TL |
3491 | |
3492 | FileChecksumGenContext gen_context; | |
3493 | gen_context.file_name = "db/tmp"; | |
3494 | std::unique_ptr<FileChecksumGenerator> checksum_crc32c_gen1 = | |
3495 | options.file_checksum_gen_factory->CreateFileChecksumGenerator( | |
3496 | gen_context); | |
f67539c2 TL |
3497 | FileChecksumTestHelper f(true); |
3498 | f.CreateWriteableFile(); | |
20effc67 | 3499 | f.SetFileChecksumGenerator(checksum_crc32c_gen1.release()); |
f67539c2 TL |
3500 | |
3501 | std::unique_ptr<TableBuilder> builder(factory.NewTableBuilder( | |
3502 | TableBuilderOptions( | |
3503 | ioptions, moptions, ikc, &int_tbl_prop_collector_factories, | |
3504 | kNoCompression, 0 /* sample_for_compression */, CompressionOptions(), | |
3505 | false /* skip_filters */, column_family_name, unknown_level), | |
3506 | TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, | |
3507 | f.GetFileWriter())); | |
20effc67 | 3508 | ASSERT_OK(f.ResetTableBuilder(std::move(builder))); |
f67539c2 TL |
3509 | f.AddKVtoKVMap(1000); |
3510 | f.WriteKVAndFlushTable(); | |
3511 | ASSERT_STREQ(f.GetFileChecksumFuncName(), "FileChecksumCrc32c"); | |
20effc67 TL |
3512 | |
3513 | std::unique_ptr<FileChecksumGenerator> checksum_crc32c_gen2 = | |
3514 | options.file_checksum_gen_factory->CreateFileChecksumGenerator( | |
3515 | gen_context); | |
f67539c2 | 3516 | std::string checksum; |
20effc67 | 3517 | ASSERT_OK(f.CalculateFileChecksum(checksum_crc32c_gen2.get(), &checksum)); |
f67539c2 TL |
3518 | EXPECT_STREQ(f.GetFileChecksum().c_str(), checksum.c_str()); |
3519 | } | |
3520 | ||
7c673cae FG |
3521 | #endif // !ROCKSDB_LITE |
3522 | ||
3523 | TEST_F(GeneralTableTest, ApproximateOffsetOfPlain) { | |
3524 | TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); | |
3525 | c.Add("k01", "hello"); | |
3526 | c.Add("k02", "hello2"); | |
3527 | c.Add("k03", std::string(10000, 'x')); | |
3528 | c.Add("k04", std::string(200000, 'x')); | |
3529 | c.Add("k05", std::string(300000, 'x')); | |
3530 | c.Add("k06", "hello3"); | |
3531 | c.Add("k07", std::string(100000, 'x')); | |
3532 | std::vector<std::string> keys; | |
3533 | stl_wrappers::KVMap kvmap; | |
3534 | Options options; | |
20effc67 | 3535 | options.db_host_id = ""; |
7c673cae FG |
3536 | test::PlainInternalKeyComparator internal_comparator(options.comparator); |
3537 | options.compression = kNoCompression; | |
3538 | BlockBasedTableOptions table_options; | |
3539 | table_options.block_size = 1024; | |
3540 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 TL |
3541 | const MutableCFOptions moptions(options); |
3542 | c.Finish(options, ioptions, moptions, table_options, internal_comparator, | |
7c673cae FG |
3543 | &keys, &kvmap); |
3544 | ||
3545 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("abc"), 0, 0)); | |
3546 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("k01"), 0, 0)); | |
3547 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("k01a"), 0, 0)); | |
3548 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("k02"), 0, 0)); | |
3549 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("k03"), 0, 0)); | |
3550 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("k04"), 10000, 11000)); | |
3551 | // k04 and k05 will be in two consecutive blocks, the index is | |
3552 | // an arbitrary slice between k04 and k05, either before or after k04a | |
3553 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("k04a"), 10000, 211000)); | |
3554 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("k05"), 210000, 211000)); | |
3555 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("k06"), 510000, 511000)); | |
3556 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("k07"), 510000, 511000)); | |
3557 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("xyz"), 610000, 612000)); | |
3558 | c.ResetTableReader(); | |
3559 | } | |
3560 | ||
3561 | static void DoCompressionTest(CompressionType comp) { | |
3562 | Random rnd(301); | |
3563 | TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); | |
3564 | std::string tmp; | |
3565 | c.Add("k01", "hello"); | |
3566 | c.Add("k02", test::CompressibleString(&rnd, 0.25, 10000, &tmp)); | |
3567 | c.Add("k03", "hello3"); | |
3568 | c.Add("k04", test::CompressibleString(&rnd, 0.25, 10000, &tmp)); | |
3569 | std::vector<std::string> keys; | |
3570 | stl_wrappers::KVMap kvmap; | |
3571 | Options options; | |
3572 | test::PlainInternalKeyComparator ikc(options.comparator); | |
3573 | options.compression = comp; | |
3574 | BlockBasedTableOptions table_options; | |
3575 | table_options.block_size = 1024; | |
3576 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 TL |
3577 | const MutableCFOptions moptions(options); |
3578 | c.Finish(options, ioptions, moptions, table_options, ikc, &keys, &kvmap); | |
7c673cae | 3579 | |
20effc67 TL |
3580 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("abc"), 0, 0)); |
3581 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("k01"), 0, 0)); | |
3582 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("k02"), 0, 0)); | |
3583 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("k03"), 2000, 3500)); | |
3584 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("k04"), 2000, 3500)); | |
3585 | ASSERT_TRUE(Between(c.ApproximateOffsetOf("xyz"), 4000, 7000)); | |
7c673cae FG |
3586 | c.ResetTableReader(); |
3587 | } | |
3588 | ||
3589 | TEST_F(GeneralTableTest, ApproximateOffsetOfCompressed) { | |
3590 | std::vector<CompressionType> compression_state; | |
3591 | if (!Snappy_Supported()) { | |
3592 | fprintf(stderr, "skipping snappy compression tests\n"); | |
3593 | } else { | |
3594 | compression_state.push_back(kSnappyCompression); | |
3595 | } | |
3596 | ||
3597 | if (!Zlib_Supported()) { | |
3598 | fprintf(stderr, "skipping zlib compression tests\n"); | |
3599 | } else { | |
3600 | compression_state.push_back(kZlibCompression); | |
3601 | } | |
3602 | ||
3603 | // TODO(kailiu) DoCompressionTest() doesn't work with BZip2. | |
3604 | /* | |
3605 | if (!BZip2_Supported()) { | |
3606 | fprintf(stderr, "skipping bzip2 compression tests\n"); | |
3607 | } else { | |
3608 | compression_state.push_back(kBZip2Compression); | |
3609 | } | |
3610 | */ | |
3611 | ||
3612 | if (!LZ4_Supported()) { | |
3613 | fprintf(stderr, "skipping lz4 and lz4hc compression tests\n"); | |
3614 | } else { | |
3615 | compression_state.push_back(kLZ4Compression); | |
3616 | compression_state.push_back(kLZ4HCCompression); | |
3617 | } | |
3618 | ||
3619 | if (!XPRESS_Supported()) { | |
3620 | fprintf(stderr, "skipping xpress and xpress compression tests\n"); | |
3621 | } | |
3622 | else { | |
3623 | compression_state.push_back(kXpressCompression); | |
3624 | } | |
3625 | ||
3626 | for (auto state : compression_state) { | |
3627 | DoCompressionTest(state); | |
3628 | } | |
3629 | } | |
3630 | ||
494da23a | 3631 | #ifndef ROCKSDB_VALGRIND_RUN |
20effc67 TL |
3632 | TEST_P(ParameterizedHarnessTest, RandomizedHarnessTest) { |
3633 | Random rnd(test::RandomSeed() + 5); | |
3634 | for (int num_entries = 0; num_entries < 2000; | |
3635 | num_entries += (num_entries < 50 ? 1 : 200)) { | |
3636 | for (int e = 0; e < num_entries; e++) { | |
3637 | Add(test::RandomKey(&rnd, rnd.Skewed(4)), | |
3638 | rnd.RandomString(rnd.Skewed(5))); | |
3639 | } | |
3640 | Test(&rnd); | |
3641 | } | |
7c673cae FG |
3642 | } |
3643 | ||
3644 | #ifndef ROCKSDB_LITE | |
20effc67 | 3645 | TEST_F(DBHarnessTest, RandomizedLongDB) { |
7c673cae | 3646 | Random rnd(test::RandomSeed()); |
7c673cae FG |
3647 | int num_entries = 100000; |
3648 | for (int e = 0; e < num_entries; e++) { | |
3649 | std::string v; | |
20effc67 | 3650 | Add(test::RandomKey(&rnd, rnd.Skewed(4)), rnd.RandomString(rnd.Skewed(5))); |
7c673cae FG |
3651 | } |
3652 | Test(&rnd); | |
3653 | ||
3654 | // We must have created enough data to force merging | |
3655 | int files = 0; | |
3656 | for (int level = 0; level < db()->NumberLevels(); level++) { | |
3657 | std::string value; | |
3658 | char name[100]; | |
3659 | snprintf(name, sizeof(name), "rocksdb.num-files-at-level%d", level); | |
3660 | ASSERT_TRUE(db()->GetProperty(name, &value)); | |
3661 | files += atoi(value.c_str()); | |
3662 | } | |
3663 | ASSERT_GT(files, 0); | |
3664 | } | |
3665 | #endif // ROCKSDB_LITE | |
494da23a | 3666 | #endif // ROCKSDB_VALGRIND_RUN |
7c673cae | 3667 | |
20effc67 TL |
3668 | class MemTableTest : public testing::Test { |
3669 | public: | |
3670 | MemTableTest() { | |
3671 | InternalKeyComparator cmp(BytewiseComparator()); | |
3672 | auto table_factory = std::make_shared<SkipListFactory>(); | |
3673 | options_.memtable_factory = table_factory; | |
3674 | ImmutableCFOptions ioptions(options_); | |
3675 | wb_ = new WriteBufferManager(options_.db_write_buffer_size); | |
3676 | memtable_ = new MemTable(cmp, ioptions, MutableCFOptions(options_), wb_, | |
3677 | kMaxSequenceNumber, 0 /* column_family_id */); | |
3678 | memtable_->Ref(); | |
3679 | } | |
3680 | ||
3681 | ~MemTableTest() { | |
3682 | delete memtable_->Unref(); | |
3683 | delete wb_; | |
3684 | } | |
3685 | ||
3686 | MemTable* GetMemTable() { return memtable_; } | |
3687 | ||
3688 | private: | |
3689 | MemTable* memtable_; | |
3690 | Options options_; | |
3691 | WriteBufferManager* wb_; | |
3692 | }; | |
7c673cae FG |
3693 | |
3694 | TEST_F(MemTableTest, Simple) { | |
7c673cae FG |
3695 | WriteBatch batch; |
3696 | WriteBatchInternal::SetSequence(&batch, 100); | |
20effc67 TL |
3697 | ASSERT_OK(batch.Put(std::string("k1"), std::string("v1"))); |
3698 | ASSERT_OK(batch.Put(std::string("k2"), std::string("v2"))); | |
3699 | ASSERT_OK(batch.Put(std::string("k3"), std::string("v3"))); | |
3700 | ASSERT_OK(batch.Put(std::string("largekey"), std::string("vlarge"))); | |
3701 | ASSERT_OK(batch.DeleteRange(std::string("chi"), std::string("xigua"))); | |
3702 | ASSERT_OK(batch.DeleteRange(std::string("begin"), std::string("end"))); | |
3703 | ColumnFamilyMemTablesDefault cf_mems_default(GetMemTable()); | |
7c673cae | 3704 | ASSERT_TRUE( |
f67539c2 TL |
3705 | WriteBatchInternal::InsertInto(&batch, &cf_mems_default, nullptr, nullptr) |
3706 | .ok()); | |
7c673cae FG |
3707 | |
3708 | for (int i = 0; i < 2; ++i) { | |
3709 | Arena arena; | |
3710 | ScopedArenaIterator arena_iter_guard; | |
3711 | std::unique_ptr<InternalIterator> iter_guard; | |
3712 | InternalIterator* iter; | |
3713 | if (i == 0) { | |
20effc67 | 3714 | iter = GetMemTable()->NewIterator(ReadOptions(), &arena); |
7c673cae FG |
3715 | arena_iter_guard.set(iter); |
3716 | } else { | |
20effc67 | 3717 | iter = GetMemTable()->NewRangeTombstoneIterator( |
494da23a | 3718 | ReadOptions(), kMaxSequenceNumber /* read_seq */); |
7c673cae FG |
3719 | iter_guard.reset(iter); |
3720 | } | |
3721 | if (iter == nullptr) { | |
3722 | continue; | |
3723 | } | |
3724 | iter->SeekToFirst(); | |
3725 | while (iter->Valid()) { | |
3726 | fprintf(stderr, "key: '%s' -> '%s'\n", iter->key().ToString().c_str(), | |
3727 | iter->value().ToString().c_str()); | |
3728 | iter->Next(); | |
3729 | } | |
3730 | } | |
7c673cae FG |
3731 | } |
3732 | ||
3733 | // Test the empty key | |
20effc67 TL |
3734 | TEST_P(ParameterizedHarnessTest, SimpleEmptyKey) { |
3735 | Random rnd(test::RandomSeed() + 1); | |
3736 | Add("", "v"); | |
3737 | Test(&rnd); | |
7c673cae FG |
3738 | } |
3739 | ||
20effc67 TL |
3740 | TEST_P(ParameterizedHarnessTest, SimpleSingle) { |
3741 | Random rnd(test::RandomSeed() + 2); | |
3742 | Add("abc", "v"); | |
3743 | Test(&rnd); | |
7c673cae FG |
3744 | } |
3745 | ||
20effc67 TL |
3746 | TEST_P(ParameterizedHarnessTest, SimpleMulti) { |
3747 | Random rnd(test::RandomSeed() + 3); | |
3748 | Add("abc", "v"); | |
3749 | Add("abcd", "v"); | |
3750 | Add("ac", "v2"); | |
3751 | Test(&rnd); | |
7c673cae FG |
3752 | } |
3753 | ||
20effc67 TL |
3754 | TEST_P(ParameterizedHarnessTest, SimpleSpecialKey) { |
3755 | Random rnd(test::RandomSeed() + 4); | |
3756 | Add("\xff\xff", "v3"); | |
3757 | Test(&rnd); | |
7c673cae FG |
3758 | } |
3759 | ||
20effc67 | 3760 | TEST(TableTest, FooterTests) { |
7c673cae FG |
3761 | { |
3762 | // upconvert legacy block based | |
3763 | std::string encoded; | |
3764 | Footer footer(kLegacyBlockBasedTableMagicNumber, 0); | |
3765 | BlockHandle meta_index(10, 5), index(20, 15); | |
3766 | footer.set_metaindex_handle(meta_index); | |
3767 | footer.set_index_handle(index); | |
3768 | footer.EncodeTo(&encoded); | |
3769 | Footer decoded_footer; | |
3770 | Slice encoded_slice(encoded); | |
20effc67 | 3771 | ASSERT_OK(decoded_footer.DecodeFrom(&encoded_slice)); |
7c673cae FG |
3772 | ASSERT_EQ(decoded_footer.table_magic_number(), kBlockBasedTableMagicNumber); |
3773 | ASSERT_EQ(decoded_footer.checksum(), kCRC32c); | |
3774 | ASSERT_EQ(decoded_footer.metaindex_handle().offset(), meta_index.offset()); | |
3775 | ASSERT_EQ(decoded_footer.metaindex_handle().size(), meta_index.size()); | |
3776 | ASSERT_EQ(decoded_footer.index_handle().offset(), index.offset()); | |
3777 | ASSERT_EQ(decoded_footer.index_handle().size(), index.size()); | |
3778 | ASSERT_EQ(decoded_footer.version(), 0U); | |
3779 | } | |
3780 | { | |
3781 | // xxhash block based | |
3782 | std::string encoded; | |
3783 | Footer footer(kBlockBasedTableMagicNumber, 1); | |
3784 | BlockHandle meta_index(10, 5), index(20, 15); | |
3785 | footer.set_metaindex_handle(meta_index); | |
3786 | footer.set_index_handle(index); | |
3787 | footer.set_checksum(kxxHash); | |
3788 | footer.EncodeTo(&encoded); | |
3789 | Footer decoded_footer; | |
3790 | Slice encoded_slice(encoded); | |
20effc67 | 3791 | ASSERT_OK(decoded_footer.DecodeFrom(&encoded_slice)); |
7c673cae FG |
3792 | ASSERT_EQ(decoded_footer.table_magic_number(), kBlockBasedTableMagicNumber); |
3793 | ASSERT_EQ(decoded_footer.checksum(), kxxHash); | |
3794 | ASSERT_EQ(decoded_footer.metaindex_handle().offset(), meta_index.offset()); | |
3795 | ASSERT_EQ(decoded_footer.metaindex_handle().size(), meta_index.size()); | |
3796 | ASSERT_EQ(decoded_footer.index_handle().offset(), index.offset()); | |
3797 | ASSERT_EQ(decoded_footer.index_handle().size(), index.size()); | |
3798 | ASSERT_EQ(decoded_footer.version(), 1U); | |
3799 | } | |
494da23a TL |
3800 | { |
3801 | // xxhash64 block based | |
3802 | std::string encoded; | |
3803 | Footer footer(kBlockBasedTableMagicNumber, 1); | |
3804 | BlockHandle meta_index(10, 5), index(20, 15); | |
3805 | footer.set_metaindex_handle(meta_index); | |
3806 | footer.set_index_handle(index); | |
3807 | footer.set_checksum(kxxHash64); | |
3808 | footer.EncodeTo(&encoded); | |
3809 | Footer decoded_footer; | |
3810 | Slice encoded_slice(encoded); | |
20effc67 | 3811 | ASSERT_OK(decoded_footer.DecodeFrom(&encoded_slice)); |
494da23a TL |
3812 | ASSERT_EQ(decoded_footer.table_magic_number(), kBlockBasedTableMagicNumber); |
3813 | ASSERT_EQ(decoded_footer.checksum(), kxxHash64); | |
3814 | ASSERT_EQ(decoded_footer.metaindex_handle().offset(), meta_index.offset()); | |
3815 | ASSERT_EQ(decoded_footer.metaindex_handle().size(), meta_index.size()); | |
3816 | ASSERT_EQ(decoded_footer.index_handle().offset(), index.offset()); | |
3817 | ASSERT_EQ(decoded_footer.index_handle().size(), index.size()); | |
3818 | ASSERT_EQ(decoded_footer.version(), 1U); | |
3819 | } | |
7c673cae FG |
3820 | // Plain table is not supported in ROCKSDB_LITE |
3821 | #ifndef ROCKSDB_LITE | |
3822 | { | |
3823 | // upconvert legacy plain table | |
3824 | std::string encoded; | |
3825 | Footer footer(kLegacyPlainTableMagicNumber, 0); | |
3826 | BlockHandle meta_index(10, 5), index(20, 15); | |
3827 | footer.set_metaindex_handle(meta_index); | |
3828 | footer.set_index_handle(index); | |
3829 | footer.EncodeTo(&encoded); | |
3830 | Footer decoded_footer; | |
3831 | Slice encoded_slice(encoded); | |
20effc67 | 3832 | ASSERT_OK(decoded_footer.DecodeFrom(&encoded_slice)); |
7c673cae FG |
3833 | ASSERT_EQ(decoded_footer.table_magic_number(), kPlainTableMagicNumber); |
3834 | ASSERT_EQ(decoded_footer.checksum(), kCRC32c); | |
3835 | ASSERT_EQ(decoded_footer.metaindex_handle().offset(), meta_index.offset()); | |
3836 | ASSERT_EQ(decoded_footer.metaindex_handle().size(), meta_index.size()); | |
3837 | ASSERT_EQ(decoded_footer.index_handle().offset(), index.offset()); | |
3838 | ASSERT_EQ(decoded_footer.index_handle().size(), index.size()); | |
3839 | ASSERT_EQ(decoded_footer.version(), 0U); | |
3840 | } | |
3841 | { | |
3842 | // xxhash block based | |
3843 | std::string encoded; | |
3844 | Footer footer(kPlainTableMagicNumber, 1); | |
3845 | BlockHandle meta_index(10, 5), index(20, 15); | |
3846 | footer.set_metaindex_handle(meta_index); | |
3847 | footer.set_index_handle(index); | |
3848 | footer.set_checksum(kxxHash); | |
3849 | footer.EncodeTo(&encoded); | |
3850 | Footer decoded_footer; | |
3851 | Slice encoded_slice(encoded); | |
20effc67 | 3852 | ASSERT_OK(decoded_footer.DecodeFrom(&encoded_slice)); |
7c673cae FG |
3853 | ASSERT_EQ(decoded_footer.table_magic_number(), kPlainTableMagicNumber); |
3854 | ASSERT_EQ(decoded_footer.checksum(), kxxHash); | |
3855 | ASSERT_EQ(decoded_footer.metaindex_handle().offset(), meta_index.offset()); | |
3856 | ASSERT_EQ(decoded_footer.metaindex_handle().size(), meta_index.size()); | |
3857 | ASSERT_EQ(decoded_footer.index_handle().offset(), index.offset()); | |
3858 | ASSERT_EQ(decoded_footer.index_handle().size(), index.size()); | |
3859 | ASSERT_EQ(decoded_footer.version(), 1U); | |
3860 | } | |
3861 | #endif // !ROCKSDB_LITE | |
3862 | { | |
3863 | // version == 2 | |
3864 | std::string encoded; | |
3865 | Footer footer(kBlockBasedTableMagicNumber, 2); | |
3866 | BlockHandle meta_index(10, 5), index(20, 15); | |
3867 | footer.set_metaindex_handle(meta_index); | |
3868 | footer.set_index_handle(index); | |
3869 | footer.EncodeTo(&encoded); | |
3870 | Footer decoded_footer; | |
3871 | Slice encoded_slice(encoded); | |
20effc67 | 3872 | ASSERT_OK(decoded_footer.DecodeFrom(&encoded_slice)); |
7c673cae FG |
3873 | ASSERT_EQ(decoded_footer.table_magic_number(), kBlockBasedTableMagicNumber); |
3874 | ASSERT_EQ(decoded_footer.checksum(), kCRC32c); | |
3875 | ASSERT_EQ(decoded_footer.metaindex_handle().offset(), meta_index.offset()); | |
3876 | ASSERT_EQ(decoded_footer.metaindex_handle().size(), meta_index.size()); | |
3877 | ASSERT_EQ(decoded_footer.index_handle().offset(), index.offset()); | |
3878 | ASSERT_EQ(decoded_footer.index_handle().size(), index.size()); | |
3879 | ASSERT_EQ(decoded_footer.version(), 2U); | |
3880 | } | |
3881 | } | |
3882 | ||
3883 | class IndexBlockRestartIntervalTest | |
11fdf7f2 TL |
3884 | : public TableTest, |
3885 | public ::testing::WithParamInterface<std::pair<int, bool>> { | |
7c673cae | 3886 | public: |
11fdf7f2 TL |
3887 | static std::vector<std::pair<int, bool>> GetRestartValues() { |
3888 | return {{-1, false}, {0, false}, {1, false}, {8, false}, | |
3889 | {16, false}, {32, false}, {-1, true}, {0, true}, | |
3890 | {1, true}, {8, true}, {16, true}, {32, true}}; | |
3891 | } | |
7c673cae FG |
3892 | }; |
3893 | ||
3894 | INSTANTIATE_TEST_CASE_P( | |
3895 | IndexBlockRestartIntervalTest, IndexBlockRestartIntervalTest, | |
3896 | ::testing::ValuesIn(IndexBlockRestartIntervalTest::GetRestartValues())); | |
3897 | ||
3898 | TEST_P(IndexBlockRestartIntervalTest, IndexBlockRestartInterval) { | |
3899 | const int kKeysInTable = 10000; | |
3900 | const int kKeySize = 100; | |
3901 | const int kValSize = 500; | |
3902 | ||
11fdf7f2 TL |
3903 | const int index_block_restart_interval = std::get<0>(GetParam()); |
3904 | const bool value_delta_encoding = std::get<1>(GetParam()); | |
7c673cae FG |
3905 | |
3906 | Options options; | |
3907 | BlockBasedTableOptions table_options; | |
3908 | table_options.block_size = 64; // small block size to get big index block | |
3909 | table_options.index_block_restart_interval = index_block_restart_interval; | |
11fdf7f2 TL |
3910 | if (value_delta_encoding) { |
3911 | table_options.format_version = 4; | |
3912 | } | |
7c673cae FG |
3913 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); |
3914 | ||
3915 | TableConstructor c(BytewiseComparator()); | |
3916 | static Random rnd(301); | |
3917 | for (int i = 0; i < kKeysInTable; i++) { | |
20effc67 TL |
3918 | InternalKey k(rnd.RandomString(kKeySize), 0, kTypeValue); |
3919 | c.Add(k.Encode().ToString(), rnd.RandomString(kValSize)); | |
7c673cae FG |
3920 | } |
3921 | ||
3922 | std::vector<std::string> keys; | |
3923 | stl_wrappers::KVMap kvmap; | |
3924 | std::unique_ptr<InternalKeyComparator> comparator( | |
3925 | new InternalKeyComparator(BytewiseComparator())); | |
3926 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 TL |
3927 | const MutableCFOptions moptions(options); |
3928 | c.Finish(options, ioptions, moptions, table_options, *comparator, &keys, | |
3929 | &kvmap); | |
7c673cae FG |
3930 | auto reader = c.GetTableReader(); |
3931 | ||
20effc67 | 3932 | ReadOptions read_options; |
f67539c2 | 3933 | std::unique_ptr<InternalIterator> db_iter(reader->NewIterator( |
20effc67 | 3934 | read_options, moptions.prefix_extractor.get(), /*arena=*/nullptr, |
f67539c2 | 3935 | /*skip_filters=*/false, TableReaderCaller::kUncategorized)); |
7c673cae FG |
3936 | |
3937 | // Test point lookup | |
3938 | for (auto& kv : kvmap) { | |
3939 | db_iter->Seek(kv.first); | |
3940 | ||
3941 | ASSERT_TRUE(db_iter->Valid()); | |
3942 | ASSERT_OK(db_iter->status()); | |
3943 | ASSERT_EQ(db_iter->key(), kv.first); | |
3944 | ASSERT_EQ(db_iter->value(), kv.second); | |
3945 | } | |
3946 | ||
3947 | // Test iterating | |
3948 | auto kv_iter = kvmap.begin(); | |
3949 | for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) { | |
3950 | ASSERT_EQ(db_iter->key(), kv_iter->first); | |
3951 | ASSERT_EQ(db_iter->value(), kv_iter->second); | |
3952 | kv_iter++; | |
3953 | } | |
3954 | ASSERT_EQ(kv_iter, kvmap.end()); | |
3955 | c.ResetTableReader(); | |
3956 | } | |
3957 | ||
3958 | class PrefixTest : public testing::Test { | |
3959 | public: | |
3960 | PrefixTest() : testing::Test() {} | |
494da23a | 3961 | ~PrefixTest() override {} |
7c673cae FG |
3962 | }; |
3963 | ||
3964 | namespace { | |
3965 | // A simple PrefixExtractor that only works for test PrefixAndWholeKeyTest | |
f67539c2 | 3966 | class TestPrefixExtractor : public ROCKSDB_NAMESPACE::SliceTransform { |
7c673cae FG |
3967 | public: |
3968 | ~TestPrefixExtractor() override{}; | |
3969 | const char* Name() const override { return "TestPrefixExtractor"; } | |
3970 | ||
f67539c2 TL |
3971 | ROCKSDB_NAMESPACE::Slice Transform( |
3972 | const ROCKSDB_NAMESPACE::Slice& src) const override { | |
7c673cae | 3973 | assert(IsValid(src)); |
f67539c2 | 3974 | return ROCKSDB_NAMESPACE::Slice(src.data(), 3); |
7c673cae FG |
3975 | } |
3976 | ||
f67539c2 | 3977 | bool InDomain(const ROCKSDB_NAMESPACE::Slice& src) const override { |
7c673cae FG |
3978 | assert(IsValid(src)); |
3979 | return true; | |
3980 | } | |
3981 | ||
f67539c2 TL |
3982 | bool InRange(const ROCKSDB_NAMESPACE::Slice& /*dst*/) const override { |
3983 | return true; | |
3984 | } | |
7c673cae | 3985 | |
f67539c2 | 3986 | bool IsValid(const ROCKSDB_NAMESPACE::Slice& src) const { |
7c673cae FG |
3987 | if (src.size() != 4) { |
3988 | return false; | |
3989 | } | |
3990 | if (src[0] != '[') { | |
3991 | return false; | |
3992 | } | |
3993 | if (src[1] < '0' || src[1] > '9') { | |
3994 | return false; | |
3995 | } | |
3996 | if (src[2] != ']') { | |
3997 | return false; | |
3998 | } | |
3999 | if (src[3] < '0' || src[3] > '9') { | |
4000 | return false; | |
4001 | } | |
4002 | return true; | |
4003 | } | |
4004 | }; | |
4005 | } // namespace | |
4006 | ||
4007 | TEST_F(PrefixTest, PrefixAndWholeKeyTest) { | |
f67539c2 TL |
4008 | ROCKSDB_NAMESPACE::Options options; |
4009 | options.compaction_style = ROCKSDB_NAMESPACE::kCompactionStyleUniversal; | |
7c673cae FG |
4010 | options.num_levels = 20; |
4011 | options.create_if_missing = true; | |
4012 | options.optimize_filters_for_hits = false; | |
4013 | options.target_file_size_base = 268435456; | |
4014 | options.prefix_extractor = std::make_shared<TestPrefixExtractor>(); | |
f67539c2 TL |
4015 | ROCKSDB_NAMESPACE::BlockBasedTableOptions bbto; |
4016 | bbto.filter_policy.reset(ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10)); | |
7c673cae FG |
4017 | bbto.block_size = 262144; |
4018 | bbto.whole_key_filtering = true; | |
4019 | ||
11fdf7f2 | 4020 | const std::string kDBPath = test::PerThreadDBPath("table_prefix_test"); |
7c673cae FG |
4021 | options.table_factory.reset(NewBlockBasedTableFactory(bbto)); |
4022 | DestroyDB(kDBPath, options); | |
f67539c2 TL |
4023 | ROCKSDB_NAMESPACE::DB* db; |
4024 | ASSERT_OK(ROCKSDB_NAMESPACE::DB::Open(options, kDBPath, &db)); | |
7c673cae FG |
4025 | |
4026 | // Create a bunch of keys with 10 filters. | |
4027 | for (int i = 0; i < 10; i++) { | |
4028 | std::string prefix = "[" + std::to_string(i) + "]"; | |
4029 | for (int j = 0; j < 10; j++) { | |
4030 | std::string key = prefix + std::to_string(j); | |
20effc67 | 4031 | ASSERT_OK(db->Put(ROCKSDB_NAMESPACE::WriteOptions(), key, "1")); |
7c673cae FG |
4032 | } |
4033 | } | |
4034 | ||
4035 | // Trigger compaction. | |
20effc67 | 4036 | ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr)); |
7c673cae FG |
4037 | delete db; |
4038 | // In the second round, turn whole_key_filtering off and expect | |
4039 | // rocksdb still works. | |
4040 | } | |
4041 | ||
11fdf7f2 TL |
4042 | /* |
4043 | * Disable TableWithGlobalSeqno since RocksDB does not store global_seqno in | |
4044 | * the SST file any more. Instead, RocksDB deduces global_seqno from the | |
4045 | * MANIFEST while reading from an SST. Therefore, it's not possible to test the | |
4046 | * functionality of global_seqno in a single, isolated unit test without the | |
4047 | * involvement of Version, VersionSet, etc. | |
4048 | */ | |
4049 | TEST_P(BlockBasedTableTest, DISABLED_TableWithGlobalSeqno) { | |
4050 | BlockBasedTableOptions bbto = GetBlockBasedTableOptions(); | |
7c673cae | 4051 | test::StringSink* sink = new test::StringSink(); |
494da23a | 4052 | std::unique_ptr<WritableFileWriter> file_writer( |
11fdf7f2 | 4053 | test::GetWritableFileWriter(sink, "" /* don't care */)); |
7c673cae FG |
4054 | Options options; |
4055 | options.table_factory.reset(NewBlockBasedTableFactory(bbto)); | |
4056 | const ImmutableCFOptions ioptions(options); | |
11fdf7f2 | 4057 | const MutableCFOptions moptions(options); |
7c673cae FG |
4058 | InternalKeyComparator ikc(options.comparator); |
4059 | std::vector<std::unique_ptr<IntTblPropCollectorFactory>> | |
4060 | int_tbl_prop_collector_factories; | |
4061 | int_tbl_prop_collector_factories.emplace_back( | |
4062 | new SstFileWriterPropertiesCollectorFactory(2 /* version */, | |
4063 | 0 /* global_seqno*/)); | |
4064 | std::string column_family_name; | |
4065 | std::unique_ptr<TableBuilder> builder(options.table_factory->NewTableBuilder( | |
11fdf7f2 TL |
4066 | TableBuilderOptions(ioptions, moptions, ikc, |
4067 | &int_tbl_prop_collector_factories, kNoCompression, | |
494da23a | 4068 | 0 /* sample_for_compression */, CompressionOptions(), |
7c673cae FG |
4069 | false /* skip_filters */, column_family_name, -1), |
4070 | TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, | |
4071 | file_writer.get())); | |
4072 | ||
4073 | for (char c = 'a'; c <= 'z'; ++c) { | |
4074 | std::string key(8, c); | |
4075 | std::string value = key; | |
4076 | InternalKey ik(key, 0, kTypeValue); | |
4077 | ||
4078 | builder->Add(ik.Encode(), value); | |
4079 | } | |
4080 | ASSERT_OK(builder->Finish()); | |
4081 | file_writer->Flush(); | |
4082 | ||
4083 | test::RandomRWStringSink ss_rw(sink); | |
4084 | uint32_t version; | |
4085 | uint64_t global_seqno; | |
4086 | uint64_t global_seqno_offset; | |
4087 | ||
4088 | // Helper function to get version, global_seqno, global_seqno_offset | |
4089 | std::function<void()> GetVersionAndGlobalSeqno = [&]() { | |
494da23a | 4090 | std::unique_ptr<RandomAccessFileReader> file_reader( |
7c673cae FG |
4091 | test::GetRandomAccessFileReader( |
4092 | new test::StringSource(ss_rw.contents(), 73342, true))); | |
4093 | ||
4094 | TableProperties* props = nullptr; | |
4095 | ASSERT_OK(ReadTableProperties(file_reader.get(), ss_rw.contents().size(), | |
4096 | kBlockBasedTableMagicNumber, ioptions, | |
11fdf7f2 | 4097 | &props, true /* compression_type_missing */)); |
7c673cae FG |
4098 | |
4099 | UserCollectedProperties user_props = props->user_collected_properties; | |
4100 | version = DecodeFixed32( | |
4101 | user_props[ExternalSstFilePropertyNames::kVersion].c_str()); | |
4102 | global_seqno = DecodeFixed64( | |
4103 | user_props[ExternalSstFilePropertyNames::kGlobalSeqno].c_str()); | |
4104 | global_seqno_offset = | |
4105 | props->properties_offsets[ExternalSstFilePropertyNames::kGlobalSeqno]; | |
4106 | ||
4107 | delete props; | |
4108 | }; | |
4109 | ||
4110 | // Helper function to update the value of the global seqno in the file | |
4111 | std::function<void(uint64_t)> SetGlobalSeqno = [&](uint64_t val) { | |
4112 | std::string new_global_seqno; | |
4113 | PutFixed64(&new_global_seqno, val); | |
4114 | ||
4115 | ASSERT_OK(ss_rw.Write(global_seqno_offset, new_global_seqno)); | |
4116 | }; | |
4117 | ||
4118 | // Helper function to get the contents of the table InternalIterator | |
494da23a | 4119 | std::unique_ptr<TableReader> table_reader; |
20effc67 | 4120 | const ReadOptions read_options; |
7c673cae | 4121 | std::function<InternalIterator*()> GetTableInternalIter = [&]() { |
494da23a | 4122 | std::unique_ptr<RandomAccessFileReader> file_reader( |
7c673cae FG |
4123 | test::GetRandomAccessFileReader( |
4124 | new test::StringSource(ss_rw.contents(), 73342, true))); | |
4125 | ||
4126 | options.table_factory->NewTableReader( | |
11fdf7f2 TL |
4127 | TableReaderOptions(ioptions, moptions.prefix_extractor.get(), |
4128 | EnvOptions(), ikc), | |
4129 | std::move(file_reader), ss_rw.contents().size(), &table_reader); | |
7c673cae | 4130 | |
f67539c2 | 4131 | return table_reader->NewIterator( |
20effc67 | 4132 | read_options, moptions.prefix_extractor.get(), /*arena=*/nullptr, |
f67539c2 | 4133 | /*skip_filters=*/false, TableReaderCaller::kUncategorized); |
7c673cae FG |
4134 | }; |
4135 | ||
4136 | GetVersionAndGlobalSeqno(); | |
f67539c2 TL |
4137 | ASSERT_EQ(2u, version); |
4138 | ASSERT_EQ(0u, global_seqno); | |
7c673cae FG |
4139 | |
4140 | InternalIterator* iter = GetTableInternalIter(); | |
4141 | char current_c = 'a'; | |
4142 | for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { | |
4143 | ParsedInternalKey pik; | |
20effc67 | 4144 | ASSERT_OK(ParseInternalKey(iter->key(), &pik, true /* log_err_key */)); |
7c673cae FG |
4145 | |
4146 | ASSERT_EQ(pik.type, ValueType::kTypeValue); | |
4147 | ASSERT_EQ(pik.sequence, 0); | |
4148 | ASSERT_EQ(pik.user_key, iter->value()); | |
4149 | ASSERT_EQ(pik.user_key.ToString(), std::string(8, current_c)); | |
4150 | current_c++; | |
4151 | } | |
4152 | ASSERT_EQ(current_c, 'z' + 1); | |
4153 | delete iter; | |
4154 | ||
4155 | // Update global sequence number to 10 | |
4156 | SetGlobalSeqno(10); | |
4157 | GetVersionAndGlobalSeqno(); | |
f67539c2 TL |
4158 | ASSERT_EQ(2u, version); |
4159 | ASSERT_EQ(10u, global_seqno); | |
7c673cae FG |
4160 | |
4161 | iter = GetTableInternalIter(); | |
4162 | current_c = 'a'; | |
4163 | for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { | |
4164 | ParsedInternalKey pik; | |
20effc67 | 4165 | ASSERT_OK(ParseInternalKey(iter->key(), &pik, true /* log_err_key */)); |
7c673cae FG |
4166 | |
4167 | ASSERT_EQ(pik.type, ValueType::kTypeValue); | |
4168 | ASSERT_EQ(pik.sequence, 10); | |
4169 | ASSERT_EQ(pik.user_key, iter->value()); | |
4170 | ASSERT_EQ(pik.user_key.ToString(), std::string(8, current_c)); | |
4171 | current_c++; | |
4172 | } | |
4173 | ASSERT_EQ(current_c, 'z' + 1); | |
4174 | ||
4175 | // Verify Seek | |
4176 | for (char c = 'a'; c <= 'z'; c++) { | |
4177 | std::string k = std::string(8, c); | |
4178 | InternalKey ik(k, 10, kValueTypeForSeek); | |
4179 | iter->Seek(ik.Encode()); | |
4180 | ASSERT_TRUE(iter->Valid()); | |
4181 | ||
4182 | ParsedInternalKey pik; | |
20effc67 | 4183 | ASSERT_OK(ParseInternalKey(iter->key(), &pik, true /* log_err_key */)); |
7c673cae FG |
4184 | |
4185 | ASSERT_EQ(pik.type, ValueType::kTypeValue); | |
4186 | ASSERT_EQ(pik.sequence, 10); | |
4187 | ASSERT_EQ(pik.user_key.ToString(), k); | |
4188 | ASSERT_EQ(iter->value().ToString(), k); | |
4189 | } | |
4190 | delete iter; | |
4191 | ||
4192 | // Update global sequence number to 3 | |
4193 | SetGlobalSeqno(3); | |
4194 | GetVersionAndGlobalSeqno(); | |
f67539c2 TL |
4195 | ASSERT_EQ(2u, version); |
4196 | ASSERT_EQ(3u, global_seqno); | |
7c673cae FG |
4197 | |
4198 | iter = GetTableInternalIter(); | |
4199 | current_c = 'a'; | |
4200 | for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { | |
4201 | ParsedInternalKey pik; | |
20effc67 | 4202 | ASSERT_OK(ParseInternalKey(iter->key(), &pik, true /* log_err_key */)); |
7c673cae FG |
4203 | |
4204 | ASSERT_EQ(pik.type, ValueType::kTypeValue); | |
4205 | ASSERT_EQ(pik.sequence, 3); | |
4206 | ASSERT_EQ(pik.user_key, iter->value()); | |
4207 | ASSERT_EQ(pik.user_key.ToString(), std::string(8, current_c)); | |
4208 | current_c++; | |
4209 | } | |
4210 | ASSERT_EQ(current_c, 'z' + 1); | |
4211 | ||
4212 | // Verify Seek | |
4213 | for (char c = 'a'; c <= 'z'; c++) { | |
4214 | std::string k = std::string(8, c); | |
4215 | // seqno=4 is less than 3 so we still should get our key | |
4216 | InternalKey ik(k, 4, kValueTypeForSeek); | |
4217 | iter->Seek(ik.Encode()); | |
4218 | ASSERT_TRUE(iter->Valid()); | |
4219 | ||
4220 | ParsedInternalKey pik; | |
20effc67 | 4221 | ASSERT_OK(ParseInternalKey(iter->key(), &pik, true /* log_err_key */)); |
7c673cae FG |
4222 | |
4223 | ASSERT_EQ(pik.type, ValueType::kTypeValue); | |
4224 | ASSERT_EQ(pik.sequence, 3); | |
4225 | ASSERT_EQ(pik.user_key.ToString(), k); | |
4226 | ASSERT_EQ(iter->value().ToString(), k); | |
4227 | } | |
4228 | ||
4229 | delete iter; | |
4230 | } | |
4231 | ||
11fdf7f2 TL |
4232 | TEST_P(BlockBasedTableTest, BlockAlignTest) { |
4233 | BlockBasedTableOptions bbto = GetBlockBasedTableOptions(); | |
4234 | bbto.block_align = true; | |
4235 | test::StringSink* sink = new test::StringSink(); | |
494da23a | 4236 | std::unique_ptr<WritableFileWriter> file_writer( |
11fdf7f2 TL |
4237 | test::GetWritableFileWriter(sink, "" /* don't care */)); |
4238 | Options options; | |
4239 | options.compression = kNoCompression; | |
4240 | options.table_factory.reset(NewBlockBasedTableFactory(bbto)); | |
4241 | const ImmutableCFOptions ioptions(options); | |
4242 | const MutableCFOptions moptions(options); | |
4243 | InternalKeyComparator ikc(options.comparator); | |
4244 | std::vector<std::unique_ptr<IntTblPropCollectorFactory>> | |
4245 | int_tbl_prop_collector_factories; | |
4246 | std::string column_family_name; | |
4247 | std::unique_ptr<TableBuilder> builder(options.table_factory->NewTableBuilder( | |
4248 | TableBuilderOptions(ioptions, moptions, ikc, | |
4249 | &int_tbl_prop_collector_factories, kNoCompression, | |
494da23a | 4250 | 0 /* sample_for_compression */, CompressionOptions(), |
11fdf7f2 TL |
4251 | false /* skip_filters */, column_family_name, -1), |
4252 | TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, | |
4253 | file_writer.get())); | |
4254 | ||
4255 | for (int i = 1; i <= 10000; ++i) { | |
4256 | std::ostringstream ostr; | |
4257 | ostr << std::setfill('0') << std::setw(5) << i; | |
4258 | std::string key = ostr.str(); | |
4259 | std::string value = "val"; | |
4260 | InternalKey ik(key, 0, kTypeValue); | |
4261 | ||
4262 | builder->Add(ik.Encode(), value); | |
4263 | } | |
4264 | ASSERT_OK(builder->Finish()); | |
4265 | file_writer->Flush(); | |
4266 | ||
4267 | test::RandomRWStringSink ss_rw(sink); | |
494da23a | 4268 | std::unique_ptr<RandomAccessFileReader> file_reader( |
11fdf7f2 TL |
4269 | test::GetRandomAccessFileReader( |
4270 | new test::StringSource(ss_rw.contents(), 73342, true))); | |
4271 | ||
4272 | // Helper function to get version, global_seqno, global_seqno_offset | |
4273 | std::function<void()> VerifyBlockAlignment = [&]() { | |
4274 | TableProperties* props = nullptr; | |
4275 | ASSERT_OK(ReadTableProperties(file_reader.get(), ss_rw.contents().size(), | |
4276 | kBlockBasedTableMagicNumber, ioptions, | |
4277 | &props, true /* compression_type_missing */)); | |
4278 | ||
4279 | uint64_t data_block_size = props->data_size / props->num_data_blocks; | |
4280 | ASSERT_EQ(data_block_size, 4096); | |
4281 | ASSERT_EQ(props->data_size, data_block_size * props->num_data_blocks); | |
4282 | delete props; | |
4283 | }; | |
4284 | ||
4285 | VerifyBlockAlignment(); | |
4286 | ||
4287 | // The below block of code verifies that we can read back the keys. Set | |
4288 | // block_align to false when creating the reader to ensure we can flip between | |
4289 | // the two modes without any issues | |
4290 | std::unique_ptr<TableReader> table_reader; | |
4291 | bbto.block_align = false; | |
4292 | Options options2; | |
4293 | options2.table_factory.reset(NewBlockBasedTableFactory(bbto)); | |
4294 | ImmutableCFOptions ioptions2(options2); | |
4295 | const MutableCFOptions moptions2(options2); | |
4296 | ||
4297 | ASSERT_OK(ioptions.table_factory->NewTableReader( | |
4298 | TableReaderOptions(ioptions2, moptions2.prefix_extractor.get(), | |
4299 | EnvOptions(), | |
4300 | GetPlainInternalComparator(options2.comparator)), | |
4301 | std::move(file_reader), ss_rw.contents().size(), &table_reader)); | |
4302 | ||
20effc67 | 4303 | ReadOptions read_options; |
11fdf7f2 | 4304 | std::unique_ptr<InternalIterator> db_iter(table_reader->NewIterator( |
20effc67 | 4305 | read_options, moptions2.prefix_extractor.get(), /*arena=*/nullptr, |
f67539c2 | 4306 | /*skip_filters=*/false, TableReaderCaller::kUncategorized)); |
11fdf7f2 TL |
4307 | |
4308 | int expected_key = 1; | |
4309 | for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) { | |
4310 | std::ostringstream ostr; | |
4311 | ostr << std::setfill('0') << std::setw(5) << expected_key++; | |
4312 | std::string key = ostr.str(); | |
4313 | std::string value = "val"; | |
4314 | ||
4315 | ASSERT_OK(db_iter->status()); | |
4316 | ASSERT_EQ(ExtractUserKey(db_iter->key()).ToString(), key); | |
4317 | ASSERT_EQ(db_iter->value().ToString(), value); | |
4318 | } | |
4319 | expected_key--; | |
4320 | ASSERT_EQ(expected_key, 10000); | |
4321 | table_reader.reset(); | |
4322 | } | |
4323 | ||
4324 | TEST_P(BlockBasedTableTest, PropertiesBlockRestartPointTest) { | |
4325 | BlockBasedTableOptions bbto = GetBlockBasedTableOptions(); | |
4326 | bbto.block_align = true; | |
4327 | test::StringSink* sink = new test::StringSink(); | |
494da23a | 4328 | std::unique_ptr<WritableFileWriter> file_writer( |
11fdf7f2 TL |
4329 | test::GetWritableFileWriter(sink, "" /* don't care */)); |
4330 | ||
4331 | Options options; | |
4332 | options.compression = kNoCompression; | |
4333 | options.table_factory.reset(NewBlockBasedTableFactory(bbto)); | |
4334 | ||
4335 | const ImmutableCFOptions ioptions(options); | |
4336 | const MutableCFOptions moptions(options); | |
4337 | InternalKeyComparator ikc(options.comparator); | |
4338 | std::vector<std::unique_ptr<IntTblPropCollectorFactory>> | |
4339 | int_tbl_prop_collector_factories; | |
4340 | std::string column_family_name; | |
4341 | ||
4342 | std::unique_ptr<TableBuilder> builder(options.table_factory->NewTableBuilder( | |
4343 | TableBuilderOptions(ioptions, moptions, ikc, | |
4344 | &int_tbl_prop_collector_factories, kNoCompression, | |
494da23a | 4345 | 0 /* sample_for_compression */, CompressionOptions(), |
11fdf7f2 TL |
4346 | false /* skip_filters */, column_family_name, -1), |
4347 | TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, | |
4348 | file_writer.get())); | |
4349 | ||
4350 | for (int i = 1; i <= 10000; ++i) { | |
4351 | std::ostringstream ostr; | |
4352 | ostr << std::setfill('0') << std::setw(5) << i; | |
4353 | std::string key = ostr.str(); | |
4354 | std::string value = "val"; | |
4355 | InternalKey ik(key, 0, kTypeValue); | |
4356 | ||
4357 | builder->Add(ik.Encode(), value); | |
4358 | } | |
4359 | ASSERT_OK(builder->Finish()); | |
4360 | file_writer->Flush(); | |
4361 | ||
4362 | test::RandomRWStringSink ss_rw(sink); | |
494da23a | 4363 | std::unique_ptr<RandomAccessFileReader> file_reader( |
11fdf7f2 TL |
4364 | test::GetRandomAccessFileReader( |
4365 | new test::StringSource(ss_rw.contents(), 73342, true))); | |
4366 | ||
4367 | { | |
4368 | RandomAccessFileReader* file = file_reader.get(); | |
4369 | uint64_t file_size = ss_rw.contents().size(); | |
4370 | ||
4371 | Footer footer; | |
20effc67 TL |
4372 | IOOptions opts; |
4373 | ASSERT_OK(ReadFooterFromFile(opts, file, nullptr /* prefetch_buffer */, | |
4374 | file_size, &footer, | |
4375 | kBlockBasedTableMagicNumber)); | |
11fdf7f2 | 4376 | |
f67539c2 | 4377 | auto BlockFetchHelper = [&](const BlockHandle& handle, BlockType block_type, |
11fdf7f2 TL |
4378 | BlockContents* contents) { |
4379 | ReadOptions read_options; | |
4380 | read_options.verify_checksums = false; | |
11fdf7f2 TL |
4381 | PersistentCacheOptions cache_options; |
4382 | ||
494da23a TL |
4383 | BlockFetcher block_fetcher( |
4384 | file, nullptr /* prefetch_buffer */, footer, read_options, handle, | |
4385 | contents, ioptions, false /* decompress */, | |
f67539c2 TL |
4386 | false /*maybe_compressed*/, block_type, |
4387 | UncompressionDict::GetEmptyDict(), cache_options); | |
11fdf7f2 TL |
4388 | |
4389 | ASSERT_OK(block_fetcher.ReadBlockContents()); | |
4390 | }; | |
4391 | ||
4392 | // -- Read metaindex block | |
4393 | auto metaindex_handle = footer.metaindex_handle(); | |
4394 | BlockContents metaindex_contents; | |
4395 | ||
f67539c2 TL |
4396 | BlockFetchHelper(metaindex_handle, BlockType::kMetaIndex, |
4397 | &metaindex_contents); | |
20effc67 | 4398 | Block metaindex_block(std::move(metaindex_contents)); |
11fdf7f2 | 4399 | |
f67539c2 | 4400 | std::unique_ptr<InternalIterator> meta_iter(metaindex_block.NewDataIterator( |
20effc67 | 4401 | BytewiseComparator(), kDisableGlobalSequenceNumber)); |
11fdf7f2 TL |
4402 | bool found_properties_block = true; |
4403 | ASSERT_OK(SeekToPropertiesBlock(meta_iter.get(), &found_properties_block)); | |
4404 | ASSERT_TRUE(found_properties_block); | |
4405 | ||
4406 | // -- Read properties block | |
4407 | Slice v = meta_iter->value(); | |
4408 | BlockHandle properties_handle; | |
4409 | ASSERT_OK(properties_handle.DecodeFrom(&v)); | |
4410 | BlockContents properties_contents; | |
4411 | ||
f67539c2 TL |
4412 | BlockFetchHelper(properties_handle, BlockType::kProperties, |
4413 | &properties_contents); | |
20effc67 | 4414 | Block properties_block(std::move(properties_contents)); |
11fdf7f2 | 4415 | |
f67539c2 | 4416 | ASSERT_EQ(properties_block.NumRestarts(), 1u); |
11fdf7f2 TL |
4417 | } |
4418 | } | |
4419 | ||
4420 | TEST_P(BlockBasedTableTest, PropertiesMetaBlockLast) { | |
4421 | // The properties meta-block should come at the end since we always need to | |
4422 | // read it when opening a file, unlike index/filter/other meta-blocks, which | |
4423 | // are sometimes read depending on the user's configuration. This ordering | |
4424 | // allows us to do a small readahead on the end of the file to read properties | |
4425 | // and meta-index blocks with one I/O. | |
4426 | TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); | |
4427 | c.Add("a1", "val1"); | |
4428 | c.Add("b2", "val2"); | |
4429 | c.Add("c3", "val3"); | |
4430 | c.Add("d4", "val4"); | |
4431 | c.Add("e5", "val5"); | |
4432 | c.Add("f6", "val6"); | |
4433 | c.Add("g7", "val7"); | |
4434 | c.Add("h8", "val8"); | |
4435 | c.Add("j9", "val9"); | |
4436 | ||
4437 | // write an SST file | |
4438 | Options options; | |
4439 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); | |
4440 | table_options.filter_policy.reset(NewBloomFilterPolicy( | |
4441 | 8 /* bits_per_key */, false /* use_block_based_filter */)); | |
4442 | options.table_factory.reset(NewBlockBasedTableFactory(table_options)); | |
4443 | ImmutableCFOptions ioptions(options); | |
4444 | MutableCFOptions moptions(options); | |
4445 | std::vector<std::string> keys; | |
4446 | stl_wrappers::KVMap kvmap; | |
4447 | c.Finish(options, ioptions, moptions, table_options, | |
4448 | GetPlainInternalComparator(options.comparator), &keys, &kvmap); | |
4449 | ||
4450 | // get file reader | |
4451 | test::StringSink* table_sink = c.TEST_GetSink(); | |
4452 | std::unique_ptr<RandomAccessFileReader> table_reader{ | |
4453 | test::GetRandomAccessFileReader( | |
4454 | new test::StringSource(table_sink->contents(), 0 /* unique_id */, | |
4455 | false /* allow_mmap_reads */))}; | |
4456 | size_t table_size = table_sink->contents().size(); | |
4457 | ||
4458 | // read footer | |
4459 | Footer footer; | |
20effc67 TL |
4460 | IOOptions opts; |
4461 | ASSERT_OK(ReadFooterFromFile(opts, table_reader.get(), | |
11fdf7f2 TL |
4462 | nullptr /* prefetch_buffer */, table_size, |
4463 | &footer, kBlockBasedTableMagicNumber)); | |
4464 | ||
4465 | // read metaindex | |
4466 | auto metaindex_handle = footer.metaindex_handle(); | |
4467 | BlockContents metaindex_contents; | |
11fdf7f2 TL |
4468 | PersistentCacheOptions pcache_opts; |
4469 | BlockFetcher block_fetcher( | |
4470 | table_reader.get(), nullptr /* prefetch_buffer */, footer, ReadOptions(), | |
4471 | metaindex_handle, &metaindex_contents, ioptions, false /* decompress */, | |
f67539c2 TL |
4472 | false /*maybe_compressed*/, BlockType::kMetaIndex, |
4473 | UncompressionDict::GetEmptyDict(), pcache_opts, | |
4474 | nullptr /*memory_allocator*/); | |
11fdf7f2 | 4475 | ASSERT_OK(block_fetcher.ReadBlockContents()); |
20effc67 | 4476 | Block metaindex_block(std::move(metaindex_contents)); |
11fdf7f2 TL |
4477 | |
4478 | // verify properties block comes last | |
4479 | std::unique_ptr<InternalIterator> metaindex_iter{ | |
20effc67 TL |
4480 | metaindex_block.NewDataIterator(options.comparator, |
4481 | kDisableGlobalSequenceNumber)}; | |
11fdf7f2 TL |
4482 | uint64_t max_offset = 0; |
4483 | std::string key_at_max_offset; | |
4484 | for (metaindex_iter->SeekToFirst(); metaindex_iter->Valid(); | |
4485 | metaindex_iter->Next()) { | |
4486 | BlockHandle handle; | |
4487 | Slice value = metaindex_iter->value(); | |
4488 | ASSERT_OK(handle.DecodeFrom(&value)); | |
4489 | if (handle.offset() > max_offset) { | |
4490 | max_offset = handle.offset(); | |
4491 | key_at_max_offset = metaindex_iter->key().ToString(); | |
4492 | } | |
4493 | } | |
4494 | ASSERT_EQ(kPropertiesBlock, key_at_max_offset); | |
4495 | // index handle is stored in footer rather than metaindex block, so need | |
4496 | // separate logic to verify it comes before properties block. | |
4497 | ASSERT_GT(max_offset, footer.index_handle().offset()); | |
4498 | c.ResetTableReader(); | |
4499 | } | |
4500 | ||
4501 | TEST_P(BlockBasedTableTest, BadOptions) { | |
f67539c2 | 4502 | ROCKSDB_NAMESPACE::Options options; |
11fdf7f2 TL |
4503 | options.compression = kNoCompression; |
4504 | BlockBasedTableOptions bbto = GetBlockBasedTableOptions(); | |
4505 | bbto.block_size = 4000; | |
4506 | bbto.block_align = true; | |
4507 | ||
4508 | const std::string kDBPath = | |
4509 | test::PerThreadDBPath("block_based_table_bad_options_test"); | |
4510 | options.table_factory.reset(NewBlockBasedTableFactory(bbto)); | |
4511 | DestroyDB(kDBPath, options); | |
f67539c2 TL |
4512 | ROCKSDB_NAMESPACE::DB* db; |
4513 | ASSERT_NOK(ROCKSDB_NAMESPACE::DB::Open(options, kDBPath, &db)); | |
11fdf7f2 TL |
4514 | |
4515 | bbto.block_size = 4096; | |
4516 | options.compression = kSnappyCompression; | |
4517 | options.table_factory.reset(NewBlockBasedTableFactory(bbto)); | |
f67539c2 | 4518 | ASSERT_NOK(ROCKSDB_NAMESPACE::DB::Open(options, kDBPath, &db)); |
11fdf7f2 TL |
4519 | } |
4520 | ||
4521 | TEST_F(BBTTailPrefetchTest, TestTailPrefetchStats) { | |
4522 | TailPrefetchStats tpstats; | |
4523 | ASSERT_EQ(0, tpstats.GetSuggestedPrefetchSize()); | |
4524 | tpstats.RecordEffectiveSize(size_t{1000}); | |
4525 | tpstats.RecordEffectiveSize(size_t{1005}); | |
4526 | tpstats.RecordEffectiveSize(size_t{1002}); | |
4527 | ASSERT_EQ(1005, tpstats.GetSuggestedPrefetchSize()); | |
4528 | ||
4529 | // One single super large value shouldn't influence much | |
4530 | tpstats.RecordEffectiveSize(size_t{1002000}); | |
4531 | tpstats.RecordEffectiveSize(size_t{999}); | |
4532 | ASSERT_LE(1005, tpstats.GetSuggestedPrefetchSize()); | |
4533 | ASSERT_GT(1200, tpstats.GetSuggestedPrefetchSize()); | |
4534 | ||
4535 | // Only history of 32 is kept | |
4536 | for (int i = 0; i < 32; i++) { | |
4537 | tpstats.RecordEffectiveSize(size_t{100}); | |
4538 | } | |
4539 | ASSERT_EQ(100, tpstats.GetSuggestedPrefetchSize()); | |
4540 | ||
4541 | // 16 large values and 16 small values. The result should be closer | |
4542 | // to the small value as the algorithm. | |
4543 | for (int i = 0; i < 16; i++) { | |
4544 | tpstats.RecordEffectiveSize(size_t{1000}); | |
4545 | } | |
4546 | tpstats.RecordEffectiveSize(size_t{10}); | |
4547 | tpstats.RecordEffectiveSize(size_t{20}); | |
4548 | for (int i = 0; i < 6; i++) { | |
4549 | tpstats.RecordEffectiveSize(size_t{100}); | |
4550 | } | |
4551 | ASSERT_LE(80, tpstats.GetSuggestedPrefetchSize()); | |
4552 | ASSERT_GT(200, tpstats.GetSuggestedPrefetchSize()); | |
4553 | } | |
4554 | ||
4555 | TEST_F(BBTTailPrefetchTest, FilePrefetchBufferMinOffset) { | |
4556 | TailPrefetchStats tpstats; | |
4557 | FilePrefetchBuffer buffer(nullptr, 0, 0, false, true); | |
20effc67 TL |
4558 | IOOptions opts; |
4559 | buffer.TryReadFromCache(opts, 500, 10, nullptr); | |
4560 | buffer.TryReadFromCache(opts, 480, 10, nullptr); | |
4561 | buffer.TryReadFromCache(opts, 490, 10, nullptr); | |
11fdf7f2 TL |
4562 | ASSERT_EQ(480, buffer.min_offset_read()); |
4563 | } | |
4564 | ||
4565 | TEST_P(BlockBasedTableTest, DataBlockHashIndex) { | |
4566 | const int kNumKeys = 500; | |
4567 | const int kKeySize = 8; | |
4568 | const int kValSize = 40; | |
4569 | ||
4570 | BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); | |
4571 | table_options.data_block_index_type = | |
4572 | BlockBasedTableOptions::kDataBlockBinaryAndHash; | |
4573 | ||
4574 | Options options; | |
4575 | options.comparator = BytewiseComparator(); | |
4576 | ||
4577 | options.table_factory.reset(new BlockBasedTableFactory(table_options)); | |
4578 | ||
4579 | TableConstructor c(options.comparator); | |
4580 | ||
4581 | static Random rnd(1048); | |
4582 | for (int i = 0; i < kNumKeys; i++) { | |
4583 | // padding one "0" to mark existent keys. | |
20effc67 | 4584 | std::string random_key(rnd.RandomString(kKeySize - 1) + "1"); |
11fdf7f2 | 4585 | InternalKey k(random_key, 0, kTypeValue); |
20effc67 | 4586 | c.Add(k.Encode().ToString(), rnd.RandomString(kValSize)); |
11fdf7f2 TL |
4587 | } |
4588 | ||
4589 | std::vector<std::string> keys; | |
4590 | stl_wrappers::KVMap kvmap; | |
4591 | const ImmutableCFOptions ioptions(options); | |
4592 | const MutableCFOptions moptions(options); | |
4593 | const InternalKeyComparator internal_comparator(options.comparator); | |
4594 | c.Finish(options, ioptions, moptions, table_options, internal_comparator, | |
4595 | &keys, &kvmap); | |
4596 | ||
4597 | auto reader = c.GetTableReader(); | |
4598 | ||
4599 | std::unique_ptr<InternalIterator> seek_iter; | |
20effc67 | 4600 | ReadOptions read_options; |
f67539c2 | 4601 | seek_iter.reset(reader->NewIterator( |
20effc67 | 4602 | read_options, moptions.prefix_extractor.get(), /*arena=*/nullptr, |
f67539c2 | 4603 | /*skip_filters=*/false, TableReaderCaller::kUncategorized)); |
11fdf7f2 TL |
4604 | for (int i = 0; i < 2; ++i) { |
4605 | ReadOptions ro; | |
4606 | // for every kv, we seek using two method: Get() and Seek() | |
4607 | // Get() will use the SuffixIndexHash in Block. For non-existent key it | |
4608 | // will invalidate the iterator | |
4609 | // Seek() will use the default BinarySeek() in Block. So for non-existent | |
4610 | // key it will land at the closest key that is large than target. | |
4611 | ||
4612 | // Search for existent keys | |
4613 | for (auto& kv : kvmap) { | |
4614 | if (i == 0) { | |
4615 | // Search using Seek() | |
4616 | seek_iter->Seek(kv.first); | |
4617 | ASSERT_OK(seek_iter->status()); | |
4618 | ASSERT_TRUE(seek_iter->Valid()); | |
4619 | ASSERT_EQ(seek_iter->key(), kv.first); | |
4620 | ASSERT_EQ(seek_iter->value(), kv.second); | |
4621 | } else { | |
4622 | // Search using Get() | |
4623 | PinnableSlice value; | |
4624 | std::string user_key = ExtractUserKey(kv.first).ToString(); | |
4625 | GetContext get_context(options.comparator, nullptr, nullptr, nullptr, | |
4626 | GetContext::kNotFound, user_key, &value, nullptr, | |
f67539c2 | 4627 | nullptr, true, nullptr, nullptr); |
11fdf7f2 TL |
4628 | ASSERT_OK(reader->Get(ro, kv.first, &get_context, |
4629 | moptions.prefix_extractor.get())); | |
4630 | ASSERT_EQ(get_context.State(), GetContext::kFound); | |
4631 | ASSERT_EQ(value, Slice(kv.second)); | |
4632 | value.Reset(); | |
4633 | } | |
4634 | } | |
4635 | ||
4636 | // Search for non-existent keys | |
4637 | for (auto& kv : kvmap) { | |
4638 | std::string user_key = ExtractUserKey(kv.first).ToString(); | |
4639 | user_key.back() = '0'; // make it non-existent key | |
4640 | InternalKey internal_key(user_key, 0, kTypeValue); | |
4641 | std::string encoded_key = internal_key.Encode().ToString(); | |
4642 | if (i == 0) { // Search using Seek() | |
4643 | seek_iter->Seek(encoded_key); | |
4644 | ASSERT_OK(seek_iter->status()); | |
4645 | if (seek_iter->Valid()) { | |
4646 | ASSERT_TRUE(BytewiseComparator()->Compare( | |
4647 | user_key, ExtractUserKey(seek_iter->key())) < 0); | |
4648 | } | |
4649 | } else { // Search using Get() | |
4650 | PinnableSlice value; | |
4651 | GetContext get_context(options.comparator, nullptr, nullptr, nullptr, | |
4652 | GetContext::kNotFound, user_key, &value, nullptr, | |
f67539c2 | 4653 | nullptr, true, nullptr, nullptr); |
11fdf7f2 TL |
4654 | ASSERT_OK(reader->Get(ro, encoded_key, &get_context, |
4655 | moptions.prefix_extractor.get())); | |
4656 | ASSERT_EQ(get_context.State(), GetContext::kNotFound); | |
4657 | value.Reset(); | |
4658 | } | |
4659 | } | |
4660 | } | |
4661 | } | |
4662 | ||
f67539c2 TL |
4663 | // BlockBasedTableIterator should invalidate itself and return |
4664 | // OutOfBound()=true immediately after Seek(), to allow LevelIterator | |
4665 | // filter out corresponding level. | |
4666 | TEST_P(BlockBasedTableTest, OutOfBoundOnSeek) { | |
4667 | TableConstructor c(BytewiseComparator(), true /*convert_to_internal_key*/); | |
4668 | c.Add("foo", "v1"); | |
4669 | std::vector<std::string> keys; | |
4670 | stl_wrappers::KVMap kvmap; | |
4671 | Options options; | |
4672 | BlockBasedTableOptions table_opt(GetBlockBasedTableOptions()); | |
4673 | options.table_factory.reset(NewBlockBasedTableFactory(table_opt)); | |
4674 | const ImmutableCFOptions ioptions(options); | |
4675 | const MutableCFOptions moptions(options); | |
4676 | c.Finish(options, ioptions, moptions, table_opt, | |
4677 | GetPlainInternalComparator(BytewiseComparator()), &keys, &kvmap); | |
4678 | auto* reader = c.GetTableReader(); | |
4679 | ReadOptions read_opt; | |
4680 | std::string upper_bound = "bar"; | |
4681 | Slice upper_bound_slice(upper_bound); | |
4682 | read_opt.iterate_upper_bound = &upper_bound_slice; | |
4683 | std::unique_ptr<InternalIterator> iter; | |
4684 | iter.reset(new KeyConvertingIterator(reader->NewIterator( | |
4685 | read_opt, /*prefix_extractor=*/nullptr, /*arena=*/nullptr, | |
4686 | /*skip_filters=*/false, TableReaderCaller::kUncategorized))); | |
4687 | iter->SeekToFirst(); | |
4688 | ASSERT_FALSE(iter->Valid()); | |
20effc67 TL |
4689 | ASSERT_OK(iter->status()); |
4690 | ASSERT_TRUE(iter->UpperBoundCheckResult() == IterBoundCheck::kOutOfBound); | |
f67539c2 TL |
4691 | iter.reset(new KeyConvertingIterator(reader->NewIterator( |
4692 | read_opt, /*prefix_extractor=*/nullptr, /*arena=*/nullptr, | |
4693 | /*skip_filters=*/false, TableReaderCaller::kUncategorized))); | |
4694 | iter->Seek("foo"); | |
4695 | ASSERT_FALSE(iter->Valid()); | |
20effc67 TL |
4696 | ASSERT_OK(iter->status()); |
4697 | ASSERT_TRUE(iter->UpperBoundCheckResult() == IterBoundCheck::kOutOfBound); | |
f67539c2 TL |
4698 | } |
4699 | ||
4700 | // BlockBasedTableIterator should invalidate itself and return | |
4701 | // OutOfBound()=true after Next(), if it finds current index key is no smaller | |
4702 | // than upper bound, unless it is pointing to the last data block. | |
4703 | TEST_P(BlockBasedTableTest, OutOfBoundOnNext) { | |
4704 | TableConstructor c(BytewiseComparator(), true /*convert_to_internal_key*/); | |
4705 | c.Add("bar", "v"); | |
4706 | c.Add("foo", "v"); | |
4707 | std::vector<std::string> keys; | |
4708 | stl_wrappers::KVMap kvmap; | |
4709 | Options options; | |
4710 | BlockBasedTableOptions table_opt(GetBlockBasedTableOptions()); | |
4711 | table_opt.flush_block_policy_factory = | |
4712 | std::make_shared<FlushBlockEveryKeyPolicyFactory>(); | |
4713 | options.table_factory.reset(NewBlockBasedTableFactory(table_opt)); | |
4714 | const ImmutableCFOptions ioptions(options); | |
4715 | const MutableCFOptions moptions(options); | |
4716 | c.Finish(options, ioptions, moptions, table_opt, | |
4717 | GetPlainInternalComparator(BytewiseComparator()), &keys, &kvmap); | |
4718 | auto* reader = c.GetTableReader(); | |
4719 | ReadOptions read_opt; | |
4720 | std::string ub1 = "bar_after"; | |
4721 | Slice ub_slice1(ub1); | |
4722 | read_opt.iterate_upper_bound = &ub_slice1; | |
4723 | std::unique_ptr<InternalIterator> iter; | |
4724 | iter.reset(new KeyConvertingIterator(reader->NewIterator( | |
4725 | read_opt, /*prefix_extractor=*/nullptr, /*arena=*/nullptr, | |
4726 | /*skip_filters=*/false, TableReaderCaller::kUncategorized))); | |
4727 | iter->Seek("bar"); | |
4728 | ASSERT_TRUE(iter->Valid()); | |
4729 | ASSERT_EQ("bar", iter->key()); | |
4730 | iter->Next(); | |
4731 | ASSERT_FALSE(iter->Valid()); | |
20effc67 | 4732 | ASSERT_TRUE(iter->UpperBoundCheckResult() == IterBoundCheck::kOutOfBound); |
f67539c2 TL |
4733 | std::string ub2 = "foo_after"; |
4734 | Slice ub_slice2(ub2); | |
4735 | read_opt.iterate_upper_bound = &ub_slice2; | |
4736 | iter.reset(new KeyConvertingIterator(reader->NewIterator( | |
4737 | read_opt, /*prefix_extractor=*/nullptr, /*arena=*/nullptr, | |
4738 | /*skip_filters=*/false, TableReaderCaller::kUncategorized))); | |
4739 | iter->Seek("foo"); | |
4740 | ASSERT_TRUE(iter->Valid()); | |
4741 | ASSERT_EQ("foo", iter->key()); | |
4742 | iter->Next(); | |
4743 | ASSERT_FALSE(iter->Valid()); | |
20effc67 | 4744 | ASSERT_FALSE(iter->UpperBoundCheckResult() == IterBoundCheck::kOutOfBound); |
f67539c2 TL |
4745 | } |
4746 | ||
4747 | } // namespace ROCKSDB_NAMESPACE | |
7c673cae FG |
4748 | |
4749 | int main(int argc, char** argv) { | |
4750 | ::testing::InitGoogleTest(&argc, argv); | |
4751 | return RUN_ALL_TESTS(); | |
4752 | } |