diff --git a/src/iceberg/table_requirement.cc b/src/iceberg/table_requirement.cc index 0b8e7957..c3117618 100644 --- a/src/iceberg/table_requirement.cc +++ b/src/iceberg/table_requirement.cc @@ -19,13 +19,20 @@ #include "iceberg/table_requirement.h" +#include "iceberg/snapshot.h" #include "iceberg/table_metadata.h" #include "iceberg/util/string_util.h" namespace iceberg::table { Status AssertDoesNotExist::Validate(const TableMetadata* base) const { - return NotImplemented("AssertDoesNotExist::Validate not implemented"); + // Validate that the table does not exist + + if (base != nullptr) { + return CommitFailed("Requirement failed: table already exists"); + } + + return {}; } Status AssertUUID::Validate(const TableMetadata* base) const { @@ -45,12 +52,42 @@ Status AssertUUID::Validate(const TableMetadata* base) const { } Status AssertRefSnapshotID::Validate(const TableMetadata* base) const { - return NotImplemented("AssertTableRefSnapshotID::Validate not implemented"); + // Validate that a reference (branch or tag) points to the expected snapshot ID + // Matches Java implementation logic + + auto it = base->refs.find(ref_name_); + if (it != base->refs.end()) { + // Reference exists + if (!snapshot_id_.has_value()) { + // A null snapshot ID means the ref should not exist already + return CommitFailed("Requirement failed: reference '{}' was created concurrently", + ref_name_); + } else if (snapshot_id_.value() != it->second->snapshot_id) { + return CommitFailed( + "Requirement failed: reference '{}' has changed: expected id {} != {}", + ref_name_, snapshot_id_.value(), it->second->snapshot_id); + } + } else if (snapshot_id_.has_value()) { + // Reference does not exist but snapshot_id is specified + 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"); + // Validate that the last assigned field ID matches the expected value + // Allows base to be null for new tables + + if (base && base->last_column_id != last_assigned_field_id_) { + return CommitFailed( + "Requirement failed: last assigned field ID does not match (expected={}, " + "actual={})", + last_assigned_field_id_, base->last_column_id); + } + + return {}; } Status AssertCurrentSchemaID::Validate(const TableMetadata* base) const { @@ -75,16 +112,43 @@ Status AssertCurrentSchemaID::Validate(const TableMetadata* base) const { } Status AssertLastAssignedPartitionId::Validate(const TableMetadata* base) const { - return NotImplemented( - "AssertCurrentTableLastAssignedPartitionId::Validate not implemented"); + // Validate that the last assigned partition ID matches the expected value + // Allows base to be null for new tables + + if (base && base->last_partition_id != last_assigned_partition_id_) { + return CommitFailed( + "Requirement failed: last assigned partition ID does not match (expected={}, " + "actual={})", + last_assigned_partition_id_, base->last_partition_id); + } + + return {}; } Status AssertDefaultSpecID::Validate(const TableMetadata* base) const { - return NotImplemented("AssertDefaultTableSpecID::Validate not implemented"); + // Validate that the default partition spec ID matches the expected value + // Matches Java implementation - assumes base is never null + + 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"); + // Validate that the default sort order ID matches the expected value + // Matches Java implementation - assumes base is never null + + 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 6b2314f1..b8d55210 100644 --- a/src/iceberg/test/table_metadata_builder_test.cc +++ b/src/iceberg/test/table_metadata_builder_test.cc @@ -264,6 +264,166 @@ 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(); + 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(); + 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(); + ref->snapshot_id = 100; + ref->retention = SnapshotRef::Branch{}; + base_metadata_->refs["main"] = ref; + + // Ref should not exist, but it does + 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); + + // Null base is now allowed (for new tables) - should succeed + ASSERT_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); + + // Null base is now allowed (for new tables) - should succeed + ASSERT_THAT(requirement.Validate(nullptr), IsOk()); +} + +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")); +} + +// Removed TableRequirementAssertDefaultSpecIDNullBase 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, 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")); +} + +// Removed TableRequirementAssertDefaultSortOrderIDNullBase 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 + // ============================================================================ // Integration Tests - End-to-End Workflow // ============================================================================