blob: 70f71560ad614e2128306bd14bc50496af309a85 [file] [log] [blame]
/*
* 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/name_mapping.h"
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace iceberg {
class NameMappingTest : public ::testing::Test {
protected:
std::unique_ptr<NameMapping> MakeNameMapping() {
std::vector<MappedField> fields;
fields.emplace_back(MappedField{.names = {"foo", "bar"}, .field_id = 1});
fields.emplace_back(MappedField{.names = {"baz"}, .field_id = 2});
std::vector<MappedField> nested_fields;
nested_fields.emplace_back(MappedField{.names = {"hello"}, .field_id = 4});
nested_fields.emplace_back(MappedField{.names = {"world"}, .field_id = 5});
auto nested_mapping = MappedFields::Make(std::move(nested_fields));
fields.emplace_back(MappedField{
.names = {"qux"}, .field_id = 3, .nested_mapping = std::move(nested_mapping)});
return NameMapping::Make(std::move(fields));
}
};
TEST_F(NameMappingTest, FindById) {
auto mapping = MakeNameMapping();
struct Param {
int32_t field_id;
bool should_have_value;
std::unordered_set<std::string> names;
};
const std::vector<Param> params = {
{.field_id = 1, .should_have_value = true, .names = {"foo", "bar"}},
{.field_id = 2, .should_have_value = true, .names = {"baz"}},
{.field_id = 3, .should_have_value = true, .names = {"qux"}},
{.field_id = 4, .should_have_value = true, .names = {"hello"}},
{.field_id = 5, .should_have_value = true, .names = {"world"}},
{.field_id = 999, .should_have_value = false, .names = {}},
};
for (const auto& param : params) {
auto field = mapping->Find(param.field_id);
if (param.should_have_value) {
ASSERT_TRUE(field.has_value());
EXPECT_EQ(field->get().field_id, param.field_id);
EXPECT_THAT(field->get().names, testing::UnorderedElementsAreArray(param.names));
} else {
EXPECT_FALSE(field.has_value());
}
}
}
TEST_F(NameMappingTest, FindByName) {
auto mapping = MakeNameMapping();
struct Param {
std::string name;
bool should_have_value;
int32_t field_id;
};
const std::vector<Param> params = {
{.name = "foo", .should_have_value = true, .field_id = 1},
{.name = "bar", .should_have_value = true, .field_id = 1},
{.name = "baz", .should_have_value = true, .field_id = 2},
{.name = "qux", .should_have_value = true, .field_id = 3},
{.name = "qux.hello", .should_have_value = true, .field_id = 4},
{.name = "qux.world", .should_have_value = true, .field_id = 5},
{.name = "non_existent", .should_have_value = false, .field_id = -1},
};
for (const auto& param : params) {
auto field = mapping->Find(param.name);
if (param.should_have_value) {
ASSERT_TRUE(field.has_value());
EXPECT_EQ(field->get().field_id, param.field_id);
} else {
EXPECT_FALSE(field.has_value());
}
}
}
TEST_F(NameMappingTest, FindByNameParts) {
auto mapping = MakeNameMapping();
struct Param {
std::vector<std::string> names;
bool should_have_value;
int32_t field_id;
};
std::vector<Param> params = {
{.names = {"foo"}, .should_have_value = true, .field_id = 1},
{.names = {"bar"}, .should_have_value = true, .field_id = 1},
{.names = {"baz"}, .should_have_value = true, .field_id = 2},
{.names = {"qux"}, .should_have_value = true, .field_id = 3},
{.names = {"qux", "hello"}, .should_have_value = true, .field_id = 4},
{.names = {"qux", "world"}, .should_have_value = true, .field_id = 5},
{.names = {"non_existent"}, .should_have_value = false, .field_id = -1},
};
for (const auto& param : params) {
auto field = mapping->Find(param.names);
if (param.should_have_value) {
ASSERT_TRUE(field.has_value());
EXPECT_EQ(field->get().field_id, param.field_id);
} else {
EXPECT_FALSE(field.has_value());
}
}
}
TEST_F(NameMappingTest, FindMethodsOnConstObject) {
auto mapping = MakeNameMapping();
const NameMapping& const_mapping = *mapping;
// Test Find by ID on const object
auto field_by_id = const_mapping.Find(1);
ASSERT_TRUE(field_by_id.has_value());
EXPECT_EQ(field_by_id->get().field_id, 1);
EXPECT_THAT(field_by_id->get().names, testing::UnorderedElementsAre("foo", "bar"));
// Test Find by name on const object
auto field_by_name = const_mapping.Find("baz");
ASSERT_TRUE(field_by_name.has_value());
EXPECT_EQ(field_by_name->get().field_id, 2);
EXPECT_THAT(field_by_name->get().names, testing::UnorderedElementsAre("baz"));
// Test Find by name parts on const object
auto field_by_parts = const_mapping.Find(std::vector<std::string>{"qux", "hello"});
ASSERT_TRUE(field_by_parts.has_value());
EXPECT_EQ(field_by_parts->get().field_id, 4);
EXPECT_THAT(field_by_parts->get().names, testing::UnorderedElementsAre("hello"));
// Test Find non-existent field on const object
auto non_existent = const_mapping.Find(999);
EXPECT_FALSE(non_existent.has_value());
auto non_existent_name = const_mapping.Find("non_existent");
EXPECT_FALSE(non_existent_name.has_value());
auto non_existent_parts =
const_mapping.Find(std::vector<std::string>{"non", "existent"});
EXPECT_FALSE(non_existent_parts.has_value());
}
TEST_F(NameMappingTest, FindMethodsOnConstEmptyMapping) {
auto empty_mapping = NameMapping::MakeEmpty();
const NameMapping& const_empty_mapping = *empty_mapping;
// Test Find by ID on const empty mapping
auto field_by_id = const_empty_mapping.Find(1);
EXPECT_FALSE(field_by_id.has_value());
// Test Find by name on const empty mapping
auto field_by_name = const_empty_mapping.Find("test");
EXPECT_FALSE(field_by_name.has_value());
// Test Find by name parts on const empty mapping
auto field_by_parts =
const_empty_mapping.Find(std::vector<std::string>{"test", "field"});
EXPECT_FALSE(field_by_parts.has_value());
}
TEST_F(NameMappingTest, Equality) {
auto mapping1 = MakeNameMapping();
auto mapping2 = MakeNameMapping();
auto empty_mapping = NameMapping::MakeEmpty();
EXPECT_EQ(*mapping1, *mapping2);
EXPECT_NE(*mapping1, *empty_mapping);
std::vector<MappedField> fields;
fields.emplace_back(
MappedField{.names = {"different"}, .field_id = 99, .nested_mapping = nullptr});
auto different_mapping = NameMapping::Make(MappedFields::Make(std::move(fields)));
EXPECT_NE(*mapping1, *different_mapping);
}
TEST_F(NameMappingTest, MappedFieldsAccess) {
auto mapping = MakeNameMapping();
const auto& fields = mapping->AsMappedFields();
EXPECT_EQ(fields.Size(), 3);
struct Param {
int32_t field_id;
std::unordered_set<std::string> names;
};
const std::vector<Param> params = {
{.field_id = 1, .names = {"foo", "bar"}},
{.field_id = 2, .names = {"baz"}},
{.field_id = 3, .names = {"qux"}},
};
for (const auto& param : params) {
auto field = fields.Field(param.field_id);
ASSERT_TRUE(field.has_value());
EXPECT_THAT(field->get().names, testing::UnorderedElementsAreArray(param.names));
}
}
TEST_F(NameMappingTest, ToString) {
{
std::vector<MappedField> fields;
fields.emplace_back(MappedField{.names = {"foo"}, .field_id = 1});
std::vector<MappedField> nested_fields;
nested_fields.emplace_back(MappedField{.names = {"hello"}, .field_id = 3});
nested_fields.emplace_back(MappedField{.names = {"world"}, .field_id = 4});
auto nested_mapping = MappedFields::Make(std::move(nested_fields));
fields.emplace_back(MappedField{
.names = {"bar"}, .field_id = 2, .nested_mapping = std::move(nested_mapping)});
auto mapping = NameMapping::Make(std::move(fields));
auto expected = R"([
([foo] -> 1)
([bar] -> 2, [([hello] -> 3), ([world] -> 4)])
])";
EXPECT_EQ(ToString(*mapping), expected);
}
{
auto empty_mapping = NameMapping::MakeEmpty();
EXPECT_EQ(ToString(*empty_mapping), "[]");
}
}
TEST(CreateMappingTest, FlatSchemaToMapping) {
Schema schema(std::vector<SchemaField>{
SchemaField::MakeRequired(1, "id", iceberg::int64()),
SchemaField::MakeRequired(2, "data", iceberg::string()),
});
auto expected = MappedFields::Make({
MappedField{.names = {"id"}, .field_id = 1},
MappedField{.names = {"data"}, .field_id = 2},
});
auto result = CreateMapping(schema);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value()->AsMappedFields(), *expected);
}
TEST(CreateMappingTest, NestedStructSchemaToMapping) {
Schema schema(std::vector<SchemaField>{
SchemaField::MakeRequired(1, "id", iceberg::int64()),
SchemaField::MakeRequired(2, "data", iceberg::string()),
SchemaField::MakeRequired(
3, "location",
std::make_shared<StructType>(std::vector<SchemaField>{
SchemaField::MakeRequired(4, "latitude", iceberg::float32()),
SchemaField::MakeRequired(5, "longitude", iceberg::float32()),
})),
});
auto expected = MappedFields::Make({
MappedField{.names = {"id"}, .field_id = 1},
MappedField{.names = {"data"}, .field_id = 2},
MappedField{.names = {"location"},
.field_id = 3,
.nested_mapping = MappedFields::Make({
MappedField{.names = {"latitude"}, .field_id = 4},
MappedField{.names = {"longitude"}, .field_id = 5},
})},
});
auto result = CreateMapping(schema);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value()->AsMappedFields(), *expected);
}
TEST(CreateMappingTest, MapSchemaToMapping) {
Schema schema(std::vector<SchemaField>{
SchemaField::MakeRequired(1, "id", iceberg::int64()),
SchemaField::MakeRequired(2, "data", iceberg::string()),
SchemaField::MakeRequired(
3, "map",
std::make_shared<MapType>(
SchemaField::MakeRequired(4, "key", iceberg::string()),
SchemaField::MakeRequired(5, "value", iceberg::float64()))),
});
auto expected = MappedFields::Make({
MappedField{.names = {"id"}, .field_id = 1},
MappedField{.names = {"data"}, .field_id = 2},
MappedField{.names = {"map"},
.field_id = 3,
.nested_mapping = MappedFields::Make({
MappedField{.names = {"key"}, .field_id = 4},
MappedField{.names = {"value"}, .field_id = 5},
})},
});
auto result = CreateMapping(schema);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value()->AsMappedFields(), *expected);
}
TEST(CreateMappingTest, ListSchemaToMapping) {
Schema schema(std::vector<SchemaField>{
SchemaField::MakeRequired(1, "id", iceberg::int64()),
SchemaField::MakeRequired(2, "data", iceberg::string()),
SchemaField::MakeRequired(3, "list",
std::make_shared<ListType>(SchemaField::MakeRequired(
4, "element", iceberg::string()))),
});
auto expected = MappedFields::Make({
MappedField{.names = {"id"}, .field_id = 1},
MappedField{.names = {"data"}, .field_id = 2},
MappedField{.names = {"list"},
.field_id = 3,
.nested_mapping = MappedFields::Make({
MappedField{.names = {"element"}, .field_id = 4},
})},
});
auto result = CreateMapping(schema);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value()->AsMappedFields(), *expected);
}
} // namespace iceberg