blob: fcac43c78593560e20eb2e5df4b03d821817da8d [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 "iceberg/schema.h"
#include <format>
#include <functional>
#include <stack>
#include "iceberg/result.h"
#include "iceberg/row/struct_like.h"
#include "iceberg/schema_internal.h"
#include "iceberg/table_metadata.h"
#include "iceberg/type.h"
#include "iceberg/util/formatter.h" // IWYU pragma: keep
#include "iceberg/util/macros.h"
#include "iceberg/util/type_util.h"
#include "iceberg/util/visit_type.h"
namespace iceberg {
struct SchemaReassignIdContext {
Schema::IdMap ids_to_reassigned;
Schema::IdMap ids_to_original;
};
namespace {
const Schema::IdMap& EmptyIdMap() {
static const Schema::IdMap kEmpty;
return kEmpty;
}
void RecordIdReassignment(int32_t old_id, int32_t new_id,
Schema::IdMap& ids_to_reassigned,
Schema::IdMap& ids_to_original) {
if (new_id != old_id) {
ids_to_reassigned[old_id] = new_id;
ids_to_original[new_id] = old_id;
}
}
SchemaField ReassignField(const SchemaField& field, int32_t new_id,
const Schema::GetId& get_id, Schema::IdMap& ids_to_reassigned,
Schema::IdMap& ids_to_original);
std::shared_ptr<Type> ReassignTypeIds(const std::shared_ptr<Type>& type,
const Schema::GetId& get_id,
Schema::IdMap& ids_to_reassigned,
Schema::IdMap& ids_to_original) {
switch (type->type_id()) {
case TypeId::kStruct: {
const auto& struct_type = static_cast<const StructType&>(*type);
const auto& fields = struct_type.fields();
std::vector<int32_t> new_ids;
new_ids.reserve(fields.size());
for (const auto& field : fields) {
const auto new_id = get_id(field.field_id());
RecordIdReassignment(field.field_id(), new_id, ids_to_reassigned,
ids_to_original);
new_ids.push_back(new_id);
}
std::vector<SchemaField> reassigned_fields;
reassigned_fields.reserve(fields.size());
for (size_t i = 0; i < fields.size(); ++i) {
reassigned_fields.emplace_back(ReassignField(fields[i], new_ids[i], get_id,
ids_to_reassigned, ids_to_original));
}
return std::make_shared<StructType>(std::move(reassigned_fields));
}
case TypeId::kList: {
const auto& list_type = static_cast<const ListType&>(*type);
const auto& element = list_type.element();
const auto new_id = get_id(element.field_id());
RecordIdReassignment(element.field_id(), new_id, ids_to_reassigned,
ids_to_original);
return std::make_shared<ListType>(
ReassignField(element, new_id, get_id, ids_to_reassigned, ids_to_original));
}
case TypeId::kMap: {
const auto& map_type = static_cast<const MapType&>(*type);
const auto& key = map_type.key();
const auto& value = map_type.value();
const auto new_key_id = get_id(key.field_id());
const auto new_value_id = get_id(value.field_id());
RecordIdReassignment(key.field_id(), new_key_id, ids_to_reassigned,
ids_to_original);
RecordIdReassignment(value.field_id(), new_value_id, ids_to_reassigned,
ids_to_original);
return std::make_shared<MapType>(
ReassignField(key, new_key_id, get_id, ids_to_reassigned, ids_to_original),
ReassignField(value, new_value_id, get_id, ids_to_reassigned, ids_to_original));
}
default:
return type;
}
}
SchemaField ReassignField(const SchemaField& field, int32_t new_id,
const Schema::GetId& get_id, Schema::IdMap& ids_to_reassigned,
Schema::IdMap& ids_to_original) {
return {new_id, std::string(field.name()),
ReassignTypeIds(field.type(), get_id, ids_to_reassigned, ids_to_original),
field.optional(), std::string(field.doc())};
}
std::vector<SchemaField> ReassignIds(std::vector<SchemaField> fields,
const Schema::GetId& get_id,
SchemaReassignIdContext& reassign_id_context) {
auto reassigned_type = ReassignTypeIds(std::make_shared<StructType>(std::move(fields)),
get_id, reassign_id_context.ids_to_reassigned,
reassign_id_context.ids_to_original);
const auto& reassigned_fields =
internal::checked_cast<const StructType&>(*reassigned_type).fields();
return {reassigned_fields.begin(), reassigned_fields.end()};
}
Status ValidateFieldNullability(const Type& type) {
auto validate_field = [&](const SchemaField& field) -> Status {
ICEBERG_PRECHECK(field.optional() || field.type()->type_id() != TypeId::kUnknown,
"Unknown type field '{}' must be optional", field.name());
return ValidateFieldNullability(*field.type());
};
switch (type.type_id()) {
case TypeId::kStruct: {
const auto& struct_type = static_cast<const StructType&>(type);
for (const auto& field : struct_type.fields()) {
ICEBERG_RETURN_UNEXPECTED(validate_field(field));
}
return {};
}
case TypeId::kList: {
const auto& list_type = static_cast<const ListType&>(type);
const auto& element = list_type.element();
return validate_field(element);
}
case TypeId::kMap: {
const auto& map_type = static_cast<const MapType&>(type);
const auto& key = map_type.key();
const auto& value = map_type.value();
ICEBERG_PRECHECK(key.type()->type_id() != TypeId::kUnknown,
"Map 'key' cannot be unknown type");
ICEBERG_RETURN_UNEXPECTED(ValidateFieldNullability(*key.type()));
return validate_field(value);
}
default:
return {};
}
}
} // namespace
Schema::Schema(std::vector<SchemaField> fields, int32_t schema_id, GetId get_id)
: StructType(std::move(fields)),
schema_id_(schema_id),
cache_(std::make_unique<SchemaCache>(this)) {
if (get_id) {
reassign_id_context_ = std::make_unique<SchemaReassignIdContext>();
fields_ = ReassignIds(std::move(fields_), get_id, *reassign_id_context_);
}
}
Schema::~Schema() = default;
Result<std::unique_ptr<Schema>> Schema::Make(std::vector<SchemaField> fields,
int32_t schema_id,
std::vector<int32_t> identifier_field_ids,
GetId get_id) {
auto schema = std::make_unique<Schema>(std::move(fields), schema_id, std::move(get_id));
if (!identifier_field_ids.empty()) {
auto id_to_parent = IndexParents(*schema);
for (auto field_id : identifier_field_ids) {
ICEBERG_RETURN_UNEXPECTED(
ValidateIdentifierFields(field_id, *schema, id_to_parent));
}
}
schema->identifier_field_ids_ = std::move(identifier_field_ids);
return schema;
}
Result<std::unique_ptr<Schema>> Schema::Make(
std::vector<SchemaField> fields, int32_t schema_id,
const std::vector<std::string>& identifier_field_names, GetId get_id) {
auto schema = std::make_unique<Schema>(std::move(fields), schema_id, std::move(get_id));
std::vector<int32_t> fresh_identifier_ids;
for (const auto& name : identifier_field_names) {
ICEBERG_ASSIGN_OR_RAISE(auto field, schema->FindFieldByName(name));
if (!field) {
return InvalidSchema("Cannot find identifier field: {}", name);
}
fresh_identifier_ids.push_back(field.value().get().field_id());
}
if (!fresh_identifier_ids.empty()) {
auto id_to_parent = IndexParents(*schema);
for (auto field_id : fresh_identifier_ids) {
ICEBERG_RETURN_UNEXPECTED(
ValidateIdentifierFields(field_id, *schema, id_to_parent));
}
}
schema->identifier_field_ids_ = std::move(fresh_identifier_ids);
return schema;
}
Status Schema::ValidateIdentifierFields(
int32_t field_id, const Schema& schema,
const std::unordered_map<int32_t, int32_t>& id_to_parent) {
ICEBERG_ASSIGN_OR_RAISE(auto field_opt, schema.FindFieldById(field_id));
ICEBERG_PRECHECK(field_opt.has_value(),
"Cannot add field {} as an identifier field: field does not exist",
field_id);
const SchemaField& field = field_opt.value().get();
ICEBERG_PRECHECK(
field.type()->is_primitive(),
"Cannot add field {} as an identifier field: not a primitive type field", field_id);
ICEBERG_PRECHECK(!field.optional(),
"Cannot add field {} as an identifier field: not a required field",
field_id);
ICEBERG_PRECHECK(
field.type()->type_id() != TypeId::kDouble &&
field.type()->type_id() != TypeId::kFloat,
"Cannot add field {} as an identifier field: must not be float or double field",
field_id);
// check whether the nested field is in a chain of required struct fields
// exploring from root for better error message for list and map types
std::stack<int32_t> ancestors;
auto parent_it = id_to_parent.find(field.field_id());
while (parent_it != id_to_parent.end()) {
ancestors.push(parent_it->second);
parent_it = id_to_parent.find(parent_it->second);
}
while (!ancestors.empty()) {
ICEBERG_ASSIGN_OR_RAISE(auto parent_opt, schema.FindFieldById(ancestors.top()));
ICEBERG_PRECHECK(
parent_opt.has_value(),
"Cannot add field {} as an identifier field: parent field id {} does not exist",
field_id, ancestors.top());
const SchemaField& parent = parent_opt.value().get();
ICEBERG_PRECHECK(
parent.type()->type_id() == TypeId::kStruct,
"Cannot add field {} as an identifier field: must not be nested in {}", field_id,
*parent.type());
ICEBERG_PRECHECK(!parent.optional(),
"Cannot add field {} as an identifier field: must not be nested in "
"optional field {}",
field_id, parent.field_id());
ancestors.pop();
}
return {};
}
const std::shared_ptr<Schema>& Schema::EmptySchema() {
static const auto empty_schema =
std::make_shared<Schema>(std::vector<SchemaField>{}, kInitialSchemaId);
return empty_schema;
}
int32_t Schema::schema_id() const { return schema_id_; }
const Schema::IdMap& Schema::IdsToReassigned() const {
return reassign_id_context_ ? reassign_id_context_->ids_to_reassigned : EmptyIdMap();
}
const Schema::IdMap& Schema::IdsToOriginal() const {
return reassign_id_context_ ? reassign_id_context_->ids_to_original : EmptyIdMap();
}
std::string Schema::ToString() const {
std::string repr = "schema<";
for (const auto& field : fields_) {
std::format_to(std::back_inserter(repr), " {}\n", field);
}
repr += ">";
return repr;
}
bool Schema::Equals(const Schema& other) const {
return schema_id_ == other.schema_id_ && fields_ == other.fields_ &&
identifier_field_ids_ == other.identifier_field_ids_;
}
Result<std::optional<std::reference_wrapper<const SchemaField>>> Schema::FindFieldByName(
std::string_view name, bool case_sensitive) const {
if (case_sensitive) {
ICEBERG_ASSIGN_OR_RAISE(auto name_id_map, cache_->GetNameIdMap());
auto it = name_id_map.get().name_to_id.find(name);
if (it == name_id_map.get().name_to_id.end()) {
return std::nullopt;
};
return FindFieldById(it->second);
}
ICEBERG_ASSIGN_OR_RAISE(auto lowercase_name_to_id, cache_->GetLowercaseNameToIdMap());
auto it = lowercase_name_to_id.get().find(StringUtils::ToLower(name));
if (it == lowercase_name_to_id.get().end()) {
return std::nullopt;
}
return FindFieldById(it->second);
}
Result<std::optional<std::reference_wrapper<const SchemaField>>> Schema::FindFieldById(
int32_t field_id) const {
ICEBERG_ASSIGN_OR_RAISE(auto id_to_field, cache_->GetIdToFieldMap());
auto it = id_to_field.get().find(field_id);
if (it == id_to_field.get().end()) {
return std::nullopt;
}
return it->second;
}
Result<std::optional<std::string_view>> Schema::FindColumnNameById(
int32_t field_id) const {
ICEBERG_ASSIGN_OR_RAISE(auto name_id_map, cache_->GetNameIdMap());
auto it = name_id_map.get().id_to_name.find(field_id);
if (it == name_id_map.get().id_to_name.end()) {
return std::nullopt;
}
return it->second;
}
Result<std::unique_ptr<StructLikeAccessor>> Schema::GetAccessorById(
int32_t field_id) const {
ICEBERG_ASSIGN_OR_RAISE(auto id_to_position_path, cache_->GetIdToPositionPathMap());
if (auto it = id_to_position_path.get().find(field_id);
it != id_to_position_path.get().cend()) {
ICEBERG_ASSIGN_OR_RAISE(auto field, FindFieldById(field_id));
if (!field.has_value()) {
return NotFound("Cannot get accessor for field id: {}", field_id);
}
return std::make_unique<StructLikeAccessor>(field.value().get().type(), it->second);
}
return NotFound("Cannot get accessor for field id: {}", field_id);
}
Result<std::unique_ptr<Schema>> Schema::Select(std::span<const std::string> names,
bool case_sensitive) const {
if (std::ranges::find(names, kAllColumns) != names.end()) {
auto struct_type = ToStructType(*this);
return FromStructType(std::move(*struct_type), std::nullopt);
}
std::unordered_set<int32_t> selected_ids;
for (const auto& name : names) {
ICEBERG_ASSIGN_OR_RAISE(auto result, FindFieldByName(name, case_sensitive));
if (result.has_value()) {
selected_ids.insert(result.value().get().field_id());
}
}
PruneColumnVisitor visitor(selected_ids, /*select_full_types=*/true);
ICEBERG_ASSIGN_OR_RAISE(
auto pruned_type, visitor.Visit(std::shared_ptr<StructType>(ToStructType(*this))));
if (!pruned_type) {
return std::make_unique<Schema>(std::vector<SchemaField>{}, kInitialSchemaId);
}
if (pruned_type->type_id() != TypeId::kStruct) {
return InvalidSchema("Projected type must be a struct type");
}
return FromStructType(std::move(internal::checked_cast<StructType&>(*pruned_type)),
std::nullopt);
}
Result<std::unique_ptr<Schema>> Schema::Project(
const std::unordered_set<int32_t>& field_ids) const {
PruneColumnVisitor visitor(field_ids, /*select_full_types=*/false);
ICEBERG_ASSIGN_OR_RAISE(
auto project_type, visitor.Visit(std::shared_ptr<StructType>(ToStructType(*this))));
if (!project_type) {
return std::make_unique<Schema>(std::vector<SchemaField>{}, kInitialSchemaId);
}
if (project_type->type_id() != TypeId::kStruct) {
return InvalidSchema("Projected type must be a struct type");
}
return FromStructType(std::move(internal::checked_cast<StructType&>(*project_type)),
std::nullopt);
}
const std::vector<int32_t>& Schema::IdentifierFieldIds() const {
return identifier_field_ids_;
}
Result<std::vector<std::string>> Schema::IdentifierFieldNames() const {
std::vector<std::string> names;
names.reserve(identifier_field_ids_.size());
for (auto id : identifier_field_ids_) {
ICEBERG_ASSIGN_OR_RAISE(auto name, FindColumnNameById(id));
if (!name.has_value()) {
return InvalidSchema("Cannot find identifier field id: {}", id);
}
names.emplace_back(name.value());
}
return names;
}
Result<int32_t> Schema::HighestFieldId() const { return cache_->GetHighestFieldId(); }
bool Schema::SameSchema(const Schema& other) const {
return fields_ == other.fields_ && identifier_field_ids_ == other.identifier_field_ids_;
}
Status Schema::Validate(int32_t format_version) const {
ICEBERG_RETURN_UNEXPECTED(ValidateFieldNullability(*this));
// Get all fields including nested ones
ICEBERG_ASSIGN_OR_RAISE(auto id_to_field, cache_->GetIdToFieldMap());
// Check each field's type and defaults
for (const auto& [field_id, field_ref] : id_to_field.get()) {
const auto& field = field_ref.get();
// Check if the field's type requires a minimum format version
if (auto it = TableMetadata::kMinFormatVersions.find(field.type()->type_id());
it != TableMetadata::kMinFormatVersions.end()) {
if (int32_t min_format_version = it->second; format_version < min_format_version) {
return InvalidSchema("Invalid type for {}: {} is not supported until v{}",
field.name(), *field.type(), min_format_version);
}
}
// TODO(GuoTao.yu): Check default values when they are supported
}
return {};
}
Result<SchemaCache::IdToFieldMapRef> SchemaCache::GetIdToFieldMap() const {
return id_to_field_.Get(schema_);
}
Result<SchemaCache::NameIdMapRef> SchemaCache::GetNameIdMap() const {
return name_id_map_.Get(schema_);
}
Result<SchemaCache::LowercaseNameToIdMapRef> SchemaCache::GetLowercaseNameToIdMap()
const {
return lowercase_name_to_id_.Get(schema_);
}
Result<SchemaCache::IdToPositionPathMapRef> SchemaCache::GetIdToPositionPathMap() const {
return id_to_position_path_.Get(schema_);
}
Result<int32_t> SchemaCache::GetHighestFieldId() const {
return highest_field_id_.Get(schema_);
}
Result<SchemaCache::IdToFieldMap> SchemaCache::InitIdToFieldMap(const Schema* schema) {
std::unordered_map<int32_t, std::reference_wrapper<const SchemaField>> id_to_field;
IdToFieldVisitor visitor(id_to_field);
ICEBERG_RETURN_UNEXPECTED(VisitTypeInline(*schema, &visitor));
return id_to_field;
}
Result<SchemaCache::NameIdMap> SchemaCache::InitNameIdMap(const Schema* schema) {
NameIdMap name_id_map;
NameToIdVisitor visitor(name_id_map.name_to_id, &name_id_map.id_to_name,
/*case_sensitive=*/true);
ICEBERG_RETURN_UNEXPECTED(
VisitTypeInline(*schema, &visitor, /*path=*/"", /*short_path=*/""));
visitor.Finish();
return name_id_map;
}
Result<SchemaCache::LowercaseNameToIdMap> SchemaCache::InitLowerCaseNameToIdMap(
const Schema* schema) {
std::unordered_map<std::string, int32_t, StringHash, std::equal_to<>>
lowercase_name_to_id;
NameToIdVisitor visitor(lowercase_name_to_id, /*id_to_name=*/nullptr,
/*case_sensitive=*/false);
ICEBERG_RETURN_UNEXPECTED(
VisitTypeInline(*schema, &visitor, /*path=*/"", /*short_path=*/""));
visitor.Finish();
return lowercase_name_to_id;
}
Result<SchemaCache::IdToPositionPathMap> SchemaCache::InitIdToPositionPath(
const Schema* schema) {
PositionPathVisitor visitor;
ICEBERG_RETURN_UNEXPECTED(VisitTypeInline(*schema, &visitor));
return visitor.Finish();
}
Result<int32_t> SchemaCache::InitHighestFieldId(const Schema* schema) {
ICEBERG_ASSIGN_OR_RAISE(auto id_to_field, InitIdToFieldMap(schema));
if (id_to_field.empty()) {
return Schema::kInitialColumnId;
}
auto max_it = std::ranges::max_element(
id_to_field,
[](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; });
return max_it->first;
}
} // namespace iceberg