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.
 /// 
----------------------------------------------------------------------------

Reply via email to