Skip to main content

minarrow/structs/
field_array.rs

1// Copyright 2025 Peter Garfield Bower
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # **FieldArray Module** - *De-Facto *Column* Array type w' Tagged Arrow Metadata*
16//!
17//! Couples a `Field` (i.e., array-level schema metadata) with an immutable `Array` of values.
18//!
19//! Used as the primary column representation in `Minarrow` tables, ensuring
20//! schema and data remain consistent.
21//!
22//! Supports creation from raw components or by inferring schema from arrays,
23//! and is the unit transferred over Arrow FFI or to external libraries
24//! such as Apache Arrow or Polars.
25//!
26//! ## Apache Arrow / Polars bridges (`cast_arrow` / `cast_polars` features)
27//!
28//! - `to_apache_arrow()` / `to_polars()` export with the stored `Field`, so
29//!   Timestamp/Time/Duration/Interval logical types round-trip correctly.
30//! - `from_apache_arrow(name, &ArrayRef)` and `from_polars(&Series)` recover
31//!   dtype, nullability, and any custom field metadata from the FFI schema.
32//!   For polars, the column name comes from `s.name()`.
33//! - Each panicking method has a `try_*` sibling returning
34//!   `Result<_, MinarrowError>`. `&Series` can also be converted via
35//!   `(&series).into()` for ergonomic call sites.
36
37use std::collections::BTreeMap;
38use std::fmt::{Display, Formatter};
39use std::sync::Arc;
40
41#[cfg(feature = "cast_arrow")]
42use arrow::array::ArrayRef;
43#[cfg(feature = "cast_polars")]
44use polars::series::Series;
45
46#[cfg(all(feature = "select", feature = "views"))]
47use crate::ArrayV;
48#[cfg(feature = "views")]
49use crate::aliases::FieldAVT;
50use crate::enums::error::MinarrowError;
51use crate::enums::shape_dim::ShapeDim;
52use crate::ffi::arrow_dtype::ArrowType;
53use crate::ffi::arrow_dtype::CategoricalIndexType;
54use crate::traits::concatenate::Concatenate;
55#[cfg(all(feature = "select", feature = "views"))]
56use crate::traits::selection::{DataSelector, RowSelection};
57use crate::traits::shape::Shape;
58use crate::{Array, Field, NumericArray, TextArray};
59#[cfg(feature = "datetime")]
60use crate::{TemporalArray, TimeUnit};
61
62/// # FieldArray
63///
64/// Named and typed data column with associated array values.
65///
66/// ## Role
67/// - Combines a `Field` with an immutable `Array` instance.
68/// - `FieldArray` integrates naturally into a `Table`, where immutability enforces row-length guarantees.
69/// It can also serve as a self-documenting array and is required when sending `Minarrow` data
70/// over FFI to `Apache Arrow`. In such cases, it's worth ensuring the correct logical `Datetime` Arrow type
71/// is built when constructing the `Field`, as this determines the `Arrow` type on the receiving side.
72///
73/// ## Examples
74/// ```rust
75/// use minarrow::{Array, Field, FieldArray, MaskedArray};
76/// use minarrow::ffi::arrow_dtype::ArrowType;
77/// use minarrow::structs::variants::integer::IntegerArray;
78///
79/// // Build a typed array
80/// let mut ints = IntegerArray::<i32>::default();
81/// ints.push(1);
82/// ints.push(2);
83/// let arr = Array::from_int32(ints);
84///
85/// // Construct with a Field and Array
86/// let field = Field::new("id", ArrowType::Int32, false, None);
87/// let fa = FieldArray::new(field, arr);
88///
89/// assert_eq!(fa.field.name, "id");
90/// assert_eq!(fa.arrow_type(), ArrowType::Int32);
91/// assert_eq!(fa.len(), 2);
92///
93/// // Take an owned slice [offset..offset+len)
94/// let sub = fa.slice_clone(0, 1);
95/// assert_eq!(sub.len(), 1);
96/// ```
97///
98/// The `fa_*` macros are a concise construction shorthand:
99/// ```rust
100/// use minarrow::{fa_i32, fa_i32_opt, fa_str32};
101/// use minarrow::ffi::arrow_dtype::ArrowType;
102///
103/// // From literal values - type and nullability inferred
104/// let fa = fa_i32!("id", 1, 2);
105/// assert_eq!(fa.field.name, "id");
106/// assert_eq!(fa.arrow_type(), ArrowType::Int32);
107/// assert_eq!(fa.len(), 2);
108///
109/// // Nullable variant with Option values
110/// let fa = fa_i32_opt!("score", Some(10i32), None::<i32>, Some(30));
111/// assert_eq!(fa.len(), 3);
112/// assert!(fa.field.nullable);
113/// assert_eq!(fa.null_count(), 1);
114///
115/// // String columns
116/// let names = fa_str32!("name", "alice", "bob");
117/// assert_eq!(names.field.name, "name");
118/// assert_eq!(names.len(), 2);
119/// ```
120#[derive(Debug, Clone, PartialEq)]
121pub struct FieldArray {
122    /// Array metadata
123    pub field: Arc<Field>,
124
125    /// The array's inner payload is wrapped in Arc for immutability
126    /// so it can safely share across threads.
127    /// When part of a Table *(or higher-dimensional structure)*,
128    /// immutability also upholds shape constraints.
129    pub array: Array,
130
131    /// Null count for the immutable array to support skipping null-mask
132    /// operations when it's `0`, and/or related strategies.
133    pub null_count: usize,
134}
135
136impl FieldArray {
137    /// Constructs a new `FieldArray` from an existing `Field` and `Array`.
138    pub fn new(field: Field, array: Array) -> Self {
139        let null_count = array.null_count();
140        FieldArray {
141            field: field.into(),
142            array,
143            null_count,
144        }
145    }
146
147    /// Constructs a new `FieldArray` from an existing `Arc<Field>` and `Array`.
148    pub fn new_arc(field: Arc<Field>, array: Array) -> Self {
149        let null_count = array.null_count();
150        FieldArray {
151            field: field,
152            array,
153            null_count,
154        }
155    }
156
157    /// Constructs a new `FieldArray` from a name and any supported typed array,
158    /// automatically wrapping as `Array` and inferring type and nullability.
159    pub fn from_arr<N, A>(name: N, arr: A) -> Self
160    where
161        N: Into<String>,
162        A: Into<Array>,
163    {
164        let array: Array = arr.into();
165        let dtype = array.arrow_type();
166        let nullable = array.is_nullable();
167        let field = Field::new(name, dtype, nullable, None);
168        FieldArray::new(field, array)
169    }
170
171    /// Constructs a new `FieldArray` from raw field components and an `Array`.
172    pub fn from_parts<T: Into<String>>(
173        field_name: T,
174        dtype: ArrowType,
175        nullable: Option<bool>,
176        metadata: Option<BTreeMap<String, String>>,
177        array: Array,
178    ) -> Self {
179        let null_count = array.null_count();
180        let field = Field {
181            name: field_name.into(),
182            dtype,
183            nullable: nullable.unwrap_or_else(|| array.is_nullable()),
184            metadata: metadata.unwrap_or_default(),
185        };
186        FieldArray {
187            field: field.into(),
188            array: array.into(),
189            null_count,
190        }
191    }
192
193    pub fn len(&self) -> usize {
194        self.array.len()
195    }
196
197    /// Removes the rows in `[start, end)`, shifting later rows left, and
198    /// refreshes the cached null count.
199    ///
200    /// # Panics
201    /// Panics if `start > end` or `end > len`.
202    pub fn delete_range(&mut self, start: usize, end: usize) {
203        self.array.delete_range(start, end);
204        self.null_count = self.array.null_count();
205    }
206
207    pub fn is_empty(&self) -> bool {
208        self.array.len() == 0
209    }
210
211    pub fn arrow_type(&self) -> ArrowType {
212        self.field.dtype.clone()
213    }
214
215    /// Returns a new FieldArray with updated timezone metadata.
216    ///
217    /// The underlying timestamp data (always UTC) remains unchanged. Only the timezone
218    /// metadata in the Field's ArrowType is updated for interpretation/display purposes.
219    ///
220    /// # Arguments
221    /// * `tz` - Timezone string in Arrow format (IANA like "America/New_York" or offset like "+05:00")
222    ///
223    /// # Errors
224    /// Returns an error if the array is not a Timestamp type.
225    #[cfg(feature = "datetime")]
226    pub fn tz(&self, tz: &str) -> Result<Self, MinarrowError> {
227        match &self.field.dtype {
228            ArrowType::Timestamp(unit, _) => {
229                let mut new_field = (*self.field).clone();
230                new_field.dtype = ArrowType::Timestamp(*unit, Some(tz.to_string()));
231                Ok(FieldArray {
232                    field: Arc::new(new_field),
233                    array: self.array.clone(),
234                    null_count: self.null_count,
235                })
236            }
237            _ => Err(MinarrowError::TypeError {
238                from: "FieldArray",
239                to: "Timestamp",
240                message: Some("tz() requires a Timestamp type".to_string()),
241            }),
242        }
243    }
244
245    /// Returns a new FieldArray with timezone metadata set to "UTC".
246    ///
247    /// The underlying timestamp data (always UTC) remains unchanged. Only the timezone
248    /// metadata in the Field's ArrowType is updated.
249    ///
250    /// # Errors
251    /// Returns an error if the array is not a Timestamp type.
252    #[cfg(feature = "datetime")]
253    pub fn utc(&self) -> Result<Self, MinarrowError> {
254        self.tz("UTC")
255    }
256
257    /// Returns a zero-copy view (`FieldArraySlice`) into the window `[offset, offset+len)`.
258    ///
259    /// The returned object holds references into the original `FieldArray`.
260    ///
261    /// The `(&Array, Offset, WindowLength), &Field)` `FieldArraySlice` pattern here
262    /// is a once-off we avoid recommending.
263    #[cfg(feature = "views")]
264    #[inline]
265    pub fn view(&self, offset: usize, len: usize) -> FieldAVT<'_> {
266        ((&self.array, offset, len), &self.field)
267    }
268
269    /// Returns a new owned FieldArray with array sliced `[offset, offset+len)`.
270    pub fn slice_clone(&self, offset: usize, len: usize) -> Self {
271        let array: Array = self.array.slice_clone(offset, len).into();
272        let null_count = array.null_count();
273        FieldArray {
274            field: self.field.clone(),
275            array: array.into(),
276            null_count,
277        }
278    }
279
280    /// Updates the cached null_count from the underlying array.
281    /// Should be called after any mutation of the array that could change null count.
282    #[inline]
283    pub fn refresh_null_count(&mut self) {
284        self.null_count = self.array.null_count();
285    }
286
287    /// Returns the cached null count.
288    /// This is kept in sync with the underlying array via refresh_null_count().
289    #[inline]
290    pub fn null_count(&self) -> usize {
291        self.null_count
292    }
293
294    /// Concatenates another FieldArray's data into this one using copy-on-write semantics.
295    /// If this FieldArray's array has Arc reference count > 1, the data is cloned first.
296    /// Both FieldArrays must have compatible types. Updates the cached null_count.
297    pub fn concat_field_array(&mut self, other: &FieldArray) {
298        self.array.concat_array(&other.array);
299        self.refresh_null_count();
300    }
301
302    /// Appends rows `[offset..offset+len)` from another FieldArray into self.
303    /// Extends data directly from the source's backing buffer.
304    pub fn concat_range(
305        &mut self,
306        other: &FieldArray,
307        offset: usize,
308        len: usize,
309    ) -> Result<(), MinarrowError> {
310        self.array.concat_array_range(&other.array, offset, len)?;
311        self.refresh_null_count();
312        Ok(())
313    }
314
315    /// Provides mutable access to the underlying array with automatic null_count refresh.
316    /// Uses copy-on-write semantics - clones array data if Arc reference count > 1.
317    /// Use this for operations that may change the null count.
318    pub fn with_array_mut<F, R>(&mut self, f: F) -> R
319    where
320        F: FnOnce(&mut Array) -> R,
321    {
322        let result = f(&mut self.array);
323        self.refresh_null_count();
324        result
325    }
326
327    /// Export this field+array over Arrow C-FFI and import into arrow-rs.
328    ///
329    /// Carries the stored `Field` through the conversion, preserving logical
330    /// types such as Timestamp/Time/Duration/Interval.
331    ///
332    /// Panics on FFI failure. For a fallible variant, see
333    /// [`FieldArray::try_to_apache_arrow`].
334    #[cfg(feature = "cast_arrow")]
335    #[inline]
336    pub fn to_apache_arrow(&self) -> ArrayRef {
337        self.try_to_apache_arrow()
338            .expect("FieldArray::to_apache_arrow failed")
339    }
340
341    /// Fallible variant of [`FieldArray::to_apache_arrow`].
342    #[cfg(feature = "cast_arrow")]
343    #[inline]
344    pub fn try_to_apache_arrow(&self) -> Result<ArrayRef, MinarrowError> {
345        crate::ffi::arrow_rs::export(
346            Arc::new(self.array.clone()),
347            crate::ffi::schema::Schema::from(vec![(*self.field).clone()]),
348        )
349    }
350
351    // ** The below polars function is tested tests/polars.rs **
352
353    /// Casts the FieldArray to a polars Series, preserving the stored `Field`.
354    ///
355    /// Panics on FFI failure. For a fallible variant, see
356    /// [`FieldArray::try_to_polars`].
357    #[cfg(feature = "cast_polars")]
358    pub fn to_polars(&self) -> Series {
359        self.try_to_polars().expect("FieldArray::to_polars failed")
360    }
361
362    /// Fallible variant of [`FieldArray::to_polars`].
363    #[cfg(feature = "cast_polars")]
364    pub fn try_to_polars(&self) -> Result<Series, MinarrowError> {
365        crate::ffi::polars::export(
366            Arc::new(self.array.clone()),
367            self.field.name.as_str(),
368            crate::ffi::schema::Schema::from(vec![(*self.field).clone()]),
369        )
370    }
371
372    // ===========================================================
373    // Apache Arrow / Polars import (`from_*`)
374    // ===========================================================
375
376    /// Build a `FieldArray` from an arrow-rs `ArrayRef` and a column name.
377    ///
378    /// arrow-rs `ArrayRef` does not carry a column name (only `Schema`/`Field` do),
379    /// so the caller must provide one. Dtype, nullability, and any custom metadata
380    /// are recovered from the FFI schema.
381    ///
382    /// Panics on FFI failure. For a fallible variant, see
383    /// [`FieldArray::try_from_apache_arrow`].
384    #[cfg(feature = "cast_arrow")]
385    #[inline]
386    pub fn from_apache_arrow(name: impl Into<String>, arr: &arrow::array::ArrayRef) -> FieldArray {
387        Self::try_from_apache_arrow(name, arr).expect("FieldArray::from_apache_arrow failed")
388    }
389
390    /// Fallible variant of [`FieldArray::from_apache_arrow`].
391    #[cfg(feature = "cast_arrow")]
392    pub fn try_from_apache_arrow(
393        name: impl Into<String>,
394        arr: &arrow::array::ArrayRef,
395    ) -> Result<FieldArray, MinarrowError> {
396        let (array_arc, mut field) = crate::ffi::arrow_rs::import(arr)?;
397        field.name = name.into();
398        let array = Arc::try_unwrap(array_arc).unwrap_or_else(|arc| (*arc).clone());
399        Ok(FieldArray::new(field, array))
400    }
401
402    /// Build a `FieldArray` from a Polars `Series`.
403    ///
404    /// A polars `Series` is inherently multi-chunked; the canonical mapping
405    /// is `Series` <-> [`crate::SuperArray`]. This helper routes through
406    /// [`crate::SuperArray::from_polars`] and then **consolidates** the
407    /// chunks into a single contiguous, 64-byte aligned buffer.
408    ///
409    /// ## Performance note
410    /// Two separate costs to be aware of:
411    ///
412    /// 1. **Alignment copy**: Polars data is typically 8-byte aligned (per
413    ///    the Arrow spec default), while Minarrow uses 64-byte aligned
414    ///    `Vec64<T>` buffers for SIMD. Most of the time this results in a
415    ///    memory copy to realign on import, unless the source data happens
416    ///    to be pre-aligned to 64 bytes. The FFI hand-off itself is
417    ///    pointer-level zero-copy; the realignment is done by
418    ///    `Buffer::from_shared` when the source isn't 64-byte aligned.
419    ///
420    /// 2. **Consolidation copy**: Multi-chunk Series are merged into a
421    ///    single contiguous buffer, which is a second O(n) allocation and
422    ///    copy pass. Single-chunk Series (e.g. after `s.rechunk()` on the
423    ///    caller side) skip this step. The consolidation itself is cheap
424    ///    on Linux when the `vmap64` feature is enabled.
425    ///
426    /// In practice you should expect at least one full allocation + copy
427    /// when importing polars data into a `FieldArray`. If you would like to
428    /// preserve the original chunk boundaries and avoid the consolidation
429    /// step, use [`crate::SuperArray::from_polars`] directly - though the
430    /// alignment copy will still occur per chunk that isn't pre-aligned.
431    ///
432    /// Name comes from `s.name()`; dtype, nullability, and any custom
433    /// metadata are recovered from the FFI schema.
434    ///
435    /// Panics on FFI failure. For a fallible variant, see
436    /// [`FieldArray::try_from_polars`].
437    #[cfg(feature = "cast_polars")]
438    #[inline]
439    pub fn from_polars(s: &polars::prelude::Series) -> FieldArray {
440        Self::try_from_polars(s).expect("FieldArray::from_polars failed")
441    }
442
443    /// Fallible variant of [`FieldArray::from_polars`].
444    #[cfg(feature = "cast_polars")]
445    pub fn try_from_polars(s: &polars::prelude::Series) -> Result<FieldArray, MinarrowError> {
446        use crate::SuperArray;
447        use crate::traits::consolidate::Consolidate;
448        let sa = SuperArray::try_from_polars(s)?;
449        let field = sa.field_ref().clone();
450        let array = sa.consolidate();
451        Ok(FieldArray::new(field, array))
452    }
453}
454
455// Provides `(&series).into()` ergonomics for building a FieldArray.
456// Panics on FFI failure - users who want the fallible flavour should call
457// the named `FieldArray::try_from_polars(&s)?` method instead.
458#[cfg(feature = "cast_polars")]
459impl From<&polars::prelude::Series> for FieldArray {
460    fn from(s: &polars::prelude::Series) -> Self {
461        FieldArray::from_polars(s)
462    }
463}
464
465/// Creates a new basic field array based on a name and an existing array
466pub fn field_array<T: Into<String>>(name: T, array: Array) -> FieldArray {
467    let dtype = array.arrow_type();
468    let nullable = array.is_nullable();
469    let field = Field::new(name, dtype, nullable, None);
470    FieldArray::new(field, array)
471}
472
473/// Helper to create a proper Field for an Array with correct type, mask, and metadata
474pub fn create_field_for_array(
475    name: &str,
476    array: &Array,
477    other_array: Option<&Array>,
478    metadata: Option<std::collections::BTreeMap<String, String>>,
479) -> Field {
480    let arrow_type = match array {
481        Array::NumericArray(num_arr) => match num_arr {
482            #[cfg(feature = "extended_numeric_types")]
483            NumericArray::Int8(_) => ArrowType::Int8,
484            #[cfg(feature = "extended_numeric_types")]
485            NumericArray::Int16(_) => ArrowType::Int16,
486            NumericArray::Int32(_) => ArrowType::Int32,
487            NumericArray::Int64(_) => ArrowType::Int64,
488            #[cfg(feature = "extended_numeric_types")]
489            NumericArray::UInt8(_) => ArrowType::UInt8,
490            #[cfg(feature = "extended_numeric_types")]
491            NumericArray::UInt16(_) => ArrowType::UInt16,
492            NumericArray::UInt32(_) => ArrowType::UInt32,
493            NumericArray::UInt64(_) => ArrowType::UInt64,
494            NumericArray::Float32(_) => ArrowType::Float32,
495            NumericArray::Float64(_) => ArrowType::Float64,
496            NumericArray::Null => ArrowType::Null,
497        },
498        Array::TextArray(text_arr) => match text_arr {
499            TextArray::String32(_) => ArrowType::String,
500            #[cfg(feature = "large_string")]
501            TextArray::String64(_) => ArrowType::LargeString,
502            #[cfg(feature = "default_categorical_8")]
503            TextArray::Categorical8(_) => ArrowType::Dictionary(CategoricalIndexType::UInt8),
504            #[cfg(feature = "extended_categorical")]
505            TextArray::Categorical16(_) => ArrowType::Dictionary(CategoricalIndexType::UInt16),
506            #[cfg(any(
507                not(feature = "default_categorical_8"),
508                feature = "extended_categorical"
509            ))]
510            TextArray::Categorical32(_) => ArrowType::Dictionary(CategoricalIndexType::UInt32),
511            #[cfg(feature = "extended_categorical")]
512            TextArray::Categorical64(_) => ArrowType::Dictionary(CategoricalIndexType::UInt64),
513            TextArray::Null => ArrowType::Null,
514        },
515        #[cfg(feature = "datetime")]
516        Array::TemporalArray(temp_arr) => match temp_arr {
517            TemporalArray::Datetime32(dt_arr) => match &dt_arr.time_unit {
518                TimeUnit::Days => ArrowType::Date32,
519                unit => ArrowType::Time32(unit.clone()),
520            },
521            TemporalArray::Datetime64(dt_arr) => match &dt_arr.time_unit {
522                TimeUnit::Milliseconds => ArrowType::Date64,
523                TimeUnit::Microseconds | TimeUnit::Nanoseconds => {
524                    ArrowType::Time64(dt_arr.time_unit.clone())
525                }
526                unit => ArrowType::Timestamp(unit.clone(), None), // TODO: extract timezone from metadata
527            },
528            TemporalArray::Null => ArrowType::Null,
529        },
530        Array::BooleanArray(_) => ArrowType::Boolean,
531        Array::Null => ArrowType::Null,
532    };
533
534    let has_mask = array.null_mask().is_some()
535        || other_array.map_or(false, |other| other.null_mask().is_some());
536
537    Field::new(name, arrow_type, has_mask, metadata)
538}
539
540impl Display for FieldArray {
541    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
542        writeln!(
543            f,
544            "\nFieldArray \"{}\" [{} values] (dtype: {:?})",
545            self.field.name,
546            self.array.len(),
547            self.field.dtype
548        )?;
549
550        // For Timestamp types with timezone, use custom printing
551        #[cfg(feature = "datetime")]
552        if let ArrowType::Timestamp(_unit, Some(ref tz)) = self.field.dtype {
553            return format_field_array_with_timezone(f, self, tz);
554        }
555
556        self.array.fmt(f)
557    }
558}
559
560#[cfg(feature = "datetime")]
561fn format_field_array_with_timezone(
562    f: &mut Formatter<'_>,
563    field_array: &FieldArray,
564    timezone: &str,
565) -> std::fmt::Result {
566    use crate::traits::print::MAX_PREVIEW;
567    use crate::{Array, TemporalArray};
568
569    let arr = &field_array.array;
570    let len = arr.len();
571    let nulls = arr.null_count();
572
573    if let Array::TemporalArray(TemporalArray::Datetime64(dt)) = arr {
574        writeln!(
575            f,
576            "DatetimeArray [{} values] (dtype: datetime[{:?}], timezone: {}, nulls: {})",
577            len, dt.time_unit, timezone, nulls
578        )?;
579
580        write!(f, "[")?;
581        for i in 0..usize::min(len, MAX_PREVIEW) {
582            if i > 0 {
583                write!(f, ", ")?;
584            }
585            write!(f, "{}", format_datetime_with_tz(dt.as_ref(), i, timezone))?;
586        }
587        if len > MAX_PREVIEW {
588            write!(f, ", ...")?;
589        }
590        writeln!(f, "]")
591    } else if let Array::TemporalArray(TemporalArray::Datetime32(dt)) = arr {
592        writeln!(
593            f,
594            "DatetimeArray [{} values] (dtype: datetime[{:?}], timezone: {}, nulls: {})",
595            len, dt.time_unit, timezone, nulls
596        )?;
597
598        write!(f, "[")?;
599        for i in 0..usize::min(len, MAX_PREVIEW) {
600            if i > 0 {
601                write!(f, ", ")?;
602            }
603            write!(f, "{}", format_datetime_with_tz(dt.as_ref(), i, timezone))?;
604        }
605        if len > MAX_PREVIEW {
606            write!(f, ", ...")?;
607        }
608        writeln!(f, "]")
609    } else {
610        field_array.array.fmt(f)
611    }
612}
613
614#[cfg(feature = "datetime")]
615fn format_datetime_with_tz<T>(arr: &crate::DatetimeArray<T>, idx: usize, timezone: &str) -> String
616where
617    T: crate::Integer + std::fmt::Display,
618{
619    use crate::traits::print::format_datetime_value;
620    format_datetime_value(arr, idx, Some(timezone))
621}
622
623impl Shape for FieldArray {
624    fn shape(&self) -> ShapeDim {
625        ShapeDim::Rank1(self.len())
626    }
627}
628
629impl Concatenate for FieldArray {
630    /// Concatenates two FieldArrays, consuming both.
631    ///
632    /// # Requirements
633    /// - Both FieldArrays must have matching field metadata:
634    ///   - Same name
635    ///   - Same dtype
636    ///   - Same nullability
637    ///
638    /// # Returns
639    /// A new FieldArray with the concatenated array data
640    ///
641    /// # Errors
642    /// - `IncompatibleTypeError` if field metadata doesn't match
643    fn concat(self, other: Self) -> Result<Self, MinarrowError> {
644        // Validate field compatibility
645        if self.field.name != other.field.name {
646            return Err(MinarrowError::IncompatibleTypeError {
647                from: "FieldArray",
648                to: "FieldArray",
649                message: Some(format!(
650                    "Field name mismatch: '{}' vs '{}'",
651                    self.field.name, other.field.name
652                )),
653            });
654        }
655
656        if self.field.dtype != other.field.dtype {
657            return Err(MinarrowError::IncompatibleTypeError {
658                from: "FieldArray",
659                to: "FieldArray",
660                message: Some(format!(
661                    "Field '{}' dtype mismatch: {:?} vs {:?}",
662                    self.field.name, self.field.dtype, other.field.dtype
663                )),
664            });
665        }
666
667        if self.field.nullable != other.field.nullable {
668            return Err(MinarrowError::IncompatibleTypeError {
669                from: "FieldArray",
670                to: "FieldArray",
671                message: Some(format!(
672                    "Field '{}' nullable mismatch: {} vs {}",
673                    self.field.name, self.field.nullable, other.field.nullable
674                )),
675            });
676        }
677
678        // Concatenate the underlying arrays
679        let concatenated_array = self.array.concat(other.array)?;
680        let null_count = concatenated_array.null_count();
681
682        // Create result FieldArray with the same field metadata
683        Ok(FieldArray {
684            field: self.field,
685            array: concatenated_array,
686            null_count,
687        })
688    }
689}
690
691// ===== Selection Trait Implementation =====
692
693#[cfg(all(feature = "select", feature = "views"))]
694impl RowSelection for FieldArray {
695    type View = ArrayV;
696
697    fn r<S: DataSelector>(&self, selection: S) -> ArrayV {
698        if selection.is_contiguous() {
699            // Contiguous selection (ranges): adjust offset and len
700            let indices = selection.resolve_indices(self.array.len());
701            if indices.is_empty() {
702                return ArrayV::new(self.array.clone(), 0, 0);
703            }
704            ArrayV::new(self.array.clone(), indices[0], indices.len())
705        } else {
706            // Non-contiguous selection (index arrays): gather into new array
707            let view = ArrayV::from(self.array.clone());
708            let indices = selection.resolve_indices(self.array.len());
709            let gathered_array = view.gather_indices(&indices);
710            ArrayV::new(gathered_array, 0, indices.len())
711        }
712    }
713
714    fn get_row_count(&self) -> usize {
715        self.array.len()
716    }
717}
718
719// ============================================================
720// fa_* macros - FieldArray creation from name + array values
721// ============================================================
722// Each fa_<variant> wraps the corresponding arr_<variant>,
723// producing a FieldArray with the given name and inferred schema.
724//
725// Usage:
726//   fa_i32!("col", 1, 2, 3)        // from literals
727//   fa_i32!("col", my_vec64)        // from a Vec64
728//   fa_i32!("col")                  // empty array
729//   fa_i32_opt!("col", Some(1), None, Some(3))  // nullable
730// ============================================================
731
732// ======== numeric ========
733
734#[cfg(feature = "extended_numeric_types")]
735#[macro_export]
736macro_rules! fa_i8 {
737    ($name:expr, @vec64 $v:expr; $mask:expr) => {
738        $crate::FieldArray::from_arr($name, $crate::arr_i8!($v; $mask))
739    };
740    ($name:expr, @slice $v:expr; $mask:expr) => {
741        $crate::FieldArray::from_arr($name, $crate::arr_i8!($v; $mask))
742    };
743    ($name:expr, $v:expr; $mask:expr) => {
744        $crate::FieldArray::from_arr($name, $crate::arr_i8!($v; $mask))
745    };
746    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
747        $crate::FieldArray::from_arr($name, $crate::arr_i8!($first, $($rest),+ ; $mask))
748    };
749    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
750        $crate::FieldArray::from_arr($name, $crate::arr_i8!($first, $($rest),+))
751    };
752    ($name:expr, @vec64 $v:expr $(,)?) => {
753        $crate::FieldArray::from_arr($name, $crate::arr_i8!($v))
754    };
755    ($name:expr, @slice $v:expr $(,)?) => {
756        $crate::FieldArray::from_arr($name, $crate::arr_i8!($v))
757    };
758    ($name:expr, $v:expr $(,)?) => {{
759        use $crate::vec64;
760        $crate::FieldArray::from_arr($name, $crate::arr_i8!(vec64![$v]))
761    }};
762    ($name:expr) => {{
763        use $crate::vec64;
764        $crate::FieldArray::from_arr($name, $crate::arr_i8!())
765    }};
766}
767
768#[cfg(feature = "extended_numeric_types")]
769#[macro_export]
770macro_rules! fa_i16 {
771    ($name:expr, @vec64 $v:expr; $mask:expr) => {
772        $crate::FieldArray::from_arr($name, $crate::arr_i16!($v; $mask))
773    };
774    ($name:expr, @slice $v:expr; $mask:expr) => {
775        $crate::FieldArray::from_arr($name, $crate::arr_i16!($v; $mask))
776    };
777    ($name:expr, $v:expr; $mask:expr) => {
778        $crate::FieldArray::from_arr($name, $crate::arr_i16!($v; $mask))
779    };
780    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
781        $crate::FieldArray::from_arr($name, $crate::arr_i16!($first, $($rest),+ ; $mask))
782    };
783    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
784        $crate::FieldArray::from_arr($name, $crate::arr_i16!($first, $($rest),+))
785    };
786    ($name:expr, @vec64 $v:expr $(,)?) => {
787        $crate::FieldArray::from_arr($name, $crate::arr_i16!($v))
788    };
789    ($name:expr, @slice $v:expr $(,)?) => {
790        $crate::FieldArray::from_arr($name, $crate::arr_i16!($v))
791    };
792    ($name:expr, $v:expr $(,)?) => {{
793        use $crate::vec64;
794        $crate::FieldArray::from_arr($name, $crate::arr_i16!(vec64![$v]))
795    }};
796    ($name:expr) => {{
797        use $crate::vec64;
798        $crate::FieldArray::from_arr($name, $crate::arr_i16!())
799    }};
800}
801
802#[macro_export]
803macro_rules! fa_i32 {
804    ($name:expr, @vec64 $v:expr; $mask:expr) => {
805        $crate::FieldArray::from_arr($name, $crate::arr_i32!($v; $mask))
806    };
807    ($name:expr, @slice $v:expr; $mask:expr) => {
808        $crate::FieldArray::from_arr($name, $crate::arr_i32!($v; $mask))
809    };
810    ($name:expr, $v:expr; $mask:expr) => {
811        $crate::FieldArray::from_arr($name, $crate::arr_i32!($v; $mask))
812    };
813    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
814        $crate::FieldArray::from_arr($name, $crate::arr_i32!($first, $($rest),+ ; $mask))
815    };
816    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
817        $crate::FieldArray::from_arr($name, $crate::arr_i32!($first, $($rest),+))
818    };
819    ($name:expr, @vec64 $v:expr $(,)?) => {
820        $crate::FieldArray::from_arr($name, $crate::arr_i32!($v))
821    };
822    ($name:expr, @slice $v:expr $(,)?) => {
823        $crate::FieldArray::from_arr($name, $crate::arr_i32!($v))
824    };
825    ($name:expr, $v:expr $(,)?) => {{
826        use $crate::vec64;
827        $crate::FieldArray::from_arr($name, $crate::arr_i32!(vec64![$v]))
828    }};
829    ($name:expr) => {{
830        use $crate::vec64;
831        $crate::FieldArray::from_arr($name, $crate::arr_i32!())
832    }};
833}
834
835#[macro_export]
836macro_rules! fa_i64 {
837    ($name:expr, @vec64 $v:expr; $mask:expr) => {
838        $crate::FieldArray::from_arr($name, $crate::arr_i64!($v; $mask))
839    };
840    ($name:expr, @slice $v:expr; $mask:expr) => {
841        $crate::FieldArray::from_arr($name, $crate::arr_i64!($v; $mask))
842    };
843    ($name:expr, $v:expr; $mask:expr) => {
844        $crate::FieldArray::from_arr($name, $crate::arr_i64!($v; $mask))
845    };
846    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
847        $crate::FieldArray::from_arr($name, $crate::arr_i64!($first, $($rest),+ ; $mask))
848    };
849    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
850        $crate::FieldArray::from_arr($name, $crate::arr_i64!($first, $($rest),+))
851    };
852    ($name:expr, @vec64 $v:expr $(,)?) => {
853        $crate::FieldArray::from_arr($name, $crate::arr_i64!($v))
854    };
855    ($name:expr, @slice $v:expr $(,)?) => {
856        $crate::FieldArray::from_arr($name, $crate::arr_i64!($v))
857    };
858    ($name:expr, $v:expr $(,)?) => {{
859        use $crate::vec64;
860        $crate::FieldArray::from_arr($name, $crate::arr_i64!(vec64![$v]))
861    }};
862    ($name:expr) => {{
863        use $crate::vec64;
864        $crate::FieldArray::from_arr($name, $crate::arr_i64!())
865    }};
866}
867
868#[cfg(feature = "extended_numeric_types")]
869#[macro_export]
870macro_rules! fa_u8 {
871    ($name:expr, @vec64 $v:expr; $mask:expr) => {
872        $crate::FieldArray::from_arr($name, $crate::arr_u8!($v; $mask))
873    };
874    ($name:expr, @slice $v:expr; $mask:expr) => {
875        $crate::FieldArray::from_arr($name, $crate::arr_u8!($v; $mask))
876    };
877    ($name:expr, $v:expr; $mask:expr) => {
878        $crate::FieldArray::from_arr($name, $crate::arr_u8!($v; $mask))
879    };
880    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
881        $crate::FieldArray::from_arr($name, $crate::arr_u8!($first, $($rest),+ ; $mask))
882    };
883    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
884        $crate::FieldArray::from_arr($name, $crate::arr_u8!($first, $($rest),+))
885    };
886    ($name:expr, @vec64 $v:expr $(,)?) => {
887        $crate::FieldArray::from_arr($name, $crate::arr_u8!($v))
888    };
889    ($name:expr, @slice $v:expr $(,)?) => {
890        $crate::FieldArray::from_arr($name, $crate::arr_u8!($v))
891    };
892    ($name:expr, $v:expr $(,)?) => {{
893        use $crate::vec64;
894        $crate::FieldArray::from_arr($name, $crate::arr_u8!(vec64![$v]))
895    }};
896    ($name:expr) => {{
897        use $crate::vec64;
898        $crate::FieldArray::from_arr($name, $crate::arr_u8!())
899    }};
900}
901
902#[cfg(feature = "extended_numeric_types")]
903#[macro_export]
904macro_rules! fa_u16 {
905    ($name:expr, @vec64 $v:expr; $mask:expr) => {
906        $crate::FieldArray::from_arr($name, $crate::arr_u16!($v; $mask))
907    };
908    ($name:expr, @slice $v:expr; $mask:expr) => {
909        $crate::FieldArray::from_arr($name, $crate::arr_u16!($v; $mask))
910    };
911    ($name:expr, $v:expr; $mask:expr) => {
912        $crate::FieldArray::from_arr($name, $crate::arr_u16!($v; $mask))
913    };
914    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
915        $crate::FieldArray::from_arr($name, $crate::arr_u16!($first, $($rest),+ ; $mask))
916    };
917    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
918        $crate::FieldArray::from_arr($name, $crate::arr_u16!($first, $($rest),+))
919    };
920    ($name:expr, @vec64 $v:expr $(,)?) => {
921        $crate::FieldArray::from_arr($name, $crate::arr_u16!($v))
922    };
923    ($name:expr, @slice $v:expr $(,)?) => {
924        $crate::FieldArray::from_arr($name, $crate::arr_u16!($v))
925    };
926    ($name:expr, $v:expr $(,)?) => {{
927        use $crate::vec64;
928        $crate::FieldArray::from_arr($name, $crate::arr_u16!(vec64![$v]))
929    }};
930    ($name:expr) => {{
931        use $crate::vec64;
932        $crate::FieldArray::from_arr($name, $crate::arr_u16!())
933    }};
934}
935
936#[macro_export]
937macro_rules! fa_u32 {
938    ($name:expr, @vec64 $v:expr; $mask:expr) => {
939        $crate::FieldArray::from_arr($name, $crate::arr_u32!($v; $mask))
940    };
941    ($name:expr, @slice $v:expr; $mask:expr) => {
942        $crate::FieldArray::from_arr($name, $crate::arr_u32!($v; $mask))
943    };
944    ($name:expr, $v:expr; $mask:expr) => {
945        $crate::FieldArray::from_arr($name, $crate::arr_u32!($v; $mask))
946    };
947    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
948        $crate::FieldArray::from_arr($name, $crate::arr_u32!($first, $($rest),+ ; $mask))
949    };
950    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
951        $crate::FieldArray::from_arr($name, $crate::arr_u32!($first, $($rest),+))
952    };
953    ($name:expr, @vec64 $v:expr $(,)?) => {
954        $crate::FieldArray::from_arr($name, $crate::arr_u32!($v))
955    };
956    ($name:expr, @slice $v:expr $(,)?) => {
957        $crate::FieldArray::from_arr($name, $crate::arr_u32!($v))
958    };
959    ($name:expr, $v:expr $(,)?) => {{
960        use $crate::vec64;
961        $crate::FieldArray::from_arr($name, $crate::arr_u32!(vec64![$v]))
962    }};
963    ($name:expr) => {{
964        use $crate::vec64;
965        $crate::FieldArray::from_arr($name, $crate::arr_u32!())
966    }};
967}
968
969#[macro_export]
970macro_rules! fa_u64 {
971    ($name:expr, @vec64 $v:expr; $mask:expr) => {
972        $crate::FieldArray::from_arr($name, $crate::arr_u64!($v; $mask))
973    };
974    ($name:expr, @slice $v:expr; $mask:expr) => {
975        $crate::FieldArray::from_arr($name, $crate::arr_u64!($v; $mask))
976    };
977    ($name:expr, $v:expr; $mask:expr) => {
978        $crate::FieldArray::from_arr($name, $crate::arr_u64!($v; $mask))
979    };
980    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
981        $crate::FieldArray::from_arr($name, $crate::arr_u64!($first, $($rest),+ ; $mask))
982    };
983    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
984        $crate::FieldArray::from_arr($name, $crate::arr_u64!($first, $($rest),+))
985    };
986    ($name:expr, @vec64 $v:expr $(,)?) => {
987        $crate::FieldArray::from_arr($name, $crate::arr_u64!($v))
988    };
989    ($name:expr, @slice $v:expr $(,)?) => {
990        $crate::FieldArray::from_arr($name, $crate::arr_u64!($v))
991    };
992    ($name:expr, $v:expr $(,)?) => {{
993        use $crate::vec64;
994        $crate::FieldArray::from_arr($name, $crate::arr_u64!(vec64![$v]))
995    }};
996    ($name:expr) => {{
997        use $crate::vec64;
998        $crate::FieldArray::from_arr($name, $crate::arr_u64!())
999    }};
1000}
1001
1002// ======== Float types ========
1003
1004#[macro_export]
1005macro_rules! fa_f32 {
1006    ($name:expr, @vec64 $v:expr; $mask:expr) => {
1007        $crate::FieldArray::from_arr($name, $crate::arr_f32!($v; $mask))
1008    };
1009    ($name:expr, @slice $v:expr; $mask:expr) => {
1010        $crate::FieldArray::from_arr($name, $crate::arr_f32!($v; $mask))
1011    };
1012    ($name:expr, $v:expr; $mask:expr) => {
1013        $crate::FieldArray::from_arr($name, $crate::arr_f32!($v; $mask))
1014    };
1015    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
1016        $crate::FieldArray::from_arr($name, $crate::arr_f32!($first, $($rest),+ ; $mask))
1017    };
1018    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1019        $crate::FieldArray::from_arr($name, $crate::arr_f32!($first, $($rest),+))
1020    };
1021    ($name:expr, @vec64 $v:expr $(,)?) => {
1022        $crate::FieldArray::from_arr($name, $crate::arr_f32!($v))
1023    };
1024    ($name:expr, @slice $v:expr $(,)?) => {
1025        $crate::FieldArray::from_arr($name, $crate::arr_f32!($v))
1026    };
1027    ($name:expr, $v:expr $(,)?) => {{
1028        use $crate::vec64;
1029        $crate::FieldArray::from_arr($name, $crate::arr_f32!(vec64![$v]))
1030    }};
1031    ($name:expr) => {{
1032        use $crate::vec64;
1033        $crate::FieldArray::from_arr($name, $crate::arr_f32!())
1034    }};
1035}
1036
1037#[macro_export]
1038macro_rules! fa_f64 {
1039    ($name:expr, @vec64 $v:expr; $mask:expr) => {
1040        $crate::FieldArray::from_arr($name, $crate::arr_f64!($v; $mask))
1041    };
1042    ($name:expr, @slice $v:expr; $mask:expr) => {
1043        $crate::FieldArray::from_arr($name, $crate::arr_f64!($v; $mask))
1044    };
1045    ($name:expr, $v:expr; $mask:expr) => {
1046        $crate::FieldArray::from_arr($name, $crate::arr_f64!($v; $mask))
1047    };
1048    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
1049        $crate::FieldArray::from_arr($name, $crate::arr_f64!($first, $($rest),+ ; $mask))
1050    };
1051    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1052        $crate::FieldArray::from_arr($name, $crate::arr_f64!($first, $($rest),+))
1053    };
1054    ($name:expr, @vec64 $v:expr $(,)?) => {
1055        $crate::FieldArray::from_arr($name, $crate::arr_f64!($v))
1056    };
1057    ($name:expr, @slice $v:expr $(,)?) => {
1058        $crate::FieldArray::from_arr($name, $crate::arr_f64!($v))
1059    };
1060    ($name:expr, $v:expr $(,)?) => {{
1061        use $crate::vec64;
1062        $crate::FieldArray::from_arr($name, $crate::arr_f64!(vec64![$v]))
1063    }};
1064    ($name:expr) => {{
1065        use $crate::vec64;
1066        $crate::FieldArray::from_arr($name, $crate::arr_f64!())
1067    }};
1068}
1069
1070// ======== Boolean ========
1071
1072#[macro_export]
1073macro_rules! fa_bool {
1074    ($name:expr, @vec64 $v:expr; $mask:expr) => {
1075        $crate::FieldArray::from_arr($name, $crate::arr_bool!($v; $mask))
1076    };
1077    ($name:expr, @slice $v:expr; $mask:expr) => {
1078        $crate::FieldArray::from_arr($name, $crate::arr_bool!($v; $mask))
1079    };
1080    ($name:expr, $v:expr; $mask:expr) => {
1081        $crate::FieldArray::from_arr($name, $crate::arr_bool!($v; $mask))
1082    };
1083    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
1084        $crate::FieldArray::from_arr($name, $crate::arr_bool!($first, $($rest),+ ; $mask))
1085    };
1086    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1087        $crate::FieldArray::from_arr($name, $crate::arr_bool!($first, $($rest),+))
1088    };
1089    ($name:expr, @vec64 $v:expr $(,)?) => {
1090        $crate::FieldArray::from_arr($name, $crate::arr_bool!($v))
1091    };
1092    ($name:expr, @slice $v:expr $(,)?) => {
1093        $crate::FieldArray::from_arr($name, $crate::arr_bool!($v))
1094    };
1095    ($name:expr, $v:expr $(,)?) => {{
1096        use $crate::vec64;
1097        $crate::FieldArray::from_arr($name, $crate::arr_bool!(vec64![$v]))
1098    }};
1099    ($name:expr) => {{
1100        use $crate::vec64;
1101        $crate::FieldArray::from_arr($name, $crate::arr_bool!())
1102    }};
1103}
1104
1105// ======== String ========
1106
1107#[macro_export]
1108macro_rules! fa_str32 {
1109    ($name:expr, @vec64 $v:expr; $mask:expr) => {
1110        $crate::FieldArray::from_arr($name, $crate::arr_str32!($v; $mask))
1111    };
1112    ($name:expr, @slice $v:expr; $mask:expr) => {
1113        $crate::FieldArray::from_arr($name, $crate::arr_str32!($v; $mask))
1114    };
1115    ($name:expr, $v:expr; $mask:expr) => {
1116        $crate::FieldArray::from_arr($name, $crate::arr_str32!($v; $mask))
1117    };
1118    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
1119        $crate::FieldArray::from_arr($name, $crate::arr_str32!($first, $($rest),+ ; $mask))
1120    };
1121    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1122        $crate::FieldArray::from_arr($name, $crate::arr_str32!($first, $($rest),+))
1123    };
1124    ($name:expr, @vec64 $v:expr $(,)?) => {
1125        $crate::FieldArray::from_arr($name, $crate::arr_str32!($v))
1126    };
1127    ($name:expr, @slice $v:expr $(,)?) => {
1128        $crate::FieldArray::from_arr($name, $crate::arr_str32!($v))
1129    };
1130    ($name:expr, $v:expr $(,)?) => {{
1131        use $crate::vec64;
1132        $crate::FieldArray::from_arr($name, $crate::arr_str32!(vec64![$v]))
1133    }};
1134    ($name:expr) => {{
1135        use $crate::vec64;
1136        $crate::FieldArray::from_arr($name, $crate::arr_str32!())
1137    }};
1138}
1139
1140#[cfg(feature = "large_string")]
1141#[macro_export]
1142macro_rules! fa_str64 {
1143    ($name:expr, @vec64 $v:expr; $mask:expr) => {
1144        $crate::FieldArray::from_arr($name, $crate::arr_str64!($v; $mask))
1145    };
1146    ($name:expr, @slice $v:expr; $mask:expr) => {
1147        $crate::FieldArray::from_arr($name, $crate::arr_str64!($v; $mask))
1148    };
1149    ($name:expr, $v:expr; $mask:expr) => {
1150        $crate::FieldArray::from_arr($name, $crate::arr_str64!($v; $mask))
1151    };
1152    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
1153        $crate::FieldArray::from_arr($name, $crate::arr_str64!($first, $($rest),+ ; $mask))
1154    };
1155    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1156        $crate::FieldArray::from_arr($name, $crate::arr_str64!($first, $($rest),+))
1157    };
1158    ($name:expr, @vec64 $v:expr $(,)?) => {
1159        $crate::FieldArray::from_arr($name, $crate::arr_str64!($v))
1160    };
1161    ($name:expr, @slice $v:expr $(,)?) => {
1162        $crate::FieldArray::from_arr($name, $crate::arr_str64!($v))
1163    };
1164    ($name:expr, $v:expr $(,)?) => {{
1165        use $crate::vec64;
1166        $crate::FieldArray::from_arr($name, $crate::arr_str64!(vec64![$v]))
1167    }};
1168    ($name:expr) => {{
1169        use $crate::vec64;
1170        $crate::FieldArray::from_arr($name, $crate::arr_str64!())
1171    }};
1172}
1173
1174// ======== Categorical ========
1175
1176#[cfg(feature = "default_categorical_8")]
1177#[macro_export]
1178macro_rules! fa_cat8 {
1179    ($name:expr, @vec64 $v:expr; $mask:expr) => {
1180        $crate::FieldArray::from_arr($name, $crate::arr_cat8!($v; $mask))
1181    };
1182    ($name:expr, @slice $v:expr; $mask:expr) => {
1183        $crate::FieldArray::from_arr($name, $crate::arr_cat8!($v; $mask))
1184    };
1185    ($name:expr, $v:expr; $mask:expr) => {
1186        $crate::FieldArray::from_arr($name, $crate::arr_cat8!($v; $mask))
1187    };
1188    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
1189        $crate::FieldArray::from_arr($name, $crate::arr_cat8!($first, $($rest),+ ; $mask))
1190    };
1191    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1192        $crate::FieldArray::from_arr($name, $crate::arr_cat8!($first, $($rest),+))
1193    };
1194    ($name:expr, @vec64 $v:expr $(,)?) => {
1195        $crate::FieldArray::from_arr($name, $crate::arr_cat8!($v))
1196    };
1197    ($name:expr, @slice $v:expr $(,)?) => {
1198        $crate::FieldArray::from_arr($name, $crate::arr_cat8!($v))
1199    };
1200    ($name:expr, $v:expr $(,)?) => {{
1201        use $crate::vec64;
1202        $crate::FieldArray::from_arr($name, $crate::arr_cat8!(vec64![$v]))
1203    }};
1204    ($name:expr) => {{
1205        use $crate::vec64;
1206        $crate::FieldArray::from_arr($name, $crate::arr_cat8!())
1207    }};
1208}
1209
1210#[cfg(feature = "extended_categorical")]
1211#[macro_export]
1212macro_rules! fa_cat16 {
1213    ($name:expr, @vec64 $v:expr; $mask:expr) => {
1214        $crate::FieldArray::from_arr($name, $crate::arr_cat16!($v; $mask))
1215    };
1216    ($name:expr, @slice $v:expr; $mask:expr) => {
1217        $crate::FieldArray::from_arr($name, $crate::arr_cat16!($v; $mask))
1218    };
1219    ($name:expr, $v:expr; $mask:expr) => {
1220        $crate::FieldArray::from_arr($name, $crate::arr_cat16!($v; $mask))
1221    };
1222    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
1223        $crate::FieldArray::from_arr($name, $crate::arr_cat16!($first, $($rest),+ ; $mask))
1224    };
1225    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1226        $crate::FieldArray::from_arr($name, $crate::arr_cat16!($first, $($rest),+))
1227    };
1228    ($name:expr, @vec64 $v:expr $(,)?) => {
1229        $crate::FieldArray::from_arr($name, $crate::arr_cat16!($v))
1230    };
1231    ($name:expr, @slice $v:expr $(,)?) => {
1232        $crate::FieldArray::from_arr($name, $crate::arr_cat16!($v))
1233    };
1234    ($name:expr, $v:expr $(,)?) => {{
1235        use $crate::vec64;
1236        $crate::FieldArray::from_arr($name, $crate::arr_cat16!(vec64![$v]))
1237    }};
1238    ($name:expr) => {{
1239        use $crate::vec64;
1240        $crate::FieldArray::from_arr($name, $crate::arr_cat16!())
1241    }};
1242}
1243
1244#[cfg(any(
1245    not(feature = "default_categorical_8"),
1246    feature = "extended_categorical"
1247))]
1248#[macro_export]
1249macro_rules! fa_cat32 {
1250    ($name:expr, @vec64 $v:expr; $mask:expr) => {
1251        $crate::FieldArray::from_arr($name, $crate::arr_cat32!($v; $mask))
1252    };
1253    ($name:expr, @slice $v:expr; $mask:expr) => {
1254        $crate::FieldArray::from_arr($name, $crate::arr_cat32!($v; $mask))
1255    };
1256    ($name:expr, $v:expr; $mask:expr) => {
1257        $crate::FieldArray::from_arr($name, $crate::arr_cat32!($v; $mask))
1258    };
1259    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
1260        $crate::FieldArray::from_arr($name, $crate::arr_cat32!($first, $($rest),+ ; $mask))
1261    };
1262    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1263        $crate::FieldArray::from_arr($name, $crate::arr_cat32!($first, $($rest),+))
1264    };
1265    ($name:expr, @vec64 $v:expr $(,)?) => {
1266        $crate::FieldArray::from_arr($name, $crate::arr_cat32!($v))
1267    };
1268    ($name:expr, @slice $v:expr $(,)?) => {
1269        $crate::FieldArray::from_arr($name, $crate::arr_cat32!($v))
1270    };
1271    ($name:expr, $v:expr $(,)?) => {{
1272        use $crate::vec64;
1273        $crate::FieldArray::from_arr($name, $crate::arr_cat32!(vec64![$v]))
1274    }};
1275    ($name:expr) => {{
1276        use $crate::vec64;
1277        $crate::FieldArray::from_arr($name, $crate::arr_cat32!())
1278    }};
1279}
1280
1281#[cfg(feature = "extended_categorical")]
1282#[macro_export]
1283macro_rules! fa_cat64 {
1284    ($name:expr, @vec64 $v:expr; $mask:expr) => {
1285        $crate::FieldArray::from_arr($name, $crate::arr_cat64!($v; $mask))
1286    };
1287    ($name:expr, @slice $v:expr; $mask:expr) => {
1288        $crate::FieldArray::from_arr($name, $crate::arr_cat64!($v; $mask))
1289    };
1290    ($name:expr, $v:expr; $mask:expr) => {
1291        $crate::FieldArray::from_arr($name, $crate::arr_cat64!($v; $mask))
1292    };
1293    ($name:expr, $first:expr, $($rest:expr),+ ; $mask:expr) => {
1294        $crate::FieldArray::from_arr($name, $crate::arr_cat64!($first, $($rest),+ ; $mask))
1295    };
1296    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1297        $crate::FieldArray::from_arr($name, $crate::arr_cat64!($first, $($rest),+))
1298    };
1299    ($name:expr, @vec64 $v:expr $(,)?) => {
1300        $crate::FieldArray::from_arr($name, $crate::arr_cat64!($v))
1301    };
1302    ($name:expr, @slice $v:expr $(,)?) => {
1303        $crate::FieldArray::from_arr($name, $crate::arr_cat64!($v))
1304    };
1305    ($name:expr, $v:expr $(,)?) => {{
1306        use $crate::vec64;
1307        $crate::FieldArray::from_arr($name, $crate::arr_cat64!(vec64![$v]))
1308    }};
1309    ($name:expr) => {{
1310        use $crate::vec64;
1311        $crate::FieldArray::from_arr($name, $crate::arr_cat64!())
1312    }};
1313}
1314
1315// ======== fa_*_opt variants - nullable FieldArray creation ========
1316
1317// ======== Integer (signed) ========
1318
1319#[cfg(feature = "extended_numeric_types")]
1320#[macro_export]
1321macro_rules! fa_i8_opt {
1322    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1323        $crate::FieldArray::from_arr($name, $crate::arr_i8_opt!($first, $($rest),+))
1324    };
1325    ($name:expr, @vec64 $v:expr $(,)?) => {
1326        $crate::FieldArray::from_arr($name, $crate::arr_i8_opt!($v))
1327    };
1328    ($name:expr, $v:expr $(,)?) => {{
1329        use $crate::vec64;
1330        $crate::FieldArray::from_arr($name, $crate::arr_i8_opt!(vec64![$v]))
1331    }};
1332    ($name:expr) => {{
1333        use $crate::vec64;
1334        $crate::FieldArray::from_arr($name, $crate::arr_i8_opt!())
1335    }};
1336}
1337
1338#[cfg(feature = "extended_numeric_types")]
1339#[macro_export]
1340macro_rules! fa_i16_opt {
1341    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1342        $crate::FieldArray::from_arr($name, $crate::arr_i16_opt!($first, $($rest),+))
1343    };
1344    ($name:expr, @vec64 $v:expr $(,)?) => {
1345        $crate::FieldArray::from_arr($name, $crate::arr_i16_opt!($v))
1346    };
1347    ($name:expr, $v:expr $(,)?) => {{
1348        use $crate::vec64;
1349        $crate::FieldArray::from_arr($name, $crate::arr_i16_opt!(vec64![$v]))
1350    }};
1351    ($name:expr) => {{
1352        use $crate::vec64;
1353        $crate::FieldArray::from_arr($name, $crate::arr_i16_opt!())
1354    }};
1355}
1356
1357#[macro_export]
1358macro_rules! fa_i32_opt {
1359    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1360        $crate::FieldArray::from_arr($name, $crate::arr_i32_opt!($first, $($rest),+))
1361    };
1362    ($name:expr, @vec64 $v:expr $(,)?) => {
1363        $crate::FieldArray::from_arr($name, $crate::arr_i32_opt!($v))
1364    };
1365    ($name:expr, $v:expr $(,)?) => {{
1366        use $crate::vec64;
1367        $crate::FieldArray::from_arr($name, $crate::arr_i32_opt!(vec64![$v]))
1368    }};
1369    ($name:expr) => {{
1370        use $crate::vec64;
1371        $crate::FieldArray::from_arr($name, $crate::arr_i32_opt!())
1372    }};
1373}
1374
1375#[macro_export]
1376macro_rules! fa_i64_opt {
1377    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1378        $crate::FieldArray::from_arr($name, $crate::arr_i64_opt!($first, $($rest),+))
1379    };
1380    ($name:expr, @vec64 $v:expr $(,)?) => {
1381        $crate::FieldArray::from_arr($name, $crate::arr_i64_opt!($v))
1382    };
1383    ($name:expr, $v:expr $(,)?) => {{
1384        use $crate::vec64;
1385        $crate::FieldArray::from_arr($name, $crate::arr_i64_opt!(vec64![$v]))
1386    }};
1387    ($name:expr) => {{
1388        use $crate::vec64;
1389        $crate::FieldArray::from_arr($name, $crate::arr_i64_opt!())
1390    }};
1391}
1392
1393// ======== Integer (unsigned) ========
1394
1395#[cfg(feature = "extended_numeric_types")]
1396#[macro_export]
1397macro_rules! fa_u8_opt {
1398    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1399        $crate::FieldArray::from_arr($name, $crate::arr_u8_opt!($first, $($rest),+))
1400    };
1401    ($name:expr, @vec64 $v:expr $(,)?) => {
1402        $crate::FieldArray::from_arr($name, $crate::arr_u8_opt!($v))
1403    };
1404    ($name:expr, $v:expr $(,)?) => {{
1405        use $crate::vec64;
1406        $crate::FieldArray::from_arr($name, $crate::arr_u8_opt!(vec64![$v]))
1407    }};
1408    ($name:expr) => {{
1409        use $crate::vec64;
1410        $crate::FieldArray::from_arr($name, $crate::arr_u8_opt!())
1411    }};
1412}
1413
1414#[cfg(feature = "extended_numeric_types")]
1415#[macro_export]
1416macro_rules! fa_u16_opt {
1417    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1418        $crate::FieldArray::from_arr($name, $crate::arr_u16_opt!($first, $($rest),+))
1419    };
1420    ($name:expr, @vec64 $v:expr $(,)?) => {
1421        $crate::FieldArray::from_arr($name, $crate::arr_u16_opt!($v))
1422    };
1423    ($name:expr, $v:expr $(,)?) => {{
1424        use $crate::vec64;
1425        $crate::FieldArray::from_arr($name, $crate::arr_u16_opt!(vec64![$v]))
1426    }};
1427    ($name:expr) => {{
1428        use $crate::vec64;
1429        $crate::FieldArray::from_arr($name, $crate::arr_u16_opt!())
1430    }};
1431}
1432
1433#[macro_export]
1434macro_rules! fa_u32_opt {
1435    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1436        $crate::FieldArray::from_arr($name, $crate::arr_u32_opt!($first, $($rest),+))
1437    };
1438    ($name:expr, @vec64 $v:expr $(,)?) => {
1439        $crate::FieldArray::from_arr($name, $crate::arr_u32_opt!($v))
1440    };
1441    ($name:expr, $v:expr $(,)?) => {{
1442        use $crate::vec64;
1443        $crate::FieldArray::from_arr($name, $crate::arr_u32_opt!(vec64![$v]))
1444    }};
1445    ($name:expr) => {{
1446        use $crate::vec64;
1447        $crate::FieldArray::from_arr($name, $crate::arr_u32_opt!())
1448    }};
1449}
1450
1451#[macro_export]
1452macro_rules! fa_u64_opt {
1453    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1454        $crate::FieldArray::from_arr($name, $crate::arr_u64_opt!($first, $($rest),+))
1455    };
1456    ($name:expr, @vec64 $v:expr $(,)?) => {
1457        $crate::FieldArray::from_arr($name, $crate::arr_u64_opt!($v))
1458    };
1459    ($name:expr, $v:expr $(,)?) => {{
1460        use $crate::vec64;
1461        $crate::FieldArray::from_arr($name, $crate::arr_u64_opt!(vec64![$v]))
1462    }};
1463    ($name:expr) => {{
1464        use $crate::vec64;
1465        $crate::FieldArray::from_arr($name, $crate::arr_u64_opt!())
1466    }};
1467}
1468
1469// ======== Float ========
1470
1471#[macro_export]
1472macro_rules! fa_f32_opt {
1473    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1474        $crate::FieldArray::from_arr($name, $crate::arr_f32_opt!($first, $($rest),+))
1475    };
1476    ($name:expr, @vec64 $v:expr $(,)?) => {
1477        $crate::FieldArray::from_arr($name, $crate::arr_f32_opt!($v))
1478    };
1479    ($name:expr, $v:expr $(,)?) => {{
1480        use $crate::vec64;
1481        $crate::FieldArray::from_arr($name, $crate::arr_f32_opt!(vec64![$v]))
1482    }};
1483    ($name:expr) => {{
1484        use $crate::vec64;
1485        $crate::FieldArray::from_arr($name, $crate::arr_f32_opt!())
1486    }};
1487}
1488
1489#[macro_export]
1490macro_rules! fa_f64_opt {
1491    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1492        $crate::FieldArray::from_arr($name, $crate::arr_f64_opt!($first, $($rest),+))
1493    };
1494    ($name:expr, @vec64 $v:expr $(,)?) => {
1495        $crate::FieldArray::from_arr($name, $crate::arr_f64_opt!($v))
1496    };
1497    ($name:expr, $v:expr $(,)?) => {{
1498        use $crate::vec64;
1499        $crate::FieldArray::from_arr($name, $crate::arr_f64_opt!(vec64![$v]))
1500    }};
1501    ($name:expr) => {{
1502        use $crate::vec64;
1503        $crate::FieldArray::from_arr($name, $crate::arr_f64_opt!())
1504    }};
1505}
1506
1507// ======== Boolean ========
1508
1509#[macro_export]
1510macro_rules! fa_bool_opt {
1511    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1512        $crate::FieldArray::from_arr($name, $crate::arr_bool_opt!($first, $($rest),+))
1513    };
1514    ($name:expr, @vec64 $v:expr $(,)?) => {
1515        $crate::FieldArray::from_arr($name, $crate::arr_bool_opt!($v))
1516    };
1517    ($name:expr, $v:expr $(,)?) => {{
1518        use $crate::vec64;
1519        $crate::FieldArray::from_arr($name, $crate::arr_bool_opt!(vec64![$v]))
1520    }};
1521    ($name:expr) => {{
1522        use $crate::vec64;
1523        $crate::FieldArray::from_arr($name, $crate::arr_bool_opt!())
1524    }};
1525}
1526
1527// ======== String ========
1528
1529#[macro_export]
1530macro_rules! fa_str32_opt {
1531    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1532        $crate::FieldArray::from_arr($name, $crate::arr_str32_opt!($first, $($rest),+))
1533    };
1534    ($name:expr, @vec64 $v:expr $(,)?) => {
1535        $crate::FieldArray::from_arr($name, $crate::arr_str32_opt!($v))
1536    };
1537    ($name:expr, $v:expr $(,)?) => {{
1538        use $crate::vec64;
1539        $crate::FieldArray::from_arr($name, $crate::arr_str32_opt!(vec64![$v]))
1540    }};
1541    ($name:expr) => {{
1542        use $crate::vec64;
1543        $crate::FieldArray::from_arr($name, $crate::arr_str32_opt!())
1544    }};
1545}
1546
1547#[cfg(feature = "large_string")]
1548#[macro_export]
1549macro_rules! fa_str64_opt {
1550    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1551        $crate::FieldArray::from_arr($name, $crate::arr_str64_opt!($first, $($rest),+))
1552    };
1553    ($name:expr, @vec64 $v:expr $(,)?) => {
1554        $crate::FieldArray::from_arr($name, $crate::arr_str64_opt!($v))
1555    };
1556    ($name:expr, $v:expr $(,)?) => {{
1557        use $crate::vec64;
1558        $crate::FieldArray::from_arr($name, $crate::arr_str64_opt!(vec64![$v]))
1559    }};
1560    ($name:expr) => {{
1561        use $crate::vec64;
1562        $crate::FieldArray::from_arr($name, $crate::arr_str64_opt!())
1563    }};
1564}
1565
1566// ======== Categorical ========
1567
1568#[cfg(feature = "default_categorical_8")]
1569#[macro_export]
1570macro_rules! fa_cat8_opt {
1571    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1572        $crate::FieldArray::from_arr($name, $crate::arr_cat8_opt!($first, $($rest),+))
1573    };
1574    ($name:expr, @vec64 $v:expr $(,)?) => {
1575        $crate::FieldArray::from_arr($name, $crate::arr_cat8_opt!($v))
1576    };
1577    ($name:expr, $v:expr $(,)?) => {{
1578        use $crate::vec64;
1579        $crate::FieldArray::from_arr($name, $crate::arr_cat8_opt!(vec64![$v]))
1580    }};
1581    ($name:expr) => {{
1582        use $crate::vec64;
1583        $crate::FieldArray::from_arr($name, $crate::arr_cat8_opt!())
1584    }};
1585}
1586
1587#[cfg(feature = "extended_categorical")]
1588#[macro_export]
1589macro_rules! fa_cat16_opt {
1590    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1591        $crate::FieldArray::from_arr($name, $crate::arr_cat16_opt!($first, $($rest),+))
1592    };
1593    ($name:expr, @vec64 $v:expr $(,)?) => {
1594        $crate::FieldArray::from_arr($name, $crate::arr_cat16_opt!($v))
1595    };
1596    ($name:expr, $v:expr $(,)?) => {{
1597        use $crate::vec64;
1598        $crate::FieldArray::from_arr($name, $crate::arr_cat16_opt!(vec64![$v]))
1599    }};
1600    ($name:expr) => {{
1601        use $crate::vec64;
1602        $crate::FieldArray::from_arr($name, $crate::arr_cat16_opt!())
1603    }};
1604}
1605
1606#[cfg(any(
1607    not(feature = "default_categorical_8"),
1608    feature = "extended_categorical"
1609))]
1610#[macro_export]
1611macro_rules! fa_cat32_opt {
1612    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1613        $crate::FieldArray::from_arr($name, $crate::arr_cat32_opt!($first, $($rest),+))
1614    };
1615    ($name:expr, @vec64 $v:expr $(,)?) => {
1616        $crate::FieldArray::from_arr($name, $crate::arr_cat32_opt!($v))
1617    };
1618    ($name:expr, $v:expr $(,)?) => {{
1619        use $crate::vec64;
1620        $crate::FieldArray::from_arr($name, $crate::arr_cat32_opt!(vec64![$v]))
1621    }};
1622    ($name:expr) => {{
1623        use $crate::vec64;
1624        $crate::FieldArray::from_arr($name, $crate::arr_cat32_opt!())
1625    }};
1626}
1627
1628#[cfg(feature = "extended_categorical")]
1629#[macro_export]
1630macro_rules! fa_cat64_opt {
1631    ($name:expr, $first:expr, $($rest:expr),+ $(,)?) => {
1632        $crate::FieldArray::from_arr($name, $crate::arr_cat64_opt!($first, $($rest),+))
1633    };
1634    ($name:expr, @vec64 $v:expr $(,)?) => {
1635        $crate::FieldArray::from_arr($name, $crate::arr_cat64_opt!($v))
1636    };
1637    ($name:expr, $v:expr $(,)?) => {{
1638        use $crate::vec64;
1639        $crate::FieldArray::from_arr($name, $crate::arr_cat64_opt!(vec64![$v]))
1640    }};
1641    ($name:expr) => {{
1642        use $crate::vec64;
1643        $crate::FieldArray::from_arr($name, $crate::arr_cat64_opt!())
1644    }};
1645}
1646
1647#[cfg(test)]
1648mod tests {
1649    use super::*;
1650    use crate::structs::variants::integer::IntegerArray;
1651    use crate::traits::masked_array::MaskedArray;
1652
1653    #[test]
1654    fn test_field_array_basic_construction() {
1655        let mut arr = IntegerArray::<i32>::default();
1656        arr.push(1);
1657        arr.push(2);
1658        let array = Array::from_int32(arr);
1659
1660        let field = Field::new("my_col", ArrowType::Int32, false, None);
1661        let field_array = FieldArray::new(field.clone(), array.clone());
1662
1663        assert_eq!(field_array.len(), 2);
1664        assert_eq!(field_array.field, field.into());
1665        assert_eq!(field_array.array, array.into());
1666    }
1667
1668    #[test]
1669    fn test_field_array_from_parts_infers_nullability() {
1670        let mut arr = IntegerArray::<i64>::default();
1671        arr.push(10);
1672        arr.push_null(); // makes it nullable
1673        let array = Array::from_int64(arr);
1674
1675        let field_array =
1676            FieldArray::from_parts("nullable_col", ArrowType::Int64, None, None, array.clone());
1677
1678        assert_eq!(field_array.field.name, "nullable_col");
1679        assert_eq!(field_array.field.dtype, ArrowType::Int64);
1680        assert_eq!(field_array.field.nullable, true);
1681        assert_eq!(field_array.len(), 2);
1682        assert_eq!(field_array.array, array.into());
1683    }
1684
1685    #[cfg(feature = "views")]
1686    #[test]
1687    fn test_field_array_slice() {
1688        let mut arr = IntegerArray::<i32>::default();
1689        arr.push(10);
1690        arr.push(20);
1691        arr.push(30);
1692
1693        let fa = field_array("x", Array::from_int32(arr));
1694        let view = fa.view(1, 2);
1695        assert_eq!(view.1.name, "x");
1696        assert_eq!(view.0.2, 2);
1697        assert_eq!(view.0.1, 1);
1698        assert_eq!(view.0.2, 2);
1699        assert_eq!(view.0.0.len(), 3);
1700    }
1701
1702    #[test]
1703    fn test_null_count_cache_sync_concat() {
1704        // Create first FieldArray with nulls
1705        let mut arr1 = IntegerArray::<i32>::default();
1706        arr1.push(1);
1707        arr1.push_null();
1708        arr1.push(3);
1709        let mut fa1 = field_array("test", Array::from_int32(arr1));
1710        assert_eq!(fa1.null_count(), 1);
1711
1712        // Create second FieldArray with nulls
1713        let mut arr2 = IntegerArray::<i32>::default();
1714        arr2.push_null();
1715        arr2.push(5);
1716        let fa2 = field_array("test", Array::from_int32(arr2));
1717        assert_eq!(fa2.null_count(), 1);
1718
1719        // Concatenate and verify null_count cache is updated
1720        fa1.concat_field_array(&fa2);
1721        assert_eq!(fa1.len(), 5);
1722        assert_eq!(fa1.null_count(), 2); // Should be 2 nulls total
1723    }
1724
1725    #[test]
1726    fn test_null_count_cache_sync_with_array_mut() {
1727        let mut arr = IntegerArray::<i32>::default();
1728        arr.push(1);
1729        arr.push(2);
1730        let mut fa = field_array("test", Array::from_int32(arr));
1731        assert_eq!(fa.null_count(), 0);
1732
1733        // Mutate through with_array_mut to add nulls
1734        fa.with_array_mut(|array| {
1735            array.concat_array(&Array::from_int32({
1736                let mut new_arr = IntegerArray::<i32>::default();
1737                new_arr.push_null();
1738                new_arr.push_null();
1739                new_arr
1740            }));
1741        });
1742
1743        assert_eq!(fa.len(), 4);
1744        assert_eq!(fa.null_count(), 2); // Cache should be refreshed automatically
1745    }
1746
1747    #[test]
1748    fn test_refresh_null_count() {
1749        let mut arr = IntegerArray::<i32>::default();
1750        arr.push(1);
1751        arr.push(2);
1752        let mut fa = field_array("test", Array::from_int32(arr));
1753        assert_eq!(fa.null_count(), 0);
1754
1755        // Manually mutate underlying array (simulating external mutation)
1756        if let Array::NumericArray(crate::NumericArray::Int32(int_arr)) = &mut fa.array {
1757            use crate::traits::masked_array::MaskedArray;
1758            std::sync::Arc::make_mut(int_arr).push_null();
1759        }
1760
1761        // Cache is now stale
1762        assert_eq!(fa.null_count, 0); // Cached value still 0
1763        assert_eq!(fa.array.null_count(), 1); // Actual value is 1
1764
1765        // Refresh the cache
1766        fa.refresh_null_count();
1767        assert_eq!(fa.null_count(), 1); // Cache now updated
1768    }
1769}
1770
1771#[cfg(test)]
1772mod concat_tests {
1773    use super::*;
1774    use crate::structs::variants::integer::IntegerArray;
1775    use crate::traits::concatenate::Concatenate;
1776    use crate::traits::masked_array::MaskedArray;
1777
1778    #[test]
1779    fn test_field_array_concat_basic() {
1780        let arr1 = IntegerArray::<i32>::from_slice(&[1, 2, 3]);
1781        let fa1 = field_array("numbers", Array::from_int32(arr1));
1782
1783        let arr2 = IntegerArray::<i32>::from_slice(&[4, 5, 6]);
1784        let fa2 = field_array("numbers", Array::from_int32(arr2));
1785
1786        let result = fa1.concat(fa2).unwrap();
1787
1788        assert_eq!(result.len(), 6);
1789        assert_eq!(result.field.name, "numbers");
1790        assert_eq!(result.field.dtype, ArrowType::Int32);
1791
1792        if let Array::NumericArray(crate::NumericArray::Int32(arr)) = result.array {
1793            assert_eq!(arr.len(), 6);
1794            assert_eq!(arr.get(0), Some(1));
1795            assert_eq!(arr.get(5), Some(6));
1796        } else {
1797            panic!("Expected Int32 array");
1798        }
1799    }
1800
1801    #[test]
1802    fn test_field_array_concat_with_nulls() {
1803        let mut arr1 = IntegerArray::<i32>::with_capacity(3, true);
1804        arr1.push(10);
1805        arr1.push_null();
1806        arr1.push(30);
1807        let fa1 = FieldArray::from_parts(
1808            "data",
1809            ArrowType::Int32,
1810            Some(true),
1811            None,
1812            Array::from_int32(arr1),
1813        );
1814
1815        let mut arr2 = IntegerArray::<i32>::with_capacity(2, true);
1816        arr2.push_null();
1817        arr2.push(50);
1818        let fa2 = FieldArray::from_parts(
1819            "data",
1820            ArrowType::Int32,
1821            Some(true),
1822            None,
1823            Array::from_int32(arr2),
1824        );
1825
1826        let result = fa1.concat(fa2).unwrap();
1827
1828        assert_eq!(result.len(), 5);
1829        assert_eq!(result.null_count(), 2);
1830
1831        if let Array::NumericArray(crate::NumericArray::Int32(arr)) = result.array {
1832            assert_eq!(arr.get(0), Some(10));
1833            assert_eq!(arr.get(1), None);
1834            assert_eq!(arr.get(2), Some(30));
1835            assert_eq!(arr.get(3), None);
1836            assert_eq!(arr.get(4), Some(50));
1837        } else {
1838            panic!("Expected Int32 array");
1839        }
1840    }
1841
1842    #[test]
1843    fn test_field_array_concat_name_mismatch() {
1844        let arr1 = IntegerArray::<i32>::from_slice(&[1, 2]);
1845        let fa1 = field_array("col_a", Array::from_int32(arr1));
1846
1847        let arr2 = IntegerArray::<i32>::from_slice(&[3, 4]);
1848        let fa2 = field_array("col_b", Array::from_int32(arr2));
1849
1850        let result = fa1.concat(fa2);
1851        assert!(result.is_err());
1852
1853        if let Err(MinarrowError::IncompatibleTypeError { message, .. }) = result {
1854            assert!(message.unwrap().contains("Field name mismatch"));
1855        } else {
1856            panic!("Expected IncompatibleTypeError");
1857        }
1858    }
1859
1860    #[test]
1861    fn test_field_array_concat_dtype_mismatch() {
1862        let arr1 = IntegerArray::<i32>::from_slice(&[1, 2]);
1863        let fa1 = field_array("data", Array::from_int32(arr1));
1864
1865        let arr2 = crate::FloatArray::<f64>::from_slice(&[3.0, 4.0]);
1866        let fa2 = field_array("data", Array::from_float64(arr2));
1867
1868        let result = fa1.concat(fa2);
1869        assert!(result.is_err());
1870
1871        if let Err(MinarrowError::IncompatibleTypeError { message, .. }) = result {
1872            assert!(message.unwrap().contains("dtype mismatch"));
1873        } else {
1874            panic!("Expected IncompatibleTypeError");
1875        }
1876    }
1877
1878    #[test]
1879    fn test_field_array_concat_nullable_mismatch() {
1880        let arr1 = IntegerArray::<i32>::from_slice(&[1, 2]);
1881        let fa1 = FieldArray::from_parts(
1882            "data",
1883            ArrowType::Int32,
1884            Some(false),
1885            None,
1886            Array::from_int32(arr1),
1887        );
1888
1889        let mut arr2 = IntegerArray::<i32>::with_capacity(2, true);
1890        arr2.push(3);
1891        arr2.push(4);
1892        let fa2 = FieldArray::from_parts(
1893            "data",
1894            ArrowType::Int32,
1895            Some(true),
1896            None,
1897            Array::from_int32(arr2),
1898        );
1899
1900        let result = fa1.concat(fa2);
1901        assert!(result.is_err());
1902
1903        if let Err(MinarrowError::IncompatibleTypeError { message, .. }) = result {
1904            assert!(message.unwrap().contains("nullable mismatch"));
1905        } else {
1906            panic!("Expected IncompatibleTypeError");
1907        }
1908    }
1909}
1910
1911#[cfg(test)]
1912mod fa_macro_tests {
1913    use crate::ffi::arrow_dtype::ArrowType;
1914
1915    #[test]
1916    fn test_fa_i32_literals() {
1917        let fa = fa_i32!("age", 10, 20, 30);
1918        assert_eq!(fa.field.name, "age");
1919        assert_eq!(fa.field.dtype, ArrowType::Int32);
1920        assert_eq!(fa.len(), 3);
1921        assert!(!fa.field.nullable);
1922    }
1923
1924    #[test]
1925    fn test_fa_i64_literals() {
1926        let fa = fa_i64!("big", 100, 200);
1927        assert_eq!(fa.field.name, "big");
1928        assert_eq!(fa.field.dtype, ArrowType::Int64);
1929        assert_eq!(fa.len(), 2);
1930    }
1931
1932    #[test]
1933    fn test_fa_u32_literals() {
1934        let fa = fa_u32!("count", 1, 2, 3, 4);
1935        assert_eq!(fa.field.name, "count");
1936        assert_eq!(fa.field.dtype, ArrowType::UInt32);
1937        assert_eq!(fa.len(), 4);
1938    }
1939
1940    #[test]
1941    fn test_fa_u64_literals() {
1942        let fa = fa_u64!("id", 99, 100);
1943        assert_eq!(fa.field.name, "id");
1944        assert_eq!(fa.field.dtype, ArrowType::UInt64);
1945        assert_eq!(fa.len(), 2);
1946    }
1947
1948    #[test]
1949    fn test_fa_f32_literals() {
1950        let fa = fa_f32!("temp", 1.0, 2.5, 3.7);
1951        assert_eq!(fa.field.name, "temp");
1952        assert_eq!(fa.field.dtype, ArrowType::Float32);
1953        assert_eq!(fa.len(), 3);
1954    }
1955
1956    #[test]
1957    fn test_fa_f64_literals() {
1958        let fa = fa_f64!("price", 9.99, 19.99);
1959        assert_eq!(fa.field.name, "price");
1960        assert_eq!(fa.field.dtype, ArrowType::Float64);
1961        assert_eq!(fa.len(), 2);
1962    }
1963
1964    #[test]
1965    fn test_fa_bool_literals() {
1966        let fa = fa_bool!("active", true, false, true);
1967        assert_eq!(fa.field.name, "active");
1968        assert_eq!(fa.field.dtype, ArrowType::Boolean);
1969        assert_eq!(fa.len(), 3);
1970    }
1971
1972    #[test]
1973    fn test_fa_str32_literals() {
1974        let fa = fa_str32!("name", "alice", "bob", "charlie");
1975        assert_eq!(fa.field.name, "name");
1976        assert_eq!(fa.field.dtype, ArrowType::String);
1977        assert_eq!(fa.len(), 3);
1978    }
1979
1980    #[cfg(feature = "large_string")]
1981    #[test]
1982    fn test_fa_str64_literals() {
1983        let fa = fa_str64!("label", "x", "y");
1984        assert_eq!(fa.field.name, "label");
1985        assert_eq!(fa.field.dtype, ArrowType::LargeString);
1986        assert_eq!(fa.len(), 2);
1987    }
1988
1989    #[cfg(any(
1990        not(feature = "default_categorical_8"),
1991        feature = "extended_categorical"
1992    ))]
1993    #[test]
1994    fn test_fa_cat32_literals() {
1995        use crate::ffi::arrow_dtype::CategoricalIndexType;
1996        let fa = fa_cat32!("colour", "red", "green", "blue");
1997        assert_eq!(fa.field.name, "colour");
1998        assert_eq!(
1999            fa.field.dtype,
2000            ArrowType::Dictionary(CategoricalIndexType::UInt32)
2001        );
2002        assert_eq!(fa.len(), 3);
2003    }
2004
2005    #[test]
2006    fn test_fa_i32_empty() {
2007        let fa = fa_i32!("empty");
2008        assert_eq!(fa.field.name, "empty");
2009        assert_eq!(fa.field.dtype, ArrowType::Int32);
2010        assert_eq!(fa.len(), 0);
2011    }
2012
2013    #[test]
2014    fn test_fa_i32_opt_with_nulls() {
2015        let fa = fa_i32_opt!("score", Some(1i32), None::<i32>, Some(3));
2016        assert_eq!(fa.field.name, "score");
2017        assert_eq!(fa.field.dtype, ArrowType::Int32);
2018        assert_eq!(fa.len(), 3);
2019        assert!(fa.field.nullable);
2020        assert_eq!(fa.null_count(), 1);
2021    }
2022
2023    #[test]
2024    fn test_fa_f64_opt_with_nulls() {
2025        let fa = fa_f64_opt!("weight", Some(1.5f64), None::<f64>);
2026        assert_eq!(fa.field.name, "weight");
2027        assert_eq!(fa.field.dtype, ArrowType::Float64);
2028        assert_eq!(fa.len(), 2);
2029        assert!(fa.field.nullable);
2030        assert_eq!(fa.null_count(), 1);
2031    }
2032
2033    #[test]
2034    fn test_fa_bool_opt_with_nulls() {
2035        let fa = fa_bool_opt!("flag", Some(true), None::<bool>, Some(false));
2036        assert_eq!(fa.field.name, "flag");
2037        assert_eq!(fa.field.dtype, ArrowType::Boolean);
2038        assert_eq!(fa.len(), 3);
2039        assert!(fa.field.nullable);
2040        assert_eq!(fa.null_count(), 1);
2041    }
2042
2043    #[test]
2044    fn test_fa_str32_opt_with_nulls() {
2045        let fa = fa_str32_opt!("city", Some("london"), None::<&str>, Some("paris"));
2046        assert_eq!(fa.field.name, "city");
2047        assert_eq!(fa.field.dtype, ArrowType::String);
2048        assert_eq!(fa.len(), 3);
2049        assert!(fa.field.nullable);
2050        assert_eq!(fa.null_count(), 1);
2051    }
2052
2053    #[test]
2054    fn test_fa_i32_single_value() {
2055        let fa = fa_i32!("data", 42);
2056        assert_eq!(fa.field.name, "data");
2057        assert_eq!(fa.len(), 1);
2058    }
2059
2060    #[test]
2061    fn test_fa_i32_trailing_comma() {
2062        let fa = fa_i32!("data", 5, 6, 7,);
2063        assert_eq!(fa.field.name, "data");
2064        assert_eq!(fa.len(), 3);
2065    }
2066}
2067
2068#[cfg(test)]
2069mod fa_macro_extensions_tests {
2070    use crate::{Array, Bitmask, NumericArray, Vec64, vec64};
2071
2072    fn f64_values_and_mask(fa: &crate::FieldArray) -> (Vec<f64>, Option<Bitmask>) {
2073        match &fa.array {
2074            Array::NumericArray(NumericArray::Float64(a)) => {
2075                (a.data.as_slice().to_vec(), a.null_mask.clone())
2076            }
2077            _ => panic!("expected Float64 FieldArray"),
2078        }
2079    }
2080
2081    #[test]
2082    fn fa_f64_vec64_marker_with_mask() {
2083        let v: Vec64<f64> = vec64![1.0, 2.0, 3.0];
2084        let m = Bitmask::from_bools(&[true, false, true]);
2085        let fa = fa_f64!("a", @vec64 v; m);
2086        assert_eq!(fa.field.name, "a");
2087        let (data, mask) = f64_values_and_mask(&fa);
2088        assert_eq!(data, vec![1.0, 2.0, 3.0]);
2089        assert_eq!(mask.expect("expected mask").get(1), false);
2090    }
2091
2092    #[test]
2093    fn fa_f64_slice_marker_with_mask() {
2094        let s: &[f64] = &[1.0, 2.0, 3.0];
2095        let m = Bitmask::from_bools(&[false, true, true]);
2096        let fa = fa_f64!("a", @slice s; m);
2097        let (data, mask) = f64_values_and_mask(&fa);
2098        assert_eq!(data, vec![1.0, 2.0, 3.0]);
2099        assert_eq!(mask.expect("expected mask").get(0), false);
2100    }
2101
2102    #[test]
2103    fn fa_f64_bare_values_with_mask() {
2104        let s: &[f64] = &[10.0, 20.0, 30.0];
2105        let m = Bitmask::from_bools(&[true, true, false]);
2106        let fa = fa_f64!("a", s; m);
2107        let (data, mask) = f64_values_and_mask(&fa);
2108        assert_eq!(data, vec![10.0, 20.0, 30.0]);
2109        assert_eq!(mask.expect("expected mask").get(2), false);
2110    }
2111
2112    #[test]
2113    fn fa_f64_literal_multi_with_mask() {
2114        let m = Bitmask::from_bools(&[true, false, true]);
2115        let fa = fa_f64!("a", 1.0, 2.0, 3.0 ; m);
2116        let (data, mask) = f64_values_and_mask(&fa);
2117        assert_eq!(data, vec![1.0, 2.0, 3.0]);
2118        assert_eq!(mask.expect("expected mask").get(1), false);
2119    }
2120
2121    #[test]
2122    fn fa_f64_slice_marker_no_mask() {
2123        let s: &[f64] = &[1.0, 2.0];
2124        let fa = fa_f64!("a", @slice s);
2125        let (data, mask) = f64_values_and_mask(&fa);
2126        assert_eq!(data, vec![1.0, 2.0]);
2127        assert!(mask.is_none());
2128    }
2129
2130    #[test]
2131    fn fa_f64_existing_forms_unchanged() {
2132        let single = fa_f64!("a", 42.0);
2133        assert_eq!(single.len(), 1);
2134
2135        let multi = fa_f64!("a", 1.0, 2.0, 3.0);
2136        assert_eq!(multi.len(), 3);
2137
2138        let v: Vec64<f64> = vec64![5.0, 6.0];
2139        let from_vec = fa_f64!("a", @vec64 v);
2140        assert_eq!(from_vec.len(), 2);
2141
2142        let empty = fa_f64!("a");
2143        assert_eq!(empty.len(), 0);
2144    }
2145}