Skip to content

Commit 4401ceb

Browse files
authored
feat: implement table requirements (#380)
1 parent aabf3d0 commit 4401ceb

File tree

6 files changed

+266
-19
lines changed

6 files changed

+266
-19
lines changed

src/iceberg/table_requirements.cc

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919

2020
#include "iceberg/table_requirements.h"
2121

22+
#include <memory>
23+
2224
#include "iceberg/table_metadata.h"
25+
#include "iceberg/table_requirement.h"
2326
#include "iceberg/table_update.h"
2427

2528
namespace iceberg {
@@ -34,19 +37,34 @@ Result<std::vector<std::unique_ptr<TableRequirement>>> TableUpdateContext::Build
3437

3538
Result<std::vector<std::unique_ptr<TableRequirement>>> TableRequirements::ForCreateTable(
3639
const std::vector<std::unique_ptr<TableUpdate>>& table_updates) {
37-
return NotImplemented("TableRequirements::ForCreateTable not implemented");
40+
TableUpdateContext context(nullptr, false);
41+
context.AddRequirement(std::make_unique<table::AssertDoesNotExist>());
42+
for (const auto& update : table_updates) {
43+
ICEBERG_RETURN_UNEXPECTED(update->GenerateRequirements(context));
44+
}
45+
return context.Build();
3846
}
3947

4048
Result<std::vector<std::unique_ptr<TableRequirement>>> TableRequirements::ForReplaceTable(
4149
const TableMetadata& base,
4250
const std::vector<std::unique_ptr<TableUpdate>>& table_updates) {
43-
return NotImplemented("TableRequirements::ForReplaceTable not implemented");
51+
TableUpdateContext context(&base, true);
52+
context.AddRequirement(std::make_unique<table::AssertUUID>(base.table_uuid));
53+
for (const auto& update : table_updates) {
54+
ICEBERG_RETURN_UNEXPECTED(update->GenerateRequirements(context));
55+
}
56+
return context.Build();
4457
}
4558

4659
Result<std::vector<std::unique_ptr<TableRequirement>>> TableRequirements::ForUpdateTable(
4760
const TableMetadata& base,
4861
const std::vector<std::unique_ptr<TableUpdate>>& table_updates) {
49-
return NotImplemented("TableRequirements::ForUpdateTable not implemented");
62+
TableUpdateContext context(&base, false);
63+
context.AddRequirement(std::make_unique<table::AssertUUID>(base.table_uuid));
64+
for (const auto& update : table_updates) {
65+
ICEBERG_RETURN_UNEXPECTED(update->GenerateRequirements(context));
66+
}
67+
return context.Build();
5068
}
5169

5270
} // namespace iceberg

src/iceberg/table_requirements.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,40 @@ class ICEBERG_EXPORT TableUpdateContext {
6868
/// \brief Build and return the list of requirements
6969
Result<std::vector<std::unique_ptr<TableRequirement>>> Build();
7070

71+
// Getters for deduplication flags
72+
bool added_last_assigned_field_id() const { return added_last_assigned_field_id_; }
73+
bool added_current_schema_id() const { return added_current_schema_id_; }
74+
bool added_last_assigned_partition_id() const {
75+
return added_last_assigned_partition_id_;
76+
}
77+
bool added_default_spec_id() const { return added_default_spec_id_; }
78+
bool added_default_sort_order_id() const { return added_default_sort_order_id_; }
79+
80+
// Setters for deduplication flags
81+
void set_added_last_assigned_field_id(bool value) {
82+
added_last_assigned_field_id_ = value;
83+
}
84+
void set_added_current_schema_id(bool value) { added_current_schema_id_ = value; }
85+
void set_added_last_assigned_partition_id(bool value) {
86+
added_last_assigned_partition_id_ = value;
87+
}
88+
void set_added_default_spec_id(bool value) { added_default_spec_id_ = value; }
89+
void set_added_default_sort_order_id(bool value) {
90+
added_default_sort_order_id_ = value;
91+
}
92+
7193
private:
7294
const TableMetadata* base_;
7395
const bool is_replace_;
7496

7597
std::vector<std::unique_ptr<TableRequirement>> requirements_;
98+
99+
// flags to avoid adding duplicate requirements
100+
bool added_last_assigned_field_id_ = false;
101+
bool added_current_schema_id_ = false;
102+
bool added_last_assigned_partition_id_ = false;
103+
bool added_default_spec_id_ = false;
104+
bool added_default_sort_order_id_ = false;
76105
};
77106

78107
/// \brief Factory class for generating table requirements

src/iceberg/table_update.cc

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,7 @@ void AssignUUID::ApplyTo(TableMetadataBuilder& builder) const {
3333
}
3434

3535
Status AssignUUID::GenerateRequirements(TableUpdateContext& context) const {
36-
// AssignUUID operation generates a requirement to assert the table's UUID
37-
// if a base metadata exists (i.e., this is an update operation)
38-
39-
const TableMetadata* base = context.base();
40-
41-
if (base != nullptr && !base->table_uuid.empty()) {
42-
// For table updates, assert that the current UUID matches what we expect
43-
context.AddRequirement(std::make_unique<AssertUUID>(base->table_uuid));
44-
}
45-
46-
// Note: For table creation (base == nullptr), no UUID requirement is needed
47-
// as the table doesn't exist yet
48-
36+
// AssignUUID does not generate additional requirements.
4937
return {};
5038
}
5139

src/iceberg/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ add_iceberg_test(table_test
8585
table_test.cc
8686
table_metadata_builder_test.cc
8787
table_requirement_test.cc
88+
table_requirements_test.cc
8889
table_update_test.cc)
8990

9091
add_iceberg_test(expression_test
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include "iceberg/table_requirements.h"
21+
22+
#include <memory>
23+
#include <string>
24+
#include <vector>
25+
26+
#include <gtest/gtest.h>
27+
28+
#include "iceberg/partition_spec.h"
29+
#include "iceberg/snapshot.h"
30+
#include "iceberg/sort_order.h"
31+
#include "iceberg/table_metadata.h"
32+
#include "iceberg/table_requirement.h"
33+
#include "iceberg/table_update.h"
34+
#include "iceberg/test/matchers.h"
35+
36+
namespace iceberg {
37+
38+
namespace {
39+
40+
// Helper function to create base metadata for tests
41+
std::unique_ptr<TableMetadata> CreateBaseMetadata(
42+
const std::string& uuid = "test-uuid-1234") {
43+
auto metadata = std::make_unique<TableMetadata>();
44+
metadata->format_version = 2;
45+
metadata->table_uuid = uuid;
46+
metadata->location = "s3://bucket/test";
47+
metadata->last_sequence_number = 0;
48+
metadata->last_updated_ms = TimePointMs{std::chrono::milliseconds(1000)};
49+
metadata->last_column_id = 0;
50+
metadata->default_spec_id = PartitionSpec::kInitialSpecId;
51+
metadata->last_partition_id = 0;
52+
metadata->current_snapshot_id = Snapshot::kInvalidSnapshotId;
53+
metadata->default_sort_order_id = SortOrder::kInitialSortOrderId;
54+
metadata->next_row_id = TableMetadata::kInitialRowId;
55+
return metadata;
56+
}
57+
58+
} // namespace
59+
60+
TEST(TableRequirementsTest, EmptyUpdatesForCreateTable) {
61+
std::vector<std::unique_ptr<TableUpdate>> updates;
62+
63+
auto result = TableRequirements::ForCreateTable(updates);
64+
ASSERT_THAT(result, IsOk());
65+
66+
auto& requirements = result.value();
67+
ASSERT_EQ(requirements.size(), 1);
68+
69+
// Should have only AssertDoesNotExist requirement
70+
auto* assert_does_not_exist =
71+
dynamic_cast<table::AssertDoesNotExist*>(requirements[0].get());
72+
EXPECT_NE(assert_does_not_exist, nullptr);
73+
}
74+
75+
TEST(TableRequirementsTest, EmptyUpdatesForUpdateTable) {
76+
auto metadata = CreateBaseMetadata();
77+
std::vector<std::unique_ptr<TableUpdate>> updates;
78+
79+
auto result = TableRequirements::ForUpdateTable(*metadata, updates);
80+
ASSERT_THAT(result, IsOk());
81+
82+
auto& requirements = result.value();
83+
ASSERT_EQ(requirements.size(), 1);
84+
85+
// Should have only AssertUUID requirement
86+
auto* assert_uuid = dynamic_cast<table::AssertUUID*>(requirements[0].get());
87+
ASSERT_NE(assert_uuid, nullptr);
88+
EXPECT_EQ(assert_uuid->uuid(), metadata->table_uuid);
89+
}
90+
91+
TEST(TableRequirementsTest, EmptyUpdatesForReplaceTable) {
92+
auto metadata = CreateBaseMetadata();
93+
std::vector<std::unique_ptr<TableUpdate>> updates;
94+
95+
auto result = TableRequirements::ForReplaceTable(*metadata, updates);
96+
ASSERT_THAT(result, IsOk());
97+
98+
auto& requirements = result.value();
99+
ASSERT_EQ(requirements.size(), 1);
100+
101+
// Should have only AssertUUID requirement
102+
auto* assert_uuid = dynamic_cast<table::AssertUUID*>(requirements[0].get());
103+
ASSERT_NE(assert_uuid, nullptr);
104+
EXPECT_EQ(assert_uuid->uuid(), metadata->table_uuid);
105+
}
106+
107+
TEST(TableRequirementsTest, TableAlreadyExists) {
108+
std::vector<std::unique_ptr<TableUpdate>> updates;
109+
110+
auto result = TableRequirements::ForCreateTable(updates);
111+
ASSERT_THAT(result, IsOk());
112+
113+
auto& requirements = result.value();
114+
ASSERT_EQ(requirements.size(), 1);
115+
116+
// Validate against existing metadata (should fail)
117+
auto metadata = CreateBaseMetadata();
118+
auto status = requirements[0]->Validate(metadata.get());
119+
EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
120+
EXPECT_THAT(status, HasErrorMessage("table already exists"));
121+
}
122+
123+
TEST(TableRequirementsTest, TableDoesNotExist) {
124+
std::vector<std::unique_ptr<TableUpdate>> updates;
125+
126+
auto result = TableRequirements::ForCreateTable(updates);
127+
ASSERT_THAT(result, IsOk());
128+
129+
auto& requirements = result.value();
130+
ASSERT_EQ(requirements.size(), 1);
131+
132+
// Validate against null metadata (should succeed)
133+
auto status = requirements[0]->Validate(nullptr);
134+
EXPECT_THAT(status, IsOk());
135+
}
136+
137+
// Test for AssignUUID update
138+
TEST(TableRequirementsTest, AssignUUID) {
139+
auto metadata = CreateBaseMetadata("original-uuid");
140+
std::vector<std::unique_ptr<TableUpdate>> updates;
141+
142+
// Add multiple AssignUUID updates
143+
updates.push_back(std::make_unique<table::AssignUUID>(metadata->table_uuid));
144+
updates.push_back(std::make_unique<table::AssignUUID>("new-uuid-1"));
145+
updates.push_back(std::make_unique<table::AssignUUID>("new-uuid-2"));
146+
147+
auto result = TableRequirements::ForUpdateTable(*metadata, updates);
148+
ASSERT_THAT(result, IsOk());
149+
150+
auto& requirements = result.value();
151+
// After deduplication: only 1 AssertUUID from ForUpdateTable
152+
ASSERT_EQ(requirements.size(), 1);
153+
154+
// Should have AssertUUID requirement with original UUID
155+
auto* assert_uuid = dynamic_cast<table::AssertUUID*>(requirements[0].get());
156+
ASSERT_NE(assert_uuid, nullptr);
157+
EXPECT_EQ(assert_uuid->uuid(), "original-uuid");
158+
159+
auto status = requirements[0]->Validate(metadata.get());
160+
EXPECT_THAT(status, IsOk());
161+
}
162+
163+
TEST(TableRequirementsTest, AssignUUIDFailure) {
164+
auto metadata = CreateBaseMetadata("original-uuid");
165+
std::vector<std::unique_ptr<TableUpdate>> updates;
166+
updates.push_back(std::make_unique<table::AssignUUID>(metadata->table_uuid));
167+
168+
auto result = TableRequirements::ForUpdateTable(*metadata, updates);
169+
ASSERT_THAT(result, IsOk());
170+
171+
auto& requirements = result.value();
172+
// After deduplication: only 1 AssertUUID from ForUpdateTable
173+
ASSERT_EQ(requirements.size(), 1);
174+
175+
// Create updated metadata with different UUID
176+
auto updated = CreateBaseMetadata("different-uuid");
177+
178+
// Validate against updated metadata (should fail)
179+
auto status = requirements[0]->Validate(updated.get());
180+
EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
181+
EXPECT_THAT(status, HasErrorMessage("UUID does not match"));
182+
}
183+
184+
TEST(TableRequirementsTest, AssignUUIDForReplaceTable) {
185+
auto metadata = CreateBaseMetadata("original-uuid");
186+
std::vector<std::unique_ptr<TableUpdate>> updates;
187+
188+
// Add multiple AssignUUID updates
189+
updates.push_back(std::make_unique<table::AssignUUID>(metadata->table_uuid));
190+
updates.push_back(std::make_unique<table::AssignUUID>("new-uuid-1"));
191+
updates.push_back(std::make_unique<table::AssignUUID>("new-uuid-2"));
192+
193+
auto result = TableRequirements::ForReplaceTable(*metadata, updates);
194+
ASSERT_THAT(result, IsOk());
195+
196+
auto& requirements = result.value();
197+
// After deduplication: only 1 AssertUUID from ForReplaceTable
198+
ASSERT_EQ(requirements.size(), 1);
199+
200+
// Should have AssertUUID requirement
201+
auto* assert_uuid = dynamic_cast<table::AssertUUID*>(requirements[0].get());
202+
ASSERT_NE(assert_uuid, nullptr);
203+
EXPECT_EQ(assert_uuid->uuid(), "original-uuid");
204+
205+
// Validate against base metadata (should succeed)
206+
auto status = requirements[0]->Validate(metadata.get());
207+
EXPECT_THAT(status, IsOk());
208+
}
209+
210+
} // namespace iceberg

src/iceberg/test/table_update_test.cc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,15 @@ std::unique_ptr<TableMetadata> CreateBaseMetadata() {
7171
TEST(TableUpdateTest, AssignUUIDGenerateRequirements) {
7272
table::AssignUUID update("new-uuid");
7373

74-
// New table - no requirements
74+
// New table - no requirements (AssignUUID doesn't generate requirements)
7575
auto new_table_reqs = GenerateRequirements(update, nullptr);
7676
EXPECT_TRUE(new_table_reqs.empty());
7777

78-
// Existing table - should generate AssertUUID requirement
78+
// Existing table - AssignUUID doesn't generate requirements anymore
79+
// The UUID assertion is added by ForUpdateTable/ForReplaceTable methods
7980
auto base = CreateBaseMetadata();
8081
auto existing_table_reqs = GenerateRequirements(update, base.get());
81-
EXPECT_EQ(existing_table_reqs.size(), 1);
82+
EXPECT_TRUE(existing_table_reqs.empty());
8283

8384
// Existing table with empty UUID - no requirements
8485
base->table_uuid = "";

0 commit comments

Comments
 (0)