blob: d7a1cde10cecdf896f13e440892ac7cc2372a589 [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 <cstddef>
#include <unordered_map>
#include "arrow/acero/options.h"
#include "arrow/array/array_nested.h"
#include "arrow/c/abi.h"
#include "arrow/c/bridge.h"
#include "arrow/compute/api.h"
#include "arrow/dataset/dataset.h"
#include "arrow/dataset/file_base.h"
#include "arrow/dataset/file_parquet.h"
#include "arrow/dataset/type_fwd.h"
#include "arrow/io/caching.h"
#include "arrow/io/interfaces.h"
#include "arrow/record_batch.h"
#include "arrow/type.h"
#include "arrow/util/range.h"
#include "arrow/util/thread_pool.h"
#include "fmt/format.h"
#include "paimon/common/metrics/metrics_impl.h"
#include "paimon/common/utils/arrow/status_utils.h"
#include "paimon/common/utils/options_utils.h"
#include "paimon/format/parquet/parquet_field_id_converter.h"
#include "paimon/format/parquet/parquet_format_defs.h"
#include "paimon/format/parquet/parquet_timestamp_converter.h"
#include "paimon/format/parquet/predicate_converter.h"
#include "paimon/reader/batch_reader.h"
#include "paimon/utils/roaring_bitmap32.h"
#include "parquet/arrow/reader.h"
#include "parquet/properties.h"
namespace arrow {
class MemoryPool;
} // namespace arrow
namespace paimon {
class Predicate;
} // namespace paimon
namespace paimon::parquet {
ParquetFileBatchReader::ParquetFileBatchReader(
std::shared_ptr<arrow::io::RandomAccessFile>&& input_stream,
std::unique_ptr<FileReaderWrapper>&& reader, const std::map<std::string, std::string>& options,
const std::shared_ptr<arrow::MemoryPool>& arrow_pool)
: options_(options),
arrow_pool_(arrow_pool),
input_stream_(std::move(input_stream)),
reader_(std::move(reader)),
read_ranges_(reader_->GetAllRowGroupRanges()),
metrics_(std::make_shared<MetricsImpl>()) {}
Result<std::unique_ptr<ParquetFileBatchReader>> ParquetFileBatchReader::Create(
std::shared_ptr<arrow::io::RandomAccessFile>&& input_stream,
const std::shared_ptr<arrow::MemoryPool>& pool,
const std::map<std::string, std::string>& options, int32_t batch_size) {
assert(input_stream);
PAIMON_ASSIGN_OR_RAISE(::parquet::ReaderProperties reader_properties,
CreateReaderProperties(pool, options));
PAIMON_ASSIGN_OR_RAISE(::parquet::ArrowReaderProperties arrow_reader_properties,
CreateArrowReaderProperties(pool, options, batch_size));
::parquet::arrow::FileReaderBuilder file_reader_builder;
PAIMON_RETURN_NOT_OK_FROM_ARROW(file_reader_builder.Open(input_stream, reader_properties));
std::unique_ptr<::parquet::arrow::FileReader> file_reader;
PAIMON_RETURN_NOT_OK_FROM_ARROW(file_reader_builder.memory_pool(pool.get())
->properties(arrow_reader_properties)
->Build(&file_reader));
PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<FileReaderWrapper> reader,
FileReaderWrapper::Create(std::move(file_reader)));
auto parquet_file_batch_reader = std::unique_ptr<ParquetFileBatchReader>(
new ParquetFileBatchReader(std::move(input_stream), std::move(reader), options, pool));
PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<::ArrowSchema> file_schema,
parquet_file_batch_reader->GetFileSchema());
PAIMON_RETURN_NOT_OK(parquet_file_batch_reader->SetReadSchema(
file_schema.get(), /*predicate=*/nullptr, /*selection_bitmap=*/std::nullopt));
return parquet_file_batch_reader;
}
Result<std::unique_ptr<::ArrowSchema>> ParquetFileBatchReader::GetFileSchema() const {
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Schema> file_schema, reader_->GetSchema());
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Schema> new_schema,
ParquetFieldIdConverter::GetPaimonIdsFromParquetIds(file_schema));
PAIMON_ASSIGN_OR_RAISE(
std::shared_ptr<arrow::DataType> new_type,
ParquetTimestampConverter::AdjustTimezone(arrow::struct_(new_schema->fields())));
auto c_schema = std::make_unique<::ArrowSchema>();
PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportType(*new_type, c_schema.get()));
return c_schema;
}
Status ParquetFileBatchReader::SetReadSchema(
::ArrowSchema* schema, const std::shared_ptr<Predicate>& predicate,
const std::optional<RoaringBitmap32>& selection_bitmap) {
if (!schema) {
return Status::Invalid("SetReadSchema failed: read schema cannot be nullptr");
}
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Schema> read_schema,
arrow::ImportSchema(schema));
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Schema> file_schema, reader_->GetSchema());
std::unordered_map<std::string, std::vector<int32_t>> field_index_map;
int32_t i = 0;
for (const auto& field : file_schema->fields()) {
std::vector<int32_t> v;
FlattenSchema(field->type(), &i, &v);
field_index_map[field->name()] = v;
}
std::vector<int32_t> column_indices;
for (const auto& field : read_schema->field_names()) {
if (field_index_map.find(field) != field_index_map.end()) {
for (int32_t index : field_index_map[field]) {
column_indices.push_back(index);
}
} else {
return Status::Invalid(fmt::format("Field {} is not found in schema.", field));
}
}
std::vector<int32_t> row_groups = arrow::internal::Iota(reader_->GetNumberOfRowGroups());
if (predicate) {
PAIMON_ASSIGN_OR_RAISE(row_groups,
FilterRowGroupsByPredicate(predicate, file_schema, row_groups));
}
if (selection_bitmap) {
PAIMON_ASSIGN_OR_RAISE(row_groups,
FilterRowGroupsByBitmap(selection_bitmap.value(), row_groups));
}
read_data_type_ = arrow::struct_(read_schema->fields());
read_row_groups_ = row_groups;
read_column_indices_ = column_indices;
metrics_->SetCounter(ParquetMetrics::READ_ROW_GROUPS_TOTAL, reader_->GetNumberOfRowGroups());
metrics_->SetCounter(ParquetMetrics::READ_ROW_GROUPS_AFTER_FILTER, row_groups.size());
PAIMON_ASSIGN_OR_RAISE(std::set<int32_t> ordered_row_groups,
reader_->FilterRowGroupsByReadRanges(read_ranges_, read_row_groups_));
return reader_->PrepareForReadingLazy(ordered_row_groups, read_column_indices_);
}
Result<std::vector<int32_t>> ParquetFileBatchReader::FilterRowGroupsByPredicate(
const std::shared_ptr<Predicate>& predicate, const std::shared_ptr<arrow::Schema> file_schema,
const std::vector<int32_t>& src_row_groups) const {
if (!predicate) {
return Status::Invalid("cannot pushdown an empty predicate");
}
// convert paimon predicate to arrow expression
PAIMON_ASSIGN_OR_RAISE(
uint32_t predicate_node_count_limit,
OptionsUtils::GetValueFromMap<uint32_t>(options_, PARQUET_READ_PREDICATE_NODE_COUNT_LIMIT,
DEFAULT_PARQUET_READ_PREDICATE_NODE_COUNT_LIMIT));
PAIMON_ASSIGN_OR_RAISE(arrow::compute::Expression expr,
PredicateConverter::Convert(predicate, predicate_node_count_limit));
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(arrow::Expression bind_expr, expr.Bind(*file_schema));
// prepare file source
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(int64_t file_length, input_stream_->GetSize());
auto file_source = arrow::dataset::FileSource(input_stream_, /*size=*/file_length);
// filter row group by arrow expression and row group meta
auto parquet_file_format = std::make_shared<arrow::dataset::ParquetFileFormat>();
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
std::shared_ptr<arrow::dataset::ParquetFileFragment> file_fragment,
parquet_file_format->MakeFragment(
file_source, /*partition_expression=*/PredicateConverter::AlwaysTrue(),
/*physical_schema=*/nullptr, /*row_groups=*/src_row_groups));
PAIMON_RETURN_NOT_OK_FROM_ARROW(
file_fragment->EnsureCompleteMetadata(reader_->GetFileReader()));
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(arrow::dataset::FragmentVector target_fragments,
file_fragment->SplitByRowGroup(bind_expr));
std::vector<int32_t> target_row_groups;
target_row_groups.reserve(src_row_groups.size());
for (const auto& fragment : target_fragments) {
auto parquet_fragment = dynamic_cast<arrow::dataset::ParquetFileFragment*>(fragment.get());
if (!parquet_fragment) {
return Status::Invalid("cannot cast to ParquetFileFragment in ParquetFileBatchReader");
}
target_row_groups.insert(target_row_groups.end(), parquet_fragment->row_groups().begin(),
parquet_fragment->row_groups().end());
}
return target_row_groups;
}
Result<std::vector<int32_t>> ParquetFileBatchReader::FilterRowGroupsByBitmap(
const RoaringBitmap32& bitmap, const std::vector<int32_t>& src_row_groups) const {
if (bitmap.IsEmpty()) {
return Status::Invalid("cannot push down an empty bitmap to ParquetFileBatchReader");
}
const auto& all_row_group_ranges = reader_->GetAllRowGroupRanges();
// filter row groups by row range
std::vector<int32_t> target_row_groups;
for (const auto& row_group_idx : src_row_groups) {
if (static_cast<size_t>(row_group_idx) >= all_row_group_ranges.size()) {
return Status::Invalid(
fmt::format("src row group {} not in row group meta", row_group_idx));
}
const auto& [start_row_idx, end_row_idx] = all_row_group_ranges[row_group_idx];
if (bitmap.ContainsAny(start_row_idx, end_row_idx)) {
target_row_groups.push_back(row_group_idx);
}
}
return target_row_groups;
}
Result<BatchReader::ReadBatch> ParquetFileBatchReader::NextBatch() {
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::RecordBatch> batch, reader_->Next());
if (batch == nullptr) {
return BatchReader::MakeEofBatch();
}
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Array> array, batch->ToStructArray());
PAIMON_ASSIGN_OR_RAISE(bool need_cast, ParquetTimestampConverter::NeedCastArrayForTimestamp(
array->type(), read_data_type_));
if (need_cast) {
PAIMON_ASSIGN_OR_RAISE(array, ParquetTimestampConverter::CastArrayForTimestamp(
array, read_data_type_, arrow_pool_));
}
PAIMON_ASSIGN_OR_RAISE(need_cast, ParquetTimestampConverter::NeedCastArrayForTimestamp(
array->type(), read_data_type_));
if (need_cast) {
return Status::Invalid(
fmt::format("unexpected: in parquet, after CastArrayForTimestamp, output type {} not "
"equal with read schema {}",
array->type()->ToString(), read_data_type_->ToString()));
}
std::unique_ptr<ArrowArray> c_array = std::make_unique<ArrowArray>();
std::unique_ptr<ArrowSchema> c_schema = std::make_unique<ArrowSchema>();
PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportArray(*array, c_array.get(), c_schema.get()));
read_rows_ += array->length();
read_batch_count_++;
metrics_->SetCounter(ParquetMetrics::READ_ROWS, read_rows_);
metrics_->SetCounter(ParquetMetrics::READ_BATCH_COUNT, read_batch_count_);
return make_pair(std::move(c_array), std::move(c_schema));
}
Result<std::vector<std::pair<uint64_t, uint64_t>>> ParquetFileBatchReader::GenReadRanges(
bool* need_prefetch) const {
*need_prefetch = true;
return reader_->GetAllRowGroupRanges();
}
Result<::parquet::ReaderProperties> ParquetFileBatchReader::CreateReaderProperties(
const std::shared_ptr<arrow::MemoryPool>& pool,
const std::map<std::string, std::string>& options) {
::parquet::ReaderProperties reader_properties;
// TODO(jinli.zjw): set more ReaderProperties (compare with java)
PAIMON_ASSIGN_OR_RAISE(
bool enable_pre_buffer,
OptionsUtils::GetValueFromMap<bool>(options, PARQUET_READ_ENABLE_PRE_BUFFER, true));
if (enable_pre_buffer) {
reader_properties.enable_buffered_stream();
} else {
reader_properties.disable_buffered_stream();
}
return reader_properties;
}
Result<::parquet::ArrowReaderProperties> ParquetFileBatchReader::CreateArrowReaderProperties(
const std::shared_ptr<arrow::MemoryPool>& pool,
const std::map<std::string, std::string>& options, int32_t batch_size) {
PAIMON_ASSIGN_OR_RAISE(
uint32_t executor_thread_count,
OptionsUtils::GetValueFromMap<uint32_t>(options, PARQUET_READ_EXECUTOR_THREAD_COUNT,
DEFAULT_PARQUET_READ_EXECUTOR_THREAD_COUNT));
::parquet::ArrowReaderProperties arrow_reader_props;
// TODO(jinli.zjw): set more ArrowReaderProperties (compare with java)
PAIMON_ASSIGN_OR_RAISE(
bool enable_pre_buffer,
OptionsUtils::GetValueFromMap<bool>(options, PARQUET_READ_ENABLE_PRE_BUFFER, true));
arrow_reader_props.set_pre_buffer(enable_pre_buffer);
arrow_reader_props.set_batch_size(static_cast<int64_t>(batch_size));
if (executor_thread_count != 0) {
PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::SetCpuThreadPoolCapacity(executor_thread_count));
arrow_reader_props.set_use_threads(true);
} else {
arrow_reader_props.set_use_threads(false);
}
PAIMON_ASSIGN_OR_RAISE(bool cache_lazy, OptionsUtils::GetValueFromMap<bool>(
options, PARQUET_READ_CACHE_OPTION_LAZY, false));
PAIMON_ASSIGN_OR_RAISE(
int64_t cache_prefetch_limit,
OptionsUtils::GetValueFromMap<int64_t>(options, PARQUET_READ_CACHE_OPTION_PREFETCH_LIMIT,
DEFAULT_PARQUET_READ_CACHE_OPTION_PREFETCH_LIMIT));
PAIMON_ASSIGN_OR_RAISE(
int64_t cache_range_size_limit,
OptionsUtils::GetValueFromMap<int64_t>(options, PARQUET_READ_CACHE_OPTION_RANGE_SIZE_LIMIT,
DEFAULT_PARQUET_READ_CACHE_OPTION_RANGE_SIZE_LIMIT));
auto cache_option = arrow::io::CacheOptions::Defaults();
cache_option.lazy = cache_lazy;
cache_option.prefetch_limit = cache_prefetch_limit;
cache_option.range_size_limit = cache_range_size_limit;
arrow_reader_props.set_cache_options(cache_option);
return arrow_reader_props;
}
} // namespace paimon::parquet