Skip to content

Commit dc8a271

Browse files
committed
Add properties pending update support
1 parent 579ec0e commit dc8a271

File tree

10 files changed

+340
-6
lines changed

10 files changed

+340
-6
lines changed

src/iceberg/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ set(ICEBERG_SOURCES
6363
table_requirements.cc
6464
table_scan.cc
6565
table_update.cc
66+
update/update_properties.cc
6667
transform.cc
6768
transform_function.cc
6869
type.cc

src/iceberg/table.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "iceberg/table_metadata.h"
2929
#include "iceberg/table_properties.h"
3030
#include "iceberg/table_scan.h"
31+
#include "iceberg/update/update_properties.h"
3132
#include "iceberg/util/macros.h"
3233

3334
namespace iceberg {
@@ -114,6 +115,10 @@ std::unique_ptr<Transaction> Table::NewTransaction() const {
114115
throw NotImplemented("Table::NewTransaction is not implemented");
115116
}
116117

118+
std::unique_ptr<PropertiesUpdate> Table::UpdateProperties() {
119+
return std::make_unique<PropertiesUpdate>(this);
120+
}
121+
117122
const std::shared_ptr<FileIO>& Table::io() const { return io_; }
118123

119124
std::unique_ptr<TableScanBuilder> Table::NewScan() const {

src/iceberg/table.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232

3333
namespace iceberg {
3434

35+
class PropertiesUpdate;
36+
3537
/// \brief Represents an Iceberg table
3638
class ICEBERG_EXPORT Table {
3739
public:
@@ -115,10 +117,15 @@ class ICEBERG_EXPORT Table {
115117
/// \return a pointer to the new Transaction
116118
virtual std::unique_ptr<Transaction> NewTransaction() const;
117119

120+
/// \brief Create a pending update to modify table properties
121+
std::unique_ptr<PropertiesUpdate> UpdateProperties();
122+
118123
/// \brief Returns a FileIO to read and write table data and metadata files
119124
const std::shared_ptr<FileIO>& io() const;
120125

121126
private:
127+
friend class PropertiesUpdate;
128+
122129
const TableIdentifier identifier_;
123130
std::shared_ptr<TableMetadata> metadata_;
124131
std::string metadata_location_;

src/iceberg/table_metadata.cc

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,12 +476,32 @@ TableMetadataBuilder& TableMetadataBuilder::RemovePartitionStatistics(
476476

477477
TableMetadataBuilder& TableMetadataBuilder::SetProperties(
478478
const std::unordered_map<std::string, std::string>& updated) {
479-
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
479+
if (updated.empty()) {
480+
return *this;
481+
}
482+
483+
for (const auto& [key, value] : updated) {
484+
impl_->metadata.properties[key] = value;
485+
}
486+
487+
impl_->changes.push_back(std::make_unique<table::SetProperties>(updated));
488+
489+
return *this;
480490
}
481491

482492
TableMetadataBuilder& TableMetadataBuilder::RemoveProperties(
483493
const std::vector<std::string>& removed) {
484-
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
494+
if (removed.empty()) {
495+
return *this;
496+
}
497+
498+
for (const auto& key : removed) {
499+
impl_->metadata.properties.erase(key);
500+
}
501+
502+
impl_->changes.push_back(std::make_unique<table::RemoveProperties>(removed));
503+
504+
return *this;
485505
}
486506

487507
TableMetadataBuilder& TableMetadataBuilder::SetLocation(std::string_view location) {

src/iceberg/table_update.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,21 +182,21 @@ Status SetSnapshotRef::GenerateRequirements(TableUpdateContext& context) const {
182182
// SetProperties
183183

184184
void SetProperties::ApplyTo(TableMetadataBuilder& builder) const {
185-
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
185+
builder.SetProperties(updated_);
186186
}
187187

188188
Status SetProperties::GenerateRequirements(TableUpdateContext& context) const {
189-
return NotImplemented("SetTableProperties::GenerateRequirements not implemented");
189+
return {};
190190
}
191191

192192
// RemoveProperties
193193

194194
void RemoveProperties::ApplyTo(TableMetadataBuilder& builder) const {
195-
throw IcebergError(std::format("{} not implemented", __FUNCTION__));
195+
builder.RemoveProperties(removed_);
196196
}
197197

198198
Status RemoveProperties::GenerateRequirements(TableUpdateContext& context) const {
199-
return NotImplemented("RemoveTableProperties::GenerateRequirements not implemented");
199+
return {};
200200
}
201201

202202
// SetLocation

src/iceberg/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ add_iceberg_test(table_test
8787
table_metadata_builder_test.cc
8888
table_requirement_test.cc
8989
table_update_test.cc
90+
update_properties_test.cc
9091
test_common.cc)
9192

9293
add_iceberg_test(expression_test
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include "iceberg/update/update_properties.h"
21+
22+
#include <memory>
23+
#include <string>
24+
#include <unordered_map>
25+
26+
#include <gmock/gmock.h>
27+
#include <gtest/gtest.h>
28+
29+
#include "iceberg/file_format.h"
30+
#include "iceberg/partition_spec.h"
31+
#include "iceberg/snapshot.h"
32+
#include "iceberg/sort_order.h"
33+
#include "iceberg/table.h"
34+
#include "iceberg/table_metadata.h"
35+
#include "iceberg/table_properties.h"
36+
#include "iceberg/test/matchers.h"
37+
#include "iceberg/test/mock_catalog.h"
38+
39+
namespace iceberg {
40+
41+
namespace {
42+
43+
std::shared_ptr<TableMetadata> MakeBaseMetadata(
44+
std::unordered_map<std::string, std::string> properties) {
45+
auto metadata = std::make_shared<TableMetadata>();
46+
metadata->format_version = 2;
47+
metadata->table_uuid = "test-uuid";
48+
metadata->location = "s3://bucket/table";
49+
metadata->last_sequence_number = TableMetadata::kInitialSequenceNumber;
50+
metadata->last_updated_ms = TimePointMs{};
51+
metadata->last_column_id = 0;
52+
metadata->default_spec_id = PartitionSpec::kInitialSpecId;
53+
metadata->last_partition_id = 0;
54+
metadata->default_sort_order_id = SortOrder::kInitialSortOrderId;
55+
metadata->current_snapshot_id = Snapshot::kInvalidSnapshotId;
56+
metadata->next_row_id = TableMetadata::kInitialRowId;
57+
metadata->properties = std::move(properties);
58+
return metadata;
59+
}
60+
61+
TableIdentifier MakeIdentifier() { return TableIdentifier{Namespace{{"ns"}}, "tbl"}; }
62+
63+
} // namespace
64+
65+
using ::testing::_;
66+
using ::testing::ByMove;
67+
using ::testing::Return;
68+
69+
TEST(UpdatePropertiesTest, ApplyMergesUpdatesAndRemovals) {
70+
auto metadata =
71+
MakeBaseMetadata({{"foo", "bar"}, {"keep", "yes"}, {"format-version", "2"}});
72+
Table table(MakeIdentifier(), metadata, "loc", /*io=*/nullptr, /*catalog=*/nullptr);
73+
74+
auto updater = table.UpdateProperties();
75+
updater->Set("foo", "baz").Remove("keep").DefaultFormat(FileFormatType::kOrc);
76+
77+
auto applied = updater->Apply();
78+
ASSERT_THAT(applied, IsOk());
79+
80+
const auto& props = *applied;
81+
EXPECT_EQ(props.at("foo"), "baz");
82+
EXPECT_FALSE(props.contains("keep"));
83+
EXPECT_EQ(props.at(TableProperties::kDefaultFileFormat.key()), "orc");
84+
}
85+
86+
TEST(UpdatePropertiesTest, CommitUsesCatalogAndRefreshesTable) {
87+
auto catalog = std::make_shared<MockCatalog>();
88+
auto base_metadata = MakeBaseMetadata({{"foo", "bar"}});
89+
Table table(MakeIdentifier(), base_metadata, "loc", /*io=*/nullptr, catalog);
90+
91+
auto updated_metadata = MakeBaseMetadata({{"foo", "new"}}); // response metadata
92+
93+
EXPECT_CALL(*catalog, UpdateTable(table.name(), _, _))
94+
.WillOnce(Return(ByMove(Result<std::unique_ptr<Table>>{
95+
std::make_unique<Table>(table.name(), updated_metadata, "loc2", nullptr,
96+
catalog)})));
97+
98+
auto updater = table.UpdateProperties();
99+
updater->Set("foo", "new");
100+
101+
EXPECT_THAT(updater->Commit(), IsOk());
102+
EXPECT_EQ(table.properties().configs().at("foo"), "new");
103+
EXPECT_EQ(table.location(), "s3://bucket/table");
104+
}
105+
106+
} // namespace iceberg

src/iceberg/type_fwd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class Table;
9696
class TableProperties;
9797
class FileIO;
9898
class Transaction;
99+
class PropertiesUpdate;
99100
class Transform;
100101
class TransformFunction;
101102

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include "iceberg/update/update_properties.h"
21+
22+
#include <format>
23+
#include <vector>
24+
25+
#include "iceberg/catalog.h"
26+
#include "iceberg/exception.h"
27+
#include "iceberg/table.h"
28+
#include "iceberg/table_metadata.h"
29+
#include "iceberg/table_properties.h"
30+
#include "iceberg/table_requirement.h"
31+
#include "iceberg/table_update.h"
32+
#include "iceberg/util/macros.h"
33+
34+
namespace iceberg {
35+
36+
PropertiesUpdate::PropertiesUpdate(Table* table) : table_(table) {}
37+
38+
PropertiesUpdate& PropertiesUpdate::Set(std::string key, std::string value) {
39+
if (key.empty()) {
40+
throw IcebergError("Property key cannot be empty");
41+
}
42+
if (removals_.contains(key)) {
43+
throw IcebergError(std::format("Cannot remove and update the same key: {}", key));
44+
}
45+
46+
updates_[std::move(key)] = std::move(value);
47+
return *this;
48+
}
49+
50+
PropertiesUpdate& PropertiesUpdate::Remove(std::string key) {
51+
if (key.empty()) {
52+
throw IcebergError("Property key cannot be empty");
53+
}
54+
if (updates_.contains(key)) {
55+
throw IcebergError(std::format("Cannot remove and update the same key: {}", key));
56+
}
57+
58+
removals_.insert(std::move(key));
59+
return *this;
60+
}
61+
62+
PropertiesUpdate& PropertiesUpdate::DefaultFormat(FileFormatType format) {
63+
return Set(std::string(TableProperties::kDefaultFileFormat.key()),
64+
std::string(ToString(format)));
65+
}
66+
67+
Result<std::unordered_map<std::string, std::string>> PropertiesUpdate::Apply() {
68+
if (table_ == nullptr) {
69+
return InvalidArgument("Cannot apply updates on a null table");
70+
}
71+
72+
if (table_->catalog_) {
73+
if (auto status = table_->Refresh(); !status) {
74+
return std::unexpected(status.error());
75+
}
76+
}
77+
78+
std::unordered_map<std::string, std::string> new_properties =
79+
table_->properties().configs();
80+
81+
for (const auto& key : removals_) {
82+
new_properties.erase(key);
83+
}
84+
for (const auto& [key, value] : updates_) {
85+
new_properties[key] = value;
86+
}
87+
88+
return new_properties;
89+
}
90+
91+
Status PropertiesUpdate::Commit() {
92+
if (table_ == nullptr) {
93+
return InvalidArgument("Cannot commit updates on a null table");
94+
}
95+
96+
if (updates_.empty() && removals_.empty()) {
97+
return {};
98+
}
99+
100+
ICEBERG_ASSIGN_OR_RAISE(auto applied, Apply());
101+
(void)applied; // apply for validation
102+
103+
if (!table_->catalog_) {
104+
return NotSupported("Commit requires a catalog-backed table");
105+
}
106+
107+
std::vector<std::unique_ptr<TableRequirement>> requirements;
108+
std::vector<std::unique_ptr<TableUpdate>> updates;
109+
110+
if (!updates_.empty()) {
111+
updates.push_back(std::make_unique<table::SetProperties>(updates_));
112+
}
113+
if (!removals_.empty()) {
114+
std::vector<std::string> removed(removals_.begin(), removals_.end());
115+
updates.push_back(std::make_unique<table::RemoveProperties>(std::move(removed)));
116+
}
117+
118+
ICEBERG_ASSIGN_OR_RAISE(auto updated_table,
119+
table_->catalog_->UpdateTable(table_->name(), requirements,
120+
updates));
121+
122+
table_->metadata_ = std::move(updated_table->metadata_);
123+
table_->metadata_location_ = std::move(updated_table->metadata_location_);
124+
table_->io_ = std::move(updated_table->io_);
125+
table_->properties_ = std::move(updated_table->properties_);
126+
table_->metadata_cache_ = std::make_unique<TableMetadataCache>(table_->metadata_.get());
127+
128+
return {};
129+
}
130+
131+
} // namespace iceberg

0 commit comments

Comments
 (0)