blob: 12f90a1fb138eb6bb6a658f5f06d0a25f265cd95 [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/format/parquet/file_reader_wrapper.h"
#include <map>
#include <string>
#include "arrow/api.h"
#include "arrow/array/builder_binary.h"
#include "arrow/array/builder_nested.h"
#include "arrow/array/builder_primitive.h"
#include "arrow/c/abi.h"
#include "arrow/c/bridge.h"
#include "arrow/io/caching.h"
#include "arrow/memory_pool.h"
#include "gtest/gtest.h"
#include "paimon/common/utils/arrow/arrow_input_stream_adapter.h"
#include "paimon/common/utils/arrow/mem_utils.h"
#include "paimon/common/utils/arrow/status_utils.h"
#include "paimon/common/utils/path_util.h"
#include "paimon/format/parquet/parquet_field_id_converter.h"
#include "paimon/format/parquet/parquet_format_defs.h"
#include "paimon/format/parquet/parquet_format_writer.h"
#include "paimon/fs/file_system.h"
#include "paimon/fs/local/local_file_system.h"
#include "paimon/memory/memory_pool.h"
#include "paimon/record_batch.h"
#include "paimon/testing/utils/testharness.h"
#include "parquet/arrow/reader.h"
#include "parquet/properties.h"
namespace arrow {
class Array;
} // namespace arrow
namespace paimon::parquet::test {
class FileReaderWrapperTest : public ::testing::Test {
public:
void SetUp() override {
dir_ = paimon::test::UniqueTestDirectory::Create();
ASSERT_TRUE(dir_);
fs_ = std::make_shared<LocalFileSystem>();
pool_ = GetDefaultPool();
arrow_pool_ = GetArrowPool(pool_);
batch_size_ = 512;
}
void TearDown() override {}
std::pair<std::shared_ptr<arrow::Schema>, std::shared_ptr<arrow::DataType>> PrepareArrowSchema()
const {
auto string_field = arrow::field(
"col1", arrow::utf8(),
arrow::KeyValueMetadata::Make({ParquetFieldIdConverter::PARQUET_FIELD_ID}, {"0"}));
auto int_field = arrow::field(
"col2", arrow::int32(),
arrow::KeyValueMetadata::Make({ParquetFieldIdConverter::PARQUET_FIELD_ID}, {"1"}));
auto bool_field = arrow::field(
"col3", arrow::boolean(),
arrow::KeyValueMetadata::Make({ParquetFieldIdConverter::PARQUET_FIELD_ID}, {"2"}));
auto struct_type = arrow::struct_({string_field, int_field, bool_field});
return std::make_pair(
arrow::schema(arrow::FieldVector({string_field, int_field, bool_field})), struct_type);
}
std::shared_ptr<arrow::Array> PrepareArray(const std::shared_ptr<arrow::DataType>& data_type,
int32_t record_batch_size,
int32_t offset = 0) const {
arrow::StructBuilder struct_builder(
data_type, arrow::default_memory_pool(),
{std::make_shared<arrow::StringBuilder>(), std::make_shared<arrow::Int32Builder>(),
std::make_shared<arrow::BooleanBuilder>()});
auto string_builder = static_cast<arrow::StringBuilder*>(struct_builder.field_builder(0));
auto int_builder = static_cast<arrow::Int32Builder*>(struct_builder.field_builder(1));
auto bool_builder = static_cast<arrow::BooleanBuilder*>(struct_builder.field_builder(2));
for (int32_t i = 0 + offset; i < record_batch_size + offset; ++i) {
EXPECT_TRUE(struct_builder.Append().ok());
EXPECT_TRUE(string_builder->Append("str_" + std::to_string(i)).ok());
if (i % 3 == 0) {
// test null
EXPECT_TRUE(int_builder->AppendNull().ok());
} else {
EXPECT_TRUE(int_builder->Append(i).ok());
}
EXPECT_TRUE(bool_builder->Append(static_cast<bool>(i % 2)).ok());
}
std::shared_ptr<arrow::Array> array;
EXPECT_TRUE(struct_builder.Finish(&array).ok());
return array;
}
void AddRecordBatchOnce(const std::shared_ptr<ParquetFormatWriter>& format_writer,
const std::shared_ptr<arrow::DataType>& struct_type,
int32_t record_batch_size, int32_t offset) const {
auto array = PrepareArray(struct_type, record_batch_size, offset);
auto arrow_array = std::make_unique<ArrowArray>();
ASSERT_TRUE(arrow::ExportArray(*array, arrow_array.get()).ok());
auto batch = std::make_shared<RecordBatch>(
/*partition=*/std::map<std::string, std::string>(), /*bucket=*/-1,
/*row_kinds=*/std::vector<RecordBatch::RowKind>(), arrow_array.get());
ASSERT_OK(format_writer->AddBatch(batch->GetData()));
}
Result<std::unique_ptr<FileReaderWrapper>> PrepareReaderWrapper(const std::string& file_path) {
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<InputStream> in, fs_->Open(file_path));
PAIMON_ASSIGN_OR_RAISE(uint64_t file_length, in->Length());
auto input_stream = std::make_unique<ArrowInputStreamAdapter>(in, arrow_pool_, file_length);
::parquet::arrow::FileReaderBuilder file_reader_builder;
::parquet::ReaderProperties reader_properties;
reader_properties.enable_buffered_stream();
PAIMON_RETURN_NOT_OK_FROM_ARROW(
file_reader_builder.Open(std::move(input_stream), reader_properties));
::parquet::ArrowReaderProperties arrow_reader_props;
arrow_reader_props.set_pre_buffer(true);
arrow_reader_props.set_batch_size(static_cast<int64_t>(batch_size_));
arrow_reader_props.set_use_threads(true);
arrow_reader_props.set_cache_options(arrow::io::CacheOptions::Defaults());
std::unique_ptr<::parquet::arrow::FileReader> file_reader;
PAIMON_RETURN_NOT_OK_FROM_ARROW(file_reader_builder.memory_pool(arrow_pool_.get())
->properties(arrow_reader_props)
->Build(&file_reader));
return FileReaderWrapper::Create(std::move(file_reader));
}
void PrepareParquetFile(const std::string& file_path, int32_t row_count) {
auto schema_pair = PrepareArrowSchema();
const auto& arrow_schema = schema_pair.first;
const auto& struct_type = schema_pair.second;
ASSERT_OK_AND_ASSIGN(std::shared_ptr<OutputStream> out,
fs_->Create(file_path, /*overwrite=*/false));
::parquet::WriterProperties::Builder builder;
builder.write_batch_size(10);
builder.max_row_group_length(1000);
builder.enable_store_decimal_as_integer();
auto writer_properties = builder.build();
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<ParquetFormatWriter> format_writer,
ParquetFormatWriter::Create(out, arrow_schema, writer_properties,
DEFAULT_PARQUET_WRITER_MAX_MEMORY_USE, arrow_pool_));
AddRecordBatchOnce(format_writer, struct_type, /*record_batch_size=*/row_count,
/*offset=*/0);
ASSERT_OK(format_writer->Flush());
ASSERT_OK(format_writer->Finish());
ASSERT_OK(out->Flush());
ASSERT_OK(out->Close());
}
private:
std::unique_ptr<paimon::test::UniqueTestDirectory> dir_;
std::shared_ptr<FileSystem> fs_;
std::shared_ptr<MemoryPool> pool_;
std::shared_ptr<arrow::MemoryPool> arrow_pool_;
int32_t batch_size_;
};
TEST_F(FileReaderWrapperTest, EmptyFile) {
std::string file_path = PathUtil::JoinPath(dir_->Str(), "test.parquet");
PrepareParquetFile(file_path, /*row_count=*/0);
ASSERT_OK_AND_ASSIGN(auto reader_wrapper, PrepareReaderWrapper(file_path));
ASSERT_EQ(0, reader_wrapper->GetNumberOfRows());
ASSERT_EQ(0, reader_wrapper->GetNumberOfRowGroups());
ASSERT_EQ(std::numeric_limits<uint64_t>::max(), reader_wrapper->GetNextRowToRead());
ASSERT_EQ(std::numeric_limits<uint64_t>::max(),
reader_wrapper->GetPreviousBatchFirstRowNumber().value());
ASSERT_OK_AND_ASSIGN(auto batch, reader_wrapper->Next());
ASSERT_EQ(0, reader_wrapper->GetPreviousBatchFirstRowNumber().value());
ASSERT_EQ(0, reader_wrapper->GetNextRowToRead());
ASSERT_TRUE(reader_wrapper->GetAllRowGroupRanges().empty());
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::RecordBatch> record_batch, reader_wrapper->Next());
ASSERT_FALSE(record_batch);
}
TEST_F(FileReaderWrapperTest, NullFileReader) {
ASSERT_NOK_WITH_MSG(FileReaderWrapper::Create(nullptr),
"file reader wrapper create failed. file reader is nullptr");
}
TEST_F(FileReaderWrapperTest, Simple) {
std::string file_path = PathUtil::JoinPath(dir_->Str(), "test.parquet");
PrepareParquetFile(file_path, /*row_count=*/5500);
ASSERT_OK_AND_ASSIGN(auto reader_wrapper, PrepareReaderWrapper(file_path));
ASSERT_EQ(5500, reader_wrapper->GetNumberOfRows());
ASSERT_EQ(6, reader_wrapper->GetNumberOfRowGroups());
ASSERT_EQ(std::numeric_limits<uint64_t>::max(), reader_wrapper->GetNextRowToRead());
ASSERT_EQ(std::numeric_limits<uint64_t>::max(),
reader_wrapper->GetPreviousBatchFirstRowNumber().value());
std::vector<std::pair<uint64_t, uint64_t>> expected_all_row_group_ranges = {
{0, 1000}, {1000, 2000}, {2000, 3000}, {3000, 4000}, {4000, 5000}, {5000, 5500}};
ASSERT_EQ(expected_all_row_group_ranges, reader_wrapper->GetAllRowGroupRanges());
ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::RecordBatch> record_batch, reader_wrapper->Next());
ASSERT_TRUE(record_batch);
ASSERT_EQ(512, record_batch->num_rows());
ASSERT_EQ(512, reader_wrapper->GetNextRowToRead());
ASSERT_EQ(0, reader_wrapper->GetPreviousBatchFirstRowNumber().value());
ASSERT_OK_AND_ASSIGN(record_batch, reader_wrapper->Next());
ASSERT_TRUE(record_batch);
ASSERT_EQ(488, record_batch->num_rows());
ASSERT_EQ(1000, reader_wrapper->GetNextRowToRead());
ASSERT_EQ(512, reader_wrapper->GetPreviousBatchFirstRowNumber().value());
ASSERT_OK_AND_ASSIGN(record_batch, reader_wrapper->Next());
ASSERT_TRUE(record_batch);
ASSERT_EQ(512, record_batch->num_rows());
ASSERT_EQ(1512, reader_wrapper->GetNextRowToRead());
ASSERT_EQ(1000, reader_wrapper->GetPreviousBatchFirstRowNumber().value());
ASSERT_NOK_WITH_MSG(reader_wrapper->SeekToRow(1001),
"should not be in the middle of readable range");
ASSERT_OK(reader_wrapper->SeekToRow(1000));
ASSERT_OK_AND_ASSIGN(record_batch, reader_wrapper->Next());
ASSERT_TRUE(record_batch);
ASSERT_EQ(512, record_batch->num_rows());
ASSERT_EQ(1512, reader_wrapper->GetNextRowToRead());
ASSERT_EQ(1000, reader_wrapper->GetPreviousBatchFirstRowNumber().value());
ASSERT_OK(reader_wrapper->SeekToRow(5600));
ASSERT_EQ(5500, reader_wrapper->GetNextRowToRead());
ASSERT_EQ(6, reader_wrapper->current_row_group_idx_);
ASSERT_EQ(1000, reader_wrapper->GetPreviousBatchFirstRowNumber().value());
ASSERT_OK_AND_ASSIGN(record_batch, reader_wrapper->Next());
ASSERT_FALSE(record_batch);
ASSERT_EQ(5500, reader_wrapper->GetNextRowToRead());
ASSERT_EQ(5500, reader_wrapper->GetPreviousBatchFirstRowNumber().value());
}
TEST_F(FileReaderWrapperTest, GetRowGroupRanges) {
std::string file_path = PathUtil::JoinPath(dir_->Str(), "test.parquet");
PrepareParquetFile(file_path, /*row_count=*/5500);
ASSERT_OK_AND_ASSIGN(auto reader_wrapper, PrepareReaderWrapper(file_path));
ASSERT_OK_AND_ASSIGN(auto ranges, reader_wrapper->GetRowGroupRanges({0, 3, 5}));
std::vector<std::pair<uint64_t, uint64_t>> expected_read_ranges = {
{0, 1000}, {3000, 4000}, {5000, 5500}};
ASSERT_EQ(expected_read_ranges, ranges);
ASSERT_NOK_WITH_MSG(reader_wrapper->GetRowGroupRanges({0, 3, 6}), "out of bound");
ASSERT_OK_AND_ASSIGN(ranges, reader_wrapper->GetRowGroupRanges({}));
ASSERT_TRUE(ranges.empty());
}
TEST_F(FileReaderWrapperTest, ReadRangesToRowGroupIds) {
std::string file_path = PathUtil::JoinPath(dir_->Str(), "test.parquet");
PrepareParquetFile(file_path, /*row_count=*/5500);
ASSERT_OK_AND_ASSIGN(auto reader_wrapper, PrepareReaderWrapper(file_path));
std::set<int32_t> expected_row_group_ids = {0, 3, 5};
std::vector<std::pair<uint64_t, uint64_t>> read_ranges = {
{0, 1000}, {3000, 4000}, {5000, 5500}};
ASSERT_OK_AND_ASSIGN(auto row_group_ids, reader_wrapper->ReadRangesToRowGroupIds(read_ranges));
ASSERT_EQ(expected_row_group_ids, row_group_ids);
std::vector<std::pair<uint64_t, uint64_t>> invalid_ranges = {
{0, 1000}, {3000, 4000}, {5000, 5600}};
ASSERT_NOK_WITH_MSG(reader_wrapper->ReadRangesToRowGroupIds(invalid_ranges),
"not match with row group range bound");
ASSERT_OK_AND_ASSIGN(row_group_ids, reader_wrapper->ReadRangesToRowGroupIds({}));
ASSERT_TRUE(row_group_ids.empty());
}
TEST_F(FileReaderWrapperTest, FilterRowGroupsByReadRanges) {
std::string file_path = PathUtil::JoinPath(dir_->Str(), "test.parquet");
PrepareParquetFile(file_path, /*row_count=*/5500);
ASSERT_OK_AND_ASSIGN(auto reader_wrapper, PrepareReaderWrapper(file_path));
std::set<int32_t> expected_row_group_ids = {0, 5};
std::vector<std::pair<uint64_t, uint64_t>> read_ranges = {
{0, 1000}, {3000, 4000}, {5000, 5500}};
ASSERT_OK_AND_ASSIGN(auto row_group_ids,
reader_wrapper->FilterRowGroupsByReadRanges(read_ranges, {0, 1, 2, 4, 5}));
ASSERT_EQ(expected_row_group_ids, row_group_ids);
ASSERT_OK_AND_ASSIGN(row_group_ids,
reader_wrapper->FilterRowGroupsByReadRanges(read_ranges, {}));
ASSERT_TRUE(row_group_ids.empty());
}
TEST_F(FileReaderWrapperTest, PrepareForReading) {
std::string file_path = PathUtil::JoinPath(dir_->Str(), "test.parquet");
PrepareParquetFile(file_path, /*row_count=*/5500);
ASSERT_OK_AND_ASSIGN(auto reader_wrapper, PrepareReaderWrapper(file_path));
ASSERT_OK(reader_wrapper->PrepareForReading(/*row_group_indices=*/{1},
/*column_indices=*/{0}));
// seek before actual read range
ASSERT_OK(reader_wrapper->SeekToRow(0));
ASSERT_EQ(1000, reader_wrapper->GetNextRowToRead());
ASSERT_EQ(std::numeric_limits<uint64_t>::max(),
reader_wrapper->GetPreviousBatchFirstRowNumber().value());
ASSERT_OK_AND_ASSIGN(auto record_batch, reader_wrapper->Next());
ASSERT_EQ(512, record_batch->num_rows());
ASSERT_EQ(1, record_batch->num_columns());
ASSERT_EQ(1512, reader_wrapper->GetNextRowToRead());
ASSERT_EQ(1000, reader_wrapper->GetPreviousBatchFirstRowNumber().value());
ASSERT_OK_AND_ASSIGN(record_batch, reader_wrapper->Next());
ASSERT_TRUE(record_batch);
ASSERT_EQ(488, record_batch->num_rows());
ASSERT_EQ(5500, reader_wrapper->GetNextRowToRead());
ASSERT_EQ(1512, reader_wrapper->GetPreviousBatchFirstRowNumber().value());
ASSERT_OK_AND_ASSIGN(record_batch, reader_wrapper->Next());
ASSERT_FALSE(record_batch);
// empty column indices
ASSERT_OK(reader_wrapper->PrepareForReading(/*row_group_indices=*/{0, 1},
/*column_indices=*/{}));
ASSERT_EQ(0, reader_wrapper->GetNextRowToRead());
ASSERT_EQ(std::numeric_limits<uint64_t>::max(),
reader_wrapper->GetPreviousBatchFirstRowNumber().value());
ASSERT_OK_AND_ASSIGN(record_batch, reader_wrapper->Next());
ASSERT_EQ(512, record_batch->num_rows());
ASSERT_EQ(0, record_batch->num_columns());
// empty row group indices
ASSERT_OK(reader_wrapper->PrepareForReading(/*row_group_indices=*/{},
/*column_indices=*/{0}));
ASSERT_EQ(5500, reader_wrapper->GetNextRowToRead());
ASSERT_EQ(std::numeric_limits<uint64_t>::max(),
reader_wrapper->GetPreviousBatchFirstRowNumber().value());
}
} // namespace paimon::parquet::test