Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 72 additions & 8 deletions src/iceberg/table_requirement.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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
160 changes: 160 additions & 0 deletions src/iceberg/test/table_metadata_builder_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<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;

// 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
// ============================================================================
Expand Down
Loading