This is an automated email from the ASF dual-hosted git repository.
gangwu 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 2f0955ed feat: implement table requirement context (#385)
2f0955ed is described below
commit 2f0955edc353a2db1a334a614c8b944e38dd8dea
Author: Feiyang Li <[email protected]>
AuthorDate: Thu Dec 4 14:12:18 2025 +0800
feat: implement table requirement context (#385)
---
src/iceberg/snapshot.h | 2 +
src/iceberg/table_requirements.cc | 74 ++-
src/iceberg/table_requirements.h | 44 +-
src/iceberg/table_update.cc | 87 +--
src/iceberg/table_update.h | 37 +-
src/iceberg/test/table_requirements_test.cc | 908 +++++++++++++++++++++++++++-
src/iceberg/test/table_update_test.cc | 259 +++++++-
src/iceberg/transaction.h | 1 -
8 files changed, 1308 insertions(+), 104 deletions(-)
diff --git a/src/iceberg/snapshot.h b/src/iceberg/snapshot.h
index d41795d5..5afe2d22 100644
--- a/src/iceberg/snapshot.h
+++ b/src/iceberg/snapshot.h
@@ -62,6 +62,8 @@ ICEBERG_EXPORT constexpr Result<SnapshotRefType>
SnapshotRefTypeFromString(
/// \brief A reference to a snapshot, either a branch or a tag.
struct ICEBERG_EXPORT SnapshotRef {
+ static constexpr std::string_view kMainBranch = "main";
+
struct ICEBERG_EXPORT Branch {
/// A positive number for the minimum number of snapshots to keep in a
branch while
/// expiring snapshots. Defaults to table property
diff --git a/src/iceberg/table_requirements.cc
b/src/iceberg/table_requirements.cc
index 3e0aa024..6de6c59e 100644
--- a/src/iceberg/table_requirements.cc
+++ b/src/iceberg/table_requirements.cc
@@ -21,10 +21,10 @@
#include <memory>
+#include "iceberg/snapshot.h"
#include "iceberg/table_metadata.h"
#include "iceberg/table_requirement.h"
#include "iceberg/table_update.h"
-#include "iceberg/util/macros.h"
namespace iceberg {
@@ -36,12 +36,78 @@ Result<std::vector<std::unique_ptr<TableRequirement>>>
TableUpdateContext::Build
return std::move(requirements_);
}
+void TableUpdateContext::RequireLastAssignedFieldIdUnchanged() {
+ if (!added_last_assigned_field_id_) {
+ if (base_ != nullptr) {
+ AddRequirement(
+
std::make_unique<table::AssertLastAssignedFieldId>(base_->last_column_id));
+ }
+ added_last_assigned_field_id_ = true;
+ }
+}
+
+void TableUpdateContext::RequireCurrentSchemaIdUnchanged() {
+ if (!added_current_schema_id_) {
+ if (base_ != nullptr && !is_replace_) {
+ AddRequirement(std::make_unique<table::AssertCurrentSchemaID>(
+ base_->current_schema_id.value()));
+ }
+ added_current_schema_id_ = true;
+ }
+}
+
+void TableUpdateContext::RequireLastAssignedPartitionIdUnchanged() {
+ if (!added_last_assigned_partition_id_) {
+ if (base_ != nullptr) {
+ AddRequirement(std::make_unique<table::AssertLastAssignedPartitionId>(
+ base_->last_partition_id));
+ }
+ added_last_assigned_partition_id_ = true;
+ }
+}
+
+void TableUpdateContext::RequireDefaultSpecIdUnchanged() {
+ if (!added_default_spec_id_) {
+ if (base_ != nullptr && !is_replace_) {
+ AddRequirement(
+
std::make_unique<table::AssertDefaultSpecID>(base_->default_spec_id));
+ }
+ added_default_spec_id_ = true;
+ }
+}
+
+void TableUpdateContext::RequireDefaultSortOrderIdUnchanged() {
+ if (!added_default_sort_order_id_) {
+ if (base_ != nullptr && !is_replace_) {
+ AddRequirement(std::make_unique<table::AssertDefaultSortOrderID>(
+ base_->default_sort_order_id));
+ }
+ added_default_sort_order_id_ = true;
+ }
+}
+
+void TableUpdateContext::RequireNoBranchesChanged() {
+ if (base_ != nullptr && !is_replace_) {
+ for (const auto& [name, ref] : base_->refs) {
+ if (ref->type() == SnapshotRefType::kBranch && name !=
SnapshotRef::kMainBranch) {
+ AddRequirement(
+ std::make_unique<table::AssertRefSnapshotID>(name,
ref->snapshot_id));
+ }
+ }
+ }
+}
+
+bool TableUpdateContext::AddChangedRef(const std::string& ref_name) {
+ auto [_, inserted] = changed_refs_.insert(ref_name);
+ return inserted;
+}
+
Result<std::vector<std::unique_ptr<TableRequirement>>>
TableRequirements::ForCreateTable(
const std::vector<std::unique_ptr<TableUpdate>>& table_updates) {
TableUpdateContext context(nullptr, false);
context.AddRequirement(std::make_unique<table::AssertDoesNotExist>());
for (const auto& update : table_updates) {
- ICEBERG_RETURN_UNEXPECTED(update->GenerateRequirements(context));
+ update->GenerateRequirements(context);
}
return context.Build();
}
@@ -52,7 +118,7 @@ Result<std::vector<std::unique_ptr<TableRequirement>>>
TableRequirements::ForRep
TableUpdateContext context(&base, true);
context.AddRequirement(std::make_unique<table::AssertUUID>(base.table_uuid));
for (const auto& update : table_updates) {
- ICEBERG_RETURN_UNEXPECTED(update->GenerateRequirements(context));
+ update->GenerateRequirements(context);
}
return context.Build();
}
@@ -63,7 +129,7 @@ Result<std::vector<std::unique_ptr<TableRequirement>>>
TableRequirements::ForUpd
TableUpdateContext context(&base, false);
context.AddRequirement(std::make_unique<table::AssertUUID>(base.table_uuid));
for (const auto& update : table_updates) {
- ICEBERG_RETURN_UNEXPECTED(update->GenerateRequirements(context));
+ update->GenerateRequirements(context);
}
return context.Build();
}
diff --git a/src/iceberg/table_requirements.h b/src/iceberg/table_requirements.h
index 7af2fb2d..f79f0bea 100644
--- a/src/iceberg/table_requirements.h
+++ b/src/iceberg/table_requirements.h
@@ -27,6 +27,8 @@
/// for optimistic concurrency control when committing table changes.
#include <memory>
+#include <string>
+#include <unordered_set>
#include <vector>
#include "iceberg/iceberg_export.h"
@@ -68,27 +70,24 @@ class ICEBERG_EXPORT TableUpdateContext {
/// \brief Build and return the list of requirements
Result<std::vector<std::unique_ptr<TableRequirement>>> Build();
- // Getters for deduplication flags
- bool added_last_assigned_field_id() const { return
added_last_assigned_field_id_; }
- bool added_current_schema_id() const { return added_current_schema_id_; }
- bool added_last_assigned_partition_id() const {
- return added_last_assigned_partition_id_;
- }
- bool added_default_spec_id() const { return added_default_spec_id_; }
- bool added_default_sort_order_id() const { return
added_default_sort_order_id_; }
-
- // Setters for deduplication flags
- void set_added_last_assigned_field_id(bool value) {
- added_last_assigned_field_id_ = value;
- }
- void set_added_current_schema_id(bool value) { added_current_schema_id_ =
value; }
- void set_added_last_assigned_partition_id(bool value) {
- added_last_assigned_partition_id_ = value;
- }
- void set_added_default_spec_id(bool value) { added_default_spec_id_ = value;
}
- void set_added_default_sort_order_id(bool value) {
- added_default_sort_order_id_ = value;
- }
+ // Helper methods to deduplicate requirements to add.
+ /// \brief Require that the last assigned field ID remains unchanged
+ void RequireLastAssignedFieldIdUnchanged();
+ /// \brief Require that the current schema ID remains unchanged
+ void RequireCurrentSchemaIdUnchanged();
+ /// \brief Require that the last assigned partition ID remains unchanged
+ void RequireLastAssignedPartitionIdUnchanged();
+ /// \brief Require that the default spec ID remains unchanged
+ void RequireDefaultSpecIdUnchanged();
+ /// \brief Require that the default sort order ID remains unchanged
+ void RequireDefaultSortOrderIdUnchanged();
+ /// \brief Require that no branches have been changed
+ void RequireNoBranchesChanged();
+
+ /// \brief Track a changed ref and return whether it was newly added
+ /// \param ref_name The name of the ref being changed
+ /// \return true if this is the first time the ref is being changed
+ bool AddChangedRef(const std::string& ref_name);
private:
const TableMetadata* base_;
@@ -102,6 +101,9 @@ class ICEBERG_EXPORT TableUpdateContext {
bool added_last_assigned_partition_id_ = false;
bool added_default_spec_id_ = false;
bool added_default_sort_order_id_ = false;
+
+ // Track refs that have been changed to avoid duplicate requirements
+ std::unordered_set<std::string> changed_refs_;
};
/// \brief Factory class for generating table requirements
diff --git a/src/iceberg/table_update.cc b/src/iceberg/table_update.cc
index 6c1ad72e..90f7de62 100644
--- a/src/iceberg/table_update.cc
+++ b/src/iceberg/table_update.cc
@@ -21,7 +21,6 @@
#include "iceberg/exception.h"
#include "iceberg/table_metadata.h"
-#include "iceberg/table_requirement.h"
#include "iceberg/table_requirements.h"
namespace iceberg::table {
@@ -32,9 +31,8 @@ void AssignUUID::ApplyTo(TableMetadataBuilder& builder) const
{
builder.AssignUUID(uuid_);
}
-Status AssignUUID::GenerateRequirements(TableUpdateContext& context) const {
+void AssignUUID::GenerateRequirements(TableUpdateContext& context) const {
// AssignUUID does not generate additional requirements.
- return {};
}
// UpgradeFormatVersion
@@ -43,8 +41,8 @@ void UpgradeFormatVersion::ApplyTo(TableMetadataBuilder&
builder) const {
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
}
-Status UpgradeFormatVersion::GenerateRequirements(TableUpdateContext& context)
const {
- return NotImplemented("UpgradeFormatVersion::GenerateRequirements not
implemented");
+void UpgradeFormatVersion::GenerateRequirements(TableUpdateContext& context)
const {
+ // UpgradeFormatVersion doesn't generate any requirements
}
// AddSchema
@@ -53,8 +51,8 @@ void AddSchema::ApplyTo(TableMetadataBuilder& builder) const {
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
}
-Status AddSchema::GenerateRequirements(TableUpdateContext& context) const {
- return NotImplemented("AddTableSchema::GenerateRequirements not
implemented");
+void AddSchema::GenerateRequirements(TableUpdateContext& context) const {
+ context.RequireLastAssignedFieldIdUnchanged();
}
// SetCurrentSchema
@@ -63,8 +61,8 @@ void SetCurrentSchema::ApplyTo(TableMetadataBuilder& builder)
const {
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
}
-Status SetCurrentSchema::GenerateRequirements(TableUpdateContext& context)
const {
- return NotImplemented("SetCurrentTableSchema::GenerateRequirements not
implemented");
+void SetCurrentSchema::GenerateRequirements(TableUpdateContext& context) const
{
+ context.RequireCurrentSchemaIdUnchanged();
}
// AddPartitionSpec
@@ -73,8 +71,8 @@ void AddPartitionSpec::ApplyTo(TableMetadataBuilder& builder)
const {
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
}
-Status AddPartitionSpec::GenerateRequirements(TableUpdateContext& context)
const {
- return NotImplemented("AddTablePartitionSpec::GenerateRequirements not
implemented");
+void AddPartitionSpec::GenerateRequirements(TableUpdateContext& context) const
{
+ context.RequireLastAssignedPartitionIdUnchanged();
}
// SetDefaultPartitionSpec
@@ -83,9 +81,8 @@ void SetDefaultPartitionSpec::ApplyTo(TableMetadataBuilder&
builder) const {
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
}
-Status SetDefaultPartitionSpec::GenerateRequirements(TableUpdateContext&
context) const {
- return NotImplemented(
- "SetDefaultTablePartitionSpec::GenerateRequirements not implemented");
+void SetDefaultPartitionSpec::GenerateRequirements(TableUpdateContext&
context) const {
+ context.RequireDefaultSpecIdUnchanged();
}
// RemovePartitionSpecs
@@ -94,9 +91,9 @@ void RemovePartitionSpecs::ApplyTo(TableMetadataBuilder&
builder) const {
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
}
-Status RemovePartitionSpecs::GenerateRequirements(TableUpdateContext& context)
const {
- return NotImplemented(
- "RemoveTablePartitionSpecs::GenerateRequirements not implemented");
+void RemovePartitionSpecs::GenerateRequirements(TableUpdateContext& context)
const {
+ context.RequireDefaultSpecIdUnchanged();
+ context.RequireNoBranchesChanged();
}
// RemoveSchemas
@@ -105,28 +102,29 @@ void RemoveSchemas::ApplyTo(TableMetadataBuilder&
builder) const {
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
}
-Status RemoveSchemas::GenerateRequirements(TableUpdateContext& context) const {
- return NotImplemented("RemoveTableSchemas::GenerateRequirements not
implemented");
+void RemoveSchemas::GenerateRequirements(TableUpdateContext& context) const {
+ context.RequireCurrentSchemaIdUnchanged();
+ context.RequireNoBranchesChanged();
}
// AddSortOrder
void AddSortOrder::ApplyTo(TableMetadataBuilder& builder) const {
- throw IcebergError(std::format("{} not implemented", __FUNCTION__));
+ builder.AddSortOrder(sort_order_);
}
-Status AddSortOrder::GenerateRequirements(TableUpdateContext& context) const {
- return NotImplemented("AddTableSortOrder::GenerateRequirements not
implemented");
+void AddSortOrder::GenerateRequirements(TableUpdateContext& context) const {
+ // AddSortOrder doesn't generate any requirements
}
// SetDefaultSortOrder
void SetDefaultSortOrder::ApplyTo(TableMetadataBuilder& builder) const {
- throw IcebergError(std::format("{} not implemented", __FUNCTION__));
+ builder.SetDefaultSortOrder(sort_order_id_);
}
-Status SetDefaultSortOrder::GenerateRequirements(TableUpdateContext& context)
const {
- return NotImplemented("SetDefaultTableSortOrder::GenerateRequirements not
implemented");
+void SetDefaultSortOrder::GenerateRequirements(TableUpdateContext& context)
const {
+ context.RequireDefaultSortOrderIdUnchanged();
}
// AddSnapshot
@@ -135,16 +133,16 @@ void AddSnapshot::ApplyTo(TableMetadataBuilder& builder)
const {
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
}
-Status AddSnapshot::GenerateRequirements(TableUpdateContext& context) const {
- return NotImplemented("AddTableSnapshot::GenerateRequirements not
implemented");
+void AddSnapshot::GenerateRequirements(TableUpdateContext& context) const {
+ // AddSnapshot doesn't generate any requirements
}
// RemoveSnapshots
void RemoveSnapshots::ApplyTo(TableMetadataBuilder& builder) const {}
-Status RemoveSnapshots::GenerateRequirements(TableUpdateContext& context)
const {
- return NotImplemented("RemoveTableSnapshots::GenerateRequirements not
implemented");
+void RemoveSnapshots::GenerateRequirements(TableUpdateContext& context) const {
+ // RemoveSnapshots doesn't generate any requirements
}
// RemoveSnapshotRef
@@ -153,8 +151,8 @@ void RemoveSnapshotRef::ApplyTo(TableMetadataBuilder&
builder) const {
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
}
-Status RemoveSnapshotRef::GenerateRequirements(TableUpdateContext& context)
const {
- return NotImplemented("RemoveTableSnapshotRef::GenerateRequirements not
implemented");
+void RemoveSnapshotRef::GenerateRequirements(TableUpdateContext& context)
const {
+ // RemoveSnapshotRef doesn't generate any requirements
}
// SetSnapshotRef
@@ -163,8 +161,17 @@ void SetSnapshotRef::ApplyTo(TableMetadataBuilder&
builder) const {
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
}
-Status SetSnapshotRef::GenerateRequirements(TableUpdateContext& context) const
{
- return NotImplemented("SetTableSnapshotRef::GenerateRequirements not
implemented");
+void SetSnapshotRef::GenerateRequirements(TableUpdateContext& context) const {
+ bool added = context.AddChangedRef(ref_name_);
+ if (added && context.base() != nullptr && !context.is_replace()) {
+ const auto& refs = context.base()->refs;
+ auto it = refs.find(ref_name_);
+ // Require that the ref does not exist (nullopt) or is the same as the
base snapshot
+ std::optional<int64_t> base_snapshot_id =
+ (it != refs.end()) ? std::make_optional(it->second->snapshot_id) :
std::nullopt;
+ context.AddRequirement(
+ std::make_unique<table::AssertRefSnapshotID>(ref_name_,
base_snapshot_id));
+ }
}
// SetProperties
@@ -173,9 +180,8 @@ void SetProperties::ApplyTo(TableMetadataBuilder& builder)
const {
builder.SetProperties(updated_);
}
-Status SetProperties::GenerateRequirements(TableUpdateContext& context) const {
- // No requirements
- return {};
+void SetProperties::GenerateRequirements(TableUpdateContext& context) const {
+ // SetProperties doesn't generate any requirements
}
// RemoveProperties
@@ -184,9 +190,8 @@ void RemoveProperties::ApplyTo(TableMetadataBuilder&
builder) const {
builder.RemoveProperties(removed_);
}
-Status RemoveProperties::GenerateRequirements(TableUpdateContext& context)
const {
- // No requirements
- return {};
+void RemoveProperties::GenerateRequirements(TableUpdateContext& context) const
{
+ // RemoveProperties doesn't generate any requirements
}
// SetLocation
@@ -195,8 +200,8 @@ void SetLocation::ApplyTo(TableMetadataBuilder& builder)
const {
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
}
-Status SetLocation::GenerateRequirements(TableUpdateContext& context) const {
- return NotImplemented("SetTableLocation::GenerateRequirements not
implemented");
+void SetLocation::GenerateRequirements(TableUpdateContext& context) const {
+ // SetLocation doesn't generate any requirements
}
} // namespace iceberg::table
diff --git a/src/iceberg/table_update.h b/src/iceberg/table_update.h
index 445295a4..93b48cf2 100644
--- a/src/iceberg/table_update.h
+++ b/src/iceberg/table_update.h
@@ -58,8 +58,7 @@ class ICEBERG_EXPORT TableUpdate {
/// provides information about the base metadata and operation mode.
///
/// \param context The context containing base metadata and operation state
- /// \return Status indicating success or failure with error details
- virtual Status GenerateRequirements(TableUpdateContext& context) const = 0;
+ virtual void GenerateRequirements(TableUpdateContext& context) const = 0;
};
namespace table {
@@ -73,7 +72,7 @@ class ICEBERG_EXPORT AssignUUID : public TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
std::string uuid_;
@@ -89,7 +88,7 @@ class ICEBERG_EXPORT UpgradeFormatVersion : public
TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
int8_t format_version_;
@@ -107,7 +106,7 @@ class ICEBERG_EXPORT AddSchema : public TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
std::shared_ptr<Schema> schema_;
@@ -123,7 +122,7 @@ class ICEBERG_EXPORT SetCurrentSchema : public TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
int32_t schema_id_;
@@ -139,7 +138,7 @@ class ICEBERG_EXPORT AddPartitionSpec : public TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
std::shared_ptr<PartitionSpec> spec_;
@@ -154,7 +153,7 @@ class ICEBERG_EXPORT SetDefaultPartitionSpec : public
TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
int32_t spec_id_;
@@ -170,7 +169,7 @@ class ICEBERG_EXPORT RemovePartitionSpecs : public
TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
std::vector<int32_t> spec_ids_;
@@ -186,7 +185,7 @@ class ICEBERG_EXPORT RemoveSchemas : public TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
std::vector<int32_t> schema_ids_;
@@ -202,7 +201,7 @@ class ICEBERG_EXPORT AddSortOrder : public TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
std::shared_ptr<SortOrder> sort_order_;
@@ -217,7 +216,7 @@ class ICEBERG_EXPORT SetDefaultSortOrder : public
TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
int32_t sort_order_id_;
@@ -233,7 +232,7 @@ class ICEBERG_EXPORT AddSnapshot : public TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
std::shared_ptr<Snapshot> snapshot_;
@@ -249,7 +248,7 @@ class ICEBERG_EXPORT RemoveSnapshots : public TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
std::vector<int64_t> snapshot_ids_;
@@ -264,7 +263,7 @@ class ICEBERG_EXPORT RemoveSnapshotRef : public TableUpdate
{
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
std::string ref_name_;
@@ -297,7 +296,7 @@ class ICEBERG_EXPORT SetSnapshotRef : public TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
std::string ref_name_;
@@ -318,7 +317,7 @@ class ICEBERG_EXPORT SetProperties : public TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
std::unordered_map<std::string, std::string> updated_;
@@ -334,7 +333,7 @@ class ICEBERG_EXPORT RemoveProperties : public TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
std::vector<std::string> removed_;
@@ -349,7 +348,7 @@ class ICEBERG_EXPORT SetLocation : public TableUpdate {
void ApplyTo(TableMetadataBuilder& builder) const override;
- Status GenerateRequirements(TableUpdateContext& context) const override;
+ void GenerateRequirements(TableUpdateContext& context) const override;
private:
std::string location_;
diff --git a/src/iceberg/test/table_requirements_test.cc
b/src/iceberg/test/table_requirements_test.cc
index 441e72d4..041b44dd 100644
--- a/src/iceberg/test/table_requirements_test.cc
+++ b/src/iceberg/test/table_requirements_test.cc
@@ -20,18 +20,21 @@
#include "iceberg/table_requirements.h"
#include <memory>
+#include <ranges>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include "iceberg/partition_spec.h"
+#include "iceberg/schema.h"
#include "iceberg/snapshot.h"
#include "iceberg/sort_order.h"
#include "iceberg/table_metadata.h"
#include "iceberg/table_requirement.h"
#include "iceberg/table_update.h"
#include "iceberg/test/matchers.h"
+#include "iceberg/type.h"
namespace iceberg {
@@ -47,16 +50,43 @@ std::unique_ptr<TableMetadata> CreateBaseMetadata(
metadata->last_sequence_number = 0;
metadata->last_updated_ms = TimePointMs{std::chrono::milliseconds(1000)};
metadata->last_column_id = 0;
+ metadata->current_schema_id = Schema::kInitialSchemaId;
metadata->default_spec_id = PartitionSpec::kInitialSpecId;
metadata->last_partition_id = 0;
metadata->current_snapshot_id = Snapshot::kInvalidSnapshotId;
- metadata->default_sort_order_id = SortOrder::kInitialSortOrderId;
+ metadata->default_sort_order_id = SortOrder::kUnsortedOrderId;
metadata->next_row_id = TableMetadata::kInitialRowId;
return metadata;
}
+// Helper function to create a simple schema for tests
+std::shared_ptr<Schema> CreateTestSchema(int32_t schema_id = 0) {
+ std::vector<SchemaField> fields;
+ fields.emplace_back(SchemaField::MakeRequired(1, "id", int32()));
+ return std::make_shared<Schema>(std::move(fields), schema_id);
+}
+
+// Helper function to count requirements of a specific type
+template <typename T>
+int CountRequirementsOfType(
+ const std::vector<std::unique_ptr<TableRequirement>>& requirements) {
+ return std::ranges::count_if(requirements, [](const auto& req) {
+ return dynamic_cast<T*>(req.get()) != nullptr;
+ });
+}
+
+// Helper function to add a branch to metadata
+void AddBranch(TableMetadata& metadata, const std::string& name, int64_t
snapshot_id) {
+ auto ref = std::make_shared<SnapshotRef>();
+ ref->snapshot_id = snapshot_id;
+ ref->retention = SnapshotRef::Branch{};
+ metadata.refs[name] = ref;
+}
+
} // namespace
+// Empty Updates Tests
+
TEST(TableRequirementsTest, EmptyUpdatesForCreateTable) {
std::vector<std::unique_ptr<TableUpdate>> updates;
@@ -104,6 +134,8 @@ TEST(TableRequirementsTest, EmptyUpdatesForReplaceTable) {
EXPECT_EQ(assert_uuid->uuid(), metadata->table_uuid);
}
+// Table Existence Tests
+
TEST(TableRequirementsTest, TableAlreadyExists) {
std::vector<std::unique_ptr<TableUpdate>> updates;
@@ -134,7 +166,8 @@ TEST(TableRequirementsTest, TableDoesNotExist) {
EXPECT_THAT(status, IsOk());
}
-// Test for AssignUUID update
+// AssignUUID Tests
+
TEST(TableRequirementsTest, AssignUUID) {
auto metadata = CreateBaseMetadata("original-uuid");
std::vector<std::unique_ptr<TableUpdate>> updates;
@@ -207,4 +240,875 @@ TEST(TableRequirementsTest, AssignUUIDForReplaceTable) {
EXPECT_THAT(status, IsOk());
}
+// UpgradeFormatVersion Tests
+
+TEST(TableRequirementsTest, UpgradeFormatVersion) {
+ auto metadata = CreateBaseMetadata();
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(std::make_unique<table::UpgradeFormatVersion>(2));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // UpgradeFormatVersion doesn't add additional requirements
+ ASSERT_EQ(requirements.size(), 1);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
+ // Validate against base metadata
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+// AddSchema Tests
+
+TEST(TableRequirementsTest, AddSchema) {
+ auto metadata = CreateBaseMetadata();
+ metadata->last_column_id = 1;
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+
+ auto schema = CreateTestSchema();
+ // Add multiple AddSchema updates
+ updates.push_back(std::make_unique<table::AddSchema>(schema, 1));
+ updates.push_back(std::make_unique<table::AddSchema>(schema, 1));
+ updates.push_back(std::make_unique<table::AddSchema>(schema, 1));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // Should have AssertUUID + AssertLastAssignedFieldId (deduplicated)
+ ASSERT_EQ(requirements.size(), 2);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
EXPECT_EQ(CountRequirementsOfType<table::AssertLastAssignedFieldId>(requirements),
1);
+
+ // Verify the last assigned field ID value
+ auto* assert_field_id =
+ dynamic_cast<table::AssertLastAssignedFieldId*>(requirements[1].get());
+ ASSERT_NE(assert_field_id, nullptr);
+ EXPECT_EQ(assert_field_id->last_assigned_field_id(), 1);
+
+ // Validate against base metadata
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+TEST(TableRequirementsTest, AddSchemaFailure) {
+ auto metadata = CreateBaseMetadata();
+ metadata->last_column_id = 2;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ auto schema = CreateTestSchema();
+ updates.push_back(std::make_unique<table::AddSchema>(schema, 2));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+
+ // Create updated metadata with different last_column_id
+ auto updated = CreateBaseMetadata();
+ updated->last_column_id = 3;
+
+ // Find and validate the AssertLastAssignedFieldId requirement
+ for (const auto& req : requirements) {
+ if (dynamic_cast<table::AssertLastAssignedFieldId*>(req.get()) != nullptr)
{
+ auto status = req->Validate(updated.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("last assigned field ID does not
match"));
+ break;
+ }
+ }
+}
+
+// SetCurrentSchema Tests
+
+TEST(TableRequirementsTest, SetCurrentSchema) {
+ auto metadata = CreateBaseMetadata();
+ metadata->current_schema_id = 3;
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+
+ // Add multiple SetCurrentSchema updates
+ updates.push_back(std::make_unique<table::SetCurrentSchema>(3));
+ updates.push_back(std::make_unique<table::SetCurrentSchema>(4));
+ updates.push_back(std::make_unique<table::SetCurrentSchema>(5));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // Should have AssertUUID + AssertCurrentSchemaID (deduplicated)
+ ASSERT_EQ(requirements.size(), 2);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
EXPECT_EQ(CountRequirementsOfType<table::AssertCurrentSchemaID>(requirements),
1);
+
+ // Verify the current schema ID value
+ auto* assert_schema_id =
+ dynamic_cast<table::AssertCurrentSchemaID*>(requirements[1].get());
+ ASSERT_NE(assert_schema_id, nullptr);
+ EXPECT_EQ(assert_schema_id->schema_id(), 3);
+
+ // Validate against base metadata
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+TEST(TableRequirementsTest, SetCurrentSchemaFailure) {
+ auto metadata = CreateBaseMetadata();
+ metadata->current_schema_id = 3;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(std::make_unique<table::SetCurrentSchema>(3));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+
+ // Create updated metadata with different current_schema_id
+ auto updated = CreateBaseMetadata();
+ updated->current_schema_id = 4;
+
+ // Find and validate the AssertCurrentSchemaID requirement
+ for (const auto& req : requirements) {
+ if (dynamic_cast<table::AssertCurrentSchemaID*>(req.get()) != nullptr) {
+ auto status = req->Validate(updated.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("current schema ID does not match"));
+ break;
+ }
+ }
+}
+
+// AddPartitionSpec Tests
+
+TEST(TableRequirementsTest, AddPartitionSpec) {
+ auto metadata = CreateBaseMetadata();
+ metadata->last_partition_id = 3;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(
+
std::make_unique<table::AddPartitionSpec>(PartitionSpec::Unpartitioned()));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // Should have AssertUUID + AssertLastAssignedPartitionId
+ ASSERT_EQ(requirements.size(), 2);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
EXPECT_EQ(CountRequirementsOfType<table::AssertLastAssignedPartitionId>(requirements),
+ 1);
+
+ // Verify the last assigned partition ID value
+ auto* assert_partition_id =
+
dynamic_cast<table::AssertLastAssignedPartitionId*>(requirements[1].get());
+ ASSERT_NE(assert_partition_id, nullptr);
+ EXPECT_EQ(assert_partition_id->last_assigned_partition_id(), 3);
+
+ // Validate against base metadata
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+TEST(TableRequirementsTest, AddPartitionSpecFailure) {
+ auto metadata = CreateBaseMetadata();
+ metadata->last_partition_id = 3;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(
+
std::make_unique<table::AddPartitionSpec>(PartitionSpec::Unpartitioned()));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+
+ // Create updated metadata with different last_partition_id
+ auto updated = CreateBaseMetadata();
+ updated->last_partition_id = 4;
+
+ // Find and validate the AssertLastAssignedPartitionId requirement
+ for (const auto& req : requirements) {
+ if (dynamic_cast<table::AssertLastAssignedPartitionId*>(req.get()) !=
nullptr) {
+ auto status = req->Validate(updated.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("last assigned partition ID does not
match"));
+ break;
+ }
+ }
+}
+
+// SetDefaultPartitionSpec Tests
+
+TEST(TableRequirementsTest, SetDefaultPartitionSpec) {
+ auto metadata = CreateBaseMetadata();
+ metadata->default_spec_id = 3;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ // Add multiple SetDefaultPartitionSpec updates
+ updates.push_back(std::make_unique<table::SetDefaultPartitionSpec>(3));
+ updates.push_back(std::make_unique<table::SetDefaultPartitionSpec>(4));
+ updates.push_back(std::make_unique<table::SetDefaultPartitionSpec>(5));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // Should have AssertUUID + AssertDefaultSpecID (deduplicated)
+ ASSERT_EQ(requirements.size(), 2);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertDefaultSpecID>(requirements),
1);
+
+ // Verify the default spec ID value
+ auto* assert_spec_id =
dynamic_cast<table::AssertDefaultSpecID*>(requirements[1].get());
+ ASSERT_NE(assert_spec_id, nullptr);
+ EXPECT_EQ(assert_spec_id->spec_id(), 3);
+
+ // Validate against base metadata
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+TEST(TableRequirementsTest, SetDefaultPartitionSpecFailure) {
+ auto metadata = CreateBaseMetadata();
+ metadata->default_spec_id = PartitionSpec::kInitialSpecId;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(
+
std::make_unique<table::SetDefaultPartitionSpec>(PartitionSpec::kInitialSpecId));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+
+ // Create updated metadata with different default_spec_id
+ auto updated = CreateBaseMetadata();
+ updated->default_spec_id = PartitionSpec::kInitialSpecId + 1;
+
+ // Find and validate the AssertDefaultSpecID requirement
+ for (const auto& req : requirements) {
+ if (dynamic_cast<table::AssertDefaultSpecID*>(req.get()) != nullptr) {
+ auto status = req->Validate(updated.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("default partition spec changed"));
+ break;
+ }
+ }
+}
+
+// RemovePartitionSpecs Tests
+
+TEST(TableRequirementsTest, RemovePartitionSpecs) {
+ auto metadata = CreateBaseMetadata();
+ metadata->default_spec_id = 3;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(
+ std::make_unique<table::RemovePartitionSpecs>(std::vector<int32_t>{1,
2}));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // Should have AssertUUID + AssertDefaultSpecID
+ ASSERT_EQ(requirements.size(), 2);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertDefaultSpecID>(requirements),
1);
+
+ // Verify the default spec ID value
+ auto* assert_spec_id =
dynamic_cast<table::AssertDefaultSpecID*>(requirements[1].get());
+ ASSERT_NE(assert_spec_id, nullptr);
+ EXPECT_EQ(assert_spec_id->spec_id(), 3);
+
+ // Validate against base metadata
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+TEST(TableRequirementsTest, RemovePartitionSpecsWithBranch) {
+ auto metadata = CreateBaseMetadata();
+ metadata->default_spec_id = 3;
+ AddBranch(*metadata, "branch", 42);
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(
+ std::make_unique<table::RemovePartitionSpecs>(std::vector<int32_t>{1,
2}));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // Should have AssertUUID + AssertDefaultSpecID + AssertRefSnapshotID
+ ASSERT_EQ(requirements.size(), 3);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertDefaultSpecID>(requirements),
1);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertRefSnapshotID>(requirements),
1);
+
+ // Validate against base metadata
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+TEST(TableRequirementsTest, RemovePartitionSpecsWithSpecChangedFailure) {
+ auto metadata = CreateBaseMetadata();
+ metadata->default_spec_id = 3;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(
+ std::make_unique<table::RemovePartitionSpecs>(std::vector<int32_t>{1,
2}));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+
+ // Create updated metadata with different default_spec_id
+ auto updated = CreateBaseMetadata();
+ updated->default_spec_id = 4;
+
+ // Find and validate the AssertDefaultSpecID requirement
+ for (const auto& req : requirements) {
+ if (dynamic_cast<table::AssertDefaultSpecID*>(req.get()) != nullptr) {
+ auto status = req->Validate(updated.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("default partition spec changed"));
+ break;
+ }
+ }
+}
+
+TEST(TableRequirementsTest, RemovePartitionSpecsWithBranchChangedFailure) {
+ auto metadata = CreateBaseMetadata();
+ metadata->default_spec_id = 3;
+ AddBranch(*metadata, "test", 42);
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(
+ std::make_unique<table::RemovePartitionSpecs>(std::vector<int32_t>{1,
2}));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+
+ // Create updated metadata with changed branch
+ auto updated = CreateBaseMetadata();
+ updated->default_spec_id = 3;
+ AddBranch(*updated, "test", 43);
+
+ // Find and validate the AssertRefSnapshotID requirement
+ for (const auto& req : requirements) {
+ if (dynamic_cast<table::AssertRefSnapshotID*>(req.get()) != nullptr) {
+ auto status = req->Validate(updated.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("has changed"));
+ break;
+ }
+ }
+}
+
+// RemoveSchemas Tests
+
+TEST(TableRequirementsTest, RemoveSchemas) {
+ auto metadata = CreateBaseMetadata();
+ metadata->current_schema_id = 3;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+
updates.push_back(std::make_unique<table::RemoveSchemas>(std::vector<int32_t>{1,
2}));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // Should have AssertUUID + AssertCurrentSchemaID
+ ASSERT_EQ(requirements.size(), 2);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
EXPECT_EQ(CountRequirementsOfType<table::AssertCurrentSchemaID>(requirements),
1);
+
+ // Verify the current schema ID value
+ auto* assert_schema_id =
+ dynamic_cast<table::AssertCurrentSchemaID*>(requirements[1].get());
+ ASSERT_NE(assert_schema_id, nullptr);
+ EXPECT_EQ(assert_schema_id->schema_id(), 3);
+
+ // Validate against base metadata
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+TEST(TableRequirementsTest, RemoveSchemasWithBranch) {
+ auto metadata = CreateBaseMetadata();
+ metadata->current_schema_id = 3;
+ AddBranch(*metadata, "branch", 42);
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+
updates.push_back(std::make_unique<table::RemoveSchemas>(std::vector<int32_t>{1,
2}));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // Should have AssertUUID + AssertCurrentSchemaID + AssertRefSnapshotID
+ ASSERT_EQ(requirements.size(), 3);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
EXPECT_EQ(CountRequirementsOfType<table::AssertCurrentSchemaID>(requirements),
1);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertRefSnapshotID>(requirements),
1);
+
+ // Validate against base metadata
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+TEST(TableRequirementsTest, RemoveSchemasWithSchemaChangedFailure) {
+ auto metadata = CreateBaseMetadata();
+ metadata->current_schema_id = 3;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+
updates.push_back(std::make_unique<table::RemoveSchemas>(std::vector<int32_t>{1,
2}));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+
+ // Create updated metadata with different current_schema_id
+ auto updated = CreateBaseMetadata();
+ updated->current_schema_id = 4;
+
+ // Find and validate the AssertCurrentSchemaID requirement
+ for (const auto& req : requirements) {
+ if (dynamic_cast<table::AssertCurrentSchemaID*>(req.get()) != nullptr) {
+ auto status = req->Validate(updated.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("current schema ID does not match"));
+ break;
+ }
+ }
+}
+
+TEST(TableRequirementsTest, RemoveSchemasWithBranchChangedFailure) {
+ auto metadata = CreateBaseMetadata();
+ metadata->current_schema_id = 3;
+ AddBranch(*metadata, "test", 42);
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+
updates.push_back(std::make_unique<table::RemoveSchemas>(std::vector<int32_t>{1,
2}));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+
+ // Create updated metadata with changed branch
+ auto updated = CreateBaseMetadata();
+ updated->current_schema_id = 3;
+ AddBranch(*updated, "test", 43);
+
+ // Find and validate the AssertRefSnapshotID requirement
+ for (const auto& req : requirements) {
+ if (dynamic_cast<table::AssertRefSnapshotID*>(req.get()) != nullptr) {
+ auto status = req->Validate(updated.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("has changed"));
+ break;
+ }
+ }
+}
+
+// AddSortOrder Tests
+
+TEST(TableRequirementsTest, AddSortOrder) {
+ auto metadata = CreateBaseMetadata();
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+
+
updates.push_back(std::make_unique<table::AddSortOrder>(SortOrder::Unsorted()));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // AddSortOrder doesn't add additional requirements
+ ASSERT_EQ(requirements.size(), 1);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
+ // Validate against base metadata
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+// SetDefaultSortOrder Tests
+
+TEST(TableRequirementsTest, SetDefaultSortOrder) {
+ auto metadata = CreateBaseMetadata();
+ metadata->default_sort_order_id = 3;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ // Add multiple SetDefaultSortOrder updates
+ updates.push_back(std::make_unique<table::SetDefaultSortOrder>(3));
+ updates.push_back(std::make_unique<table::SetDefaultSortOrder>(4));
+ updates.push_back(std::make_unique<table::SetDefaultSortOrder>(5));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // Should have AssertUUID + AssertDefaultSortOrderID (deduplicated)
+ ASSERT_EQ(requirements.size(), 2);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
EXPECT_EQ(CountRequirementsOfType<table::AssertDefaultSortOrderID>(requirements),
1);
+
+ // Verify the default sort order ID value
+ auto* assert_sort_order_id =
+ dynamic_cast<table::AssertDefaultSortOrderID*>(requirements[1].get());
+ ASSERT_NE(assert_sort_order_id, nullptr);
+ EXPECT_EQ(assert_sort_order_id->sort_order_id(), 3);
+
+ // Validate against base metadata
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+TEST(TableRequirementsTest, SetDefaultSortOrderFailure) {
+ auto metadata = CreateBaseMetadata();
+ metadata->default_sort_order_id = SortOrder::kUnsortedOrderId;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(
+
std::make_unique<table::SetDefaultSortOrder>(SortOrder::kUnsortedOrderId));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+
+ // Create updated metadata with different default_sort_order_id
+ auto updated = CreateBaseMetadata();
+ updated->default_sort_order_id = SortOrder::kUnsortedOrderId + 1;
+
+ // Find and validate the AssertDefaultSortOrderID requirement
+ for (const auto& req : requirements) {
+ if (dynamic_cast<table::AssertDefaultSortOrderID*>(req.get()) != nullptr) {
+ auto status = req->Validate(updated.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("default sort order changed"));
+ break;
+ }
+ }
+}
+
+// AddSnapshot Tests
+
+TEST(TableRequirementsTest, AddSnapshot) {
+ auto metadata = CreateBaseMetadata();
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ auto snapshot = std::make_shared<Snapshot>();
+ snapshot->snapshot_id = 1;
+ snapshot->sequence_number = 1;
+ snapshot->timestamp_ms = TimePointMs{std::chrono::milliseconds(1000)};
+ snapshot->manifest_list = "s3://bucket/manifest_list";
+ updates.push_back(std::make_unique<table::AddSnapshot>(snapshot));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ ASSERT_EQ(requirements.size(), 1);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+// RemoveSnapshots Tests
+
+TEST(TableRequirementsTest, RemoveSnapshots) {
+ auto metadata = CreateBaseMetadata();
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+
updates.push_back(std::make_unique<table::RemoveSnapshots>(std::vector<int64_t>{0}));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ ASSERT_EQ(requirements.size(), 1);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+// SetSnapshotRef Tests
+
+TEST(TableRequirementsTest, SetSnapshotRef) {
+ constexpr int64_t kSnapshotId = 14;
+ const std::string kRefName = "branch";
+
+ auto metadata = CreateBaseMetadata();
+ AddBranch(*metadata, kRefName, kSnapshotId);
+
+ // Multiple updates to same ref should deduplicate
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(std::make_unique<table::SetSnapshotRef>(kRefName,
kSnapshotId,
+
SnapshotRefType::kBranch));
+ updates.push_back(std::make_unique<table::SetSnapshotRef>(kRefName,
kSnapshotId + 1,
+
SnapshotRefType::kBranch));
+ updates.push_back(std::make_unique<table::SetSnapshotRef>(kRefName,
kSnapshotId + 2,
+
SnapshotRefType::kBranch));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+
+ ASSERT_EQ(requirements.size(), 2);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertRefSnapshotID>(requirements),
1);
+
+ auto* assert_ref =
dynamic_cast<table::AssertRefSnapshotID*>(requirements[1].get());
+ ASSERT_NE(assert_ref, nullptr);
+ EXPECT_EQ(assert_ref->snapshot_id(), kSnapshotId);
+ EXPECT_EQ(assert_ref->ref_name(), kRefName);
+}
+
+// RemoveSnapshotRef Tests
+
+TEST(TableRequirementsTest, RemoveSnapshotRef) {
+ auto metadata = CreateBaseMetadata();
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(std::make_unique<table::RemoveSnapshotRef>("branch"));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ ASSERT_EQ(requirements.size(), 1);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+// SetAndRemoveProperties Tests
+
+TEST(TableRequirementsTest, SetProperties) {
+ auto metadata = CreateBaseMetadata();
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+
+ std::unordered_map<std::string, std::string> props;
+ props["test"] = "value";
+ updates.push_back(std::make_unique<table::SetProperties>(props));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // SetProperties doesn't add additional requirements
+ ASSERT_EQ(requirements.size(), 1);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
+ // Validate against base metadata
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+TEST(TableRequirementsTest, RemoveProperties) {
+ auto metadata = CreateBaseMetadata();
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+
+ updates.push_back(
+
std::make_unique<table::RemoveProperties>(std::vector<std::string>{"test"}));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // RemoveProperties doesn't add additional requirements
+ ASSERT_EQ(requirements.size(), 1);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
+ // Validate against base metadata
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+// SetLocation Tests
+
+TEST(TableRequirementsTest, SetLocation) {
+ auto metadata = CreateBaseMetadata();
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+
+
updates.push_back(std::make_unique<table::SetLocation>("s3://new-bucket/test"));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // SetLocation doesn't add additional requirements
+ ASSERT_EQ(requirements.size(), 1);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
+ // Validate against base metadata
+ for (const auto& req : requirements) {
+ EXPECT_THAT(req->Validate(metadata.get()), IsOk());
+ }
+}
+
+// AssertRefSnapshotID Tests
+
+TEST(TableRequirementsTest, AssertRefSnapshotIDSuccess) {
+ auto metadata = CreateBaseMetadata();
+ AddBranch(*metadata, "branch", 14);
+
+ table::AssertRefSnapshotID requirement("branch", 14);
+ auto status = requirement.Validate(metadata.get());
+ EXPECT_THAT(status, IsOk());
+}
+
+TEST(TableRequirementsTest, AssertRefSnapshotIDCreatedConcurrently) {
+ auto metadata = CreateBaseMetadata();
+ AddBranch(*metadata, "random_branch", 14);
+
+ // Requirement expects ref doesn't exist (nullopt snapshot_id)
+ table::AssertRefSnapshotID requirement("random_branch", std::nullopt);
+ auto status = requirement.Validate(metadata.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("was created concurrently"));
+}
+
+TEST(TableRequirementsTest, AssertRefSnapshotIDMissing) {
+ auto metadata = CreateBaseMetadata();
+ // No branch added
+
+ // Requirement expects a snapshot ID that doesn't exist
+ table::AssertRefSnapshotID requirement("random_branch", 14);
+ auto status = requirement.Validate(metadata.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("is missing"));
+}
+
+TEST(TableRequirementsTest, AssertRefSnapshotIDChanged) {
+ auto metadata = CreateBaseMetadata();
+ AddBranch(*metadata, "random_branch", 15);
+
+ // Requirement expects snapshot ID 14, but actual is 15
+ table::AssertRefSnapshotID requirement("random_branch", 14);
+ auto status = requirement.Validate(metadata.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("has changed"));
+}
+
+// Replace Table Tests (less restrictive than Update)
+
+TEST(TableRequirementsTest, ReplaceTableDoesNotRequireCurrentSchemaID) {
+ auto metadata = CreateBaseMetadata();
+ metadata->current_schema_id = 3;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(std::make_unique<table::SetCurrentSchema>(5));
+
+ auto result = TableRequirements::ForReplaceTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // Replace table should NOT add AssertCurrentSchemaID
+
EXPECT_EQ(CountRequirementsOfType<table::AssertCurrentSchemaID>(requirements),
0);
+}
+
+TEST(TableRequirementsTest, ReplaceTableDoesNotRequireDefaultSpecID) {
+ auto metadata = CreateBaseMetadata();
+ metadata->default_spec_id = 3;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(std::make_unique<table::SetDefaultPartitionSpec>(5));
+
+ auto result = TableRequirements::ForReplaceTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // Replace table should NOT add AssertDefaultSpecID
+ EXPECT_EQ(CountRequirementsOfType<table::AssertDefaultSpecID>(requirements),
0);
+}
+
+TEST(TableRequirementsTest, ReplaceTableDoesNotRequireDefaultSortOrderID) {
+ auto metadata = CreateBaseMetadata();
+ metadata->default_sort_order_id = 3;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ updates.push_back(std::make_unique<table::SetDefaultSortOrder>(5));
+
+ auto result = TableRequirements::ForReplaceTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // Replace table should NOT add AssertDefaultSortOrderID
+
EXPECT_EQ(CountRequirementsOfType<table::AssertDefaultSortOrderID>(requirements),
0);
+}
+
+TEST(TableRequirementsTest, ReplaceTableDoesNotAddBranchRequirements) {
+ auto metadata = CreateBaseMetadata();
+ metadata->current_schema_id = 3;
+ AddBranch(*metadata, "branch", 42);
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+
updates.push_back(std::make_unique<table::RemoveSchemas>(std::vector<int32_t>{1,
2}));
+
+ auto result = TableRequirements::ForReplaceTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // Replace table should NOT add AssertRefSnapshotID for branches
+ EXPECT_EQ(CountRequirementsOfType<table::AssertRefSnapshotID>(requirements),
0);
+}
+
+// Combined Updates Tests
+
+TEST(TableRequirementsTest, MultipleUpdatesDeduplication) {
+ auto metadata = CreateBaseMetadata();
+ metadata->last_column_id = 1;
+ metadata->current_schema_id = 0;
+
+ std::vector<std::unique_ptr<TableUpdate>> updates;
+ auto schema = CreateTestSchema();
+ // Add multiple AddSchema updates - should only generate one requirement
+ updates.push_back(std::make_unique<table::AddSchema>(schema, 1));
+ updates.push_back(std::make_unique<table::AddSchema>(schema, 1));
+ // Add multiple SetCurrentSchema updates - should only generate one
requirement
+ updates.push_back(std::make_unique<table::SetCurrentSchema>(0));
+ updates.push_back(std::make_unique<table::SetCurrentSchema>(1));
+
+ auto result = TableRequirements::ForUpdateTable(*metadata, updates);
+ ASSERT_THAT(result, IsOk());
+
+ auto& requirements = result.value();
+ // Should have: 1 AssertUUID + 1 AssertLastAssignedFieldId + 1
AssertCurrentSchemaID
+ ASSERT_EQ(requirements.size(), 3);
+ EXPECT_EQ(CountRequirementsOfType<table::AssertUUID>(requirements), 1);
+
EXPECT_EQ(CountRequirementsOfType<table::AssertLastAssignedFieldId>(requirements),
1);
+
EXPECT_EQ(CountRequirementsOfType<table::AssertCurrentSchemaID>(requirements),
1);
+}
+
} // namespace iceberg
diff --git a/src/iceberg/test/table_update_test.cc
b/src/iceberg/test/table_update_test.cc
index 298141a9..d250b6d2 100644
--- a/src/iceberg/test/table_update_test.cc
+++ b/src/iceberg/test/table_update_test.cc
@@ -19,6 +19,7 @@
#include "iceberg/table_update.h"
+#include <functional>
#include <memory>
#include <string>
#include <vector>
@@ -26,23 +27,34 @@
#include <gtest/gtest.h>
#include "iceberg/partition_spec.h"
+#include "iceberg/schema.h"
#include "iceberg/snapshot.h"
+#include "iceberg/sort_field.h"
#include "iceberg/sort_order.h"
#include "iceberg/table_metadata.h"
#include "iceberg/table_requirement.h"
#include "iceberg/table_requirements.h"
#include "iceberg/test/matchers.h"
+#include "iceberg/transform.h"
+#include "iceberg/type.h"
namespace iceberg {
namespace {
+// Helper function to create a simple schema for testing
+std::shared_ptr<Schema> CreateTestSchema() {
+ auto field1 = SchemaField::MakeRequired(1, "id", int32());
+ auto field2 = SchemaField::MakeRequired(2, "data", string());
+ auto field3 = SchemaField::MakeRequired(3, "ts", timestamp());
+ return std::make_shared<Schema>(std::vector<SchemaField>{field1, field2,
field3}, 0);
+}
+
// Helper function to generate requirements
std::vector<std::unique_ptr<TableRequirement>> GenerateRequirements(
const TableUpdate& update, const TableMetadata* base) {
TableUpdateContext context(base, /*is_replace=*/false);
- EXPECT_THAT(update.GenerateRequirements(context), IsOk());
-
+ update.GenerateRequirements(context), IsOk();
auto requirements = context.Build();
EXPECT_THAT(requirements, IsOk());
return std::move(requirements.value());
@@ -56,35 +68,250 @@ std::unique_ptr<TableMetadata> CreateBaseMetadata() {
metadata->location = "s3://bucket/test";
metadata->last_sequence_number = 0;
metadata->last_updated_ms = TimePointMs{std::chrono::milliseconds(1000)};
- metadata->last_column_id = 0;
+ metadata->last_column_id = 3;
+ metadata->current_schema_id = 0;
+ metadata->schemas.push_back(CreateTestSchema());
metadata->default_spec_id = PartitionSpec::kInitialSpecId;
metadata->last_partition_id = 0;
metadata->current_snapshot_id = Snapshot::kInvalidSnapshotId;
metadata->default_sort_order_id = SortOrder::kInitialSortOrderId;
+ metadata->sort_orders.push_back(SortOrder::Unsorted());
metadata->next_row_id = TableMetadata::kInitialRowId;
return metadata;
}
} // namespace
-// Test GenerateRequirements for AssignUUID update
-TEST(TableUpdateTest, AssignUUIDGenerateRequirements) {
- table::AssignUUID update("new-uuid");
+// Parameter struct for testing GenerateRequirements behavior
+struct GenerateRequirementsTestParam {
+ std::string test_name;
+ std::function<std::unique_ptr<TableUpdate>()> update_factory;
+ // Expected number of requirements for existing table (new table always
expects 0)
+ size_t expected_existing_table_count;
+ // Optional validator function to check the generated requirements
+ std::function<void(const std::vector<std::unique_ptr<TableRequirement>>&,
+ const TableMetadata*)>
+ validator;
+};
- // New table - no requirements (AssignUUID doesn't generate requirements)
- auto new_table_reqs = GenerateRequirements(update, nullptr);
+class GenerateRequirementsTest
+ : public ::testing::TestWithParam<GenerateRequirementsTestParam> {};
+
+TEST_P(GenerateRequirementsTest, GeneratesExpectedRequirements) {
+ const auto& param = GetParam();
+ auto update = param.update_factory();
+
+ // New table - always no requirements
+ auto new_table_reqs = GenerateRequirements(*update, nullptr);
EXPECT_TRUE(new_table_reqs.empty());
- // Existing table - AssignUUID doesn't generate requirements anymore
- // The UUID assertion is added by ForUpdateTable/ForReplaceTable methods
+ // Existing table - check expected count
+ auto base = CreateBaseMetadata();
+ auto existing_table_reqs = GenerateRequirements(*update, base.get());
+ ASSERT_EQ(existing_table_reqs.size(), param.expected_existing_table_count);
+
+ // Validate the requirements if validator is provided
+ if (param.validator) {
+ param.validator(existing_table_reqs, base.get());
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TableUpdateGenerateRequirements, GenerateRequirementsTest,
+ ::testing::Values(
+ // Updates that generate no requirements
+ GenerateRequirementsTestParam{
+ .test_name = "AssignUUID",
+ .update_factory =
+ [] { return std::make_unique<table::AssignUUID>("new-uuid"); },
+ .expected_existing_table_count = 0,
+ .validator = nullptr},
+ GenerateRequirementsTestParam{
+ .test_name = "UpgradeFormatVersion",
+ .update_factory =
+ [] { return std::make_unique<table::UpgradeFormatVersion>(3);
},
+ .expected_existing_table_count = 0,
+ .validator = nullptr},
+ GenerateRequirementsTestParam{
+ .test_name = "AddSortOrder",
+ .update_factory =
+ [] {
+ auto schema = CreateTestSchema();
+ SortField sort_field(1, Transform::Identity(),
+ SortDirection::kAscending,
NullOrder::kFirst);
+ auto sort_order =
+ SortOrder::Make(*schema, 1,
std::vector<SortField>{sort_field})
+ .value();
+ return
std::make_unique<table::AddSortOrder>(std::move(sort_order));
+ },
+ .expected_existing_table_count = 0,
+ .validator = nullptr},
+ GenerateRequirementsTestParam{.test_name = "AddSnapshot",
+ .update_factory =
+ [] {
+ auto snapshot =
std::make_shared<Snapshot>();
+ return
std::make_unique<table::AddSnapshot>(
+ snapshot);
+ },
+ .expected_existing_table_count = 0,
+ .validator = nullptr},
+ GenerateRequirementsTestParam{
+ .test_name = "RemoveSnapshotRef",
+ .update_factory =
+ [] { return
std::make_unique<table::RemoveSnapshotRef>("my-branch"); },
+ .expected_existing_table_count = 0,
+ .validator = nullptr},
+ GenerateRequirementsTestParam{
+ .test_name = "SetProperties",
+ .update_factory =
+ [] {
+ return std::make_unique<table::SetProperties>(
+ std::unordered_map<std::string, std::string>{{"key",
"value"}});
+ },
+ .expected_existing_table_count = 0,
+ .validator = nullptr},
+ GenerateRequirementsTestParam{
+ .test_name = "RemoveProperties",
+ .update_factory =
+ [] {
+ return std::make_unique<table::RemoveProperties>(
+ std::vector<std::string>{"key"});
+ },
+ .expected_existing_table_count = 0,
+ .validator = nullptr},
+ GenerateRequirementsTestParam{
+ .test_name = "SetLocation",
+ .update_factory =
+ [] { return
std::make_unique<table::SetLocation>("s3://new/location"); },
+ .expected_existing_table_count = 0,
+ .validator = nullptr},
+
+ // Updates that generate single requirement for existing tables
+ GenerateRequirementsTestParam{
+ .test_name = "AddSchema",
+ .update_factory =
+ [] {
+ auto new_schema = std::make_shared<Schema>(
+ std::vector<SchemaField>{
+ SchemaField::MakeRequired(4, "new_col", string())},
+ 3);
+ return std::make_unique<table::AddSchema>(new_schema, 3);
+ },
+ .expected_existing_table_count = 1,
+ .validator =
+ [](const std::vector<std::unique_ptr<TableRequirement>>& reqs,
+ const TableMetadata* base) {
+ auto* assert_id = dynamic_cast<const
table::AssertLastAssignedFieldId*>(
+ reqs[0].get());
+ ASSERT_NE(assert_id, nullptr);
+ EXPECT_EQ(assert_id->last_assigned_field_id(),
base->last_column_id);
+ }},
+ GenerateRequirementsTestParam{
+ .test_name = "SetCurrentSchema",
+ .update_factory = [] { return
std::make_unique<table::SetCurrentSchema>(1); },
+ .expected_existing_table_count = 1,
+ .validator =
+ [](const std::vector<std::unique_ptr<TableRequirement>>& reqs,
+ const TableMetadata* base) {
+ auto* assert_id =
+ dynamic_cast<const
table::AssertCurrentSchemaID*>(reqs[0].get());
+ ASSERT_NE(assert_id, nullptr);
+ EXPECT_EQ(assert_id->schema_id(), base->current_schema_id);
+ }},
+ GenerateRequirementsTestParam{
+ .test_name = "AddPartitionSpec",
+ .update_factory =
+ [] {
+ PartitionField partition_field(1, 1, "id_identity",
+ Transform::Identity());
+ auto spec = std::shared_ptr<PartitionSpec>(
+ PartitionSpec::Make(1,
{partition_field}).value().release());
+ return std::make_unique<table::AddPartitionSpec>(spec);
+ },
+ .expected_existing_table_count = 1,
+ .validator =
+ [](const std::vector<std::unique_ptr<TableRequirement>>& reqs,
+ const TableMetadata* base) {
+ auto* assert_id =
+ dynamic_cast<const
table::AssertLastAssignedPartitionId*>(
+ reqs[0].get());
+ ASSERT_NE(assert_id, nullptr);
+ EXPECT_EQ(assert_id->last_assigned_partition_id(),
+ base->last_partition_id);
+ }},
+ GenerateRequirementsTestParam{
+ .test_name = "SetDefaultPartitionSpec",
+ .update_factory =
+ [] { return
std::make_unique<table::SetDefaultPartitionSpec>(1); },
+ .expected_existing_table_count = 1,
+ .validator =
+ [](const std::vector<std::unique_ptr<TableRequirement>>& reqs,
+ const TableMetadata* base) {
+ auto* assert_id =
+ dynamic_cast<const
table::AssertDefaultSpecID*>(reqs[0].get());
+ ASSERT_NE(assert_id, nullptr);
+ EXPECT_EQ(assert_id->spec_id(), base->default_spec_id);
+ }},
+ GenerateRequirementsTestParam{
+ .test_name = "SetDefaultSortOrder",
+ .update_factory =
+ [] { return std::make_unique<table::SetDefaultSortOrder>(1); },
+ .expected_existing_table_count = 1,
+ .validator =
+ [](const std::vector<std::unique_ptr<TableRequirement>>& reqs,
+ const TableMetadata* base) {
+ auto* assert_sort_order =
+ dynamic_cast<const
table::AssertDefaultSortOrderID*>(reqs[0].get());
+ ASSERT_NE(assert_sort_order, nullptr);
+ EXPECT_EQ(assert_sort_order->sort_order_id(),
+ base->default_sort_order_id);
+ }},
+ GenerateRequirementsTestParam{
+ .test_name = "RemovePartitionSpecs",
+ .update_factory =
+ [] {
+ return std::make_unique<table::RemovePartitionSpecs>(
+ std::vector<int>{1});
+ },
+ .expected_existing_table_count = 1,
+ .validator =
+ [](const std::vector<std::unique_ptr<TableRequirement>>& reqs,
+ const TableMetadata* base) {
+ auto* assert_id =
+ dynamic_cast<const
table::AssertDefaultSpecID*>(reqs[0].get());
+ ASSERT_NE(assert_id, nullptr);
+ EXPECT_EQ(assert_id->spec_id(), base->default_spec_id);
+ }},
+ GenerateRequirementsTestParam{
+ .test_name = "RemoveSchemas",
+ .update_factory =
+ [] {
+ return
std::make_unique<table::RemoveSchemas>(std::vector<int>{1});
+ },
+ .expected_existing_table_count = 1,
+ .validator =
+ [](const std::vector<std::unique_ptr<TableRequirement>>& reqs,
+ const TableMetadata* base) {
+ auto* assert_id =
+ dynamic_cast<const
table::AssertCurrentSchemaID*>(reqs[0].get());
+ ASSERT_NE(assert_id, nullptr);
+ EXPECT_EQ(assert_id->schema_id(), base->current_schema_id);
+ }}),
+ [](const testing::TestParamInfo<GenerateRequirementsTestParam>& info) {
+ return info.param.test_name;
+ });
+
+// Test AssignUUID ApplyTo
+TEST(TableUpdateTest, AssignUUIDApplyUpdate) {
auto base = CreateBaseMetadata();
- auto existing_table_reqs = GenerateRequirements(update, base.get());
- EXPECT_TRUE(existing_table_reqs.empty());
+ auto builder = TableMetadataBuilder::BuildFrom(base.get());
+
+ // Apply AssignUUID update
+ table::AssignUUID uuid_update("apply-uuid");
+ uuid_update.ApplyTo(*builder);
- // Existing table with empty UUID - no requirements
- base->table_uuid = "";
- auto empty_uuid_reqs = GenerateRequirements(update, base.get());
- EXPECT_TRUE(empty_uuid_reqs.empty());
+ ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build());
+ EXPECT_EQ(metadata->table_uuid, "apply-uuid");
}
} // namespace iceberg
diff --git a/src/iceberg/transaction.h b/src/iceberg/transaction.h
index 0bcedd6d..72ba5182 100644
--- a/src/iceberg/transaction.h
+++ b/src/iceberg/transaction.h
@@ -21,7 +21,6 @@
#pragma once
#include <memory>
-#include <vector>
#include "iceberg/iceberg_export.h"
#include "iceberg/result.h"