blob: d04e3385b67c2624cad449479f271f5b821bb28d [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/table_scan.h"
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <ranges>
#include <utility>
#include "iceberg/data_table_scan.h"
#include "iceberg/expression/binder.h"
#include "iceberg/expression/expression.h"
#include "iceberg/incremental_append_scan.h"
#include "iceberg/incremental_changelog_scan.h"
#include "iceberg/manifest/manifest_entry.h"
#include "iceberg/metrics/metrics_reporters.h"
#include "iceberg/result.h"
#include "iceberg/schema.h"
#include "iceberg/snapshot.h"
#include "iceberg/table.h"
#include "iceberg/table_metadata.h"
#include "iceberg/util/content_file_util.h"
#include "iceberg/util/int128.h"
#include "iceberg/util/macros.h"
#include "iceberg/util/snapshot_util.h"
#include "iceberg/util/timepoint.h"
#include "iceberg/util/type_util.h"
namespace iceberg {
namespace {
const std::vector<std::string> kScanColumns = {
"snapshot_id", "file_path", "file_ordinal", "file_format",
"block_size_in_bytes", "file_size_in_bytes", "record_count", "partition",
"key_metadata", "split_offsets", "sort_order_id",
};
const std::vector<std::string> kStatsColumns = {
"value_counts", "null_value_counts", "nan_value_counts",
"lower_bounds", "upper_bounds", "column_sizes",
};
const std::vector<std::string> kScanColumnsWithStats = [] {
auto cols = kScanColumns;
cols.insert(cols.end(), kStatsColumns.begin(), kStatsColumns.end());
return cols;
}();
} // namespace
namespace internal {
Status TableScanContext::Validate() const {
ICEBERG_CHECK(columns_to_keep_stats.empty() || return_column_stats,
"Cannot select columns to keep stats when column stats are not returned");
ICEBERG_CHECK(projected_schema == nullptr || !selected_columns.has_value(),
"Cannot set projection schema and selected columns at the same time");
ICEBERG_CHECK(!snapshot_id.has_value() ||
(!from_snapshot_id.has_value() && !to_snapshot_id.has_value()),
"Cannot mix snapshot scan and incremental scan");
ICEBERG_CHECK(!min_rows_requested.has_value() || min_rows_requested.value() >= 0,
"Min rows requested cannot be negative");
return {};
}
bool IsScanCurrentLineage(const TableScanContext& context) {
return !context.from_snapshot_id.has_value() && !context.to_snapshot_id.has_value();
}
Result<int64_t> ToSnapshotIdInclusive(const TableScanContext& context,
const TableMetadata& metadata) {
// Get the branch's current snapshot ID if branch is set
std::shared_ptr<Snapshot> branch_snapshot;
const std::string& branch = context.branch;
if (!branch.empty()) {
auto iter = metadata.refs.find(branch);
ICEBERG_CHECK(iter != metadata.refs.end() && iter->second != nullptr,
"Cannot find branch: {}", branch);
ICEBERG_ASSIGN_OR_RAISE(branch_snapshot,
metadata.SnapshotById(iter->second->snapshot_id));
}
if (context.to_snapshot_id.has_value()) {
int64_t to_snapshot_id_value = context.to_snapshot_id.value();
if (branch_snapshot != nullptr) {
// Validate `to_snapshot_id` is on the current branch
ICEBERG_ASSIGN_OR_RAISE(
bool is_ancestor,
SnapshotUtil::IsAncestorOf(metadata, branch_snapshot->snapshot_id,
to_snapshot_id_value));
ICEBERG_CHECK(is_ancestor,
"End snapshot is not a valid snapshot on the current branch: {}",
branch);
}
return to_snapshot_id_value;
}
// If to_snapshot_id is not set, use branch's current snapshot if branch is set
if (branch_snapshot != nullptr) {
return branch_snapshot->snapshot_id;
}
// Get current snapshot from table's current snapshot
std::shared_ptr<Snapshot> current_snapshot;
ICEBERG_ASSIGN_OR_RAISE(current_snapshot, metadata.Snapshot());
ICEBERG_CHECK(current_snapshot != nullptr,
"End snapshot is not set and table has no current snapshot");
return current_snapshot->snapshot_id;
}
Result<std::optional<int64_t>> FromSnapshotIdExclusive(const TableScanContext& context,
const TableMetadata& metadata,
int64_t to_snapshot_id_inclusive) {
if (!context.from_snapshot_id.has_value()) {
return std::nullopt;
}
int64_t from_snapshot_id = context.from_snapshot_id.value();
// Validate `from_snapshot_id` is an ancestor of `to_snapshot_id_inclusive`
if (context.from_snapshot_id_inclusive) {
ICEBERG_ASSIGN_OR_RAISE(
bool is_ancestor,
SnapshotUtil::IsAncestorOf(metadata, to_snapshot_id_inclusive, from_snapshot_id));
ICEBERG_CHECK(
is_ancestor,
"Starting snapshot (inclusive) {} is not an ancestor of end snapshot {}",
from_snapshot_id, to_snapshot_id_inclusive);
// For inclusive behavior, return the parent snapshot ID (can be nullopt)
ICEBERG_ASSIGN_OR_RAISE(auto from_snapshot, metadata.SnapshotById(from_snapshot_id));
return from_snapshot->parent_snapshot_id;
}
// Validate there is an ancestor of `to_snapshot_id_inclusive` where parent is
// `from_snapshot_id`
ICEBERG_ASSIGN_OR_RAISE(bool is_parent_ancestor,
SnapshotUtil::IsParentAncestorOf(
metadata, to_snapshot_id_inclusive, from_snapshot_id));
ICEBERG_CHECK(
is_parent_ancestor,
"Starting snapshot (exclusive) {} is not a parent ancestor of end snapshot {}",
from_snapshot_id, to_snapshot_id_inclusive);
return from_snapshot_id;
}
} // namespace internal
ScanTask::~ScanTask() = default;
// FileScanTask implementation
FileScanTask::FileScanTask(std::shared_ptr<DataFile> data_file,
std::vector<std::shared_ptr<DataFile>> delete_files,
std::shared_ptr<Expression> residual_filter)
: data_file_(std::move(data_file)),
delete_files_(std::move(delete_files)),
residual_filter_(std::move(residual_filter)) {
ICEBERG_DCHECK(data_file_ != nullptr, "Data file cannot be null for FileScanTask");
}
FileScanTask::FileScanTask(std::shared_ptr<DataFile> data_file,
std::vector<std::shared_ptr<DataFile>> delete_files,
std::shared_ptr<Expression> residual_filter, Range range)
: FileScanTask(std::move(data_file), std::move(delete_files),
std::move(residual_filter)) {
range_ = range;
}
Result<std::shared_ptr<FileScanTask>> FileScanTask::MakeSplit(
std::shared_ptr<DataFile> data_file, int64_t start, int64_t length,
std::vector<std::shared_ptr<DataFile>> delete_files,
std::shared_ptr<Expression> filter) {
if (data_file == nullptr) {
return InvalidArgument("Cannot create a file scan task without a data file");
}
const int64_t file_size = data_file->file_size_in_bytes;
if (file_size < 0) {
return InvalidArgument("Cannot split file {} with negative size {}",
data_file->file_path, file_size);
}
if (start < 0 || start > file_size) {
return InvalidArgument("Split start {} is outside file {} of size {}", start,
data_file->file_path, file_size);
}
// Subtraction is safe after validating start, even when file_size is INT64_MAX.
if (length < 0 || length > file_size - start) {
return InvalidArgument("Split length {} from start {} is outside file {} of size {}",
length, start, data_file->file_path, file_size);
}
// A full-file range is a whole-file task (is_split() is false). This is also the
// only valid empty range: [0, 0) of an empty file.
if (start == 0 && length == file_size) {
return std::make_shared<FileScanTask>(std::move(data_file), std::move(delete_files),
std::move(filter));
}
if (length == 0) {
return InvalidArgument("A partial split of file {} must have positive length",
data_file->file_path);
}
return std::shared_ptr<FileScanTask>(
new FileScanTask(std::move(data_file), std::move(delete_files), std::move(filter),
Range{.start = start, .length = length}));
}
int64_t FileScanTask::length() const {
return range_ ? range_->length : data_file_->file_size_in_bytes;
}
int64_t FileScanTask::size_bytes() const { return length(); }
int32_t FileScanTask::files_count() const { return 1; }
int64_t FileScanTask::estimated_row_count() const {
if (!range_) {
return data_file_->record_count;
}
const int64_t record_count = data_file_->record_count;
const int64_t file_size = data_file_->file_size_in_bytes;
if (record_count <= 0 || file_size <= 0 || range_->start > file_size ||
range_->length > file_size - range_->start) {
// Keep arithmetic safe even for tasks invalidated by metadata mutation.
return 0;
}
// floor(end * rows / size) - floor(start * rows / size). For non-overlapping
// splits covering a file with unchanged metadata, estimates sum to record_count.
// int128 avoids overflow.
const auto end = static_cast<int128_t>(range_->start) + range_->length;
const auto count_at_end = end * record_count / file_size;
const auto count_at_start =
static_cast<int128_t>(range_->start) * record_count / file_size;
return static_cast<int64_t>(count_at_end - count_at_start);
}
// ChangelogScanTask implementation
ChangelogScanTask::ChangelogScanTask(int32_t change_ordinal, int64_t commit_snapshot_id,
std::shared_ptr<DataFile> data_file,
std::vector<std::shared_ptr<DataFile>> delete_files,
std::shared_ptr<Expression> residual_filter)
: change_ordinal_(change_ordinal),
commit_snapshot_id_(commit_snapshot_id),
data_file_(std::move(data_file)),
delete_files_(std::move(delete_files)),
residual_filter_(std::move(residual_filter)) {
ICEBERG_DCHECK(data_file_ != nullptr, "Data file cannot be null for ChangelogScanTask");
}
int64_t ChangelogScanTask::size_bytes() const {
int64_t total_size = data_file_->file_size_in_bytes;
for (const auto& delete_file : delete_files_) {
ICEBERG_DCHECK(delete_file->content_size_in_bytes.has_value(),
"Delete file content size must be available");
total_size +=
(delete_file->IsDeletionVector() ? delete_file->content_size_in_bytes.value()
: delete_file->file_size_in_bytes);
}
return total_size;
}
int32_t ChangelogScanTask::files_count() const { return 1 + delete_files_.size(); }
int64_t ChangelogScanTask::estimated_row_count() const {
return data_file_->record_count;
}
// Generic template implementation for Make
template <typename ScanType>
Result<std::unique_ptr<TableScanBuilder<ScanType>>> TableScanBuilder<ScanType>::Make(
const Table& table) {
ICEBERG_PRECHECK(table.metadata() != nullptr, "Table metadata cannot be null");
ICEBERG_PRECHECK(table.io() != nullptr, "FileIO cannot be null");
auto builder =
std::unique_ptr<TableScanBuilder<ScanType>>(new TableScanBuilder<ScanType>(
table.metadata(), table.io(), table.full_name(), table.reporter()));
return builder;
}
template <typename ScanType>
TableScanBuilder<ScanType>::TableScanBuilder(
std::shared_ptr<TableMetadata> table_metadata, std::shared_ptr<FileIO> file_io,
std::string table_name, std::shared_ptr<MetricsReporter> metrics_reporter)
: metadata_(std::move(table_metadata)), io_(std::move(file_io)) {
context_.table_name = std::move(table_name);
context_.metrics_reporter = std::move(metrics_reporter);
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::Option(std::string key,
std::string value) {
context_.options[std::move(key)] = std::move(value);
return *this;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::Project(
std::shared_ptr<Schema> schema) {
context_.projected_schema = std::move(schema);
return *this;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::CaseSensitive(
bool case_sensitive) {
context_.case_sensitive = case_sensitive;
return *this;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::IncludeColumnStats() {
context_.return_column_stats = true;
context_.columns_to_keep_stats.clear();
requested_column_stats_.reset();
return *this;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::IncludeColumnStats(
const std::vector<std::string>& requested_columns) {
context_.return_column_stats = true;
requested_column_stats_ = requested_columns;
return *this;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::Select(
const std::vector<std::string>& column_names) {
context_.selected_columns = column_names;
return *this;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::Filter(
std::shared_ptr<Expression> filter) {
context_.filter = std::move(filter);
return *this;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::IgnoreResiduals() {
context_.ignore_residuals = true;
return *this;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::MinRowsRequested(
int64_t num_rows) {
context_.min_rows_requested = num_rows;
return *this;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::PlanWith(Executor& executor) {
context_.plan_executor = std::ref(executor);
return *this;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::UseSnapshot(int64_t snapshot_id) {
ICEBERG_BUILDER_CHECK(!context_.snapshot_id.has_value(),
"Cannot override snapshot, already set snapshot id={}",
context_.snapshot_id.value());
ICEBERG_BUILDER_ASSIGN_OR_RETURN(std::ignore, metadata_->SnapshotById(snapshot_id));
context_.snapshot_id = snapshot_id;
context_.use_snapshot_schema = true;
return *this;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::UseRef(const std::string& ref) {
if (ref == SnapshotRef::kMainBranch) {
context_.snapshot_id.reset();
context_.use_snapshot_schema = false;
return *this;
}
ICEBERG_BUILDER_CHECK(!context_.snapshot_id.has_value(),
"Cannot override ref, already set snapshot id={}",
context_.snapshot_id.value());
auto iter = metadata_->refs.find(ref);
ICEBERG_BUILDER_CHECK(iter != metadata_->refs.end(), "Cannot find ref {}", ref);
ICEBERG_BUILDER_CHECK(iter->second != nullptr, "Ref {} is null", ref);
const int64_t snapshot_id = iter->second->snapshot_id;
ICEBERG_BUILDER_ASSIGN_OR_RETURN(std::ignore, metadata_->SnapshotById(snapshot_id));
context_.snapshot_id = snapshot_id;
context_.use_snapshot_schema = (iter->second->type() == SnapshotRefType::kTag);
return *this;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::AsOfTime(
int64_t timestamp_millis) {
auto time_point_ms = TimePointMsFromUnixMs(timestamp_millis);
ICEBERG_BUILDER_ASSIGN_OR_RETURN(
auto snapshot_id, SnapshotUtil::SnapshotIdAsOfTime(*metadata_, time_point_ms));
return UseSnapshot(snapshot_id);
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::FromSnapshot(
int64_t from_snapshot_id, bool inclusive)
requires IsIncrementalScan<ScanType>
{
if (inclusive) {
ICEBERG_BUILDER_ASSIGN_OR_RETURN(std::ignore,
metadata_->SnapshotById(from_snapshot_id));
}
this->context_.from_snapshot_id = from_snapshot_id;
this->context_.from_snapshot_id_inclusive = inclusive;
return *this;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::FromSnapshot(
const std::string& ref, bool inclusive)
requires IsIncrementalScan<ScanType>
{
auto iter = metadata_->refs.find(ref);
ICEBERG_BUILDER_CHECK(iter != metadata_->refs.end(), "Cannot find ref: {}", ref);
ICEBERG_BUILDER_CHECK(iter->second != nullptr, "Ref {} is null", ref);
ICEBERG_BUILDER_CHECK(iter->second->type() == SnapshotRefType::kTag,
"Ref {} is not a tag", ref);
return FromSnapshot(iter->second->snapshot_id, inclusive);
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::ToSnapshot(int64_t to_snapshot_id)
requires IsIncrementalScan<ScanType>
{
ICEBERG_BUILDER_ASSIGN_OR_RETURN(std::ignore, metadata_->SnapshotById(to_snapshot_id));
context_.to_snapshot_id = to_snapshot_id;
return *this;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::ToSnapshot(const std::string& ref)
requires IsIncrementalScan<ScanType>
{
auto iter = metadata_->refs.find(ref);
ICEBERG_BUILDER_CHECK(iter != metadata_->refs.end(), "Cannot find ref: {}", ref);
ICEBERG_BUILDER_CHECK(iter->second != nullptr, "Ref {} is null", ref);
ICEBERG_BUILDER_CHECK(iter->second->type() == SnapshotRefType::kTag,
"Ref {} is not a tag", ref);
return ToSnapshot(iter->second->snapshot_id);
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::UseBranch(
const std::string& branch)
requires IsIncrementalScan<ScanType>
{
auto iter = metadata_->refs.find(branch);
ICEBERG_BUILDER_CHECK(iter != metadata_->refs.end(), "Cannot find ref: {}", branch);
ICEBERG_BUILDER_CHECK(iter->second != nullptr, "Ref {} is null", branch);
ICEBERG_BUILDER_CHECK(iter->second->type() == SnapshotRefType::kBranch,
"Ref {} is not a branch", branch);
context_.branch = branch;
return *this;
}
template <typename ScanType>
Status TableScanBuilder<ScanType>::ResolveColumnStatsSelection() {
if (!requested_column_stats_.has_value()) {
return {};
}
context_.columns_to_keep_stats.clear();
context_.columns_to_keep_stats.reserve(requested_column_stats_->size());
ICEBERG_ASSIGN_OR_RAISE(auto schema_ref, ResolveSnapshotSchema());
const auto& schema = schema_ref.get();
for (const auto& column_name : *requested_column_stats_) {
ICEBERG_ASSIGN_OR_RAISE(auto field, schema->FindFieldByName(column_name));
ICEBERG_CHECK(field.has_value(), "Cannot find stats column: {}", column_name);
context_.columns_to_keep_stats.insert(field.value().get().field_id());
}
return {};
}
template <typename ScanType>
Result<std::reference_wrapper<const std::shared_ptr<Schema>>>
TableScanBuilder<ScanType>::ResolveSnapshotSchema() {
if (snapshot_schema_ == nullptr) {
if (context_.snapshot_id.has_value() && context_.use_snapshot_schema) {
ICEBERG_ASSIGN_OR_RAISE(auto snapshot,
metadata_->SnapshotById(*context_.snapshot_id));
int32_t schema_id = snapshot->schema_id.value_or(Schema::kInitialSchemaId);
ICEBERG_ASSIGN_OR_RAISE(snapshot_schema_, metadata_->SchemaById(schema_id));
} else {
ICEBERG_ASSIGN_OR_RAISE(snapshot_schema_, metadata_->Schema());
}
}
ICEBERG_CHECK(snapshot_schema_ != nullptr, "Snapshot schema is null");
return snapshot_schema_;
}
template <typename ScanType>
TableScanBuilder<ScanType>& TableScanBuilder<ScanType>::ReportWith(
std::shared_ptr<MetricsReporter> reporter) {
context_.metrics_reporter =
MetricsReporters::Combine(context_.metrics_reporter, std::move(reporter));
return *this;
}
template <typename ScanType>
Result<std::unique_ptr<ScanType>> TableScanBuilder<ScanType>::Build() {
ICEBERG_RETURN_UNEXPECTED(CheckErrors());
ICEBERG_RETURN_UNEXPECTED(ResolveColumnStatsSelection());
ICEBERG_RETURN_UNEXPECTED(context_.Validate());
ICEBERG_ASSIGN_OR_RAISE(auto schema, ResolveSnapshotSchema());
return ScanType::Make(metadata_, schema.get(), io_, std::move(context_));
}
// Explicit template instantiations
template class ICEBERG_TEMPLATE_EXPORT TableScanBuilder<DataTableScan>;
template class ICEBERG_TEMPLATE_EXPORT TableScanBuilder<IncrementalAppendScan>;
template class ICEBERG_TEMPLATE_EXPORT TableScanBuilder<IncrementalChangelogScan>;
TableScan::TableScan(std::shared_ptr<TableMetadata> metadata,
std::shared_ptr<Schema> schema, std::shared_ptr<FileIO> file_io,
internal::TableScanContext context)
: metadata_(std::move(metadata)),
schema_(std::move(schema)),
io_(std::move(file_io)),
context_(std::move(context)) {}
TableScan::~TableScan() = default;
const std::shared_ptr<TableMetadata>& TableScan::metadata() const { return metadata_; }
Result<std::shared_ptr<Snapshot>> TableScan::snapshot() const {
auto snapshot_id = context_.snapshot_id ? context_.snapshot_id.value()
: metadata_->current_snapshot_id;
if (snapshot_id == kInvalidSnapshotId) {
return std::shared_ptr<Snapshot>{nullptr};
}
return metadata_->SnapshotById(snapshot_id);
}
Result<std::shared_ptr<Schema>> TableScan::schema() const {
return ResolveProjectedSchema();
}
const internal::TableScanContext& TableScan::context() const { return context_; }
const std::shared_ptr<FileIO>& TableScan::io() const { return io_; }
const std::shared_ptr<Expression>& TableScan::filter() const {
const static std::shared_ptr<Expression> true_expr = True::Instance();
if (!context_.filter) {
return true_expr;
}
return context_.filter;
}
bool TableScan::is_case_sensitive() const { return context_.case_sensitive; }
Result<std::reference_wrapper<const std::shared_ptr<Schema>>>
TableScan::ResolveProjectedSchema() const {
if (projected_schema_ != nullptr) {
return projected_schema_;
}
if (context_.selected_columns.has_value() &&
!std::ranges::contains(context_.selected_columns.value(), Schema::kAllColumns)) {
std::unordered_set<int32_t> required_field_ids;
// Include columns referenced by filter
if (context_.filter != nullptr) {
ICEBERG_ASSIGN_OR_RAISE(auto is_bound, IsBoundVisitor::IsBound(context_.filter));
if (is_bound) {
ICEBERG_ASSIGN_OR_RAISE(required_field_ids,
ReferenceVisitor::GetReferencedFieldIds(context_.filter));
} else {
ICEBERG_ASSIGN_OR_RAISE(auto filter, Binder::Bind(*schema_, context_.filter,
context_.case_sensitive));
ICEBERG_ASSIGN_OR_RAISE(required_field_ids,
ReferenceVisitor::GetReferencedFieldIds(filter));
}
}
// Include columns selected by option
ICEBERG_ASSIGN_OR_RAISE(
auto selected,
schema_->Select(context_.selected_columns.value(), context_.case_sensitive));
ICEBERG_ASSIGN_OR_RAISE(
auto selected_field_ids,
GetProjectedIdsVisitor::GetProjectedIds(*selected, /*include_struct_ids=*/true));
required_field_ids.insert(std::make_move_iterator(selected_field_ids.begin()),
std::make_move_iterator(selected_field_ids.end()));
ICEBERG_ASSIGN_OR_RAISE(projected_schema_, schema_->Project(required_field_ids));
} else if (context_.projected_schema != nullptr) {
projected_schema_ = context_.projected_schema;
} else {
projected_schema_ = schema_;
}
return projected_schema_;
}
const std::vector<std::string>& TableScan::ScanColumns() const {
return context_.return_column_stats ? kScanColumnsWithStats : kScanColumns;
}
// Friend function template for IncrementalScan that implements the shared PlanFiles
// logic. It resolves the from/to snapshot range from the scan context and delegates
// to the two-arg virtual PlanFiles() override in the concrete subclass.
// Defined as a friend to access the protected two-arg PlanFiles().
template <typename ScanTaskType>
Result<std::vector<std::shared_ptr<ScanTaskType>>> ResolvePlanFiles(
const IncrementalScan<ScanTaskType>& scan) {
if (IsScanCurrentLineage(scan.context())) {
if (scan.metadata()->current_snapshot_id == kInvalidSnapshotId) {
return std::vector<std::shared_ptr<ScanTaskType>>{};
}
}
ICEBERG_ASSIGN_OR_RAISE(
int64_t to_snapshot_id_inclusive,
internal::ToSnapshotIdInclusive(scan.context(), *scan.metadata()));
ICEBERG_ASSIGN_OR_RAISE(
std::optional<int64_t> from_snapshot_id_exclusive,
internal::FromSnapshotIdExclusive(scan.context(), *scan.metadata(),
to_snapshot_id_inclusive));
return scan.PlanFiles(from_snapshot_id_exclusive, to_snapshot_id_inclusive);
}
template Result<std::vector<std::shared_ptr<FileScanTask>>> ResolvePlanFiles(
const IncrementalScan<FileScanTask>& scan);
template Result<std::vector<std::shared_ptr<ChangelogScanTask>>> ResolvePlanFiles(
const IncrementalScan<ChangelogScanTask>& scan);
} // namespace iceberg