| /* |
| * 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 <cstdint> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "gtest/gtest.h" |
| #include "paimon/common/sst/block_iterator.h" |
| #include "paimon/common/sst/block_reader.h" |
| #include "paimon/common/sst/block_writer.h" |
| #include "paimon/memory/memory_pool.h" |
| #include "paimon/testing/utils/testharness.h" |
| |
| namespace paimon::test { |
| |
| class BlockWriteReadTest : public ::testing::Test { |
| protected: |
| void SetUp() override { |
| pool_ = GetDefaultPool(); |
| comparator_ = [](const MemorySlice& a, const MemorySlice& b) -> Result<int32_t> { |
| std::string_view va = a.ReadStringView(); |
| std::string_view vb = b.ReadStringView(); |
| if (va < vb) { |
| return -1; |
| } |
| if (va > vb) { |
| return 1; |
| } |
| return 0; |
| }; |
| } |
| |
| std::shared_ptr<Bytes> MakeBytes(const std::string& str) const { |
| return std::make_shared<Bytes>(str, pool_.get()); |
| } |
| |
| /// Build a block with the given key-value pairs via BlockWriter. |
| MemorySlice BuildBlock(const std::vector<std::pair<std::string, std::string>>& pairs, |
| BlockWriter* writer) const { |
| for (const auto& [k, v] : pairs) { |
| auto key = MakeBytes(k); |
| auto value = MakeBytes(v); |
| EXPECT_OK(writer->Write(key, value)); |
| } |
| EXPECT_OK_AND_ASSIGN(auto result, writer->Finish()); |
| return result; |
| } |
| |
| std::shared_ptr<MemoryPool> pool_; |
| MemorySlice::SliceComparator comparator_; |
| }; |
| |
| TEST_F(BlockWriteReadTest, AlignedWriteAndRead) { |
| BlockWriter writer(1024, pool_); |
| ASSERT_EQ(writer.Size(), 0); |
| |
| auto key1 = MakeBytes("aaaa"); |
| auto val1 = MakeBytes("1111"); |
| ASSERT_OK(writer.Write(key1, val1)); |
| ASSERT_EQ(writer.Size(), 1); |
| ASSERT_GT(writer.Memory(), 0); |
| |
| auto key2 = MakeBytes("bbbb"); |
| auto val2 = MakeBytes("2222"); |
| auto key3 = MakeBytes("cccc"); |
| auto val3 = MakeBytes("3333"); |
| ASSERT_OK(writer.Write(key2, val2)); |
| ASSERT_OK(writer.Write(key3, val3)); |
| ASSERT_EQ(writer.Size(), 3); |
| |
| auto block = BuildBlock({}, &writer); |
| ASSERT_GT(block.Length(), 0); |
| // Last byte should be ALIGNED (0) because all kv pairs are same-size |
| ASSERT_EQ(block.ReadByte(block.Length() - 1), static_cast<int8_t>(BlockAlignedType::ALIGNED)); |
| |
| // Read back |
| ASSERT_OK_AND_ASSIGN(auto reader, BlockReader::Create(block, comparator_)); |
| ASSERT_EQ(reader->RecordCount(), 3); |
| ASSERT_NE(reader->Comparator(), nullptr); |
| |
| // SeekTo returns byte positions, should be increasing |
| ASSERT_EQ(reader->SeekTo(0), 0); |
| ASSERT_GT(reader->SeekTo(1), 0); |
| ASSERT_GT(reader->SeekTo(2), reader->SeekTo(1)); |
| |
| // BlockInput should be readable |
| ASSERT_TRUE(reader->BlockInput().IsReadable()); |
| |
| // Iterate and verify all entries |
| auto iter = reader->Iterator(); |
| ASSERT_TRUE(iter->HasNext()); |
| ASSERT_OK_AND_ASSIGN(auto entry1, iter->Next()); |
| ASSERT_EQ(entry1.key.ReadStringView(), "aaaa"); |
| ASSERT_EQ(entry1.value.ReadStringView(), "1111"); |
| |
| ASSERT_OK_AND_ASSIGN(auto entry2, iter->Next()); |
| ASSERT_EQ(entry2.key.ReadStringView(), "bbbb"); |
| ASSERT_EQ(entry2.value.ReadStringView(), "2222"); |
| |
| ASSERT_OK_AND_ASSIGN(auto entry3, iter->Next()); |
| ASSERT_EQ(entry3.key.ReadStringView(), "cccc"); |
| ASSERT_EQ(entry3.value.ReadStringView(), "3333"); |
| |
| ASSERT_FALSE(iter->HasNext()); |
| } |
| |
| TEST_F(BlockWriteReadTest, UnalignedWriteAndRead) { |
| BlockWriter writer(1024, pool_, /*aligned=*/false); |
| auto block = BuildBlock({{"a", "short"}, {"bb", "a_longer_value"}}, &writer); |
| ASSERT_GT(block.Length(), 0); |
| // Last byte should be UNALIGNED (1) |
| ASSERT_EQ(block.ReadByte(block.Length() - 1), static_cast<int8_t>(BlockAlignedType::UNALIGNED)); |
| |
| ASSERT_OK_AND_ASSIGN(auto reader, BlockReader::Create(block, comparator_)); |
| ASSERT_EQ(reader->RecordCount(), 2); |
| |
| auto iter = reader->Iterator(); |
| ASSERT_OK_AND_ASSIGN(auto entry1, iter->Next()); |
| ASSERT_EQ(entry1.key.ReadStringView(), "a"); |
| ASSERT_EQ(entry1.value.ReadStringView(), "short"); |
| |
| ASSERT_OK_AND_ASSIGN(auto entry2, iter->Next()); |
| ASSERT_EQ(entry2.key.ReadStringView(), "bb"); |
| ASSERT_EQ(entry2.value.ReadStringView(), "a_longer_value"); |
| |
| ASSERT_FALSE(iter->HasNext()); |
| } |
| |
| TEST_F(BlockWriteReadTest, EmptyBlockWriteAndRead) { |
| BlockWriter writer(1024, pool_); |
| |
| ASSERT_OK_AND_ASSIGN(auto block, writer.Finish()); |
| ASSERT_GT(block.Length(), 0); |
| // Empty block must be UNALIGNED (1) |
| ASSERT_EQ(block.ReadByte(block.Length() - 1), static_cast<int8_t>(BlockAlignedType::UNALIGNED)); |
| |
| // Reader should see 0 records |
| ASSERT_OK_AND_ASSIGN(auto reader, BlockReader::Create(block, comparator_)); |
| ASSERT_EQ(reader->RecordCount(), 0); |
| |
| auto iter = reader->Iterator(); |
| ASSERT_FALSE(iter->HasNext()); |
| } |
| |
| TEST_F(BlockWriteReadTest, ResetAndRewrite) { |
| BlockWriter writer(1024, pool_); |
| |
| auto key = MakeBytes("old_key"); |
| auto val = MakeBytes("old_val"); |
| ASSERT_OK(writer.Write(key, val)); |
| ASSERT_EQ(writer.Size(), 1); |
| |
| writer.Reset(); |
| ASSERT_EQ(writer.Size(), 0); |
| |
| // Write new data and read back |
| auto block = BuildBlock({{"new_key", "new_val"}}, &writer); |
| ASSERT_OK_AND_ASSIGN(auto reader, BlockReader::Create(block, comparator_)); |
| ASSERT_EQ(reader->RecordCount(), 1); |
| |
| auto iter = reader->Iterator(); |
| ASSERT_OK_AND_ASSIGN(auto entry, iter->Next()); |
| ASSERT_EQ(entry.key.ReadStringView(), "new_key"); |
| ASSERT_EQ(entry.value.ReadStringView(), "new_val"); |
| } |
| |
| TEST_F(BlockWriteReadTest, MemoryAlignedVsUnaligned) { |
| BlockWriter aligned_writer(1024, pool_); |
| auto k1 = MakeBytes("aaaa"); |
| auto v1 = MakeBytes("1111"); |
| auto k2 = MakeBytes("bbbb"); |
| auto v2 = MakeBytes("2222"); |
| ASSERT_OK(aligned_writer.Write(k1, v1)); |
| ASSERT_OK(aligned_writer.Write(k2, v2)); |
| |
| BlockWriter unaligned_writer(1024, pool_, /*aligned=*/false); |
| auto k3 = MakeBytes("aaaa"); |
| auto v3 = MakeBytes("1111"); |
| auto k4 = MakeBytes("bbbb"); |
| auto v4 = MakeBytes("2222"); |
| ASSERT_OK(unaligned_writer.Write(k3, v3)); |
| ASSERT_OK(unaligned_writer.Write(k4, v4)); |
| |
| // Unaligned memory should be larger due to position index overhead |
| ASSERT_GT(unaligned_writer.Memory(), aligned_writer.Memory()); |
| } |
| |
| TEST_F(BlockWriteReadTest, SkipKeyAndReadValue) { |
| BlockWriter writer(1024, pool_); |
| auto block = BuildBlock({{"key1", "val1"}, {"key2", "val2"}}, &writer); |
| |
| ASSERT_OK_AND_ASSIGN(auto reader, BlockReader::Create(block, comparator_)); |
| auto iter = reader->Iterator(); |
| |
| ASSERT_TRUE(iter->HasNext()); |
| ASSERT_OK_AND_ASSIGN(auto value1, iter->SkipKeyAndReadValue()); |
| ASSERT_EQ(value1.ReadStringView(), "val1"); |
| |
| ASSERT_TRUE(iter->HasNext()); |
| ASSERT_OK_AND_ASSIGN(auto value2, iter->SkipKeyAndReadValue()); |
| ASSERT_EQ(value2.ReadStringView(), "val2"); |
| |
| ASSERT_FALSE(iter->HasNext()); |
| } |
| |
| TEST_F(BlockWriteReadTest, IteratorSeekTo) { |
| BlockWriter writer(1024, pool_); |
| auto block = |
| BuildBlock({{"apple", "1"}, {"banana", "2"}, {"cherry", "3"}, {"date", "4"}}, &writer); |
| |
| ASSERT_OK_AND_ASSIGN(auto reader, BlockReader::Create(block, comparator_)); |
| |
| // Exact match |
| { |
| auto iter = reader->Iterator(); |
| auto target = MemorySlice::Wrap(MakeBytes("cherry")); |
| ASSERT_OK_AND_ASSIGN(bool found, iter->SeekTo(target)); |
| ASSERT_TRUE(found); |
| ASSERT_TRUE(iter->HasNext()); |
| ASSERT_OK_AND_ASSIGN(auto entry, iter->Next()); |
| ASSERT_EQ(entry.key.ReadStringView(), "cherry"); |
| ASSERT_EQ(entry.value.ReadStringView(), "3"); |
| } |
| |
| // No exact match: "blueberry" should land on "cherry" (first key >= target) |
| { |
| auto iter = reader->Iterator(); |
| auto target = MemorySlice::Wrap(MakeBytes("blueberry")); |
| ASSERT_OK_AND_ASSIGN(bool found, iter->SeekTo(target)); |
| ASSERT_FALSE(found); |
| ASSERT_TRUE(iter->HasNext()); |
| ASSERT_OK_AND_ASSIGN(auto entry, iter->Next()); |
| ASSERT_EQ(entry.key.ReadStringView(), "cherry"); |
| } |
| } |
| |
| } // namespace paimon::test |