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 9c00bfae fix: correct errors in update properties implementation (#429)
9c00bfae is described below

commit 9c00bfaef9167d47748265497a92093cccef52b5
Author: Feiyang Li <[email protected]>
AuthorDate: Tue Dec 23 11:14:53 2025 +0800

    fix: correct errors in update properties implementation (#429)
---
 src/iceberg/test/update_properties_test.cc | 51 +++++++++++++++---------------
 src/iceberg/update/update_properties.cc    | 32 +++++++++++++++----
 src/iceberg/update/update_properties.h     | 10 +++---
 3 files changed, 56 insertions(+), 37 deletions(-)

diff --git a/src/iceberg/test/update_properties_test.cc 
b/src/iceberg/test/update_properties_test.cc
index 1084f08f..5350af42 100644
--- a/src/iceberg/test/update_properties_test.cc
+++ b/src/iceberg/test/update_properties_test.cc
@@ -19,7 +19,6 @@
 
 #include "iceberg/update/update_properties.h"
 
-#include "iceberg/table.h"
 #include "iceberg/table_update.h"
 #include "iceberg/test/matchers.h"
 #include "iceberg/test/update_test_base.h"
@@ -28,11 +27,6 @@ namespace iceberg {
 
 class UpdatePropertiesTest : public UpdateTestBase {};
 
-TEST_F(UpdatePropertiesTest, EmptyUpdates) {
-  ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateProperties());
-  EXPECT_THAT(update->Commit(), IsOk());
-}
-
 TEST_F(UpdatePropertiesTest, SetProperty) {
   ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateProperties());
   update->Set("key1", "value1").Set("key2", "value2");
@@ -43,7 +37,14 @@ TEST_F(UpdatePropertiesTest, SetProperty) {
 }
 
 TEST_F(UpdatePropertiesTest, RemoveProperty) {
-  ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateProperties());
+  // First, add properties to remove
+  ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateProperties());
+  setup_update->Set("key1", "value1").Set("key2", "value2");
+  EXPECT_THAT(setup_update->Commit(), IsOk());
+
+  // Reload and remove the properties
+  ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_));
+  ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateProperties());
   update->Remove("key1").Remove("key2");
 
   ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply());
@@ -69,6 +70,17 @@ TEST_F(UpdatePropertiesTest, RemoveThenSetSameKey) {
   EXPECT_THAT(result, HasErrorMessage("already marked for removal"));
 }
 
+TEST_F(UpdatePropertiesTest, SetAndRemoveDifferentKeys) {
+  ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateProperties());
+  update->Set("key1", "value1").Remove("key2");
+  EXPECT_THAT(update->Commit(), IsOk());
+
+  ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_));
+  const auto& props = reloaded->properties().configs();
+  EXPECT_EQ(props.at("key1"), "value1");
+  EXPECT_FALSE(props.contains("key2"));
+}
+
 TEST_F(UpdatePropertiesTest, UpgradeFormatVersionValid) {
   ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateProperties());
   update->Set("format-version", "2");
@@ -108,33 +120,20 @@ TEST_F(UpdatePropertiesTest, 
UpgradeFormatVersionUnsupported) {
 }
 
 TEST_F(UpdatePropertiesTest, CommitSuccess) {
+  ICEBERG_UNWRAP_OR_FAIL(auto empty_update, table_->NewUpdateProperties());
+  EXPECT_THAT(empty_update->Commit(), IsOk());
+
   ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateProperties());
   update->Set("new.property", "new.value");
+  update->Set("format-version", "3");
 
   EXPECT_THAT(update->Commit(), IsOk());
 
   ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_));
   const auto& props = reloaded->properties().configs();
   EXPECT_EQ(props.at("new.property"), "new.value");
-}
-
-TEST_F(UpdatePropertiesTest, FluentInterface) {
-  ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateProperties());
-  auto& ref = update->Set("key1", "value1").Remove("key2");
-
-  EXPECT_EQ(&ref, update.get());
-  EXPECT_THAT(update->Apply(), IsOk());
-}
-
-TEST_F(UpdatePropertiesTest, SetAndRemoveDifferentKeys) {
-  ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateProperties());
-  update->Set("key1", "value1").Remove("key2");
-  EXPECT_THAT(update->Commit(), IsOk());
-
-  ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_));
-  const auto& props = reloaded->properties().configs();
-  EXPECT_EQ(props.at("key1"), "value1");
-  EXPECT_FALSE(props.contains("key2"));
+  const auto& format_version = reloaded->metadata()->format_version;
+  EXPECT_EQ(format_version, 3);
 }
 
 }  // namespace iceberg
