blob: dd0aa8f63ad97f5e3e37aabf78af2ac910984f10 [file] [log] [blame]
/*
* 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_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 {
namespace {
// Helper function to create base metadata for tests
std::unique_ptr<TableMetadata> CreateBaseMetadata(
const std::string& uuid = "test-uuid-1234") {
auto metadata = std::make_unique<TableMetadata>();
metadata->format_version = 2;
metadata->table_uuid = uuid;
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->current_schema_id = Schema::kInitialSchemaId;
metadata->default_spec_id = PartitionSpec::kInitialSpecId;
metadata->last_partition_id = 0;
metadata->current_snapshot_id = kInvalidSnapshotId;
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;
auto result = TableRequirements::ForCreateTable(updates);
ASSERT_THAT(result, IsOk());
auto& requirements = result.value();
ASSERT_EQ(requirements.size(), 1);
// Should have only AssertDoesNotExist requirement
auto* assert_does_not_exist =
dynamic_cast<table::AssertDoesNotExist*>(requirements[0].get());
EXPECT_NE(assert_does_not_exist, nullptr);
}
TEST(TableRequirementsTest, IsCreate) {
// Should have only AssertDoesNotExist requirement
std::vector<std::unique_ptr<TableRequirement>> requirements;
requirements.push_back(std::make_unique<table::AssertDoesNotExist>());
EXPECT_TRUE(TableRequirements::IsCreate(requirements));
// Not only have AssertDoesNotExist requirement
requirements.push_back(std::make_unique<table::AssertCurrentSchemaID>(0));
auto res = TableRequirements::IsCreate(requirements);
EXPECT_THAT(res, IsError(ErrorKind::kInvalidArgument));
EXPECT_THAT(res,
HasErrorMessage("Cannot have other requirements than AssertDoesNotExist"));
}
TEST(TableRequirementsTest, EmptyUpdatesForUpdateTable) {
auto metadata = CreateBaseMetadata();
std::vector<std::unique_ptr<TableUpdate>> updates;
auto result = TableRequirements::ForUpdateTable(*metadata, updates);
ASSERT_THAT(result, IsOk());
auto& requirements = result.value();
ASSERT_EQ(requirements.size(), 1);
// Should have only AssertUUID requirement
auto* assert_uuid = dynamic_cast<table::AssertUUID*>(requirements[0].get());
ASSERT_NE(assert_uuid, nullptr);
EXPECT_EQ(assert_uuid->uuid(), metadata->table_uuid);
}
TEST(TableRequirementsTest, EmptyUpdatesForReplaceTable) {
auto metadata = CreateBaseMetadata();
std::vector<std::unique_ptr<TableUpdate>> updates;
auto result = TableRequirements::ForReplaceTable(*metadata, updates);
ASSERT_THAT(result, IsOk());
auto& requirements = result.value();
ASSERT_EQ(requirements.size(), 1);
// Should have only AssertUUID requirement
auto* assert_uuid = dynamic_cast<table::AssertUUID*>(requirements[0].get());
ASSERT_NE(assert_uuid, nullptr);
EXPECT_EQ(assert_uuid->uuid(), metadata->table_uuid);
}
// Table Existence Tests
TEST(TableRequirementsTest, TableAlreadyExists) {
std::vector<std::unique_ptr<TableUpdate>> updates;
auto result = TableRequirements::ForCreateTable(updates);
ASSERT_THAT(result, IsOk());
auto& requirements = result.value();
ASSERT_EQ(requirements.size(), 1);
// Validate against existing metadata (should fail)
auto metadata = CreateBaseMetadata();
auto status = requirements[0]->Validate(metadata.get());
EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
EXPECT_THAT(status, HasErrorMessage("table already exists"));
}
TEST(TableRequirementsTest, TableDoesNotExist) {
std::vector<std::unique_ptr<TableUpdate>> updates;
auto result = TableRequirements::ForCreateTable(updates);
ASSERT_THAT(result, IsOk());
auto& requirements = result.value();
ASSERT_EQ(requirements.size(), 1);
// Validate against null metadata (should succeed)
auto status = requirements[0]->Validate(nullptr);
EXPECT_THAT(status, IsOk());
}
// AssignUUID Tests
TEST(TableRequirementsTest, AssignUUID) {
auto metadata = CreateBaseMetadata("original-uuid");
std::vector<std::unique_ptr<TableUpdate>> updates;
// Add multiple AssignUUID updates
updates.push_back(std::make_unique<table::AssignUUID>(metadata->table_uuid));
updates.push_back(std::make_unique<table::AssignUUID>("new-uuid-1"));
updates.push_back(std::make_unique<table::AssignUUID>("new-uuid-2"));
auto result = TableRequirements::ForUpdateTable(*metadata, updates);
ASSERT_THAT(result, IsOk());
auto& requirements = result.value();
// After deduplication: only 1 AssertUUID from ForUpdateTable
ASSERT_EQ(requirements.size(), 1);
// Should have AssertUUID requirement with original UUID
auto* assert_uuid = dynamic_cast<table::AssertUUID*>(requirements[0].get());
ASSERT_NE(assert_uuid, nullptr);
EXPECT_EQ(assert_uuid->uuid(), "original-uuid");
auto status = requirements[0]->Validate(metadata.get());
EXPECT_THAT(status, IsOk());
}
TEST(TableRequirementsTest, AssignUUIDFailure) {
auto metadata = CreateBaseMetadata("original-uuid");
std::vector<std::unique_ptr<TableUpdate>> updates;
updates.push_back(std::make_unique<table::AssignUUID>(metadata->table_uuid));
auto result = TableRequirements::ForUpdateTable(*metadata, updates);
ASSERT_THAT(result, IsOk());
auto& requirements = result.value();
// After deduplication: only 1 AssertUUID from ForUpdateTable
ASSERT_EQ(requirements.size(), 1);
// Create updated metadata with different UUID
auto updated = CreateBaseMetadata("different-uuid");
// Validate against updated metadata (should fail)
auto status = requirements[0]->Validate(updated.get());
EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
EXPECT_THAT(status, HasErrorMessage("UUID does not match"));
}
TEST(TableRequirementsTest, AssignUUIDForReplaceTable) {
auto metadata = CreateBaseMetadata("original-uuid");
std::vector<std::unique_ptr<TableUpdate>> updates;
// Add multiple AssignUUID updates
updates.push_back(std::make_unique<table::AssignUUID>(metadata->table_uuid));
updates.push_back(std::make_unique<table::AssignUUID>("new-uuid-1"));
updates.push_back(std::make_unique<table::AssignUUID>("new-uuid-2"));
auto result = TableRequirements::ForReplaceTable(*metadata, updates);
ASSERT_THAT(result, IsOk());
auto& requirements = result.value();
// After deduplication: only 1 AssertUUID from ForReplaceTable
ASSERT_EQ(requirements.size(), 1);
// Should have AssertUUID requirement
auto* assert_uuid = dynamic_cast<table::AssertUUID*>(requirements[0].get());
ASSERT_NE(assert_uuid, nullptr);
EXPECT_EQ(assert_uuid->uuid(), "original-uuid");
// Validate against base metadata (should succeed)
auto status = requirements[0]->Validate(metadata.get());
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::unordered_set<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::unordered_set<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::unordered_set<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::unordered_set<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;
ICEBERG_UNWRAP_OR_FAIL(auto ref1, SnapshotRef::MakeBranch(kSnapshotId));
updates.push_back(std::make_unique<table::SetSnapshotRef>(kRefName, *ref1));
ICEBERG_UNWRAP_OR_FAIL(auto ref2, SnapshotRef::MakeBranch(kSnapshotId + 1));
updates.push_back(std::make_unique<table::SetSnapshotRef>(kRefName, *ref2));
ICEBERG_UNWRAP_OR_FAIL(auto ref3, SnapshotRef::MakeBranch(kSnapshotId + 2));
updates.push_back(std::make_unique<table::SetSnapshotRef>(kRefName, *ref3));
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::unordered_set<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::unordered_set<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