Skip to main content

iceberg/arrow/
schema.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Conversion between Arrow schema and Iceberg schema.
19
20use std::collections::HashMap;
21use std::sync::Arc;
22
23use arrow_array::types::{Decimal128Type, validate_decimal_precision_and_scale};
24use arrow_array::{
25    BinaryArray, BooleanArray, Date32Array, Datum as ArrowDatum, Decimal128Array,
26    FixedSizeBinaryArray, Float32Array, Float64Array, Int32Array, Int64Array, Scalar, StringArray,
27    TimestampMicrosecondArray, TimestampNanosecondArray,
28};
29use arrow_schema::extension::{ExtensionType, Uuid as UuidExtensionType};
30use arrow_schema::{
31    ArrowError, DataType, Field, FieldRef, Fields, Schema as ArrowSchema, TimeUnit,
32};
33use parquet::arrow::PARQUET_FIELD_ID_META_KEY;
34use parquet::file::statistics::Statistics;
35use parquet_geospatial::{WkbEdges, WkbMetadata, WkbType, WkbTypeHint};
36use uuid::Uuid;
37
38use crate::error::{Result, invalid_data};
39use crate::spec::decimal_utils::i128_from_be_bytes;
40use crate::spec::geospatial::EdgeInterpolationAlgorithm;
41use crate::spec::{
42    Datum, FIRST_FIELD_ID, GeographyType, GeometryType, LIST_FIELD_NAME, ListType,
43    MAP_KEY_FIELD_NAME, MAP_VALUE_FIELD_NAME, MapType, NestedField, NestedFieldRef,
44    PrimitiveLiteral, PrimitiveType, Schema, SchemaVisitor, StructType, Type, VariantType,
45};
46use crate::{Error, ErrorKind};
47
48/// When iceberg map type convert to Arrow map type, the default map field name is "key_value".
49pub const DEFAULT_MAP_FIELD_NAME: &str = "key_value";
50/// UTC time zone for Arrow timestamp type.
51pub const UTC_TIME_ZONE: &str = "+00:00";
52const UNSET_GEOSPATIAL_CRS: &str = "srid:0";
53
54/// The canonical Arrow [`arrow.parquet.variant`] extension type.
55///
56/// Iceberg stores a Variant as a `Struct { metadata: Binary, value: Binary }`. Attaching this
57/// extension type to the enclosing field marks that struct as a single logical Variant value,
58/// so Arrow consumers treat it as a Variant rather than an anonymous struct. It carries no
59/// metadata.
60///
61/// [`arrow.parquet.variant`]: https://arrow.apache.org/docs/format/CanonicalExtensions.html#parquet-variant
62#[derive(Debug, Clone, Copy, Default)]
63pub(crate) struct VariantExtensionType;
64
65impl ExtensionType for VariantExtensionType {
66    const NAME: &'static str = "arrow.parquet.variant";
67
68    type Metadata = ();
69
70    fn metadata(&self) -> &Self::Metadata {
71        &()
72    }
73
74    fn serialize_metadata(&self) -> Option<String> {
75        None
76    }
77
78    fn deserialize_metadata(
79        metadata: Option<&str>,
80    ) -> std::result::Result<Self::Metadata, ArrowError> {
81        match metadata {
82            None | Some("") => Ok(()),
83            Some(other) => Err(ArrowError::InvalidArgumentError(format!(
84                "arrow.parquet.variant extension type takes no metadata, got {other:?}"
85            ))),
86        }
87    }
88
89    fn supports_data_type(&self, data_type: &DataType) -> std::result::Result<(), ArrowError> {
90        match data_type {
91            DataType::Struct(_) => Ok(()),
92            other => Err(ArrowError::InvalidArgumentError(format!(
93                "arrow.parquet.variant extension type requires a Struct storage type, got {other}"
94            ))),
95        }
96    }
97
98    fn try_new(
99        data_type: &DataType,
100        _metadata: Self::Metadata,
101    ) -> std::result::Result<Self, ArrowError> {
102        Self.supports_data_type(data_type)?;
103        Ok(Self)
104    }
105}
106
107fn to_arrow_wkb_edges(algorithm: EdgeInterpolationAlgorithm) -> WkbEdges {
108    match algorithm {
109        EdgeInterpolationAlgorithm::Spherical => WkbEdges::Spherical,
110        EdgeInterpolationAlgorithm::Vincenty => WkbEdges::Vincenty,
111        EdgeInterpolationAlgorithm::Thomas => WkbEdges::Thomas,
112        EdgeInterpolationAlgorithm::Andoyer => WkbEdges::Andoyer,
113        EdgeInterpolationAlgorithm::Karney => WkbEdges::Karney,
114    }
115}
116
117impl From<WkbEdges> for EdgeInterpolationAlgorithm {
118    fn from(edges: WkbEdges) -> Self {
119        match edges {
120            WkbEdges::Spherical => Self::Spherical,
121            WkbEdges::Vincenty => Self::Vincenty,
122            WkbEdges::Thomas => Self::Thomas,
123            WkbEdges::Andoyer => Self::Andoyer,
124            WkbEdges::Karney => Self::Karney,
125        }
126    }
127}
128
129fn iceberg_crs_from_wkb_metadata(metadata: &WkbMetadata) -> Result<String> {
130    match metadata.crs.as_ref() {
131        None => Ok(UNSET_GEOSPATIAL_CRS.to_string()),
132        Some(serde_json::Value::String(crs)) => Ok(crs.clone()),
133        Some(serde_json::Value::Object(crs)) => {
134            let id = crs.get("id");
135            let authority = id
136                .and_then(|id| id.get("authority"))
137                .and_then(serde_json::Value::as_str)
138                .filter(|authority| !authority.is_empty());
139            let code = match id.and_then(|id| id.get("code")) {
140                Some(serde_json::Value::String(code)) => Some(code.clone()),
141                Some(serde_json::Value::Number(code)) => Some(code.to_string()),
142                _ => None,
143            };
144
145            match (authority, code) {
146                (Some(authority), Some(code)) => Ok(format!("{authority}:{code}")),
147                _ => Err(Error::new(
148                    ErrorKind::DataInvalid,
149                    "Cannot determine Iceberg geospatial type from PROJJSON CRS without an embedded authority/code",
150                )),
151            }
152        }
153        Some(_) => Err(Error::new(
154            ErrorKind::DataInvalid,
155            "Geospatial CRS metadata must be a string or PROJJSON object",
156        )),
157    }
158}
159
160/// A post order arrow schema visitor.
161///
162/// For order of methods called, please refer to the internal `visit_schema` function.
163pub trait ArrowSchemaVisitor {
164    /// Return type of this visitor on arrow field.
165    type T;
166
167    /// Return type of this visitor on arrow schema.
168    type U;
169
170    /// Called before each field of the schema or of a struct, whatever its type.
171    /// List elements and map keys and values use [`Self::before_list_element`],
172    /// [`Self::before_map_key`] and [`Self::before_map_value`] instead.
173    fn before_field(&mut self, _field: &FieldRef) -> Result<()> {
174        Ok(())
175    }
176
177    /// Called after each field of the schema or of a struct, whatever its type.
178    /// List elements and map keys and values use [`Self::after_list_element`],
179    /// [`Self::after_map_key`] and [`Self::after_map_value`] instead.
180    fn after_field(&mut self, _field: &FieldRef) -> Result<()> {
181        Ok(())
182    }
183
184    /// Called before list element.
185    fn before_list_element(&mut self, _field: &FieldRef) -> Result<()> {
186        Ok(())
187    }
188
189    /// Called after list element.
190    fn after_list_element(&mut self, _field: &FieldRef) -> Result<()> {
191        Ok(())
192    }
193
194    /// Called before map key.
195    fn before_map_key(&mut self, _field: &FieldRef) -> Result<()> {
196        Ok(())
197    }
198
199    /// Called after map key.
200    fn after_map_key(&mut self, _field: &FieldRef) -> Result<()> {
201        Ok(())
202    }
203
204    /// Called before map value.
205    fn before_map_value(&mut self, _field: &FieldRef) -> Result<()> {
206        Ok(())
207    }
208
209    /// Called after map value.
210    fn after_map_value(&mut self, _field: &FieldRef) -> Result<()> {
211        Ok(())
212    }
213
214    /// Called after schema's type visited.
215    fn schema(&mut self, schema: &ArrowSchema, values: Vec<Self::T>) -> Result<Self::U>;
216
217    /// Called after struct's fields visited.
218    fn r#struct(&mut self, fields: &Fields, results: Vec<Self::T>) -> Result<Self::T>;
219
220    /// Called after list fields visited.
221    fn list(&mut self, list: &DataType, value: Self::T) -> Result<Self::T>;
222
223    /// Called after map's key and value fields visited.
224    fn map(&mut self, map: &DataType, key_value: Self::T, value: Self::T) -> Result<Self::T>;
225
226    /// Called when see a primitive type.
227    fn primitive(&mut self, p: &DataType) -> Result<Self::T>;
228
229    /// Called when a field carries the `arrow.parquet.variant` extension type.
230    ///
231    /// The default treats the variant as its underlying Arrow struct storage: it
232    /// re-enters normal traversal, preserving the behavior of visitors that don't
233    /// special-case variants. A visitor that produces Iceberg types (or otherwise
234    /// needs variant identity) should override this to fold the struct into a
235    /// single logical variant.
236    ///
237    /// Takes the `&FieldRef` rather than a `&DataType` because the variant signal
238    /// lives on the field's metadata, not on its data type.
239    fn variant(&mut self, field: &FieldRef) -> Result<Self::T>
240    where Self: Sized {
241        visit_type(field.data_type(), self)
242    }
243
244    /// Called when a field carries the `arrow.uuid` extension type.
245    ///
246    /// The default treats the field as its underlying Arrow storage, preserving
247    /// the behavior of visitors that don't special-case UUIDs. A visitor that
248    /// produces Iceberg types should override this to fold the storage into a
249    /// single logical UUID.
250    ///
251    /// Takes the `&FieldRef` rather than a `&DataType` because the UUID signal
252    /// lives on the field's metadata, not on its data type.
253    fn uuid(&mut self, field: &FieldRef) -> Result<Self::T>
254    where Self: Sized {
255        visit_type(field.data_type(), self)
256    }
257}
258
259/// Visiting a type in post order.
260fn visit_type<V: ArrowSchemaVisitor>(r#type: &DataType, visitor: &mut V) -> Result<V::T> {
261    match r#type {
262        p if p.is_primitive()
263            || matches!(
264                p,
265                DataType::Boolean
266                    | DataType::Utf8
267                    | DataType::LargeUtf8
268                    | DataType::Utf8View
269                    | DataType::Null
270                    | DataType::Binary
271                    | DataType::LargeBinary
272                    | DataType::BinaryView
273                    | DataType::FixedSizeBinary(_)
274            ) =>
275        {
276            visitor.primitive(p)
277        }
278        DataType::List(element_field) => visit_list(r#type, element_field, visitor),
279        DataType::LargeList(element_field) => visit_list(r#type, element_field, visitor),
280        DataType::FixedSizeList(element_field, _) => visit_list(r#type, element_field, visitor),
281        DataType::Map(field, _) => match field.data_type() {
282            DataType::Struct(fields) => {
283                if fields.len() != 2 {
284                    return Err(invalid_data!("Map field must have exactly 2 fields"));
285                }
286
287                let key_field = &fields[0];
288                let value_field = &fields[1];
289
290                let key_result = {
291                    visitor.before_map_key(key_field)?;
292                    let ret = visit_field(key_field, visitor)?;
293                    visitor.after_map_key(key_field)?;
294                    ret
295                };
296
297                let value_result = {
298                    visitor.before_map_value(value_field)?;
299                    let ret = visit_field(value_field, visitor)?;
300                    visitor.after_map_value(value_field)?;
301                    ret
302                };
303
304                visitor.map(r#type, key_result, value_result)
305            }
306            _ => Err(invalid_data!("Map field must have struct type")),
307        },
308        DataType::Struct(fields) => visit_struct(fields, visitor),
309        DataType::Dictionary(_key_type, value_type) => visit_type(value_type, visitor),
310        other => Err(invalid_data!("Cannot visit Arrow data type: {other}")),
311    }
312}
313
314/// Dispatch a field, and fold it into following extension types when carried:
315///  - `arrow.parquet.variant` -- fold into a variant,
316///  - `arrow.uuid` -- fold it into a UUID type,
317///  - and in all other cases we visit it's data.
318fn visit_field<V: ArrowSchemaVisitor>(field: &FieldRef, visitor: &mut V) -> Result<V::T> {
319    if field.extension_type_name() == Some(VariantExtensionType::NAME) {
320        visitor.variant(field)
321    } else if field.extension_type_name() == Some(UuidExtensionType::NAME) {
322        visitor.uuid(field)
323    } else {
324        visit_type(field.data_type(), visitor)
325    }
326}
327
328/// Visit list types in post order.
329fn visit_list<V: ArrowSchemaVisitor>(
330    data_type: &DataType,
331    element_field: &FieldRef,
332    visitor: &mut V,
333) -> Result<V::T> {
334    visitor.before_list_element(element_field)?;
335    let value = visit_field(element_field, visitor)?;
336    visitor.after_list_element(element_field)?;
337    visitor.list(data_type, value)
338}
339
340/// Visit struct type in post order.
341fn visit_struct<V: ArrowSchemaVisitor>(fields: &Fields, visitor: &mut V) -> Result<V::T> {
342    let mut results = Vec::with_capacity(fields.len());
343    for field in fields {
344        visitor.before_field(field)?;
345        let result = visit_field(field, visitor)?;
346        visitor.after_field(field)?;
347        results.push(result);
348    }
349
350    visitor.r#struct(fields, results)
351}
352
353/// Visit schema in post order.
354pub(crate) fn visit_schema<V: ArrowSchemaVisitor>(
355    schema: &ArrowSchema,
356    visitor: &mut V,
357) -> Result<V::U> {
358    let mut results = Vec::with_capacity(schema.fields().len());
359    for field in schema.fields() {
360        visitor.before_field(field)?;
361        let result = visit_field(field, visitor)?;
362        visitor.after_field(field)?;
363        results.push(result);
364    }
365    visitor.schema(schema, results)
366}
367
368/// Convert Arrow schema to Iceberg schema.
369///
370/// Iceberg schema fields require a unique field id, and this function assumes that each field
371/// in the provided Arrow schema contains a field id in its metadata. If the metadata is missing
372/// or the field id is not set, the conversion will fail
373pub fn arrow_schema_to_schema(schema: &ArrowSchema) -> Result<Schema> {
374    let mut visitor = ArrowSchemaConverter::new();
375    visit_schema(schema, &mut visitor)
376}
377
378/// Convert Arrow schema to Iceberg schema with automatically assigned field IDs.
379///
380/// Unlike [`arrow_schema_to_schema`], this function does not require field IDs in the Arrow
381/// schema metadata. Instead, it automatically assigns unique field IDs starting from 1,
382/// following Iceberg's field ID assignment rules.
383///
384/// This is useful when converting Arrow schemas that don't originate from Iceberg tables,
385/// such as schemas from DataFusion or other Arrow-based systems.
386pub fn arrow_schema_to_schema_auto_assign_ids(schema: &ArrowSchema) -> Result<Schema> {
387    let mut visitor = ArrowSchemaConverter::new_with_field_ids_from(FIRST_FIELD_ID);
388    visit_schema(schema, &mut visitor)
389}
390
391/// Convert Arrow type to iceberg type.
392pub fn arrow_type_to_type(ty: &DataType) -> Result<Type> {
393    let mut visitor = ArrowSchemaConverter::new();
394    visit_type(ty, &mut visitor)
395}
396
397const ARROW_FIELD_DOC_KEY: &str = "doc";
398
399pub(super) fn try_get_field_id_from_metadata(field: &Field) -> Result<Option<i32>> {
400    field
401        .metadata()
402        .get(PARQUET_FIELD_ID_META_KEY)
403        .map(|value| {
404            value.parse::<i32>().map_err(|e| {
405                invalid_data!("Failed to parse field id")
406                    .with_context("value", value)
407                    .with_source(e)
408            })
409        })
410        .transpose()
411}
412
413pub(super) fn get_field_id_from_metadata(field: &FieldRef) -> Result<i32> {
414    try_get_field_id_from_metadata(field)?
415        .ok_or_else(|| invalid_data!("Field id not found in metadata"))
416}
417
418fn get_field_doc(field: &FieldRef) -> Option<String> {
419    if let Some(value) = field.metadata().get(ARROW_FIELD_DOC_KEY) {
420        return Some(value.clone());
421    }
422    None
423}
424
425struct ArrowSchemaConverter {
426    /// When set, the schema builder will reassign field IDs starting from this value
427    /// using level-order traversal (breadth-first).
428    reassign_field_ids_from: Option<i32>,
429    /// Generates unique placeholder IDs for fields before reassignment.
430    /// Required because `ReassignFieldIds` builds an old-to-new ID mapping
431    /// that expects unique input IDs.
432    next_field_id: i32,
433}
434
435impl ArrowSchemaConverter {
436    fn new() -> Self {
437        Self {
438            reassign_field_ids_from: None,
439            next_field_id: 0,
440        }
441    }
442
443    fn new_with_field_ids_from(start_from: i32) -> Self {
444        Self {
445            reassign_field_ids_from: Some(start_from),
446            next_field_id: 0,
447        }
448    }
449
450    fn get_field_id(&mut self, field: &FieldRef) -> Result<i32> {
451        if self.reassign_field_ids_from.is_some() {
452            // Field IDs will be reassigned by the schema builder.
453            // We need unique temporary IDs because ReassignFieldIds builds an
454            // old->new ID mapping that requires unique input IDs.
455            let temp_id = self.next_field_id;
456            self.next_field_id += 1;
457            Ok(temp_id)
458        } else {
459            // Get field ID from arrow field metadata
460            get_field_id_from_metadata(field)
461        }
462    }
463
464    fn convert_fields(
465        &mut self,
466        fields: &Fields,
467        field_results: &[Type],
468    ) -> Result<Vec<NestedFieldRef>> {
469        let mut results = Vec::with_capacity(fields.len());
470        for i in 0..fields.len() {
471            let field = &fields[i];
472            let field_type = self.apply_arrow_extension(field, field_results[i].clone())?;
473            let id = self.get_field_id(field)?;
474            let doc = get_field_doc(field);
475            let nested_field = NestedField {
476                id,
477                doc,
478                name: field.name().clone(),
479                required: !field.is_nullable(),
480                field_type: Box::new(field_type),
481                initial_default: None,
482                write_default: None,
483            };
484            results.push(Arc::new(nested_field));
485        }
486        Ok(results)
487    }
488
489    fn apply_arrow_extension(&self, arrow_field: &FieldRef, iceberg_type: Type) -> Result<Type> {
490        if arrow_field.extension_type_name() != Some(WkbType::NAME) {
491            return Ok(iceberg_type);
492        }
493
494        if !matches!(&iceberg_type, Type::Primitive(PrimitiveType::Binary)) {
495            return Err(Error::new(
496                ErrorKind::DataInvalid,
497                format!(
498                    "WKB extension type on field {} requires binary storage, got {iceberg_type}",
499                    arrow_field.name()
500                ),
501            ));
502        }
503
504        let wkb_type = arrow_field.try_extension_type::<WkbType>().map_err(|err| {
505            Error::new(
506                ErrorKind::DataInvalid,
507                format!(
508                    "Invalid geospatial Arrow extension metadata for field {}",
509                    arrow_field.name()
510                ),
511            )
512            .with_source(err)
513        })?;
514
515        let crs = iceberg_crs_from_wkb_metadata(wkb_type.metadata())?;
516
517        match wkb_type.metadata().type_hint() {
518            WkbTypeHint::Geometry => Ok(Type::Primitive(PrimitiveType::Geometry(
519                GeometryType::new(Some(crs))?,
520            ))),
521            WkbTypeHint::Geography => Ok(Type::Primitive(PrimitiveType::Geography(
522                GeographyType::new(
523                    Some(crs),
524                    wkb_type
525                        .metadata()
526                        .algorithm
527                        .unwrap_or(WkbEdges::Spherical)
528                        .into(),
529                )?,
530            ))),
531        }
532    }
533}
534
535impl ArrowSchemaVisitor for ArrowSchemaConverter {
536    type T = Type;
537    type U = Schema;
538
539    fn schema(&mut self, schema: &ArrowSchema, values: Vec<Self::T>) -> Result<Self::U> {
540        let fields = self.convert_fields(schema.fields(), &values)?;
541        let mut builder = Schema::builder().with_fields(fields);
542        if let Some(start_from) = self.reassign_field_ids_from {
543            builder = builder.with_reassigned_field_ids(start_from)
544        }
545        builder.build()
546    }
547
548    fn r#struct(&mut self, fields: &Fields, results: Vec<Self::T>) -> Result<Self::T> {
549        let fields = self.convert_fields(fields, &results)?;
550        Ok(Type::Struct(StructType::new(fields)))
551    }
552
553    fn list(&mut self, list: &DataType, value: Self::T) -> Result<Self::T> {
554        let element_field = match list {
555            DataType::List(element_field) => element_field,
556            DataType::LargeList(element_field) => element_field,
557            DataType::FixedSizeList(element_field, _) => element_field,
558            _ => {
559                return Err(invalid_data!("List type must have list data type"));
560            }
561        };
562
563        let value = self.apply_arrow_extension(element_field, value)?;
564        let id = self.get_field_id(element_field)?;
565        let doc = get_field_doc(element_field);
566        let mut element_field =
567            NestedField::list_element(id, value.clone(), !element_field.is_nullable());
568        if let Some(doc) = doc {
569            element_field = element_field.with_doc(doc);
570        }
571        let element_field = Arc::new(element_field);
572        Ok(Type::List(ListType { element_field }))
573    }
574
575    fn map(&mut self, map: &DataType, key_value: Self::T, value: Self::T) -> Result<Self::T> {
576        match map {
577            DataType::Map(field, _) => match field.data_type() {
578                DataType::Struct(fields) => {
579                    if fields.len() != 2 {
580                        return Err(invalid_data!("Map field must have exactly 2 fields"));
581                    }
582
583                    let key_field = &fields[0];
584                    let value_field = &fields[1];
585                    let key_value = self.apply_arrow_extension(key_field, key_value)?;
586                    let value = self.apply_arrow_extension(value_field, value)?;
587
588                    let key_id = self.get_field_id(key_field)?;
589                    let key_doc = get_field_doc(key_field);
590                    let mut key_field = NestedField::map_key_element(key_id, key_value.clone());
591                    if let Some(doc) = key_doc {
592                        key_field = key_field.with_doc(doc);
593                    }
594                    let key_field = Arc::new(key_field);
595
596                    let value_id = self.get_field_id(value_field)?;
597                    let value_doc = get_field_doc(value_field);
598                    let mut value_field = NestedField::map_value_element(
599                        value_id,
600                        value.clone(),
601                        !value_field.is_nullable(),
602                    );
603                    if let Some(doc) = value_doc {
604                        value_field = value_field.with_doc(doc);
605                    }
606                    let value_field = Arc::new(value_field);
607
608                    Ok(Type::Map(MapType {
609                        key_field,
610                        value_field,
611                    }))
612                }
613                _ => Err(invalid_data!("Map field must have struct type")),
614            },
615            _ => Err(invalid_data!("Map type must have map data type")),
616        }
617    }
618
619    fn primitive(&mut self, p: &DataType) -> Result<Self::T> {
620        match p {
621            DataType::Null => Ok(Type::Primitive(PrimitiveType::Unknown)),
622            DataType::Boolean => Ok(Type::Primitive(PrimitiveType::Boolean)),
623            DataType::Int8 | DataType::Int16 | DataType::Int32 => {
624                Ok(Type::Primitive(PrimitiveType::Int))
625            }
626            DataType::UInt8 | DataType::UInt16 => Ok(Type::Primitive(PrimitiveType::Int)),
627            DataType::UInt32 => Ok(Type::Primitive(PrimitiveType::Long)),
628            DataType::Int64 => Ok(Type::Primitive(PrimitiveType::Long)),
629            DataType::UInt64 => {
630                // Block uint64 - no safe casting option
631                Err(invalid_data!(
632                    "UInt64 is not supported. Use Int64 for values ≤ 9,223,372,036,854,775,807 or Decimal(20,0) for full uint64 range."
633                ))
634            }
635            DataType::Float32 => Ok(Type::Primitive(PrimitiveType::Float)),
636            DataType::Float64 => Ok(Type::Primitive(PrimitiveType::Double)),
637            DataType::Decimal128(p, s) => Type::decimal(*p as u32, *s as u32)
638                .map_err(|e| invalid_data!("Failed to create decimal type").with_source(e)),
639            DataType::Date32 => Ok(Type::Primitive(PrimitiveType::Date)),
640            DataType::Time64(unit) if unit == &TimeUnit::Microsecond => {
641                Ok(Type::Primitive(PrimitiveType::Time))
642            }
643            DataType::Timestamp(unit, None) if unit == &TimeUnit::Microsecond => {
644                Ok(Type::Primitive(PrimitiveType::Timestamp))
645            }
646            DataType::Timestamp(unit, None) if unit == &TimeUnit::Nanosecond => {
647                Ok(Type::Primitive(PrimitiveType::TimestampNs))
648            }
649            DataType::Timestamp(unit, Some(zone))
650                if unit == &TimeUnit::Microsecond
651                    && (zone.as_ref() == "UTC" || zone.as_ref() == "+00:00") =>
652            {
653                Ok(Type::Primitive(PrimitiveType::Timestamptz))
654            }
655            DataType::Timestamp(unit, Some(zone))
656                if unit == &TimeUnit::Nanosecond
657                    && (zone.as_ref() == "UTC" || zone.as_ref() == "+00:00") =>
658            {
659                Ok(Type::Primitive(PrimitiveType::TimestamptzNs))
660            }
661            DataType::Binary | DataType::LargeBinary | DataType::BinaryView => {
662                Ok(Type::Primitive(PrimitiveType::Binary))
663            }
664            DataType::FixedSizeBinary(width) => {
665                Ok(Type::Primitive(PrimitiveType::Fixed(*width as u64)))
666            }
667            DataType::Utf8View | DataType::Utf8 | DataType::LargeUtf8 => {
668                Ok(Type::Primitive(PrimitiveType::String))
669            }
670            _ => Err(invalid_data!("Unsupported Arrow data type: {p}")),
671        }
672    }
673
674    fn variant(&mut self, field: &FieldRef) -> Result<Self::T> {
675        // The extension may only sit on struct storage (mirrors
676        // `VariantExtensionType::supports_data_type`).
677        if !matches!(field.data_type(), DataType::Struct(_)) {
678            return Err(invalid_data!(
679                "arrow.parquet.variant extension requires Struct storage"
680            ));
681        }
682        // Fold the whole struct into a single logical variant without descending:
683        // the storage sub-fields carry no Iceberg field id, so visiting them would
684        // fail. The enclosing field's own id is read by the caller.
685        Ok(Type::Variant(VariantType))
686    }
687
688    fn uuid(&mut self, field: &FieldRef) -> Result<Self::T> {
689        // The extension expects a fixed size binary.
690        // See: Apache Arrow spec https://arrow.apache.org/docs/format/CanonicalExtensions.html#uuid
691        let field_data_type = field.data_type();
692        let expected_data_type = DataType::FixedSizeBinary(16);
693        if *field_data_type != expected_data_type {
694            let error_message = format!(
695                "{} extension requires {expected_data_type} storage, found {field_data_type}",
696                UuidExtensionType::NAME,
697            );
698
699            return Err(Error::new(ErrorKind::DataInvalid, error_message));
700        }
701
702        Ok(Type::Primitive(PrimitiveType::Uuid))
703    }
704}
705
706struct ToArrowSchemaConverter;
707
708enum ArrowSchemaOrFieldOrType {
709    Schema(ArrowSchema),
710    Field(Field),
711    Type(DataType),
712}
713
714impl SchemaVisitor for ToArrowSchemaConverter {
715    type T = ArrowSchemaOrFieldOrType;
716
717    fn schema(
718        &mut self,
719        _schema: &Schema,
720        value: ArrowSchemaOrFieldOrType,
721    ) -> Result<ArrowSchemaOrFieldOrType> {
722        let struct_type = match value {
723            ArrowSchemaOrFieldOrType::Type(DataType::Struct(fields)) => fields,
724            _ => unreachable!(),
725        };
726        Ok(ArrowSchemaOrFieldOrType::Schema(ArrowSchema::new(
727            struct_type,
728        )))
729    }
730
731    fn field(
732        &mut self,
733        field: &NestedFieldRef,
734        value: ArrowSchemaOrFieldOrType,
735    ) -> Result<ArrowSchemaOrFieldOrType> {
736        let ty = match value {
737            ArrowSchemaOrFieldOrType::Type(ty) => ty,
738            _ => unreachable!(),
739        };
740        let metadata = if let Some(doc) = &field.doc {
741            HashMap::from([
742                (PARQUET_FIELD_ID_META_KEY.to_string(), field.id.to_string()),
743                (ARROW_FIELD_DOC_KEY.to_string(), doc.clone()),
744            ])
745        } else {
746            HashMap::from([(PARQUET_FIELD_ID_META_KEY.to_string(), field.id.to_string())])
747        };
748        let mut arrow_field =
749            Field::new(field.name.clone(), ty, !field.required).with_metadata(metadata);
750
751        match field.field_type.as_ref() {
752            Type::Variant(_) => {
753                // A variant column's storage is a struct; tag the field with the canonical
754                // `arrow.parquet.variant` extension type so consumers read it as a Variant, not a struct.
755                arrow_field.try_with_extension_type(VariantExtensionType)?;
756            }
757            Type::Primitive(PrimitiveType::Uuid) => {
758                // Preserve the canonical UUID extension on its FixedSizeBinary(16) storage.
759                arrow_field.try_with_extension_type(UuidExtensionType)?;
760            }
761            Type::Primitive(PrimitiveType::Geometry(geometry)) => {
762                let metadata = WkbMetadata::new(Some(geometry.crs()), None);
763                arrow_field.try_with_extension_type(WkbType::new(Some(metadata)))?;
764            }
765            Type::Primitive(PrimitiveType::Geography(geography)) => {
766                let metadata = WkbMetadata::new(
767                    Some(geography.crs()),
768                    Some(to_arrow_wkb_edges(geography.algorithm())),
769                );
770                arrow_field.try_with_extension_type(WkbType::new(Some(metadata)))?;
771            }
772            _ => {}
773        }
774
775        Ok(ArrowSchemaOrFieldOrType::Field(arrow_field))
776    }
777
778    fn r#struct(
779        &mut self,
780        _: &StructType,
781        results: Vec<ArrowSchemaOrFieldOrType>,
782    ) -> Result<ArrowSchemaOrFieldOrType> {
783        let fields = results
784            .into_iter()
785            .map(|result| match result {
786                ArrowSchemaOrFieldOrType::Field(field) => field,
787                _ => unreachable!(),
788            })
789            .collect();
790        Ok(ArrowSchemaOrFieldOrType::Type(DataType::Struct(fields)))
791    }
792
793    fn list(&mut self, list: &ListType, value: ArrowSchemaOrFieldOrType) -> Result<Self::T> {
794        // `field` already carries the element's field id, doc, and — for a variant element —
795        // the arrow.parquet.variant extension type. Don't overwrite its metadata here (doing so
796        // would drop the extension type for `list<variant>`).
797        let field = match self.field(&list.element_field, value)? {
798            ArrowSchemaOrFieldOrType::Field(field) => field,
799            _ => unreachable!(),
800        };
801        Ok(ArrowSchemaOrFieldOrType::Type(DataType::List(Arc::new(
802            field,
803        ))))
804    }
805
806    fn map(
807        &mut self,
808        map: &MapType,
809        key_value: ArrowSchemaOrFieldOrType,
810        value: ArrowSchemaOrFieldOrType,
811    ) -> Result<ArrowSchemaOrFieldOrType> {
812        let key_field = match self.field(&map.key_field, key_value)? {
813            ArrowSchemaOrFieldOrType::Field(field) => field,
814            _ => unreachable!(),
815        };
816        let value_field = match self.field(&map.value_field, value)? {
817            ArrowSchemaOrFieldOrType::Field(field) => field,
818            _ => unreachable!(),
819        };
820        let field = Field::new(
821            DEFAULT_MAP_FIELD_NAME,
822            DataType::Struct(vec![key_field, value_field].into()),
823            // Map field is always not nullable
824            false,
825        );
826
827        Ok(ArrowSchemaOrFieldOrType::Type(DataType::Map(
828            field.into(),
829            false,
830        )))
831    }
832
833    fn primitive(&mut self, p: &PrimitiveType) -> Result<ArrowSchemaOrFieldOrType> {
834        match p {
835            PrimitiveType::Unknown => Ok(ArrowSchemaOrFieldOrType::Type(DataType::Null)),
836            PrimitiveType::Boolean => Ok(ArrowSchemaOrFieldOrType::Type(DataType::Boolean)),
837            PrimitiveType::Int => Ok(ArrowSchemaOrFieldOrType::Type(DataType::Int32)),
838            PrimitiveType::Long => Ok(ArrowSchemaOrFieldOrType::Type(DataType::Int64)),
839            PrimitiveType::Float => Ok(ArrowSchemaOrFieldOrType::Type(DataType::Float32)),
840            PrimitiveType::Double => Ok(ArrowSchemaOrFieldOrType::Type(DataType::Float64)),
841            PrimitiveType::Decimal { precision, scale } => {
842                let (precision, scale) = {
843                    let precision: u8 = precision.to_owned().try_into().map_err(|err| {
844                        invalid_data!("incompatible precision for decimal type convert")
845                            .with_source(err)
846                    })?;
847                    let scale = scale.to_owned().try_into().map_err(|err| {
848                        invalid_data!("incompatible scale for decimal type convert")
849                            .with_source(err)
850                    })?;
851                    (precision, scale)
852                };
853                validate_decimal_precision_and_scale::<Decimal128Type>(precision, scale).map_err(
854                    |err| {
855                        invalid_data!("incompatible precision and scale for decimal type convert")
856                            .with_source(err)
857                    },
858                )?;
859                Ok(ArrowSchemaOrFieldOrType::Type(DataType::Decimal128(
860                    precision, scale,
861                )))
862            }
863            PrimitiveType::Date => Ok(ArrowSchemaOrFieldOrType::Type(DataType::Date32)),
864            PrimitiveType::Time => Ok(ArrowSchemaOrFieldOrType::Type(DataType::Time64(
865                TimeUnit::Microsecond,
866            ))),
867            PrimitiveType::Timestamp => Ok(ArrowSchemaOrFieldOrType::Type(DataType::Timestamp(
868                TimeUnit::Microsecond,
869                None,
870            ))),
871            PrimitiveType::Timestamptz => Ok(ArrowSchemaOrFieldOrType::Type(
872                // Timestampz always stored as UTC
873                DataType::Timestamp(TimeUnit::Microsecond, Some(UTC_TIME_ZONE.into())),
874            )),
875            PrimitiveType::TimestampNs => Ok(ArrowSchemaOrFieldOrType::Type(DataType::Timestamp(
876                TimeUnit::Nanosecond,
877                None,
878            ))),
879            PrimitiveType::TimestamptzNs => Ok(ArrowSchemaOrFieldOrType::Type(
880                // Store timestamptz_ns as UTC
881                DataType::Timestamp(TimeUnit::Nanosecond, Some(UTC_TIME_ZONE.into())),
882            )),
883            PrimitiveType::String => Ok(ArrowSchemaOrFieldOrType::Type(DataType::Utf8)),
884            PrimitiveType::Uuid => Ok(ArrowSchemaOrFieldOrType::Type(DataType::FixedSizeBinary(
885                16,
886            ))),
887            PrimitiveType::Fixed(len) => Ok(ArrowSchemaOrFieldOrType::Type(
888                i32::try_from(*len)
889                    .ok()
890                    .map(DataType::FixedSizeBinary)
891                    .unwrap_or(DataType::LargeBinary),
892            )),
893            PrimitiveType::Binary | PrimitiveType::Geometry(_) | PrimitiveType::Geography(_) => {
894                Ok(ArrowSchemaOrFieldOrType::Type(DataType::LargeBinary))
895            }
896        }
897    }
898
899    fn variant(&mut self, _v: &VariantType) -> Result<ArrowSchemaOrFieldOrType> {
900        // Variant is stored as a struct of two binary sub-fields (no field IDs on sub-fields).
901        // Uses Binary (not LargeBinary) matching the Parquet BINARY primitive directly.
902        // `metadata` is always present; `value` is nullable, since in a shredded variant the
903        // value may be absent. The enclosing field carries the `arrow.parquet.variant` extension type
904        // (attached in `field`).
905        let metadata_field = Field::new("metadata", DataType::Binary, false);
906        let value_field = Field::new("value", DataType::Binary, true);
907        Ok(ArrowSchemaOrFieldOrType::Type(DataType::Struct(
908            vec![metadata_field, value_field].into(),
909        )))
910    }
911}
912
913/// Convert iceberg schema to an arrow schema.
914pub fn schema_to_arrow_schema(schema: &Schema) -> Result<ArrowSchema> {
915    let mut converter = ToArrowSchemaConverter;
916    match crate::spec::visit_schema(schema, &mut converter)? {
917        ArrowSchemaOrFieldOrType::Schema(schema) => Ok(schema),
918        _ => unreachable!(),
919    }
920}
921
922/// Convert iceberg type to an arrow type.
923pub fn type_to_arrow_type(ty: &Type) -> Result<DataType> {
924    let mut converter = ToArrowSchemaConverter;
925    match crate::spec::visit_type(ty, &mut converter)? {
926        ArrowSchemaOrFieldOrType::Type(ty) => Ok(ty),
927        _ => unreachable!(),
928    }
929}
930
931/// Convert Iceberg Datum to Arrow Datum.
932pub(crate) fn get_arrow_datum(datum: &Datum) -> Result<Arc<dyn ArrowDatum + Send + Sync>> {
933    match (datum.data_type(), datum.literal()) {
934        (PrimitiveType::Boolean, PrimitiveLiteral::Boolean(value)) => {
935            Ok(Arc::new(BooleanArray::new_scalar(*value)))
936        }
937        (PrimitiveType::Int, PrimitiveLiteral::Int(value)) => {
938            Ok(Arc::new(Int32Array::new_scalar(*value)))
939        }
940        (PrimitiveType::Long, PrimitiveLiteral::Long(value)) => {
941            Ok(Arc::new(Int64Array::new_scalar(*value)))
942        }
943        (PrimitiveType::Float, PrimitiveLiteral::Float(value)) => {
944            Ok(Arc::new(Float32Array::new_scalar(value.into_inner())))
945        }
946        (PrimitiveType::Double, PrimitiveLiteral::Double(value)) => {
947            Ok(Arc::new(Float64Array::new_scalar(value.into_inner())))
948        }
949        (PrimitiveType::String, PrimitiveLiteral::String(value)) => {
950            Ok(Arc::new(StringArray::new_scalar(value.as_str())))
951        }
952        (PrimitiveType::Binary, PrimitiveLiteral::Binary(value)) => {
953            Ok(Arc::new(BinaryArray::new_scalar(value.as_slice())))
954        }
955        (PrimitiveType::Date, PrimitiveLiteral::Int(value)) => {
956            Ok(Arc::new(Date32Array::new_scalar(*value)))
957        }
958        (PrimitiveType::Timestamp, PrimitiveLiteral::Long(value)) => {
959            Ok(Arc::new(TimestampMicrosecondArray::new_scalar(*value)))
960        }
961        (PrimitiveType::Timestamptz, PrimitiveLiteral::Long(value)) => Ok(Arc::new(Scalar::new(
962            TimestampMicrosecondArray::new(vec![*value; 1].into(), None).with_timezone_utc(),
963        ))),
964        (PrimitiveType::TimestampNs, PrimitiveLiteral::Long(value)) => {
965            Ok(Arc::new(TimestampNanosecondArray::new_scalar(*value)))
966        }
967        (PrimitiveType::TimestamptzNs, PrimitiveLiteral::Long(value)) => Ok(Arc::new(Scalar::new(
968            TimestampNanosecondArray::new(vec![*value; 1].into(), None).with_timezone_utc(),
969        ))),
970        (PrimitiveType::Decimal { precision, scale }, PrimitiveLiteral::Int128(value)) => {
971            let array = Decimal128Array::from_value(*value, 1)
972                .with_precision_and_scale(*precision as _, *scale as _)
973                .unwrap();
974            Ok(Arc::new(Scalar::new(array)))
975        }
976        (PrimitiveType::Uuid, PrimitiveLiteral::UInt128(value)) => {
977            let bytes = Uuid::from_u128(*value).into_bytes();
978            let array = FixedSizeBinaryArray::try_from_iter(vec![bytes].into_iter()).unwrap();
979            Ok(Arc::new(Scalar::new(array)))
980        }
981        (PrimitiveType::Fixed(_), PrimitiveLiteral::Binary(value)) => {
982            let array = FixedSizeBinaryArray::try_from_iter(std::iter::once(value.as_slice()))
983                .map_err(|e| invalid_data!("FixedSizeBinary conversion failed").with_source(e))?;
984            Ok(Arc::new(Scalar::new(array)))
985        }
986
987        (primitive_type, _) => Err(Error::new(
988            ErrorKind::FeatureUnsupported,
989            format!("Converting datum from type {primitive_type:?} to arrow not supported yet."),
990        )),
991    }
992}
993
994pub(crate) fn get_parquet_stat_min_as_datum(
995    primitive_type: &PrimitiveType,
996    stats: &Statistics,
997) -> Result<Option<Datum>> {
998    Ok(match (primitive_type, stats) {
999        (PrimitiveType::Boolean, Statistics::Boolean(stats)) => {
1000            stats.min_opt().map(|val| Datum::bool(*val))
1001        }
1002        (PrimitiveType::Int, Statistics::Int32(stats)) => {
1003            stats.min_opt().map(|val| Datum::int(*val))
1004        }
1005        (PrimitiveType::Date, Statistics::Int32(stats)) => {
1006            stats.min_opt().map(|val| Datum::date(*val))
1007        }
1008        (PrimitiveType::Long, Statistics::Int64(stats)) => {
1009            stats.min_opt().map(|val| Datum::long(*val))
1010        }
1011        (PrimitiveType::Time, Statistics::Int64(stats)) => {
1012            let Some(val) = stats.min_opt() else {
1013                return Ok(None);
1014            };
1015
1016            Some(Datum::time_micros(*val)?)
1017        }
1018        (PrimitiveType::Timestamp, Statistics::Int64(stats)) => {
1019            stats.min_opt().map(|val| Datum::timestamp_micros(*val))
1020        }
1021        (PrimitiveType::Timestamptz, Statistics::Int64(stats)) => {
1022            stats.min_opt().map(|val| Datum::timestamptz_micros(*val))
1023        }
1024        (PrimitiveType::TimestampNs, Statistics::Int64(stats)) => {
1025            stats.min_opt().map(|val| Datum::timestamp_nanos(*val))
1026        }
1027        (PrimitiveType::TimestamptzNs, Statistics::Int64(stats)) => {
1028            stats.min_opt().map(|val| Datum::timestamptz_nanos(*val))
1029        }
1030        (PrimitiveType::Float, Statistics::Float(stats)) => {
1031            stats.min_opt().map(|val| Datum::float(*val))
1032        }
1033        (PrimitiveType::Double, Statistics::Double(stats)) => {
1034            stats.min_opt().map(|val| Datum::double(*val))
1035        }
1036        (PrimitiveType::String, Statistics::ByteArray(stats)) => {
1037            let Some(val) = stats.min_opt() else {
1038                return Ok(None);
1039            };
1040
1041            Some(Datum::string(val.as_utf8()?))
1042        }
1043        (
1044            PrimitiveType::Decimal {
1045                precision: _,
1046                scale: _,
1047            },
1048            Statistics::ByteArray(stats),
1049        ) => {
1050            let Some(bytes) = stats.min_bytes_opt() else {
1051                return Ok(None);
1052            };
1053            Some(Datum::new(
1054                primitive_type.clone(),
1055                PrimitiveLiteral::Int128(
1056                    // An empty bound carries no value; reject it rather than
1057                    // decode it as zero, which could prune matching row groups.
1058                    i128_from_be_bytes(bytes)
1059                        .filter(|_| !bytes.is_empty())
1060                        .ok_or_else(|| invalid_data!("Can't convert bytes to i128: {bytes:?}"))?,
1061                ),
1062            ))
1063        }
1064        (
1065            PrimitiveType::Decimal {
1066                precision: _,
1067                scale: _,
1068            },
1069            Statistics::FixedLenByteArray(stats),
1070        ) => {
1071            let Some(bytes) = stats.min_bytes_opt() else {
1072                return Ok(None);
1073            };
1074            Some(Datum::new(
1075                primitive_type.clone(),
1076                PrimitiveLiteral::Int128(
1077                    i128_from_be_bytes(bytes)
1078                        .ok_or_else(|| invalid_data!("Can't convert bytes to i128: {bytes:?}"))?,
1079                ),
1080            ))
1081        }
1082        (
1083            PrimitiveType::Decimal {
1084                precision: _,
1085                scale: _,
1086            },
1087            Statistics::Int32(stats),
1088        ) => stats.min_opt().map(|val| {
1089            Datum::new(
1090                primitive_type.clone(),
1091                PrimitiveLiteral::Int128(i128::from(*val)),
1092            )
1093        }),
1094
1095        (
1096            PrimitiveType::Decimal {
1097                precision: _,
1098                scale: _,
1099            },
1100            Statistics::Int64(stats),
1101        ) => stats.min_opt().map(|val| {
1102            Datum::new(
1103                primitive_type.clone(),
1104                PrimitiveLiteral::Int128(i128::from(*val)),
1105            )
1106        }),
1107        (PrimitiveType::Uuid, Statistics::FixedLenByteArray(stats)) => {
1108            let Some(bytes) = stats.min_bytes_opt() else {
1109                return Ok(None);
1110            };
1111            if bytes.len() != 16 {
1112                return Err(Error::new(
1113                    ErrorKind::Unexpected,
1114                    "Invalid length of uuid bytes.",
1115                ));
1116            }
1117            Some(Datum::uuid(Uuid::from_bytes(
1118                bytes[..16].try_into().unwrap(),
1119            )))
1120        }
1121        (PrimitiveType::Fixed(len), Statistics::FixedLenByteArray(stat)) => {
1122            let Some(bytes) = stat.min_bytes_opt() else {
1123                return Ok(None);
1124            };
1125            if bytes.len() != *len as usize {
1126                return Err(Error::new(
1127                    ErrorKind::Unexpected,
1128                    "Invalid length of fixed bytes.",
1129                ));
1130            }
1131            Some(Datum::fixed(bytes.to_vec()))
1132        }
1133        (PrimitiveType::Binary, Statistics::ByteArray(stat)) => {
1134            return Ok(stat
1135                .min_bytes_opt()
1136                .map(|bytes| Datum::binary(bytes.to_vec())));
1137        }
1138        _ => {
1139            return Ok(None);
1140        }
1141    })
1142}
1143
1144pub(crate) fn get_parquet_stat_max_as_datum(
1145    primitive_type: &PrimitiveType,
1146    stats: &Statistics,
1147) -> Result<Option<Datum>> {
1148    Ok(match (primitive_type, stats) {
1149        (PrimitiveType::Boolean, Statistics::Boolean(stats)) => {
1150            stats.max_opt().map(|val| Datum::bool(*val))
1151        }
1152        (PrimitiveType::Int, Statistics::Int32(stats)) => {
1153            stats.max_opt().map(|val| Datum::int(*val))
1154        }
1155        (PrimitiveType::Date, Statistics::Int32(stats)) => {
1156            stats.max_opt().map(|val| Datum::date(*val))
1157        }
1158        (PrimitiveType::Long, Statistics::Int64(stats)) => {
1159            stats.max_opt().map(|val| Datum::long(*val))
1160        }
1161        (PrimitiveType::Time, Statistics::Int64(stats)) => {
1162            let Some(val) = stats.max_opt() else {
1163                return Ok(None);
1164            };
1165
1166            Some(Datum::time_micros(*val)?)
1167        }
1168        (PrimitiveType::Timestamp, Statistics::Int64(stats)) => {
1169            stats.max_opt().map(|val| Datum::timestamp_micros(*val))
1170        }
1171        (PrimitiveType::Timestamptz, Statistics::Int64(stats)) => {
1172            stats.max_opt().map(|val| Datum::timestamptz_micros(*val))
1173        }
1174        (PrimitiveType::TimestampNs, Statistics::Int64(stats)) => {
1175            stats.max_opt().map(|val| Datum::timestamp_nanos(*val))
1176        }
1177        (PrimitiveType::TimestamptzNs, Statistics::Int64(stats)) => {
1178            stats.max_opt().map(|val| Datum::timestamptz_nanos(*val))
1179        }
1180        (PrimitiveType::Float, Statistics::Float(stats)) => {
1181            stats.max_opt().map(|val| Datum::float(*val))
1182        }
1183        (PrimitiveType::Double, Statistics::Double(stats)) => {
1184            stats.max_opt().map(|val| Datum::double(*val))
1185        }
1186        (PrimitiveType::String, Statistics::ByteArray(stats)) => {
1187            let Some(val) = stats.max_opt() else {
1188                return Ok(None);
1189            };
1190
1191            Some(Datum::string(val.as_utf8()?))
1192        }
1193        (
1194            PrimitiveType::Decimal {
1195                precision: _,
1196                scale: _,
1197            },
1198            Statistics::ByteArray(stats),
1199        ) => {
1200            let Some(bytes) = stats.max_bytes_opt() else {
1201                return Ok(None);
1202            };
1203            Some(Datum::new(
1204                primitive_type.clone(),
1205                PrimitiveLiteral::Int128(
1206                    // An empty bound carries no value; reject it rather than
1207                    // decode it as zero, which could prune matching row groups.
1208                    i128_from_be_bytes(bytes)
1209                        .filter(|_| !bytes.is_empty())
1210                        .ok_or_else(|| invalid_data!("Can't convert bytes to i128: {bytes:?}"))?,
1211                ),
1212            ))
1213        }
1214        (
1215            PrimitiveType::Decimal {
1216                precision: _,
1217                scale: _,
1218            },
1219            Statistics::FixedLenByteArray(stats),
1220        ) => {
1221            let Some(bytes) = stats.max_bytes_opt() else {
1222                return Ok(None);
1223            };
1224            Some(Datum::new(
1225                primitive_type.clone(),
1226                PrimitiveLiteral::Int128(
1227                    i128_from_be_bytes(bytes)
1228                        .ok_or_else(|| invalid_data!("Can't convert bytes to i128: {bytes:?}"))?,
1229                ),
1230            ))
1231        }
1232        (
1233            PrimitiveType::Decimal {
1234                precision: _,
1235                scale: _,
1236            },
1237            Statistics::Int32(stats),
1238        ) => stats.max_opt().map(|val| {
1239            Datum::new(
1240                primitive_type.clone(),
1241                PrimitiveLiteral::Int128(i128::from(*val)),
1242            )
1243        }),
1244
1245        (
1246            PrimitiveType::Decimal {
1247                precision: _,
1248                scale: _,
1249            },
1250            Statistics::Int64(stats),
1251        ) => stats.max_opt().map(|val| {
1252            Datum::new(
1253                primitive_type.clone(),
1254                PrimitiveLiteral::Int128(i128::from(*val)),
1255            )
1256        }),
1257        (PrimitiveType::Uuid, Statistics::FixedLenByteArray(stats)) => {
1258            let Some(bytes) = stats.max_bytes_opt() else {
1259                return Ok(None);
1260            };
1261            if bytes.len() != 16 {
1262                return Err(Error::new(
1263                    ErrorKind::Unexpected,
1264                    "Invalid length of uuid bytes.",
1265                ));
1266            }
1267            Some(Datum::uuid(Uuid::from_bytes(
1268                bytes[..16].try_into().unwrap(),
1269            )))
1270        }
1271        (PrimitiveType::Fixed(len), Statistics::FixedLenByteArray(stat)) => {
1272            let Some(bytes) = stat.max_bytes_opt() else {
1273                return Ok(None);
1274            };
1275            if bytes.len() != *len as usize {
1276                return Err(Error::new(
1277                    ErrorKind::Unexpected,
1278                    "Invalid length of fixed bytes.",
1279                ));
1280            }
1281            Some(Datum::fixed(bytes.to_vec()))
1282        }
1283        (PrimitiveType::Binary, Statistics::ByteArray(stat)) => {
1284            return Ok(stat
1285                .max_bytes_opt()
1286                .map(|bytes| Datum::binary(bytes.to_vec())));
1287        }
1288        _ => {
1289            return Ok(None);
1290        }
1291    })
1292}
1293
1294impl TryFrom<&ArrowSchema> for Schema {
1295    type Error = Error;
1296
1297    fn try_from(schema: &ArrowSchema) -> Result<Self> {
1298        arrow_schema_to_schema(schema)
1299    }
1300}
1301
1302impl TryFrom<&Schema> for ArrowSchema {
1303    type Error = Error;
1304
1305    fn try_from(schema: &Schema) -> Result<Self> {
1306        schema_to_arrow_schema(schema)
1307    }
1308}
1309
1310/// Converts a Datum (Iceberg type + primitive literal) to its corresponding Arrow DataType
1311/// with Run-End Encoding (REE).
1312///
1313/// This function is used for constant fields in record batches, where all values are the same.
1314/// Run-End Encoding provides efficient storage for such constant columns.
1315///
1316/// # Arguments
1317/// * `datum` - The Datum to convert, which contains both type and value information
1318///
1319/// # Returns
1320/// Arrow DataType with Run-End Encoding applied
1321///
1322/// # Example
1323/// ```
1324/// use iceberg::arrow::datum_to_arrow_type_with_ree;
1325/// use iceberg::spec::Datum;
1326///
1327/// let datum = Datum::string("test_file.parquet");
1328/// let ree_type = datum_to_arrow_type_with_ree(&datum);
1329/// // Returns: RunEndEncoded(Int32, Utf8)
1330/// ```
1331pub fn datum_to_arrow_type_with_ree(datum: &Datum) -> DataType {
1332    primitive_type_to_arrow_type_with_ree(datum.data_type())
1333}
1334
1335/// Returns the run-end-encoded Arrow type used to materialize a per-file constant
1336/// column of the given Iceberg primitive type. Shared by both the value and the
1337/// all-null constant paths so a column keeps one Arrow type across files.
1338pub(crate) fn primitive_type_to_arrow_type_with_ree(primitive_type: &PrimitiveType) -> DataType {
1339    // Helper to create REE type with the given values type.
1340    // Note: values field is nullable as Arrow expects this when building the
1341    // final Arrow schema with `RunArray::try_new`.
1342    let make_ree = |values_type: DataType| -> DataType {
1343        let run_ends_field = Arc::new(Field::new("run_ends", DataType::Int32, false));
1344        let values_field = Arc::new(Field::new("values", values_type, true));
1345        DataType::RunEndEncoded(run_ends_field, values_field)
1346    };
1347
1348    match primitive_type {
1349        PrimitiveType::Unknown => make_ree(DataType::Null),
1350        PrimitiveType::Boolean => make_ree(DataType::Boolean),
1351        PrimitiveType::Int => make_ree(DataType::Int32),
1352        PrimitiveType::Long => make_ree(DataType::Int64),
1353        PrimitiveType::Float => make_ree(DataType::Float32),
1354        PrimitiveType::Double => make_ree(DataType::Float64),
1355        PrimitiveType::Date => make_ree(DataType::Date32),
1356        PrimitiveType::Time => make_ree(DataType::Int64),
1357        PrimitiveType::Timestamp => make_ree(DataType::Int64),
1358        PrimitiveType::Timestamptz => make_ree(DataType::Int64),
1359        PrimitiveType::TimestampNs => make_ree(DataType::Int64),
1360        PrimitiveType::TimestamptzNs => make_ree(DataType::Int64),
1361        PrimitiveType::String => make_ree(DataType::Utf8),
1362        PrimitiveType::Uuid => make_ree(DataType::Binary),
1363        PrimitiveType::Fixed(_) => make_ree(DataType::Binary),
1364        PrimitiveType::Binary => make_ree(DataType::Binary),
1365        // Match the LargeBinary storage used by schema_to_arrow_schema in the read layer.
1366        // Its 64-bit offsets cover the array's total WKB bytes, not only one geometry's size.
1367        PrimitiveType::Geometry(_) | PrimitiveType::Geography(_) => make_ree(DataType::LargeBinary),
1368        PrimitiveType::Decimal { precision, scale } => {
1369            make_ree(DataType::Decimal128(*precision as u8, *scale as i8))
1370        }
1371    }
1372}
1373
1374/// A visitor that strips metadata from an Arrow schema.
1375///
1376/// This visitor recursively removes all metadata from fields at every level of the schema,
1377/// including nested struct, list, and map fields. This is useful for schema comparison
1378/// where metadata differences should be ignored.
1379struct MetadataStripVisitor {
1380    /// Fields whose types are still being visited, innermost last
1381    pending_fields: Vec<PendingField>,
1382}
1383
1384/// What `MetadataStripVisitor` keeps of a field until its type is visited
1385struct PendingField {
1386    name: String,
1387    nullable: bool,
1388}
1389
1390impl MetadataStripVisitor {
1391    fn new() -> Self {
1392        Self {
1393            pending_fields: Vec::new(),
1394        }
1395    }
1396
1397    /// Records a field whose type is about to be visited.
1398    fn start_field(&mut self, name: &str, nullable: bool) -> Result<()> {
1399        self.pending_fields.push(PendingField {
1400            name: name.to_string(),
1401            nullable,
1402        });
1403        Ok(())
1404    }
1405
1406    /// Builds the innermost pending field with `data_type` and no metadata.
1407    /// `kind` names the type in the error for an unbalanced stack.
1408    fn finish_field(&mut self, data_type: DataType, kind: &str) -> Result<Field> {
1409        let PendingField { name, nullable } = self.pending_fields.pop().ok_or_else(|| {
1410            Error::new(
1411                ErrorKind::Unexpected,
1412                format!("Field stack underflow in {kind}"),
1413            )
1414        })?;
1415        Ok(Field::new(name, data_type, nullable))
1416    }
1417}
1418
1419impl ArrowSchemaVisitor for MetadataStripVisitor {
1420    type T = Field;
1421    type U = ArrowSchema;
1422
1423    fn before_field(&mut self, field: &FieldRef) -> Result<()> {
1424        self.start_field(field.name(), field.is_nullable())
1425    }
1426
1427    fn before_list_element(&mut self, field: &FieldRef) -> Result<()> {
1428        self.start_field(LIST_FIELD_NAME, field.is_nullable())
1429    }
1430
1431    fn before_map_key(&mut self, _field: &FieldRef) -> Result<()> {
1432        // Map keys are always required, as `schema_to_arrow_schema` builds them.
1433        self.start_field(MAP_KEY_FIELD_NAME, false)
1434    }
1435
1436    fn before_map_value(&mut self, field: &FieldRef) -> Result<()> {
1437        self.start_field(MAP_VALUE_FIELD_NAME, field.is_nullable())
1438    }
1439
1440    fn schema(&mut self, _schema: &ArrowSchema, values: Vec<Self::T>) -> Result<Self::U> {
1441        if !self.pending_fields.is_empty() {
1442            return Err(Error::new(
1443                ErrorKind::Unexpected,
1444                format!(
1445                    "{} fields left on the field stack after traversal",
1446                    self.pending_fields.len()
1447                ),
1448            ));
1449        }
1450        Ok(ArrowSchema::new(values))
1451    }
1452
1453    fn r#struct(&mut self, _fields: &Fields, results: Vec<Self::T>) -> Result<Self::T> {
1454        self.finish_field(DataType::Struct(Fields::from(results)), "struct")
1455    }
1456
1457    fn list(&mut self, list: &DataType, value: Self::T) -> Result<Self::T> {
1458        let list_type = match list {
1459            DataType::List(_) => DataType::List(Arc::new(value)),
1460            DataType::LargeList(_) => DataType::LargeList(Arc::new(value)),
1461            DataType::FixedSizeList(_, size) => DataType::FixedSizeList(Arc::new(value), *size),
1462            _ => {
1463                return Err(Error::new(
1464                    ErrorKind::Unexpected,
1465                    format!("Expected list type, got {list}"),
1466                ));
1467            }
1468        };
1469
1470        self.finish_field(list_type, "list")
1471    }
1472
1473    fn map(&mut self, map: &DataType, key_value: Self::T, value: Self::T) -> Result<Self::T> {
1474        // Reconstruct the map's struct field (contains key and value)
1475        let struct_field = Field::new(
1476            DEFAULT_MAP_FIELD_NAME,
1477            DataType::Struct(Fields::from(vec![key_value, value])),
1478            false,
1479        );
1480
1481        // Get the sorted flag from the original map type
1482        let sorted = match map {
1483            DataType::Map(_, sorted) => *sorted,
1484            _ => {
1485                return Err(Error::new(
1486                    ErrorKind::Unexpected,
1487                    format!("Expected map type, got {map}"),
1488                ));
1489            }
1490        };
1491
1492        self.finish_field(DataType::Map(Arc::new(struct_field), sorted), "map")
1493    }
1494
1495    fn primitive(&mut self, p: &DataType) -> Result<Self::T> {
1496        self.finish_field(p.clone(), "primitive")
1497    }
1498}
1499
1500/// Strips all metadata from an Arrow schema and its nested fields.
1501///
1502/// This function recursively removes metadata from all fields at every level of the schema,
1503/// including nested struct, list, and map fields. This is useful for schema comparison
1504/// where metadata differences should be ignored.
1505///
1506/// Some other differences are normalized away too, so that schemas that differ only in
1507/// them compare equal once stripped. The fields inside lists and maps are renamed as
1508/// [`schema_to_arrow_schema`] names them, and dictionaries are unwrapped:
1509/// - A list's element field is renamed to [`LIST_FIELD_NAME`], so `item` (the arrow-rs
1510///   default) becomes `element`.
1511/// - A map's entries field is renamed to [`DEFAULT_MAP_FIELD_NAME`] and made non-nullable.
1512/// - A map's key and value fields are renamed to [`MAP_KEY_FIELD_NAME`] and
1513///   [`MAP_VALUE_FIELD_NAME`], so `keys` and `values` (the arrow-rs `MapBuilder` default)
1514///   become `key` and `value`, and the key is made non-nullable.
1515/// - A dictionary-encoded field becomes a field of the dictionary's value type.
1516///
1517/// The names of the schema's fields and of struct fields are kept. Extension type
1518/// metadata (`ARROW:extension:*`) is dropped with the rest, so a variant field becomes a
1519/// plain struct of its `metadata` and `value` fields.
1520///
1521/// # Arguments
1522/// * `schema` - The Arrow schema to strip metadata from
1523///
1524/// # Returns
1525/// A new Arrow schema with all metadata removed, or an error if the schema structure
1526/// is invalid.
1527///
1528/// # Example
1529/// ```
1530/// use std::collections::HashMap;
1531///
1532/// use arrow_schema::{DataType, Field, Schema as ArrowSchema};
1533/// use iceberg::arrow::strip_metadata_from_schema;
1534///
1535/// let mut metadata = HashMap::new();
1536/// metadata.insert("key".to_string(), "value".to_string());
1537///
1538/// let field = Field::new("col1", DataType::Int32, false).with_metadata(metadata);
1539/// let schema = ArrowSchema::new(vec![field]);
1540///
1541/// let stripped = strip_metadata_from_schema(&schema).unwrap();
1542/// assert!(stripped.field(0).metadata().is_empty());
1543/// ```
1544pub fn strip_metadata_from_schema(schema: &ArrowSchema) -> Result<ArrowSchema> {
1545    let mut visitor = MetadataStripVisitor::new();
1546    visit_schema(schema, &mut visitor)
1547}
1548
1549#[cfg(test)]
1550mod tests {
1551    use std::collections::HashMap;
1552    use std::sync::Arc;
1553
1554    use arrow_schema::extension::ExtensionType;
1555    use arrow_schema::{DataType, Field, Schema as ArrowSchema, TimeUnit};
1556    use parquet_geospatial::WkbEdges;
1557
1558    use super::*;
1559    use crate::spec::decimal_utils::decimal_new;
1560    use crate::spec::geospatial::{
1561        DEFAULT_GEOSPATIAL_CRS, EdgeInterpolationAlgorithm as IcebergEdgeInterpolationAlgorithm,
1562    };
1563    use crate::spec::{Literal, Schema};
1564
1565    /// Create a simple field with metadata.
1566    fn simple_field(name: &str, ty: DataType, nullable: bool, value: &str) -> Field {
1567        Field::new(name, ty, nullable).with_metadata(HashMap::from([(
1568            PARQUET_FIELD_ID_META_KEY.to_string(),
1569            value.to_string(),
1570        )]))
1571    }
1572
1573    fn arrow_schema_for_arrow_schema_to_schema_test() -> ArrowSchema {
1574        let fields = Fields::from(vec![
1575            simple_field("key", DataType::Int32, false, "28"),
1576            simple_field("value", DataType::Utf8, true, "29"),
1577        ]);
1578
1579        let r#struct = DataType::Struct(fields);
1580        let map = DataType::Map(
1581            Arc::new(simple_field(DEFAULT_MAP_FIELD_NAME, r#struct, false, "17")),
1582            false,
1583        );
1584        let dictionary = DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8));
1585
1586        let fields = Fields::from(vec![
1587            simple_field("aa", DataType::Int32, false, "18"),
1588            simple_field("bb", DataType::Utf8, true, "19"),
1589            simple_field(
1590                "cc",
1591                DataType::Timestamp(TimeUnit::Microsecond, None),
1592                false,
1593                "20",
1594            ),
1595        ]);
1596
1597        let r#struct = DataType::Struct(fields);
1598
1599        ArrowSchema::new(vec![
1600            simple_field("a", DataType::Int32, false, "2"),
1601            simple_field("b", DataType::Int64, false, "1"),
1602            simple_field("c", DataType::Utf8, false, "3"),
1603            simple_field("n", DataType::Utf8, false, "21"),
1604            simple_field(
1605                "d",
1606                DataType::Timestamp(TimeUnit::Microsecond, None),
1607                true,
1608                "4",
1609            ),
1610            simple_field("e", DataType::Boolean, true, "6"),
1611            simple_field("f", DataType::Float32, false, "5"),
1612            simple_field("g", DataType::Float64, false, "7"),
1613            simple_field("p", DataType::Decimal128(10, 2), false, "27"),
1614            simple_field("h", DataType::Date32, false, "8"),
1615            simple_field("i", DataType::Time64(TimeUnit::Microsecond), false, "9"),
1616            simple_field(
1617                "j",
1618                DataType::Timestamp(TimeUnit::Microsecond, Some("UTC".into())),
1619                false,
1620                "10",
1621            ),
1622            simple_field(
1623                "k",
1624                DataType::Timestamp(TimeUnit::Microsecond, Some("+00:00".into())),
1625                false,
1626                "12",
1627            ),
1628            simple_field("l", DataType::Binary, false, "13"),
1629            simple_field("o", DataType::LargeBinary, false, "22"),
1630            simple_field("m", DataType::FixedSizeBinary(10), false, "11"),
1631            simple_field(
1632                "list",
1633                DataType::List(Arc::new(simple_field(
1634                    "element",
1635                    DataType::Int32,
1636                    false,
1637                    "15",
1638                ))),
1639                true,
1640                "14",
1641            ),
1642            simple_field(
1643                "large_list",
1644                DataType::LargeList(Arc::new(simple_field(
1645                    "element",
1646                    DataType::Utf8,
1647                    false,
1648                    "23",
1649                ))),
1650                true,
1651                "24",
1652            ),
1653            simple_field(
1654                "fixed_list",
1655                DataType::FixedSizeList(
1656                    Arc::new(simple_field("element", DataType::Binary, false, "26")),
1657                    10,
1658                ),
1659                true,
1660                "25",
1661            ),
1662            simple_field("map", map, false, "16"),
1663            simple_field("struct", r#struct, false, "17"),
1664            simple_field("dictionary", dictionary, false, "30"),
1665        ])
1666    }
1667
1668    fn iceberg_schema_for_arrow_schema_to_schema_test() -> Schema {
1669        let schema_json = r#"{
1670            "type":"struct",
1671            "schema-id":0,
1672            "fields":[
1673                {
1674                    "id":2,
1675                    "name":"a",
1676                    "required":true,
1677                    "type":"int"
1678                },
1679                {
1680                    "id":1,
1681                    "name":"b",
1682                    "required":true,
1683                    "type":"long"
1684                },
1685                {
1686                    "id":3,
1687                    "name":"c",
1688                    "required":true,
1689                    "type":"string"
1690                },
1691                {
1692                    "id":21,
1693                    "name":"n",
1694                    "required":true,
1695                    "type":"string"
1696                },
1697                {
1698                    "id":4,
1699                    "name":"d",
1700                    "required":false,
1701                    "type":"timestamp"
1702                },
1703                {
1704                    "id":6,
1705                    "name":"e",
1706                    "required":false,
1707                    "type":"boolean"
1708                },
1709                {
1710                    "id":5,
1711                    "name":"f",
1712                    "required":true,
1713                    "type":"float"
1714                },
1715                {
1716                    "id":7,
1717                    "name":"g",
1718                    "required":true,
1719                    "type":"double"
1720                },
1721                {
1722                    "id":27,
1723                    "name":"p",
1724                    "required":true,
1725                    "type":"decimal(10,2)"
1726                },
1727                {
1728                    "id":8,
1729                    "name":"h",
1730                    "required":true,
1731                    "type":"date"
1732                },
1733                {
1734                    "id":9,
1735                    "name":"i",
1736                    "required":true,
1737                    "type":"time"
1738                },
1739                {
1740                    "id":10,
1741                    "name":"j",
1742                    "required":true,
1743                    "type":"timestamptz"
1744                },
1745                {
1746                    "id":12,
1747                    "name":"k",
1748                    "required":true,
1749                    "type":"timestamptz"
1750                },
1751                {
1752                    "id":13,
1753                    "name":"l",
1754                    "required":true,
1755                    "type":"binary"
1756                },
1757                {
1758                    "id":22,
1759                    "name":"o",
1760                    "required":true,
1761                    "type":"binary"
1762                },
1763                {
1764                    "id":11,
1765                    "name":"m",
1766                    "required":true,
1767                    "type":"fixed[10]"
1768                },
1769                {
1770                    "id":14,
1771                    "name":"list",
1772                    "required": false,
1773                    "type": {
1774                        "type": "list",
1775                        "element-id": 15,
1776                        "element-required": true,
1777                        "element": "int"
1778                    }
1779                },
1780                {
1781                    "id":24,
1782                    "name":"large_list",
1783                    "required": false,
1784                    "type": {
1785                        "type": "list",
1786                        "element-id": 23,
1787                        "element-required": true,
1788                        "element": "string"
1789                    }
1790                },
1791                {
1792                    "id":25,
1793                    "name":"fixed_list",
1794                    "required": false,
1795                    "type": {
1796                        "type": "list",
1797                        "element-id": 26,
1798                        "element-required": true,
1799                        "element": "binary"
1800                    }
1801                },
1802                {
1803                    "id":16,
1804                    "name":"map",
1805                    "required": true,
1806                    "type": {
1807                        "type": "map",
1808                        "key-id": 28,
1809                        "key": "int",
1810                        "value-id": 29,
1811                        "value-required": false,
1812                        "value": "string"
1813                    }
1814                },
1815                {
1816                    "id":17,
1817                    "name":"struct",
1818                    "required": true,
1819                    "type": {
1820                        "type": "struct",
1821                        "fields": [
1822                            {
1823                                "id":18,
1824                                "name":"aa",
1825                                "required":true,
1826                                "type":"int"
1827                            },
1828                            {
1829                                "id":19,
1830                                "name":"bb",
1831                                "required":false,
1832                                "type":"string"
1833                            },
1834                            {
1835                                "id":20,
1836                                "name":"cc",
1837                                "required":true,
1838                                "type":"timestamp"
1839                            }
1840                        ]
1841                    }
1842                },
1843                {
1844                    "id":30,
1845                    "name":"dictionary",
1846                    "required":true,
1847                    "type":"string"
1848                }
1849            ],
1850            "identifier-field-ids":[]
1851        }"#;
1852
1853        let schema: Schema = serde_json::from_str(schema_json).unwrap();
1854        schema
1855    }
1856
1857    #[test]
1858    fn test_arrow_schema_to_schema() {
1859        let arrow_schema = arrow_schema_for_arrow_schema_to_schema_test();
1860        let schema = iceberg_schema_for_arrow_schema_to_schema_test();
1861        let converted_schema = arrow_schema_to_schema(&arrow_schema).unwrap();
1862        pretty_assertions::assert_eq!(converted_schema, schema);
1863    }
1864
1865    #[test]
1866    fn test_converting_uuid_from_arrow_to_iceberg_to_arrow_should_give_uuid() {
1867        let arrow_schema = ArrowSchema::new(vec![
1868            simple_field("uuid_field", DataType::FixedSizeBinary(16), false, "1")
1869                .with_extension_type(UuidExtensionType),
1870        ]);
1871        let output =
1872            schema_to_arrow_schema(&arrow_schema_to_schema(&arrow_schema).unwrap()).unwrap();
1873
1874        assert_eq!(arrow_schema, output);
1875    }
1876
1877    #[test]
1878    fn test_converting_uuid_from_iceberg_to_arrow_to_iceberg_should_give_uuid() {
1879        let iceberg_schema = Schema::builder()
1880            .with_fields(vec![
1881                NestedField::optional(1, "uuid_field", Type::Primitive(PrimitiveType::Uuid)).into(),
1882            ])
1883            .build()
1884            .unwrap();
1885        let output =
1886            arrow_schema_to_schema(&schema_to_arrow_schema(&iceberg_schema).unwrap()).unwrap();
1887
1888        assert_eq!(iceberg_schema, output);
1889    }
1890
1891    #[test]
1892    fn test_arrow_schema_to_schema_should_convert_uuid_when_fixed_size_binary() {
1893        let converted_schema = arrow_schema_to_schema(&ArrowSchema::new(vec![
1894            simple_field("uuid_field", DataType::FixedSizeBinary(16), false, "1")
1895                .with_extension_type(UuidExtensionType),
1896        ]))
1897        .unwrap();
1898
1899        let expected = Schema::builder()
1900            .with_fields([NestedField::required(
1901                1,
1902                "uuid_field",
1903                Type::Primitive(PrimitiveType::Uuid),
1904            )
1905            .into()])
1906            .build()
1907            .unwrap();
1908
1909        pretty_assertions::assert_eq!(expected, converted_schema);
1910    }
1911
1912    #[test]
1913    fn test_arrow_schema_to_schema_should_reject_uuid_when_not_a_fixed_size_binary() {
1914        // The field must be built correctly, and then changed to the wrong type,
1915        // to avoid Arrow's own validation and panic.
1916        let field = simple_field_with_uuid_ext(DataType::Utf8);
1917
1918        let error = arrow_schema_to_schema(&ArrowSchema::new(vec![field])).unwrap_err();
1919
1920        pretty_assertions::assert_eq!(
1921            "arrow.uuid extension requires FixedSizeBinary(16) storage, found Utf8",
1922            error.message()
1923        );
1924    }
1925
1926    #[test]
1927    fn test_arrow_schema_to_schema_should_reject_uuid_when_fixed_size_binary_that_is_less_than_16()
1928    {
1929        let field = simple_field_with_uuid_ext(DataType::FixedSizeBinary(8));
1930        let error = arrow_schema_to_schema(&ArrowSchema::new(vec![field])).unwrap_err();
1931
1932        pretty_assertions::assert_eq!(
1933            "arrow.uuid extension requires FixedSizeBinary(16) storage, found FixedSizeBinary(8)",
1934            error.message()
1935        );
1936    }
1937
1938    #[test]
1939    fn test_arrow_schema_to_schema_should_reject_uuid_when_fixed_size_binary_that_is_more_than_16()
1940    {
1941        let field = simple_field_with_uuid_ext(DataType::FixedSizeBinary(17));
1942        let error = arrow_schema_to_schema(&ArrowSchema::new(vec![field])).unwrap_err();
1943
1944        pretty_assertions::assert_eq!(
1945            "arrow.uuid extension requires FixedSizeBinary(16) storage, found FixedSizeBinary(17)",
1946            error.message()
1947        );
1948    }
1949
1950    fn simple_field_with_uuid_ext(field_data_type: DataType) -> Field {
1951        let mut field = simple_field("uuid_field", DataType::FixedSizeBinary(16), false, "1")
1952            .with_extension_type(UuidExtensionType);
1953
1954        // We want to allow setting a UUID extension to the wrong data type.
1955        // This is to test our logic against bad schemas.
1956        //
1957        // To avoid Arrow's own internal validation and panic, we need to build
1958        // the field correctly above, and then manually override the type here.
1959        field = field.with_data_type(field_data_type);
1960
1961        field
1962    }
1963
1964    fn arrow_schema_for_schema_to_arrow_schema_test() -> ArrowSchema {
1965        let fields = Fields::from(vec![
1966            simple_field("key", DataType::Int32, false, "28"),
1967            simple_field("value", DataType::Utf8, true, "29"),
1968        ]);
1969
1970        let r#struct = DataType::Struct(fields);
1971        let map = DataType::Map(
1972            Arc::new(Field::new(DEFAULT_MAP_FIELD_NAME, r#struct, false)),
1973            false,
1974        );
1975
1976        let fields = Fields::from(vec![
1977            simple_field("aa", DataType::Int32, false, "18"),
1978            simple_field("bb", DataType::Utf8, true, "19"),
1979            simple_field(
1980                "cc",
1981                DataType::Timestamp(TimeUnit::Microsecond, None),
1982                false,
1983                "20",
1984            ),
1985        ]);
1986
1987        let r#struct = DataType::Struct(fields);
1988
1989        ArrowSchema::new(vec![
1990            simple_field("a", DataType::Int32, false, "2"),
1991            simple_field("b", DataType::Int64, false, "1"),
1992            simple_field("c", DataType::Utf8, false, "3"),
1993            simple_field("n", DataType::Utf8, false, "21"),
1994            simple_field(
1995                "d",
1996                DataType::Timestamp(TimeUnit::Microsecond, None),
1997                true,
1998                "4",
1999            ),
2000            simple_field("e", DataType::Boolean, true, "6"),
2001            simple_field("f", DataType::Float32, false, "5"),
2002            simple_field("g", DataType::Float64, false, "7"),
2003            simple_field("p", DataType::Decimal128(10, 2), false, "27"),
2004            simple_field("h", DataType::Date32, false, "8"),
2005            simple_field("i", DataType::Time64(TimeUnit::Microsecond), false, "9"),
2006            simple_field(
2007                "j",
2008                DataType::Timestamp(TimeUnit::Microsecond, Some("+00:00".into())),
2009                false,
2010                "10",
2011            ),
2012            simple_field(
2013                "k",
2014                DataType::Timestamp(TimeUnit::Microsecond, Some("+00:00".into())),
2015                false,
2016                "12",
2017            ),
2018            simple_field("l", DataType::LargeBinary, false, "13"),
2019            simple_field("o", DataType::LargeBinary, false, "22"),
2020            simple_field("m", DataType::FixedSizeBinary(10), false, "11"),
2021            simple_field(
2022                "list",
2023                DataType::List(Arc::new(simple_field(
2024                    "element",
2025                    DataType::Int32,
2026                    false,
2027                    "15",
2028                ))),
2029                true,
2030                "14",
2031            ),
2032            simple_field(
2033                "large_list",
2034                DataType::List(Arc::new(simple_field(
2035                    "element",
2036                    DataType::Utf8,
2037                    false,
2038                    "23",
2039                ))),
2040                true,
2041                "24",
2042            ),
2043            simple_field(
2044                "fixed_list",
2045                DataType::List(Arc::new(simple_field(
2046                    "element",
2047                    DataType::LargeBinary,
2048                    false,
2049                    "26",
2050                ))),
2051                true,
2052                "25",
2053            ),
2054            simple_field("map", map, false, "16"),
2055            simple_field("struct", r#struct, false, "17"),
2056            simple_field("uuid", DataType::FixedSizeBinary(16), false, "30")
2057                .with_extension_type(UuidExtensionType),
2058            Field::new(
2059                "v",
2060                DataType::Struct(Fields::from(vec![
2061                    Field::new("metadata", DataType::Binary, false),
2062                    Field::new("value", DataType::Binary, true),
2063                ])),
2064                true,
2065            )
2066            .with_metadata(HashMap::from([
2067                (PARQUET_FIELD_ID_META_KEY.to_string(), "31".to_string()),
2068                (
2069                    arrow_schema::extension::EXTENSION_TYPE_NAME_KEY.to_string(),
2070                    "arrow.parquet.variant".to_string(),
2071                ),
2072            ])),
2073        ])
2074    }
2075
2076    fn iceberg_schema_for_schema_to_arrow_schema() -> Schema {
2077        let schema_json = r#"{
2078            "type":"struct",
2079            "schema-id":0,
2080            "fields":[
2081                {
2082                    "id":2,
2083                    "name":"a",
2084                    "required":true,
2085                    "type":"int"
2086                },
2087                {
2088                    "id":1,
2089                    "name":"b",
2090                    "required":true,
2091                    "type":"long"
2092                },
2093                {
2094                    "id":3,
2095                    "name":"c",
2096                    "required":true,
2097                    "type":"string"
2098                },
2099                {
2100                    "id":21,
2101                    "name":"n",
2102                    "required":true,
2103                    "type":"string"
2104                },
2105                {
2106                    "id":4,
2107                    "name":"d",
2108                    "required":false,
2109                    "type":"timestamp"
2110                },
2111                {
2112                    "id":6,
2113                    "name":"e",
2114                    "required":false,
2115                    "type":"boolean"
2116                },
2117                {
2118                    "id":5,
2119                    "name":"f",
2120                    "required":true,
2121                    "type":"float"
2122                },
2123                {
2124                    "id":7,
2125                    "name":"g",
2126                    "required":true,
2127                    "type":"double"
2128                },
2129                {
2130                    "id":27,
2131                    "name":"p",
2132                    "required":true,
2133                    "type":"decimal(10,2)"
2134                },
2135                {
2136                    "id":8,
2137                    "name":"h",
2138                    "required":true,
2139                    "type":"date"
2140                },
2141                {
2142                    "id":9,
2143                    "name":"i",
2144                    "required":true,
2145                    "type":"time"
2146                },
2147                {
2148                    "id":10,
2149                    "name":"j",
2150                    "required":true,
2151                    "type":"timestamptz"
2152                },
2153                {
2154                    "id":12,
2155                    "name":"k",
2156                    "required":true,
2157                    "type":"timestamptz"
2158                },
2159                {
2160                    "id":13,
2161                    "name":"l",
2162                    "required":true,
2163                    "type":"binary"
2164                },
2165                {
2166                    "id":22,
2167                    "name":"o",
2168                    "required":true,
2169                    "type":"binary"
2170                },
2171                {
2172                    "id":11,
2173                    "name":"m",
2174                    "required":true,
2175                    "type":"fixed[10]"
2176                },
2177                {
2178                    "id":14,
2179                    "name":"list",
2180                    "required": false,
2181                    "type": {
2182                        "type": "list",
2183                        "element-id": 15,
2184                        "element-required": true,
2185                        "element": "int"
2186                    }
2187                },
2188                {
2189                    "id":24,
2190                    "name":"large_list",
2191                    "required": false,
2192                    "type": {
2193                        "type": "list",
2194                        "element-id": 23,
2195                        "element-required": true,
2196                        "element": "string"
2197                    }
2198                },
2199                {
2200                    "id":25,
2201                    "name":"fixed_list",
2202                    "required": false,
2203                    "type": {
2204                        "type": "list",
2205                        "element-id": 26,
2206                        "element-required": true,
2207                        "element": "binary"
2208                    }
2209                },
2210                {
2211                    "id":16,
2212                    "name":"map",
2213                    "required": true,
2214                    "type": {
2215                        "type": "map",
2216                        "key-id": 28,
2217                        "key": "int",
2218                        "value-id": 29,
2219                        "value-required": false,
2220                        "value": "string"
2221                    }
2222                },
2223                {
2224                    "id":17,
2225                    "name":"struct",
2226                    "required": true,
2227                    "type": {
2228                        "type": "struct",
2229                        "fields": [
2230                            {
2231                                "id":18,
2232                                "name":"aa",
2233                                "required":true,
2234                                "type":"int"
2235                            },
2236                            {
2237                                "id":19,
2238                                "name":"bb",
2239                                "required":false,
2240                                "type":"string"
2241                            },
2242                            {
2243                                "id":20,
2244                                "name":"cc",
2245                                "required":true,
2246                                "type":"timestamp"
2247                            }
2248                        ]
2249                    }
2250                },
2251                {
2252                    "id":30,
2253                    "name":"uuid",
2254                    "required":true,
2255                    "type":"uuid"
2256                },
2257                {
2258                    "id":31,
2259                    "name":"v",
2260                    "required":false,
2261                    "type":"variant"
2262                }
2263            ],
2264            "identifier-field-ids":[]
2265        }"#;
2266
2267        let schema: Schema = serde_json::from_str(schema_json).unwrap();
2268        schema
2269    }
2270
2271    #[test]
2272    fn test_schema_to_arrow_schema() {
2273        let expected_arrow_schema = arrow_schema_for_schema_to_arrow_schema_test();
2274        let schema = iceberg_schema_for_schema_to_arrow_schema();
2275        let converted_arrow_schema = schema_to_arrow_schema(&schema).unwrap();
2276        assert_eq!(converted_arrow_schema, expected_arrow_schema);
2277    }
2278
2279    #[test]
2280    fn test_variant_type_to_arrow_type() {
2281        // Variant maps to a struct with a required `metadata` and a nullable `value` binary
2282        // field, with no field ids on the sub-fields, matching the Parquet BINARY layout.
2283        let arrow_type = type_to_arrow_type(&Type::Variant(VariantType)).unwrap();
2284        assert_eq!(
2285            arrow_type,
2286            DataType::Struct(Fields::from(vec![
2287                Field::new("metadata", DataType::Binary, false),
2288                Field::new("value", DataType::Binary, true),
2289            ]))
2290        );
2291    }
2292
2293    #[test]
2294    fn test_variant_field_carries_arrow_extension_type() {
2295        // Converting a schema with a variant column tags the column's field with the
2296        // canonical `arrow.parquet.variant` extension type (the struct storage stays as-is).
2297        let schema = Schema::builder()
2298            .with_fields(vec![
2299                NestedField::optional(1, "v", Type::Variant(VariantType)).into(),
2300            ])
2301            .build()
2302            .unwrap();
2303
2304        let arrow_schema = schema_to_arrow_schema(&schema).unwrap();
2305        let field = arrow_schema.field_with_name("v").unwrap();
2306
2307        assert_eq!(field.extension_type_name(), Some("arrow.parquet.variant"));
2308        // Attaching the extension type must not clobber the Iceberg field id.
2309        assert_eq!(
2310            field.metadata().get(PARQUET_FIELD_ID_META_KEY),
2311            Some(&"1".to_string())
2312        );
2313        assert_eq!(
2314            field.data_type(),
2315            &DataType::Struct(Fields::from(vec![
2316                Field::new("metadata", DataType::Binary, false),
2317                Field::new("value", DataType::Binary, true),
2318            ]))
2319        );
2320    }
2321
2322    #[test]
2323    fn test_schema_to_arrow_schema_should_convert_uuid_to_arrow_uuid() {
2324        let schema = Schema::builder()
2325            .with_fields(vec![
2326                NestedField::optional(1, "uuid_field", Type::Primitive(PrimitiveType::Uuid)).into(),
2327            ])
2328            .build()
2329            .unwrap();
2330
2331        let arrow_schema = schema_to_arrow_schema(&schema).unwrap();
2332        let field = arrow_schema.field_with_name("uuid_field").unwrap();
2333
2334        assert_eq!(field.extension_type_name(), Some(UuidExtensionType::NAME));
2335        // Attaching the extension type must not clobber the Iceberg field id.
2336        assert_eq!(
2337            field.metadata().get(PARQUET_FIELD_ID_META_KEY),
2338            Some(&"1".to_string())
2339        );
2340        assert_eq!(*field.data_type(), DataType::FixedSizeBinary(16));
2341    }
2342
2343    #[test]
2344    fn test_variant_nested_in_list_and_map_carries_arrow_extension_type() {
2345        // A variant nested in a list element or map value keeps the arrow.parquet.variant
2346        // extension type. Regression guard: the list converter must not overwrite the
2347        // element field's metadata (which would drop the extension type).
2348        let schema = Schema::builder()
2349            .with_fields(vec![
2350                NestedField::optional(
2351                    1,
2352                    "l",
2353                    Type::List(ListType::new(
2354                        NestedField::optional(2, "element", Type::Variant(VariantType)).into(),
2355                    )),
2356                )
2357                .into(),
2358                NestedField::optional(
2359                    3,
2360                    "m",
2361                    Type::Map(MapType::new(
2362                        NestedField::map_key_element(4, Type::Primitive(PrimitiveType::String))
2363                            .into(),
2364                        NestedField::map_value_element(5, Type::Variant(VariantType), false).into(),
2365                    )),
2366                )
2367                .into(),
2368            ])
2369            .build()
2370            .unwrap();
2371
2372        let arrow_schema = schema_to_arrow_schema(&schema).unwrap();
2373
2374        let DataType::List(element) = arrow_schema.field_with_name("l").unwrap().data_type() else {
2375            panic!("expected a list");
2376        };
2377        assert_eq!(element.extension_type_name(), Some("arrow.parquet.variant"));
2378
2379        let DataType::Map(entries, _) = arrow_schema.field_with_name("m").unwrap().data_type()
2380        else {
2381            panic!("expected a map");
2382        };
2383        let DataType::Struct(kv) = entries.data_type() else {
2384            panic!("expected a key_value struct");
2385        };
2386        let value = kv.iter().find(|f| f.name() == "value").unwrap();
2387        assert_eq!(value.extension_type_name(), Some("arrow.parquet.variant"));
2388    }
2389
2390    /// The unshredded Arrow storage of a variant: `metadata` (required) + `value`
2391    /// (nullable) binary, with no field ids on the sub-fields.
2392    fn variant_storage() -> DataType {
2393        DataType::Struct(Fields::from(vec![
2394            Field::new("metadata", DataType::Binary, false),
2395            Field::new("value", DataType::Binary, true),
2396        ]))
2397    }
2398
2399    #[test]
2400    fn test_variant_arrow_field_folds_to_iceberg_variant() {
2401        // A field tagged with the arrow.parquet.variant extension is folded into an
2402        // atomic Type::Variant; its storage sub-fields (which carry no field id) are
2403        // never descended into.
2404        let field = simple_field("v", variant_storage(), true, "1")
2405            .with_extension_type(VariantExtensionType);
2406        let arrow_schema = ArrowSchema::new(vec![field]);
2407
2408        let converted = arrow_schema_to_schema(&arrow_schema).unwrap();
2409        let expected = Schema::builder()
2410            .with_fields(vec![
2411                NestedField::optional(1, "v", Type::Variant(VariantType)).into(),
2412            ])
2413            .build()
2414            .unwrap();
2415        pretty_assertions::assert_eq!(converted, expected);
2416    }
2417
2418    #[test]
2419    fn test_variant_schema_round_trips() {
2420        // Iceberg -> Arrow -> Iceberg is the identity for variants at every position:
2421        // top-level, nested in a struct, as a list element, and as a map value.
2422        let schema = Schema::builder()
2423            .with_fields(vec![
2424                NestedField::optional(1, "v", Type::Variant(VariantType)).into(),
2425                NestedField::optional(
2426                    2,
2427                    "s",
2428                    Type::Struct(StructType::new(vec![
2429                        NestedField::optional(3, "sv", Type::Variant(VariantType)).into(),
2430                    ])),
2431                )
2432                .into(),
2433                NestedField::optional(
2434                    4,
2435                    "l",
2436                    Type::List(ListType::new(
2437                        NestedField::optional(5, "element", Type::Variant(VariantType)).into(),
2438                    )),
2439                )
2440                .into(),
2441                NestedField::optional(
2442                    6,
2443                    "m",
2444                    Type::Map(MapType::new(
2445                        NestedField::map_key_element(7, Type::Primitive(PrimitiveType::String))
2446                            .into(),
2447                        NestedField::map_value_element(8, Type::Variant(VariantType), false).into(),
2448                    )),
2449                )
2450                .into(),
2451            ])
2452            .build()
2453            .unwrap();
2454
2455        let arrow_schema = schema_to_arrow_schema(&schema).unwrap();
2456        let round_tripped = arrow_schema_to_schema(&arrow_schema).unwrap();
2457        pretty_assertions::assert_eq!(round_tripped, schema);
2458    }
2459
2460    #[test]
2461    fn test_variant_recognized_with_auto_assigned_ids() {
2462        // Recognition also works when the Arrow schema has no field ids: the variant
2463        // field gets an auto-assigned id and its storage is still not descended into.
2464        let field =
2465            Field::new("v", variant_storage(), true).with_extension_type(VariantExtensionType);
2466        let arrow_schema = ArrowSchema::new(vec![field]);
2467
2468        let converted = arrow_schema_to_schema_auto_assign_ids(&arrow_schema).unwrap();
2469        let expected = Schema::builder()
2470            .with_fields(vec![
2471                NestedField::optional(1, "v", Type::Variant(VariantType)).into(),
2472            ])
2473            .build()
2474            .unwrap();
2475        pretty_assertions::assert_eq!(converted, expected);
2476    }
2477
2478    #[test]
2479    fn test_variant_extension_on_non_struct_storage_is_rejected() {
2480        // The extension may only sit on struct storage. A hand-injected tag on a
2481        // non-struct field is rejected rather than silently reinterpreted as a variant.
2482        let field = Field::new("v", DataType::Int32, true).with_metadata(HashMap::from([
2483            (PARQUET_FIELD_ID_META_KEY.to_string(), "1".to_string()),
2484            (
2485                arrow_schema::extension::EXTENSION_TYPE_NAME_KEY.to_string(),
2486                VariantExtensionType::NAME.to_string(),
2487            ),
2488        ]));
2489        let arrow_schema = ArrowSchema::new(vec![field]);
2490
2491        let err = arrow_schema_to_schema(&arrow_schema).unwrap_err();
2492        assert!(
2493            err.to_string().contains("requires Struct storage"),
2494            "unexpected error: {err}"
2495        );
2496    }
2497
2498    #[test]
2499    fn test_geospatial_arrow_schema_roundtrip() {
2500        let schema = Schema::builder()
2501            .with_schema_id(1)
2502            .with_fields(vec![
2503                NestedField::required(
2504                    1,
2505                    "geom",
2506                    Type::Primitive(PrimitiveType::Geometry(
2507                        GeometryType::new(Some("EPSG:3857".to_string())).unwrap(),
2508                    )),
2509                )
2510                .into(),
2511                NestedField::optional(
2512                    2,
2513                    "geog",
2514                    Type::Primitive(PrimitiveType::Geography(
2515                        GeographyType::new(
2516                            Some("OGC:CRS27".to_string()),
2517                            IcebergEdgeInterpolationAlgorithm::Karney,
2518                        )
2519                        .unwrap(),
2520                    )),
2521                )
2522                .into(),
2523                NestedField::optional(
2524                    3,
2525                    "geom_list",
2526                    Type::List(ListType::new(
2527                        NestedField::list_element(
2528                            4,
2529                            Type::Primitive(PrimitiveType::Geometry(GeometryType::default())),
2530                            true,
2531                        )
2532                        .into(),
2533                    )),
2534                )
2535                .into(),
2536            ])
2537            .build()
2538            .unwrap();
2539
2540        let arrow_schema = schema_to_arrow_schema(&schema).unwrap();
2541        let geom = arrow_schema.field(0);
2542        assert_eq!(geom.data_type(), &DataType::LargeBinary);
2543
2544        let geog = arrow_schema.field(1);
2545        let geog_wkb = geog.try_extension_type::<WkbType>().unwrap();
2546        assert_eq!(geog_wkb.metadata().algorithm, Some(WkbEdges::Karney));
2547        let geog_metadata: serde_json::Value =
2548            serde_json::from_str(&geog_wkb.serialize_metadata().unwrap()).unwrap();
2549        assert_eq!(geog_metadata.get("edges").unwrap(), "karney");
2550        assert!(geog_metadata.get("algorithm").is_none());
2551
2552        let list = arrow_schema.field(2);
2553        let DataType::List(element) = list.data_type() else {
2554            panic!("Expected list field");
2555        };
2556        assert!(
2557            matches!(
2558                element
2559                    .try_extension_type::<WkbType>()
2560                    .unwrap()
2561                    .metadata()
2562                    .type_hint(),
2563                WkbTypeHint::Geometry
2564            ),
2565            "Expected list element to retain WKB extension metadata"
2566        );
2567        assert_eq!(
2568            element
2569                .try_extension_type::<WkbType>()
2570                .unwrap()
2571                .metadata()
2572                .crs
2573                .as_ref()
2574                .and_then(serde_json::Value::as_str),
2575            Some(DEFAULT_GEOSPATIAL_CRS)
2576        );
2577
2578        let converted = arrow_schema_to_schema(&arrow_schema).unwrap();
2579        assert_eq!(converted.as_struct().fields(), schema.as_struct().fields());
2580    }
2581
2582    #[test]
2583    fn test_geospatial_arrow_crs_import() {
2584        let mut geom = simple_field("geom", DataType::LargeBinary, true, "1");
2585        geom.try_with_extension_type(WkbType::new(Some(WkbMetadata::new(
2586            Some(r#"{"id":{"authority":"EPSG","code":3857}}"#),
2587            None,
2588        ))))
2589        .unwrap();
2590        let mut unset_geom = simple_field("unset_geom", DataType::LargeBinary, true, "2");
2591        unset_geom
2592            .try_with_extension_type(WkbType::new(Some(WkbMetadata::new(None, None))))
2593            .unwrap();
2594
2595        let arrow_schema = ArrowSchema::new(vec![geom, unset_geom]);
2596        let schema = arrow_schema_to_schema(&arrow_schema).unwrap();
2597
2598        assert_eq!(
2599            schema.field_by_id(1).unwrap().field_type.as_ref(),
2600            &Type::Primitive(PrimitiveType::Geometry(
2601                GeometryType::new(Some("EPSG:3857".to_string())).unwrap()
2602            ))
2603        );
2604        assert_eq!(
2605            schema.field_by_id(2).unwrap().field_type.as_ref(),
2606            &Type::Primitive(PrimitiveType::Geometry(
2607                GeometryType::new(Some(UNSET_GEOSPATIAL_CRS.to_string())).unwrap()
2608            ))
2609        );
2610    }
2611
2612    #[test]
2613    fn test_geospatial_arrow_invalid_crs_is_rejected() {
2614        for crs in [r#"{}"#, "42"] {
2615            let mut field = simple_field("geom", DataType::LargeBinary, true, "1");
2616            field
2617                .try_with_extension_type(WkbType::new(Some(WkbMetadata::new(Some(crs), None))))
2618                .unwrap();
2619
2620            let err = arrow_schema_to_schema(&ArrowSchema::new(vec![field])).unwrap_err();
2621            assert_eq!(err.kind(), ErrorKind::DataInvalid, "{err}");
2622        }
2623    }
2624
2625    #[test]
2626    fn test_geospatial_arrow_extension_requires_binary_storage() {
2627        use arrow_schema::extension::{EXTENSION_TYPE_METADATA_KEY, EXTENSION_TYPE_NAME_KEY};
2628
2629        let metadata = WkbType::new(Some(WkbMetadata::new(None, None)))
2630            .serialize_metadata()
2631            .unwrap();
2632        let field =
2633            simple_field("geom", DataType::Int32, true, "1").with_metadata(HashMap::from([
2634                (PARQUET_FIELD_ID_META_KEY.to_string(), "1".to_string()),
2635                (
2636                    EXTENSION_TYPE_NAME_KEY.to_string(),
2637                    WkbType::NAME.to_string(),
2638                ),
2639                (EXTENSION_TYPE_METADATA_KEY.to_string(), metadata),
2640            ]));
2641
2642        let err = arrow_schema_to_schema(&ArrowSchema::new(vec![field])).unwrap_err();
2643        assert_eq!(err.kind(), ErrorKind::DataInvalid, "{err}");
2644        assert!(
2645            err.message().contains("requires binary storage"),
2646            "unexpected error: {err}"
2647        );
2648    }
2649
2650    #[test]
2651    fn test_geospatial_parquet_statistics_are_ignored() {
2652        use parquet::data_type::ByteArray;
2653        use parquet::file::statistics::ValueStatistics;
2654
2655        let stats = Statistics::ByteArray(ValueStatistics::new(
2656            Some(ByteArray::from(vec![1, 2, 3])),
2657            Some(ByteArray::from(vec![4, 5, 6])),
2658            None,
2659            None,
2660            false,
2661        ));
2662
2663        for primitive_type in [
2664            PrimitiveType::Geometry(GeometryType::default()),
2665            PrimitiveType::Geography(GeographyType::default()),
2666        ] {
2667            assert_eq!(
2668                get_parquet_stat_min_as_datum(&primitive_type, &stats).unwrap(),
2669                None
2670            );
2671            assert_eq!(
2672                get_parquet_stat_max_as_datum(&primitive_type, &stats).unwrap(),
2673                None
2674            );
2675        }
2676    }
2677
2678    #[test]
2679    fn test_type_conversion() {
2680        // test primitive type
2681        {
2682            let arrow_type = DataType::Int32;
2683            let iceberg_type = Type::Primitive(PrimitiveType::Int);
2684            assert_eq!(arrow_type, type_to_arrow_type(&iceberg_type).unwrap());
2685            assert_eq!(iceberg_type, arrow_type_to_type(&arrow_type).unwrap());
2686        }
2687
2688        {
2689            let arrow_type = DataType::Null;
2690            let iceberg_type = Type::Primitive(PrimitiveType::Unknown);
2691            assert_eq!(arrow_type, type_to_arrow_type(&iceberg_type).unwrap());
2692            assert_eq!(iceberg_type, arrow_type_to_type(&arrow_type).unwrap());
2693        }
2694
2695        // test struct type
2696        {
2697            // no metadata will cause error
2698            let arrow_type = DataType::Struct(Fields::from(vec![
2699                Field::new("a", DataType::Int64, false),
2700                Field::new("b", DataType::Utf8, true),
2701            ]));
2702            assert_eq!(
2703                &arrow_type_to_type(&arrow_type).unwrap_err().to_string(),
2704                "DataInvalid => Field id not found in metadata"
2705            );
2706
2707            let arrow_type = DataType::Struct(Fields::from(vec![
2708                Field::new("a", DataType::Int64, false).with_metadata(HashMap::from_iter([(
2709                    PARQUET_FIELD_ID_META_KEY.to_string(),
2710                    1.to_string(),
2711                )])),
2712                Field::new("b", DataType::Utf8, true).with_metadata(HashMap::from_iter([(
2713                    PARQUET_FIELD_ID_META_KEY.to_string(),
2714                    2.to_string(),
2715                )])),
2716            ]));
2717            let iceberg_type = Type::Struct(StructType::new(vec![
2718                NestedField {
2719                    id: 1,
2720                    doc: None,
2721                    name: "a".to_string(),
2722                    required: true,
2723                    field_type: Box::new(Type::Primitive(PrimitiveType::Long)),
2724                    initial_default: None,
2725                    write_default: None,
2726                }
2727                .into(),
2728                NestedField {
2729                    id: 2,
2730                    doc: None,
2731                    name: "b".to_string(),
2732                    required: false,
2733                    field_type: Box::new(Type::Primitive(PrimitiveType::String)),
2734                    initial_default: None,
2735                    write_default: None,
2736                }
2737                .into(),
2738            ]));
2739            assert_eq!(iceberg_type, arrow_type_to_type(&arrow_type).unwrap());
2740            assert_eq!(arrow_type, type_to_arrow_type(&iceberg_type).unwrap());
2741
2742            // initial_default and write_default is ignored
2743            let iceberg_type = Type::Struct(StructType::new(vec![
2744                NestedField {
2745                    id: 1,
2746                    doc: None,
2747                    name: "a".to_string(),
2748                    required: true,
2749                    field_type: Box::new(Type::Primitive(PrimitiveType::Long)),
2750                    initial_default: Some(Literal::Primitive(PrimitiveLiteral::Int(114514))),
2751                    write_default: None,
2752                }
2753                .into(),
2754                NestedField {
2755                    id: 2,
2756                    doc: None,
2757                    name: "b".to_string(),
2758                    required: false,
2759                    field_type: Box::new(Type::Primitive(PrimitiveType::String)),
2760                    initial_default: None,
2761                    write_default: Some(Literal::Primitive(PrimitiveLiteral::String(
2762                        "514".to_string(),
2763                    ))),
2764                }
2765                .into(),
2766            ]));
2767            assert_eq!(arrow_type, type_to_arrow_type(&iceberg_type).unwrap());
2768        }
2769
2770        // test dictionary type
2771        {
2772            let arrow_type =
2773                DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Int8));
2774            let iceberg_type = Type::Primitive(PrimitiveType::Int);
2775            assert_eq!(
2776                iceberg_type,
2777                arrow_type_to_type(&arrow_type).unwrap(),
2778                "Expected dictionary conversion to use the contained value"
2779            );
2780
2781            let arrow_type =
2782                DataType::Dictionary(Box::new(DataType::Utf8), Box::new(DataType::Boolean));
2783            let iceberg_type = Type::Primitive(PrimitiveType::Boolean);
2784            assert_eq!(iceberg_type, arrow_type_to_type(&arrow_type).unwrap());
2785        }
2786    }
2787
2788    #[test]
2789    fn test_unsigned_integer_type_conversion() {
2790        let test_cases = vec![
2791            (DataType::UInt8, PrimitiveType::Int),
2792            (DataType::UInt16, PrimitiveType::Int),
2793            (DataType::UInt32, PrimitiveType::Long),
2794        ];
2795
2796        for (arrow_type, expected_iceberg_type) in test_cases {
2797            let arrow_field = Field::new("test", arrow_type.clone(), false).with_metadata(
2798                HashMap::from([(PARQUET_FIELD_ID_META_KEY.to_string(), "1".to_string())]),
2799            );
2800            let arrow_schema = ArrowSchema::new(vec![arrow_field]);
2801
2802            let iceberg_schema = arrow_schema_to_schema(&arrow_schema).unwrap();
2803            let iceberg_field = iceberg_schema.as_struct().fields().first().unwrap();
2804
2805            assert!(
2806                matches!(iceberg_field.field_type.as_ref(), Type::Primitive(t) if *t == expected_iceberg_type),
2807                "Expected {arrow_type:?} to map to {expected_iceberg_type:?}"
2808            );
2809        }
2810
2811        // Test UInt64 blocking
2812        {
2813            let arrow_field = Field::new("test", DataType::UInt64, false).with_metadata(
2814                HashMap::from([(PARQUET_FIELD_ID_META_KEY.to_string(), "1".to_string())]),
2815            );
2816            let arrow_schema = ArrowSchema::new(vec![arrow_field]);
2817
2818            let result = arrow_schema_to_schema(&arrow_schema);
2819            assert!(result.is_err());
2820            assert!(
2821                result
2822                    .unwrap_err()
2823                    .to_string()
2824                    .contains("UInt64 is not supported")
2825            );
2826        }
2827    }
2828
2829    #[test]
2830    fn test_datum_conversion() {
2831        {
2832            let datum = Datum::bool(true);
2833            let arrow_datum = get_arrow_datum(&datum).unwrap();
2834            let (array, is_scalar) = arrow_datum.get();
2835            let array = array.as_any().downcast_ref::<BooleanArray>().unwrap();
2836            assert!(is_scalar);
2837            assert!(array.value(0));
2838        }
2839        {
2840            let datum = Datum::int(42);
2841            let arrow_datum = get_arrow_datum(&datum).unwrap();
2842            let (array, is_scalar) = arrow_datum.get();
2843            let array = array.as_any().downcast_ref::<Int32Array>().unwrap();
2844            assert!(is_scalar);
2845            assert_eq!(array.value(0), 42);
2846        }
2847        {
2848            let datum = Datum::long(42);
2849            let arrow_datum = get_arrow_datum(&datum).unwrap();
2850            let (array, is_scalar) = arrow_datum.get();
2851            let array = array.as_any().downcast_ref::<Int64Array>().unwrap();
2852            assert!(is_scalar);
2853            assert_eq!(array.value(0), 42);
2854        }
2855        {
2856            let datum = Datum::float(42.42_f32);
2857            let arrow_datum = get_arrow_datum(&datum).unwrap();
2858            let (array, is_scalar) = arrow_datum.get();
2859            let array = array.as_any().downcast_ref::<Float32Array>().unwrap();
2860            assert!(is_scalar);
2861            assert_eq!(array.value(0), 42.42);
2862        }
2863        {
2864            let datum = Datum::double(42.42);
2865            let arrow_datum = get_arrow_datum(&datum).unwrap();
2866            let (array, is_scalar) = arrow_datum.get();
2867            let array = array.as_any().downcast_ref::<Float64Array>().unwrap();
2868            assert!(is_scalar);
2869            assert_eq!(array.value(0), 42.42);
2870        }
2871        {
2872            let datum = Datum::string("abc");
2873            let arrow_datum = get_arrow_datum(&datum).unwrap();
2874            let (array, is_scalar) = arrow_datum.get();
2875            let array = array.as_any().downcast_ref::<StringArray>().unwrap();
2876            assert!(is_scalar);
2877            assert_eq!(array.value(0), "abc");
2878        }
2879        {
2880            let datum = Datum::binary(vec![1, 2, 3, 4]);
2881            let arrow_datum = get_arrow_datum(&datum).unwrap();
2882            let (array, is_scalar) = arrow_datum.get();
2883            let array = array.as_any().downcast_ref::<BinaryArray>().unwrap();
2884            assert!(is_scalar);
2885            assert_eq!(array.value(0), &[1, 2, 3, 4]);
2886        }
2887        {
2888            let datum = Datum::date(42);
2889            let arrow_datum = get_arrow_datum(&datum).unwrap();
2890            let (array, is_scalar) = arrow_datum.get();
2891            let array = array.as_any().downcast_ref::<Date32Array>().unwrap();
2892            assert!(is_scalar);
2893            assert_eq!(array.value(0), 42);
2894        }
2895        {
2896            let datum = Datum::timestamp_micros(42);
2897            let arrow_datum = get_arrow_datum(&datum).unwrap();
2898            let (array, is_scalar) = arrow_datum.get();
2899            let array = array
2900                .as_any()
2901                .downcast_ref::<TimestampMicrosecondArray>()
2902                .unwrap();
2903            assert!(is_scalar);
2904            assert_eq!(array.value(0), 42);
2905        }
2906        {
2907            let datum = Datum::timestamptz_micros(42);
2908            let arrow_datum = get_arrow_datum(&datum).unwrap();
2909            let (array, is_scalar) = arrow_datum.get();
2910            let array = array
2911                .as_any()
2912                .downcast_ref::<TimestampMicrosecondArray>()
2913                .unwrap();
2914            assert!(is_scalar);
2915            assert_eq!(array.timezone(), Some("+00:00"));
2916            assert_eq!(array.value(0), 42);
2917        }
2918        {
2919            let datum = Datum::decimal_with_precision(decimal_new(123, 2), 30).unwrap();
2920            let arrow_datum = get_arrow_datum(&datum).unwrap();
2921            let (array, is_scalar) = arrow_datum.get();
2922            let array = array.as_any().downcast_ref::<Decimal128Array>().unwrap();
2923            assert!(is_scalar);
2924            assert_eq!(array.precision(), 30);
2925            assert_eq!(array.scale(), 2);
2926            assert_eq!(array.value(0), 123);
2927        }
2928        {
2929            let datum = Datum::uuid_from_str("42424242-4242-4242-4242-424242424242").unwrap();
2930            let arrow_datum = get_arrow_datum(&datum).unwrap();
2931            let (array, is_scalar) = arrow_datum.get();
2932            let array = array
2933                .as_any()
2934                .downcast_ref::<FixedSizeBinaryArray>()
2935                .unwrap();
2936            assert!(is_scalar);
2937            assert_eq!(array.value(0), [66u8; 16]);
2938        }
2939        {
2940            let datum = Datum::fixed(vec![1u8, 2, 3, 4, 5, 6, 7, 8]);
2941            let arrow_datum = get_arrow_datum(&datum).unwrap();
2942            let (array, is_scalar) = arrow_datum.get();
2943            let array = array
2944                .as_any()
2945                .downcast_ref::<FixedSizeBinaryArray>()
2946                .unwrap();
2947            assert!(is_scalar);
2948            assert_eq!(array.value_length(), 8);
2949            assert_eq!(array.value(0), &[1u8, 2, 3, 4, 5, 6, 7, 8]);
2950        }
2951    }
2952
2953    #[test]
2954    fn test_arrow_schema_to_schema_with_field_id() {
2955        // Create a complex Arrow schema without field ID metadata
2956        // Including: primitives, list, nested struct, map, and nested list of structs
2957        let arrow_schema = ArrowSchema::new(vec![
2958            Field::new("id", DataType::Int64, false),
2959            Field::new("name", DataType::Utf8, true),
2960            Field::new("price", DataType::Decimal128(10, 2), false),
2961            Field::new("uuid", DataType::FixedSizeBinary(16), false)
2962                .with_extension_type(UuidExtensionType),
2963            Field::new(
2964                "created_at",
2965                DataType::Timestamp(TimeUnit::Microsecond, Some("+00:00".into())),
2966                true,
2967            ),
2968            Field::new(
2969                "tags",
2970                DataType::List(Arc::new(Field::new("item", DataType::Utf8, true))),
2971                true,
2972            ),
2973            Field::new(
2974                "address",
2975                DataType::Struct(Fields::from(vec![
2976                    Field::new("street", DataType::Utf8, true),
2977                    Field::new("city", DataType::Utf8, false),
2978                    Field::new("zip", DataType::Int32, true),
2979                ])),
2980                true,
2981            ),
2982            Field::new(
2983                "attributes",
2984                DataType::Map(
2985                    Arc::new(Field::new(
2986                        DEFAULT_MAP_FIELD_NAME,
2987                        DataType::Struct(Fields::from(vec![
2988                            Field::new("key", DataType::Utf8, false),
2989                            Field::new("value", DataType::Utf8, true),
2990                        ])),
2991                        false,
2992                    )),
2993                    false,
2994                ),
2995                true,
2996            ),
2997            Field::new(
2998                "orders",
2999                DataType::List(Arc::new(Field::new(
3000                    "element",
3001                    DataType::Struct(Fields::from(vec![
3002                        Field::new("order_id", DataType::Int64, false),
3003                        Field::new("amount", DataType::Float64, false),
3004                    ])),
3005                    true,
3006                ))),
3007                true,
3008            ),
3009        ]);
3010
3011        let schema = arrow_schema_to_schema_auto_assign_ids(&arrow_schema).unwrap();
3012
3013        // Build expected schema with exact field IDs following level-order assignment:
3014        // Level 0: id=1, name=2, price=3, uuid=4, created_at=5, tags=6, address=7, attributes=8, orders=9
3015        // Level 1: tags.element=10, address.{street=11,city=12,zip=13}, attributes.{key=14,value=15}, orders.element=16
3016        // Level 2: orders.element.{order_id=17,amount=18}
3017        let expected = Schema::builder()
3018            .with_fields(vec![
3019                NestedField::required(1, "id", Type::Primitive(PrimitiveType::Long)).into(),
3020                NestedField::optional(2, "name", Type::Primitive(PrimitiveType::String)).into(),
3021                NestedField::required(
3022                    3,
3023                    "price",
3024                    Type::Primitive(PrimitiveType::Decimal {
3025                        precision: 10,
3026                        scale: 2,
3027                    }),
3028                )
3029                .into(),
3030                NestedField::required(4, "uuid", Type::Primitive(PrimitiveType::Uuid)).into(),
3031                NestedField::optional(5, "created_at", Type::Primitive(PrimitiveType::Timestamptz))
3032                    .into(),
3033                NestedField::optional(
3034                    6,
3035                    "tags",
3036                    Type::List(ListType {
3037                        element_field: NestedField::list_element(
3038                            10,
3039                            Type::Primitive(PrimitiveType::String),
3040                            false,
3041                        )
3042                        .into(),
3043                    }),
3044                )
3045                .into(),
3046                NestedField::optional(
3047                    7,
3048                    "address",
3049                    Type::Struct(StructType::new(vec![
3050                        NestedField::optional(11, "street", Type::Primitive(PrimitiveType::String))
3051                            .into(),
3052                        NestedField::required(12, "city", Type::Primitive(PrimitiveType::String))
3053                            .into(),
3054                        NestedField::optional(13, "zip", Type::Primitive(PrimitiveType::Int))
3055                            .into(),
3056                    ])),
3057                )
3058                .into(),
3059                NestedField::optional(
3060                    8,
3061                    "attributes",
3062                    Type::Map(MapType {
3063                        key_field: NestedField::map_key_element(
3064                            14,
3065                            Type::Primitive(PrimitiveType::String),
3066                        )
3067                        .into(),
3068                        value_field: NestedField::map_value_element(
3069                            15,
3070                            Type::Primitive(PrimitiveType::String),
3071                            false,
3072                        )
3073                        .into(),
3074                    }),
3075                )
3076                .into(),
3077                NestedField::optional(
3078                    9,
3079                    "orders",
3080                    Type::List(ListType {
3081                        element_field: NestedField::list_element(
3082                            16,
3083                            Type::Struct(StructType::new(vec![
3084                                NestedField::required(
3085                                    17,
3086                                    "order_id",
3087                                    Type::Primitive(PrimitiveType::Long),
3088                                )
3089                                .into(),
3090                                NestedField::required(
3091                                    18,
3092                                    "amount",
3093                                    Type::Primitive(PrimitiveType::Double),
3094                                )
3095                                .into(),
3096                            ])),
3097                            false,
3098                        )
3099                        .into(),
3100                    }),
3101                )
3102                .into(),
3103            ])
3104            .build()
3105            .unwrap();
3106
3107        pretty_assertions::assert_eq!(schema, expected);
3108        assert_eq!(schema.highest_field_id(), 18);
3109    }
3110
3111    fn decimal_byte_array_stats(min: Vec<u8>, max: Vec<u8>) -> Statistics {
3112        Statistics::byte_array(
3113            Some(parquet::data_type::ByteArray::from(min)),
3114            Some(parquet::data_type::ByteArray::from(max)),
3115            None,
3116            Some(0),
3117            false,
3118        )
3119    }
3120
3121    fn decimal_bounds(stats: &Statistics) -> Result<(Option<Datum>, Option<Datum>)> {
3122        let ty = PrimitiveType::Decimal {
3123            precision: 38,
3124            scale: 2,
3125        };
3126        Ok((
3127            get_parquet_stat_min_as_datum(&ty, stats)?,
3128            get_parquet_stat_max_as_datum(&ty, stats)?,
3129        ))
3130    }
3131
3132    fn decimal_datum(value: i128) -> Datum {
3133        Datum::new(
3134            PrimitiveType::Decimal {
3135                precision: 38,
3136                scale: 2,
3137            },
3138            PrimitiveLiteral::Int128(value),
3139        )
3140    }
3141
3142    #[test]
3143    fn test_byte_array_decimal_stats_short_bounds() {
3144        // Minimal-length big-endian two's-complement: -1234 and 1234.
3145        let stats = decimal_byte_array_stats(vec![0xFB, 0x2E], vec![0x04, 0xD2]);
3146        let (min, max) = decimal_bounds(&stats).unwrap();
3147        assert_eq!(min, Some(decimal_datum(-1234)));
3148        assert_eq!(max, Some(decimal_datum(1234)));
3149
3150        // Single-byte bounds: sign bit set must sign-extend, not zero-extend.
3151        let stats = decimal_byte_array_stats(vec![0x80], vec![0x7F]);
3152        let (min, max) = decimal_bounds(&stats).unwrap();
3153        assert_eq!(min, Some(decimal_datum(-128)));
3154        assert_eq!(max, Some(decimal_datum(127)));
3155    }
3156
3157    #[test]
3158    fn test_byte_array_decimal_stats_full_width_bounds() {
3159        let stats = decimal_byte_array_stats(
3160            i128::MIN.to_be_bytes().to_vec(),
3161            i128::MAX.to_be_bytes().to_vec(),
3162        );
3163        let (min, max) = decimal_bounds(&stats).unwrap();
3164        assert_eq!(min, Some(decimal_datum(i128::MIN)));
3165        assert_eq!(max, Some(decimal_datum(i128::MAX)));
3166    }
3167
3168    #[test]
3169    fn test_byte_array_decimal_stats_empty_bound_errors() {
3170        let stats = decimal_byte_array_stats(vec![], vec![0x01]);
3171        let err = decimal_bounds(&stats).unwrap_err();
3172        assert_eq!(err.kind(), ErrorKind::DataInvalid);
3173
3174        let stats = decimal_byte_array_stats(vec![0x01], vec![]);
3175        let err = decimal_bounds(&stats).unwrap_err();
3176        assert_eq!(err.kind(), ErrorKind::DataInvalid);
3177    }
3178
3179    #[test]
3180    fn test_byte_array_decimal_stats_oversized_bound_errors() {
3181        let stats = decimal_byte_array_stats(vec![0; 17], vec![0x01]);
3182        let err = decimal_bounds(&stats).unwrap_err();
3183        assert_eq!(err.kind(), ErrorKind::DataInvalid);
3184
3185        let stats = decimal_byte_array_stats(vec![0x01], vec![0; 17]);
3186        let err = decimal_bounds(&stats).unwrap_err();
3187        assert_eq!(err.kind(), ErrorKind::DataInvalid);
3188    }
3189
3190    /// Every field is rebuilt without metadata, at any depth and inside any
3191    /// container. Fields are rebuilt in nesting order rather than looked up by
3192    /// name, so a path that repeats names (`a.b.a.b`) keeps each level's name
3193    /// and nullability.
3194    #[test]
3195    fn test_strip_metadata_from_nested_schema() {
3196        // The same schema, built with and without metadata on the schema and every field.
3197        let schema = |with_metadata: bool| {
3198            let metadata = |value: &str| {
3199                if with_metadata {
3200                    HashMap::from([("k".to_string(), value.to_string())])
3201                } else {
3202                    HashMap::new()
3203                }
3204            };
3205            let field = |name: &str, ty: DataType, nullable: bool| {
3206                Field::new(name, ty, nullable).with_metadata(metadata(name))
3207            };
3208            let element = |ty: DataType| Arc::new(field(LIST_FIELD_NAME, ty, false));
3209            let ints = || DataType::List(element(DataType::Int32));
3210            let map = |value: DataType, sorted: bool| {
3211                let entries = Fields::from(vec![
3212                    field(MAP_KEY_FIELD_NAME, DataType::Utf8, false),
3213                    field(MAP_VALUE_FIELD_NAME, value, true),
3214                ]);
3215                let entries = field(DEFAULT_MAP_FIELD_NAME, DataType::Struct(entries), false);
3216                DataType::Map(Arc::new(entries), sorted)
3217            };
3218            let struct_of = |fields: Vec<Field>| DataType::Struct(Fields::from(fields));
3219
3220            let a_b_a_b = struct_of(vec![field(
3221                "b",
3222                struct_of(vec![field(
3223                    "a",
3224                    struct_of(vec![field("b", DataType::Int32, true)]),
3225                    false,
3226                )]),
3227                false,
3228            )]);
3229            ArrowSchema::new(vec![
3230                field("list", ints(), true),
3231                field(
3232                    "large_list",
3233                    DataType::LargeList(element(DataType::Int32)),
3234                    false,
3235                ),
3236                field(
3237                    "fixed_list",
3238                    DataType::FixedSizeList(element(DataType::Int32), 2),
3239                    true,
3240                ),
3241                field("list_of_list", DataType::List(element(ints())), false),
3242                field("sorted_map", map(ints(), true), false),
3243                field(
3244                    "map_of_struct",
3245                    map(
3246                        struct_of(vec![
3247                            field("a", DataType::Int32, true),
3248                            field("b", ints(), false),
3249                        ]),
3250                        false,
3251                    ),
3252                    true,
3253                ),
3254                field(
3255                    "list_of_struct",
3256                    DataType::List(element(struct_of(vec![field(
3257                        "m",
3258                        map(ints(), false),
3259                        true,
3260                    )]))),
3261                    true,
3262                ),
3263                field("a", a_b_a_b, true),
3264            ])
3265            .with_metadata(metadata("schema"))
3266        };
3267
3268        let (input, expected) = (schema(true), schema(false));
3269        // Otherwise a builder that stopped adding metadata would pass trivially.
3270        assert_ne!(input, expected);
3271        pretty_assertions::assert_eq!(strip_metadata_from_schema(&input).unwrap(), expected);
3272    }
3273
3274    /// arrow-rs names a list's element `item`, and an arrow-rs `MapBuilder` names
3275    /// a map's fields `entries`, `keys` and `values`. Once stripped, a schema
3276    /// using those names, with a nullable map key, compares equal to the same
3277    /// schema from `schema_to_arrow_schema`.
3278    #[test]
3279    fn test_strip_metadata_normalizes_container_field_names() {
3280        // A list of ints and a map from string to a list of ints, with the given
3281        // names for the list elements and the map's entries, key and value.
3282        let schema = |[element, entries, key, value]: [&str; 4], key_nullable: bool| {
3283            let ints =
3284                |nullable| DataType::List(Arc::new(Field::new(element, DataType::Int32, nullable)));
3285            let key_value = Fields::from(vec![
3286                Field::new(key, DataType::Utf8, key_nullable),
3287                Field::new(value, ints(false), true),
3288            ]);
3289            let entries = Field::new(entries, DataType::Struct(key_value), false);
3290            ArrowSchema::new(vec![
3291                Field::new("l", ints(true), true),
3292                Field::new("m", DataType::Map(Arc::new(entries), false), true),
3293            ])
3294        };
3295        let arrow_rs = schema(["item", "entries", "keys", "values"], true);
3296        let expected = schema(
3297            [
3298                LIST_FIELD_NAME,
3299                DEFAULT_MAP_FIELD_NAME,
3300                MAP_KEY_FIELD_NAME,
3301                MAP_VALUE_FIELD_NAME,
3302            ],
3303            false,
3304        );
3305
3306        // The same schema as an Iceberg table's.
3307        let int_list = |id, required| {
3308            let int = Type::Primitive(PrimitiveType::Int);
3309            Type::List(ListType::new(
3310                NestedField::list_element(id, int, required).into(),
3311            ))
3312        };
3313        let string_to_int_list = MapType::new(
3314            NestedField::map_key_element(4, Type::Primitive(PrimitiveType::String)).into(),
3315            NestedField::map_value_element(5, int_list(6, true), false).into(),
3316        );
3317        let iceberg = Schema::builder()
3318            .with_fields(vec![
3319                NestedField::optional(1, "l", int_list(2, false)).into(),
3320                NestedField::optional(3, "m", Type::Map(string_to_int_list)).into(),
3321            ])
3322            .build()
3323            .unwrap();
3324
3325        pretty_assertions::assert_eq!(strip_metadata_from_schema(&arrow_rs).unwrap(), expected);
3326        pretty_assertions::assert_eq!(
3327            strip_metadata_from_schema(&schema_to_arrow_schema(&iceberg).unwrap()).unwrap(),
3328            expected
3329        );
3330    }
3331
3332    /// Names and nullability that `schema_to_arrow_schema` doesn't fix, such as a
3333    /// struct field's name or a list element's nullability, still tell schemas apart.
3334    #[test]
3335    fn test_strip_metadata_keeps_other_differences() {
3336        let schema = |child: &str, element_nullable: bool| {
3337            let child = Field::new(child, DataType::Int32, true);
3338            let element = Field::new("item", DataType::Int32, element_nullable);
3339            ArrowSchema::new(vec![
3340                Field::new("s", DataType::Struct(Fields::from(vec![child])), true),
3341                Field::new("l", DataType::List(Arc::new(element)), true),
3342            ])
3343        };
3344        let strip = |schema: ArrowSchema| strip_metadata_from_schema(&schema).unwrap();
3345
3346        assert_ne!(strip(schema("a", true)), strip(schema("b", true)));
3347        assert_ne!(strip(schema("a", true)), strip(schema("a", false)));
3348    }
3349
3350    /// Stripping drops the `arrow.parquet.variant` extension type with the rest of
3351    /// the metadata, so a variant becomes a plain struct, in a list or map too.
3352    #[test]
3353    fn test_strip_metadata_turns_variants_into_structs() {
3354        let variant = || Type::Variant(VariantType);
3355        let schema = Schema::builder()
3356            .with_fields(vec![
3357                NestedField::optional(1, "v", variant()).into(),
3358                NestedField::optional(
3359                    2,
3360                    "l",
3361                    Type::List(ListType::new(
3362                        NestedField::list_element(3, variant(), false).into(),
3363                    )),
3364                )
3365                .into(),
3366                NestedField::optional(
3367                    4,
3368                    "m",
3369                    Type::Map(MapType::new(
3370                        NestedField::map_key_element(5, Type::Primitive(PrimitiveType::String))
3371                            .into(),
3372                        NestedField::map_value_element(6, variant(), false).into(),
3373                    )),
3374                )
3375                .into(),
3376            ])
3377            .build()
3378            .unwrap();
3379
3380        let storage = || {
3381            DataType::Struct(Fields::from(vec![
3382                Field::new("metadata", DataType::Binary, false),
3383                Field::new("value", DataType::Binary, true),
3384            ]))
3385        };
3386        let entries = Fields::from(vec![
3387            Field::new(MAP_KEY_FIELD_NAME, DataType::Utf8, false),
3388            Field::new(MAP_VALUE_FIELD_NAME, storage(), true),
3389        ]);
3390        let entries = Field::new(DEFAULT_MAP_FIELD_NAME, DataType::Struct(entries), false);
3391        let expected = ArrowSchema::new(vec![
3392            Field::new("v", storage(), true),
3393            Field::new(
3394                "l",
3395                DataType::List(Arc::new(Field::new(LIST_FIELD_NAME, storage(), true))),
3396                true,
3397            ),
3398            Field::new("m", DataType::Map(Arc::new(entries), false), true),
3399        ]);
3400
3401        pretty_assertions::assert_eq!(
3402            strip_metadata_from_schema(&schema_to_arrow_schema(&schema).unwrap()).unwrap(),
3403            expected
3404        );
3405    }
3406
3407    /// A dictionary-encoded field compares equal to a plain field of its value
3408    /// type once stripped, at any depth.
3409    #[test]
3410    fn test_strip_metadata_unwraps_dictionaries() {
3411        let schema = |dictionary_encoded: bool| {
3412            let maybe_dictionary = |key: DataType, value: DataType| {
3413                if dictionary_encoded {
3414                    DataType::Dictionary(Box::new(key), Box::new(value))
3415                } else {
3416                    value
3417                }
3418            };
3419            let element = Field::new(
3420                LIST_FIELD_NAME,
3421                maybe_dictionary(DataType::Int8, DataType::LargeUtf8),
3422                false,
3423            );
3424            ArrowSchema::new(vec![
3425                Field::new("d", maybe_dictionary(DataType::Int32, DataType::Utf8), true),
3426                Field::new("l", DataType::List(Arc::new(element)), true),
3427            ])
3428        };
3429
3430        pretty_assertions::assert_eq!(
3431            strip_metadata_from_schema(&schema(true)).unwrap(),
3432            schema(false)
3433        );
3434    }
3435}