This is an automated email from the ASF dual-hosted git repository.
gangwu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new 2adb461 feat: add PendingUpdate interface for table changes (#334)
2adb461 is described below
commit 2adb461815a9d06e35b1955b2f0b0aa004cef560
Author: Xinli Shang <[email protected]>
AuthorDate: Tue Nov 25 18:14:08 2025 -0800
feat: add PendingUpdate interface for table changes (#334)
Add PendingUpdate<T> template class that provides a builder pattern API
for constructing and committing table metadata changes. This interface
follows the Java PendingUpdate design with C++ idioms:
- Apply() returns Result<T> with uncommitted changes for validation
- Commit() returns Status and atomically commits changes to the table
- Template parameter T allows type-safe results (Snapshot, Schema, etc.)
The interface is designed for future implementations like AppendFiles,
UpdateSchema, UpdateProperties, and other table update operations.
---
src/iceberg/pending_update.h | 94 +++++++++++++++++++++++++++++++
src/iceberg/result.h | 6 +-
src/iceberg/test/CMakeLists.txt | 7 ++-
src/iceberg/test/pending_update_test.cc | 99 +++++++++++++++++++++++++++++++++
src/iceberg/type_fwd.h | 4 ++
5 files changed, 205 insertions(+), 5 deletions(-)
diff --git a/src/iceberg/pending_update.h b/src/iceberg/pending_update.h
new file mode 100644
index 0000000..4c6fe09
--- /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<PendingUpdate> 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 <typename T>
+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<T> Apply() = 0;
+
+ protected:
+ PendingUpdateTyped() = default;
+};
+
+} // namespace iceberg
diff --git a/src/iceberg/result.h b/src/iceberg/result.h
index 99df372..743473b 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 0efc4e1..302789e 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 0000000..bc26304
--- /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 <gtest/gtest.h>
+
+#include "iceberg/result.h"
+#include "iceberg/test/matchers.h"
+
+namespace iceberg {
+
+// Mock implementation for testing the interface
+class MockSnapshot {};
+
+class MockPendingUpdate : public PendingUpdateTyped<MockSnapshot> {
+ public:
+ MockPendingUpdate() = default;
+
+ Result<MockSnapshot> 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<PendingUpdate> base_ptr =
std::make_unique<MockPendingUpdate>();
+ 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 8ae213a..81681eb 100644
--- a/src/iceberg/type_fwd.h
+++ b/src/iceberg/type_fwd.h
@@ -156,6 +156,10 @@ class TableRequirement;
class TableMetadataBuilder;
class TableUpdateContext;
+class PendingUpdate;
+template <typename T>
+class PendingUpdateTyped;
+
///
----------------------------------------------------------------------------
/// TODO: Forward declarations below are not added yet.
///
----------------------------------------------------------------------------