| /* |
| * 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 <memory> |
| #include <string> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "iceberg/json_serde_internal.h" |
| #include "iceberg/name_mapping.h" |
| #include "iceberg/schema.h" |
| #include "iceberg/table_properties.h" |
| #include "iceberg/test/matchers.h" |
| #include "iceberg/test/update_test_base.h" |
| #include "iceberg/type.h" |
| #include "iceberg/update/update_properties.h" |
| #include "iceberg/update/update_schema.h" |
| |
| namespace iceberg { |
| |
| class UpdateMappingTest : public UpdateTestBase {}; |
| |
| TEST_F(UpdateMappingTest, AddColumnMappingUpdate) { |
| // Set initial name mapping to match current schema (x, y, z) |
| ICEBERG_UNWRAP_OR_FAIL(auto schema, table_->metadata()->Schema()); |
| auto initial_mapping = CreateMapping(*schema); |
| ASSERT_TRUE(initial_mapping.has_value()); |
| ICEBERG_UNWRAP_OR_FAIL(auto mapping_json, ToJsonString(ToJson(**initial_mapping))); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto props_update, table_->NewUpdateProperties()); |
| props_update->Set(std::string(TableProperties::kDefaultNameMapping), |
| std::move(mapping_json)); |
| EXPECT_THAT(props_update->Commit(), IsOk()); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); |
| |
| // Add column ts |
| ICEBERG_UNWRAP_OR_FAIL(auto schema_update, reloaded->NewUpdateSchema()); |
| schema_update->AddColumn("ts", timestamp_tz(), "Timestamp"); |
| EXPECT_THAT(schema_update->Commit(), IsOk()); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto updated_table, catalog_->LoadTable(table_ident_)); |
| auto updated_mapping_str = updated_table->metadata()->properties.configs().at( |
| std::string(TableProperties::kDefaultNameMapping)); |
| ICEBERG_UNWRAP_OR_FAIL(auto json, FromJsonString(updated_mapping_str)); |
| ICEBERG_UNWRAP_OR_FAIL(auto updated_mapping, NameMappingFromJson(json)); |
| |
| auto expected = MappedFields::Make({ |
| MappedField{.names = {"x"}, .field_id = 1}, |
| MappedField{.names = {"y"}, .field_id = 2}, |
| MappedField{.names = {"z"}, .field_id = 3}, |
| MappedField{.names = {"ts"}, .field_id = 4}, |
| }); |
| EXPECT_EQ(updated_mapping->AsMappedFields(), *expected); |
| } |
| |
| TEST_F(UpdateMappingTest, AddNestedColumnMappingUpdate) { |
| // Set initial name mapping (schema has x, y, z) |
| ICEBERG_UNWRAP_OR_FAIL(auto schema, table_->metadata()->Schema()); |
| auto initial_mapping = CreateMapping(*schema); |
| ASSERT_TRUE(initial_mapping.has_value()); |
| ICEBERG_UNWRAP_OR_FAIL(auto mapping_json, ToJsonString(ToJson(**initial_mapping))); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto props_update, table_->NewUpdateProperties()); |
| props_update->Set(std::string(TableProperties::kDefaultNameMapping), |
| std::move(mapping_json)); |
| EXPECT_THAT(props_update->Commit(), IsOk()); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); |
| |
| // Add point struct with x, y - mapping updated automatically on commit |
| auto point_struct = std::make_shared<StructType>(std::vector<SchemaField>{ |
| SchemaField::MakeRequired(4, "x", float64()), |
| SchemaField::MakeRequired(5, "y", float64()), |
| }); |
| ICEBERG_UNWRAP_OR_FAIL(auto add_point, reloaded->NewUpdateSchema()); |
| add_point->AddColumn("point", point_struct, "Point struct"); |
| EXPECT_THAT(add_point->Commit(), IsOk()); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto with_point, catalog_->LoadTable(table_ident_)); |
| |
| // Add point.z - mapping updated automatically on commit |
| ICEBERG_UNWRAP_OR_FAIL(auto add_z, with_point->NewUpdateSchema()); |
| add_z->AddColumn("point", "z", float64(), "Z coordinate"); |
| EXPECT_THAT(add_z->Commit(), IsOk()); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto with_z, catalog_->LoadTable(table_ident_)); |
| auto mapping_after_z = with_z->metadata()->properties.configs().at( |
| std::string(TableProperties::kDefaultNameMapping)); |
| ICEBERG_UNWRAP_OR_FAIL(auto json2, FromJsonString(mapping_after_z)); |
| ICEBERG_UNWRAP_OR_FAIL(auto mapping2, NameMappingFromJson(json2)); |
| |
| auto expected_after_z = MappedFields::Make({ |
| MappedField{.names = {"x"}, .field_id = 1}, |
| MappedField{.names = {"y"}, .field_id = 2}, |
| MappedField{.names = {"z"}, .field_id = 3}, |
| MappedField{.names = {"point"}, |
| .field_id = 4, |
| .nested_mapping = MappedFields::Make({ |
| MappedField{.names = {"x"}, .field_id = 5}, |
| MappedField{.names = {"y"}, .field_id = 6}, |
| MappedField{.names = {"z"}, .field_id = 7}, |
| })}, |
| }); |
| EXPECT_EQ(mapping2->AsMappedFields(), *expected_after_z); |
| } |
| |
| TEST_F(UpdateMappingTest, RenameMappingUpdate) { |
| ICEBERG_UNWRAP_OR_FAIL(auto schema, table_->metadata()->Schema()); |
| auto initial_mapping = CreateMapping(*schema); |
| ASSERT_TRUE(initial_mapping.has_value()); |
| ICEBERG_UNWRAP_OR_FAIL(auto mapping_json, ToJsonString(ToJson(**initial_mapping))); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto props_update, table_->NewUpdateProperties()); |
| props_update->Set(std::string(TableProperties::kDefaultNameMapping), |
| std::move(mapping_json)); |
| EXPECT_THAT(props_update->Commit(), IsOk()); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); |
| |
| // Rename x -> x_renamed |
| ICEBERG_UNWRAP_OR_FAIL(auto rename_update, reloaded->NewUpdateSchema()); |
| rename_update->RenameColumn("x", "x_renamed"); |
| EXPECT_THAT(rename_update->Commit(), IsOk()); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto updated_table, catalog_->LoadTable(table_ident_)); |
| auto updated_mapping_str = updated_table->metadata()->properties.configs().at( |
| std::string(TableProperties::kDefaultNameMapping)); |
| ICEBERG_UNWRAP_OR_FAIL(auto json, FromJsonString(updated_mapping_str)); |
| ICEBERG_UNWRAP_OR_FAIL(auto updated_mapping, NameMappingFromJson(json)); |
| |
| // Field 1 should have both names |
| EXPECT_THAT(updated_mapping->Find(1)->get().names, |
| testing::UnorderedElementsAre("x", "x_renamed")); |
| } |
| |
| TEST_F(UpdateMappingTest, RenameNestedFieldMappingUpdate) { |
| auto point_struct = std::make_shared<StructType>(std::vector<SchemaField>{ |
| SchemaField::MakeRequired(4, "x", float64()), |
| SchemaField::MakeRequired(5, "y", float64()), |
| }); |
| ICEBERG_UNWRAP_OR_FAIL(auto add_point, table_->NewUpdateSchema()); |
| add_point->AddColumn("point", point_struct, "Point struct"); |
| EXPECT_THAT(add_point->Commit(), IsOk()); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto with_point, catalog_->LoadTable(table_ident_)); |
| ICEBERG_UNWRAP_OR_FAIL(auto schema, with_point->metadata()->Schema()); |
| auto initial_mapping = CreateMapping(*schema); |
| ASSERT_TRUE(initial_mapping.has_value()); |
| ICEBERG_UNWRAP_OR_FAIL(auto mapping_json, ToJsonString(ToJson(**initial_mapping))); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto props_update, with_point->NewUpdateProperties()); |
| props_update->Set(std::string(TableProperties::kDefaultNameMapping), |
| std::move(mapping_json)); |
| EXPECT_THAT(props_update->Commit(), IsOk()); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto with_mapping, catalog_->LoadTable(table_ident_)); |
| |
| // Rename point.x -> X, point.y -> Y |
| ICEBERG_UNWRAP_OR_FAIL(auto rename_update, with_mapping->NewUpdateSchema()); |
| rename_update->RenameColumn("point.x", "X").RenameColumn("point.y", "Y"); |
| EXPECT_THAT(rename_update->Commit(), IsOk()); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto updated_table, catalog_->LoadTable(table_ident_)); |
| auto updated_mapping_str = updated_table->metadata()->properties.configs().at( |
| std::string(TableProperties::kDefaultNameMapping)); |
| ICEBERG_UNWRAP_OR_FAIL(auto json, FromJsonString(updated_mapping_str)); |
| ICEBERG_UNWRAP_OR_FAIL(auto updated_mapping, NameMappingFromJson(json)); |
| |
| auto point_field = updated_mapping->Find("point"); |
| ASSERT_TRUE(point_field.has_value()); |
| auto x_field = updated_mapping->Find("point.X"); |
| ASSERT_TRUE(x_field.has_value()); |
| auto y_field = updated_mapping->Find("point.Y"); |
| ASSERT_TRUE(y_field.has_value()); |
| EXPECT_THAT(x_field->get().names, testing::UnorderedElementsAre("x", "X")); |
| EXPECT_THAT(y_field->get().names, testing::UnorderedElementsAre("y", "Y")); |
| } |
| |
| TEST_F(UpdateMappingTest, RenameComplexFieldMappingUpdate) { |
| auto point_struct = std::make_shared<StructType>(std::vector<SchemaField>{ |
| SchemaField::MakeRequired(4, "x", float64()), |
| SchemaField::MakeRequired(5, "y", float64()), |
| }); |
| ICEBERG_UNWRAP_OR_FAIL(auto add_point, table_->NewUpdateSchema()); |
| add_point->AddColumn("point", point_struct, "Point struct"); |
| EXPECT_THAT(add_point->Commit(), IsOk()); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto with_point, catalog_->LoadTable(table_ident_)); |
| ICEBERG_UNWRAP_OR_FAIL(auto schema, with_point->metadata()->Schema()); |
| auto initial_mapping = CreateMapping(*schema); |
| ASSERT_TRUE(initial_mapping.has_value()); |
| ICEBERG_UNWRAP_OR_FAIL(auto mapping_json, ToJsonString(ToJson(**initial_mapping))); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto props_update, with_point->NewUpdateProperties()); |
| props_update->Set(std::string(TableProperties::kDefaultNameMapping), |
| std::move(mapping_json)); |
| EXPECT_THAT(props_update->Commit(), IsOk()); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto with_mapping, catalog_->LoadTable(table_ident_)); |
| |
| // Rename point -> p2 |
| ICEBERG_UNWRAP_OR_FAIL(auto rename_update, with_mapping->NewUpdateSchema()); |
| rename_update->RenameColumn("point", "p2"); |
| EXPECT_THAT(rename_update->Commit(), IsOk()); |
| |
| ICEBERG_UNWRAP_OR_FAIL(auto updated_table, catalog_->LoadTable(table_ident_)); |
| auto updated_mapping_str = updated_table->metadata()->properties.configs().at( |
| std::string(TableProperties::kDefaultNameMapping)); |
| ICEBERG_UNWRAP_OR_FAIL(auto json, FromJsonString(updated_mapping_str)); |
| ICEBERG_UNWRAP_OR_FAIL(auto updated_mapping, NameMappingFromJson(json)); |
| |
| // Field 4 (point) should have both names |
| auto point_field = updated_mapping->Find(4); |
| ASSERT_TRUE(point_field.has_value()); |
| EXPECT_THAT(point_field->get().names, testing::UnorderedElementsAre("point", "p2")); |
| } |
| |
| } // namespace iceberg |