This is an automated email from the ASF dual-hosted git repository.
wgtmac pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new 39c04b90 refactor: simplify src/iceberg/manifest/manifest_group.cc
(#952)
39c04b90 is described below
commit 39c04b908d4d02112d4bea7312ad10207e317562
Author: Zehua Zou <[email protected]>
AuthorDate: Sat Oct 10 10:49:23 2026 +0800
refactor: simplify src/iceberg/manifest/manifest_group.cc (#952)
---
src/iceberg/manifest/manifest_group.cc | 454 ++++++++++++++------------------
src/iceberg/manifest/manifest_group.h | 7 +-
src/iceberg/test/manifest_group_test.cc | 136 ++++++++++
3 files changed, 332 insertions(+), 265 deletions(-)
diff --git a/src/iceberg/manifest/manifest_group.cc
b/src/iceberg/manifest/manifest_group.cc
index 87ee857e..036a71bd 100644
--- a/src/iceberg/manifest/manifest_group.cc
+++ b/src/iceberg/manifest/manifest_group.cc
@@ -131,6 +131,146 @@ ManifestGroup::~ManifestGroup() = default;
ManifestGroup::ManifestGroup(ManifestGroup&&) noexcept = default;
ManifestGroup& ManifestGroup::operator=(ManifestGroup&&) noexcept = default;
+class ManifestGroup::PlanningContext {
+ public:
+ // `group` is borrowed; its planning configuration must remain unchanged for
the
+ // lifetime of this context.
+ static Result<PlanningContext> Make(const ManifestGroup& group,
+ std::vector<std::string> columns) {
+ std::unique_ptr<Evaluator> file_evaluator;
+ if (!group.file_filter_ || group.file_filter_->op() ==
Expression::Operation::kTrue) {
+ return PlanningContext(group, std::move(file_evaluator),
std::move(columns));
+ }
+
+ auto data_file_schema = DataFileFilterSchema();
+ ICEBERG_ASSIGN_OR_RAISE(
+ file_evaluator,
+ Evaluator::Make(*data_file_schema, group.file_filter_,
group.case_sensitive_));
+ if (std::ranges::contains(columns, Schema::kAllColumns)) {
+ return PlanningContext(group, std::move(file_evaluator),
std::move(columns));
+ }
+
+ ICEBERG_ASSIGN_OR_RAISE(
+ auto bound_file_filter,
+ Binder::Bind(*data_file_schema, group.file_filter_,
group.case_sensitive_));
+ ICEBERG_ASSIGN_OR_RAISE(auto referenced_field_ids,
+
ReferenceVisitor::GetReferencedFieldIds(bound_file_filter));
+
+ std::unordered_set<std::string> selected_columns(columns.cbegin(),
columns.cend());
+ for (const auto field_id : referenced_field_ids) {
+ if (field_id == DataFile::kSpecIdFieldId) {
+ continue;
+ }
+ ICEBERG_ASSIGN_OR_RAISE(auto column_name,
+ data_file_schema->FindColumnNameById(field_id));
+ if (column_name.has_value()) {
+ std::string column_name_str(column_name.value());
+ if (selected_columns.insert(column_name_str).second) {
+ columns.push_back(std::move(column_name_str));
+ }
+ }
+ }
+ return PlanningContext(group, std::move(file_evaluator),
std::move(columns));
+ }
+
+ Result<std::unique_ptr<ManifestEvaluator>> MakeManifestEvaluator(
+ int32_t spec_id) const {
+ auto spec_iter = group_.specs_by_id_.find(spec_id);
+ ICEBERG_CHECK(spec_iter != group_.specs_by_id_.cend(),
+ "Cannot find partition spec for ID {}", spec_id);
+
+ auto projector = Projections::Inclusive(*spec_iter->second,
*group_.schema_,
+ group_.case_sensitive_);
+ ICEBERG_ASSIGN_OR_RAISE(auto partition_filter,
+ projector->Project(group_.data_filter_));
+ ICEBERG_ASSIGN_OR_RAISE(partition_filter,
And::Make(std::move(partition_filter),
+
group_.partition_filter_));
+ return ManifestEvaluator::MakePartitionFilter(std::move(partition_filter),
+ spec_iter->second,
*group_.schema_,
+ group_.case_sensitive_);
+ }
+
+ Result<std::shared_ptr<ResidualEvaluator>> MakeResidualEvaluator(
+ int32_t spec_id) const {
+ auto spec_iter = group_.specs_by_id_.find(spec_id);
+ ICEBERG_CHECK(spec_iter != group_.specs_by_id_.cend(),
+ "Cannot find partition spec for ID {}", spec_id);
+
+ ICEBERG_ASSIGN_OR_RAISE(
+ auto evaluator,
+ ResidualEvaluator::Make(
+ (group_.ignore_residuals_ ? True::Instance() :
group_.data_filter_),
+ *spec_iter->second, *group_.schema_, group_.case_sensitive_));
+ return std::shared_ptr<ResidualEvaluator>(std::move(evaluator));
+ }
+
+ Result<bool> ShouldReadManifest(const ManifestFile& manifest,
+ const ManifestEvaluator& evaluator) const {
+ ICEBERG_ASSIGN_OR_RAISE(bool should_match, evaluator.Evaluate(manifest));
+ const bool has_non_deleted_files =
+ manifest.has_added_files() || manifest.has_existing_files();
+ const bool has_non_existing_files =
+ manifest.has_added_files() || manifest.has_deleted_files();
+ const bool has_only_ignored_files =
+ (group_.ignore_deleted_ && !has_non_deleted_files) ||
+ (group_.ignore_existing_ && !has_non_existing_files);
+ if (!should_match || has_only_ignored_files) {
+ if (group_.scan_metrics_) {
+ group_.scan_metrics_->skipped_data_manifests->Increment(1);
+ }
+ return false;
+ }
+
+ if (group_.scan_metrics_) {
+ group_.scan_metrics_->scanned_data_manifests->Increment(1);
+ }
+ return true;
+ }
+
+ Result<bool> ShouldKeepEntry(const ManifestEntry& entry) const {
+ if (group_.ignore_existing_ && entry.status == ManifestStatus::kExisting) {
+ if (group_.scan_metrics_) {
+ group_.scan_metrics_->skipped_data_files->Increment(1);
+ }
+ return false;
+ }
+
+ ICEBERG_DCHECK(entry.data_file != nullptr, "Data file cannot be null");
+ if (file_evaluator_ != nullptr) {
+ DataFileStructLike data_file(*entry.data_file);
+ ICEBERG_ASSIGN_OR_RAISE(bool should_match,
file_evaluator_->Evaluate(data_file));
+ if (!should_match) {
+ if (group_.scan_metrics_) {
+ group_.scan_metrics_->skipped_data_files->Increment(1);
+ }
+ return false;
+ }
+ }
+
+ if (!group_.manifest_entry_predicate_(entry)) {
+ if (group_.scan_metrics_) {
+ group_.scan_metrics_->skipped_data_files->Increment(1);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ const std::vector<std::string>& columns() const { return columns_; }
+
+ private:
+ PlanningContext(const ManifestGroup& group, std::unique_ptr<Evaluator>
file_evaluator,
+ std::vector<std::string> columns)
+ : group_(group),
+ file_evaluator_(std::move(file_evaluator)),
+ columns_(std::move(columns)) {}
+
+ const ManifestGroup& group_;
+ std::unique_ptr<Evaluator> file_evaluator_;
+ std::vector<std::string> columns_;
+};
+
class ManifestGroup::FilePlanningStream final : public FileScanTaskStream {
public:
static Result<FileScanTaskStreamPtr> Make(std::unique_ptr<ManifestGroup>
group) {
@@ -141,20 +281,12 @@ class ManifestGroup::FilePlanningStream final : public
FileScanTaskStream {
auto stats_projection =
group->PrepareStatsProjection(delete_index->has_equality_deletes());
+ ICEBERG_ASSIGN_OR_RAISE(
+ auto context, PlanningContext::Make(*group,
std::move(stats_projection.columns)));
- std::unique_ptr<Evaluator> data_file_evaluator;
- if (group->file_filter_ &&
- group->file_filter_->op() != Expression::Operation::kTrue) {
- ICEBERG_ASSIGN_OR_RAISE(
- data_file_evaluator,
- Evaluator::Make(*DataFileFilterSchema(), group->file_filter_,
- group->case_sensitive_));
- }
- const bool drop_stats = stats_projection.drop_stats;
-
- return FileScanTaskStreamPtr(new FilePlanningStream(
- std::move(group), std::move(delete_index),
std::move(data_file_evaluator),
- std::move(stats_projection.columns), drop_stats));
+ return FileScanTaskStreamPtr(
+ new FilePlanningStream(std::move(group), std::move(delete_index),
+ std::move(context),
stats_projection.drop_stats));
}
Result<std::optional<std::shared_ptr<FileScanTask>>> NextImpl() override {
@@ -165,24 +297,8 @@ class ManifestGroup::FilePlanningStream final : public
FileScanTaskStream {
}
auto [spec_id, value] = std::move(entry).value();
- if (group_->ignore_existing_ && value.status ==
ManifestStatus::kExisting) {
- IncrementSkippedDataFiles();
- continue;
- }
-
- ICEBERG_DCHECK(value.data_file != nullptr, "Data file cannot be null");
- if (data_file_evaluator_) {
- DataFileStructLike data_file(*value.data_file);
- ICEBERG_ASSIGN_OR_RAISE(bool should_match,
- data_file_evaluator_->Evaluate(data_file));
- if (!should_match) {
- IncrementSkippedDataFiles();
- continue;
- }
- }
-
- if (!group_->manifest_entry_predicate_(value)) {
- IncrementSkippedDataFiles();
+ ICEBERG_ASSIGN_OR_RAISE(bool should_keep,
context_.ShouldKeepEntry(value));
+ if (!should_keep) {
continue;
}
@@ -211,40 +327,19 @@ class ManifestGroup::FilePlanningStream final : public
FileScanTaskStream {
private:
FilePlanningStream(std::unique_ptr<ManifestGroup> group,
std::unique_ptr<DeleteFileIndex> delete_index,
- std::unique_ptr<Evaluator> data_file_evaluator,
- std::vector<std::string> columns, bool drop_stats)
+ PlanningContext context, bool drop_stats)
: group_(std::move(group)),
delete_index_(std::move(delete_index)),
- data_file_evaluator_(std::move(data_file_evaluator)),
- columns_(std::move(columns)),
+ context_(std::move(context)),
drop_stats_(drop_stats) {}
using TaggedEntry = std::pair<int32_t, ManifestEntry>;
using TaggedStream = std::pair<int32_t, ManifestEntryStreamPtr>;
- // FIXME: Perhaps refactor this concurrent/sequential stream state machine
into a
+ // FIXME: Perhaps refactor this stream state machine into a
// generic reusable ParallelStream<T> utility, similar to Iceberg Java's
// ParallelIterable.
Result<std::optional<TaggedEntry>> NextEntry() {
- if (!group_->executor_.has_value()) {
- while (true) {
- if (!entry_stream_) {
- ICEBERG_ASSIGN_OR_RAISE(bool opened, OpenNextManifest());
- if (!opened) {
- return std::nullopt;
- }
- }
-
- ICEBERG_ASSIGN_OR_RAISE(auto entry, entry_stream_->Next());
- if (!entry.has_value()) {
- entry_stream_.reset();
- continue;
- }
- return std::optional<TaggedEntry>{std::in_place, current_spec_id_,
- std::move(entry).value()};
- }
- }
-
while (true) {
if (next_batch_stream_ == batch_streams_.size()) {
ICEBERG_ASSIGN_OR_RAISE(bool loaded, LoadNextManifestBatch());
@@ -270,21 +365,7 @@ class ManifestGroup::FilePlanningStream final : public
FileScanTaskStream {
return cached->second.get();
}
- auto spec_iter = group_->specs_by_id_.find(spec_id);
- ICEBERG_CHECK(spec_iter != group_->specs_by_id_.cend(),
- "Cannot find partition spec for ID {}", spec_id);
-
- const auto& spec = spec_iter->second;
- auto projector =
- Projections::Inclusive(*spec, *group_->schema_,
group_->case_sensitive_);
- ICEBERG_ASSIGN_OR_RAISE(auto partition_filter,
- projector->Project(group_->data_filter_));
- ICEBERG_ASSIGN_OR_RAISE(partition_filter,
And::Make(std::move(partition_filter),
-
group_->partition_filter_));
- ICEBERG_ASSIGN_OR_RAISE(auto evaluator,
- ManifestEvaluator::MakePartitionFilter(
- std::move(partition_filter), spec,
*group_->schema_,
- group_->case_sensitive_));
+ ICEBERG_ASSIGN_OR_RAISE(auto evaluator,
context_.MakeManifestEvaluator(spec_id));
auto* result = evaluator.get();
manifest_evaluators_.emplace(spec_id, std::move(evaluator));
return result;
@@ -296,67 +377,25 @@ class ManifestGroup::FilePlanningStream final : public
FileScanTaskStream {
return cached->second.get();
}
- auto spec_iter = group_->specs_by_id_.find(spec_id);
- ICEBERG_CHECK(spec_iter != group_->specs_by_id_.cend(),
- "Cannot find partition spec for ID {}", spec_id);
-
- ICEBERG_ASSIGN_OR_RAISE(
- auto evaluator,
- ResidualEvaluator::Make(
- (group_->ignore_residuals_ ? True::Instance() :
group_->data_filter_),
- *spec_iter->second, *group_->schema_, group_->case_sensitive_));
+ ICEBERG_ASSIGN_OR_RAISE(auto evaluator,
context_.MakeResidualEvaluator(spec_id));
auto* result = evaluator.get();
residual_evaluators_.emplace(spec_id, std::move(evaluator));
return result;
}
- Result<bool> ShouldReadManifest(const ManifestFile& manifest) {
- ICEBERG_ASSIGN_OR_RAISE(auto evaluator,
- GetManifestEvaluator(manifest.partition_spec_id));
- ICEBERG_ASSIGN_OR_RAISE(bool should_match, evaluator->Evaluate(manifest));
- const bool has_non_deleted_files =
- manifest.has_added_files() || manifest.has_existing_files();
- const bool has_non_existing_files =
- manifest.has_added_files() || manifest.has_deleted_files();
- const bool has_only_ignored_files =
- (group_->ignore_deleted_ && !has_non_deleted_files) ||
- (group_->ignore_existing_ && !has_non_existing_files);
- if (!should_match || has_only_ignored_files) {
- IncrementSkippedDataManifests();
- return false;
- }
-
- if (group_->scan_metrics_) {
- group_->scan_metrics_->scanned_data_manifests->Increment(1);
- }
- return true;
- }
-
- Result<bool> OpenNextManifest() {
- while (next_manifest_ < group_->data_manifests_.size()) {
- const auto& manifest = group_->data_manifests_[next_manifest_++];
- ICEBERG_ASSIGN_OR_RAISE(bool should_read, ShouldReadManifest(manifest));
- if (!should_read) {
- continue;
- }
-
- ICEBERG_ASSIGN_OR_RAISE(auto reader, group_->MakeReader(manifest,
columns_));
- ICEBERG_ASSIGN_OR_RAISE(entry_stream_, group_->ignore_deleted_
- ? reader->LiveEntriesStream()
- : reader->EntriesStream());
- current_spec_id_ = manifest.partition_spec_id;
- return true;
- }
- return false;
- }
-
Result<bool> LoadNextManifestBatch() {
+ const size_t batch_size = group_->executor_.has_value() ?
kManifestReadBatchSize : 1;
std::vector<const ManifestFile*> manifests;
- manifests.reserve(kManifestReadBatchSize);
+ manifests.reserve(batch_size);
+ // Skipped manifests do not count toward the batch size. Keep scanning
until enough
+ // readable manifests are collected or all input is exhausted.
while (next_manifest_ < group_->data_manifests_.size() &&
- manifests.size() < kManifestReadBatchSize) {
+ manifests.size() < batch_size) {
const auto& manifest = group_->data_manifests_[next_manifest_++];
- ICEBERG_ASSIGN_OR_RAISE(bool should_read, ShouldReadManifest(manifest));
+ ICEBERG_ASSIGN_OR_RAISE(auto evaluator,
+
GetManifestEvaluator(manifest.partition_spec_id));
+ ICEBERG_ASSIGN_OR_RAISE(bool should_read,
+ context_.ShouldReadManifest(manifest,
*evaluator));
if (should_read) {
manifests.push_back(&manifest);
}
@@ -375,7 +414,7 @@ class ManifestGroup::FilePlanningStream final : public
FileScanTaskStream {
group_->executor_, manifests,
[this](const ManifestFile* manifest) ->
Result<std::vector<TaggedStream>> {
ICEBERG_ASSIGN_OR_RAISE(auto reader,
- group_->MakeReader(*manifest, columns_));
+ group_->MakeReader(*manifest,
context_.columns()));
ICEBERG_ASSIGN_OR_RAISE(auto stream, group_->ignore_deleted_
?
reader->LiveEntriesStream()
:
reader->EntriesStream());
@@ -388,18 +427,6 @@ class ManifestGroup::FilePlanningStream final : public
FileScanTaskStream {
return true;
}
- void IncrementSkippedDataManifests() {
- if (group_->scan_metrics_) {
- group_->scan_metrics_->skipped_data_manifests->Increment(1);
- }
- }
-
- void IncrementSkippedDataFiles() {
- if (group_->scan_metrics_) {
- group_->scan_metrics_->skipped_data_files->Increment(1);
- }
- }
-
void UpdateResultMetrics(const DataFile& data_file,
const std::vector<std::shared_ptr<DataFile>>&
delete_files) {
if (!group_->scan_metrics_) {
@@ -420,15 +447,12 @@ class ManifestGroup::FilePlanningStream final : public
FileScanTaskStream {
std::unique_ptr<ManifestGroup> group_;
std::unique_ptr<DeleteFileIndex> delete_index_;
- std::unique_ptr<Evaluator> data_file_evaluator_;
- std::vector<std::string> columns_;
+ PlanningContext context_;
std::unordered_map<int32_t, std::unique_ptr<ManifestEvaluator>>
manifest_evaluators_;
std::unordered_map<int32_t, std::shared_ptr<ResidualEvaluator>>
residual_evaluators_;
- ManifestEntryStreamPtr entry_stream_;
std::vector<TaggedStream> batch_streams_;
size_t next_manifest_ = 0;
size_t next_batch_stream_ = 0;
- int32_t current_spec_id_ = 0;
bool drop_stats_;
// Limit the number of manifest readers and streams retained by
executor-backed
@@ -522,31 +546,26 @@ Result<FileScanTaskStreamPtr>
ManifestGroup::PlanFilesStream() && {
Result<std::vector<std::shared_ptr<ScanTask>>> ManifestGroup::Plan(
const CreateTasksFunction& create_tasks) {
+ delete_index_builder_.WithScanMetrics(scan_metrics_);
+ ICEBERG_ASSIGN_OR_RAISE(auto delete_index, delete_index_builder_.Build());
+
+ auto stats_projection =
PrepareStatsProjection(delete_index->has_equality_deletes());
+ ICEBERG_ASSIGN_OR_RAISE(
+ auto context, PlanningContext::Make(*this,
std::move(stats_projection.columns)));
+
std::unordered_map<int32_t, std::shared_ptr<ResidualEvaluator>>
residual_cache;
auto get_residual_evaluator = [&](int32_t spec_id) ->
Result<ResidualEvaluator*> {
- if (residual_cache.contains(spec_id)) {
- return residual_cache[spec_id].get();
+ auto cached = residual_cache.find(spec_id);
+ if (cached != residual_cache.end()) {
+ return cached->second.get();
}
- auto spec_iter = specs_by_id_.find(spec_id);
- ICEBERG_CHECK(spec_iter != specs_by_id_.cend(),
- "Cannot find partition spec for ID {}", spec_id);
-
- const auto& spec = spec_iter->second;
- ICEBERG_ASSIGN_OR_RAISE(
- auto residual_evaluator,
- ResidualEvaluator::Make((ignore_residuals_ ? True::Instance() :
data_filter_),
- *spec, *schema_, case_sensitive_));
- residual_cache[spec_id] = std::move(residual_evaluator);
-
- return residual_cache[spec_id].get();
+ ICEBERG_ASSIGN_OR_RAISE(auto evaluator,
context.MakeResidualEvaluator(spec_id));
+ auto* result = evaluator.get();
+ residual_cache.emplace(spec_id, std::move(evaluator));
+ return result;
};
- delete_index_builder_.WithScanMetrics(scan_metrics_);
- ICEBERG_ASSIGN_OR_RAISE(auto delete_index, delete_index_builder_.Build());
-
- auto stats_projection =
PrepareStatsProjection(delete_index->has_equality_deletes());
-
std::unordered_map<int32_t, std::unique_ptr<TaskContext>> task_context_cache;
auto get_task_context = [&](int32_t spec_id) -> Result<TaskContext*> {
if (task_context_cache.contains(spec_id)) {
@@ -569,7 +588,7 @@ Result<std::vector<std::shared_ptr<ScanTask>>>
ManifestGroup::Plan(
return task_context_cache[spec_id].get();
};
- ICEBERG_ASSIGN_OR_RAISE(auto entry_groups,
ReadEntries(stats_projection.columns));
+ ICEBERG_ASSIGN_OR_RAISE(auto entry_groups, ReadEntries(context));
std::vector<std::shared_ptr<ScanTask>> all_tasks;
for (auto& [spec_id, entries] : entry_groups) {
@@ -583,7 +602,8 @@ Result<std::vector<std::shared_ptr<ScanTask>>>
ManifestGroup::Plan(
}
Result<std::vector<ManifestEntry>> ManifestGroup::Entries() {
- ICEBERG_ASSIGN_OR_RAISE(auto entry_groups, ReadEntries(columns_));
+ ICEBERG_ASSIGN_OR_RAISE(auto context, PlanningContext::Make(*this,
columns_));
+ ICEBERG_ASSIGN_OR_RAISE(auto entry_groups, ReadEntries(context));
std::vector<ManifestEntry> all_entries;
for (auto& [_, entries] : entry_groups) {
@@ -595,37 +615,10 @@ Result<std::vector<ManifestEntry>>
ManifestGroup::Entries() {
}
Result<std::unique_ptr<ManifestReader>> ManifestGroup::MakeReader(
- const ManifestFile& manifest, std::vector<std::string> columns) {
+ const ManifestFile& manifest, const std::vector<std::string>& columns) {
ICEBERG_ASSIGN_OR_RAISE(auto reader,
ManifestReader::Make(manifest, io_, schema_,
specs_by_id_));
- if (file_filter_ && file_filter_->op() != Expression::Operation::kTrue &&
- !std::ranges::contains(columns, Schema::kAllColumns)) {
- auto data_file_schema = DataFileFilterSchema();
- ICEBERG_ASSIGN_OR_RAISE(
- auto bound_file_filter,
- Binder::Bind(*data_file_schema, file_filter_, case_sensitive_));
- ICEBERG_ASSIGN_OR_RAISE(auto referenced_field_ids,
-
ReferenceVisitor::GetReferencedFieldIds(bound_file_filter));
-
- std::unordered_set<std::string> selected_columns(columns.cbegin(),
columns.cend());
- for (const auto field_id : referenced_field_ids) {
- if (field_id == DataFile::kSpecIdFieldId) {
- continue;
- }
- ICEBERG_ASSIGN_OR_RAISE(auto column_name,
- data_file_schema->FindColumnNameById(field_id));
- if (column_name.has_value()) {
- std::string column_name_str(column_name.value());
- if (selected_columns.contains(column_name_str)) {
- continue;
- }
- columns.push_back(std::move(column_name_str));
- selected_columns.insert(columns.back());
- }
- }
- }
-
reader->FilterRows(data_filter_)
.FilterPartitions(partition_filter_)
.CaseSensitive(case_sensitive_)
@@ -659,101 +652,38 @@ ManifestGroup::StatsProjection
ManifestGroup::PrepareStatsProjection(
}
Result<std::unordered_map<int32_t, std::vector<ManifestEntry>>>
-ManifestGroup::ReadEntries(const std::vector<std::string>& columns) {
+ManifestGroup::ReadEntries(PlanningContext& context) {
const auto cache_capacity = static_cast<int32_t>(specs_by_id_.size());
auto get_manifest_evaluator = internal::MemoizeLru(
- [this](int32_t spec_id) -> Result<std::shared_ptr<ManifestEvaluator>> {
- auto spec_iter = specs_by_id_.find(spec_id);
- ICEBERG_CHECK(spec_iter != specs_by_id_.cend(),
- "Cannot find partition spec for ID {}", spec_id);
-
- auto projector =
- Projections::Inclusive(*spec_iter->second, *schema_,
case_sensitive_);
- ICEBERG_ASSIGN_OR_RAISE(auto partition_filter,
projector->Project(data_filter_));
- ICEBERG_ASSIGN_OR_RAISE(partition_filter,
- And::Make(partition_filter,
partition_filter_));
- ICEBERG_ASSIGN_OR_RAISE(
- auto evaluator, ManifestEvaluator::MakePartitionFilter(
- std::move(partition_filter),
spec_iter->second, *schema_,
- case_sensitive_));
+ [&context](int32_t spec_id) ->
Result<std::shared_ptr<ManifestEvaluator>> {
+ ICEBERG_ASSIGN_OR_RAISE(auto evaluator,
context.MakeManifestEvaluator(spec_id));
return std::shared_ptr<ManifestEvaluator>(std::move(evaluator));
},
cache_capacity);
- const bool has_file_filter =
- file_filter_ && file_filter_->op() != Expression::Operation::kTrue;
- std::unique_ptr<Evaluator> data_file_evaluator;
- if (has_file_filter) {
- ICEBERG_ASSIGN_OR_RAISE(
- data_file_evaluator,
- Evaluator::Make(*DataFileFilterSchema(), file_filter_,
case_sensitive_));
- }
-
return ParallelCollect(
executor_, data_manifests_,
- [&](const ManifestFile& manifest)
+ [this, &context, &get_manifest_evaluator](const ManifestFile& manifest)
-> Result<std::unordered_map<int32_t, std::vector<ManifestEntry>>> {
const int32_t spec_id = manifest.partition_spec_id;
ICEBERG_ASSIGN_OR_RAISE(auto manifest_evaluator,
get_manifest_evaluator(spec_id));
- ICEBERG_ASSIGN_OR_RAISE(bool should_match,
- manifest_evaluator->Evaluate(manifest));
- if (!should_match) {
- // Skip this manifest because it doesn't match partition filter
- if (scan_metrics_) {
- scan_metrics_->skipped_data_manifests->Increment(1);
- }
+ ICEBERG_ASSIGN_OR_RAISE(
+ bool should_read, context.ShouldReadManifest(manifest,
*manifest_evaluator));
+ if (!should_read) {
return {};
}
- if (ignore_deleted_) {
- // only scan manifests that have entries other than deletes
- if (!manifest.has_added_files() && !manifest.has_existing_files()) {
- if (scan_metrics_)
scan_metrics_->skipped_data_manifests->Increment(1);
- return {};
- }
- }
- if (ignore_existing_) {
- // only scan manifests that have entries other than existing
- if (!manifest.has_added_files() && !manifest.has_deleted_files()) {
- if (scan_metrics_)
scan_metrics_->skipped_data_manifests->Increment(1);
- return {};
- }
- }
-
- if (scan_metrics_) {
- scan_metrics_->scanned_data_manifests->Increment(1);
- }
-
- // Read manifest entries
- ICEBERG_ASSIGN_OR_RAISE(auto reader, MakeReader(manifest, columns));
+ ICEBERG_ASSIGN_OR_RAISE(auto reader, MakeReader(manifest,
context.columns()));
ICEBERG_ASSIGN_OR_RAISE(
auto entries, ignore_deleted_ ? reader->LiveEntries() :
reader->Entries());
std::unordered_map<int32_t, std::vector<ManifestEntry>>
manifest_result;
-
for (auto& entry : entries) {
- if (ignore_existing_ && entry.status == ManifestStatus::kExisting) {
- if (scan_metrics_) scan_metrics_->skipped_data_files->Increment(1);
- continue;
+ ICEBERG_ASSIGN_OR_RAISE(bool should_keep,
context.ShouldKeepEntry(entry));
+ if (should_keep) {
+ manifest_result[spec_id].push_back(std::move(entry));
}
-
- if (data_file_evaluator != nullptr) {
- DataFileStructLike data_file(*entry.data_file);
- ICEBERG_ASSIGN_OR_RAISE(bool should_match,
- data_file_evaluator->Evaluate(data_file));
- if (!should_match) {
- if (scan_metrics_)
scan_metrics_->skipped_data_files->Increment(1);
- continue;
- }
- }
-
- if (!manifest_entry_predicate_(entry)) {
- if (scan_metrics_) scan_metrics_->skipped_data_files->Increment(1);
- continue;
- }
-
- manifest_result[spec_id].push_back(std::move(entry));
}
return manifest_result;
});
diff --git a/src/iceberg/manifest/manifest_group.h
b/src/iceberg/manifest/manifest_group.h
index 50448aa6..8c523155 100644
--- a/src/iceberg/manifest/manifest_group.h
+++ b/src/iceberg/manifest/manifest_group.h
@@ -178,6 +178,7 @@ class ICEBERG_EXPORT ManifestGroup : public ErrorCollector {
const CreateTasksFunction& create_tasks);
private:
+ class PlanningContext;
class FilePlanningStream;
struct StatsProjection {
@@ -191,10 +192,10 @@ class ICEBERG_EXPORT ManifestGroup : public
ErrorCollector {
DeleteFileIndex::Builder&& delete_index_builder);
Result<std::unordered_map<int32_t, std::vector<ManifestEntry>>> ReadEntries(
- const std::vector<std::string>& columns);
+ PlanningContext& context);
- Result<std::unique_ptr<ManifestReader>> MakeReader(const ManifestFile&
manifest,
- std::vector<std::string>
columns);
+ Result<std::unique_ptr<ManifestReader>> MakeReader(
+ const ManifestFile& manifest, const std::vector<std::string>& columns);
StatsProjection PrepareStatsProjection(bool has_equality_deletes) const;
diff --git a/src/iceberg/test/manifest_group_test.cc
b/src/iceberg/test/manifest_group_test.cc
index f1ac2c01..063ce580 100644
--- a/src/iceberg/test/manifest_group_test.cc
+++ b/src/iceberg/test/manifest_group_test.cc
@@ -38,6 +38,8 @@
#include "iceberg/manifest/manifest_list.h"
#include "iceberg/manifest/manifest_reader.h"
#include "iceberg/manifest/manifest_writer.h"
+#include "iceberg/metrics/metrics_context.h"
+#include "iceberg/metrics/scan_report.h"
#include "iceberg/partition_spec.h"
#include "iceberg/schema.h"
#include "iceberg/table_scan.h"
@@ -813,6 +815,140 @@ TEST_P(ManifestGroupTest, MultipleDataManifests) {
EXPECT_EQ(executor.submit_count(), 2);
}
+TEST_P(ManifestGroupTest, StreamBatchBoundary) {
+ auto version = GetParam();
+
+ // Use one more manifest than the current executor batch size to exercise
+ // loading the next batch.
+ constexpr size_t kManifestCount = 33;
+ std::vector<ManifestFile> manifests;
+ std::vector<std::string> expected_paths;
+ manifests.reserve(kManifestCount);
+ expected_paths.reserve(kManifestCount);
+ const auto partition = PartitionValues(std::vector<Literal>{});
+ for (size_t i = 0; i < kManifestCount; ++i) {
+ auto path = std::format("/path/to/data-{}.parquet", i);
+ manifests.push_back(WriteDataManifest(
+ version, /*snapshot_id=*/1000L + static_cast<int64_t>(i),
+ {MakeEntry(ManifestStatus::kAdded,
+ /*snapshot_id=*/1000L + static_cast<int64_t>(i),
+ /*sequence_number=*/static_cast<int64_t>(i) + 1,
+ MakeDataFile(path, partition,
unpartitioned_spec_->spec_id()))},
+ unpartitioned_spec_));
+ expected_paths.emplace_back(std::move(path));
+ }
+
+ test::ThreadExecutor executor;
+ for (bool use_executor : {false, true}) {
+ SCOPED_TRACE(std::format("use_executor={}", use_executor));
+ ICEBERG_UNWRAP_OR_FAIL(
+ auto group, ManifestGroup::Make(file_io_, schema_, GetSpecsById(),
manifests));
+
+ if (use_executor) {
+ group->PlanWith(std::ref(executor));
+ }
+
+ ICEBERG_UNWRAP_OR_FAIL(auto stream, std::move(*group).PlanFilesStream());
+ ICEBERG_UNWRAP_OR_FAIL(auto tasks, stream->ToVector());
+ EXPECT_EQ(GetPaths(tasks), expected_paths);
+ }
+}
+
+TEST_P(ManifestGroupTest, FilterMetricsParity) {
+ auto version = GetParam();
+
+ constexpr int64_t kSnapshotId = 1000L;
+ const auto partition_0 = PartitionValues({Literal::Int(0)});
+ const auto partition_1 = PartitionValues({Literal::Int(1)});
+ auto selected_manifest = WriteDataManifest(
+ version, kSnapshotId,
+ {MakeEntry(ManifestStatus::kAdded, kSnapshotId, /*sequence_number=*/1,
+ MakeDataFile("/path/to/keep.parquet", partition_0,
+ partitioned_spec_->spec_id(),
/*record_count=*/20)),
+ MakeEntry(ManifestStatus::kExisting, kSnapshotId, /*sequence_number=*/1,
+ MakeDataFile("/path/to/existing.parquet", partition_0,
+ partitioned_spec_->spec_id(),
/*record_count=*/20)),
+ MakeEntry(ManifestStatus::kAdded, kSnapshotId, /*sequence_number=*/1,
+ MakeDataFile("/path/to/small.parquet", partition_0,
+ partitioned_spec_->spec_id(),
/*record_count=*/5)),
+ MakeEntry(ManifestStatus::kAdded, kSnapshotId, /*sequence_number=*/1,
+ MakeDataFile("/path/to/predicate.parquet", partition_0,
+ partitioned_spec_->spec_id(),
/*record_count=*/20))},
+ partitioned_spec_);
+ auto skipped_manifest = WriteDataManifest(
+ version, kSnapshotId,
+ {MakeEntry(ManifestStatus::kAdded, kSnapshotId, /*sequence_number=*/1,
+ MakeDataFile("/path/to/pruned.parquet", partition_1,
+ partitioned_spec_->spec_id(),
/*record_count=*/20))},
+ partitioned_spec_);
+ const std::vector<ManifestFile> manifests = {selected_manifest,
skipped_manifest};
+
+ auto configure = [](ManifestGroup& group, const
std::shared_ptr<ScanMetrics>& metrics) {
+ group.FilterPartitions(Expressions::Equal("data_bucket_16_2",
Literal::Int(0)))
+ .IgnoreExisting()
+ .FilterFiles(Expressions::GreaterThanOrEqual("record_count",
Literal::Long(10)))
+ .FilterManifestEntries([](const ManifestEntry& entry) {
+ return entry.data_file->file_path != "/path/to/predicate.parquet";
+ })
+ .WithScanMetrics(metrics);
+ };
+ auto make_metrics = [] {
+ auto context = MetricsContext::Default();
+ return std::shared_ptr<ScanMetrics>(ScanMetrics::Make(*context));
+ };
+
+ auto entries_metrics = make_metrics();
+ ICEBERG_UNWRAP_OR_FAIL(
+ auto entries_group,
+ ManifestGroup::Make(file_io_, schema_, GetSpecsById(), manifests));
+ configure(*entries_group, entries_metrics);
+ ICEBERG_UNWRAP_OR_FAIL(auto entries, entries_group->Entries());
+ EXPECT_THAT(GetEntryPaths(entries),
testing::ElementsAre("/path/to/keep.parquet"));
+
+ struct StreamResult {
+ std::vector<std::string> paths;
+ std::shared_ptr<ScanMetrics> metrics;
+ };
+ auto run_stream = [&](bool use_executor) -> Result<StreamResult> {
+ auto metrics = make_metrics();
+ ICEBERG_ASSIGN_OR_RAISE(
+ auto group, ManifestGroup::Make(file_io_, schema_, GetSpecsById(),
manifests));
+ configure(*group, metrics);
+ test::ThreadExecutor executor;
+ if (use_executor) {
+ group->PlanWith(std::ref(executor));
+ }
+ ICEBERG_ASSIGN_OR_RAISE(auto stream, std::move(*group).PlanFilesStream());
+ ICEBERG_ASSIGN_OR_RAISE(auto tasks, stream->ToVector());
+ return StreamResult{.paths = GetPaths(tasks), .metrics =
std::move(metrics)};
+ };
+
+ ICEBERG_UNWRAP_OR_FAIL(auto serial, run_stream(false));
+ ICEBERG_UNWRAP_OR_FAIL(auto parallel, run_stream(true));
+ EXPECT_THAT(serial.paths, testing::ElementsAre("/path/to/keep.parquet"));
+ EXPECT_EQ(parallel.paths, serial.paths);
+
+ auto check_filter_metrics = [](const std::shared_ptr<ScanMetrics>& metrics) {
+ EXPECT_EQ(metrics->skipped_data_manifests->value(), 1);
+ EXPECT_EQ(metrics->scanned_data_manifests->value(), 1);
+ EXPECT_EQ(metrics->skipped_data_files->value(), 3);
+ };
+ check_filter_metrics(entries_metrics);
+ check_filter_metrics(serial.metrics);
+ check_filter_metrics(parallel.metrics);
+
+ EXPECT_EQ(entries_metrics->result_data_files->value(), 0);
+ EXPECT_EQ(entries_metrics->result_delete_files->value(), 0);
+ EXPECT_EQ(entries_metrics->total_file_size_in_bytes->value(), 0);
+ EXPECT_EQ(entries_metrics->total_delete_file_size_in_bytes->value(), 0);
+
+ EXPECT_EQ(serial.metrics->result_data_files->value(), 1);
+ EXPECT_EQ(serial.metrics->result_delete_files->value(), 0);
+ EXPECT_EQ(serial.metrics->total_file_size_in_bytes->value(), 10);
+ EXPECT_EQ(serial.metrics->total_delete_file_size_in_bytes->value(), 0);
+ EXPECT_EQ(parallel.metrics->ToResult(), serial.metrics->ToResult());
+}
+
TEST_P(ManifestGroupTest, PartitionFilter) {
auto version = GetParam();