| /* |
| * 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/reader/blob_view_resolving_batch_reader.h" |
| |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "arrow/api.h" |
| #include "arrow/array/array_binary.h" |
| #include "arrow/array/array_nested.h" |
| #include "arrow/array/builder_binary.h" |
| #include "arrow/c/abi.h" |
| #include "arrow/c/bridge.h" |
| #include "gtest/gtest.h" |
| #include "paimon/catalog/identifier.h" |
| #include "paimon/common/data/blob_descriptor.h" |
| #include "paimon/common/data/blob_utils.h" |
| #include "paimon/common/data/blob_view_struct.h" |
| #include "paimon/common/metrics/metrics_impl.h" |
| #include "paimon/common/utils/arrow/status_utils.h" |
| #include "paimon/memory/bytes.h" |
| #include "paimon/memory/memory_pool.h" |
| #include "paimon/reader/batch_reader.h" |
| #include "paimon/result.h" |
| #include "paimon/status.h" |
| #include "paimon/testing/utils/read_result_collector.h" |
| #include "paimon/testing/utils/testharness.h" |
| |
| namespace paimon::test { |
| |
| class BlobViewResolvingBatchReaderTest : public ::testing::Test { |
| public: |
| void SetUp() override { |
| pool_ = GetDefaultPool(); |
| } |
| |
| void TearDown() override { |
| pool_.reset(); |
| } |
| |
| class InMemoryBatchReader : public BatchReader { |
| public: |
| explicit InMemoryBatchReader(const std::shared_ptr<arrow::StructArray>& struct_array) |
| : struct_array_(struct_array) { |
| if (!struct_array_) { |
| exhausted_ = true; |
| } |
| } |
| |
| Result<ReadBatch> NextBatch() override { |
| if (exhausted_) { |
| return MakeEofBatch(); |
| } |
| exhausted_ = true; |
| auto c_array = std::make_unique<ArrowArray>(); |
| auto c_schema = std::make_unique<ArrowSchema>(); |
| PAIMON_RETURN_NOT_OK_FROM_ARROW( |
| arrow::ExportArray(*struct_array_, c_array.get(), c_schema.get())); |
| return std::make_pair(std::move(c_array), std::move(c_schema)); |
| } |
| |
| std::shared_ptr<Metrics> GetReaderMetrics() const override { |
| return std::make_shared<MetricsImpl>(); |
| } |
| |
| void Close() override {} |
| |
| private: |
| std::shared_ptr<arrow::StructArray> struct_array_; |
| bool exhausted_ = false; |
| }; |
| |
| std::string MakeBlobViewStructBytes(const std::string& database, const std::string& table, |
| int32_t field_id, int64_t row_id) const { |
| Identifier identifier(database, table); |
| BlobViewStruct view_struct(identifier, field_id, row_id); |
| auto bytes = view_struct.Serialize(pool_); |
| return std::string(bytes->data(), bytes->size()); |
| } |
| |
| Result<std::string> MakeBlobDescriptorBytes(const std::string& uri, int64_t offset, |
| int64_t length) const { |
| PAIMON_ASSIGN_OR_RAISE(auto descriptor, BlobDescriptor::Create(uri, offset, length)); |
| auto bytes = descriptor->Serialize(pool_); |
| return std::string(bytes->data(), bytes->size()); |
| } |
| |
| std::shared_ptr<arrow::StructArray> BuildStructArray(const std::vector<std::string>& values, |
| const std::vector<bool>& valid) const { |
| arrow::LargeBinaryBuilder builder; |
| EXPECT_TRUE(builder.Reserve(static_cast<int64_t>(values.size())).ok()); |
| for (size_t i = 0; i < values.size(); ++i) { |
| if (!valid[i]) { |
| EXPECT_TRUE(builder.AppendNull().ok()); |
| } else { |
| EXPECT_TRUE(builder |
| .Append(reinterpret_cast<const uint8_t*>(values[i].data()), |
| static_cast<int64_t>(values[i].size())) |
| .ok()); |
| } |
| } |
| std::shared_ptr<arrow::Array> array; |
| EXPECT_TRUE(builder.Finish(&array).ok()); |
| |
| arrow::FieldVector fields = {BlobUtils::ToArrowField("blob_col", /*nullable=*/true)}; |
| arrow::ArrayVector arrays = {array}; |
| auto result = arrow::StructArray::Make(arrays, fields).ValueOrDie(); |
| return result; |
| } |
| |
| private: |
| std::shared_ptr<MemoryPool> pool_; |
| }; |
| |
| TEST_F(BlobViewResolvingBatchReaderTest, TestEofBatch) { |
| auto inner_reader = std::make_unique<InMemoryBatchReader>(nullptr); |
| auto resolver = BlobViewResolver([](const BlobViewStruct&) -> Result<std::shared_ptr<Bytes>> { |
| return std::shared_ptr<Bytes>(); |
| }); |
| BlobViewResolvingBatchReader reader(std::move(inner_reader), {"blob_col"}, std::move(resolver), |
| pool_); |
| ASSERT_OK_AND_ASSIGN(auto batch, reader.NextBatch()); |
| ASSERT_TRUE(BatchReader::IsEofBatch(batch)); |
| } |
| |
| TEST_F(BlobViewResolvingBatchReaderTest, TestEmptyReadBlobViewFields) { |
| std::string view_bytes = MakeBlobViewStructBytes("db", "table", /*field_id=*/1, /*row_id=*/7); |
| std::shared_ptr<arrow::StructArray> struct_array = BuildStructArray({view_bytes}, {true}); |
| |
| bool resolver_called = false; |
| auto resolver = BlobViewResolver( |
| [&resolver_called](const BlobViewStruct&) -> Result<std::shared_ptr<Bytes>> { |
| resolver_called = true; |
| return std::shared_ptr<Bytes>(); |
| }); |
| |
| auto inner_reader = std::make_unique<InMemoryBatchReader>(struct_array); |
| BlobViewResolvingBatchReader reader(std::move(inner_reader), /*read_blob_view_fields=*/{}, |
| std::move(resolver), pool_); |
| ASSERT_OK_AND_ASSIGN(auto result_array, ReadResultCollector::CollectResult(&reader)); |
| auto expected_array = std::make_shared<arrow::ChunkedArray>(struct_array); |
| ASSERT_TRUE(expected_array->Equals(*result_array)); |
| ASSERT_FALSE(resolver_called); |
| } |
| |
| TEST_F(BlobViewResolvingBatchReaderTest, TestResolvesBlobViewColumn) { |
| auto row0_view = MakeBlobViewStructBytes("db", "tbl", /*field_id=*/3, /*row_id=*/100); |
| auto row1_view = MakeBlobViewStructBytes("db", "tbl", /*field_id=*/3, /*row_id=*/200); |
| std::shared_ptr<arrow::StructArray> src_struct = |
| BuildStructArray({row0_view, row1_view}, {true, true}); |
| |
| ASSERT_OK_AND_ASSIGN(auto expected_row0_descriptor, |
| MakeBlobDescriptorBytes("/path/a", /*offset=*/0, /*length=*/8)); |
| ASSERT_OK_AND_ASSIGN(auto expected_row1_descriptor, |
| MakeBlobDescriptorBytes("/path/b", /*offset=*/16, /*length=*/32)); |
| |
| auto resolver = |
| BlobViewResolver([&](const BlobViewStruct& view_struct) -> Result<std::shared_ptr<Bytes>> { |
| if (view_struct.RowId() == 100) { |
| return std::make_shared<Bytes>(expected_row0_descriptor, pool_.get()); |
| } |
| if (view_struct.RowId() == 200) { |
| return std::make_shared<Bytes>(expected_row1_descriptor, pool_.get()); |
| } |
| return Status::Invalid("unexpected view struct"); |
| }); |
| |
| auto inner_reader = std::make_unique<InMemoryBatchReader>(src_struct); |
| BlobViewResolvingBatchReader reader(std::move(inner_reader), {"blob_col"}, std::move(resolver), |
| pool_); |
| ASSERT_OK_AND_ASSIGN(auto result_array, ReadResultCollector::CollectResult(&reader)); |
| auto struct_array = std::dynamic_pointer_cast<arrow::StructArray>(result_array->chunk(0)); |
| |
| auto result_blob_column = |
| std::dynamic_pointer_cast<arrow::LargeBinaryArray>(struct_array->field(0)); |
| ASSERT_FALSE(result_blob_column->IsNull(0)); |
| ASSERT_FALSE(result_blob_column->IsNull(1)); |
| ASSERT_EQ(result_blob_column->GetString(0), expected_row0_descriptor); |
| ASSERT_EQ(result_blob_column->GetString(1), expected_row1_descriptor); |
| } |
| |
| TEST_F(BlobViewResolvingBatchReaderTest, TestResolverError) { |
| auto view_bytes = MakeBlobViewStructBytes("db", "tbl", /*field_id=*/1, /*row_id=*/5); |
| std::shared_ptr<arrow::StructArray> src_struct = BuildStructArray({view_bytes}, {true}); |
| auto resolver = BlobViewResolver([](const BlobViewStruct&) -> Result<std::shared_ptr<Bytes>> { |
| return Status::Invalid("cache miss"); |
| }); |
| auto inner_reader = std::make_unique<InMemoryBatchReader>(src_struct); |
| BlobViewResolvingBatchReader reader(std::move(inner_reader), {"blob_col"}, std::move(resolver), |
| pool_); |
| ASSERT_NOK_WITH_MSG(reader.NextBatch(), "cache miss"); |
| } |
| |
| } // namespace paimon::test |