| /* | |
| * 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/row_delta.h" | |
| #include <memory> | |
| #include <string> | |
| #include <string_view> | |
| #include <utility> | |
| #include <vector> | |
| #include "iceberg/expression/expressions.h" | |
| #include "iceberg/manifest/manifest_entry.h" | |
| #include "iceberg/snapshot.h" | |
| #include "iceberg/table.h" | |
| #include "iceberg/table_metadata.h" | |
| #include "iceberg/transaction.h" | |
| #include "iceberg/util/error_collector.h" | |
| #include "iceberg/util/formatter_internal.h" | |
| #include "iceberg/util/macros.h" | |
| #include "iceberg/util/snapshot_util.h" | |
| namespace iceberg { | |
| Result<std::shared_ptr<RowDelta>> RowDelta::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 RowDelta without a context"); | |
| return std::shared_ptr<RowDelta>(new RowDelta(std::move(table_name), std::move(ctx))); | |
| } | |
| RowDelta::RowDelta(std::string table_name, std::shared_ptr<TransactionContext> ctx) | |
| : MergingSnapshotUpdate(std::move(table_name), std::move(ctx)), | |
| conflict_detection_filter_(Expressions::AlwaysTrue()) {} | |
| RowDelta& RowDelta::AddRows(const std::shared_ptr<DataFile>& inserts) { | |
| ICEBERG_BUILDER_RETURN_IF_ERROR(AddDataFile(inserts)); | |
| return *this; | |
| } | |
| RowDelta& RowDelta::AddDeletes(const std::shared_ptr<DataFile>& deletes) { | |
| ICEBERG_BUILDER_RETURN_IF_ERROR(AddDeleteFile(deletes)); | |
| return *this; | |
| } | |
| RowDelta& RowDelta::RemoveRows(const std::shared_ptr<DataFile>& file) { | |
| ICEBERG_BUILDER_RETURN_IF_ERROR(DeleteDataFile(file)); | |
| removed_data_files_.insert(file); | |
| return *this; | |
| } | |
| RowDelta& RowDelta::RemoveDeletes(const std::shared_ptr<DataFile>& deletes) { | |
| ICEBERG_BUILDER_RETURN_IF_ERROR(DeleteDeleteFile(deletes)); | |
| return *this; | |
| } | |
| RowDelta& RowDelta::ValidateFromSnapshot(int64_t snapshot_id) { | |
| starting_snapshot_id_ = snapshot_id; | |
| return *this; | |
| } | |
| RowDelta& RowDelta::CaseSensitive(bool case_sensitive) { | |
| MergingSnapshotUpdate::CaseSensitive(case_sensitive); | |
| return *this; | |
| } | |
| RowDelta& RowDelta::ValidateDataFilesExist( | |
| std::span<const std::string> referenced_files) { | |
| for (const auto& file : referenced_files) { | |
| referenced_data_files_.insert(file); | |
| } | |
| return *this; | |
| } | |
| RowDelta& RowDelta::ValidateDeletedFiles() { | |
| validate_deletes_ = true; | |
| return *this; | |
| } | |
| RowDelta& RowDelta::ConflictDetectionFilter(std::shared_ptr<Expression> filter) { | |
| ICEBERG_BUILDER_CHECK(filter != nullptr, "Conflict detection filter cannot be null"); | |
| conflict_detection_filter_ = std::move(filter); | |
| return *this; | |
| } | |
| RowDelta& RowDelta::ValidateNoConflictingDataFiles() { | |
| validate_new_data_files_ = true; | |
| return *this; | |
| } | |
| RowDelta& RowDelta::ValidateNoConflictingDeleteFiles() { | |
| validate_new_delete_files_ = true; | |
| return *this; | |
| } | |
| std::string RowDelta::operation() { | |
| if (AddsDataFiles() && !AddsDeleteFiles() && !DeletesDataFiles()) { | |
| return DataOperation::kAppend; | |
| } | |
| if (AddsDeleteFiles() && !AddsDataFiles()) { | |
| return DataOperation::kDelete; | |
| } | |
| return DataOperation::kOverwrite; | |
| } | |
| Status RowDelta::Validate(const TableMetadata& current_metadata, | |
| const std::shared_ptr<Snapshot>& snapshot) { | |
| if (snapshot == nullptr) { | |
| return {}; | |
| } | |
| if (validate_deletes_) { | |
| FailMissingDeletePaths(); | |
| } | |
| if (starting_snapshot_id_.has_value()) { | |
| ICEBERG_ASSIGN_OR_RAISE(bool is_ancestor, SnapshotUtil::IsAncestorOf( | |
| current_metadata, snapshot->snapshot_id, | |
| starting_snapshot_id_.value())); | |
| ICEBERG_CHECK(is_ancestor, "Snapshot {} is not an ancestor of {}", | |
| starting_snapshot_id_.value(), snapshot->snapshot_id); | |
| } | |
| auto io = ctx_->table->io(); | |
| if (!referenced_data_files_.empty()) { | |
| ICEBERG_RETURN_UNEXPECTED(MergingSnapshotUpdate::ValidateDataFilesExist( | |
| current_metadata, starting_snapshot_id_, referenced_data_files_, | |
| /*skip_deletes=*/!validate_deletes_, conflict_detection_filter_, snapshot, io, | |
| IsCaseSensitive())); | |
| } | |
| if (validate_new_data_files_) { | |
| ICEBERG_RETURN_UNEXPECTED(MergingSnapshotUpdate::ValidateAddedDataFiles( | |
| current_metadata, starting_snapshot_id_, conflict_detection_filter_, snapshot, io, | |
| IsCaseSensitive())); | |
| } | |
| if (validate_new_delete_files_) { | |
| // validate that explicitly deleted files have not had added deletes | |
| if (!removed_data_files_.empty()) { | |
| ICEBERG_RETURN_UNEXPECTED(MergingSnapshotUpdate::ValidateNoNewDeletesForDataFiles( | |
| current_metadata, starting_snapshot_id_, conflict_detection_filter_, | |
| removed_data_files_, snapshot, io, IsCaseSensitive())); | |
| } | |
| // validate that previous deletes do not conflict with added deletes | |
| ICEBERG_RETURN_UNEXPECTED(MergingSnapshotUpdate::ValidateNoNewDeleteFiles( | |
| current_metadata, starting_snapshot_id_, conflict_detection_filter_, snapshot, io, | |
| IsCaseSensitive())); | |
| } | |
| ICEBERG_RETURN_UNEXPECTED(ValidateNoConflictingFileAndPositionDeletes()); | |
| return MergingSnapshotUpdate::ValidateAddedDVs( | |
| current_metadata, starting_snapshot_id_, conflict_detection_filter_, snapshot, io); | |
| } | |
| Status RowDelta::ValidateNoConflictingFileAndPositionDeletes() const { | |
| std::vector<std::string_view> conflicting_files; | |
| for (const auto& file : removed_data_files_) { | |
| if (file != nullptr && referenced_data_files_.contains(file->file_path)) { | |
| conflicting_files.push_back(file->file_path); | |
| } | |
| } | |
| if (!conflicting_files.empty()) { | |
| return ValidationFailed( | |
| "Cannot delete data files {} that are referenced by new delete files", | |
| FormatRange(conflicting_files, ", ", "[", "]")); | |
| } | |
| return {}; | |
| } | |
| } // namespace iceberg |