diff --git a/src/iceberg/table_requirements.cc b/src/iceberg/table_requirements.cc index aae874ecd..fcb744aad 100644 --- a/src/iceberg/table_requirements.cc +++ b/src/iceberg/table_requirements.cc @@ -19,7 +19,6 @@ #include "iceberg/table_requirements.h" -#include "iceberg/exception.h" #include "iceberg/table_metadata.h" #include "iceberg/table_update.h" diff --git a/src/iceberg/test/CMakeLists.txt b/src/iceberg/test/CMakeLists.txt index 302789e07..f0c6027e7 100644 --- a/src/iceberg/test/CMakeLists.txt +++ b/src/iceberg/test/CMakeLists.txt @@ -82,8 +82,10 @@ add_iceberg_test(table_test json_internal_test.cc pending_update_test.cc schema_json_test.cc - table_metadata_builder_test.cc table_test.cc + table_metadata_builder_test.cc + table_requirement_test.cc + table_update_test.cc test_common.cc) add_iceberg_test(expression_test diff --git a/src/iceberg/test/meson.build b/src/iceberg/test/meson.build index 6fbe82dfd..4e5d30ad1 100644 --- a/src/iceberg/test/meson.build +++ b/src/iceberg/test/meson.build @@ -48,7 +48,9 @@ iceberg_tests = { 'json_internal_test.cc', 'schema_json_test.cc', 'table_metadata_builder_test.cc', + 'table_requirement_test.cc', 'table_test.cc', + 'table_update_test.cc', 'test_common.cc', ), }, diff --git a/src/iceberg/test/table_metadata_builder_test.cc b/src/iceberg/test/table_metadata_builder_test.cc index 9b9804819..f49a60dae 100644 --- a/src/iceberg/test/table_metadata_builder_test.cc +++ b/src/iceberg/test/table_metadata_builder_test.cc @@ -20,62 +20,40 @@ #include #include -#include #include #include "iceberg/partition_spec.h" +#include "iceberg/snapshot.h" #include "iceberg/sort_order.h" #include "iceberg/table_metadata.h" -#include "iceberg/table_requirement.h" -#include "iceberg/table_requirements.h" #include "iceberg/table_update.h" #include "iceberg/test/matchers.h" namespace iceberg { -// Helper functions to reduce test boilerplate namespace { -// Generate requirements and return them -std::vector> GenerateRequirements( - const TableUpdate& update, const TableMetadata* base) { - TableUpdateContext context(base, /*is_replace=*/false); - EXPECT_THAT(update.GenerateRequirements(context), IsOk()); - - auto requirements = context.Build(); - EXPECT_THAT(requirements, IsOk()); - return std::move(requirements.value()); +// Helper function to create base metadata for tests +std::unique_ptr CreateBaseMetadata() { + auto metadata = std::make_unique(); + metadata->format_version = 2; + metadata->table_uuid = "test-uuid-1234"; + 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->default_spec_id = PartitionSpec::kInitialSpecId; + metadata->last_partition_id = 0; + metadata->current_snapshot_id = Snapshot::kInvalidSnapshotId; + metadata->default_sort_order_id = SortOrder::kInitialSortOrderId; + metadata->next_row_id = TableMetadata::kInitialRowId; + return metadata; } } // namespace -// Test fixture for TableMetadataBuilder tests -class TableMetadataBuilderTest : public ::testing::Test { - protected: - void SetUp() override { - // Create a base metadata for update tests - base_metadata_ = std::make_unique(); - base_metadata_->format_version = 2; - base_metadata_->table_uuid = "test-uuid-1234"; - base_metadata_->location = "s3://bucket/test"; - base_metadata_->last_sequence_number = 0; - base_metadata_->last_updated_ms = TimePointMs{std::chrono::milliseconds(1000)}; - base_metadata_->last_column_id = 0; - base_metadata_->default_spec_id = PartitionSpec::kInitialSpecId; - base_metadata_->last_partition_id = 0; - base_metadata_->current_snapshot_id = Snapshot::kInvalidSnapshotId; - base_metadata_->default_sort_order_id = SortOrder::kInitialSortOrderId; - base_metadata_->next_row_id = TableMetadata::kInitialRowId; - } - - std::unique_ptr base_metadata_; -}; - -// ============================================================================ -// TableMetadataBuilder - Basic Construction Tests -// ============================================================================ - -TEST_F(TableMetadataBuilderTest, BuildFromEmpty) { +// test construction of TableMetadataBuilder +TEST(TableMetadataBuilderTest, BuildFromEmpty) { auto builder = TableMetadataBuilder::BuildFromEmpty(2); ASSERT_NE(builder, nullptr); @@ -91,8 +69,9 @@ TEST_F(TableMetadataBuilderTest, BuildFromEmpty) { EXPECT_EQ(metadata->current_snapshot_id, Snapshot::kInvalidSnapshotId); } -TEST_F(TableMetadataBuilderTest, BuildFromExisting) { - auto builder = TableMetadataBuilder::BuildFrom(base_metadata_.get()); +TEST(TableMetadataBuilderTest, BuildFromExisting) { + auto base = CreateBaseMetadata(); + auto builder = TableMetadataBuilder::BuildFrom(base.get()); ASSERT_NE(builder, nullptr); ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build()); @@ -103,345 +82,59 @@ TEST_F(TableMetadataBuilderTest, BuildFromExisting) { EXPECT_EQ(metadata->location, "s3://bucket/test"); } -// ============================================================================ -// TableMetadataBuilder - AssignUUID Tests -// ============================================================================ - -TEST_F(TableMetadataBuilderTest, AssignUUIDForNewTable) { +// Test AssignUUID method +TEST(TableMetadataBuilderTest, AssignUUID) { + // Assign UUID for new table auto builder = TableMetadataBuilder::BuildFromEmpty(2); builder->AssignUUID("new-uuid-5678"); - ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build()); EXPECT_EQ(metadata->table_uuid, "new-uuid-5678"); -} -TEST_F(TableMetadataBuilderTest, AssignUUIDAndUpdateExisting) { - auto builder = TableMetadataBuilder::BuildFrom(base_metadata_.get()); + // Update existing table's UUID + auto base = CreateBaseMetadata(); + builder = TableMetadataBuilder::BuildFrom(base.get()); builder->AssignUUID("updated-uuid-9999"); - - ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build()); + ICEBERG_UNWRAP_OR_FAIL(metadata, builder->Build()); EXPECT_EQ(metadata->table_uuid, "updated-uuid-9999"); -} -TEST_F(TableMetadataBuilderTest, AssignUUIDWithEmptyUUID) { - auto builder = TableMetadataBuilder::BuildFromEmpty(2); + // Empty UUID should fail + builder = TableMetadataBuilder::BuildFromEmpty(2); builder->AssignUUID(""); - ASSERT_THAT(builder->Build(), HasErrorMessage("Cannot assign empty UUID")); -} -TEST_F(TableMetadataBuilderTest, AssignUUIDWithSameUUID) { - auto builder = TableMetadataBuilder::BuildFrom(base_metadata_.get()); - builder->AssignUUID("test-uuid-1234"); // Same UUID - - ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build()); + // Assign same UUID (no-op) + base = CreateBaseMetadata(); + builder = TableMetadataBuilder::BuildFrom(base.get()); + builder->AssignUUID("test-uuid-1234"); + ICEBERG_UNWRAP_OR_FAIL(metadata, builder->Build()); EXPECT_EQ(metadata->table_uuid, "test-uuid-1234"); -} -TEST_F(TableMetadataBuilderTest, AssignUUIDWithAutoGenerate) { - auto builder = TableMetadataBuilder::BuildFromEmpty(2); - builder->AssignUUID(); // Auto-generate - - ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build()); + // Auto-generate UUID + builder = TableMetadataBuilder::BuildFromEmpty(2); + builder->AssignUUID(); + ICEBERG_UNWRAP_OR_FAIL(metadata, builder->Build()); EXPECT_FALSE(metadata->table_uuid.empty()); -} -TEST_F(TableMetadataBuilderTest, AssignUUIDAndCaseInsensitiveComparison) { - base_metadata_->table_uuid = "TEST-UUID-ABCD"; - auto builder = TableMetadataBuilder::BuildFrom(base_metadata_.get()); + // Case insensitive comparison + base = CreateBaseMetadata(); + base->table_uuid = "TEST-UUID-ABCD"; + builder = TableMetadataBuilder::BuildFrom(base.get()); builder->AssignUUID("test-uuid-abcd"); // Different case - should be no-op - - ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build()); + ICEBERG_UNWRAP_OR_FAIL(metadata, builder->Build()); EXPECT_EQ(metadata->table_uuid, "TEST-UUID-ABCD"); // Original case preserved } -// ============================================================================ -// TableUpdate - ApplyTo Tests -// ============================================================================ - -TEST_F(TableMetadataBuilderTest, TableUpdateWithAssignUUID) { +// Test applying TableUpdate to builder +TEST(TableMetadataBuilderTest, ApplyUpdate) { + // Apply AssignUUID update auto builder = TableMetadataBuilder::BuildFromEmpty(2); - table::AssignUUID update("apply-uuid"); update.ApplyTo(*builder); + // TODO(Li Feiyang): Add more update and `apply` once other build methods are + // implemented ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build()); EXPECT_EQ(metadata->table_uuid, "apply-uuid"); } -// ============================================================================ -// TableUpdate - GenerateRequirements Tests -// ============================================================================ - -TEST_F(TableMetadataBuilderTest, - TableUpdateWithAssignUUIDAndGenerateRequirementsForNewTable) { - table::AssignUUID update("new-uuid"); - - auto requirements = GenerateRequirements(update, nullptr); - EXPECT_TRUE(requirements.empty()); // No requirements for new table -} - -TEST_F(TableMetadataBuilderTest, - TableUpdateWithAssignUUIDAndGenerateRequirementsForExistingTable) { - table::AssignUUID update("new-uuid"); - - auto requirements = GenerateRequirements(update, base_metadata_.get()); - EXPECT_EQ(requirements.size(), 1); // Should generate AssertUUID requirement -} - -TEST_F(TableMetadataBuilderTest, - TableUpdateWithAssignUUIDAndGenerateRequirementsWithEmptyUUID) { - base_metadata_->table_uuid = ""; - table::AssignUUID update("new-uuid"); - - auto requirements = GenerateRequirements(update, base_metadata_.get()); - EXPECT_TRUE(requirements.empty()); // No requirement when base has no UUID -} - -// ============================================================================ -// TableRequirement - Validate Tests -// ============================================================================ - -TEST_F(TableMetadataBuilderTest, TableRequirementAssertUUIDSuccess) { - table::AssertUUID requirement("test-uuid-1234"); - - ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk()); -} - -TEST_F(TableMetadataBuilderTest, TableRequirementAssertUUIDMismatch) { - table::AssertUUID requirement("wrong-uuid"); - - auto status = requirement.Validate(base_metadata_.get()); - EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); - EXPECT_THAT(status, HasErrorMessage("UUID does not match")); -} - -TEST_F(TableMetadataBuilderTest, TableRequirementAssertUUIDNullBase) { - table::AssertUUID requirement("any-uuid"); - - auto status = requirement.Validate(nullptr); - EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); - EXPECT_THAT(status, HasErrorMessage("metadata is missing")); -} - -TEST_F(TableMetadataBuilderTest, TableRequirementAssertUUIDCaseInsensitive) { - base_metadata_->table_uuid = "TEST-UUID-1234"; - table::AssertUUID requirement("test-uuid-1234"); - - ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk()); -} - -TEST_F(TableMetadataBuilderTest, TableRequirementAssertCurrentSchemaIDSuccess) { - base_metadata_->current_schema_id = 5; - table::AssertCurrentSchemaID requirement(5); - - ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk()); -} - -TEST_F(TableMetadataBuilderTest, TableRequirementAssertCurrentSchemaIDMismatch) { - base_metadata_->current_schema_id = 5; - table::AssertCurrentSchemaID requirement(10); - - auto status = requirement.Validate(base_metadata_.get()); - EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); - EXPECT_THAT(status, HasErrorMessage("schema ID does not match")); -} - -TEST_F(TableMetadataBuilderTest, TableRequirementAssertCurrentSchemaIDNullBase) { - table::AssertCurrentSchemaID requirement(5); - - auto status = requirement.Validate(nullptr); - EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); - EXPECT_THAT(status, HasErrorMessage("metadata is missing")); -} - -TEST_F(TableMetadataBuilderTest, TableRequirementAssertCurrentSchemaIDNotSet) { - base_metadata_->current_schema_id = std::nullopt; - table::AssertCurrentSchemaID requirement(5); - - auto status = requirement.Validate(base_metadata_.get()); - EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); - 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; - - 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 -// ============================================================================ - -TEST_F(TableMetadataBuilderTest, IntegrationCreateTableWithUUID) { - auto builder = TableMetadataBuilder::BuildFromEmpty(2); - builder->AssignUUID("integration-test-uuid"); - - ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build()); - EXPECT_EQ(metadata->table_uuid, "integration-test-uuid"); - EXPECT_EQ(metadata->format_version, 2); -} - -TEST_F(TableMetadataBuilderTest, IntegrationOptimisticConcurrencyControl) { - table::AssignUUID update("new-uuid"); - - // Generate and validate requirements - auto requirements = GenerateRequirements(update, base_metadata_.get()); - for (const auto& req : requirements) { - auto val_status = req->Validate(base_metadata_.get()); - ASSERT_THAT(val_status, IsOk()) << "Requirement validation failed"; - } - - // Apply update and build - auto builder = TableMetadataBuilder::BuildFrom(base_metadata_.get()); - update.ApplyTo(*builder); - - ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build()); - ASSERT_NE(metadata, nullptr); -} - } // namespace iceberg diff --git a/src/iceberg/test/table_requirement_test.cc b/src/iceberg/test/table_requirement_test.cc new file mode 100644 index 000000000..24af87dfd --- /dev/null +++ b/src/iceberg/test/table_requirement_test.cc @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "iceberg/table_requirement.h" + +#include +#include + +#include + +#include "iceberg/snapshot.h" +#include "iceberg/table_metadata.h" +#include "iceberg/test/matchers.h" + +namespace iceberg { + +TEST(TableRequirementTest, AssertUUID) { + auto base = std::make_unique(); + base->table_uuid = "test-uuid-1234"; + + // Success - UUID matches + table::AssertUUID requirement("test-uuid-1234"); + ASSERT_THAT(requirement.Validate(base.get()), IsOk()); + + // UUID mismatch + table::AssertUUID wrong_uuid("wrong-uuid"); + auto status = wrong_uuid.Validate(base.get()); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("UUID does not match")); + + // Null base metadata + table::AssertUUID any_uuid("any-uuid"); + status = any_uuid.Validate(nullptr); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("metadata is missing")); + + // Case insensitive UUID comparison + base->table_uuid = "TEST-UUID-1234"; + table::AssertUUID lowercase_uuid("test-uuid-1234"); + ASSERT_THAT(lowercase_uuid.Validate(base.get()), IsOk()); +} + +TEST(TableRequirementTest, AssertCurrentSchemaID) { + auto base = std::make_unique(); + base->current_schema_id = 5; + + // Success - schema ID matches + table::AssertCurrentSchemaID requirement(5); + ASSERT_THAT(requirement.Validate(base.get()), IsOk()); + + // Schema ID mismatch + table::AssertCurrentSchemaID wrong_id(10); + auto status = wrong_id.Validate(base.get()); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("schema ID does not match")); + + // Null base metadata + table::AssertCurrentSchemaID req_for_null(5); + status = req_for_null.Validate(nullptr); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("metadata is missing")); + + // Schema ID not set + base->current_schema_id = std::nullopt; + table::AssertCurrentSchemaID req_for_nullopt(5); + status = req_for_nullopt.Validate(base.get()); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("schema ID is not set")); +} + +TEST(TableRequirementTest, AssertDoesNotExist) { + // Success - table does not exist (null metadata) + table::AssertDoesNotExist requirement; + ASSERT_THAT(requirement.Validate(nullptr), IsOk()); + + // Table already exists + auto base = std::make_unique(); + auto status = requirement.Validate(base.get()); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("table already exists")); +} + +TEST(TableRequirementTest, AssertRefSnapshotID) { + auto base = std::make_unique(); + auto ref = std::make_shared(); + ref->snapshot_id = 100; + ref->retention = SnapshotRef::Branch{}; + base->refs["main"] = ref; + + // Success - ref snapshot ID matches + table::AssertRefSnapshotID requirement("main", 100); + ASSERT_THAT(requirement.Validate(base.get()), IsOk()); + + // Snapshot ID mismatch + table::AssertRefSnapshotID wrong_id("main", 200); + auto status = wrong_id.Validate(base.get()); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("has changed")); + + // Ref missing + table::AssertRefSnapshotID missing_ref("missing-ref", 100); + status = missing_ref.Validate(base.get()); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("is missing")); + + // Ref should not exist and doesn't (nullopt snapshot ID) + table::AssertRefSnapshotID nonexistent("nonexistent", std::nullopt); + ASSERT_THAT(nonexistent.Validate(base.get()), IsOk()); + + // Ref should not exist but does (nullopt snapshot ID but ref exists) + table::AssertRefSnapshotID exists_but_shouldnt("main", std::nullopt); + status = exists_but_shouldnt.Validate(base.get()); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("created concurrently")); +} + +TEST(TableRequirementTest, AssertLastAssignedFieldId) { + auto base = std::make_unique(); + base->last_column_id = 10; + + // Success - field ID matches + table::AssertLastAssignedFieldId requirement(10); + ASSERT_THAT(requirement.Validate(base.get()), IsOk()); + + // Field ID mismatch + table::AssertLastAssignedFieldId wrong_id(15); + auto status = wrong_id.Validate(base.get()); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("last assigned field ID does not match")); + + // Null base metadata (should succeed) + table::AssertLastAssignedFieldId req_for_null(10); + EXPECT_THAT(req_for_null.Validate(nullptr), IsOk()); +} + +TEST(TableRequirementTest, AssertLastAssignedPartitionId) { + auto base = std::make_unique(); + base->last_partition_id = 5; + + // Success - partition ID matches + table::AssertLastAssignedPartitionId requirement(5); + ASSERT_THAT(requirement.Validate(base.get()), IsOk()); + + // Partition ID mismatch + table::AssertLastAssignedPartitionId wrong_id(8); + auto status = wrong_id.Validate(base.get()); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("last assigned partition ID does not match")); + + // Null base metadata + table::AssertLastAssignedPartitionId req_for_null(5); + status = req_for_null.Validate(nullptr); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("metadata is missing")); +} + +TEST(TableRequirementTest, AssertDefaultSpecID) { + auto base = std::make_unique(); + base->default_spec_id = 3; + + // Success - spec ID matches + table::AssertDefaultSpecID requirement(3); + ASSERT_THAT(requirement.Validate(base.get()), IsOk()); + + // Spec ID mismatch + table::AssertDefaultSpecID wrong_id(7); + auto status = wrong_id.Validate(base.get()); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("spec changed")); +} + +TEST(TableRequirementTest, AssertDefaultSortOrderID) { + auto base = std::make_unique(); + base->default_sort_order_id = 2; + + // Success - sort order ID matches + table::AssertDefaultSortOrderID requirement(2); + ASSERT_THAT(requirement.Validate(base.get()), IsOk()); + + // Sort order ID mismatch + table::AssertDefaultSortOrderID wrong_id(4); + auto status = wrong_id.Validate(base.get()); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("sort order changed")); +} + +} // namespace iceberg diff --git a/src/iceberg/test/table_update_test.cc b/src/iceberg/test/table_update_test.cc new file mode 100644 index 000000000..9607db59f --- /dev/null +++ b/src/iceberg/test/table_update_test.cc @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "iceberg/table_update.h" + +#include +#include +#include + +#include + +#include "iceberg/partition_spec.h" +#include "iceberg/snapshot.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" + +namespace iceberg { + +namespace { + +// Helper function to generate requirements +std::vector> GenerateRequirements( + const TableUpdate& update, const TableMetadata* base) { + TableUpdateContext context(base, /*is_replace=*/false); + EXPECT_THAT(update.GenerateRequirements(context), IsOk()); + + auto requirements = context.Build(); + EXPECT_THAT(requirements, IsOk()); + return std::move(requirements.value()); +} + +// Helper function to create base metadata for tests +std::unique_ptr CreateBaseMetadata() { + auto metadata = std::make_unique(); + metadata->format_version = 2; + metadata->table_uuid = "test-uuid-1234"; + 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->default_spec_id = PartitionSpec::kInitialSpecId; + metadata->last_partition_id = 0; + metadata->current_snapshot_id = Snapshot::kInvalidSnapshotId; + metadata->default_sort_order_id = SortOrder::kInitialSortOrderId; + metadata->next_row_id = TableMetadata::kInitialRowId; + return metadata; +} + +} // namespace + +// Test GenerateRequirements for AssignUUID update +TEST(TableUpdateTest, AssignUUIDGenerateRequirements) { + table::AssignUUID update("new-uuid"); + + // New table - no requirements + auto new_table_reqs = GenerateRequirements(update, nullptr); + EXPECT_TRUE(new_table_reqs.empty()); + + // Existing table - should generate AssertUUID requirement + auto base = CreateBaseMetadata(); + auto existing_table_reqs = GenerateRequirements(update, base.get()); + EXPECT_EQ(existing_table_reqs.size(), 1); + + // Existing table with empty UUID - no requirements + base->table_uuid = ""; + auto empty_uuid_reqs = GenerateRequirements(update, base.get()); + EXPECT_TRUE(empty_uuid_reqs.empty()); +} + +} // namespace iceberg