| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include "paimon/common/sst/sst_file_reader.h" |
| |
| #include "fmt/format.h" |
| #include "paimon/common/lookup/sort/sort_lookup_store_footer.h" |
| #include "paimon/common/sst/sst_file_utils.h" |
| #include "paimon/common/utils/crc32c.h" |
| #include "paimon/common/utils/murmurhash_utils.h" |
| namespace paimon { |
| |
| Result<std::shared_ptr<SstFileReader>> SstFileReader::Create( |
| const BlockHandle& index_block_handle, |
| const std::optional<BloomFilterHandle>& bloom_filter_handle, |
| MemorySlice::SliceComparator comparator, const std::shared_ptr<BlockCache>& block_cache, |
| const std::shared_ptr<MemoryPool>& pool) { |
| // read bloom filter directly now |
| std::shared_ptr<BloomFilter> bloom_filter = nullptr; |
| if (bloom_filter_handle.has_value() && |
| (bloom_filter_handle->ExpectedEntries() || bloom_filter_handle->Size() || |
| bloom_filter_handle->Offset())) { |
| bloom_filter = std::make_shared<BloomFilter>(bloom_filter_handle->ExpectedEntries(), |
| bloom_filter_handle->Size()); |
| PAIMON_ASSIGN_OR_RAISE( |
| MemorySegment bloom_filter_data, |
| block_cache->GetBlock(bloom_filter_handle->Offset(), bloom_filter_handle->Size(), |
| /*is_index=*/true, /*decompress_func=*/nullptr)); |
| PAIMON_RETURN_NOT_OK(bloom_filter->SetMemorySegment(bloom_filter_data)); |
| } |
| |
| // create index block reader |
| PAIMON_ASSIGN_OR_RAISE( |
| MemorySegment trailer_data, |
| block_cache->GetBlock(index_block_handle.Offset() + index_block_handle.Size(), |
| BlockTrailer::ENCODED_LENGTH, /*is_index=*/true, |
| /*decompress_func=*/nullptr)); |
| auto trailer_slice = MemorySlice::Wrap(trailer_data); |
| auto trailer_input = trailer_slice.ToInput(); |
| std::shared_ptr<BlockTrailer> trailer = BlockTrailer::ReadBlockTrailer(&trailer_input); |
| PAIMON_ASSIGN_OR_RAISE( |
| MemorySegment block_data, |
| block_cache->GetBlock(index_block_handle.Offset(), index_block_handle.Size(), true, |
| [pool, trailer](const MemorySegment& seg) -> Result<MemorySegment> { |
| return DecompressBlock(seg, trailer, pool); |
| })); |
| PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<BlockReader> reader, |
| BlockReader::Create(MemorySlice::Wrap(block_data), comparator)); |
| return std::shared_ptr<SstFileReader>( |
| new SstFileReader(pool, block_cache, bloom_filter, reader, comparator)); |
| } |
| |
| Result<std::shared_ptr<SstFileReader>> SstFileReader::CreateForSortLookupStore( |
| const std::shared_ptr<InputStream>& in, MemorySlice::SliceComparator comparator, |
| const std::shared_ptr<BlockCache>& block_cache, const std::shared_ptr<MemoryPool>& pool) { |
| PAIMON_ASSIGN_OR_RAISE(uint64_t file_len, in->Length()); |
| PAIMON_RETURN_NOT_OK( |
| in->Seek(file_len - SortLookupStoreFooter::ENCODED_LENGTH, SeekOrigin::FS_SEEK_SET)); |
| auto footer_bytes = Bytes::AllocateBytes(SortLookupStoreFooter::ENCODED_LENGTH, pool.get()); |
| PAIMON_RETURN_NOT_OK(in->Read(footer_bytes->data(), footer_bytes->size())); |
| auto footer_segment = MemorySegment::Wrap(std::move(footer_bytes)); |
| auto footer_slice = MemorySlice::Wrap(footer_segment); |
| auto footer_input = footer_slice.ToInput(); |
| PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<SortLookupStoreFooter> read_footer, |
| SortLookupStoreFooter::ReadSortLookupStoreFooter(&footer_input)); |
| return SstFileReader::Create(read_footer->GetIndexBlockHandle(), |
| read_footer->GetBloomFilterHandle(), std::move(comparator), |
| block_cache, pool); |
| } |
| |
| SstFileReader::SstFileReader(const std::shared_ptr<MemoryPool>& pool, |
| const std::shared_ptr<BlockCache>& block_cache, |
| const std::shared_ptr<BloomFilter>& bloom_filter, |
| const std::shared_ptr<BlockReader>& index_block_reader, |
| MemorySlice::SliceComparator comparator) |
| : pool_(pool), |
| block_cache_(block_cache), |
| bloom_filter_(bloom_filter), |
| index_block_reader_(index_block_reader), |
| comparator_(std::move(comparator)) {} |
| |
| std::unique_ptr<BlockIterator> SstFileReader::CreateIndexIterator() { |
| return index_block_reader_->Iterator(); |
| } |
| |
| Result<std::shared_ptr<Bytes>> SstFileReader::Lookup(const std::shared_ptr<Bytes>& key) { |
| if (bloom_filter_.get() && !bloom_filter_->TestHash(MurmurHashUtils::HashBytes(key))) { |
| return std::shared_ptr<Bytes>(); |
| } |
| auto key_slice = MemorySlice::Wrap(key); |
| // seek the index to the block containing the key |
| auto index_block_iterator = index_block_reader_->Iterator(); |
| PAIMON_ASSIGN_OR_RAISE([[maybe_unused]] bool _, index_block_iterator->SeekTo(key_slice)); |
| // if indexIterator does not have a next, it means the key does not exist in this iterator |
| if (index_block_iterator->HasNext()) { |
| // seek the current iterator to the key |
| PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<BlockIterator> current, |
| GetNextBlock(index_block_iterator)); |
| PAIMON_ASSIGN_OR_RAISE(bool success, current->SeekTo(key_slice)); |
| if (success) { |
| PAIMON_ASSIGN_OR_RAISE(BlockEntry ret, current->Next()); |
| return ret.value.CopyBytes(pool_.get()); |
| } |
| } |
| return std::shared_ptr<Bytes>(); |
| } |
| |
| Result<std::unique_ptr<BlockIterator>> SstFileReader::GetNextBlock( |
| std::unique_ptr<BlockIterator>& index_iterator) { |
| PAIMON_ASSIGN_OR_RAISE(BlockEntry block_entry, index_iterator->Next()); |
| auto block_input = block_entry.value.ToInput(); |
| PAIMON_ASSIGN_OR_RAISE(BlockHandle block_handle, BlockHandle::ReadBlockHandle(&block_input)); |
| PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<BlockReader> reader, ReadBlock(block_handle, false)); |
| return reader->Iterator(); |
| } |
| |
| Result<std::shared_ptr<BlockReader>> SstFileReader::ReadBlock(const BlockHandle& handle, |
| bool index) { |
| PAIMON_ASSIGN_OR_RAISE( |
| MemorySegment trailer_data, |
| block_cache_->GetBlock(handle.Offset() + handle.Size(), BlockTrailer::ENCODED_LENGTH, |
| /*is_index=*/true, /*decompress_func=*/nullptr)); |
| auto trailer_slice = MemorySlice::Wrap(trailer_data); |
| auto trailer_input = trailer_slice.ToInput(); |
| std::shared_ptr<paimon::BlockTrailer> trailer = BlockTrailer::ReadBlockTrailer(&trailer_input); |
| PAIMON_ASSIGN_OR_RAISE( |
| MemorySegment block_data, |
| block_cache_->GetBlock(handle.Offset(), handle.Size(), index, |
| [this, trailer](const MemorySegment& seg) -> Result<MemorySegment> { |
| return DecompressBlock(seg, trailer, pool_); |
| })); |
| return BlockReader::Create(MemorySlice::Wrap(block_data), comparator_); |
| } |
| |
| Result<MemorySegment> SstFileReader::DecompressBlock(const MemorySegment& compressed_data, |
| const std::shared_ptr<BlockTrailer>& trailer, |
| const std::shared_ptr<MemoryPool>& pool) { |
| // check crc32c |
| auto crc32c_code = CRC32C::calculate(compressed_data.Data(), compressed_data.Size()); |
| auto compression_val = |
| static_cast<char>(static_cast<int32_t>(trailer->CompressionType()) & 0xFF); |
| crc32c_code = CRC32C::calculate(&compression_val, 1, crc32c_code); |
| if (trailer->Crc32c() != static_cast<int32_t>(crc32c_code)) { |
| return Status::Invalid(fmt::format("Expected crc32c({:#x}) but found crc32c({:#x})", |
| static_cast<uint32_t>(trailer->Crc32c()), crc32c_code)); |
| } |
| |
| // decompress data |
| PAIMON_ASSIGN_OR_RAISE(BlockCompressionType compress_type, |
| SstFileUtils::From(trailer->CompressionType())); |
| PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<BlockCompressionFactory> factory, |
| BlockCompressionFactory::Create(compress_type)); |
| if (!factory || factory->GetCompressionType() == BlockCompressionType::NONE) { |
| return compressed_data; |
| } else { |
| auto decompressor = factory->GetDecompressor(); |
| auto slice = MemorySlice::Wrap(compressed_data); |
| auto input = slice.ToInput(); |
| PAIMON_ASSIGN_OR_RAISE(int32_t uncompressed_size, input.ReadVarLenInt()); |
| auto output = MemorySegment::AllocateHeapMemory(uncompressed_size, pool.get()); |
| |
| PAIMON_ASSIGN_OR_RAISE( |
| int32_t actual_uncompressed_size, |
| decompressor->Decompress(compressed_data.Data() + input.Position(), input.Available(), |
| output.MutableData(), output.Size())); |
| if (actual_uncompressed_size != output.Size()) { |
| return Status::Invalid(fmt::format( |
| "Invalid data: expect uncompressed size {}, actual uncompressed size {}", |
| output.Size(), actual_uncompressed_size)); |
| } |
| return output; |
| } |
| } |
| |
| Status SstFileReader::Close() { |
| // TODO(xinyu.lxy): support close FileBasedBloomFilter |
| block_cache_->Close(); |
| return Status::OK(); |
| } |
| |
| } // namespace paimon |