blob: f35f454c060c5bc46e247ccf7067a062617290a4 [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_table_scan.h"
#include <algorithm>
#include <chrono>
#include <iterator>
#include <optional>
#include <utility>
#include "iceberg/expression/sanitize_expression.h"
#include "iceberg/manifest/manifest_group.h"
#include "iceberg/metrics/metrics_context.h"
#include "iceberg/metrics/metrics_reporter.h"
#include "iceberg/metrics/scan_report.h"
#include "iceberg/schema.h"
#include "iceberg/snapshot.h"
#include "iceberg/table_metadata.h"
#include "iceberg/util/macros.h"
#include "iceberg/util/type_util.h"
namespace iceberg {
namespace {
template <typename T>
class EmptyStream final : public Stream<T> {
public:
Result<std::optional<T>> NextImpl() override { return std::nullopt; }
};
Result<ScanReport> MakeScanReport(const DataTableScan& scan, const Snapshot& snapshot,
ScanMetricsResult scan_metrics) {
ICEBERG_ASSIGN_OR_RAISE(auto schema_ptr, scan.schema());
ICEBERG_ASSIGN_OR_RAISE(
auto projected_id_set,
GetProjectedIdsVisitor::GetProjectedIds(*schema_ptr, /*include_struct_ids=*/true));
std::vector<int32_t> projected_field_ids(projected_id_set.begin(),
projected_id_set.end());
std::ranges::sort(projected_field_ids);
std::vector<std::string> projected_field_names;
projected_field_names.reserve(projected_field_ids.size());
for (int32_t field_id : projected_field_ids) {
ICEBERG_ASSIGN_OR_RAISE(auto field_name, schema_ptr->FindColumnNameById(field_id));
ICEBERG_CHECK(field_name.has_value(), "Projected field {} not found in schema",
field_id);
projected_field_names.emplace_back(*field_name);
}
ICEBERG_ASSIGN_OR_RAISE(auto sanitized_filter,
SanitizeExpression::Sanitize(*schema_ptr, scan.filter(),
scan.context().case_sensitive));
return ScanReport{
.table_name = scan.context().table_name,
.snapshot_id = snapshot.snapshot_id,
.filter = std::move(sanitized_filter),
.schema_id = schema_ptr->schema_id(),
.projected_field_ids = std::move(projected_field_ids),
.projected_field_names = std::move(projected_field_names),
.scan_metrics = std::move(scan_metrics),
.metadata = scan.context().options,
};
}
class ReportingFileTaskStream final : public FileScanTaskStream {
public:
ReportingFileTaskStream(FileScanTaskStreamPtr stream,
std::shared_ptr<ScanMetrics> scan_metrics,
std::chrono::nanoseconds planning_duration,
std::shared_ptr<MetricsReporter> reporter, ScanReport report)
: stream_(std::move(stream)),
scan_metrics_(std::move(scan_metrics)),
planning_duration_(std::move(planning_duration)),
reporter_(std::move(reporter)),
report_(std::move(report)) {}
~ReportingFileTaskStream() override { Finalize(); }
Result<std::optional<std::shared_ptr<FileScanTask>>> NextImpl() override {
auto start = std::chrono::steady_clock::now();
auto result = stream_->Next();
planning_duration_ += std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - start);
if (!result.has_value()) {
// Failed planning does not emit a successful scan report.
finalized_ = true;
} else if (!result.value().has_value()) {
Finalize();
}
return result;
}
private:
void Finalize() {
if (finalized_) {
return;
}
finalized_ = true;
scan_metrics_->total_planning_duration->Record(planning_duration_);
report_.scan_metrics = scan_metrics_->ToResult();
std::ignore = reporter_->Report(report_);
}
FileScanTaskStreamPtr stream_;
std::shared_ptr<ScanMetrics> scan_metrics_;
std::chrono::nanoseconds planning_duration_;
std::shared_ptr<MetricsReporter> reporter_;
ScanReport report_;
bool finalized_ = false;
};
} // namespace
Result<std::unique_ptr<DataTableScan>> DataTableScan::Make(
std::shared_ptr<TableMetadata> metadata, std::shared_ptr<Schema> schema,
std::shared_ptr<FileIO> io, internal::TableScanContext context) {
ICEBERG_PRECHECK(metadata != nullptr, "Table metadata cannot be null");
ICEBERG_PRECHECK(schema != nullptr, "Schema cannot be null");
ICEBERG_PRECHECK(io != nullptr, "FileIO cannot be null");
return std::unique_ptr<DataTableScan>(new DataTableScan(
std::move(metadata), std::move(schema), std::move(io), std::move(context)));
}
Result<std::vector<std::shared_ptr<FileScanTask>>> DataTableScan::PlanFiles() const {
ICEBERG_ASSIGN_OR_RAISE(auto stream, PlanFilesStream());
return stream->ToVector();
}
Result<FileScanTaskStreamPtr> DataTableScan::PlanFilesStream() const {
ICEBERG_ASSIGN_OR_RAISE(auto snapshot, this->snapshot());
if (!snapshot) {
return std::make_unique<EmptyStream<std::shared_ptr<FileScanTask>>>();
}
std::shared_ptr<ScanMetrics> scan_metrics;
std::optional<std::chrono::steady_clock::time_point> planning_start;
if (context_.metrics_reporter) {
auto metrics_context = MetricsContext::Default();
scan_metrics = ScanMetrics::Make(*metrics_context);
planning_start = std::chrono::steady_clock::now();
}
TableMetadataCache metadata_cache(metadata_.get());
ICEBERG_ASSIGN_OR_RAISE(auto specs_by_id, metadata_cache.GetPartitionSpecsById());
SnapshotReader snapshot_reader(snapshot.get());
ICEBERG_ASSIGN_OR_RAISE(auto data_manifests, snapshot_reader.DataManifests(io_));
ICEBERG_ASSIGN_OR_RAISE(auto delete_manifests, snapshot_reader.DeleteManifests(io_));
if (scan_metrics) {
scan_metrics->total_data_manifests->Increment(
static_cast<int64_t>(data_manifests.size()));
scan_metrics->total_delete_manifests->Increment(
static_cast<int64_t>(delete_manifests.size()));
}
std::vector<ManifestFile> owned_data_manifests(
std::make_move_iterator(data_manifests.begin()),
std::make_move_iterator(data_manifests.end()));
std::vector<ManifestFile> owned_delete_manifests(
std::make_move_iterator(delete_manifests.begin()),
std::make_move_iterator(delete_manifests.end()));
ICEBERG_ASSIGN_OR_RAISE(
auto manifest_group,
ManifestGroup::Make(io_, schema_, specs_by_id, std::move(owned_data_manifests),
std::move(owned_delete_manifests)));
manifest_group->CaseSensitive(context_.case_sensitive)
.Select(ScanColumns())
.FilterData(filter())
.IgnoreDeleted()
.ColumnsToKeepStats(context_.columns_to_keep_stats)
.WithScanMetrics(scan_metrics);
if (data_manifests.size() > 1 || delete_manifests.size() > 1) {
manifest_group->PlanWith(context_.plan_executor);
}
if (context_.ignore_residuals) {
manifest_group->IgnoreResiduals();
}
ICEBERG_ASSIGN_OR_RAISE(auto stream, std::move(*manifest_group).PlanFilesStream());
if (!planning_start.has_value()) {
return stream;
}
auto planning_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - planning_start.value());
auto report = MakeScanReport(*this, *snapshot, ScanMetricsResult{});
if (!report.has_value()) {
// Scan reporting is best effort.
return stream;
}
return std::make_unique<ReportingFileTaskStream>(
std::move(stream), std::move(scan_metrics), planning_duration,
context_.metrics_reporter, std::move(report).value());
}
} // namespace iceberg