1 // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
2 // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file. See the AUTHORS file for names of contributors.
8 #include "table/plain/plain_table_reader.h"
13 #include "db/dbformat.h"
14 #include "memory/arena.h"
15 #include "monitoring/histogram.h"
16 #include "monitoring/perf_context_imp.h"
17 #include "rocksdb/cache.h"
18 #include "rocksdb/comparator.h"
19 #include "rocksdb/env.h"
20 #include "rocksdb/filter_policy.h"
21 #include "rocksdb/options.h"
22 #include "rocksdb/statistics.h"
23 #include "table/block_based/block.h"
24 #include "table/block_based/filter_block.h"
25 #include "table/format.h"
26 #include "table/get_context.h"
27 #include "table/internal_iterator.h"
28 #include "table/meta_blocks.h"
29 #include "table/plain/plain_table_bloom.h"
30 #include "table/plain/plain_table_factory.h"
31 #include "table/plain/plain_table_key_coding.h"
32 #include "table/two_level_iterator.h"
33 #include "util/coding.h"
34 #include "util/dynamic_bloom.h"
35 #include "util/hash.h"
36 #include "util/stop_watch.h"
37 #include "util/string_util.h"
39 namespace ROCKSDB_NAMESPACE
{
43 // Safely getting a uint32_t element from a char array, where, starting from
44 // `base`, every 4 bytes are considered as an fixed 32 bit integer.
45 inline uint32_t GetFixed32Element(const char* base
, size_t offset
) {
46 return DecodeFixed32(base
+ offset
* sizeof(uint32_t));
50 // Iterator to iterate IndexedTable
51 class PlainTableIterator
: public InternalIterator
{
53 explicit PlainTableIterator(PlainTableReader
* table
, bool use_prefix_seek
);
55 PlainTableIterator(const PlainTableIterator
&) = delete;
56 void operator=(const Iterator
&) = delete;
58 ~PlainTableIterator() override
;
60 bool Valid() const override
;
62 void SeekToFirst() override
;
64 void SeekToLast() override
;
66 void Seek(const Slice
& target
) override
;
68 void SeekForPrev(const Slice
& target
) override
;
74 Slice
key() const override
;
76 Slice
value() const override
;
78 Status
status() const override
;
81 PlainTableReader
* table_
;
82 PlainTableKeyDecoder decoder_
;
83 bool use_prefix_seek_
;
85 uint32_t next_offset_
;
91 extern const uint64_t kPlainTableMagicNumber
;
92 PlainTableReader::PlainTableReader(
93 const ImmutableOptions
& ioptions
,
94 std::unique_ptr
<RandomAccessFileReader
>&& file
,
95 const EnvOptions
& storage_options
, const InternalKeyComparator
& icomparator
,
96 EncodingType encoding_type
, uint64_t file_size
,
97 const TableProperties
* table_properties
,
98 const SliceTransform
* prefix_extractor
)
99 : internal_comparator_(icomparator
),
100 encoding_type_(encoding_type
),
101 full_scan_mode_(false),
102 user_key_len_(static_cast<uint32_t>(table_properties
->fixed_key_len
)),
103 prefix_extractor_(prefix_extractor
),
104 enable_bloom_(false),
106 file_info_(std::move(file
), storage_options
,
107 static_cast<uint32_t>(table_properties
->data_size
)),
109 file_size_(file_size
),
110 table_properties_(nullptr) {}
112 PlainTableReader::~PlainTableReader() {
114 status_
.PermitUncheckedError();
117 Status
PlainTableReader::Open(
118 const ImmutableOptions
& ioptions
, const EnvOptions
& env_options
,
119 const InternalKeyComparator
& internal_comparator
,
120 std::unique_ptr
<RandomAccessFileReader
>&& file
, uint64_t file_size
,
121 std::unique_ptr
<TableReader
>* table_reader
, const int bloom_bits_per_key
,
122 double hash_table_ratio
, size_t index_sparseness
, size_t huge_page_tlb_size
,
123 bool full_scan_mode
, const bool immortal_table
,
124 const SliceTransform
* prefix_extractor
) {
125 if (file_size
> PlainTableIndex::kMaxFileSize
) {
126 return Status::NotSupported("File is too large for PlainTableReader!");
129 std::unique_ptr
<TableProperties
> props
;
130 auto s
= ReadTableProperties(file
.get(), file_size
, kPlainTableMagicNumber
,
136 assert(hash_table_ratio
>= 0.0);
137 auto& user_props
= props
->user_collected_properties
;
138 auto prefix_extractor_in_file
= props
->prefix_extractor_name
;
140 if (!full_scan_mode
&&
141 !prefix_extractor_in_file
.empty() /* old version sst file*/
142 && prefix_extractor_in_file
!= "nullptr") {
143 if (!prefix_extractor
) {
144 return Status::InvalidArgument(
145 "Prefix extractor is missing when opening a PlainTable built "
146 "using a prefix extractor");
147 } else if (prefix_extractor_in_file
!= prefix_extractor
->AsString()) {
148 return Status::InvalidArgument(
149 "Prefix extractor given doesn't match the one used to build "
154 EncodingType encoding_type
= kPlain
;
155 auto encoding_type_prop
=
156 user_props
.find(PlainTablePropertyNames::kEncodingType
);
157 if (encoding_type_prop
!= user_props
.end()) {
158 encoding_type
= static_cast<EncodingType
>(
159 DecodeFixed32(encoding_type_prop
->second
.c_str()));
162 std::unique_ptr
<PlainTableReader
> new_reader(new PlainTableReader(
163 ioptions
, std::move(file
), env_options
, internal_comparator
,
164 encoding_type
, file_size
, props
.get(), prefix_extractor
));
166 s
= new_reader
->MmapDataIfNeeded();
171 if (!full_scan_mode
) {
172 s
= new_reader
->PopulateIndex(props
.get(), bloom_bits_per_key
,
173 hash_table_ratio
, index_sparseness
,
179 // Flag to indicate it is a full scan mode so that none of the indexes
181 new_reader
->full_scan_mode_
= true;
183 // PopulateIndex can add to the props, so don't store them until now
184 new_reader
->table_properties_
= std::move(props
);
186 if (immortal_table
&& new_reader
->file_info_
.is_mmap_mode
) {
187 new_reader
->dummy_cleanable_
.reset(new Cleanable());
190 *table_reader
= std::move(new_reader
);
194 void PlainTableReader::SetupForCompaction() {}
196 InternalIterator
* PlainTableReader::NewIterator(
197 const ReadOptions
& options
, const SliceTransform
* /* prefix_extractor */,
198 Arena
* arena
, bool /*skip_filters*/, TableReaderCaller
/*caller*/,
199 size_t /*compaction_readahead_size*/, bool /* allow_unprepared_value */) {
200 // Not necessarily used here, but make sure this has been initialized
201 assert(table_properties_
);
203 // Auto prefix mode is not implemented in PlainTable.
204 bool use_prefix_seek
= !IsTotalOrderMode() && !options
.total_order_seek
&&
205 !options
.auto_prefix_mode
;
206 if (arena
== nullptr) {
207 return new PlainTableIterator(this, use_prefix_seek
);
209 auto mem
= arena
->AllocateAligned(sizeof(PlainTableIterator
));
210 return new (mem
) PlainTableIterator(this, use_prefix_seek
);
214 Status
PlainTableReader::PopulateIndexRecordList(
215 PlainTableIndexBuilder
* index_builder
,
216 std::vector
<uint32_t>* prefix_hashes
) {
217 Slice prev_key_prefix_slice
;
218 std::string prev_key_prefix_buf
;
219 uint32_t pos
= data_start_offset_
;
221 bool is_first_record
= true;
222 Slice key_prefix_slice
;
223 PlainTableKeyDecoder
decoder(&file_info_
, encoding_type_
, user_key_len_
,
225 while (pos
< file_info_
.data_end_offset
) {
226 uint32_t key_offset
= pos
;
227 ParsedInternalKey key
;
229 bool seekable
= false;
230 Status s
= Next(&decoder
, &pos
, &key
, nullptr, &value_slice
, &seekable
);
235 key_prefix_slice
= GetPrefix(key
);
237 bloom_
.AddHash(GetSliceHash(key
.user_key
));
239 if (is_first_record
|| prev_key_prefix_slice
!= key_prefix_slice
) {
240 if (!is_first_record
) {
241 prefix_hashes
->push_back(GetSliceHash(prev_key_prefix_slice
));
243 if (file_info_
.is_mmap_mode
) {
244 prev_key_prefix_slice
= key_prefix_slice
;
246 prev_key_prefix_buf
= key_prefix_slice
.ToString();
247 prev_key_prefix_slice
= prev_key_prefix_buf
;
252 index_builder
->AddKeyPrefix(GetPrefix(key
), key_offset
);
254 if (!seekable
&& is_first_record
) {
255 return Status::Corruption("Key for a prefix is not seekable");
258 is_first_record
= false;
261 prefix_hashes
->push_back(GetSliceHash(key_prefix_slice
));
262 auto s
= index_
.InitFromRawData(index_builder
->Finish());
266 void PlainTableReader::AllocateBloom(int bloom_bits_per_key
, int num_keys
,
267 size_t huge_page_tlb_size
) {
268 uint32_t bloom_total_bits
= num_keys
* bloom_bits_per_key
;
269 if (bloom_total_bits
> 0) {
270 enable_bloom_
= true;
271 bloom_
.SetTotalBits(&arena_
, bloom_total_bits
, ioptions_
.bloom_locality
,
272 huge_page_tlb_size
, ioptions_
.logger
);
276 void PlainTableReader::FillBloom(const std::vector
<uint32_t>& prefix_hashes
) {
277 assert(bloom_
.IsInitialized());
278 for (const auto prefix_hash
: prefix_hashes
) {
279 bloom_
.AddHash(prefix_hash
);
283 Status
PlainTableReader::MmapDataIfNeeded() {
284 if (file_info_
.is_mmap_mode
) {
285 // Get mmapped memory.
286 return file_info_
.file
->Read(
287 IOOptions(), 0, static_cast<size_t>(file_size_
), &file_info_
.file_data
,
288 nullptr, nullptr, Env::IO_TOTAL
/* rate_limiter_priority */);
293 Status
PlainTableReader::PopulateIndex(TableProperties
* props
,
294 int bloom_bits_per_key
,
295 double hash_table_ratio
,
296 size_t index_sparseness
,
297 size_t huge_page_tlb_size
) {
298 assert(props
!= nullptr);
300 BlockContents index_block_contents
;
301 Status s
= ReadMetaBlock(file_info_
.file
.get(), nullptr /* prefetch_buffer */,
302 file_size_
, kPlainTableMagicNumber
, ioptions_
,
303 PlainTableIndexBuilder::kPlainTableIndexBlock
,
304 BlockType::kIndex
, &index_block_contents
);
306 bool index_in_file
= s
.ok();
308 BlockContents bloom_block_contents
;
309 bool bloom_in_file
= false;
310 // We only need to read the bloom block if index block is in file.
312 s
= ReadMetaBlock(file_info_
.file
.get(), nullptr /* prefetch_buffer */,
313 file_size_
, kPlainTableMagicNumber
, ioptions_
,
314 BloomBlockBuilder::kBloomBlock
, BlockType::kFilter
,
315 &bloom_block_contents
);
316 bloom_in_file
= s
.ok() && bloom_block_contents
.data
.size() > 0;
321 // If bloom_block_contents.allocation is not empty (which will be the case
322 // for non-mmap mode), it holds the alloated memory for the bloom block.
323 // It needs to be kept alive to keep `bloom_block` valid.
324 bloom_block_alloc_
= std::move(bloom_block_contents
.allocation
);
325 bloom_block
= &bloom_block_contents
.data
;
327 bloom_block
= nullptr;
332 // If index_block_contents.allocation is not empty (which will be the case
333 // for non-mmap mode), it holds the alloated memory for the index block.
334 // It needs to be kept alive to keep `index_block` valid.
335 index_block_alloc_
= std::move(index_block_contents
.allocation
);
336 index_block
= &index_block_contents
.data
;
338 index_block
= nullptr;
341 if ((prefix_extractor_
== nullptr) && (hash_table_ratio
!= 0)) {
342 // moptions.prefix_extractor is requried for a hash-based look-up.
343 return Status::NotSupported(
344 "PlainTable requires a prefix extractor enable prefix hash mode.");
347 // First, read the whole file, for every kIndexIntervalForSamePrefixKeys rows
348 // for a prefix (starting from the first one), generate a record of (hash,
349 // offset) and append it to IndexRecordList, which is a data structure created
352 if (!index_in_file
) {
353 // Allocate bloom filter here for total order mode.
354 if (IsTotalOrderMode()) {
355 AllocateBloom(bloom_bits_per_key
,
356 static_cast<uint32_t>(props
->num_entries
),
359 } else if (bloom_in_file
) {
360 enable_bloom_
= true;
361 auto num_blocks_property
= props
->user_collected_properties
.find(
362 PlainTablePropertyNames::kNumBloomBlocks
);
364 uint32_t num_blocks
= 0;
365 if (num_blocks_property
!= props
->user_collected_properties
.end()) {
366 Slice
temp_slice(num_blocks_property
->second
);
367 if (!GetVarint32(&temp_slice
, &num_blocks
)) {
371 // cast away const qualifier, because bloom_ won't be changed
372 bloom_
.SetRawData(const_cast<char*>(bloom_block
->data()),
373 static_cast<uint32_t>(bloom_block
->size()) * 8,
376 // Index in file but no bloom in file. Disable bloom filter in this case.
377 enable_bloom_
= false;
378 bloom_bits_per_key
= 0;
381 PlainTableIndexBuilder
index_builder(&arena_
, ioptions_
, prefix_extractor_
,
382 index_sparseness
, hash_table_ratio
,
385 std::vector
<uint32_t> prefix_hashes
;
386 if (!index_in_file
) {
387 // Populates _bloom if enabled (total order mode)
388 s
= PopulateIndexRecordList(&index_builder
, &prefix_hashes
);
393 s
= index_
.InitFromRawData(*index_block
);
399 if (!index_in_file
) {
400 if (!IsTotalOrderMode()) {
401 // Calculated bloom filter size and allocate memory for
402 // bloom filter based on the number of prefixes, then fill it.
403 AllocateBloom(bloom_bits_per_key
, index_
.GetNumPrefixes(),
406 FillBloom(prefix_hashes
);
411 // Fill two table properties.
412 if (!index_in_file
) {
413 props
->user_collected_properties
["plain_table_hash_table_size"] =
414 std::to_string(index_
.GetIndexSize() * PlainTableIndex::kOffsetLen
);
415 props
->user_collected_properties
["plain_table_sub_index_size"] =
416 std::to_string(index_
.GetSubIndexSize());
418 props
->user_collected_properties
["plain_table_hash_table_size"] =
420 props
->user_collected_properties
["plain_table_sub_index_size"] =
427 Status
PlainTableReader::GetOffset(PlainTableKeyDecoder
* decoder
,
428 const Slice
& target
, const Slice
& prefix
,
429 uint32_t prefix_hash
, bool& prefix_matched
,
430 uint32_t* offset
) const {
431 prefix_matched
= false;
432 uint32_t prefix_index_offset
;
433 auto res
= index_
.GetOffset(prefix_hash
, &prefix_index_offset
);
434 if (res
== PlainTableIndex::kNoPrefixForBucket
) {
435 *offset
= file_info_
.data_end_offset
;
437 } else if (res
== PlainTableIndex::kDirectToFile
) {
438 *offset
= prefix_index_offset
;
442 // point to sub-index, need to do a binary search
443 uint32_t upper_bound
= 0;
444 const char* base_ptr
=
445 index_
.GetSubIndexBasePtrAndUpperBound(prefix_index_offset
, &upper_bound
);
447 uint32_t high
= upper_bound
;
448 ParsedInternalKey mid_key
;
449 ParsedInternalKey parsed_target
;
450 Status s
= ParseInternalKey(target
, &parsed_target
,
451 false /* log_err_key */); // TODO
452 if (!s
.ok()) return s
;
454 // The key is between [low, high). Do a binary search between it.
455 while (high
- low
> 1) {
456 uint32_t mid
= (high
+ low
) / 2;
457 uint32_t file_offset
= GetFixed32Element(base_ptr
, mid
);
459 s
= decoder
->NextKeyNoValue(file_offset
, &mid_key
, nullptr, &tmp
);
463 int cmp_result
= internal_comparator_
.Compare(mid_key
, parsed_target
);
464 if (cmp_result
< 0) {
467 if (cmp_result
== 0) {
468 // Happen to have found the exact key or target is smaller than the
469 // first key after base_offset.
470 prefix_matched
= true;
471 *offset
= file_offset
;
478 // Both of the key at the position low or low+1 could share the same
479 // prefix as target. We need to rule out one of them to avoid to go
480 // to the wrong prefix.
481 ParsedInternalKey low_key
;
483 uint32_t low_key_offset
= GetFixed32Element(base_ptr
, low
);
484 s
= decoder
->NextKeyNoValue(low_key_offset
, &low_key
, nullptr, &tmp
);
489 if (GetPrefix(low_key
) == prefix
) {
490 prefix_matched
= true;
491 *offset
= low_key_offset
;
492 } else if (low
+ 1 < upper_bound
) {
493 // There is possible a next prefix, return it
494 prefix_matched
= false;
495 *offset
= GetFixed32Element(base_ptr
, low
+ 1);
497 // target is larger than a key of the last prefix in this bucket
498 // but with a different prefix. Key does not exist.
499 *offset
= file_info_
.data_end_offset
;
504 bool PlainTableReader::MatchBloom(uint32_t hash
) const {
505 if (!enable_bloom_
) {
509 if (bloom_
.MayContainHash(hash
)) {
510 PERF_COUNTER_ADD(bloom_sst_hit_count
, 1);
513 PERF_COUNTER_ADD(bloom_sst_miss_count
, 1);
518 Status
PlainTableReader::Next(PlainTableKeyDecoder
* decoder
, uint32_t* offset
,
519 ParsedInternalKey
* parsed_key
,
520 Slice
* internal_key
, Slice
* value
,
521 bool* seekable
) const {
522 if (*offset
== file_info_
.data_end_offset
) {
523 *offset
= file_info_
.data_end_offset
;
527 if (*offset
> file_info_
.data_end_offset
) {
528 return Status::Corruption("Offset is out of file size");
532 Status s
= decoder
->NextKey(*offset
, parsed_key
, internal_key
, value
,
533 &bytes_read
, seekable
);
537 *offset
= *offset
+ bytes_read
;
541 void PlainTableReader::Prepare(const Slice
& target
) {
543 uint32_t prefix_hash
= GetSliceHash(GetPrefix(target
));
544 bloom_
.Prefetch(prefix_hash
);
548 Status
PlainTableReader::Get(const ReadOptions
& /*ro*/, const Slice
& target
,
549 GetContext
* get_context
,
550 const SliceTransform
* /* prefix_extractor */,
551 bool /*skip_filters*/) {
552 // Check bloom filter first.
554 uint32_t prefix_hash
;
555 if (IsTotalOrderMode()) {
556 if (full_scan_mode_
) {
558 Status::InvalidArgument("Get() is not allowed in full scan mode.");
560 // Match whole user key for bloom filter check.
561 if (!MatchBloom(GetSliceHash(ExtractUserKey(target
)))) {
564 // in total order mode, there is only one bucket 0, and we always use empty
566 prefix_slice
= Slice();
569 prefix_slice
= GetPrefix(target
);
570 prefix_hash
= GetSliceHash(prefix_slice
);
571 if (!MatchBloom(prefix_hash
)) {
577 PlainTableKeyDecoder
decoder(&file_info_
, encoding_type_
, user_key_len_
,
579 Status s
= GetOffset(&decoder
, target
, prefix_slice
, prefix_hash
,
580 prefix_match
, &offset
);
585 ParsedInternalKey found_key
;
586 ParsedInternalKey parsed_target
;
587 s
= ParseInternalKey(target
, &parsed_target
,
588 false /* log_err_key */); // TODO
589 if (!s
.ok()) return s
;
592 while (offset
< file_info_
.data_end_offset
) {
593 s
= Next(&decoder
, &offset
, &found_key
, nullptr, &found_value
);
598 // Need to verify prefix for the first key found if it is not yet
600 if (GetPrefix(found_key
) != prefix_slice
) {
605 // TODO(ljin): since we know the key comparison result here,
606 // can we enable the fast path?
607 if (internal_comparator_
.Compare(found_key
, parsed_target
) >= 0) {
608 bool dont_care
__attribute__((__unused__
));
609 if (!get_context
->SaveValue(found_key
, found_value
, &dont_care
,
610 dummy_cleanable_
.get())) {
618 uint64_t PlainTableReader::ApproximateOffsetOf(const Slice
& /*key*/,
619 TableReaderCaller
/*caller*/) {
623 uint64_t PlainTableReader::ApproximateSize(const Slice
& /*start*/,
624 const Slice
& /*end*/,
625 TableReaderCaller
/*caller*/) {
629 PlainTableIterator::PlainTableIterator(PlainTableReader
* table
,
630 bool use_prefix_seek
)
632 decoder_(&table_
->file_info_
, table_
->encoding_type_
,
633 table_
->user_key_len_
, table_
->prefix_extractor_
),
634 use_prefix_seek_(use_prefix_seek
) {
635 next_offset_
= offset_
= table_
->file_info_
.data_end_offset
;
638 PlainTableIterator::~PlainTableIterator() {}
640 bool PlainTableIterator::Valid() const {
641 return offset_
< table_
->file_info_
.data_end_offset
&&
642 offset_
>= table_
->data_start_offset_
;
645 void PlainTableIterator::SeekToFirst() {
646 status_
= Status::OK();
647 next_offset_
= table_
->data_start_offset_
;
648 if (next_offset_
>= table_
->file_info_
.data_end_offset
) {
649 next_offset_
= offset_
= table_
->file_info_
.data_end_offset
;
655 void PlainTableIterator::SeekToLast() {
657 status_
= Status::NotSupported("SeekToLast() is not supported in PlainTable");
658 next_offset_
= offset_
= table_
->file_info_
.data_end_offset
;
661 void PlainTableIterator::Seek(const Slice
& target
) {
662 if (use_prefix_seek_
!= !table_
->IsTotalOrderMode()) {
663 // This check is done here instead of NewIterator() to permit creating an
664 // iterator with total_order_seek = true even if we won't be able to Seek()
665 // it. This is needed for compaction: it creates iterator with
666 // total_order_seek = true but usually never does Seek() on it,
667 // only SeekToFirst().
668 status_
= Status::InvalidArgument(
669 "total_order_seek not implemented for PlainTable.");
670 offset_
= next_offset_
= table_
->file_info_
.data_end_offset
;
674 // If the user doesn't set prefix seek option and we are not able to do a
675 // total Seek(). assert failure.
676 if (table_
->IsTotalOrderMode()) {
677 if (table_
->full_scan_mode_
) {
679 Status::InvalidArgument("Seek() is not allowed in full scan mode.");
680 offset_
= next_offset_
= table_
->file_info_
.data_end_offset
;
682 } else if (table_
->GetIndexSize() > 1) {
684 status_
= Status::NotSupported(
685 "PlainTable cannot issue non-prefix seek unless in total order "
687 offset_
= next_offset_
= table_
->file_info_
.data_end_offset
;
692 Slice prefix_slice
= table_
->GetPrefix(target
);
693 uint32_t prefix_hash
= 0;
694 // Bloom filter is ignored in total-order mode.
695 if (!table_
->IsTotalOrderMode()) {
696 prefix_hash
= GetSliceHash(prefix_slice
);
697 if (!table_
->MatchBloom(prefix_hash
)) {
698 status_
= Status::OK();
699 offset_
= next_offset_
= table_
->file_info_
.data_end_offset
;
704 status_
= table_
->GetOffset(&decoder_
, target
, prefix_slice
, prefix_hash
,
705 prefix_match
, &next_offset_
);
707 offset_
= next_offset_
= table_
->file_info_
.data_end_offset
;
711 if (next_offset_
< table_
->file_info_
.data_end_offset
) {
712 for (Next(); status_
.ok() && Valid(); Next()) {
714 // Need to verify the first key's prefix
715 if (table_
->GetPrefix(key()) != prefix_slice
) {
716 offset_
= next_offset_
= table_
->file_info_
.data_end_offset
;
721 if (table_
->internal_comparator_
.Compare(key(), target
) >= 0) {
726 offset_
= table_
->file_info_
.data_end_offset
;
730 void PlainTableIterator::SeekForPrev(const Slice
& /*target*/) {
733 Status::NotSupported("SeekForPrev() is not supported in PlainTable");
734 offset_
= next_offset_
= table_
->file_info_
.data_end_offset
;
737 void PlainTableIterator::Next() {
738 offset_
= next_offset_
;
739 if (offset_
< table_
->file_info_
.data_end_offset
) {
741 ParsedInternalKey parsed_key
;
743 table_
->Next(&decoder_
, &next_offset_
, &parsed_key
, &key_
, &value_
);
745 offset_
= next_offset_
= table_
->file_info_
.data_end_offset
;
750 void PlainTableIterator::Prev() { assert(false); }
752 Slice
PlainTableIterator::key() const {
757 Slice
PlainTableIterator::value() const {
762 Status
PlainTableIterator::status() const { return status_
; }
764 } // namespace ROCKSDB_NAMESPACE
765 #endif // ROCKSDB_LITE