Skip to content

Commit 9825bd3

Browse files
committed
feat: implement TableMetadataBuilder with AssignUUID
This commit implements the core TableMetadataBuilder pattern following the established design from the previous session. Pattern established: 1. Builder validates input and collects errors (no exceptions) 2. Changes are tracked for serialization 3. Requirements generated for optimistic concurrency 4. Build() validates metadata consistency before returning
1 parent 9fa0d15 commit 9825bd3

File tree

8 files changed

+458
-16
lines changed

8 files changed

+458
-16
lines changed

src/iceberg/table_metadata.cc

Lines changed: 105 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "iceberg/table_metadata.h"
2121

2222
#include <algorithm>
23+
#include <chrono>
2324
#include <format>
2425
#include <string>
2526

@@ -36,6 +37,7 @@
3637
#include "iceberg/table_update.h"
3738
#include "iceberg/util/gzip_internal.h"
3839
#include "iceberg/util/macros.h"
40+
#include "iceberg/util/uuid.h"
3941

4042
namespace iceberg {
4143

@@ -201,13 +203,46 @@ Status TableMetadataUtil::Write(FileIO& io, const std::string& location,
201203

202204
// TableMetadataBuilder implementation
203205

204-
struct TableMetadataBuilder::Impl {};
206+
struct TableMetadataBuilder::Impl {
207+
// Base metadata (nullptr for new tables)
208+
const TableMetadata* base;
209+
210+
// Working metadata copy
211+
TableMetadata metadata;
212+
213+
// Change tracking
214+
std::vector<std::unique_ptr<TableUpdate>> changes;
215+
216+
// Error collection (since methods return *this and cannot throw)
217+
std::vector<Status> errors;
218+
219+
// Metadata location tracking
220+
std::optional<std::string> metadata_location;
221+
std::optional<std::string> previous_metadata_location;
222+
223+
// Constructor for new table
224+
explicit Impl(int8_t format_version) : base(nullptr), metadata{} {
225+
metadata.format_version = format_version;
226+
metadata.last_sequence_number = TableMetadata::kInitialSequenceNumber;
227+
metadata.last_updated_ms = TimePointMs{std::chrono::milliseconds(0)};
228+
metadata.last_column_id = 0;
229+
metadata.default_spec_id = TableMetadata::kInitialSpecId;
230+
metadata.last_partition_id = 0;
231+
metadata.current_snapshot_id = TableMetadata::kInvalidSnapshotId;
232+
metadata.default_sort_order_id = TableMetadata::kInitialSortOrderId;
233+
metadata.next_row_id = TableMetadata::kInitialRowId;
234+
}
235+
236+
// Constructor from existing metadata
237+
explicit Impl(const TableMetadata* base_metadata)
238+
: base(base_metadata), metadata(*base_metadata) {}
239+
};
205240

206241
TableMetadataBuilder::TableMetadataBuilder(int8_t format_version)
207-
: impl_(std::make_unique<Impl>()) {}
242+
: impl_(std::make_unique<Impl>(format_version)) {}
208243

209244
TableMetadataBuilder::TableMetadataBuilder(const TableMetadata* base)
210-
: impl_(std::make_unique<Impl>()) {}
245+
: impl_(std::make_unique<Impl>(base)) {}
211246

212247
TableMetadataBuilder::~TableMetadataBuilder() = default;
213248

@@ -238,12 +273,35 @@ TableMetadataBuilder& TableMetadataBuilder::SetPreviousMetadataLocation(
238273
}
239274

240275
TableMetadataBuilder& TableMetadataBuilder::AssignUUID() {
241-
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
276+
if (impl_->metadata.table_uuid.empty()) {
277+
// Generate a random UUID
278+
return AssignUUID(Uuid::GenerateV4().ToString());
279+
}
280+
281+
return *this;
242282
}
243283

244284
TableMetadataBuilder& TableMetadataBuilder::AssignUUID(std::string_view uuid) {
245-
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
246-
;
285+
std::string uuid_str(uuid);
286+
287+
// Validation: UUID cannot be null or empty
288+
if (uuid_str.empty()) {
289+
impl_->errors.emplace_back(InvalidArgument("Cannot assign null or empty UUID"));
290+
return *this;
291+
}
292+
293+
// Check if UUID is already set to the same value (no-op)
294+
if (StringUtils::EqualsIgnoreCase(impl_->metadata.table_uuid, uuid_str)) {
295+
return *this;
296+
}
297+
298+
// Update the metadata
299+
impl_->metadata.table_uuid = uuid_str;
300+
301+
// Record the change
302+
impl_->changes.push_back(std::make_unique<table::AssignUUID>(uuid_str));
303+
304+
return *this;
247305
}
248306

249307
TableMetadataBuilder& TableMetadataBuilder::UpgradeFormatVersion(
@@ -378,11 +436,50 @@ TableMetadataBuilder& TableMetadataBuilder::RemoveEncryptionKey(std::string_view
378436
}
379437

380438
TableMetadataBuilder& TableMetadataBuilder::DiscardChanges() {
381-
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
439+
// Clear all changes and errors
440+
impl_->changes.clear();
441+
impl_->errors.clear();
442+
443+
// Reset metadata to base state
444+
if (impl_->base != nullptr) {
445+
impl_->metadata = *impl_->base;
446+
} else {
447+
// Reset to initial state for new table
448+
*impl_ = Impl(impl_->metadata.format_version);
449+
}
450+
451+
return *this;
382452
}
383453

384454
Result<std::unique_ptr<TableMetadata>> TableMetadataBuilder::Build() {
385-
return NotImplemented("TableMetadataBuilder::Build not implemented");
455+
// 1. Check for accumulated errors
456+
if (!impl_->errors.empty()) {
457+
std::string error_msg = "Failed to build TableMetadata due to validation errors:\n";
458+
for (const auto& error : impl_->errors) {
459+
error_msg += " - " + error.error().message + "\n";
460+
}
461+
return CommitFailed("{}", error_msg);
462+
}
463+
464+
// 2. Validate metadata consistency
465+
466+
// Validate UUID exists for format version > 1
467+
if (impl_->metadata.format_version > 1 && impl_->metadata.table_uuid.empty()) {
468+
return InvalidArgument("UUID is required for format version {}",
469+
impl_->metadata.format_version);
470+
}
471+
472+
// 3. Update last_updated_ms if there are changes
473+
if (!impl_->changes.empty() && impl_->base != nullptr) {
474+
impl_->metadata.last_updated_ms =
475+
TimePointMs{std::chrono::duration_cast<std::chrono::milliseconds>(
476+
std::chrono::system_clock::now().time_since_epoch())};
477+
}
478+
479+
// 4. Create and return the TableMetadata
480+
auto result = std::make_unique<TableMetadata>(std::move(impl_->metadata));
481+
482+
return result;
386483
}
387484

388485
} // namespace iceberg

src/iceberg/table_metadata.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ struct ICEBERG_EXPORT TableMetadata {
7373
static constexpr int64_t kInitialSequenceNumber = 0;
7474
static constexpr int64_t kInvalidSequenceNumber = -1;
7575
static constexpr int64_t kInitialRowId = 0;
76+
static constexpr int32_t kInitialSpecId = 0;
77+
static constexpr int32_t kInitialSortOrderId = 1;
78+
static constexpr int64_t kInvalidSnapshotId = -1;
7679

7780
/// An integer version number for the format
7881
int8_t format_version;

src/iceberg/table_requirement.cc

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,28 @@
2020
#include "iceberg/table_requirement.h"
2121

2222
#include "iceberg/table_metadata.h"
23+
#include "util/string_util.h"
2324

2425
namespace iceberg::table {
2526

2627
Status AssertDoesNotExist::Validate(const TableMetadata* base) const {
27-
return NotImplemented("AssertTableDoesNotExist::Validate not implemented");
28+
return NotImplemented("AssertDoesNotExist::Validate not implemented");
2829
}
2930

3031
Status AssertUUID::Validate(const TableMetadata* base) const {
31-
return NotImplemented("AssertTableUUID::Validate not implemented");
32+
// Validate that the table UUID matches the expected value
33+
34+
if (base == nullptr) {
35+
return CommitFailed("Requirement failed: current table metadata is missing");
36+
}
37+
38+
if (!StringUtils::EqualsIgnoreCase(base->table_uuid, uuid_)) {
39+
return CommitFailed(
40+
"Requirement failed: table UUID does not match (expected='{}', actual='{}')",
41+
uuid_, base->table_uuid);
42+
}
43+
44+
return {};
3245
}
3346

3447
Status AssertRefSnapshotID::Validate(const TableMetadata* base) const {

src/iceberg/table_requirements.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@
2626
namespace iceberg {
2727

2828
void TableUpdateContext::AddRequirement(std::unique_ptr<TableRequirement> requirement) {
29-
throw IcebergError("TableUpdateContext::AddRequirement not implemented");
29+
requirements_.emplace_back(std::move(requirement));
3030
}
3131

3232
Result<std::vector<std::unique_ptr<TableRequirement>>> TableUpdateContext::Build() {
33-
return NotImplemented("TableUpdateContext::Build not implemented");
33+
return std::move(requirements_);
3434
}
3535

3636
Result<std::vector<std::unique_ptr<TableRequirement>>> TableRequirements::ForCreateTable(

src/iceberg/table_update.cc

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,32 @@
2121

2222
#include "iceberg/exception.h"
2323
#include "iceberg/table_metadata.h"
24+
#include "iceberg/table_requirement.h"
2425
#include "iceberg/table_requirements.h"
2526

2627
namespace iceberg::table {
2728

2829
// AssignUUID
2930

3031
void AssignUUID::ApplyTo(TableMetadataBuilder& builder) const {
31-
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
32+
builder.AssignUUID(uuid_);
3233
}
3334

3435
Status AssignUUID::GenerateRequirements(TableUpdateContext& context) const {
35-
return NotImplemented("AssignTableUUID::GenerateRequirements not implemented");
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+
49+
return {};
3650
}
3751

3852
// UpgradeFormatVersion
@@ -42,8 +56,7 @@ void UpgradeFormatVersion::ApplyTo(TableMetadataBuilder& builder) const {
4256
}
4357

4458
Status UpgradeFormatVersion::GenerateRequirements(TableUpdateContext& context) const {
45-
return NotImplemented(
46-
"UpgradeTableFormatVersion::GenerateRequirements not implemented");
59+
return NotImplemented("UpgradeFormatVersion::GenerateRequirements not implemented");
4760
}
4861

4962
// AddSchema

src/iceberg/test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ add_iceberg_test(table_test
8484
table_test.cc
8585
schema_json_test.cc)
8686

87+
add_iceberg_test(table_metadata_builder_test SOURCES table_metadata_builder_test.cc)
88+
8789
add_iceberg_test(expression_test
8890
SOURCES
8991
expression_test.cc

src/iceberg/test/meson.build

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ iceberg_tests = {
8080
),
8181
},
8282
'roaring_test': {'sources': files('roaring_test.cc')},
83+
'table_metadata_builder_test': {
84+
'sources': files('table_metadata_builder_test.cc'),
85+
},
8386
}
8487

8588
if get_option('rest').enabled()

0 commit comments

Comments
 (0)