8282
8383
8484# Avro types that have a name and can be referred to by it later in the schema
85- AVRO_NAMED_TYPES = {"record" , "enum" , "fixed" }
85+ AVRO_NAMED_TYPES = frozenset ( {"record" , "enum" , "fixed" })
8686
8787
8888class AvroSchemaConversion :
@@ -513,7 +513,6 @@ class ConvertSchemaToAvro(SchemaVisitorPerPrimitiveType[AvroType]):
513513 last_list_field_id : int
514514 last_map_key_field_id : int
515515 last_map_value_field_id : int
516- defined_names : set [str ]
517516
518517 def __init__ (self , schema_name : str | None ) -> None :
519518 """Convert an Iceberg schema to an Avro schema.
@@ -522,18 +521,16 @@ def __init__(self, schema_name: str | None) -> None:
522521 schema_name: The name of the root record.
523522 """
524523 self .schema_name = schema_name
525- self .defined_names : set [str ] = set ()
526524
527- def _define_once (self , named_type : dict [str , Any ]) -> AvroType :
528- """Return the definition of a named type the first time, and a reference to its name after that.
525+ @staticmethod
526+ def _name_fixed (result : AvroType , field_id : int ) -> None :
527+ """Add the field-id to the name of a fixed type.
529528
530- Avro allows only one definition per name; later uses must refer to it by name.
529+ Avro allows only one definition per name, and a decimal, uuid or fixed type can be used by more
530+ than one field. Field-ids are unique, so the names stay unique, the same way records are named.
531531 """
532- name = named_type ["name" ]
533- if name in self .defined_names :
534- return name
535- self .defined_names .add (name )
536- return named_type
532+ if isinstance (result , dict ) and result .get ("type" ) == "fixed" :
533+ result ["name" ] = f"{ result ['name' ]} _{ field_id } "
537534
538535 def schema (self , schema : Schema , struct_result : AvroType ) -> AvroType :
539536 if isinstance (struct_result , dict ) and self .schema_name is not None :
@@ -556,6 +553,7 @@ def field(self, field: NestedField, field_result: AvroType) -> AvroType:
556553 # Sets the schema name
557554 if isinstance (field_result , dict ) and field_result .get ("type" ) == "record" :
558555 field_result ["name" ] = f"r{ field .field_id } "
556+ self ._name_fixed (field_result , field .field_id )
559557
560558 original_name = field .name
561559 sanitized_name = make_compatible_name (original_name )
@@ -583,9 +581,12 @@ def list(self, list_type: ListType, element_result: AvroType) -> AvroType:
583581 # Sets the schema name in case of a record
584582 if isinstance (element_result , dict ) and element_result .get ("type" ) == "record" :
585583 element_result ["name" ] = f"r{ self .last_list_field_id } "
584+ self ._name_fixed (element_result , self .last_list_field_id )
586585 return {"type" : "array" , "element-id" : self .last_list_field_id , "items" : element_result }
587586
588587 def map (self , map_type : MapType , key_result : AvroType , value_result : AvroType ) -> AvroType :
588+ self ._name_fixed (key_result , self .last_map_key_field_id )
589+ self ._name_fixed (value_result , self .last_map_value_field_id )
589590 if isinstance (key_result , StringType ):
590591 # Avro Maps does not support other keys than a String,
591592 return {
@@ -611,19 +612,17 @@ def map(self, map_type: MapType, key_result: AvroType, value_result: AvroType) -
611612 }
612613
613614 def visit_fixed (self , fixed_type : FixedType ) -> AvroType :
614- return self . _define_once ( {"type" : "fixed" , "size" : len (fixed_type ), "name" : f"fixed_{ len (fixed_type )} " })
615+ return {"type" : "fixed" , "size" : len (fixed_type ), "name" : f"fixed_{ len (fixed_type )} " }
615616
616617 def visit_decimal (self , decimal_type : DecimalType ) -> AvroType :
617- return self ._define_once (
618- {
619- "type" : "fixed" ,
620- "size" : decimal_required_bytes (decimal_type .precision ),
621- "logicalType" : "decimal" ,
622- "precision" : decimal_type .precision ,
623- "scale" : decimal_type .scale ,
624- "name" : f"decimal_{ decimal_type .precision } _{ decimal_type .scale } " ,
625- }
626- )
618+ return {
619+ "type" : "fixed" ,
620+ "size" : decimal_required_bytes (decimal_type .precision ),
621+ "logicalType" : "decimal" ,
622+ "precision" : decimal_type .precision ,
623+ "scale" : decimal_type .scale ,
624+ "name" : f"decimal_{ decimal_type .precision } _{ decimal_type .scale } " ,
625+ }
627626
628627 def visit_boolean (self , boolean_type : BooleanType ) -> AvroType :
629628 return "boolean"
@@ -662,7 +661,7 @@ def visit_string(self, string_type: StringType) -> AvroType:
662661 return "string"
663662
664663 def visit_uuid (self , uuid_type : UUIDType ) -> AvroType :
665- return self . _define_once ( {"type" : "fixed" , "size" : 16 , "logicalType" : "uuid" , "name" : "uuid_fixed" })
664+ return {"type" : "fixed" , "size" : 16 , "logicalType" : "uuid" , "name" : "uuid_fixed" }
666665
667666 def visit_binary (self , binary_type : BinaryType ) -> AvroType :
668667 return "bytes"
0 commit comments