diff --git a/src/iceberg/update/update_properties.cc 
b/src/iceberg/update/update_properties.cc
index f8a1d85d..9cdd7b5a 100644
--- a/src/iceberg/update/update_properties.cc
+++ b/src/iceberg/update/update_properties.cc
@@ -56,7 +56,7 @@ UpdateProperties& UpdateProperties::Set(const std::string& 
key,
 
   if (!TableProperties::reserved_properties().contains(key) ||
       key == TableProperties::kFormatVersion.key()) {
-    updates_.emplace(key, value);
+    updates_.insert_or_assign(key, value);
   }
 
   return *this;
@@ -72,9 +72,21 @@ UpdateProperties& UpdateProperties::Remove(const 
std::string& key) {
 
 Result<PendingUpdate::ApplyResult> UpdateProperties::Apply() {
   ICEBERG_RETURN_UNEXPECTED(CheckErrors());
+  const auto& current_props = transaction_->current().properties.configs();
+  std::unordered_map<std::string, std::string> new_properties;
+  std::vector<std::string> removals;
+  for (const auto& [key, value] : current_props) {
+    if (!removals_.contains(key)) {
+      new_properties[key] = value;
+    }
+  }
+
+  for (const auto& [key, value] : updates_) {
+    new_properties[key] = value;
+  }
 
-  auto iter = updates_.find(TableProperties::kFormatVersion.key());
-  if (iter != updates_.end()) {
+  auto iter = new_properties.find(TableProperties::kFormatVersion.key());
+  if (iter != new_properties.end()) {
     int parsed_version = 0;
     const auto& val = iter->second;
     auto [ptr, ec] = std::from_chars(val.data(), val.data() + val.size(), 
parsed_version);
@@ -92,12 +104,12 @@ Result<PendingUpdate::ApplyResult> 
UpdateProperties::Apply() {
     }
     format_version_ = static_cast<int8_t>(parsed_version);
 
-    updates_.erase(iter);
+    updates_.erase(TableProperties::kFormatVersion.key());
   }
 
   if (auto schema = transaction_->current().Schema(); schema.has_value()) {
     ICEBERG_RETURN_UNEXPECTED(
-        MetricsConfig::VerifyReferencedColumns(updates_, *schema.value()));
+        MetricsConfig::VerifyReferencedColumns(new_properties, 
*schema.value()));
   }
 
   ApplyResult result;
@@ -105,8 +117,14 @@ Result<PendingUpdate::ApplyResult> 
UpdateProperties::Apply() {
     
result.updates.emplace_back(std::make_unique<table::SetProperties>(updates_));
   }
   if (!removals_.empty()) {
-    result.updates.emplace_back(std::make_unique<table::RemoveProperties>(
-        std::vector<std::string>{removals_.begin(), removals_.end()}));
+    for (const auto& key : removals_) {
+      if (current_props.contains(key)) {
+        removals.push_back(key);
+      }
+    }
+    if (!removals.empty()) {
+      
result.updates.emplace_back(std::make_unique<table::RemoveProperties>(removals));
+    }
   }
   if (format_version_.has_value()) {
     result.updates.emplace_back(
diff --git a/src/iceberg/update/update_properties.h 
b/src/iceberg/update/update_properties.h
index fc8f46f1..e0f578be 100644
--- a/src/iceberg/update/update_properties.h
+++ b/src/iceberg/update/update_properties.h
@@ -41,14 +41,16 @@ class ICEBERG_EXPORT UpdateProperties : public 
PendingUpdate {
 
   /// \brief Sets a property key to a specified value.
   ///
-  /// The key can not be marked for previous removal and reserved property 
keys will be
-  /// ignored.
+  /// The key must not have been previously marked for removal and reserved 
property keys
+  /// will be ignored.
+  ///
+  /// \param key The property key to set
+  /// \param value The property value to set
+  /// \return Reference to this UpdateProperties for chaining
   UpdateProperties& Set(const std::string& key, const std::string& value);
 
   /// \brief Marks a property for removal.
   ///
-  /// The key can not be already marked for removal.
-  ///
   /// \param key The property key to remove
   /// \return Reference to this UpdateProperties for chaining
   UpdateProperties& Remove(const std::string& key);

Reply via email to