diff --git a/src/iceberg/pending_update.h b/src/iceberg/pending_update.h new file mode 100644 index 000000000..4c6fe095f --- /dev/null +++ b/src/iceberg/pending_update.h @@ -0,0 +1,94 @@ +/* + * 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. + */ + +#pragma once + +/// \file iceberg/pending_update.h +/// API for table changes using builder pattern + +#include "iceberg/iceberg_export.h" +#include "iceberg/result.h" +#include "iceberg/type_fwd.h" + +namespace iceberg { + +/// \brief Base class for table metadata changes using builder pattern +/// +/// This base class allows storing different types of PendingUpdate operations +/// in the same collection (e.g., in Transaction). It provides the common Commit() +/// interface that all updates share. +/// +/// This matches the Java Iceberg pattern where BaseTransaction stores a +/// List without type parameters. +class ICEBERG_EXPORT PendingUpdate { + public: + virtual ~PendingUpdate() = default; + + /// \brief Apply and commit the pending changes to the table + /// + /// Changes are committed by calling the underlying table's commit operation. + /// + /// Once the commit is successful, the updated table will be refreshed. + /// + /// \return Status::OK if the commit was successful, or an error: + /// - ValidationFailed: if update cannot be applied to current metadata + /// - CommitFailed: if update cannot be committed due to conflicts + /// - CommitStateUnknown: if commit success state is unknown + virtual Status Commit() = 0; + + // Non-copyable, movable + PendingUpdate(const PendingUpdate&) = delete; + PendingUpdate& operator=(const PendingUpdate&) = delete; + PendingUpdate(PendingUpdate&&) noexcept = default; + PendingUpdate& operator=(PendingUpdate&&) noexcept = default; + + protected: + PendingUpdate() = default; +}; + +/// \brief Template class for type-safe table metadata changes using builder pattern +/// +/// PendingUpdateTyped extends PendingUpdate with a type-safe Apply() method that +/// returns the specific result type for each operation. Subclasses implement +/// specific types of table updates such as schema changes, property updates, or +/// snapshot-producing operations like appends and deletes. +/// +/// Apply() can be used to validate and inspect the uncommitted changes before +/// committing. Commit() applies the changes and commits them to the table. +/// +/// \tparam T The type of result returned by Apply() +template +class ICEBERG_EXPORT PendingUpdateTyped : public PendingUpdate { + public: + ~PendingUpdateTyped() override = default; + + /// \brief Apply the pending changes and return the uncommitted result + /// + /// This does not result in a permanent update. + /// + /// \return the uncommitted changes that would be committed, or an error: + /// - ValidationFailed: if pending changes cannot be applied + /// - InvalidArgument: if pending changes are conflicting + virtual Result Apply() = 0; + + protected: + PendingUpdateTyped() = default; +}; + +} // namespace iceberg diff --git a/src/iceberg/result.h b/src/iceberg/result.h index 99df37247..743473b10 100644 --- a/src/iceberg/result.h +++ b/src/iceberg/result.h @@ -37,9 +37,9 @@ enum class ErrorKind { kInvalidArgument, kInvalidArrowData, kInvalidExpression, - kInvalidSchema, kInvalidManifest, kInvalidManifestList, + kInvalidSchema, kIOError, kJsonParseError, kNoSuchNamespace, @@ -49,6 +49,7 @@ enum class ErrorKind { kNotImplemented, kNotSupported, kUnknownError, + kValidationFailed, }; /// \brief Error with a kind and a message. @@ -86,9 +87,9 @@ DEFINE_ERROR_FUNCTION(Invalid) DEFINE_ERROR_FUNCTION(InvalidArgument) DEFINE_ERROR_FUNCTION(InvalidArrowData) DEFINE_ERROR_FUNCTION(InvalidExpression) -DEFINE_ERROR_FUNCTION(InvalidSchema) DEFINE_ERROR_FUNCTION(InvalidManifest) DEFINE_ERROR_FUNCTION(InvalidManifestList) +DEFINE_ERROR_FUNCTION(InvalidSchema) DEFINE_ERROR_FUNCTION(IOError) DEFINE_ERROR_FUNCTION(JsonParseError) DEFINE_ERROR_FUNCTION(NoSuchNamespace) @@ -98,6 +99,7 @@ DEFINE_ERROR_FUNCTION(NotFound) DEFINE_ERROR_FUNCTION(NotImplemented) DEFINE_ERROR_FUNCTION(NotSupported) DEFINE_ERROR_FUNCTION(UnknownError) +DEFINE_ERROR_FUNCTION(ValidationFailed) #undef DEFINE_ERROR_FUNCTION diff --git a/src/iceberg/test/CMakeLists.txt b/src/iceberg/test/CMakeLists.txt index d82fe17b8..1c6cf7cfe 100644 --- a/src/iceberg/test/CMakeLists.txt +++ b/src/iceberg/test/CMakeLists.txt @@ -79,11 +79,12 @@ add_iceberg_test(schema_test add_iceberg_test(table_test SOURCES - test_common.cc json_internal_test.cc - table_test.cc + pending_update_test.cc schema_json_test.cc - table_metadata_builder_test.cc) + table_metadata_builder_test.cc + table_test.cc + test_common.cc) add_iceberg_test(expression_test SOURCES diff --git a/src/iceberg/test/pending_update_test.cc b/src/iceberg/test/pending_update_test.cc new file mode 100644 index 000000000..bc26304f7 --- /dev/null +++ b/src/iceberg/test/pending_update_test.cc @@ -0,0 +1,99 @@ +/* + * 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/pending_update.h" + +#include + +#include "iceberg/result.h" +#include "iceberg/test/matchers.h" + +namespace iceberg { + +// Mock implementation for testing the interface +class MockSnapshot {}; + +class MockPendingUpdate : public PendingUpdateTyped { + public: + MockPendingUpdate() = default; + + Result Apply() override { + if (should_fail_) { + return ValidationFailed("Mock validation failed"); + } + apply_called_ = true; + return MockSnapshot{}; + } + + Status Commit() override { + if (should_fail_commit_) { + return CommitFailed("Mock commit failed"); + } + commit_called_ = true; + return {}; + } + + void SetShouldFail(bool fail) { should_fail_ = fail; } + void SetShouldFailCommit(bool fail) { should_fail_commit_ = fail; } + bool ApplyCalled() const { return apply_called_; } + bool CommitCalled() const { return commit_called_; } + + private: + bool should_fail_ = false; + bool should_fail_commit_ = false; + bool apply_called_ = false; + bool commit_called_ = false; +}; + +TEST(PendingUpdateTest, ApplySuccess) { + MockPendingUpdate update; + auto result = update.Apply(); + EXPECT_THAT(result, IsOk()); +} + +TEST(PendingUpdateTest, ApplyValidationFailed) { + MockPendingUpdate update; + update.SetShouldFail(true); + auto result = update.Apply(); + EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); + EXPECT_THAT(result, HasErrorMessage("Mock validation failed")); +} + +TEST(PendingUpdateTest, CommitSuccess) { + MockPendingUpdate update; + auto status = update.Commit(); + EXPECT_THAT(status, IsOk()); + EXPECT_TRUE(update.CommitCalled()); +} + +TEST(PendingUpdateTest, CommitFailed) { + MockPendingUpdate update; + update.SetShouldFailCommit(true); + auto status = update.Commit(); + EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed)); + EXPECT_THAT(status, HasErrorMessage("Mock commit failed")); +} + +TEST(PendingUpdateTest, BaseClassPolymorphism) { + std::unique_ptr base_ptr = std::make_unique(); + auto status = base_ptr->Commit(); + EXPECT_THAT(status, IsOk()); +} + +} // namespace iceberg diff --git a/src/iceberg/type_fwd.h b/src/iceberg/type_fwd.h index 79b43f5fd..363f4c0be 100644 --- a/src/iceberg/type_fwd.h +++ b/src/iceberg/type_fwd.h @@ -154,6 +154,10 @@ class TableRequirement; class TableMetadataBuilder; class TableUpdateContext; +class PendingUpdate; +template +class PendingUpdateTyped; + /// ---------------------------------------------------------------------------- /// TODO: Forward declarations below are not added yet. /// ----------------------------------------------------------------------------