blob: 4091718fc8795e281a5142b5c892f253a43773e7 [file]
/*
* 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