blob: 342b119a53bfdf3d8729141d34f15a38570ac6f4 [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.
*/
#pragma once
#include <stack>
#include <avro/Node.hh>
#include "iceberg/name_mapping.h"
#include "iceberg/result.h"
#include "iceberg/schema_util.h"
#include "iceberg/type.h"
namespace avro {
class Schema;
class ValidSchema;
} // namespace avro
namespace iceberg::avro {
struct MapLogicalType : public ::avro::CustomLogicalType {
MapLogicalType() : ::avro::CustomLogicalType("map") {}
};
/// \brief A visitor that converts an Iceberg type to an Avro node.
class ToAvroNodeVisitor {
public:
Status Visit(const BooleanType& type, ::avro::NodePtr* node);
Status Visit(const IntType& type, ::avro::NodePtr* node);
Status Visit(const LongType& type, ::avro::NodePtr* node);
Status Visit(const FloatType& type, ::avro::NodePtr* node);
Status Visit(const DoubleType& type, ::avro::NodePtr* node);
Status Visit(const DecimalType& type, ::avro::NodePtr* node);
Status Visit(const DateType& type, ::avro::NodePtr* node);
Status Visit(const TimeType& type, ::avro::NodePtr* node);
Status Visit(const TimestampType& type, ::avro::NodePtr* node);
Status Visit(const TimestampTzType& type, ::avro::NodePtr* node);
Status Visit(const TimestampNsType& type, ::avro::NodePtr* node);
Status Visit(const TimestampTzNsType& type, ::avro::NodePtr* node);
Status Visit(const StringType& type, ::avro::NodePtr* node);
Status Visit(const UuidType& type, ::avro::NodePtr* node);
Status Visit(const FixedType& type, ::avro::NodePtr* node);
Status Visit(const BinaryType& type, ::avro::NodePtr* node);
Status Visit(const UnknownType&, ::avro::NodePtr*);
Status Visit(const VariantType&, ::avro::NodePtr*);
Status Visit(const GeometryType&, ::avro::NodePtr*);
Status Visit(const GeographyType&, ::avro::NodePtr*);
Status Visit(const StructType& type, ::avro::NodePtr* node);
Status Visit(const ListType& type, ::avro::NodePtr* node);
Status Visit(const MapType& type, ::avro::NodePtr* node);
Status Visit(const SchemaField& field, ::avro::NodePtr* node);
private:
// Store recently accessed field ids on the current visitor path.
std::stack<int32_t> field_ids_;
};
/// \brief A visitor that checks the presence of field IDs in an Avro schema.
class HasIdVisitor {
public:
HasIdVisitor() = default;
/// \brief Visit an Avro node to check for field IDs.
/// \param node The Avro node to visit.
/// \return Status indicating success or an error if unsupported Avro types are
/// encountered.
Status Visit(const ::avro::NodePtr& node);
/// \brief Visit an Avro schema to check for field IDs.
/// \param schema The Avro schema to visit.
/// \return Status indicating success or an error if unsupported Avro types are
/// encountered.
Status Visit(const ::avro::ValidSchema& schema);
/// \brief Visit an Avro schema to check for field IDs.
/// \param schema The Avro schema to visit.
/// \return Status indicating success or an error if unsupported Avro types are
/// encountered.
Status Visit(const ::avro::Schema& node);
/// \brief Check if all fields in the visited schema have field IDs.
/// \return True if all fields have IDs, false otherwise.
bool AllHaveIds() const {
return total_fields_ == fields_with_id_ && fields_with_id_ != 0;
}
/// \brief Check if all fields in the visited schema have field IDs.
/// \return True if all fields have IDs, false otherwise.
bool HasNoIds() const { return total_fields_ == 0; }
private:
/// \brief Visit a record node to check for field IDs.
/// \param node The record node to visit.
/// \return Status indicating success or error.
Status VisitRecord(const ::avro::NodePtr& node);
/// \brief Visit an array node to check for element IDs.
/// \param node The array node to visit.
/// \return Status indicating success or error.
Status VisitArray(const ::avro::NodePtr& node);
/// \brief Visit a map node to check for key and value IDs.
/// \param node The map node to visit.
/// \return Status indicating success or error.
Status VisitMap(const ::avro::NodePtr& node);
/// \brief Visit a union node to check for field IDs in each branch.
/// \param node The union node to visit.
/// \return Status indicating success or error.
Status VisitUnion(const ::avro::NodePtr& node);
private:
// Total number of fields visited.
size_t total_fields_ = 0;
// Number of fields with IDs.
size_t fields_with_id_ = 0;
};
/// \brief Project an Iceberg Schema onto an Avro NodePtr.
///
/// This function creates a projection from an Iceberg Schema to an Avro schema node.
/// The projection determines how to read data from the Avro schema into the expected
/// Iceberg Schema.
///
/// \param expected_schema The Iceberg Schema that defines the expected structure.
/// \param avro_node The Avro node to read data from.
/// \param prune_source Whether the source schema can be pruned.
/// \return The schema projection result.
Result<SchemaProjection> Project(const Schema& expected_schema,
const ::avro::NodePtr& avro_node, bool prune_source);
std::string ToString(const ::avro::NodePtr& node);
std::string ToString(const ::avro::LogicalType& logical_type);
std::string ToString(const ::avro::LogicalType::Type& logical_type);
/// \brief Check if an Avro node can be read as an Iceberg date.
///
/// Iceberg dates are encoded as Avro ints with the date logical type. Readers
/// also accept plain ints where the expected Iceberg type is date.
/// \param node The Avro node to check.
/// \return True if the node is an Avro date or a plain Avro int.
bool IsAvroDateOrPlainInt(const ::avro::NodePtr& node);
/// \brief Check if an Avro node has a map logical type.
/// \param node The Avro node to check.
/// \return True if the node has a map logical type, false otherwise.
bool HasMapLogicalType(const ::avro::NodePtr& node);
/// \brief Check if a string is a valid Avro name.
///
/// Valid Avro names must:
/// 1. Start with a letter or underscore
/// 2. Contain only letters, digits, or underscores
///
/// \param name The name to check.
/// \return True if the name is valid, false otherwise.
bool ValidAvroName(std::string_view name);
/// \brief Create a new Avro node with field IDs from name mapping.
/// \param original_node The original Avro node to copy.
/// \param mapping The name mapping to apply field IDs from.
/// \return A new Avro node with field IDs applied, or an error.
Result<::avro::NodePtr> MakeAvroNodeWithFieldIds(const ::avro::NodePtr& original_node,
const NameMapping& mapping);
/// \brief Sanitize a field name to make it compatible with Avro field name requirements.
///
/// Converts names that are not valid Avro names to valid Avro names.
/// Conversion rules:
/// 1. If the first character is not a letter or underscore, it is specially handled:
/// - Digits: Prefixed with an underscore (e.g., '3' -> '_3')
/// - Other characters: Converted to '_x' followed by the uppercase hexadecimal
/// representation of the character (e.g., '$' -> '_x24')
/// 2. For characters other than the first:
/// - If it's a letter, digit, or underscore, it remains unchanged
/// - Other characters: Converted to '_x' followed by the uppercase hexadecimal
/// representation
///
/// Examples:
/// - "123field" -> "_123field"
/// - "user-name" -> "user_x2Dname"
/// - "$price" -> "_x24price"
/// - "valid_name_123" -> "valid_name_123" (no conversion needed)
///
/// \param field_name The original field name to sanitize.
/// \return A sanitized field name that follows Avro naming conventions.
std::string SanitizeFieldName(std::string_view field_name);
} // namespace iceberg::avro