Skip to content

Commit b856413

Browse files
committed
refactor: allocate table labels only when present
Clarify label lifecycle and cover refresh, empty transactions, and REST load/create/register in both planning modes.
1 parent 9793879 commit b856413

4 files changed

Lines changed: 143 additions & 19 deletions

File tree

‎src/iceberg/table.cc‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ Table::Table(TableIdentifier identifier, std::shared_ptr<TableMetadata> metadata
9393
catalog_(std::move(catalog)),
9494
reporter_(std::move(reporter)),
9595
metadata_cache_(std::make_unique<TableMetadataCache>(metadata_.get())),
96-
labels_(std::move(labels)) {}
96+
labels_(labels.empty() ? nullptr
97+
: std::make_unique<const Labels>(std::move(labels))) {}
9798

9899
const std::string& Table::uuid() const { return metadata_->table_uuid; }
99100

@@ -167,7 +168,13 @@ const std::shared_ptr<Catalog>& Table::catalog() const { return catalog_; }
167168

168169
const std::shared_ptr<MetricsReporter>& Table::reporter() const { return reporter_; }
169170

170-
const Labels& Table::labels() const { return labels_; }
171+
const Labels& Table::labels() const {
172+
if (labels_) {
173+
return *labels_;
174+
}
175+
static const Labels kEmpty;
176+
return kEmpty;
177+
}
171178

172179
Result<std::unique_ptr<LocationProvider>> Table::location_provider() const {
173180
return LocationProvider::Make(metadata_->location, metadata_->properties);

‎src/iceberg/table.h‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,8 @@ class ICEBERG_EXPORT Table : public std::enable_shared_from_this<Table> {
134134

135135
/// \brief Returns the catalog-provided labels for this table.
136136
///
137-
/// Labels come only from the catalog's load, create or register response and are fixed
138-
/// for the life of this table: Refresh() does not update them, and a table returned by
139-
/// Transaction::Commit() has none. Load the table again to get current labels.
137+
/// Labels are supplied at construction and are not updated by Refresh().
138+
/// Reload the table to obtain current catalog labels.
140139
const Labels& labels() const;
141140

142141
/// \brief Returns a LocationProvider for this table
@@ -235,7 +234,7 @@ class ICEBERG_EXPORT Table : public std::enable_shared_from_this<Table> {
235234
std::shared_ptr<Catalog> catalog_;
236235
std::shared_ptr<MetricsReporter> reporter_;
237236
std::unique_ptr<class TableMetadataCache> metadata_cache_;
238-
const Labels labels_;
237+
const std::unique_ptr<const Labels> labels_;
239238
};
240239

241240
/// \brief A table created by stage-create and not yet committed.

‎src/iceberg/test/rest_catalog_unit_test.cc‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
#include "iceberg/catalog/rest/types.h"
4040
#include "iceberg/file_io.h"
4141
#include "iceberg/file_io_registry.h"
42+
#include "iceberg/labels.h"
43+
#include "iceberg/schema.h"
4244
#include "iceberg/storage_credential.h"
4345
#include "iceberg/table.h"
4446
#include "iceberg/table_identifier.h"
@@ -214,6 +216,76 @@ class RestCatalogLoadTableTest : public ::testing::Test {
214216
std::unordered_set<Endpoint> no_plan_endpoint_set_;
215217
};
216218

219+
class RestCatalogLabelsTest : public RestCatalogLoadTableTest,
220+
public ::testing::WithParamInterface<bool> {
221+
protected:
222+
std::string LabelsResponse() const {
223+
auto response = nlohmann::json::parse(kLoadTableDefaultResponse);
224+
response["config"]["scan-planning-mode"] = GetParam() ? "server" : "client";
225+
response["labels"] = nlohmann::json::parse(
226+
R"({"object-labels":{"owner":"team-a"},"fields":[{"field-id":1,"labels":{"classification":"pii"}}]})");
227+
return response.dump();
228+
}
229+
230+
Result<std::shared_ptr<Catalog>> MakeLabelsCatalog() {
231+
auto endpoints = all_plan_endpoints_;
232+
endpoints.insert(Endpoint::CreateTable());
233+
endpoints.insert(Endpoint::RegisterTable());
234+
ICEBERG_ASSIGN_OR_RAISE(auto catalog, MakeCatalog(endpoints));
235+
return catalog->AsCatalog();
236+
}
237+
238+
void CheckLabels(const std::shared_ptr<Table>& table) const {
239+
ASSERT_NE(table, nullptr);
240+
EXPECT_EQ(table->labels(), labels_);
241+
EXPECT_EQ(std::dynamic_pointer_cast<RestTable>(table) != nullptr, GetParam());
242+
}
243+
244+
const Labels labels_{
245+
.object_labels = {{"owner", "team-a"}},
246+
.fields = {{.field_id = 1, .labels = {{"classification", "pii"}}}}};
247+
};
248+
249+
TEST_P(RestCatalogLabelsTest, LoadExposesLabels) {
250+
EXPECT_CALL(*mock_client_, Get(::testing::EndsWith("/tables/my_table"), _, _, _, _))
251+
.WillOnce(Return(HttpResponse(200, LabelsResponse())));
252+
ICEBERG_UNWRAP_OR_FAIL(auto catalog, MakeLabelsCatalog());
253+
ICEBERG_UNWRAP_OR_FAIL(auto table, catalog->LoadTable(identifier_));
254+
255+
CheckLabels(table);
256+
}
257+
258+
TEST_P(RestCatalogLabelsTest, CreateExposesLabels) {
259+
EXPECT_CALL(*mock_client_,
260+
Post(::testing::EndsWith("/namespaces/default/tables"), _, _, _, _))
261+
.WillOnce(Return(HttpResponse(200, LabelsResponse())));
262+
ICEBERG_UNWRAP_OR_FAIL(auto catalog, MakeLabelsCatalog());
263+
auto schema = std::make_shared<Schema>(
264+
std::vector<SchemaField>{SchemaField::MakeRequired(1, "id", int32())}, 1);
265+
ICEBERG_UNWRAP_OR_FAIL(auto table,
266+
catalog->CreateTable(identifier_, schema, /*spec=*/nullptr,
267+
/*order=*/nullptr, /*location=*/"",
268+
/*properties=*/{}));
269+
270+
CheckLabels(table);
271+
}
272+
273+
TEST_P(RestCatalogLabelsTest, RegisterExposesLabels) {
274+
EXPECT_CALL(*mock_client_,
275+
Post(::testing::EndsWith("/namespaces/default/register"), _, _, _, _))
276+
.WillOnce(Return(HttpResponse(200, LabelsResponse())));
277+
ICEBERG_UNWRAP_OR_FAIL(auto catalog, MakeLabelsCatalog());
278+
ICEBERG_UNWRAP_OR_FAIL(
279+
auto table, catalog->RegisterTable(identifier_, "s3://bucket/metadata/v1.json"));
280+
281+
CheckLabels(table);
282+
}
283+
284+
INSTANTIATE_TEST_SUITE_P(PlanningModes, RestCatalogLabelsTest, ::testing::Bool(),
285+
[](const ::testing::TestParamInfo<bool>& info) {
286+
return info.param ? "Server" : "Client";
287+
});
288+
217289
TEST_F(RestCatalogLoadTableTest, InitializationPropagatesClientFactoryErrors) {
218290
auto config = RestCatalogProperties::FromMap(
219291
{{"uri", "http://test-server"}, {"io-impl", "rest-catalog-unit-test-noop"}});

‎src/iceberg/test/table_test.cc‎

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "iceberg/test/matchers.h"
3030
#include "iceberg/test/mock_catalog.h"
3131
#include "iceberg/test/mock_io.h"
32+
#include "iceberg/transaction.h"
3233

3334
namespace iceberg {
3435

@@ -175,27 +176,72 @@ class TableLabelsTest : public ::testing::Test {
175176
std::shared_ptr<TableMetadata> metadata_;
176177
};
177178

178-
TEST_F(TableLabelsTest, ExposesProvidedLabels) {
179+
TEST_F(TableLabelsTest, EmptyLabelsShareFallback) {
179180
ICEBERG_UNWRAP_OR_FAIL(
180-
auto table, Table::Make(ident_, metadata_, "s3://bucket/meta.json", io_, catalog_,
181-
/*full_name=*/"", /*reporter=*/nullptr, labels_));
181+
auto first, Table::Make(ident_, metadata_, "s3://bucket/meta.json", io_, catalog_));
182+
ICEBERG_UNWRAP_OR_FAIL(
183+
auto second, Table::Make(ident_, metadata_, "s3://bucket/meta.json", io_, catalog_,
184+
/*full_name=*/"", /*reporter=*/nullptr, Labels{}));
182185

183-
EXPECT_EQ(table->labels(), labels_);
186+
EXPECT_TRUE(first->labels().empty());
187+
EXPECT_TRUE(second->labels().empty());
188+
EXPECT_EQ(&first->labels(), &second->labels());
184189
}
185190

186-
TEST_F(TableLabelsTest, RefreshKeepsLabels) {
191+
TEST_F(TableLabelsTest, ExposesProvidedLabels) {
192+
for (const auto& labels : {labels_, Labels{.object_labels = {{"owner", "team-a"}}},
193+
Labels{.fields = {{.field_id = 1}}}}) {
194+
ICEBERG_UNWRAP_OR_FAIL(
195+
auto table, Table::Make(ident_, metadata_, "s3://bucket/meta.json", io_, catalog_,
196+
/*full_name=*/"", /*reporter=*/nullptr, labels));
197+
198+
EXPECT_EQ(table->labels(), labels);
199+
}
200+
}
201+
202+
class TableLabelsRefreshTest : public TableLabelsTest,
203+
public ::testing::WithParamInterface<bool> {};
204+
205+
TEST_P(TableLabelsRefreshTest, RefreshKeepsLabels) {
206+
const std::string metadata_location =
207+
GetParam() ? "s3://bucket/meta2.json" : "s3://bucket/meta.json";
208+
const Labels changed_labels{
209+
.object_labels = {{"owner", "team-b"}},
210+
.fields = {{.field_id = 1, .labels = {{"classification", "public"}}}}};
211+
for (const auto& refreshed_labels : {Labels{}, changed_labels}) {
212+
SCOPED_TRACE(refreshed_labels.empty() ? "Absent labels" : "Changed labels");
213+
ICEBERG_UNWRAP_OR_FAIL(
214+
auto table, Table::Make(ident_, metadata_, "s3://bucket/meta.json", io_, catalog_,
215+
/*full_name=*/"", /*reporter=*/nullptr, labels_));
216+
ICEBERG_UNWRAP_OR_FAIL(
217+
auto refreshed,
218+
Table::Make(ident_, metadata_, metadata_location, io_, catalog_,
219+
/*full_name=*/"", /*reporter=*/nullptr, refreshed_labels));
220+
EXPECT_CALL(*catalog_, LoadTable(::testing::_))
221+
.WillOnce(::testing::Return(refreshed));
222+
223+
ASSERT_THAT(table->Refresh(), IsOk());
224+
225+
EXPECT_EQ(table->metadata_file_location(), metadata_location);
226+
EXPECT_EQ(table->labels(), labels_);
227+
}
228+
}
229+
230+
INSTANTIATE_TEST_SUITE_P(MetadataLocation, TableLabelsRefreshTest, ::testing::Bool(),
231+
[](const ::testing::TestParamInfo<bool>& info) {
232+
return info.param ? "Changed" : "Unchanged";
233+
});
234+
235+
TEST_F(TableLabelsTest, EmptyTransactionKeepsLabels) {
187236
ICEBERG_UNWRAP_OR_FAIL(
188237
auto table, Table::Make(ident_, metadata_, "s3://bucket/meta.json", io_, catalog_,
189238
/*full_name=*/"", /*reporter=*/nullptr, labels_));
190-
ICEBERG_UNWRAP_OR_FAIL(
191-
auto refreshed,
192-
Table::Make(ident_, metadata_, "s3://bucket/meta2.json", io_, catalog_));
193-
EXPECT_CALL(*catalog_, LoadTable(::testing::_)).WillOnce(::testing::Return(refreshed));
194-
195-
ASSERT_THAT(table->Refresh(), IsOk());
239+
EXPECT_CALL(*catalog_, UpdateTable(::testing::_, ::testing::_, ::testing::_)).Times(0);
240+
ICEBERG_UNWRAP_OR_FAIL(auto transaction, table->NewTransaction());
241+
ICEBERG_UNWRAP_OR_FAIL(auto committed, transaction->Commit());
196242

197-
EXPECT_EQ(table->metadata_file_location(), "s3://bucket/meta2.json");
198-
EXPECT_EQ(table->labels(), labels_);
243+
ASSERT_EQ(committed, table);
244+
EXPECT_EQ(committed->labels(), labels_);
199245
}
200246

201247
TEST(StaticTableTest, NewMutatingOperationsAreNotSupported) {

0 commit comments

Comments
 (0)