Skip to main content

arrow_schema/
ffi.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//! Contains declarations to bind to the [C Data Interface](https://arrow.apache.org/docs/format/CDataInterface.html).
19//!
20//! ```
21//! # use arrow_schema::{DataType, Field, Schema};
22//! # use arrow_schema::ffi::FFI_ArrowSchema;
23//!
24//! // Create from data type
25//! let ffi_data_type = FFI_ArrowSchema::try_from(&DataType::LargeUtf8).unwrap();
26//! let back = DataType::try_from(&ffi_data_type).unwrap();
27//! assert_eq!(back, DataType::LargeUtf8);
28//!
29//! // Create from schema
30//! let schema = Schema::new(vec![Field::new("foo", DataType::Int64, false)]);
31//! let ffi_schema = FFI_ArrowSchema::try_from(&schema).unwrap();
32//! let back = Schema::try_from(&ffi_schema).unwrap();
33//!
34//! assert_eq!(schema, back);
35//! ```
36
37use crate::{
38    ArrowError, DataType, Field, FieldRef, IntervalUnit, Schema, TimeUnit, UnionFields, UnionMode,
39};
40use bitflags::bitflags;
41use std::borrow::Cow;
42use std::sync::Arc;
43use std::{
44    collections::HashMap,
45    ffi::{CStr, CString, c_char, c_void},
46};
47
48bitflags! {
49    /// Flags for [`FFI_ArrowSchema`]
50    ///
51    /// Old Workaround at <https://github.com/bitflags/bitflags/issues/356>
52    /// is no longer required as `bitflags` [fixed the issue](https://github.com/bitflags/bitflags/pull/355).
53    pub struct Flags: i64 {
54        /// Indicates that the dictionary is ordered
55        const DICTIONARY_ORDERED = 0b00000001;
56        /// Indicates that the field is nullable
57        const NULLABLE = 0b00000010;
58        /// Indicates that the map keys are sorted
59        const MAP_KEYS_SORTED = 0b00000100;
60    }
61}
62
63/// ABI-compatible struct for `ArrowSchema` from C Data Interface
64/// See <https://arrow.apache.org/docs/format/CDataInterface.html#the-arrowschema-structure>
65///
66/// ```
67/// # use arrow_schema::DataType;
68/// # use arrow_schema::ffi::FFI_ArrowSchema;
69/// fn array_schema(data_type: &DataType) -> FFI_ArrowSchema {
70///     FFI_ArrowSchema::try_from(data_type).unwrap()
71/// }
72/// ```
73///
74#[repr(C)]
75#[derive(Debug)]
76pub struct FFI_ArrowSchema {
77    // Fields are intentionally private so safety guarantees can be upheld via
78    // explicit unsafe functions.
79    /// Null-terminated, UTF8-encoded string describing the data type
80    format: *const c_char,
81    /// Null-terminated, UTF8-encoded string of the field or array name
82    name: *const c_char,
83    /// Binary string describing the type’s metadata
84    metadata: *const c_char,
85    /// A bitfield of flags enriching the type description
86    /// Refer to [Arrow Flags](https://arrow.apache.org/docs/format/CDataInterface.html#c.ArrowSchema.flags)
87    flags: i64,
88    /// The number of children this type has
89    n_children: i64,
90    /// C array of pointers to each child type of this type
91    children: *mut *mut FFI_ArrowSchema,
92    /// Pointer to the type of dictionary values
93    dictionary: *mut FFI_ArrowSchema,
94    /// Producer-provided release callback.
95    release: Option<unsafe extern "C" fn(arg1: *mut FFI_ArrowSchema)>,
96    /// Opaque producer-provided private data, owned by the release callback.
97    private_data: *mut c_void,
98}
99
100struct SchemaPrivateData {
101    children: Box<[*mut FFI_ArrowSchema]>,
102    dictionary: *mut FFI_ArrowSchema,
103    metadata: Option<Vec<u8>>,
104}
105
106// callback used to drop [FFI_ArrowSchema] when it is exported.
107unsafe extern "C" fn release_schema(schema: *mut FFI_ArrowSchema) {
108    if schema.is_null() {
109        return;
110    }
111    let schema = unsafe { &mut *schema };
112
113    // take ownership back to release it.
114    drop(unsafe { CString::from_raw(schema.format.cast_mut()) });
115    if !schema.name.is_null() {
116        drop(unsafe { CString::from_raw(schema.name.cast_mut()) });
117    }
118    if !schema.private_data.is_null() {
119        let private_data =
120            unsafe { Box::from_raw(schema.private_data.cast::<SchemaPrivateData>()) };
121        for child in &private_data.children {
122            drop(unsafe { Box::from_raw(*child) })
123        }
124        if !private_data.dictionary.is_null() {
125            drop(unsafe { Box::from_raw(private_data.dictionary) });
126        }
127
128        drop(private_data);
129    }
130
131    schema.release = None;
132}
133
134impl FFI_ArrowSchema {
135    /// create a new [`FFI_ArrowSchema`].
136    ///
137    /// # Errors
138    ///
139    /// Errors if the fields' [`DataType`] is not supported,
140    /// or if `format` contains an interior nul byte.
141    pub fn try_new(
142        format: &str,
143        children: Vec<FFI_ArrowSchema>,
144        dictionary: Option<FFI_ArrowSchema>,
145    ) -> Result<Self, ArrowError> {
146        // Convert the format before leaking any of the children,
147        // so that an error here does not leak memory.
148        let format = CString::new(format).map_err(|err| {
149            ArrowError::CDataInterface(format!(
150                "Null byte at position {} not allowed in format",
151                err.nul_position()
152            ))
153        })?;
154
155        let mut this = Self::empty();
156
157        let children_ptr = children
158            .into_iter()
159            .map(Box::new)
160            .map(Box::into_raw)
161            .collect::<Box<_>>();
162
163        this.format = format.into_raw();
164        this.release = Some(release_schema);
165        this.n_children = children_ptr.len() as i64;
166
167        let dictionary_ptr = dictionary
168            .map(|d| Box::into_raw(Box::new(d)))
169            .unwrap_or(std::ptr::null_mut());
170
171        let mut private_data = Box::new(SchemaPrivateData {
172            children: children_ptr,
173            dictionary: dictionary_ptr,
174            metadata: None,
175        });
176
177        // intentionally set from private_data (see https://github.com/apache/arrow-rs/issues/580)
178        this.children = private_data.children.as_mut_ptr();
179
180        this.dictionary = dictionary_ptr;
181
182        this.private_data = Box::into_raw(private_data).cast::<c_void>();
183
184        Ok(this)
185    }
186
187    /// Set the name of the schema
188    pub fn with_name(mut self, name: &str) -> Result<Self, ArrowError> {
189        self.name = CString::new(name)
190            .map_err(|e| {
191                ArrowError::CDataInterface(format!(
192                    "Null byte at position {} not allowed in name",
193                    e.nul_position()
194                ))
195            })?
196            .into_raw();
197        Ok(self)
198    }
199
200    /// Set the flags of the schema
201    pub fn with_flags(mut self, flags: Flags) -> Result<Self, ArrowError> {
202        self.flags = flags.bits();
203        Ok(self)
204    }
205
206    /// Add metadata to the schema.
207    ///
208    /// # Safety
209    ///
210    /// `self` must be a schema this crate produced (e.g. via
211    /// [`FFI_ArrowSchema::try_new`] or a `TryFrom`), not one from a foreign
212    /// producer and not [`FFI_ArrowSchema::empty`]. It reinterprets
213    /// `private_data` as our own type, so any other schema is undefined
214    /// behavior. See <https://github.com/apache/arrow-rs/issues/10679> and
215    /// <https://github.com/apache/arrow-rs/issues/10286>.
216    pub unsafe fn with_metadata<I, S>(mut self, metadata: I) -> Result<Self, ArrowError>
217    where
218        I: IntoIterator<Item = (S, S)>,
219        S: AsRef<str>,
220    {
221        let metadata: Vec<(S, S)> = metadata.into_iter().collect();
222        // https://arrow.apache.org/docs/format/CDataInterface.html#c.ArrowSchema.metadata
223        let new_metadata = if !metadata.is_empty() {
224            let mut metadata_serialized: Vec<u8> = Vec::new();
225            let num_entries: i32 = metadata.len().try_into().map_err(|_| {
226                ArrowError::CDataInterface(format!(
227                    "metadata can only have {} entries, but {} were provided",
228                    i32::MAX,
229                    metadata.len()
230                ))
231            })?;
232            metadata_serialized.extend(num_entries.to_ne_bytes());
233
234            for (key, value) in metadata {
235                let key_len: i32 = key.as_ref().len().try_into().map_err(|_| {
236                    ArrowError::CDataInterface(format!(
237                        "metadata key can only have {} bytes, but {} were provided",
238                        i32::MAX,
239                        key.as_ref().len()
240                    ))
241                })?;
242                let value_len: i32 = value.as_ref().len().try_into().map_err(|_| {
243                    ArrowError::CDataInterface(format!(
244                        "metadata value can only have {} bytes, but {} were provided",
245                        i32::MAX,
246                        value.as_ref().len()
247                    ))
248                })?;
249
250                metadata_serialized.extend(key_len.to_ne_bytes());
251                metadata_serialized.extend_from_slice(key.as_ref().as_bytes());
252                metadata_serialized.extend(value_len.to_ne_bytes());
253                metadata_serialized.extend_from_slice(value.as_ref().as_bytes());
254            }
255
256            self.metadata = metadata_serialized.as_ptr().cast::<c_char>();
257            Some(metadata_serialized)
258        } else {
259            self.metadata = std::ptr::null_mut();
260            None
261        };
262
263        // Safety: `self.private_data` was allocated as `Box<SchemaPrivateData>` by `try_new`.
264        // We take ownership temporarily with `from_raw` and put it back with `into_raw`,
265        // so there is no double-free and no other code can access `private_data` concurrently.
266        unsafe {
267            let mut private_data = Box::from_raw(self.private_data.cast::<SchemaPrivateData>());
268            private_data.metadata = new_metadata;
269            self.private_data = Box::into_raw(private_data).cast::<c_void>();
270        }
271
272        Ok(self)
273    }
274
275    /// Takes ownership of the pointed to [`FFI_ArrowSchema`]
276    ///
277    /// This acts to [move] the data out of `schema`, setting the release callback to NULL
278    ///
279    /// # Safety
280    ///
281    /// * `schema` must be [valid] for reads and writes
282    /// * `schema` must be properly aligned
283    /// * `schema` must point to a properly initialized value of [`FFI_ArrowSchema`]
284    ///
285    /// [move]: https://arrow.apache.org/docs/format/CDataInterface.html#moving-an-array
286    /// [valid]: https://doc.rust-lang.org/std/ptr/index.html#safety
287    pub unsafe fn from_raw(schema: *mut FFI_ArrowSchema) -> Self {
288        unsafe { std::ptr::replace(schema, Self::empty()) }
289    }
290
291    /// Create an empty [`FFI_ArrowSchema`]
292    pub fn empty() -> Self {
293        Self {
294            format: std::ptr::null_mut(),
295            name: std::ptr::null_mut(),
296            metadata: std::ptr::null_mut(),
297            flags: 0,
298            n_children: 0,
299            children: std::ptr::null_mut(),
300            dictionary: std::ptr::null_mut(),
301            release: None,
302            private_data: std::ptr::null_mut(),
303        }
304    }
305
306    /// Returns the producer-provided release callback, if any.
307    pub fn release(&self) -> Option<unsafe extern "C" fn(arg1: *mut FFI_ArrowSchema)> {
308        self.release
309    }
310
311    /// Returns the opaque producer-provided private data pointer.
312    pub fn private_data(&self) -> *mut c_void {
313        self.private_data
314    }
315
316    /// Replaces the release callback, returning the previous one.
317    ///
318    /// Lets a consumer wrap release: save the old callback, install its own, and
319    /// chain back on drop. See <https://github.com/apache/arrow-rs/issues/9771>.
320    ///
321    /// # Safety
322    ///
323    /// [`Drop`] calls this callback with a pointer to `self`. The new callback
324    /// must correctly release this schema (usually by chaining to the returned
325    /// one) and must match the [`FFI_ArrowSchema::private_data`] it reads. A
326    /// wrong callback is undefined behavior on drop.
327    pub unsafe fn set_release(
328        &mut self,
329        release: Option<unsafe extern "C" fn(arg1: *mut FFI_ArrowSchema)>,
330    ) -> Option<unsafe extern "C" fn(arg1: *mut FFI_ArrowSchema)> {
331        std::mem::replace(&mut self.release, release)
332    }
333
334    /// Replaces the private data pointer, returning the previous one.
335    ///
336    /// # Safety
337    ///
338    /// The old pointer is returned without being freed; the caller owns it from
339    /// here. The new pointer must match what the current
340    /// [`FFI_ArrowSchema::release`] callback expects.
341    pub unsafe fn set_private_data(&mut self, private_data: *mut c_void) -> *mut c_void {
342        std::mem::replace(&mut self.private_data, private_data)
343    }
344
345    /// Returns the format of this schema.
346    ///
347    /// # Panics
348    ///
349    /// Panics if the format field is null or is not valid UTF-8
350    pub fn format(&self) -> &str {
351        assert!(!self.format.is_null());
352        // safe because the lifetime of `self.format` equals `self`
353        unsafe { CStr::from_ptr(self.format) }
354            .to_str()
355            .expect("The external API has a non-utf8 as format")
356    }
357
358    /// Returns the name of this schema.
359    ///
360    /// # Panics
361    ///
362    /// Panics if the name field is not valid UTF-8
363    pub fn name(&self) -> Option<&str> {
364        if self.name.is_null() {
365            None
366        } else {
367            // safe because the lifetime of `self.name` equals `self`
368            Some(
369                unsafe { CStr::from_ptr(self.name) }
370                    .to_str()
371                    .expect("The external API has a non-utf8 as name"),
372            )
373        }
374    }
375
376    /// Returns the flags of this schema.
377    pub fn flags(&self) -> Option<Flags> {
378        Flags::from_bits(self.flags)
379    }
380
381    /// Returns the child of this schema at `index`.
382    ///
383    /// # Panics
384    ///
385    /// Panics if `index` is greater than or equal to the number of children.
386    ///
387    /// This is to make sure that the unsafe access to raw pointer is sound.
388    pub fn child(&self, index: usize) -> &Self {
389        assert!(index < self.n_children as usize);
390        unsafe { self.children.add(index).as_ref().unwrap().as_ref().unwrap() }
391    }
392
393    /// Returns an iterator to the schema's children.
394    pub fn children(&self) -> impl Iterator<Item = &Self> {
395        (0..self.n_children as usize).map(move |i| self.child(i))
396    }
397
398    /// Returns if the field is semantically nullable,
399    /// regardless of whether it actually has null values.
400    pub fn nullable(&self) -> bool {
401        (self.flags / 2) & 1 == 1
402    }
403
404    /// Returns the reference to the underlying dictionary of the schema.
405    /// Check [ArrowSchema.dictionary](https://arrow.apache.org/docs/format/CDataInterface.html#c.ArrowSchema.dictionary).
406    ///
407    /// This must be `Some` if the schema represents a dictionary-encoded type, `None` otherwise.
408    pub fn dictionary(&self) -> Option<&Self> {
409        // Safety: per the C Data Interface spec, `self.dictionary` is either null (returns None)
410        // or a valid pointer to an `FFI_ArrowSchema` that lives at least as long as `self`.
411        unsafe { self.dictionary.as_ref() }
412    }
413
414    /// For map types, returns whether the keys within each map value are sorted.
415    ///
416    /// Refer to [Arrow Flags](https://arrow.apache.org/docs/format/CDataInterface.html#c.ArrowSchema.flags)
417    pub fn map_keys_sorted(&self) -> bool {
418        self.flags & 0b00000100 != 0
419    }
420
421    /// For dictionary-encoded types, returns whether the ordering of dictionary indices is semantically meaningful.
422    pub fn dictionary_ordered(&self) -> bool {
423        self.flags & 0b00000001 != 0
424    }
425
426    /// Returns the metadata in the schema as `Key-Value` pairs
427    pub fn metadata(&self) -> Result<HashMap<String, String>, ArrowError> {
428        if self.metadata.is_null() {
429            Ok(HashMap::new())
430        } else {
431            let mut pos = 0;
432
433            // On some platforms, c_char = u8, and on some, c_char = i8.
434            let buffer = self.metadata.cast::<u8>();
435
436            fn next_four_bytes(buffer: *const u8, pos: &mut usize) -> [u8; 4] {
437                // Safety: the caller advances `pos` only by the number of bytes consumed,
438                // so `*pos..*pos+4` is always within the bounds of the metadata buffer.
439                let out = unsafe {
440                    [
441                        *buffer.add(*pos),
442                        *buffer.add(*pos + 1),
443                        *buffer.add(*pos + 2),
444                        *buffer.add(*pos + 3),
445                    ]
446                };
447                *pos += 4;
448                out
449            }
450
451            fn next_n_bytes(buffer: *const u8, pos: &mut usize, n: usize) -> &[u8] {
452                // Safety: same as `next_four_bytes`; `*pos..*pos+n` is within the metadata buffer.
453                let out = unsafe { std::slice::from_raw_parts(buffer.add(*pos), n) };
454                *pos += n;
455                out
456            }
457
458            /// A length read from the metadata, which the producer may have got wrong.
459            fn checked_length(what: &str, length: i32) -> Result<usize, ArrowError> {
460                usize::try_from(length).map_err(|_| {
461                    ArrowError::CDataInterface(format!("Invalid {what} in metadata: {length}"))
462                })
463            }
464
465            let num_entries = i32::from_ne_bytes(next_four_bytes(buffer, &mut pos));
466            let num_entries = checked_length("number of entries", num_entries)?;
467
468            // The count comes from the producer, so do not preallocate all of it
469            let mut metadata = HashMap::with_capacity(num_entries.min(128));
470
471            for _ in 0..num_entries {
472                let key_length = i32::from_ne_bytes(next_four_bytes(buffer, &mut pos));
473                let key_length = checked_length("key length", key_length)?;
474                let key = String::from_utf8(next_n_bytes(buffer, &mut pos, key_length).to_vec())?;
475                let value_length = i32::from_ne_bytes(next_four_bytes(buffer, &mut pos));
476                let value_length = checked_length("value length", value_length)?;
477                let value =
478                    String::from_utf8(next_n_bytes(buffer, &mut pos, value_length).to_vec())?;
479                metadata.insert(key, value);
480            }
481
482            Ok(metadata)
483        }
484    }
485}
486
487impl Drop for FFI_ArrowSchema {
488    fn drop(&mut self) {
489        match self.release {
490            None => (),
491            // Safety: the release callback was set by the schema producer and follows the
492            // C Data Interface contract: it frees all resources associated with the schema
493            // and sets `release` to None. `self` is a valid, non-null pointer here.
494            Some(release) => unsafe { release(self) },
495        }
496    }
497}
498
499unsafe impl Send for FFI_ArrowSchema {}
500
501impl TryFrom<&FFI_ArrowSchema> for DataType {
502    type Error = ArrowError;
503
504    /// See [CDataInterface docs](https://arrow.apache.org/docs/format/CDataInterface.html#data-type-description-format-strings)
505    fn try_from(c_schema: &FFI_ArrowSchema) -> Result<Self, ArrowError> {
506        let mut dtype = match c_schema.format() {
507            "n" => DataType::Null,
508            "b" => DataType::Boolean,
509            "c" => DataType::Int8,
510            "C" => DataType::UInt8,
511            "s" => DataType::Int16,
512            "S" => DataType::UInt16,
513            "i" => DataType::Int32,
514            "I" => DataType::UInt32,
515            "l" => DataType::Int64,
516            "L" => DataType::UInt64,
517            "e" => DataType::Float16,
518            "f" => DataType::Float32,
519            "g" => DataType::Float64,
520            "vz" => DataType::BinaryView,
521            "z" => DataType::Binary,
522            "Z" => DataType::LargeBinary,
523            "vu" => DataType::Utf8View,
524            "u" => DataType::Utf8,
525            "U" => DataType::LargeUtf8,
526            "tdD" => DataType::Date32,
527            "tdm" => DataType::Date64,
528            "tts" => DataType::Time32(TimeUnit::Second),
529            "ttm" => DataType::Time32(TimeUnit::Millisecond),
530            "ttu" => DataType::Time64(TimeUnit::Microsecond),
531            "ttn" => DataType::Time64(TimeUnit::Nanosecond),
532            "tDs" => DataType::Duration(TimeUnit::Second),
533            "tDm" => DataType::Duration(TimeUnit::Millisecond),
534            "tDu" => DataType::Duration(TimeUnit::Microsecond),
535            "tDn" => DataType::Duration(TimeUnit::Nanosecond),
536            "tiM" => DataType::Interval(IntervalUnit::YearMonth),
537            "tiD" => DataType::Interval(IntervalUnit::DayTime),
538            "tin" => DataType::Interval(IntervalUnit::MonthDayNano),
539            "+l" => {
540                let c_child = c_schema.child(0);
541                DataType::List(Arc::new(Field::try_from(c_child)?))
542            }
543            "+L" => {
544                let c_child = c_schema.child(0);
545                DataType::LargeList(Arc::new(Field::try_from(c_child)?))
546            }
547            "+vl" => {
548                let c_child = c_schema.child(0);
549                DataType::ListView(Arc::new(Field::try_from(c_child)?))
550            }
551            "+vL" => {
552                let c_child = c_schema.child(0);
553                DataType::LargeListView(Arc::new(Field::try_from(c_child)?))
554            }
555            "+s" => {
556                let fields = c_schema.children().map(Field::try_from);
557                DataType::Struct(fields.collect::<Result<_, ArrowError>>()?)
558            }
559            "+m" => {
560                let c_child = c_schema.child(0);
561                let map_keys_sorted = c_schema.map_keys_sorted();
562                DataType::Map(Arc::new(Field::try_from(c_child)?), map_keys_sorted)
563            }
564            "+r" => {
565                let c_run_ends = c_schema.child(0);
566                let c_values = c_schema.child(1);
567                DataType::RunEndEncoded(
568                    Arc::new(Field::try_from(c_run_ends)?),
569                    Arc::new(Field::try_from(c_values)?),
570                )
571            }
572            // Parametrized types, requiring string parse
573            other => {
574                match other.splitn(2, ':').collect::<Vec<&str>>().as_slice() {
575                    // FixedSizeBinary type in format "w:num_bytes"
576                    ["w", num_bytes] => {
577                        let parsed_num_bytes = num_bytes.parse::<i32>().map_err(|_| {
578                            ArrowError::CDataInterface(
579                                "FixedSizeBinary requires an integer parameter representing number of bytes per element".to_string())
580                        })?;
581                        DataType::FixedSizeBinary(parsed_num_bytes)
582                    }
583                    // FixedSizeList type in format "+w:num_elems"
584                    ["+w", num_elems] => {
585                        let c_child = c_schema.child(0);
586                        let parsed_num_elems = num_elems.parse::<i32>().map_err(|_| {
587                            ArrowError::CDataInterface(
588                                "The FixedSizeList type requires an integer parameter representing number of elements per list".to_string())
589                        })?;
590                        DataType::FixedSizeList(
591                            Arc::new(Field::try_from(c_child)?),
592                            parsed_num_elems,
593                        )
594                    }
595                    // Decimal types in format "d:precision,scale" or "d:precision,scale,bitWidth"
596                    ["d", extra] => match extra.splitn(3, ',').collect::<Vec<&str>>().as_slice() {
597                        [precision, scale] => {
598                            let parsed_precision = precision.parse::<u8>().map_err(|_| {
599                                ArrowError::CDataInterface(
600                                    "The decimal type requires an integer precision".to_string(),
601                                )
602                            })?;
603                            let parsed_scale = scale.parse::<i8>().map_err(|_| {
604                                ArrowError::CDataInterface(
605                                    "The decimal type requires an integer scale".to_string(),
606                                )
607                            })?;
608                            DataType::Decimal128(parsed_precision, parsed_scale)
609                        }
610                        [precision, scale, bits] => {
611                            let parsed_precision = precision.parse::<u8>().map_err(|_| {
612                                ArrowError::CDataInterface(
613                                    "The decimal type requires an integer precision".to_string(),
614                                )
615                            })?;
616                            let parsed_scale = scale.parse::<i8>().map_err(|_| {
617                                ArrowError::CDataInterface(
618                                    "The decimal type requires an integer scale".to_string(),
619                                )
620                            })?;
621                            match *bits {
622                                    "32" => DataType::Decimal32(parsed_precision, parsed_scale),
623                                    "64" => DataType::Decimal64(parsed_precision, parsed_scale),
624                                    "128" => DataType::Decimal128(parsed_precision, parsed_scale),
625                                    "256" => DataType::Decimal256(parsed_precision, parsed_scale),
626                                    _ => return Err(ArrowError::CDataInterface("Only 32/64/128/256 bit wide decimals are supported in the Rust implementation".to_string())),
627                                }
628                        }
629                        _ => {
630                            return Err(ArrowError::CDataInterface(format!(
631                                "The decimal pattern \"d:{extra:?}\" is not supported in the Rust implementation"
632                            )));
633                        }
634                    },
635                    // DenseUnion
636                    ["+ud", extra] => {
637                        let type_ids = extra
638                            .split(',')
639                            .map(|t| {
640                                t.parse::<i8>().map_err(|_| {
641                                    ArrowError::CDataInterface(
642                                        "The Union type requires an integer type id".to_string(),
643                                    )
644                                })
645                            })
646                            .collect::<Result<Vec<_>, ArrowError>>()?;
647                        let mut fields = Vec::with_capacity(type_ids.len());
648                        for idx in 0..c_schema.n_children {
649                            let c_child = c_schema.child(idx as usize);
650                            let field = Field::try_from(c_child)?;
651                            fields.push(field);
652                        }
653
654                        if fields.len() != type_ids.len() {
655                            return Err(ArrowError::CDataInterface(
656                                "The Union type requires same number of fields and type ids"
657                                    .to_string(),
658                            ));
659                        }
660
661                        DataType::Union(UnionFields::try_new(type_ids, fields)?, UnionMode::Dense)
662                    }
663                    // SparseUnion
664                    ["+us", extra] => {
665                        let type_ids = extra
666                            .split(',')
667                            .map(|t| {
668                                t.parse::<i8>().map_err(|_| {
669                                    ArrowError::CDataInterface(
670                                        "The Union type requires an integer type id".to_string(),
671                                    )
672                                })
673                            })
674                            .collect::<Result<Vec<_>, ArrowError>>()?;
675                        let mut fields = Vec::with_capacity(type_ids.len());
676                        for idx in 0..c_schema.n_children {
677                            let c_child = c_schema.child(idx as usize);
678                            let field = Field::try_from(c_child)?;
679                            fields.push(field);
680                        }
681
682                        if fields.len() != type_ids.len() {
683                            return Err(ArrowError::CDataInterface(
684                                "The Union type requires same number of fields and type ids"
685                                    .to_string(),
686                            ));
687                        }
688
689                        DataType::Union(UnionFields::try_new(type_ids, fields)?, UnionMode::Sparse)
690                    }
691
692                    // Timestamps in format "tts:" and "tts:America/New_York" for no timezones and timezones resp.
693                    ["tss", ""] => DataType::Timestamp(TimeUnit::Second, None),
694                    ["tsm", ""] => DataType::Timestamp(TimeUnit::Millisecond, None),
695                    ["tsu", ""] => DataType::Timestamp(TimeUnit::Microsecond, None),
696                    ["tsn", ""] => DataType::Timestamp(TimeUnit::Nanosecond, None),
697                    ["tss", tz] => DataType::Timestamp(TimeUnit::Second, Some(Arc::from(*tz))),
698                    ["tsm", tz] => DataType::Timestamp(TimeUnit::Millisecond, Some(Arc::from(*tz))),
699                    ["tsu", tz] => DataType::Timestamp(TimeUnit::Microsecond, Some(Arc::from(*tz))),
700                    ["tsn", tz] => DataType::Timestamp(TimeUnit::Nanosecond, Some(Arc::from(*tz))),
701                    _ => {
702                        return Err(ArrowError::CDataInterface(format!(
703                            "The datatype \"{other:?}\" is still not supported in Rust implementation"
704                        )));
705                    }
706                }
707            }
708        };
709
710        if let Some(dict_schema) = c_schema.dictionary() {
711            let value_type = Self::try_from(dict_schema)?;
712            dtype = DataType::Dictionary(Box::new(dtype), Box::new(value_type));
713        }
714
715        Ok(dtype)
716    }
717}
718
719impl TryFrom<&FFI_ArrowSchema> for Field {
720    type Error = ArrowError;
721
722    fn try_from(c_schema: &FFI_ArrowSchema) -> Result<Self, ArrowError> {
723        let dtype = DataType::try_from(c_schema)?;
724        let field = Field::new(c_schema.name().unwrap_or(""), dtype, c_schema.nullable())
725            .with_dict_is_ordered(c_schema.dictionary_ordered())
726            .with_metadata(c_schema.metadata()?);
727        Ok(field)
728    }
729}
730
731impl TryFrom<&FFI_ArrowSchema> for Schema {
732    type Error = ArrowError;
733
734    fn try_from(c_schema: &FFI_ArrowSchema) -> Result<Self, ArrowError> {
735        // interpret it as a struct type then extract its fields
736        let dtype = DataType::try_from(c_schema)?;
737        if let DataType::Struct(fields) = dtype {
738            Ok(Schema::new(fields).with_metadata(c_schema.metadata()?))
739        } else {
740            Err(ArrowError::CDataInterface(
741                "Unable to interpret C data struct as a Schema".to_string(),
742            ))
743        }
744    }
745}
746
747impl TryFrom<&DataType> for FFI_ArrowSchema {
748    type Error = ArrowError;
749
750    /// See [CDataInterface docs](https://arrow.apache.org/docs/format/CDataInterface.html#data-type-description-format-strings)
751    fn try_from(dtype: &DataType) -> Result<Self, ArrowError> {
752        let format = get_format_string(dtype)?;
753        // allocate and hold the children
754        let children = match dtype {
755            DataType::List(child)
756            | DataType::LargeList(child)
757            | DataType::ListView(child)
758            | DataType::LargeListView(child)
759            | DataType::FixedSizeList(child, _)
760            | DataType::Map(child, _) => {
761                vec![FFI_ArrowSchema::try_from(child.as_ref())?]
762            }
763            DataType::Union(fields, _) => fields
764                .iter()
765                .map(|(_, f)| f.as_ref().try_into())
766                .collect::<Result<Vec<_>, ArrowError>>()?,
767            DataType::Struct(fields) => fields
768                .iter()
769                .map(FFI_ArrowSchema::try_from)
770                .collect::<Result<Vec<_>, ArrowError>>()?,
771            DataType::RunEndEncoded(run_ends, values) => vec![
772                FFI_ArrowSchema::try_from(run_ends.as_ref())?,
773                FFI_ArrowSchema::try_from(values.as_ref())?,
774            ],
775            _ => vec![],
776        };
777        let dictionary = if let DataType::Dictionary(_, value_data_type) = dtype {
778            Some(Self::try_from(value_data_type.as_ref())?)
779        } else {
780            None
781        };
782
783        let flags = match dtype {
784            DataType::Map(_, true) => Flags::MAP_KEYS_SORTED,
785            _ => Flags::empty(),
786        };
787
788        FFI_ArrowSchema::try_new(&format, children, dictionary)?.with_flags(flags)
789    }
790}
791
792fn get_format_string(dtype: &DataType) -> Result<Cow<'static, str>, ArrowError> {
793    match dtype {
794        DataType::Null => Ok("n".into()),
795        DataType::Boolean => Ok("b".into()),
796        DataType::Int8 => Ok("c".into()),
797        DataType::UInt8 => Ok("C".into()),
798        DataType::Int16 => Ok("s".into()),
799        DataType::UInt16 => Ok("S".into()),
800        DataType::Int32 => Ok("i".into()),
801        DataType::UInt32 => Ok("I".into()),
802        DataType::Int64 => Ok("l".into()),
803        DataType::UInt64 => Ok("L".into()),
804        DataType::Float16 => Ok("e".into()),
805        DataType::Float32 => Ok("f".into()),
806        DataType::Float64 => Ok("g".into()),
807        DataType::BinaryView => Ok("vz".into()),
808        DataType::Binary => Ok("z".into()),
809        DataType::LargeBinary => Ok("Z".into()),
810        DataType::Utf8View => Ok("vu".into()),
811        DataType::Utf8 => Ok("u".into()),
812        DataType::LargeUtf8 => Ok("U".into()),
813        DataType::FixedSizeBinary(num_bytes) => Ok(Cow::Owned(format!("w:{num_bytes}"))),
814        DataType::FixedSizeList(_, num_elems) => Ok(Cow::Owned(format!("+w:{num_elems}"))),
815        DataType::Decimal32(precision, scale) => {
816            Ok(Cow::Owned(format!("d:{precision},{scale},32")))
817        }
818        DataType::Decimal64(precision, scale) => {
819            Ok(Cow::Owned(format!("d:{precision},{scale},64")))
820        }
821        DataType::Decimal128(precision, scale) => Ok(Cow::Owned(format!("d:{precision},{scale}"))),
822        DataType::Decimal256(precision, scale) => {
823            Ok(Cow::Owned(format!("d:{precision},{scale},256")))
824        }
825        DataType::Date32 => Ok("tdD".into()),
826        DataType::Date64 => Ok("tdm".into()),
827        DataType::Time32(TimeUnit::Second) => Ok("tts".into()),
828        DataType::Time32(TimeUnit::Millisecond) => Ok("ttm".into()),
829        DataType::Time64(TimeUnit::Microsecond) => Ok("ttu".into()),
830        DataType::Time64(TimeUnit::Nanosecond) => Ok("ttn".into()),
831        DataType::Timestamp(TimeUnit::Second, None) => Ok("tss:".into()),
832        DataType::Timestamp(TimeUnit::Millisecond, None) => Ok("tsm:".into()),
833        DataType::Timestamp(TimeUnit::Microsecond, None) => Ok("tsu:".into()),
834        DataType::Timestamp(TimeUnit::Nanosecond, None) => Ok("tsn:".into()),
835        DataType::Timestamp(TimeUnit::Second, Some(tz)) => Ok(Cow::Owned(format!("tss:{tz}"))),
836        DataType::Timestamp(TimeUnit::Millisecond, Some(tz)) => Ok(Cow::Owned(format!("tsm:{tz}"))),
837        DataType::Timestamp(TimeUnit::Microsecond, Some(tz)) => Ok(Cow::Owned(format!("tsu:{tz}"))),
838        DataType::Timestamp(TimeUnit::Nanosecond, Some(tz)) => Ok(Cow::Owned(format!("tsn:{tz}"))),
839        DataType::Duration(TimeUnit::Second) => Ok("tDs".into()),
840        DataType::Duration(TimeUnit::Millisecond) => Ok("tDm".into()),
841        DataType::Duration(TimeUnit::Microsecond) => Ok("tDu".into()),
842        DataType::Duration(TimeUnit::Nanosecond) => Ok("tDn".into()),
843        DataType::Interval(IntervalUnit::YearMonth) => Ok("tiM".into()),
844        DataType::Interval(IntervalUnit::DayTime) => Ok("tiD".into()),
845        DataType::Interval(IntervalUnit::MonthDayNano) => Ok("tin".into()),
846        DataType::List(_) => Ok("+l".into()),
847        DataType::LargeList(_) => Ok("+L".into()),
848        DataType::ListView(_) => Ok("+vl".into()),
849        DataType::LargeListView(_) => Ok("+vL".into()),
850        DataType::Struct(_) => Ok("+s".into()),
851        DataType::Map(_, _) => Ok("+m".into()),
852        DataType::RunEndEncoded(_, _) => Ok("+r".into()),
853        DataType::Dictionary(key_data_type, _) => get_format_string(key_data_type),
854        DataType::Union(fields, mode) => {
855            let formats = fields
856                .iter()
857                .map(|(t, _)| t.to_string())
858                .collect::<Vec<_>>();
859            match mode {
860                UnionMode::Dense => Ok(Cow::Owned(format!("{}:{}", "+ud", formats.join(",")))),
861                UnionMode::Sparse => Ok(Cow::Owned(format!("{}:{}", "+us", formats.join(",")))),
862            }
863        }
864        other => Err(ArrowError::CDataInterface(format!(
865            "The datatype \"{other:?}\" is still not supported in Rust implementation"
866        ))),
867    }
868}
869
870impl TryFrom<&FieldRef> for FFI_ArrowSchema {
871    type Error = ArrowError;
872
873    fn try_from(value: &FieldRef) -> Result<Self, Self::Error> {
874        value.as_ref().try_into()
875    }
876}
877
878impl TryFrom<&Field> for FFI_ArrowSchema {
879    type Error = ArrowError;
880
881    fn try_from(field: &Field) -> Result<Self, ArrowError> {
882        let mut flags = if field.is_nullable() {
883            Flags::NULLABLE
884        } else {
885            Flags::empty()
886        };
887
888        if field.dict_is_ordered() == Some(true) {
889            flags |= Flags::DICTIONARY_ORDERED;
890        }
891
892        let schema = FFI_ArrowSchema::try_from(field.data_type())?
893            .with_name(field.name())?
894            .with_flags(flags)?;
895        // SAFETY: schema was just constructed by this crate.
896        unsafe { schema.with_metadata(field.metadata()) }
897    }
898}
899
900impl TryFrom<&Schema> for FFI_ArrowSchema {
901    type Error = ArrowError;
902
903    fn try_from(schema: &Schema) -> Result<Self, ArrowError> {
904        let dtype = DataType::Struct(schema.fields().clone());
905        let c_schema = FFI_ArrowSchema::try_from(&dtype)?;
906        // SAFETY: c_schema was just constructed by this crate.
907        unsafe { c_schema.with_metadata(&schema.metadata) }
908    }
909}
910
911impl TryFrom<DataType> for FFI_ArrowSchema {
912    type Error = ArrowError;
913
914    fn try_from(dtype: DataType) -> Result<Self, ArrowError> {
915        FFI_ArrowSchema::try_from(&dtype)
916    }
917}
918
919impl TryFrom<Field> for FFI_ArrowSchema {
920    type Error = ArrowError;
921
922    fn try_from(field: Field) -> Result<Self, ArrowError> {
923        FFI_ArrowSchema::try_from(&field)
924    }
925}
926
927impl TryFrom<Schema> for FFI_ArrowSchema {
928    type Error = ArrowError;
929
930    fn try_from(schema: Schema) -> Result<Self, ArrowError> {
931        FFI_ArrowSchema::try_from(&schema)
932    }
933}
934
935#[cfg(test)]
936mod tests {
937    use super::*;
938    use crate::Fields;
939    use std::sync::atomic::{AtomicBool, Ordering};
940
941    fn round_trip_type(dtype: DataType) {
942        let c_schema = FFI_ArrowSchema::try_from(&dtype).unwrap();
943        let restored = DataType::try_from(&c_schema).unwrap();
944        assert_eq!(restored, dtype);
945    }
946
947    fn round_trip_field(field: Field) {
948        let c_schema = FFI_ArrowSchema::try_from(&field).unwrap();
949        let restored = Field::try_from(&c_schema).unwrap();
950        assert_eq!(restored, field);
951    }
952
953    fn round_trip_schema(schema: Schema) {
954        let c_schema = FFI_ArrowSchema::try_from(&schema).unwrap();
955        let restored = Schema::try_from(&c_schema).unwrap();
956        assert_eq!(restored, schema);
957    }
958
959    #[test]
960    fn test_try_new_with_interior_nul_byte() {
961        let err = FFI_ArrowSchema::try_new("i\0nt", vec![], None).unwrap_err();
962        assert_eq!(
963            err.to_string(),
964            "C Data interface error: Null byte at position 1 not allowed in format"
965        );
966    }
967
968    #[test]
969    fn test_type() {
970        round_trip_type(DataType::Int64);
971        round_trip_type(DataType::UInt64);
972        round_trip_type(DataType::Float64);
973        round_trip_type(DataType::Date64);
974        round_trip_type(DataType::Time64(TimeUnit::Nanosecond));
975        round_trip_type(DataType::FixedSizeBinary(12));
976        round_trip_type(DataType::FixedSizeList(
977            Arc::new(Field::new("a", DataType::Int64, false)),
978            5,
979        ));
980        round_trip_type(DataType::Utf8);
981        round_trip_type(DataType::Utf8View);
982        round_trip_type(DataType::BinaryView);
983        round_trip_type(DataType::Binary);
984        round_trip_type(DataType::LargeBinary);
985        round_trip_type(DataType::List(Arc::new(Field::new(
986            "a",
987            DataType::Int16,
988            false,
989        ))));
990        round_trip_type(DataType::ListView(Arc::new(Field::new(
991            "a",
992            DataType::Int16,
993            false,
994        ))));
995        round_trip_type(DataType::LargeListView(Arc::new(Field::new(
996            "a",
997            DataType::Int16,
998            false,
999        ))));
1000        round_trip_type(DataType::Struct(Fields::from(vec![Field::new(
1001            "a",
1002            DataType::Utf8,
1003            true,
1004        )])));
1005        round_trip_type(DataType::RunEndEncoded(
1006            Arc::new(Field::new("run_ends", DataType::Int32, false)),
1007            Arc::new(Field::new("values", DataType::Binary, true)),
1008        ));
1009    }
1010
1011    #[test]
1012    fn test_field() {
1013        let dtype = DataType::Struct(vec![Field::new("a", DataType::Utf8, true)].into());
1014        round_trip_field(Field::new("test", dtype, true));
1015    }
1016
1017    #[test]
1018    fn test_schema() {
1019        let schema = Schema::new(vec![
1020            Field::new("name", DataType::Utf8, false),
1021            Field::new("address", DataType::Utf8, false),
1022            Field::new("priority", DataType::UInt8, false),
1023        ])
1024        .with_metadata([("hello", "world")]);
1025
1026        round_trip_schema(schema);
1027
1028        // test that we can interpret struct types as schema
1029        let dtype = DataType::Struct(Fields::from(vec![
1030            Field::new("a", DataType::Utf8, true),
1031            Field::new("b", DataType::Int16, false),
1032        ]));
1033        let c_schema = FFI_ArrowSchema::try_from(&dtype).unwrap();
1034        let schema = Schema::try_from(&c_schema).unwrap();
1035        assert_eq!(schema.fields().len(), 2);
1036
1037        // test that we assert the input type
1038        let c_schema = FFI_ArrowSchema::try_from(&DataType::Float64).unwrap();
1039        let result = Schema::try_from(&c_schema);
1040        assert!(result.is_err());
1041    }
1042
1043    #[test]
1044    fn test_map_keys_sorted() {
1045        let keys = Field::new(Field::MAP_KEY_FIELD_DEFAULT_NAME, DataType::Int32, false);
1046        let values = Field::new(Field::MAP_VALUE_FIELD_DEFAULT_NAME, DataType::UInt32, false);
1047        let entry_struct = DataType::Struct(vec![keys, values].into());
1048
1049        // Construct a map array from the above two
1050        let map_data_type = DataType::Map(
1051            Arc::new(Field::new(
1052                Field::MAP_ENTRIES_FIELD_DEFAULT_NAME,
1053                entry_struct,
1054                false,
1055            )),
1056            true,
1057        );
1058
1059        let arrow_schema = FFI_ArrowSchema::try_from(map_data_type).unwrap();
1060        assert!(arrow_schema.map_keys_sorted());
1061    }
1062
1063    #[test]
1064    fn test_dictionary_ordered() {
1065        #[expect(deprecated)]
1066        let schema = Schema::new(vec![Field::new_dict(
1067            "dict",
1068            DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
1069            false,
1070            0,
1071            true,
1072        )]);
1073
1074        let arrow_schema = FFI_ArrowSchema::try_from(schema).unwrap();
1075        assert!(arrow_schema.child(0).dictionary_ordered());
1076
1077        // Round-trip: the ordered flag must be preserved when converting back to a Field.
1078        let field = Field::try_from(arrow_schema.child(0)).unwrap();
1079        assert_eq!(field.dict_is_ordered(), Some(true));
1080    }
1081
1082    #[test]
1083    fn test_set_field_metadata() {
1084        let metadata_cases: Vec<HashMap<String, String>> = vec![
1085            [].into(),
1086            [("key".to_string(), "value".to_string())].into(),
1087            [
1088                ("key".to_string(), String::new()),
1089                ("ascii123".to_string(), "你好".to_string()),
1090                (String::new(), "value".to_string()),
1091            ]
1092            .into(),
1093        ];
1094
1095        let mut schema = FFI_ArrowSchema::try_new("b", vec![], None)
1096            .unwrap()
1097            .with_name("test")
1098            .unwrap();
1099
1100        for metadata in metadata_cases {
1101            // SAFETY: schema was constructed by this crate via try_new.
1102            schema = unsafe { schema.with_metadata(&metadata) }.unwrap();
1103            let field = Field::try_from(&schema).unwrap();
1104            assert_eq!(field.metadata(), &metadata);
1105        }
1106    }
1107
1108    #[test]
1109    fn test_name_with_null_byte() {
1110        let schema = FFI_ArrowSchema::try_new("i", vec![], None).unwrap();
1111        assert!(schema.with_name("ab\0cd").is_err());
1112    }
1113
1114    #[test]
1115    fn test_import_field_with_null_name() {
1116        let dtype = DataType::Int16;
1117        let c_schema = FFI_ArrowSchema::try_from(&dtype).unwrap();
1118        assert!(c_schema.name().is_none());
1119        let field = Field::try_from(&c_schema).unwrap();
1120        assert_eq!(field.name(), "");
1121    }
1122
1123    // A consumer wraps the release callback with its own, then chains back to
1124    // the original on drop. This is the cross-thread use case from #9771 and is
1125    // what the release/private_data accessors exist for.
1126    static WRAPPER_RAN: AtomicBool = AtomicBool::new(false);
1127
1128    struct WrapperData {
1129        original_release: Option<unsafe extern "C" fn(*mut FFI_ArrowSchema)>,
1130        original_private_data: *mut c_void,
1131    }
1132
1133    unsafe extern "C" fn wrapping_release(schema: *mut FFI_ArrowSchema) {
1134        let schema = unsafe { &mut *schema };
1135        let data = unsafe { Box::from_raw(schema.private_data().cast::<WrapperData>()) };
1136        WRAPPER_RAN.store(true, Ordering::SeqCst);
1137        // restore the originals, then let the original callback free everything
1138        unsafe { schema.set_release(data.original_release) };
1139        unsafe { schema.set_private_data(data.original_private_data) };
1140        if let Some(release) = schema.release() {
1141            unsafe { release(schema) };
1142        }
1143    }
1144
1145    #[test]
1146    fn test_wrap_release_callback() {
1147        let mut schema = FFI_ArrowSchema::try_from(&DataType::Int32).unwrap();
1148
1149        let data = Box::new(WrapperData {
1150            original_release: schema.release(),
1151            original_private_data: schema.private_data(),
1152        });
1153        unsafe { schema.set_release(Some(wrapping_release)) };
1154        unsafe { schema.set_private_data(Box::into_raw(data).cast::<c_void>()) };
1155
1156        drop(schema); // runs wrapping_release, which chains to the original
1157        assert!(WRAPPER_RAN.load(Ordering::SeqCst));
1158    }
1159}