blob: 7076486ac639d5721b6985135aada480adf2398c [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 "iceberg/data/file_scan_task_reader.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "iceberg/arrow_c_data_guard_internal.h"
#include "iceberg/arrow_c_data_util_internal.h"
#include "iceberg/data/delete_filter.h"
#include "iceberg/file_reader.h"
#include "iceberg/manifest/manifest_entry.h"
#include "iceberg/schema.h"
#include "iceberg/schema_internal.h"
#include "iceberg/table_scan.h"
#include "iceberg/util/macros.h"
namespace iceberg {
namespace {
ReaderOptions MakeReaderOptions(const DataFile& data_file, std::shared_ptr<FileIO> io,
std::shared_ptr<Schema> projection,
std::shared_ptr<Expression> filter,
std::shared_ptr<NameMapping> name_mapping,
ReaderProperties properties) {
return ReaderOptions{
.path = data_file.file_path,
.length = static_cast<size_t>(data_file.file_size_in_bytes),
.io = std::move(io),
.projection = std::move(projection),
.filter = std::move(filter),
.name_mapping = std::move(name_mapping),
.properties = std::move(properties),
};
}
class MergeOnReadStreamSource {
public:
MergeOnReadStreamSource(std::unique_ptr<Reader> reader,
std::unique_ptr<DeleteFilter> delete_filter,
std::shared_ptr<::iceberg::Schema> required_schema,
std::shared_ptr<::iceberg::Schema> projected_schema,
ProjectionContext projection_context)
: reader_(std::move(reader)),
delete_filter_(std::move(delete_filter)),
required_schema_(std::move(required_schema)),
projected_schema_(std::move(projected_schema)),
project_all_rows_(required_schema_->SameSchema(*projected_schema_)),
projection_context_(std::move(projection_context)) {}
~MergeOnReadStreamSource() {
if (cached_schema_.has_value() && cached_schema_->release != nullptr) {
cached_schema_->release(&cached_schema_.value());
}
}
Status Close() {
if (reader_ == nullptr) {
return {};
}
return reader_->Close();
}
Result<std::optional<ArrowArray>> Next() {
if (!cached_schema_.has_value()) {
// File readers expose one stable Arrow schema for every batch in the stream.
ICEBERG_ASSIGN_OR_RAISE(cached_schema_, reader_->Schema());
}
ArrowSchema& input_arrow_schema = cached_schema_.value();
while (true) {
ICEBERG_ASSIGN_OR_RAISE(auto next_batch, reader_->Next());
if (!next_batch.has_value()) {
return std::nullopt;
}
ArrowArray input_batch = std::move(next_batch.value());
internal::ArrowArrayGuard input_batch_guard(&input_batch);
ICEBERG_ASSIGN_OR_RAISE(
auto alive, delete_filter_->ComputeAliveRows(input_arrow_schema, input_batch));
if (alive.empty()) {
continue;
}
if (alive.alive_count() == input_batch.length && project_all_rows_) {
// Transfer ownership to the stream result; the local guard must not release it.
ArrowArray output_batch = input_batch;
input_batch.release = nullptr;
return output_batch;
}
return ProjectBatch(&input_batch, alive.indices, projection_context_);
}
}
Result<ArrowSchema> Schema() {
ArrowSchema schema;
ICEBERG_RETURN_UNEXPECTED(ToArrowSchema(*projected_schema_, &schema));
return schema;
}
private:
std::unique_ptr<Reader> reader_;
std::unique_ptr<DeleteFilter> delete_filter_;
std::shared_ptr<::iceberg::Schema> required_schema_;
std::shared_ptr<::iceberg::Schema> projected_schema_;
bool project_all_rows_ = false;
ProjectionContext projection_context_;
std::optional<ArrowSchema> cached_schema_;
};
} // namespace
class FileScanTaskReader::Impl {
public:
static Result<std::unique_ptr<Impl>> Make(Options options) {
ICEBERG_PRECHECK(options.io != nullptr, "FileIO must not be null");
ICEBERG_PRECHECK(options.table_schema != nullptr, "Table schema must not be null");
ICEBERG_PRECHECK(options.projected_schema != nullptr,
"Projected schema must not be null");
for (const auto& schema : options.schemas) {
ICEBERG_PRECHECK(schema != nullptr, "Schema list must not contain null schemas");
}
ICEBERG_ASSIGN_OR_RAISE(
auto field_lookup,
DeleteFilter::MakeFieldLookup(options.table_schema, options.schemas));
auto delete_counter = std::make_shared<DeleteCounter>();
return std::unique_ptr<Impl>(
new Impl(std::move(options), std::move(field_lookup), std::move(delete_counter)));
}
Result<ArrowArrayStream> Open(const FileScanTask& task) {
const auto& data_file = task.data_file();
ICEBERG_PRECHECK(data_file != nullptr, "Data file must not be null");
ICEBERG_PRECHECK(data_file->file_size_in_bytes >= 0,
"Data file size must not be negative: {}",
data_file->file_size_in_bytes);
if (task.delete_files().empty()) {
auto options =
MakeReaderOptions(*data_file, io_, projected_schema_, task.residual_filter(),
name_mapping_, properties_);
ICEBERG_ASSIGN_OR_RAISE(
auto reader, ReaderFactoryRegistry::Open(data_file->file_format, options));
return MakeArrowArrayStream(std::move(reader));
}
const bool has_position_deletes =
std::any_of(task.delete_files().begin(), task.delete_files().end(),
[](const std::shared_ptr<DataFile>& f) {
return f->content == DataFile::Content::kPositionDeletes;
});
ICEBERG_ASSIGN_OR_RAISE(
auto delete_filter,
DeleteFilter::Make(data_file->file_path, task.delete_files(), projected_schema_,
io_, field_lookup_, has_position_deletes, delete_counter_));
auto required_schema = delete_filter->RequiredSchema();
auto project_batch_function = ProjectionContext::ResolveProjectBatchFunction();
// ProjectionContext borrows schemas that are kept in MergeOnReadStreamSource.
ICEBERG_ASSIGN_OR_RAISE(auto projection_context,
ProjectionContext::Make(*required_schema, *projected_schema_,
project_batch_function));
auto options = MakeReaderOptions(*data_file, io_, required_schema,
task.residual_filter(), name_mapping_, properties_);
ICEBERG_ASSIGN_OR_RAISE(auto reader,
ReaderFactoryRegistry::Open(data_file->file_format, options));
auto mor_reader = std::make_unique<MergeOnReadStreamSource>(
std::move(reader), std::move(delete_filter), std::move(required_schema),
projected_schema_, std::move(projection_context));
return MakeArrowArrayStream(std::move(mor_reader));
}
private:
Impl(Options options, DeleteFilter::FieldLookup field_lookup,
std::shared_ptr<DeleteCounter> delete_counter)
: io_(std::move(options.io)),
schemas_(std::move(options.schemas)),
projected_schema_(std::move(options.projected_schema)),
name_mapping_(std::move(options.name_mapping)),
properties_(ReaderProperties::FromMap(options.properties)),
field_lookup_(std::move(field_lookup)),
delete_counter_(std::move(delete_counter)) {}
std::shared_ptr<FileIO> io_;
std::vector<std::shared_ptr<Schema>> schemas_;
std::shared_ptr<Schema> projected_schema_;
std::shared_ptr<NameMapping> name_mapping_;
ReaderProperties properties_;
DeleteFilter::FieldLookup field_lookup_;
std::shared_ptr<DeleteCounter> delete_counter_;
};
Result<std::unique_ptr<FileScanTaskReader>> FileScanTaskReader::Make(Options options) {
ICEBERG_ASSIGN_OR_RAISE(auto impl, Impl::Make(std::move(options)));
return std::unique_ptr<FileScanTaskReader>(new FileScanTaskReader(std::move(impl)));
}
FileScanTaskReader::FileScanTaskReader(std::unique_ptr<Impl> impl)
: impl_(std::move(impl)) {}
FileScanTaskReader::~FileScanTaskReader() = default;
Result<ArrowArrayStream> FileScanTaskReader::Open(const FileScanTask& task) {
return impl_->Open(task);
}
} // namespace iceberg