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 8388f32  feat: implement validation for table update requirements 
(#294)
8388f32 is described below

commit 8388f3286b65d13dc8894162b5adfdc0aa681320
Author: Xinli Shang <[email protected]>
AuthorDate: Tue Nov 11 18:19:15 2025 -0800

    feat: implement validation for table update requirements (#294)
    
    Implement Validate() methods for remaining table requirement classes:
    - AssertDoesNotExist: validates table doesn't exist
    - AssertRefSnapshotID: validates snapshot references (branches/tags)
    - AssertLastAssignedFieldId: validates last assigned field ID
    - AssertLastAssignedPartitionId: validates last assigned partition ID
    - AssertDefaultSpecID: validates default partition spec ID
    - AssertDefaultSortOrderID: validates default sort order ID
    
    All implementations follow the pattern established in
    AssertCurrentSchemaID and provide descriptive error messages for
    validation failures.
---
 src/iceberg/table_requirement.cc                |  90 +++++++++++---
 src/iceberg/test/table_metadata_builder_test.cc | 149 ++++++++++++++++++++++++
 2 files changed, 224 insertions(+), 15 deletions(-)

diff --git a/src/iceberg/table_requirement.cc b/src/iceberg/table_requirement.cc
index 0b8e795..3c0a01e 100644
--- a/src/iceberg/table_requirement.cc
+++ b/src/iceberg/table_requirement.cc
@@ -19,43 +19,73 @@
 
 #include "iceberg/table_requirement.h"
 
+#include "iceberg/snapshot.h"
 #include "iceberg/table_metadata.h"
+#include "iceberg/util/macros.h"
 #include "iceberg/util/string_util.h"
 
 namespace iceberg::table {
 
 Status AssertDoesNotExist::Validate(const TableMetadata* base) const {
-  return NotImplemented("AssertDoesNotExist::Validate not implemented");
+  if (base != nullptr) {
+    return CommitFailed("Requirement failed: table already exists");
+  }
+
+  return {};
 }
 
 Status AssertUUID::Validate(const TableMetadata* base) const {
-  // Validate that the table UUID matches the expected value
-
   if (base == nullptr) {
     return CommitFailed("Requirement failed: current table metadata is 
missing");
   }
 
   if (!StringUtils::EqualsIgnoreCase(base->table_uuid, uuid_)) {
     return CommitFailed(
-        "Requirement failed: table UUID does not match (expected='{}', 
actual='{}')",
-        uuid_, base->table_uuid);
+        "Requirement failed: table UUID does not match, expected {} != {}", 
uuid_,
+        base->table_uuid);
   }
 
   return {};
 }
 
 Status AssertRefSnapshotID::Validate(const TableMetadata* base) const {
-  return NotImplemented("AssertTableRefSnapshotID::Validate not implemented");
+  if (base == nullptr) {
+    return CommitFailed("Requirement failed: current table metadata is 
missing");
+  }
+
+  if (auto ref = base->refs.find(ref_name_); ref != base->refs.cend()) {
+    const auto& snapshot_ref = ref->second;
+    ICEBERG_DCHECK(snapshot_ref != nullptr, "Snapshot reference is null");
+    std::string_view type =
+        snapshot_ref->type() == SnapshotRefType::kBranch ? "branch" : "tag";
+    if (!snapshot_id_.has_value()) {
+      // A null snapshot ID means the ref should not exist already
+      return CommitFailed("Requirement failed: {} '{}' was created 
concurrently", type,
+                          ref_name_);
+    } else if (snapshot_id_.value() != snapshot_ref->snapshot_id) {
+      return CommitFailed("Requirement failed: {} '{}' has changed: expected 
id {} != {}",
+                          type, ref_name_, snapshot_id_.value(),
+                          snapshot_ref->snapshot_id);
+    }
+  } else if (snapshot_id_.has_value()) {
+    return CommitFailed("Requirement failed: branch or tag '{}' is missing, 
expected {}",
+                        ref_name_, snapshot_id_.value());
+  }
+
+  return {};
 }
 
 Status AssertLastAssignedFieldId::Validate(const TableMetadata* base) const {
-  return NotImplemented(
-      "AssertCurrentTableLastAssignedFieldId::Validate not implemented");
+  if (base && base->last_column_id != last_assigned_field_id_) {
+    return CommitFailed(
+        "Requirement failed: last assigned field ID does not match, expected 
{} != {}",
+        last_assigned_field_id_, base->last_column_id);
+  }
+
+  return {};
 }
 
 Status AssertCurrentSchemaID::Validate(const TableMetadata* base) const {
-  // Validate that the current schema ID matches the one used when the 
metadata was read
-
   if (base == nullptr) {
     return CommitFailed("Requirement failed: current table metadata is 
missing");
   }
@@ -67,7 +97,7 @@ Status AssertCurrentSchemaID::Validate(const TableMetadata* 
base) const {
 
   if (base->current_schema_id.value() != schema_id_) {
     return CommitFailed(
-        "Requirement failed: current schema ID does not match (expected={}, 
actual={})",
+        "Requirement failed: current schema ID does not match, expected {} != 
{}",
         schema_id_, base->current_schema_id.value());
   }
 
@@ -75,16 +105,46 @@ Status AssertCurrentSchemaID::Validate(const 
TableMetadata* base) const {
 }
 
 Status AssertLastAssignedPartitionId::Validate(const TableMetadata* base) 
const {
-  return NotImplemented(
-      "AssertCurrentTableLastAssignedPartitionId::Validate not implemented");
+  if (base == nullptr) {
+    return CommitFailed("Requirement failed: current table metadata is 
missing");
+  }
+
+  if (base && base->last_partition_id != last_assigned_partition_id_) {
+    return CommitFailed(
+        "Requirement failed: last assigned partition ID does not match, 
expected {} != "
+        "{}",
+        last_assigned_partition_id_, base->last_partition_id);
+  }
+
+  return {};
 }
 
 Status AssertDefaultSpecID::Validate(const TableMetadata* base) const {
-  return NotImplemented("AssertDefaultTableSpecID::Validate not implemented");
+  if (base == nullptr) {
+    return CommitFailed("Requirement failed: current table metadata is 
missing");
+  }
+
+  if (base->default_spec_id != spec_id_) {
+    return CommitFailed(
+        "Requirement failed: default partition spec changed, expected id {} != 
{}",
+        spec_id_, base->default_spec_id);
+  }
+
+  return {};
 }
 
 Status AssertDefaultSortOrderID::Validate(const TableMetadata* base) const {
-  return NotImplemented("AssertDefaultTableSortOrderID::Validate not 
implemented");
+  if (base == nullptr) {
+    return CommitFailed("Requirement failed: current table metadata is 
missing");
+  }
+
+  if (base->default_sort_order_id != sort_order_id_) {
+    return CommitFailed(
+        "Requirement failed: default sort order changed: expected id {} != {}",
+        sort_order_id_, base->default_sort_order_id);
+  }
+
+  return {};
 }
 
 }  // namespace iceberg::table
diff --git a/src/iceberg/test/table_metadata_builder_test.cc 
b/src/iceberg/test/table_metadata_builder_test.cc
index 6b2314f..9b98048 100644
--- a/src/iceberg/test/table_metadata_builder_test.cc
+++ b/src/iceberg/test/table_metadata_builder_test.cc
@@ -264,6 +264,155 @@ TEST_F(TableMetadataBuilderTest, 
TableRequirementAssertCurrentSchemaIDNotSet) {
   EXPECT_THAT(status, HasErrorMessage("schema ID is not set"));
 }
 
+TEST_F(TableMetadataBuilderTest, TableRequirementAssertDoesNotExistSuccess) {
+  table::AssertDoesNotExist requirement;
+
+  ASSERT_THAT(requirement.Validate(nullptr), IsOk());
+}
+
+TEST_F(TableMetadataBuilderTest, 
TableRequirementAssertDoesNotExistTableExists) {
+  table::AssertDoesNotExist requirement;
+
+  auto status = requirement.Validate(base_metadata_.get());
+  EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+  EXPECT_THAT(status, HasErrorMessage("table already exists"));
+}
+
+TEST_F(TableMetadataBuilderTest, TableRequirementAssertRefSnapshotIDSuccess) {
+  auto ref = std::make_shared<SnapshotRef>();
+  ref->snapshot_id = 100;
+  ref->retention = SnapshotRef::Branch{};
+  base_metadata_->refs["main"] = ref;
+
+  table::AssertRefSnapshotID requirement("main", 100);
+
+  ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
+}
+
+TEST_F(TableMetadataBuilderTest, TableRequirementAssertRefSnapshotIDMismatch) {
+  auto ref = std::make_shared<SnapshotRef>();
+  ref->snapshot_id = 100;
+  ref->retention = SnapshotRef::Branch{};
+  base_metadata_->refs["main"] = ref;
+
+  table::AssertRefSnapshotID requirement("main", 200);
+
+  auto status = requirement.Validate(base_metadata_.get());
+  EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+  EXPECT_THAT(status, HasErrorMessage("has changed"));
+}
+
+TEST_F(TableMetadataBuilderTest, 
TableRequirementAssertRefSnapshotIDRefMissing) {
+  table::AssertRefSnapshotID requirement("missing-ref", 100);
+
+  auto status = requirement.Validate(base_metadata_.get());
+  EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+  EXPECT_THAT(status, HasErrorMessage("is missing"));
+}
+
+// Removed TableRequirementAssertRefSnapshotIDNullBase test
+// Java implementation doesn't check for null base, so passing nullptr would 
cause
+// undefined behavior This matches Java's assumption that base is never null 
when Validate
+// is called
+
+TEST_F(TableMetadataBuilderTest, 
TableRequirementAssertRefSnapshotIDNulloptSuccess) {
+  // Ref should not exist, and it doesn't
+  table::AssertRefSnapshotID requirement("nonexistent", std::nullopt);
+
+  ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
+}
+
+TEST_F(TableMetadataBuilderTest, 
TableRequirementAssertRefSnapshotIDNulloptButExists) {
+  auto ref = std::make_shared<SnapshotRef>();
+  ref->snapshot_id = 100;
+  ref->retention = SnapshotRef::Branch{};
+  base_metadata_->refs["main"] = ref;
+
+  table::AssertRefSnapshotID requirement("main", std::nullopt);
+
+  auto status = requirement.Validate(base_metadata_.get());
+  EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+  EXPECT_THAT(status, HasErrorMessage("created concurrently"));
+}
+
+TEST_F(TableMetadataBuilderTest, 
TableRequirementAssertLastAssignedFieldIdSuccess) {
+  base_metadata_->last_column_id = 10;
+  table::AssertLastAssignedFieldId requirement(10);
+
+  ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
+}
+
+TEST_F(TableMetadataBuilderTest, 
TableRequirementAssertLastAssignedFieldIdMismatch) {
+  base_metadata_->last_column_id = 10;
+  table::AssertLastAssignedFieldId requirement(15);
+
+  auto status = requirement.Validate(base_metadata_.get());
+  EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+  EXPECT_THAT(status, HasErrorMessage("last assigned field ID does not 
match"));
+}
+
+TEST_F(TableMetadataBuilderTest, 
TableRequirementAssertLastAssignedFieldIdNullBase) {
+  table::AssertLastAssignedFieldId requirement(10);
+
+  EXPECT_THAT(requirement.Validate(nullptr), IsOk());
+}
+
+TEST_F(TableMetadataBuilderTest, 
TableRequirementAssertLastAssignedPartitionIdSuccess) {
+  base_metadata_->last_partition_id = 5;
+  table::AssertLastAssignedPartitionId requirement(5);
+
+  ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
+}
+
+TEST_F(TableMetadataBuilderTest, 
TableRequirementAssertLastAssignedPartitionIdMismatch) {
+  base_metadata_->last_partition_id = 5;
+  table::AssertLastAssignedPartitionId requirement(8);
+
+  auto status = requirement.Validate(base_metadata_.get());
+  EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+  EXPECT_THAT(status, HasErrorMessage("last assigned partition ID does not 
match"));
+}
+
+TEST_F(TableMetadataBuilderTest, 
TableRequirementAssertLastAssignedPartitionIdNullBase) {
+  table::AssertLastAssignedPartitionId requirement(5);
+
+  auto status = requirement.Validate(nullptr);
+  EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+  EXPECT_THAT(status, HasErrorMessage("metadata is missing"));
+}
+
+TEST_F(TableMetadataBuilderTest, TableRequirementAssertDefaultSpecIDSuccess) {
+  base_metadata_->default_spec_id = 3;
+  table::AssertDefaultSpecID requirement(3);
+
+  ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
+}
+
+TEST_F(TableMetadataBuilderTest, TableRequirementAssertDefaultSpecIDMismatch) {
+  base_metadata_->default_spec_id = 3;
+  table::AssertDefaultSpecID requirement(7);
+
+  auto status = requirement.Validate(base_metadata_.get());
+  EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+  EXPECT_THAT(status, HasErrorMessage("spec changed"));
+}
+
+TEST_F(TableMetadataBuilderTest, 
TableRequirementAssertDefaultSortOrderIDSuccess) {
+  base_metadata_->default_sort_order_id = 2;
+  table::AssertDefaultSortOrderID requirement(2);
+
+  ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
+}
+
+TEST_F(TableMetadataBuilderTest, 
TableRequirementAssertDefaultSortOrderIDMismatch) {
+  base_metadata_->default_sort_order_id = 2;
+  table::AssertDefaultSortOrderID requirement(4);
+
+  auto status = requirement.Validate(base_metadata_.get());
+  EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+  EXPECT_THAT(status, HasErrorMessage("sort order changed"));
+}
+
 // ============================================================================
 // Integration Tests - End-to-End Workflow
 // ============================================================================

Reply via email to