blob: f63a4c6d362b641aa3f8a2f9689df495008063ba [file]
/*
* 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