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 f1596756 feat: implement update location (#508)
f1596756 is described below
commit f15967568f2f6205fb35890e21c88383e5ae31a7
Author: Feiyang Li <[email protected]>
AuthorDate: Fri Jan 16 20:54:29 2026 +0800
feat: implement update location (#508)
---
src/iceberg/CMakeLists.txt | 1 +
src/iceberg/meson.build | 1 +
src/iceberg/table.cc | 7 ++
src/iceberg/table.h | 5 ++
src/iceberg/test/CMakeLists.txt | 1 +
src/iceberg/test/update_location_test.cc | 135 +++++++++++++++++++++++++++++++
src/iceberg/transaction.cc | 13 +++
src/iceberg/transaction.h | 4 +
src/iceberg/type_fwd.h | 3 +-
src/iceberg/update/meson.build | 2 +
src/iceberg/update/pending_update.h | 1 +
src/iceberg/update/update_location.cc | 58 +++++++++++++
src/iceberg/update/update_location.h | 58 +++++++++++++
13 files changed, 288 insertions(+), 1 deletion(-)
diff --git a/src/iceberg/CMakeLists.txt b/src/iceberg/CMakeLists.txt
index 42442281..35c312f6 100644
--- a/src/iceberg/CMakeLists.txt
+++ b/src/iceberg/CMakeLists.txt
@@ -87,6 +87,7 @@ set(ICEBERG_SOURCES
update/expire_snapshots.cc
update/pending_update.cc
update/snapshot_update.cc
+ update/update_location.cc
update/update_partition_spec.cc
update/update_properties.cc
update/update_schema.cc
diff --git a/src/iceberg/meson.build b/src/iceberg/meson.build
index 87f508cd..317b4fa9 100644
--- a/src/iceberg/meson.build
+++ b/src/iceberg/meson.build
@@ -105,6 +105,7 @@ iceberg_sources = files(
'update/expire_snapshots.cc',
'update/pending_update.cc',
'update/snapshot_update.cc',
+ 'update/update_location.cc',
'update/update_partition_spec.cc',
'update/update_properties.cc',
'update/update_schema.cc',
diff --git a/src/iceberg/table.cc b/src/iceberg/table.cc
index c79ac53f..5c406deb 100644
--- a/src/iceberg/table.cc
+++ b/src/iceberg/table.cc
@@ -192,6 +192,13 @@ Result<std::shared_ptr<ExpireSnapshots>>
Table::NewExpireSnapshots() {
return transaction->NewExpireSnapshots();
}
+Result<std::shared_ptr<UpdateLocation>> Table::NewUpdateLocation() {
+ ICEBERG_ASSIGN_OR_RAISE(
+ auto transaction, Transaction::Make(shared_from_this(),
Transaction::Kind::kUpdate,
+ /*auto_commit=*/true));
+ return transaction->NewUpdateLocation();
+}
+
Result<std::shared_ptr<StagedTable>> StagedTable::Make(
TableIdentifier identifier, std::shared_ptr<TableMetadata> metadata,
std::string metadata_location, std::shared_ptr<FileIO> io,
diff --git a/src/iceberg/table.h b/src/iceberg/table.h
index cc948248..fd346e15 100644
--- a/src/iceberg/table.h
+++ b/src/iceberg/table.h
@@ -29,6 +29,7 @@
#include "iceberg/snapshot.h"
#include "iceberg/table_identifier.h"
#include "iceberg/type_fwd.h"
+#include "iceberg/update/update_location.h"
#include "iceberg/util/timepoint.h"
namespace iceberg {
@@ -151,6 +152,10 @@ class ICEBERG_EXPORT Table : public
std::enable_shared_from_this<Table> {
/// changes.
virtual Result<std::shared_ptr<ExpireSnapshots>> NewExpireSnapshots();
+ /// \brief Create a new UpdateLocation to update the table location and
commit the
+ /// changes.
+ virtual Result<std::shared_ptr<UpdateLocation>> NewUpdateLocation();
+
protected:
Table(TableIdentifier identifier, std::shared_ptr<TableMetadata> metadata,
std::string metadata_location, std::shared_ptr<FileIO> io,
diff --git a/src/iceberg/test/CMakeLists.txt b/src/iceberg/test/CMakeLists.txt
index 1f6ab552..364c690e 100644
--- a/src/iceberg/test/CMakeLists.txt
+++ b/src/iceberg/test/CMakeLists.txt
@@ -172,6 +172,7 @@ if(ICEBERG_BUILD_BUNDLE)
SOURCES
expire_snapshots_test.cc
transaction_test.cc
+ update_location_test.cc
update_partition_spec_test.cc
update_properties_test.cc
update_schema_test.cc
diff --git a/src/iceberg/test/update_location_test.cc
b/src/iceberg/test/update_location_test.cc
new file mode 100644
index 00000000..53b347b5
--- /dev/null
+++ b/src/iceberg/test/update_location_test.cc
@@ -0,0 +1,135 @@
+/*
+ * 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/update/update_location.h"
+
+#include <memory>
+#include <string>
+
+#include <arrow/filesystem/mockfs.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "iceberg/arrow/arrow_fs_file_io_internal.h"
+#include "iceberg/result.h"
+#include "iceberg/test/matchers.h"
+#include "iceberg/test/update_test_base.h"
+
+namespace iceberg {
+
+class UpdateLocationTest : public UpdateTestBase {};
+
+TEST_F(UpdateLocationTest, SetLocationSuccess) {
+ const std::string new_location = "/warehouse/new_location";
+
+ // Create metadata directory for the new location
+ auto arrow_fs =
std::dynamic_pointer_cast<::arrow::fs::internal::MockFileSystem>(
+ static_cast<arrow::ArrowFileSystemFileIO&>(*file_io_).fs());
+ ASSERT_TRUE(arrow_fs != nullptr);
+ ASSERT_TRUE(arrow_fs->CreateDir(new_location + "/metadata").ok());
+
+ ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateLocation());
+ update->SetLocation(new_location);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply());
+ EXPECT_EQ(result, new_location);
+
+ // Commit and verify the location was persisted
+ EXPECT_THAT(update->Commit(), IsOk());
+ ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_));
+ EXPECT_EQ(reloaded->location(), new_location);
+}
+
+TEST_F(UpdateLocationTest, SetLocationMultipleTimes) {
+ // Test that setting location multiple times uses the last value
+ const std::string final_location = "/warehouse/final_location";
+
+ // Create metadata directory for the new location
+ auto arrow_fs =
std::dynamic_pointer_cast<::arrow::fs::internal::MockFileSystem>(
+ static_cast<arrow::ArrowFileSystemFileIO&>(*file_io_).fs());
+ ASSERT_TRUE(arrow_fs != nullptr);
+ ASSERT_TRUE(arrow_fs->CreateDir(final_location + "/metadata").ok());
+
+ ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateLocation());
+ update->SetLocation("/warehouse/first_location")
+ .SetLocation("/warehouse/second_location")
+ .SetLocation(final_location);
+
+ ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply());
+ EXPECT_EQ(result, final_location);
+
+ // Commit and verify the final location was persisted
+ EXPECT_THAT(update->Commit(), IsOk());
+ ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_));
+ EXPECT_EQ(reloaded->location(), final_location);
+}
+
+TEST_F(UpdateLocationTest, SetEmptyLocation) {
+ ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateLocation());
+ update->SetLocation("");
+
+ auto result = update->Apply();
+ EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed));
+ EXPECT_THAT(result, HasErrorMessage("Location cannot be empty"));
+}
+
+TEST_F(UpdateLocationTest, ApplyWithoutSettingLocation) {
+ ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateLocation());
+
+ auto result = update->Apply();
+ EXPECT_THAT(result, IsError(ErrorKind::kInvalidArgument));
+ EXPECT_THAT(result, HasErrorMessage("Location must be set before applying"));
+}
+
+TEST_F(UpdateLocationTest, MultipleUpdatesSequentially) {
+ // Get arrow_fs for creating directories
+ auto arrow_fs =
std::dynamic_pointer_cast<::arrow::fs::internal::MockFileSystem>(
+ static_cast<arrow::ArrowFileSystemFileIO&>(*file_io_).fs());
+ ASSERT_TRUE(arrow_fs != nullptr);
+
+ // First update
+ const std::string first_location = "/warehouse/first";
+ ASSERT_TRUE(arrow_fs->CreateDir(first_location + "/metadata").ok());
+
+ ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateLocation());
+ update->SetLocation(first_location);
+ ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply());
+ EXPECT_EQ(result, first_location);
+ EXPECT_THAT(update->Commit(), IsOk());
+
+ // Reload and verify
+ ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_));
+ EXPECT_EQ(reloaded->location(), first_location);
+
+ // Second update
+ const std::string second_location = "/warehouse/second";
+ ASSERT_TRUE(arrow_fs->CreateDir(second_location + "/metadata").ok());
+
+ ICEBERG_UNWRAP_OR_FAIL(update, reloaded->NewUpdateLocation());
+ update->SetLocation(second_location);
+ ICEBERG_UNWRAP_OR_FAIL(result, update->Apply());
+ EXPECT_EQ(result, second_location);
+ EXPECT_THAT(update->Commit(), IsOk());
+
+ // Reload and verify
+ ICEBERG_UNWRAP_OR_FAIL(reloaded, catalog_->LoadTable(table_ident_));
+ EXPECT_EQ(reloaded->location(), second_location);
+}
+
+} // namespace iceberg
diff --git a/src/iceberg/transaction.cc b/src/iceberg/transaction.cc
index f763c567..10a87e65 100644
--- a/src/iceberg/transaction.cc
+++ b/src/iceberg/transaction.cc
@@ -33,6 +33,7 @@
#include "iceberg/update/expire_snapshots.h"
#include "iceberg/update/pending_update.h"
#include "iceberg/update/snapshot_update.h"
+#include "iceberg/update/update_location.h"
#include "iceberg/update/update_partition_spec.h"
#include "iceberg/update/update_properties.h"
#include "iceberg/update/update_schema.h"
@@ -183,6 +184,11 @@ Status Transaction::Apply(PendingUpdate& update) {
metadata_builder_->RemoveSchemas(std::move(result.schema_ids_to_remove));
}
} break;
+ case PendingUpdate::Kind::kUpdateLocation: {
+ auto& update_location = internal::checked_cast<UpdateLocation&>(update);
+ ICEBERG_ASSIGN_OR_RAISE(auto location, update_location.Apply());
+ metadata_builder_->SetLocation(location);
+ } break;
default:
return NotSupported("Unsupported pending update: {}",
static_cast<int32_t>(update.kind()));
@@ -280,4 +286,11 @@ Result<std::shared_ptr<ExpireSnapshots>>
Transaction::NewExpireSnapshots() {
return expire_snapshots;
}
+Result<std::shared_ptr<UpdateLocation>> Transaction::NewUpdateLocation() {
+ ICEBERG_ASSIGN_OR_RAISE(std::shared_ptr<UpdateLocation> update_location,
+ UpdateLocation::Make(shared_from_this()));
+ ICEBERG_RETURN_UNEXPECTED(AddUpdate(update_location));
+ return update_location;
+}
+
} // namespace iceberg
diff --git a/src/iceberg/transaction.h b/src/iceberg/transaction.h
index 057a27a9..7133a3b5 100644
--- a/src/iceberg/transaction.h
+++ b/src/iceberg/transaction.h
@@ -82,6 +82,10 @@ class ICEBERG_EXPORT Transaction : public
std::enable_shared_from_this<Transacti
/// changes.
Result<std::shared_ptr<ExpireSnapshots>> NewExpireSnapshots();
+ /// \brief Create a new UpdateLocation to update the table location and
commit the
+ /// changes.
+ Result<std::shared_ptr<UpdateLocation>> NewUpdateLocation();
+
private:
Transaction(std::shared_ptr<Table> table, Kind kind, bool auto_commit,
std::unique_ptr<TableMetadataBuilder> metadata_builder);
diff --git a/src/iceberg/type_fwd.h b/src/iceberg/type_fwd.h
index c8854031..251334c1 100644
--- a/src/iceberg/type_fwd.h
+++ b/src/iceberg/type_fwd.h
@@ -187,13 +187,14 @@ class TableUpdateContext;
class Transaction;
/// \brief Update family.
+class ExpireSnapshots;
class PendingUpdate;
class SnapshotUpdate;
+class UpdateLocation;
class UpdatePartitionSpec;
class UpdateProperties;
class UpdateSchema;
class UpdateSortOrder;
-class ExpireSnapshots;
///
----------------------------------------------------------------------------
/// TODO: Forward declarations below are not added yet.
diff --git a/src/iceberg/update/meson.build b/src/iceberg/update/meson.build
index 4238e022..3387fd11 100644
--- a/src/iceberg/update/meson.build
+++ b/src/iceberg/update/meson.build
@@ -17,8 +17,10 @@
install_headers(
[
+ 'expire_snapshots.h',
'pending_update.h',
'snapshot_update.h',
+ 'update_location.h',
'update_partition_spec.h',
'update_schema.h',
'update_sort_order.h',
diff --git a/src/iceberg/update/pending_update.h
b/src/iceberg/update/pending_update.h
index 8a8329ee..441d086a 100644
--- a/src/iceberg/update/pending_update.h
+++ b/src/iceberg/update/pending_update.h
@@ -43,6 +43,7 @@ class ICEBERG_EXPORT PendingUpdate : public ErrorCollector {
public:
enum class Kind : uint8_t {
kExpireSnapshots,
+ kUpdateLocation,
kUpdatePartitionSpec,
kUpdateProperties,
kUpdateSchema,
diff --git a/src/iceberg/update/update_location.cc
b/src/iceberg/update/update_location.cc
new file mode 100644
index 00000000..c82a138f
--- /dev/null
+++ b/src/iceberg/update/update_location.cc
@@ -0,0 +1,58 @@
+/*
+ * 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/update/update_location.h"
+
+#include <memory>
+#include <string>
+#include <string_view>
+
+#include "iceberg/result.h"
+#include "iceberg/transaction.h"
+#include "iceberg/util/macros.h"
+
+namespace iceberg {
+
+Result<std::shared_ptr<UpdateLocation>> UpdateLocation::Make(
+ std::shared_ptr<Transaction> transaction) {
+ ICEBERG_PRECHECK(transaction != nullptr,
+ "Cannot create UpdateLocation without a transaction");
+ return std::shared_ptr<UpdateLocation>(new
UpdateLocation(std::move(transaction)));
+}
+
+UpdateLocation::UpdateLocation(std::shared_ptr<Transaction> transaction)
+ : PendingUpdate(std::move(transaction)) {}
+
+UpdateLocation::~UpdateLocation() = default;
+
+UpdateLocation& UpdateLocation::SetLocation(std::string_view location) {
+ ICEBERG_BUILDER_CHECK(!location.empty(), "Location cannot be empty");
+ location_ = std::string(location);
+ return *this;
+}
+
+Result<std::string> UpdateLocation::Apply() {
+ ICEBERG_RETURN_UNEXPECTED(CheckErrors());
+ if (location_.empty()) {
+ return InvalidArgument("Location must be set before applying");
+ }
+ return location_;
+}
+
+} // namespace iceberg
diff --git a/src/iceberg/update/update_location.h
b/src/iceberg/update/update_location.h
new file mode 100644
index 00000000..891853e9
--- /dev/null
+++ b/src/iceberg/update/update_location.h
@@ -0,0 +1,58 @@
+/*
+ * 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
+
+#include <memory>
+#include <string_view>
+
+#include "iceberg/type_fwd.h"
+#include "iceberg/update/pending_update.h"
+
+/// \file iceberg/update/update_location.h
+/// \brief Updates the table location.
+
+namespace iceberg {
+
+/// \brief Updating table location with a new base location.
+class ICEBERG_EXPORT UpdateLocation : public PendingUpdate {
+ public:
+ static Result<std::shared_ptr<UpdateLocation>> Make(
+ std::shared_ptr<Transaction> transaction);
+
+ ~UpdateLocation() override;
+
+ /// \brief Sets the new location for the table.
+ ///
+ /// \param location The new table location
+ /// \return Reference to this for method chaining
+ UpdateLocation& SetLocation(std::string_view location);
+
+ Kind kind() const final { return Kind::kUpdateLocation; }
+
+ /// \brief Apply the pending changes and return the new location.
+ Result<std::string> Apply();
+
+ private:
+ explicit UpdateLocation(std::shared_ptr<Transaction> transaction);
+
+ std::string location_;
+};
+
+} // namespace iceberg