blob: cba6e028cd39c4e02f4893e2b1d585443248ed8b [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/update/overwrite_files.h"
#include <vector>
#include "iceberg/expression/evaluator.h"
#include "iceberg/expression/expressions.h"
#include "iceberg/expression/projections.h"
#include "iceberg/expression/strict_metrics_evaluator.h"
#include "iceberg/manifest/manifest_entry.h"
#include "iceberg/partition_spec.h"
#include "iceberg/schema.h"
#include "iceberg/schema_field.h"
#include "iceberg/snapshot.h"
#include "iceberg/table.h"
#include "iceberg/table_metadata.h"
#include "iceberg/transaction.h"
#include "iceberg/type.h"
#include "iceberg/util/error_collector.h"
#include "iceberg/util/macros.h"
namespace iceberg {
Result<std::shared_ptr<OverwriteFiles>> OverwriteFiles::Make(
std::string table_name, std::shared_ptr<TransactionContext> ctx) {
ICEBERG_PRECHECK(!table_name.empty(), "Table name cannot be empty");
ICEBERG_PRECHECK(ctx != nullptr, "Cannot create OverwriteFiles without a context");
return std::shared_ptr<OverwriteFiles>(
new OverwriteFiles(std::move(table_name), std::move(ctx)));
}
OverwriteFiles::OverwriteFiles(std::string table_name,
std::shared_ptr<TransactionContext> ctx)
: MergingSnapshotUpdate(std::move(table_name), std::move(ctx)) {}
OverwriteFiles::~OverwriteFiles() = default;
OverwriteFiles& OverwriteFiles::AddFile(const std::shared_ptr<DataFile>& file) {
ICEBERG_BUILDER_CHECK(file != nullptr, "Invalid data file: null");
ICEBERG_BUILDER_CHECK(file->content == DataFile::Content::kData,
"Invalid data file to add: {} has delete-file content",
file->file_path);
ICEBERG_BUILDER_RETURN_IF_ERROR(AddDataFile(file));
return *this;
}
OverwriteFiles& OverwriteFiles::DeleteFile(const std::shared_ptr<DataFile>& file) {
ICEBERG_BUILDER_CHECK(file != nullptr, "Invalid data file: null");
ICEBERG_BUILDER_CHECK(file->content == DataFile::Content::kData,
"Invalid data file to delete: {} has delete-file content",
file->file_path);
deleted_data_files_.insert(file);
ICEBERG_BUILDER_RETURN_IF_ERROR(DeleteDataFile(file));
return *this;
}
OverwriteFiles& OverwriteFiles::DeleteFiles(const DataFileSet& data_files_to_delete,
const DeleteFileSet& delete_files_to_delete) {
// Both sets use DataFile pointers, so validate content before forwarding to the
// data-file and delete-file removal paths.
for (const auto& file : data_files_to_delete) {
ICEBERG_BUILDER_CHECK(file != nullptr, "Invalid data file: null");
ICEBERG_BUILDER_CHECK(file->content == DataFile::Content::kData,
"Invalid data file to delete: {} has delete-file content",
file->file_path);
deleted_data_files_.insert(file);
ICEBERG_BUILDER_RETURN_IF_ERROR(DeleteDataFile(file));
}
for (const auto& file : delete_files_to_delete) {
ICEBERG_BUILDER_CHECK(file != nullptr, "Invalid delete file: null");
ICEBERG_BUILDER_CHECK(file->content != DataFile::Content::kData,
"Invalid delete file to delete: {} has data-file content",
file->file_path);
ICEBERG_BUILDER_RETURN_IF_ERROR(DeleteDeleteFile(file));
}
return *this;
}
OverwriteFiles& OverwriteFiles::OverwriteByRowFilter(std::shared_ptr<Expression> expr) {
ICEBERG_BUILDER_CHECK(expr != nullptr, "Invalid row filter expression: null");
ICEBERG_BUILDER_RETURN_IF_ERROR(DeleteByRowFilter(std::move(expr)));
return *this;
}
OverwriteFiles& OverwriteFiles::ValidateFromSnapshot(int64_t snapshot_id) {
ICEBERG_BUILDER_CHECK(snapshot_id >= 0, "Invalid snapshot id: {}", snapshot_id);
starting_snapshot_id_ = snapshot_id;
return *this;
}
OverwriteFiles& OverwriteFiles::ConflictDetectionFilter(
std::shared_ptr<Expression> expr) {
ICEBERG_BUILDER_CHECK(expr != nullptr, "Invalid conflict detection filter: null");
conflict_detection_filter_ = std::move(expr);
return *this;
}
OverwriteFiles& OverwriteFiles::CaseSensitive(bool case_sensitive) {
MergingSnapshotUpdate::CaseSensitive(case_sensitive);
return *this;
}
OverwriteFiles& OverwriteFiles::ValidateNoConflictingData() {
validate_new_data_files_ = true;
FailMissingDeletePaths();
return *this;
}
OverwriteFiles& OverwriteFiles::ValidateNoConflictingDeletes() {
validate_new_deletes_ = true;
FailMissingDeletePaths();
return *this;
}
OverwriteFiles& OverwriteFiles::ValidateAddedFilesMatchOverwriteFilter() {
validate_added_files_match_overwrite_filter_ = true;
return *this;
}
std::string OverwriteFiles::operation() {
if (DeletesDataFiles() && !AddsDataFiles()) {
return DataOperation::kDelete;
}
if (AddsDataFiles() && !DeletesDataFiles()) {
return DataOperation::kAppend;
}
return DataOperation::kOverwrite;
}
std::shared_ptr<Expression> OverwriteFiles::DataConflictDetectionFilter() const {
if (conflict_detection_filter_ != nullptr) {
return conflict_detection_filter_;
}
if (auto filter = RowFilter(); filter != nullptr &&
filter != Expressions::AlwaysFalse() &&
deleted_data_files_.empty()) {
return filter;
}
return Expressions::AlwaysTrue();
}
Status OverwriteFiles::Validate(const TableMetadata& current_metadata,
const std::shared_ptr<Snapshot>& snapshot) {
auto row_filter = RowFilter();
if (validate_added_files_match_overwrite_filter_) {
ICEBERG_ASSIGN_OR_RAISE(auto spec, DataSpec());
ICEBERG_ASSIGN_OR_RAISE(auto schema, current_metadata.Schema());
ICEBERG_ASSIGN_OR_RAISE(auto partition_type, spec->PartitionType(*schema));
auto partition_schema = partition_type->ToSchema();
ICEBERG_ASSIGN_OR_RAISE(
auto inclusive_expr,
Projections::Inclusive(*spec, *schema, IsCaseSensitive())->Project(row_filter));
ICEBERG_ASSIGN_OR_RAISE(
auto inclusive_evaluator,
Evaluator::Make(*partition_schema, inclusive_expr, IsCaseSensitive()));
ICEBERG_ASSIGN_OR_RAISE(
auto strict_expr,
Projections::Strict(*spec, *schema, IsCaseSensitive())->Project(row_filter));
ICEBERG_ASSIGN_OR_RAISE(
auto strict_evaluator,
Evaluator::Make(*partition_schema, strict_expr, IsCaseSensitive()));
ICEBERG_ASSIGN_OR_RAISE(
auto metrics_evaluator,
StrictMetricsEvaluator::Make(row_filter, schema, IsCaseSensitive()));
// the real test is that the strict or metrics test matches the file, indicating that
// all records in the file match the filter. inclusive is used to avoid testing the
// metrics, which is more complicated
const auto file_test = [&](const DataFile& file) -> Result<bool> {
ICEBERG_ASSIGN_OR_RAISE(bool inclusive_match,
inclusive_evaluator->Evaluate(file.partition));
if (!inclusive_match) {
return false;
}
ICEBERG_ASSIGN_OR_RAISE(bool strict_match,
strict_evaluator->Evaluate(file.partition));
if (strict_match) {
return true;
}
return metrics_evaluator->Evaluate(file);
};
for (const auto& file : AddedDataFiles()) {
ICEBERG_ASSIGN_OR_RAISE(bool matches_filter, file_test(*file));
if (!matches_filter) {
return ValidationFailed(
"Cannot append file with rows that do not match filter: {}: {}",
row_filter->ToString(), file->file_path);
}
}
}
if (validate_new_data_files_) {
ICEBERG_RETURN_UNEXPECTED(ValidateAddedDataFiles(
current_metadata, starting_snapshot_id_, DataConflictDetectionFilter(), snapshot,
ctx_->table->io(), IsCaseSensitive()));
}
if (validate_new_deletes_) {
if (row_filter != nullptr && row_filter != Expressions::AlwaysFalse()) {
auto filter =
conflict_detection_filter_ != nullptr ? conflict_detection_filter_ : row_filter;
ICEBERG_RETURN_UNEXPECTED(
ValidateNoNewDeleteFiles(current_metadata, starting_snapshot_id_, filter,
snapshot, ctx_->table->io(), IsCaseSensitive()));
ICEBERG_RETURN_UNEXPECTED(
ValidateDeletedDataFiles(current_metadata, starting_snapshot_id_, filter,
snapshot, ctx_->table->io(), IsCaseSensitive()));
}
if (!deleted_data_files_.empty()) {
ICEBERG_RETURN_UNEXPECTED(ValidateNoNewDeletesForDataFiles(
current_metadata, starting_snapshot_id_, conflict_detection_filter_,
deleted_data_files_, snapshot, ctx_->table->io(), IsCaseSensitive()));
}
}
return {};
}
} // namespace iceberg