blob: bcd4af2106341e8d4d3df72d4f5ee24abb4c3d67 [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/parquet_file_batch_reader.h"
#include <atomic>
#include <functional>
#include <iostream>
#include <limits>
#include <memory>
#include <string>
#include "arrow/api.h"
#include "arrow/array/array_base.h"
#include "arrow/array/array_nested.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/io/interfaces.h"
#include "arrow/ipc/api.h"
#include "arrow/ipc/json_simple.h"
#include "gtest/gtest.h"
#include "paimon/common/types/data_field.h"
#include "paimon/common/utils/arrow/arrow_input_stream_adapter.h"
#include "paimon/common/utils/arrow/arrow_utils.h"
#include "paimon/common/utils/arrow/mem_utils.h"
#include "paimon/common/utils/checked_cast.h"
#include "paimon/common/utils/date_time_utils.h"
#include "paimon/common/utils/path_util.h"
#include "paimon/defs.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/format/parquet/parquet_reader_builder.h"
#include "paimon/fs/file_system.h"
#include "paimon/fs/local/local_file_system.h"
#include "paimon/memory/memory_pool.h"
#include "paimon/predicate/literal.h"
#include "paimon/predicate/predicate_builder.h"
#include "paimon/reader/batch_reader.h"
#include "paimon/testing/utils/counting_cache_test_utils.h"
#include "paimon/testing/utils/read_result_collector.h"
#include "paimon/testing/utils/testharness.h"
#include "paimon/testing/utils/timezone_guard.h"
#include "paimon/utils/roaring_bitmap32.h"
#include "parquet/properties.h"
namespace paimon {
class Predicate;
} // namespace paimon
namespace paimon::parquet::test {
std::string SerializeSchemaToString(const std::shared_ptr<arrow::Schema>& schema) {
std::shared_ptr<arrow::Buffer> serialized = arrow::ipc::SerializeSchema(*schema).ValueOrDie();
return std::string(reinterpret_cast<const char*>(serialized->data()),
static_cast<size_t>(serialized->size()));
}
class FailedUriInputStream : public InputStream {
public:
explicit FailedUriInputStream(const std::shared_ptr<InputStream>& input) : input_(input) {}
Status Seek(int64_t offset, SeekOrigin origin) override {
return input_->Seek(offset, origin);
}
Result<int64_t> GetPos() const override {
return input_->GetPos();
}
Result<int64_t> Read(char* buffer, int64_t size) override {
return input_->Read(buffer, size);
}
Result<int64_t> Read(char* buffer, int64_t size, int64_t offset) override {
return input_->Read(buffer, size, offset);
}
void ReadAsync(char* buffer, int64_t size, int64_t offset,
std::function<void(Status)>&& callback) override {
return input_->ReadAsync(buffer, size, offset, std::move(callback));
}
Result<std::string> GetUri() const override {
return Status::Invalid("failed to get uri");
}
Result<int64_t> Length() const override {
return input_->Length();
}
Status Close() override {
return input_->Close();
}
private:
std::shared_ptr<InputStream> input_;
};
class ParquetFileBatchReaderTest : public ::testing::Test,
public ::testing::WithParamInterface<bool> {
public:
static std::shared_ptr<arrow::Field> WithMapSelectedKeys(
const std::shared_ptr<arrow::Field>& field, const std::string& selected_keys) {
auto metadata =
field->metadata() ? field->metadata()->Copy() : arrow::key_value_metadata({});
auto set_status = metadata->Set(DataField::MAP_SELECTED_KEYS, selected_keys);
EXPECT_TRUE(set_status.ok()) << set_status.ToString();
return field->WithMetadata(metadata);
}
static std::shared_ptr<arrow::Schema> MakeReadSchema(const arrow::FieldVector& fields) {
return arrow::schema(fields);
}
void SetUp() override {
dir_ = paimon::test::UniqueTestDirectory::Create();
ASSERT_TRUE(dir_);
fs_ = std::make_shared<LocalFileSystem>();
pool_ = GetArrowPool(GetDefaultPool());
batch_size_ = 10;
file_path_ = PathUtil::JoinPath(dir_->Str(), "test.parquet");
arrow::FieldVector fields = {
arrow::field("f1", arrow::boolean()),
arrow::field("f2", arrow::int8()),
arrow::field("f3", arrow::int16()),
arrow::field("f4", arrow::int32()),
arrow::field("f5", arrow::int64()),
arrow::field("f6", arrow::float32()),
arrow::field("f7", arrow::float64()),
arrow::field("f8", arrow::utf8()),
arrow::field("f9", arrow::binary()),
arrow::field("f10", arrow::map(arrow::list(arrow::float32()),
arrow::struct_({arrow::field("f0", arrow::boolean()),
arrow::field("f1", arrow::int64())}))),
arrow::field("f11", arrow::timestamp(arrow::TimeUnit::NANO)),
arrow::field("f12", arrow::date32()),
arrow::field("f13", arrow::decimal128(2, 2))};
schema_ = arrow::schema(fields);
struct_array_ = std::dynamic_pointer_cast<arrow::StructArray>(
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
[true, 31, null, 300001, 3000000001, 3.1, 300.000000001, "s31", "a31", [[[5.11, 5.21], [true, 61]]], "1970-01-01 00:00:00.000071", 81, "0.91"],
[false, 32, 302, 300002, 3000000002, 3.2, 300.000000002, "s32", "a32", [[[5.12, 5.22], [false, 62]]], "1970-01-01 00:00:00.000072", 82, "0.92"],
[true, 33, 303, 300003, 3000000003, 3.3, 300.000000003, "s33", "a33", null, "1970-01-01 00:00:00.000073", 83, "0.93"],
[false, 34, 304, 300004, 3000000004, 3.4, 300.000000004, "s34", "a34", [[[5.141, 5.241], [false, 641]], [[5.14, 5.24], [false, 64]]], "1970-01-01 00:00:00.000074", 84, "0.94"],
[true, 35, 305, 300005, 3000000005, 3.5, 300.000000005, "s35", "a35", [[[5.15, 5.25], [true, 65]]], "1970-01-01 00:00:00.000075", 85, "0.95"],
[false, 36, 306, 300006, 3000000006, 3.6, 300.000000006, "s36", "a36", [[[5.16, 5.26], null]], "1970-01-01 00:00:00.000076", 86, "0.96"]
])")
.ValueOrDie());
}
void TearDown() override {}
void WriteArray(const std::string& file_path, const std::shared_ptr<arrow::Array>& src_array,
const std::shared_ptr<arrow::Schema>& arrow_schema, int64_t write_batch_size,
bool enable_dictionary, int64_t max_row_group_length,
int64_t max_page_size = 1024 * 1024 * 1024) const {
ASSERT_OK_AND_ASSIGN(std::shared_ptr<OutputStream> out,
fs_->Create(file_path, /*overwrite=*/true));
::parquet::WriterProperties::Builder builder;
builder.write_batch_size(write_batch_size);
builder.max_row_group_length(max_row_group_length);
builder.data_pagesize(max_page_size);
builder.enable_write_page_index();
enable_dictionary ? builder.enable_dictionary() : builder.disable_dictionary();
auto writer_properties = builder.build();
ASSERT_OK_AND_ASSIGN(auto format_writer, ParquetFormatWriter::Create(
out, arrow_schema, writer_properties,
DEFAULT_PARQUET_WRITER_MAX_MEMORY_USE, pool_));
auto arrow_array = std::make_unique<ArrowArray>();
ASSERT_TRUE(arrow::ExportArray(*src_array, arrow_array.get()).ok());
ASSERT_OK(format_writer->AddBatch(arrow_array.get()));
ASSERT_OK(format_writer->Flush());
ASSERT_OK(format_writer->Finish());
ASSERT_OK(out->Flush());
ASSERT_OK(out->Close());
}
std::unique_ptr<ParquetFileBatchReader> PrepareParquetFileBatchReader(
const std::string& file_name, const std::shared_ptr<arrow::Schema>& read_schema,
const std::shared_ptr<Predicate>& predicate,
const std::optional<RoaringBitmap32>& selection_bitmap, int32_t batch_size,
bool enable_page_level_filter = false) const {
EXPECT_OK_AND_ASSIGN(auto input_stream, fs_->Open(file_name));
auto length = fs_->GetFileStatus(file_name).value().GetLen();
auto in_stream =
std::make_unique<ArrowInputStreamAdapter>(std::move(input_stream), length, pool_);
auto storage_read_bytes = in_stream->StorageReadBytes();
std::map<std::string, std::string> options;
options[PARQUET_READ_ENABLE_PAGE_INDEX_FILTER] =
enable_page_level_filter ? "true" : "false";
return PrepareParquetFileBatchReader(std::move(in_stream), options, read_schema, predicate,
selection_bitmap, batch_size,
std::move(storage_read_bytes));
}
std::unique_ptr<paimon::parquet::ParquetFileBatchReader> PrepareParquetFileBatchReader(
std::unique_ptr<arrow::io::RandomAccessFile>&& in_stream,
const std::map<std::string, std::string>& options,
const std::shared_ptr<arrow::Schema>& read_schema,
const std::shared_ptr<Predicate>& predicate,
const std::optional<RoaringBitmap32>& selection_bitmap, int32_t batch_size,
std::shared_ptr<std::atomic<uint64_t>> storage_read_bytes = nullptr) const {
EXPECT_OK_AND_ASSIGN(auto parquet_batch_reader,
ParquetFileBatchReader::Create(
std::move(in_stream), options, batch_size,
/*file_metadata=*/nullptr, std::move(storage_read_bytes), pool_));
std::unique_ptr<ArrowSchema> c_schema = std::make_unique<ArrowSchema>();
auto arrow_status = arrow::ExportSchema(*read_schema, c_schema.get());
EXPECT_TRUE(arrow_status.ok());
EXPECT_OK(parquet_batch_reader->SetReadSchema(c_schema.get(), predicate, selection_bitmap));
return parquet_batch_reader;
}
protected:
std::string file_path_;
std::unique_ptr<paimon::test::UniqueTestDirectory> dir_;
std::shared_ptr<FileSystem> fs_;
std::shared_ptr<arrow::MemoryPool> pool_;
int32_t batch_size_;
std::shared_ptr<arrow::Schema> schema_;
std::shared_ptr<arrow::StructArray> struct_array_;
};
static std::shared_ptr<arrow::StructArray> MakeSequentialIntData(int32_t num_rows) {
arrow::Int32Builder val_builder;
EXPECT_TRUE(val_builder.Reserve(num_rows).ok());
for (int32_t i = 0; i < num_rows; ++i) {
val_builder.UnsafeAppend(i);
}
auto val_array = val_builder.Finish().ValueOrDie();
auto field = arrow::field("f0", arrow::int32());
return arrow::StructArray::Make({val_array}, {field}).ValueOrDie();
}
TEST_F(ParquetFileBatchReaderTest, TestParquetMetadataCacheReusesSerializedFooter) {
WriteArray(file_path_, struct_array_, schema_, /*write_batch_size=*/struct_array_->length(),
/*enable_dictionary=*/false,
/*max_row_group_length=*/struct_array_->length());
auto cache = std::make_shared<paimon::test::CountingRoutingCache>(CacheKind::DATA_FILE_FOOTER,
128 * 1024 * 1024);
auto open_reader = [&]() -> Result<std::unique_ptr<ParquetFileBatchReader>> {
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<InputStream> input_stream, fs_->Open(file_path_));
std::map<std::string, std::string> options;
ParquetReaderBuilder builder(options, batch_size_);
builder.WithMemoryPool(GetDefaultPool())->WithCache(cache);
PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<FileBatchReader> reader,
builder.Build(input_stream));
auto parquet_reader = dynamic_cast<ParquetFileBatchReader*>(reader.release());
if (parquet_reader == nullptr) {
return Status::Invalid("failed to cast FileBatchReader to ParquetFileBatchReader");
}
return std::unique_ptr<ParquetFileBatchReader>(parquet_reader);
};
ASSERT_OK_AND_ASSIGN(auto reader1, open_reader());
ASSERT_OK_AND_ASSIGN(auto schema1, reader1->GetFileSchema());
ASSERT_TRUE(schema1);
ASSERT_TRUE(schema1->release);
schema1->release(schema1.get());
ASSERT_EQ(1, cache->GetCount());
ASSERT_EQ(1, cache->SupplierCallCount());
ASSERT_EQ(1, cache->Size());
ASSERT_EQ(CacheKind::DATA_FILE_FOOTER, cache->LastKind());
ASSERT_OK_AND_ASSIGN(auto reader2, open_reader());
ASSERT_OK_AND_ASSIGN(auto schema2, reader2->GetFileSchema());
ASSERT_TRUE(schema2);
ASSERT_TRUE(schema2->release);
schema2->release(schema2.get());
ASSERT_EQ(2, cache->GetCount());
ASSERT_EQ(1, cache->SupplierCallCount());
ASSERT_EQ(1, cache->Size());
ASSERT_EQ(CacheKind::DATA_FILE_FOOTER, cache->LastKind());
}
TEST_F(ParquetFileBatchReaderTest, TestParquetMetadataCacheBypassesWhenGetUriFails) {
WriteArray(file_path_, struct_array_, schema_, /*write_batch_size=*/struct_array_->length(),
/*enable_dictionary=*/false,
/*max_row_group_length=*/struct_array_->length());
ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> input_stream, fs_->Open(file_path_));
auto failed_uri_input_stream = std::make_shared<FailedUriInputStream>(input_stream);
auto cache = std::make_shared<paimon::test::CountingRoutingCache>(CacheKind::DATA_FILE_FOOTER,
128 * 1024 * 1024);
std::map<std::string, std::string> options;
ParquetReaderBuilder builder(options, batch_size_);
builder.WithMemoryPool(GetDefaultPool())->WithCache(cache);
ASSERT_OK_AND_ASSIGN(std::unique_ptr<FileBatchReader> reader,
builder.Build(failed_uri_input_stream));
auto parquet_reader = dynamic_cast<ParquetFileBatchReader*>(reader.get());
ASSERT_TRUE(parquet_reader);
ASSERT_OK_AND_ASSIGN(auto file_schema, parquet_reader->GetFileSchema());
ASSERT_TRUE(file_schema);
ASSERT_TRUE(file_schema->release);
file_schema->release(file_schema.get());
ASSERT_EQ(0, cache->GetCount());
ASSERT_EQ(0, cache->SupplierCallCount());
ASSERT_EQ(0, cache->Size());
}
TEST_F(ParquetFileBatchReaderTest, TestReadBinaryWrittenFromBinaryAndLargeBinary) {
auto check_binary_read_result = [&](const std::shared_ptr<arrow::DataType>& write_type,
const std::string& file_name) {
std::string data_json = R"([
["descriptor-1"],
[""],
[null],
["descriptor-2"]
])";
auto write_field = arrow::field("f0", write_type);
auto write_schema = arrow::schema({write_field});
auto write_array = std::dynamic_pointer_cast<arrow::StructArray>(
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_({write_field}), data_json)
.ValueOrDie());
std::string file_path = PathUtil::JoinPath(dir_->Str(), file_name);
WriteArray(file_path, write_array, write_schema, /*write_batch_size=*/write_array->length(),
/*enable_dictionary=*/false, /*max_row_group_length=*/write_array->length());
auto read_field = arrow::field("f0", arrow::binary());
auto read_schema = arrow::schema({read_field});
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_path, read_schema, /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt, batch_size_);
ASSERT_OK_AND_ASSIGN(auto c_file_schema, parquet_batch_reader->GetFileSchema());
auto file_schema = arrow::ImportSchema(c_file_schema.get()).ValueOrDie();
ASSERT_TRUE(file_schema->Equals(*read_schema));
auto expected_array = std::dynamic_pointer_cast<arrow::StructArray>(
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_({read_field}), data_json)
.ValueOrDie());
auto expected_chunked_array = std::make_shared<arrow::ChunkedArray>(expected_array);
ASSERT_OK_AND_ASSIGN(auto result_array, paimon::test::ReadResultCollector::CollectResult(
parquet_batch_reader.get()));
ASSERT_TRUE(result_array->Equals(expected_chunked_array));
};
check_binary_read_result(arrow::binary(), "binary.parquet");
check_binary_read_result(arrow::large_binary(), "large-binary.parquet");
}
TEST_F(ParquetFileBatchReaderTest, TestSimple) {
std::string file_name = paimon::test::GetDataDir() +
"/parquet/parquet_append_table.db/parquet_append_table/bucket-0/"
"data-9ea62f34-1dca-49c1-bf7a-d37303d8fb76-0.parquet";
auto parquet_batch_reader = PrepareParquetFileBatchReader(
file_name, schema_, /*predicate=*/nullptr, /*selection_bitmap=*/std::nullopt, batch_size_);
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> result_array,
paimon::test::ReadResultCollector::CollectResult(parquet_batch_reader.get()));
std::shared_ptr<arrow::ChunkedArray> expected_array =
std::make_shared<arrow::ChunkedArray>(struct_array_);
ASSERT_TRUE(result_array->Equals(*expected_array,
arrow::EqualOptions::Defaults().diff_sink(&std::cout)));
}
TEST_F(ParquetFileBatchReaderTest, TestSetReadSchema) {
std::string file_name = paimon::test::GetDataDir() +
"parquet/parquet_append_table.db/parquet_append_table/bucket-0/"
"data-9ea62f34-1dca-49c1-bf7a-d37303d8fb76-0.parquet";
ASSERT_OK_AND_ASSIGN(std::shared_ptr<InputStream> input_stream, fs_->Open(file_name));
auto length = fs_->GetFileStatus(file_name).value().GetLen();
auto in_stream =
std::make_unique<ArrowInputStreamAdapter>(std::move(input_stream), length, pool_);
std::map<std::string, std::string> options;
ASSERT_OK_AND_ASSIGN(auto parquet_batch_reader,
ParquetFileBatchReader::Create(std::move(in_stream), options, batch_size_,
/*file_metadata=*/nullptr,
/*storage_read_bytes=*/nullptr, pool_));
// test GetFileSchema()
ASSERT_OK_AND_ASSIGN(auto c_file_schema, parquet_batch_reader->GetFileSchema());
auto arrow_file_schema = arrow::ImportSchema(c_file_schema.get()).ValueOrDie();
ASSERT_TRUE(arrow_file_schema->Equals(*schema_));
// NextBatch() without SetReadSchema(), will return data with
// file schema
ASSERT_OK_AND_ASSIGN(
auto result_with_file_schema,
paimon::test::ReadResultCollector::CollectResult(parquet_batch_reader.get()));
auto expected_file_chunk_array = std::make_shared<arrow::ChunkedArray>(struct_array_);
ASSERT_TRUE(result_with_file_schema->Equals(expected_file_chunk_array));
// NextBatch() with SetReadSchema(), will return data with read schema
arrow::Schema read_schema({schema_->field(0), schema_->field(9), schema_->field(12)});
std::unique_ptr<ArrowSchema> c_read_schema = std::make_unique<ArrowSchema>();
ASSERT_TRUE(arrow::ExportSchema(read_schema, c_read_schema.get()).ok());
ASSERT_OK(parquet_batch_reader->SetReadSchema(c_read_schema.get(),
/*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt));
auto expected_read_array =
arrow::StructArray::Make(
{struct_array_->field(0), struct_array_->field(9), struct_array_->field(12)},
read_schema.fields())
.ValueOrDie();
auto expected_read_chunk_array = std::make_shared<arrow::ChunkedArray>(expected_read_array);
ASSERT_OK_AND_ASSIGN(
auto result_with_read_schema,
paimon::test::ReadResultCollector::CollectResult(parquet_batch_reader.get()));
ASSERT_TRUE(result_with_read_schema->Equals(expected_read_chunk_array));
// NextBatch() with predicate
auto predicate = PredicateBuilder::IsNull(
/*field_index=*/0, /*field_name=*/"f1", FieldType::BOOLEAN);
c_read_schema = std::make_unique<ArrowSchema>();
ASSERT_TRUE(arrow::ExportSchema(read_schema, c_read_schema.get()).ok());
ASSERT_OK(parquet_batch_reader->SetReadSchema(c_read_schema.get(), predicate,
/*selection_bitmap=*/std::nullopt));
ASSERT_OK_AND_ASSIGN(result_with_read_schema, paimon::test::ReadResultCollector::CollectResult(
parquet_batch_reader.get()));
ASSERT_FALSE(result_with_read_schema);
}
TEST_F(ParquetFileBatchReaderTest, TestSetReadSchemaWithLegacyParquetMissingFieldIds) {
std::string file_name = paimon::test::GetDataDir() +
"/parquet/append_09.db/append_09/f1=20/bucket-0/"
"data-b446f78a-2cfb-4b3b-add8-31295d24a277-0.parquet";
std::vector<DataField> read_fields = {
DataField(0, arrow::field("f0", arrow::utf8())),
DataField(2, arrow::field("f2", arrow::int32())),
DataField(3, arrow::field("f3", arrow::float64())),
};
auto read_schema = DataField::ConvertDataFieldsToArrowSchema(read_fields);
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_name, read_schema, /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt, batch_size_);
ASSERT_OK_AND_ASSIGN(auto result_array, paimon::test::ReadResultCollector::CollectResult(
parquet_batch_reader.get()));
std::shared_ptr<arrow::ChunkedArray> expected_array;
ASSERT_TRUE(arrow::ipc::internal::json::ChunkedArrayFromJSON(
arrow::struct_(read_schema->fields()), {R"([
["Lucy", 1, 14.1]
])"},
&expected_array)
.ok());
ASSERT_TRUE(result_array->Equals(expected_array))
<< "expected: " << expected_array->ToString() << "\nactual: " << result_array->ToString();
}
TEST_F(ParquetFileBatchReaderTest, TestNextBatchSimple) {
std::string file_name = paimon::test::GetDataDir() +
"parquet/parquet_append_table.db/parquet_append_table/bucket-0/"
"data-9ea62f34-1dca-49c1-bf7a-d37303d8fb76-0.parquet";
for (auto batch_size : {1, 2, 3, 5, 8, 10}) {
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_name, schema_, /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt, batch_size);
ASSERT_OK_AND_ASSIGN(auto result_array, paimon::test::ReadResultCollector::CollectResult(
parquet_batch_reader.get()));
parquet_batch_reader->Close();
auto expected_array = std::make_shared<arrow::ChunkedArray>(struct_array_);
ASSERT_TRUE(result_array->Equals(expected_array));
// test metrics
auto read_metrics = parquet_batch_reader->GetReaderMetrics();
ASSERT_TRUE(read_metrics);
ASSERT_OK_AND_ASSIGN(uint64_t storage_read_bytes,
read_metrics->GetCounter(ParquetMetrics::READ_STORAGE_BYTES));
ASSERT_GT(storage_read_bytes, 0u);
}
}
TEST_F(ParquetFileBatchReaderTest, TestNextBatchWithTargetSchema) {
std::string file_name = paimon::test::GetDataDir() +
"parquet/parquet_append_table.db/parquet_append_table/bucket-0/"
"data-9ea62f34-1dca-49c1-bf7a-d37303d8fb76-0.parquet";
auto read_schema = arrow::schema(arrow::FieldVector(
{schema_->field(4), schema_->field(9), schema_->field(10), schema_->field(12)}));
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_name, read_schema, /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt, batch_size_);
ASSERT_OK_AND_ASSIGN(auto result_array, paimon::test::ReadResultCollector::CollectResult(
parquet_batch_reader.get()));
parquet_batch_reader->Close();
auto expected_read_array =
arrow::StructArray::Make({struct_array_->field(4), struct_array_->field(9),
struct_array_->field(10), struct_array_->field(12)},
read_schema->fields())
.ValueOrDie();
auto expected_chunk_array = std::make_shared<arrow::ChunkedArray>(expected_read_array);
ASSERT_TRUE(result_array->Equals(expected_chunk_array));
}
TEST_F(ParquetFileBatchReaderTest, TestNextBatchWithOutofOrderTargetSchema) {
std::string file_name = paimon::test::GetDataDir() +
"parquet/parquet_append_table.db/parquet_append_table/bucket-0/"
"data-9ea62f34-1dca-49c1-bf7a-d37303d8fb76-0.parquet";
// read with f13, f11, f10, f5
auto read_schema = arrow::schema(arrow::FieldVector(
{schema_->field(12), schema_->field(10), schema_->field(9), schema_->field(4)}));
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_name, read_schema, /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt, batch_size_);
ASSERT_OK_AND_ASSIGN(auto result_array, paimon::test::ReadResultCollector::CollectResult(
parquet_batch_reader.get()));
parquet_batch_reader->Close();
auto expected_read_array =
arrow::StructArray::Make({struct_array_->field(12), struct_array_->field(10),
struct_array_->field(9), struct_array_->field(4)},
read_schema->fields())
.ValueOrDie();
auto expected_chunk_array = std::make_shared<arrow::ChunkedArray>(expected_read_array);
ASSERT_TRUE(result_array->Equals(expected_chunk_array));
}
TEST_F(ParquetFileBatchReaderTest, TestNextBatchWithDictionary) {
auto f0 = arrow::field("f0", arrow::list(arrow::utf8()));
auto f1 = arrow::field("f1", arrow::map(arrow::utf8(), arrow::binary()));
auto f2 = arrow::field(
"f2", arrow::struct_({field("sub1", arrow::int64()), field("sub2", arrow::binary()),
field("sub3", arrow::utf8())}));
arrow::FieldVector fields = {f0, f1, f2};
auto src_array = std::dynamic_pointer_cast<arrow::StructArray>(
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
[["a", "a", "b"], [["a", "q"], ["b", "w"]], [10, "q", "a"]],
[["a", "c"], [["a", "e"], ["b", "r"], ["c", "e"]], [20, "w", "a"]],
[["a", "d"], [["d", "r"], ["e", "t"]], [null, "e", "b"]],
[["a"], [["a", "q"]], [null, "w", "c"]],
[null, [["a", "w"], ["f", "y"]], [50, "r", null]],
[["a"], null, [60, "r", "b"]],
[["a", "b", "e"], [["a", null], ["b", "w"]], null]
])")
.ValueOrDie());
auto src_schema = arrow::schema(fields);
auto arrow_schema = arrow::schema(fields);
auto expected_array = arrow::ChunkedArray::Make({src_array}).ValueOrDie();
auto check_result = [&](bool enable_dictionary) {
WriteArray(file_path_, src_array, arrow_schema, /*write_batch_size=*/1, enable_dictionary,
/*max_row_group_length=*/3);
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_path_, arrow_schema, /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt, /*batch_size=*/2);
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> result_array,
paimon::test::ReadResultCollector::CollectResult(parquet_batch_reader.get()));
ASSERT_TRUE(result_array->Equals(expected_array)) << result_array->ToString();
};
check_result(true);
check_result(false);
}
TEST_F(ParquetFileBatchReaderTest, TestNestedStructChildProjectionRecall) {
auto f0 = arrow::field("f0", arrow::int32());
auto f1 = arrow::field(
"f1", arrow::struct_({arrow::field("c0", arrow::int64()), arrow::field("c1", arrow::utf8()),
arrow::field("c2", arrow::float64())}));
auto f2 = arrow::field("f2", arrow::utf8());
auto write_schema = arrow::schema({f0, f1, f2});
auto write_array = std::dynamic_pointer_cast<arrow::StructArray>(
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(write_schema->fields()), R"([
[1, [100, "a", 1.1], "x"],
[2, [200, "b", 2.2], "y"],
[3, [300, null, 3.3], "z"]
])")
.ValueOrDie());
WriteArray(file_path_, write_array, write_schema,
/*write_batch_size=*/write_array->length(),
/*enable_dictionary=*/false, /*max_row_group_length=*/write_array->length());
auto read_schema = MakeReadSchema({
f0,
arrow::field("f1", arrow::struct_({arrow::field("c1", arrow::utf8())})),
});
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_path_, read_schema, /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt, /*batch_size=*/2);
ASSERT_OK_AND_ASSIGN(auto result_array, paimon::test::ReadResultCollector::CollectResult(
parquet_batch_reader.get()));
std::shared_ptr<arrow::ChunkedArray> expected_array;
ASSERT_TRUE(arrow::ipc::internal::json::ChunkedArrayFromJSON(
arrow::struct_(read_schema->fields()), {R"([
[1, ["a"]],
[2, ["b"]],
[3, [null]]
])"},
&expected_array)
.ok());
ASSERT_TRUE(result_array->Equals(expected_array))
<< "expected: " << expected_array->ToString() << "\nactual: " << result_array->ToString();
}
TEST_F(ParquetFileBatchReaderTest, TestReadSchemaWithMapSelectedKeysMetadata) {
auto id_field = arrow::field("id", arrow::int32());
auto map_field = arrow::field("m", arrow::map(arrow::utf8(), arrow::int32()));
auto write_schema = arrow::schema({id_field, map_field});
auto write_array = std::dynamic_pointer_cast<arrow::StructArray>(
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(write_schema->fields()), R"([
[1, [["k1", 10], ["k2", 20], ["k3", 30]]],
[2, [["k2", 200]]],
[3, null]
])")
.ValueOrDie());
WriteArray(file_path_, write_array, write_schema,
/*write_batch_size=*/write_array->length(),
/*enable_dictionary=*/false, /*max_row_group_length=*/write_array->length());
// selected-keys metadata is consumed by upper-level field mapping; format reader should
// still accept the schema and read data correctly.
auto read_schema = MakeReadSchema(
{id_field, WithMapSelectedKeys(map_field, "k1,k3")}); // NOLINT(whitespace/comma)
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_path_, read_schema, /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt, /*batch_size=*/2);
ASSERT_OK_AND_ASSIGN(auto result_array, paimon::test::ReadResultCollector::CollectResult(
parquet_batch_reader.get()));
auto expected_array = arrow::ChunkedArray::Make({write_array}).ValueOrDie();
ASSERT_TRUE(result_array->Equals(expected_array))
<< "expected: " << expected_array->ToString() << "\nactual: " << result_array->ToString();
}
TEST_F(ParquetFileBatchReaderTest, TestNestedListTimestampTimezoneAndMapFieldName) {
const std::string timezone = "Asia/Shanghai";
paimon::test::TimezoneGuard timezone_guard(timezone);
auto write_attrs_type =
std::make_shared<arrow::MapType>(arrow::field("key", arrow::utf8(), /*nullable=*/false),
arrow::field("attrs", arrow::utf8()));
auto write_element_type = arrow::struct_({
arrow::field("key", arrow::utf8()),
arrow::field("attrs", write_attrs_type),
arrow::field("updated_at", arrow::timestamp(arrow::TimeUnit::MICRO, timezone)),
});
auto write_schema = arrow::schema(
{arrow::field("annotations", arrow::list(arrow::field("element", write_element_type)))});
const std::string data_json = R"([
[[ ["ann-1", [["source", "model"]], "2026-07-16 12:00:00.000001"] ]],
[[ ["ann-2", [], "2026-07-16 12:00:00.000002"],
["ann-3", null, null] ]],
[null]
])";
auto write_array = std::dynamic_pointer_cast<arrow::StructArray>(
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(write_schema->fields()), data_json)
.ValueOrDie());
WriteArray(file_path_, write_array, write_schema, /*write_batch_size=*/write_array->length(),
/*enable_dictionary=*/false, /*max_row_group_length=*/write_array->length());
auto read_element_type = arrow::struct_({
arrow::field("key", arrow::utf8()),
arrow::field("attrs", arrow::map(arrow::utf8(), arrow::utf8())),
arrow::field("updated_at", arrow::timestamp(arrow::TimeUnit::MICRO, timezone)),
});
auto read_schema = arrow::schema(
{arrow::field("annotations", arrow::list(arrow::field("element", read_element_type)))});
auto expected_array = std::dynamic_pointer_cast<arrow::StructArray>(
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(read_schema->fields()), data_json)
.ValueOrDie());
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_path_, read_schema, /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt, /*batch_size=*/2);
ASSERT_OK_AND_ASSIGN(auto result_array, paimon::test::ReadResultCollector::CollectResult(
parquet_batch_reader.get()));
auto expected_chunked_array = arrow::ChunkedArray::Make({expected_array}).ValueOrDie();
ASSERT_TRUE(result_array->Equals(expected_chunked_array))
<< "expected: " << expected_chunked_array->ToString()
<< "\nactual: " << result_array->ToString();
auto projected_element_type = arrow::struct_({
arrow::field("key", arrow::utf8()),
arrow::field("attrs", arrow::map(arrow::utf8(), arrow::utf8())),
});
auto partial_read_schema = arrow::schema({arrow::field(
"annotations", arrow::list(arrow::field("element", projected_element_type)))});
auto c_partial_read_schema = std::make_unique<ArrowSchema>();
ASSERT_TRUE(arrow::ExportSchema(*partial_read_schema, c_partial_read_schema.get()).ok());
ASSERT_NOK_WITH_MSG(
parquet_batch_reader->SetReadSchema(c_partial_read_schema.get(), /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt),
"Parquet does not support partial projection inside list/map");
auto mismatched_element_type = arrow::struct_({
arrow::field("key", arrow::utf8()),
arrow::field("attrs", arrow::map(arrow::utf8(), arrow::utf8())),
arrow::field("updated_at", arrow::utf8()),
});
auto mismatched_read_schema = arrow::schema({arrow::field(
"annotations", arrow::list(arrow::field("element", mismatched_element_type)))});
auto c_mismatched_read_schema = std::make_unique<ArrowSchema>();
ASSERT_TRUE(arrow::ExportSchema(*mismatched_read_schema, c_mismatched_read_schema.get()).ok());
ASSERT_NOK_WITH_MSG(
parquet_batch_reader->SetReadSchema(c_mismatched_read_schema.get(), /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt),
"Parquet does not support partial projection inside list/map");
auto unsupported_timestamp_element_type = arrow::struct_({
arrow::field("key", arrow::utf8()),
arrow::field("attrs", arrow::map(arrow::utf8(), arrow::utf8())),
arrow::field("updated_at", arrow::timestamp(arrow::TimeUnit::NANO, timezone)),
});
auto unsupported_timestamp_schema = arrow::schema({arrow::field(
"annotations", arrow::list(arrow::field("element", unsupported_timestamp_element_type)))});
auto c_unsupported_timestamp_schema = std::make_unique<ArrowSchema>();
ASSERT_TRUE(
arrow::ExportSchema(*unsupported_timestamp_schema, c_unsupported_timestamp_schema.get())
.ok());
ASSERT_NOK_WITH_MSG(parquet_batch_reader->SetReadSchema(c_unsupported_timestamp_schema.get(),
/*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt),
"Parquet does not support partial projection inside list/map");
}
TEST_F(ParquetFileBatchReaderTest, TestNestedTimestampSecondReadFromMilliFile) {
const std::string timezone = "Asia/Shanghai";
paimon::test::TimezoneGuard timezone_guard(timezone);
// Parquet has no second-precision timestamp, so the writer stores second timestamps as
// milliseconds. Reading them back with a second-precision schema must cast milli to second,
// including for timestamp leaves nested inside list/struct/map.
auto event_type = arrow::struct_({
arrow::field("name", arrow::utf8()),
arrow::field("ts_sec", arrow::timestamp(arrow::TimeUnit::SECOND)),
arrow::field("ts_tz_sec", arrow::timestamp(arrow::TimeUnit::SECOND, timezone)),
});
auto schema = arrow::schema({
arrow::field("events", arrow::list(arrow::field("element", event_type))),
arrow::field("marks", arrow::map(arrow::utf8(), arrow::timestamp(arrow::TimeUnit::SECOND))),
});
const std::string data_json = R"([
[[ ["e-1", "2026-07-16 12:00:01", "2026-07-16 12:00:02"] ],
[["begin", "2026-07-16 12:00:03"]]],
[[ ["e-2", "2026-07-16 12:00:04", null],
["e-3", null, "2026-07-16 12:00:05"] ], []],
[[null], null]
])";
auto write_array = std::dynamic_pointer_cast<arrow::StructArray>(
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(schema->fields()), data_json)
.ValueOrDie());
WriteArray(file_path_, write_array, schema, /*write_batch_size=*/write_array->length(),
/*enable_dictionary=*/false, /*max_row_group_length=*/write_array->length());
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_path_, schema, /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt, /*batch_size=*/2);
// The nested second timestamps are physically stored as milliseconds in the file.
ASSERT_OK_AND_ASSIGN(auto c_file_schema, parquet_batch_reader->GetFileSchema());
auto file_schema = arrow::ImportSchema(c_file_schema.get()).ValueOr(nullptr);
ASSERT_TRUE(file_schema);
auto file_event_type =
static_cast<const arrow::ListType&>(*file_schema->field(0)->type()).value_type();
ASSERT_EQ(arrow::Type::STRUCT, file_event_type->id());
ASSERT_EQ(arrow::TimeUnit::MILLI,
static_cast<const arrow::TimestampType&>(*file_event_type->field(1)->type()).unit());
ASSERT_EQ(arrow::TimeUnit::MILLI,
static_cast<const arrow::TimestampType&>(*file_event_type->field(2)->type()).unit());
auto file_mark_type =
static_cast<const arrow::MapType&>(*file_schema->field(1)->type()).item_type();
ASSERT_EQ(arrow::TimeUnit::MILLI,
static_cast<const arrow::TimestampType&>(*file_mark_type).unit());
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> result_array,
paimon::test::ReadResultCollector::CollectResult(parquet_batch_reader.get()));
auto expected_array = arrow::ChunkedArray::Make({write_array}).ValueOrDie();
ASSERT_TRUE(result_array->Equals(expected_array))
<< "expected: " << expected_array->ToString() << "\nactual: " << result_array->ToString();
}
TEST_F(ParquetFileBatchReaderTest, TestGetFileSchemaWithFieldId) {
std::string file_name = paimon::test::GetDataDir() +
"parquet/parquet_append_table.db/parquet_append_table/bucket-0/"
"data-9ea62f34-1dca-49c1-bf7a-d37303d8fb76-0.parquet";
auto parquet_batch_reader = PrepareParquetFileBatchReader(
file_name, schema_, /*predicate=*/nullptr, /*selection_bitmap=*/std::nullopt, batch_size_);
ASSERT_OK_AND_ASSIGN(auto c_file_schema, parquet_batch_reader->GetFileSchema());
auto arrow_file_schema = arrow::ImportSchema(c_file_schema.get()).ValueOr(nullptr);
ASSERT_TRUE(arrow_file_schema);
ASSERT_OK_AND_ASSIGN(auto data_fields,
DataField::ConvertArrowSchemaToDataFields(arrow_file_schema));
auto list_type = arrow::list(DataField::ConvertDataFieldToArrowField(
DataField(536880129, arrow::field("item", arrow::float32()))));
std::vector<DataField> struct_fields = {DataField(10, arrow::field("f0", arrow::boolean())),
DataField(11, arrow::field("f1", arrow::int64()))};
auto struct_type = DataField::ConvertDataFieldsToArrowStructType(struct_fields);
auto map_type = arrow::map(list_type, struct_type);
std::vector<DataField> expected_data_fields = {
DataField(0, arrow::field("f1", arrow::boolean())),
DataField(1, arrow::field("f2", arrow::int8())),
DataField(2, arrow::field("f3", arrow::int16())),
DataField(3, arrow::field("f4", arrow::int32())),
DataField(4, arrow::field("f5", arrow::int64())),
DataField(5, arrow::field("f6", arrow::float32())),
DataField(6, arrow::field("f7", arrow::float64())),
DataField(7, arrow::field("f8", arrow::utf8())),
DataField(8, arrow::field("f9", arrow::binary())),
DataField(9, arrow::field("f10", map_type)),
DataField(12, arrow::field("f11", arrow::timestamp(arrow::TimeUnit::NANO))),
DataField(13, arrow::field("f12", arrow::date32())),
DataField(14, arrow::field("f13", arrow::decimal128(2, 2))),
};
ASSERT_EQ(data_fields, expected_data_fields);
}
TEST_F(ParquetFileBatchReaderTest, TestCreateReaderProperties) {
{
// test default options
std::map<std::string, std::string> options;
ASSERT_OK_AND_ASSIGN(auto reader_properties,
ParquetFileBatchReader::CreateReaderProperties(pool_, options));
ASSERT_EQ(reader_properties.is_buffered_stream_enabled(), true);
}
}
TEST_F(ParquetFileBatchReaderTest, TestCreateArrowReaderProperties) {
{
// test default options
std::map<std::string, std::string> options;
int32_t batch_size = 1024;
ASSERT_OK_AND_ASSIGN(
auto arrow_reader_properties,
ParquetFileBatchReader::CreateArrowReaderProperties(pool_, options, batch_size));
ASSERT_EQ(arrow_reader_properties.pre_buffer(), true);
ASSERT_EQ(arrow_reader_properties.batch_size(), 1024);
ASSERT_EQ(arrow_reader_properties.use_threads(), true);
ASSERT_EQ(arrow::GetCpuThreadPoolCapacity(), 3);
auto expected_cache_options = arrow::io::CacheOptions::Defaults();
expected_cache_options.hole_size_limit = DEFAULT_PARQUET_READ_CACHE_OPTION_HOLE_SIZE_LIMIT;
ASSERT_EQ(arrow_reader_properties.cache_options(), expected_cache_options);
}
{
std::map<std::string, std::string> options = {{PARQUET_READ_EXECUTOR_THREAD_COUNT, "0"}};
int32_t batch_size = 1024;
ASSERT_OK_AND_ASSIGN(
auto arrow_reader_properties,
ParquetFileBatchReader::CreateArrowReaderProperties(pool_, options, batch_size));
ASSERT_EQ(arrow_reader_properties.use_threads(), false);
}
{
std::map<std::string, std::string> options = {{PARQUET_READ_EXECUTOR_THREAD_COUNT, "6"}};
int32_t batch_size = 1024;
ASSERT_OK_AND_ASSIGN(
auto arrow_reader_properties,
ParquetFileBatchReader::CreateArrowReaderProperties(pool_, options, batch_size));
ASSERT_EQ(arrow_reader_properties.use_threads(), true);
ASSERT_EQ(arrow::GetCpuThreadPoolCapacity(), 6);
}
{
std::map<std::string, std::string> options = {
{PARQUET_READ_CACHE_OPTION_LAZY, "true"},
{PARQUET_READ_CACHE_OPTION_PREFETCH_LIMIT, "2"},
{PARQUET_READ_CACHE_OPTION_HOLE_SIZE_LIMIT, "1048576"},
{PARQUET_READ_CACHE_OPTION_RANGE_SIZE_LIMIT, "8388608"},
};
ASSERT_OK_AND_ASSIGN(
auto arrow_reader_properties,
ParquetFileBatchReader::CreateArrowReaderProperties(pool_, options, 1024));
const auto& cache_options = arrow_reader_properties.cache_options();
ASSERT_TRUE(cache_options.lazy);
ASSERT_EQ(cache_options.prefetch_limit, 2);
ASSERT_EQ(cache_options.hole_size_limit, 1024 * 1024);
ASSERT_EQ(cache_options.range_size_limit, 8 * 1024 * 1024);
}
{
std::map<std::string, std::string> options = {
{PARQUET_READ_CACHE_OPTION_HOLE_SIZE_LIMIT, "-1"},
};
ASSERT_NOK_WITH_MSG(
ParquetFileBatchReader::CreateArrowReaderProperties(pool_, options, 1024),
"parquet.read.cache-option.hole-size-limit must be non-negative");
}
{
std::map<std::string, std::string> options = {
{PARQUET_READ_CACHE_OPTION_HOLE_SIZE_LIMIT, "1048576"},
{PARQUET_READ_CACHE_OPTION_RANGE_SIZE_LIMIT, "1048576"},
};
ASSERT_NOK_WITH_MSG(
ParquetFileBatchReader::CreateArrowReaderProperties(pool_, options, 1024),
"parquet.read.cache-option.range-size-limit must be greater than "
"parquet.read.cache-option.hole-size-limit");
}
}
TEST_F(ParquetFileBatchReaderTest, TestBitmapRowGroupPushDownWithMultiRowGroups) {
arrow::FieldVector fields = {arrow::field("f0", arrow::int32())};
auto arrow_type = arrow::struct_(fields);
auto src_array = std::dynamic_pointer_cast<arrow::StructArray>(
arrow::ipc::internal::json::ArrayFromJSON(arrow_type, R"([
[0],
[1],
[2],
[3],
[4],
[5],
[6],
[7],
[8],
[9],
[10],
[11]
])")
.ValueOrDie());
auto src_schema = arrow::schema(fields);
std::optional<RoaringBitmap32> bitmap = RoaringBitmap32::From({3, 5});
// data in file rowGroup0:[0, 1, 2, 3, 4, 5] | rowGroup1:[6, 7, 8, 9, 10, 11]
auto arrow_schema = arrow::schema(fields);
WriteArray(file_path_, src_array, arrow_schema, /*write_batch_size=*/12,
/*enable_dictionary=*/true,
/*max_row_group_length=*/6);
auto parquet_batch_reader = PrepareParquetFileBatchReader(
file_path_, arrow_schema, /*predicate=*/nullptr, bitmap, /*batch_size=*/12);
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> result_array,
paimon::test::ReadResultCollector::CollectResult(parquet_batch_reader.get()));
auto expected_array = arrow::ChunkedArray::Make({src_array->Slice(0, 6)}).ValueOrDie();
ASSERT_TRUE(result_array->Equals(expected_array)) << result_array->ToString();
}
TEST_F(ParquetFileBatchReaderTest, TestBitmapPagePushDownWithMultiRowGroups) {
arrow::FieldVector fields = {arrow::field("f0", arrow::int32())};
auto arrow_type = arrow::struct_(fields);
auto src_array = std::dynamic_pointer_cast<arrow::StructArray>(
arrow::ipc::internal::json::ArrayFromJSON(arrow_type, R"([
[0],
[1],
[2],
[3],
[4],
[5],
[6],
[7],
[8],
[9],
[10],
[11]
])")
.ValueOrDie());
auto src_schema = arrow::schema(fields);
std::optional<RoaringBitmap32> bitmap = RoaringBitmap32::From({3, 5});
// data in file rowGroup0:[0, 1, 2, 3, 4, 5] | rowGroup1:[6, 7, 8, 9, 10, 11]
auto arrow_schema = arrow::schema(fields);
WriteArray(file_path_, src_array, arrow_schema, /*write_batch_size=*/12,
/*enable_dictionary=*/true,
/*max_row_group_length=*/6);
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_path_, arrow_schema, /*predicate=*/nullptr, bitmap,
/*batch_size=*/12, /*enable_page_level_filter=*/true);
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> result_array,
paimon::test::ReadResultCollector::CollectResult(parquet_batch_reader.get()));
auto expected_array = arrow::ChunkedArray(src_array->Slice(3, 3));
ASSERT_TRUE(result_array->Equals(expected_array)) << result_array->ToString();
}
TEST_F(ParquetFileBatchReaderTest, TestPredicateAndBitmapRowGroupPushDown) {
arrow::FieldVector fields = {arrow::field("f0", arrow::int32())};
auto arrow_type = arrow::struct_(fields);
arrow::StructBuilder struct_builder(arrow_type, arrow::default_memory_pool(),
{std::make_shared<arrow::Int32Builder>()});
auto int_builder = checked_cast<arrow::Int32Builder*>(struct_builder.field_builder(0));
int32_t length = 1024;
for (int32_t i = 0; i < length; ++i) {
ASSERT_TRUE(struct_builder.Append().ok());
ASSERT_TRUE(int_builder->Append(i).ok());
}
// data file:
// rowGroup0: [0, 256)
// rowGroup1: [256, 512)
// rowGroup2: [512, 768)
// rowGroup3: [768, 1024)
std::shared_ptr<arrow::Array> src_array;
ASSERT_TRUE(struct_builder.Finish(&src_array).ok());
auto src_schema = arrow::schema(fields);
auto arrow_schema = arrow::schema(fields);
WriteArray(file_path_, src_array, arrow_schema, /*write_batch_size=*/1024,
/*enable_dictionary=*/true,
/*max_row_group_length=*/256);
{
// simple case
std::optional<RoaringBitmap32> bitmap = RoaringBitmap32::From({100, 400, 600});
ASSERT_OK_AND_ASSIGN(
auto predicate,
PredicateBuilder::Or(
{PredicateBuilder::LessThan(/*field_index=*/0, /*field_name=*/"f0", FieldType::INT,
Literal(255)),
PredicateBuilder::GreaterThan(/*field_index=*/0, /*field_name=*/"f0",
FieldType::INT, Literal(600))}));
auto parquet_batch_reader = PrepareParquetFileBatchReader(
file_path_, arrow_schema, predicate, bitmap, /*batch_size=*/length);
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> result_array,
paimon::test::ReadResultCollector::CollectResult(parquet_batch_reader.get()));
auto expected_array =
arrow::ChunkedArray::Make({src_array->Slice(0, 256), src_array->Slice(512, 256)})
.ValueOrDie();
ASSERT_TRUE(result_array->Equals(expected_array)) << result_array->ToString();
}
{
// test all data has been filtered out with predicate and bitmap pushdown
std::optional<RoaringBitmap32> bitmap = RoaringBitmap32::From({100, 400, 600});
auto predicate = PredicateBuilder::GreaterThan(/*field_index=*/0, /*field_name=*/"f0",
FieldType::INT, Literal(800));
auto parquet_batch_reader = PrepareParquetFileBatchReader(
file_path_, arrow_schema, predicate, bitmap, /*batch_size=*/length);
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> result_array,
paimon::test::ReadResultCollector::CollectResult(parquet_batch_reader.get()));
ASSERT_FALSE(result_array);
}
}
TEST_F(ParquetFileBatchReaderTest, TestPredicateAndBitmapPagePushDown) {
arrow::FieldVector fields = {arrow::field("f0", arrow::int32())};
auto arrow_type = arrow::struct_(fields);
arrow::StructBuilder struct_builder(arrow_type, arrow::default_memory_pool(),
{std::make_shared<arrow::Int32Builder>()});
auto int_builder = checked_cast<arrow::Int32Builder*>(struct_builder.field_builder(0));
int32_t length = 1024;
for (int32_t i = 0; i < length; ++i) {
ASSERT_TRUE(struct_builder.Append().ok());
ASSERT_TRUE(int_builder->Append(i).ok());
}
// data file:
// rowGroup0: [0, 256)
// rowGroup1: [256, 512)
// rowGroup2: [512, 768)
// rowGroup3: [768, 1024)
std::shared_ptr<arrow::Array> src_array;
ASSERT_TRUE(struct_builder.Finish(&src_array).ok());
auto src_schema = arrow::schema(fields);
auto arrow_schema = arrow::schema(fields);
WriteArray(file_path_, src_array, arrow_schema, /*write_batch_size=*/1024,
/*enable_dictionary=*/true,
/*max_row_group_length=*/256);
{
// simple case
std::optional<RoaringBitmap32> bitmap = RoaringBitmap32::From({100, 400, 600});
ASSERT_OK_AND_ASSIGN(
auto predicate,
PredicateBuilder::Or(
{PredicateBuilder::LessThan(/*field_index=*/0, /*field_name=*/"f0", FieldType::INT,
Literal(255)),
PredicateBuilder::GreaterThan(/*field_index=*/0, /*field_name=*/"f0",
FieldType::INT, Literal(600))}));
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_path_, arrow_schema, predicate, bitmap,
/*batch_size=*/length, /*enable_page_level_filter=*/true);
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> result_array,
paimon::test::ReadResultCollector::CollectResult(parquet_batch_reader.get()));
auto expected_array =
arrow::ChunkedArray::Make({src_array->Slice(100, 1), src_array->Slice(600, 1)})
.ValueOrDie();
ASSERT_TRUE(result_array->Equals(expected_array)) << result_array->ToString();
}
{
// test all data has been filtered out with predicate and bitmap pushdown
std::optional<RoaringBitmap32> bitmap = RoaringBitmap32::From({100, 400, 600});
auto predicate = PredicateBuilder::GreaterThan(/*field_index=*/0, /*field_name=*/"f0",
FieldType::INT, Literal(800));
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_path_, arrow_schema, predicate, bitmap,
/*batch_size=*/length, /*enable_page_level_filter=*/true);
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> result_array,
paimon::test::ReadResultCollector::CollectResult(parquet_batch_reader.get()));
ASSERT_FALSE(result_array);
}
}
TEST_F(ParquetFileBatchReaderTest, TestReadNoField) {
// if only read partition fields, format reader will set empty read schema
std::string file_name = paimon::test::GetDataDir() +
"parquet/parquet_append_table.db/parquet_append_table/bucket-0/"
"data-9ea62f34-1dca-49c1-bf7a-d37303d8fb76-0.parquet";
// read no field
auto read_schema = arrow::schema(arrow::FieldVector());
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_name, read_schema, /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt, /*batch_size=*/2);
// read 2 rows
ASSERT_NOK(parquet_batch_reader->GetPreviousBatchFileRowId(0));
ASSERT_OK_AND_ASSIGN(auto batch1, parquet_batch_reader->NextBatch());
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(0).value(), 0);
// read 2 rows
ASSERT_OK_AND_ASSIGN(auto batch2, parquet_batch_reader->NextBatch());
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(0).value(), 2);
// read 2 rows
ASSERT_OK_AND_ASSIGN(auto batch3, parquet_batch_reader->NextBatch());
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(0).value(), 4);
// read rows with eof
ASSERT_OK_AND_ASSIGN(auto batch4, parquet_batch_reader->NextBatch());
ASSERT_TRUE(BatchReader::IsEofBatch(batch4));
ASSERT_NOK(parquet_batch_reader->GetPreviousBatchFileRowId(0));
parquet_batch_reader->Close();
arrow::FieldVector fields;
auto arrow_type = arrow::struct_(fields);
auto expected_array = std::dynamic_pointer_cast<arrow::StructArray>(
arrow::ipc::internal::json::ArrayFromJSON(arrow_type, R"([
[],
[]
])")
.ValueOrDie());
auto check_batch = [](BatchReader::ReadBatch&& result_batch,
const std::shared_ptr<arrow::Array>& expected_array) {
auto& [c_array, c_schema] = result_batch;
auto result_array = arrow::ImportArray(c_array.get(), c_schema.get()).ValueOr(nullptr);
ASSERT_TRUE(result_array);
ASSERT_TRUE(result_array->Equals(expected_array));
};
check_batch(std::move(batch1), expected_array);
check_batch(std::move(batch2), expected_array);
check_batch(std::move(batch3), expected_array);
}
TEST_P(ParquetFileBatchReaderTest, TestTimestampType) {
auto enable_tz = GetParam();
std::string timezone_str = enable_tz ? "Asia/Tokyo" : DateTimeUtils::GetLocalTimezoneName();
paimon::test::TimezoneGuard tz_guard(timezone_str);
arrow::FieldVector fields = {
arrow::field("ts_sec", arrow::timestamp(arrow::TimeUnit::SECOND)),
arrow::field("ts_milli", arrow::timestamp(arrow::TimeUnit::MILLI)),
arrow::field("ts_micro", arrow::timestamp(arrow::TimeUnit::MICRO)),
arrow::field("ts_nano", arrow::timestamp(arrow::TimeUnit::NANO)),
arrow::field("ts_tz_sec", arrow::timestamp(arrow::TimeUnit::SECOND, timezone_str)),
arrow::field("ts_tz_milli", arrow::timestamp(arrow::TimeUnit::MILLI, timezone_str)),
arrow::field("ts_tz_micro", arrow::timestamp(arrow::TimeUnit::MICRO, timezone_str)),
arrow::field("ts_tz_nano", arrow::timestamp(arrow::TimeUnit::NANO, timezone_str)),
};
auto array = std::dynamic_pointer_cast<arrow::StructArray>(
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
["1970-01-01 00:00:01", "1970-01-01 00:00:00.001", "1970-01-01 00:00:00.000001", "1970-01-01 00:00:00.000000001", "1970-01-01 00:00:02", "1970-01-01 00:00:00.002", "1970-01-01 00:00:00.000002", "1970-01-01 00:00:00.000000002"],
["1970-01-01 00:00:03", "1970-01-01 00:00:00.003", null, "1970-01-01 00:00:00.000000003", "1970-01-01 00:00:04", "1970-01-01 00:00:00.004", "1970-01-01 00:00:00.000004", "1970-01-01 00:00:00.000000004"],
["1970-01-01 00:00:05", "1970-01-01 00:00:00.005", null, null, "1970-01-01 00:00:06", null, "1970-01-01 00:00:00.000006", null]
])")
.ValueOrDie());
std::shared_ptr<arrow::ChunkedArray> expected_array =
std::make_shared<arrow::ChunkedArray>(array);
{
// read data generated by Java Paimon
std::string file_name = paimon::test::GetDataDir() +
"/parquet/append_with_multiple_ts_precision_and_timezone.db/"
"append_with_multiple_ts_precision_and_timezone/bucket-0/"
"data-9b8abdde-df4d-4655-bb4c-ffda164ef9d4-0.parquet";
auto parquet_batch_reader = PrepareParquetFileBatchReader(
file_name, std::make_shared<arrow::Schema>(fields),
/*predicate=*/nullptr, /*selection_bitmap=*/std::nullopt, batch_size_);
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> result_array,
paimon::test::ReadResultCollector::CollectResult(parquet_batch_reader.get()));
ASSERT_TRUE(result_array->Equals(*expected_array)) << result_array->ToString();
}
{
// read data generated by C++ Paimon
auto arrow_schema = std::make_shared<arrow::Schema>(fields);
WriteArray(file_path_, array, arrow_schema,
/*write_batch_size=*/1, /*enable_dictionary=*/true,
/*max_row_group_length=*/1);
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_path_, arrow_schema, /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt, /*batch_size=*/2);
// check file schema
ASSERT_OK_AND_ASSIGN(auto c_file_schema, parquet_batch_reader->GetFileSchema());
auto result_file_schema = arrow::ImportSchema(c_file_schema.get()).ValueOr(nullptr);
ASSERT_TRUE(result_file_schema);
arrow::FieldVector expected_fields = {
arrow::field("ts_sec", arrow::timestamp(arrow::TimeUnit::MILLI)),
arrow::field("ts_milli", arrow::timestamp(arrow::TimeUnit::MILLI)),
arrow::field("ts_micro", arrow::timestamp(arrow::TimeUnit::MICRO)),
arrow::field("ts_nano", arrow::timestamp(arrow::TimeUnit::NANO)),
arrow::field("ts_tz_sec", arrow::timestamp(arrow::TimeUnit::MILLI, timezone_str)),
arrow::field("ts_tz_milli", arrow::timestamp(arrow::TimeUnit::MILLI, timezone_str)),
arrow::field("ts_tz_micro", arrow::timestamp(arrow::TimeUnit::MICRO, timezone_str)),
arrow::field("ts_tz_nano", arrow::timestamp(arrow::TimeUnit::NANO)),
};
auto expected_file_schema = arrow::schema(expected_fields);
ASSERT_TRUE(result_file_schema->Equals(expected_file_schema))
<< result_file_schema->ToString();
// check array
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> result_array,
paimon::test::ReadResultCollector::CollectResult(parquet_batch_reader.get()));
ASSERT_TRUE(result_array->Equals(expected_array)) << result_array->ToString();
}
}
INSTANTIATE_TEST_SUITE_P(TestParam, ParquetFileBatchReaderTest, ::testing::Values(false, true));
TEST_F(ParquetFileBatchReaderTest, TestAddMetadataPerFieldMetadata) {
// Write a simple parquet file, call AddMetadata to inject per-field metadata
// before Finish, then read back and verify the file schema carries the metadata.
auto write_schema = arrow::schema({
arrow::field("id", arrow::int32()),
arrow::field("name", arrow::utf8()),
arrow::field("score", arrow::float64()),
});
std::string file_path = PathUtil::JoinPath(dir_->Str(), "update_schema_test.parquet");
ASSERT_OK_AND_ASSIGN(std::shared_ptr<OutputStream> out,
fs_->Create(file_path, /*overwrite=*/true));
::parquet::WriterProperties::Builder builder;
builder.write_batch_size(10);
auto writer_properties = builder.build();
ASSERT_OK_AND_ASSIGN(auto format_writer,
ParquetFormatWriter::Create(out, write_schema, writer_properties,
DEFAULT_PARQUET_WRITER_MAX_MEMORY_USE, pool_));
// Write one batch of data.
auto data = arrow::ipc::internal::json::ArrayFromJSON(
arrow::struct_(write_schema->fields()),
R"([[1, "alice", 95.5], [2, "bob", 88.0], [3, "charlie", 72.3]])")
.ValueOrDie();
ArrowArray c_array;
ASSERT_TRUE(arrow::ExportArray(*data, &c_array).ok());
ASSERT_OK(format_writer->AddBatch(&c_array));
ASSERT_OK(format_writer->Flush());
// Build an updated schema with per-field metadata on "name" and "score".
auto name_meta = std::make_shared<arrow::KeyValueMetadata>();
name_meta->Append("shredding.field_mapping", "0:alice,1:bob,2:charlie");
name_meta->Append("shredding.num_columns", "3");
auto score_meta = std::make_shared<arrow::KeyValueMetadata>();
score_meta->Append("custom.unit", "percent");
auto updated_schema = arrow::schema({
write_schema->field(0), // id — no metadata
write_schema->field(1)->WithMetadata(name_meta), // name — shredding metadata
write_schema->field(2)->WithMetadata(score_meta), // score — custom metadata
});
// AddMetadata must be called before Finish.
ASSERT_OK(format_writer->AddMetadata(
{{ArrowUtils::kArrowSchemaMetadataKey, SerializeSchemaToString(updated_schema)}}));
ASSERT_OK(format_writer->Finish());
ASSERT_OK(out->Flush());
ASSERT_OK(out->Close());
// Read back: GetFileSchema should reflect the updated per-field metadata.
auto parquet_batch_reader =
PrepareParquetFileBatchReader(file_path, write_schema, /*predicate=*/nullptr,
/*selection_bitmap=*/std::nullopt, batch_size_);
ASSERT_OK_AND_ASSIGN(auto c_file_schema, parquet_batch_reader->GetFileSchema());
auto file_schema = arrow::ImportSchema(c_file_schema.get()).ValueOrDie();
// Field 0 "id": no metadata.
ASSERT_EQ("id", file_schema->field(0)->name());
// Field 1 "name": should have the shredding metadata we set.
ASSERT_EQ("name", file_schema->field(1)->name());
auto read_name_meta = file_schema->field(1)->metadata();
ASSERT_NE(nullptr, read_name_meta);
auto field_mapping_val = read_name_meta->Get("shredding.field_mapping").ValueOrDie();
ASSERT_EQ("0:alice,1:bob,2:charlie", field_mapping_val);
auto num_columns_val = read_name_meta->Get("shredding.num_columns").ValueOrDie();
ASSERT_EQ("3", num_columns_val);
// Field 2 "score": should have the custom metadata.
ASSERT_EQ("score", file_schema->field(2)->name());
auto read_score_meta = file_schema->field(2)->metadata();
ASSERT_NE(nullptr, read_score_meta);
auto unit_val = read_score_meta->Get("custom.unit").ValueOrDie();
ASSERT_EQ("percent", unit_val);
// Also verify data integrity — read it back and compare content.
ASSERT_OK_AND_ASSIGN(auto result_array, paimon::test::ReadResultCollector::CollectResult(
parquet_batch_reader.get()));
ASSERT_EQ(result_array->num_chunks(), 1);
ASSERT_TRUE(data->Equals(*result_array->chunk(0))) << result_array->ToString();
}
TEST_F(ParquetFileBatchReaderTest, TestRowMappingSimple) {
arrow::FieldVector fields = {arrow::field("f0", arrow::int32())};
auto src_array = MakeSequentialIntData(12);
// data in file rowGroup0:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
// one row per page
auto arrow_schema = arrow::schema(fields);
WriteArray(file_path_, src_array, arrow_schema, /*write_batch_size=*/1,
/*enable_dictionary=*/true, /*max_row_group_length=*/12, /*max_page_size=*/1);
// 1<=f0<=3 || 5<=f0<=6
ASSERT_OK_AND_ASSIGN(
auto predicate,
PredicateBuilder::Or({PredicateBuilder::Between(/*field_index=*/0, /*field_name=*/"f0",
FieldType::INT, Literal(1), Literal(3)),
PredicateBuilder::Between(/*field_index=*/0, /*field_name=*/"f0",
FieldType::INT, Literal(5), Literal(6))}));
auto parquet_batch_reader = PrepareParquetFileBatchReader(
file_path_, arrow_schema, /*predicate=*/predicate, std::nullopt, /*batch_size=*/2,
/*enable_page_level_filter=*/true);
ASSERT_NOK(parquet_batch_reader->GetPreviousBatchFileRowId(0));
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> batch1,
paimon::test::ReadResultCollector::CollectResultOneBatch(parquet_batch_reader.get()));
auto expected_batch1 = src_array->Slice(1, 2);
ASSERT_TRUE(batch1->chunk(0)->Equals(expected_batch1)) << batch1->ToString();
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(0).value(), 1);
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(1).value(), 2);
// out of bound return invalid
ASSERT_NOK(parquet_batch_reader->GetPreviousBatchFileRowId(2));
// Not adjacent pages
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> batch2,
paimon::test::ReadResultCollector::CollectResultOneBatch(parquet_batch_reader.get()));
auto expected_batch2 = std::dynamic_pointer_cast<arrow::StructArray>(
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_(fields), R"([
[3],
[5]
])")
.ValueOrDie());
ASSERT_TRUE(batch2->chunk(0)->Equals(expected_batch2)) << batch2->ToString();
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(0).value(), 3);
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(1).value(), 5);
// Only one record read
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> batch3,
paimon::test::ReadResultCollector::CollectResultOneBatch(parquet_batch_reader.get()));
auto expected_batch3 = src_array->Slice(6, 1);
ASSERT_TRUE(batch3->chunk(0)->Equals(expected_batch3)) << batch3->ToString();
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(0).value(), 6);
ASSERT_NOK(parquet_batch_reader->GetPreviousBatchFileRowId(1));
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> eof_batch,
paimon::test::ReadResultCollector::CollectResultOneBatch(parquet_batch_reader.get()));
ASSERT_EQ(nullptr, eof_batch);
// previous batch is eof, return invalid.
ASSERT_NOK(parquet_batch_reader->GetPreviousBatchFileRowId(0));
}
TEST_F(ParquetFileBatchReaderTest, TestRowMappingFullyAndPartially) {
arrow::FieldVector fields = {arrow::field("f0", arrow::int32())};
auto src_array = MakeSequentialIntData(12);
// data in file RowGroup0:[0, 1, 2] | RowGroup1:[3, 4, 5] | RowGroup2:[6, 7, 8] | RowGroup3:[9,
// 10, 11] one row per page
auto arrow_schema = arrow::schema(fields);
WriteArray(file_path_, src_array, arrow_schema, /*write_batch_size=*/1,
/*enable_dictionary=*/true, /*max_row_group_length=*/3, /*max_page_size=*/1);
// 3<=f0<=5 || f0==6 || f0==8
// RowGroup 1 is fully matched, RowGroup 2 is partially matched, RowGroup 0 and RowGroup 3 are
// not matched.
ASSERT_OK_AND_ASSIGN(
auto predicate,
PredicateBuilder::Or({PredicateBuilder::Between(/*field_index=*/0, /*field_name=*/"f0",
FieldType::INT, Literal(3), Literal(5)),
PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0",
FieldType::INT, Literal(6)),
PredicateBuilder::Equal(/*field_index=*/0, /*field_name=*/"f0",
FieldType::INT, Literal(8))}));
auto parquet_batch_reader = PrepareParquetFileBatchReader(
file_path_, arrow_schema, /*predicate=*/predicate, std::nullopt, /*batch_size=*/3,
/*enable_page_level_filter=*/true);
ASSERT_NOK(parquet_batch_reader->GetPreviousBatchFileRowId(0));
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> batch1,
paimon::test::ReadResultCollector::CollectResultOneBatch(parquet_batch_reader.get()));
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(0).value(), 3);
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(2).value(), 5);
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> batch2,
paimon::test::ReadResultCollector::CollectResultOneBatch(parquet_batch_reader.get()));
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(0).value(), 6);
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(1).value(), 8);
}
TEST_F(ParquetFileBatchReaderTest, TestRowMappingSetReadSchemaTwice) {
arrow::FieldVector fields = {arrow::field("f0", arrow::int32())};
auto src_array = MakeSequentialIntData(12);
// data in file RowGroup0:[0, 1, 2] | RowGroup1:[3, 4, 5] | RowGroup2:[6, 7, 8] | RowGroup3:[9,
// 10, 11] one row per page
auto arrow_schema = arrow::schema(fields);
WriteArray(file_path_, src_array, arrow_schema, /*write_batch_size=*/1,
/*enable_dictionary=*/true, /*max_row_group_length=*/3, /*max_page_size=*/1);
// 1<=f0<=3 || 6<=f0<=7
ASSERT_OK_AND_ASSIGN(
auto predicate,
PredicateBuilder::Or({PredicateBuilder::Between(/*field_index=*/0, /*field_name=*/"f0",
FieldType::INT, Literal(1), Literal(3)),
PredicateBuilder::Between(/*field_index=*/0, /*field_name=*/"f0",
FieldType::INT, Literal(6), Literal(7))}));
auto parquet_batch_reader = PrepareParquetFileBatchReader(
file_path_, arrow_schema, /*predicate=*/predicate, std::nullopt, /*batch_size=*/3,
/*enable_page_level_filter=*/true);
ASSERT_NOK(parquet_batch_reader->GetPreviousBatchFileRowId(0));
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> batch1,
paimon::test::ReadResultCollector::CollectResultOneBatch(parquet_batch_reader.get()));
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(0).value(), 1);
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(1).value(), 2);
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> batch2,
paimon::test::ReadResultCollector::CollectResultOneBatch(parquet_batch_reader.get()));
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(0).value(), 3);
ASSERT_NOK(parquet_batch_reader->GetPreviousBatchFileRowId(1));
ASSERT_OK_AND_ASSIGN(
predicate,
PredicateBuilder::Or({PredicateBuilder::Between(/*field_index=*/0, /*field_name=*/"f0",
FieldType::INT, Literal(3), Literal(5))}));
std::unique_ptr<ArrowSchema> c_schema = std::make_unique<ArrowSchema>();
ASSERT_TRUE(arrow::ExportSchema(*arrow_schema, c_schema.get()).ok());
ASSERT_OK(
parquet_batch_reader->SetReadSchema(c_schema.get(), /*predicate=*/predicate, std::nullopt));
ASSERT_NOK(parquet_batch_reader->GetPreviousBatchFileRowId(0));
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<arrow::ChunkedArray> batch3,
paimon::test::ReadResultCollector::CollectResultOneBatch(parquet_batch_reader.get()));
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(0).value(), 3);
ASSERT_EQ(parquet_batch_reader->GetPreviousBatchFileRowId(2).value(), 5);
}
} // namespace paimon::parquet::test