Skip to main content

sea_orm_arrow/
lib.rs

1pub use arrow;
2
3use arrow::array::*;
4use arrow::datatypes::i256;
5use sea_query::{ColumnType, Value};
6
7// ---------------------------------------------------------------------------
8// Error type
9// ---------------------------------------------------------------------------
10
11/// Errors that can occur when converting between SeaORM [`Value`]s and Arrow arrays.
12#[derive(Debug, thiserror::Error)]
13pub enum ArrowError {
14    /// The Arrow array type is incompatible with the target SeaORM column type.
15    #[error("expected {expected} for column type {col_type}, got Arrow type {actual}")]
16    TypeMismatch {
17        expected: &'static str,
18        col_type: &'static str,
19        actual: String,
20    },
21
22    /// A value lies outside the representable range for the target type.
23    #[error("{0}")]
24    OutOfRange(String),
25
26    /// The column type or Arrow data type is not supported for conversion.
27    #[error("{0}")]
28    Unsupported(String),
29}
30
31fn type_err(expected: &'static str, col_type: &'static str, array: &dyn Array) -> ArrowError {
32    ArrowError::TypeMismatch {
33        expected,
34        col_type,
35        actual: format!("{:?}", array.data_type()),
36    }
37}
38
39// ---------------------------------------------------------------------------
40// Arrow -> Value
41// ---------------------------------------------------------------------------
42
43/// Extract a [`Value`] from an Arrow array at the given row index,
44/// based on the expected [`ColumnType`] from the entity definition.
45///
46/// For date/time column types, this produces chrono `Value` variants when
47/// the `with-chrono` feature is enabled, or time-crate variants when only
48/// `with-time` is enabled.
49pub fn arrow_array_to_value(
50    array: &dyn Array,
51    col_type: &ColumnType,
52    row: usize,
53) -> Result<Value, ArrowError> {
54    if array.is_null(row) {
55        return Ok(null_value_for_type(col_type));
56    }
57    match col_type {
58        ColumnType::TinyInteger => {
59            let arr = array
60                .as_any()
61                .downcast_ref::<Int8Array>()
62                .ok_or_else(|| type_err("Int8Array", "TinyInteger", array))?;
63            Ok(Value::TinyInt(Some(arr.value(row))))
64        }
65        ColumnType::SmallInteger => {
66            let arr = array
67                .as_any()
68                .downcast_ref::<Int16Array>()
69                .ok_or_else(|| type_err("Int16Array", "SmallInteger", array))?;
70            Ok(Value::SmallInt(Some(arr.value(row))))
71        }
72        ColumnType::Integer => {
73            let arr = array
74                .as_any()
75                .downcast_ref::<Int32Array>()
76                .ok_or_else(|| type_err("Int32Array", "Integer", array))?;
77            Ok(Value::Int(Some(arr.value(row))))
78        }
79        ColumnType::BigInteger => {
80            let arr = array
81                .as_any()
82                .downcast_ref::<Int64Array>()
83                .ok_or_else(|| type_err("Int64Array", "BigInteger", array))?;
84            Ok(Value::BigInt(Some(arr.value(row))))
85        }
86        ColumnType::TinyUnsigned => {
87            let arr = array
88                .as_any()
89                .downcast_ref::<UInt8Array>()
90                .ok_or_else(|| type_err("UInt8Array", "TinyUnsigned", array))?;
91            Ok(Value::TinyUnsigned(Some(arr.value(row))))
92        }
93        ColumnType::SmallUnsigned => {
94            let arr = array
95                .as_any()
96                .downcast_ref::<UInt16Array>()
97                .ok_or_else(|| type_err("UInt16Array", "SmallUnsigned", array))?;
98            Ok(Value::SmallUnsigned(Some(arr.value(row))))
99        }
100        ColumnType::Unsigned => {
101            let arr = array
102                .as_any()
103                .downcast_ref::<UInt32Array>()
104                .ok_or_else(|| type_err("UInt32Array", "Unsigned", array))?;
105            Ok(Value::Unsigned(Some(arr.value(row))))
106        }
107        ColumnType::BigUnsigned => {
108            let arr = array
109                .as_any()
110                .downcast_ref::<UInt64Array>()
111                .ok_or_else(|| type_err("UInt64Array", "BigUnsigned", array))?;
112            Ok(Value::BigUnsigned(Some(arr.value(row))))
113        }
114        ColumnType::Float => {
115            let arr = array
116                .as_any()
117                .downcast_ref::<Float32Array>()
118                .ok_or_else(|| type_err("Float32Array", "Float", array))?;
119            Ok(Value::Float(Some(arr.value(row))))
120        }
121        ColumnType::Double => {
122            let arr = array
123                .as_any()
124                .downcast_ref::<Float64Array>()
125                .ok_or_else(|| type_err("Float64Array", "Double", array))?;
126            Ok(Value::Double(Some(arr.value(row))))
127        }
128        ColumnType::String(_) | ColumnType::Text | ColumnType::Char(_) => {
129            if let Some(arr) = array.as_any().downcast_ref::<StringArray>() {
130                Ok(Value::String(Some(arr.value(row).to_owned())))
131            } else if let Some(arr) = array.as_any().downcast_ref::<LargeStringArray>() {
132                Ok(Value::String(Some(arr.value(row).to_owned())))
133            } else {
134                Err(type_err(
135                    "StringArray or LargeStringArray",
136                    "String/Text",
137                    array,
138                ))
139            }
140        }
141        ColumnType::Boolean => {
142            let arr = array
143                .as_any()
144                .downcast_ref::<BooleanArray>()
145                .ok_or_else(|| type_err("BooleanArray", "Boolean", array))?;
146            Ok(Value::Bool(Some(arr.value(row))))
147        }
148        // Binary types
149        ColumnType::Binary(_) | ColumnType::VarBinary(_) => arrow_to_bytes(array, row),
150        // Decimal types
151        ColumnType::Decimal(_) | ColumnType::Money(_) => arrow_to_decimal(array, row),
152        // Date/time types: delegate to feature-gated helpers.
153        // Prefer chrono when available; fall back to time crate.
154        #[cfg(feature = "with-chrono")]
155        ColumnType::Date => arrow_to_chrono_date(array, row),
156        #[cfg(feature = "with-chrono")]
157        ColumnType::Time => arrow_to_chrono_time(array, row),
158        #[cfg(feature = "with-chrono")]
159        ColumnType::DateTime | ColumnType::Timestamp => arrow_to_chrono_datetime(array, row),
160        #[cfg(feature = "with-chrono")]
161        ColumnType::TimestampWithTimeZone => arrow_to_chrono_datetime_utc(array, row),
162
163        #[cfg(all(feature = "with-time", not(feature = "with-chrono")))]
164        ColumnType::Date => arrow_to_time_date(array, row),
165        #[cfg(all(feature = "with-time", not(feature = "with-chrono")))]
166        ColumnType::Time => arrow_to_time_time(array, row),
167        #[cfg(all(feature = "with-time", not(feature = "with-chrono")))]
168        ColumnType::DateTime | ColumnType::Timestamp => arrow_to_time_datetime(array, row),
169        #[cfg(all(feature = "with-time", not(feature = "with-chrono")))]
170        ColumnType::TimestampWithTimeZone => arrow_to_time_datetime_tz(array, row),
171
172        _ => Err(ArrowError::Unsupported(format!(
173            "Unsupported column type for Arrow conversion: {col_type:?}"
174        ))),
175    }
176}
177
178/// When both `with-chrono` and `with-time` are enabled, this provides the
179/// time-crate alternative for date/time columns. Called as a fallback when
180/// the chrono Value variant doesn't match the model's field type.
181#[cfg(all(feature = "with-chrono", feature = "with-time"))]
182pub fn arrow_array_to_value_alt(
183    array: &dyn Array,
184    col_type: &ColumnType,
185    row: usize,
186) -> Result<Option<Value>, ArrowError> {
187    if array.is_null(row) {
188        return Ok(Some(null_value_for_type_time(col_type)));
189    }
190    match col_type {
191        ColumnType::Date => arrow_to_time_date(array, row).map(Some),
192        ColumnType::Time => arrow_to_time_time(array, row).map(Some),
193        ColumnType::DateTime | ColumnType::Timestamp => {
194            arrow_to_time_datetime(array, row).map(Some)
195        }
196        ColumnType::TimestampWithTimeZone => arrow_to_time_datetime_tz(array, row).map(Some),
197        _ => Ok(None),
198    }
199}
200
201/// Returns true for ColumnTypes that may need a chrono->time fallback.
202pub fn is_datetime_column(col_type: &ColumnType) -> bool {
203    matches!(
204        col_type,
205        ColumnType::Date
206            | ColumnType::Time
207            | ColumnType::DateTime
208            | ColumnType::Timestamp
209            | ColumnType::TimestampWithTimeZone
210    )
211}
212
213// ---------------------------------------------------------------------------
214// Binary helpers
215// ---------------------------------------------------------------------------
216
217fn arrow_to_bytes(array: &dyn Array, row: usize) -> Result<Value, ArrowError> {
218    if let Some(arr) = array.as_any().downcast_ref::<BinaryArray>() {
219        return Ok(Value::Bytes(Some(arr.value(row).to_vec())));
220    }
221    if let Some(arr) = array.as_any().downcast_ref::<LargeBinaryArray>() {
222        return Ok(Value::Bytes(Some(arr.value(row).to_vec())));
223    }
224    if let Some(arr) = array.as_any().downcast_ref::<FixedSizeBinaryArray>() {
225        return Ok(Value::Bytes(Some(arr.value(row).to_vec())));
226    }
227    Err(type_err(
228        "BinaryArray, LargeBinaryArray, or FixedSizeBinaryArray",
229        "Binary/VarBinary",
230        array,
231    ))
232}
233
234// ---------------------------------------------------------------------------
235// Decimal helpers
236// ---------------------------------------------------------------------------
237
238/// Convert Arrow Decimal128Array or Decimal256Array to a decimal Value.
239/// Prefers rust_decimal when available and precision fits, otherwise bigdecimal.
240fn arrow_to_decimal(array: &dyn Array, row: usize) -> Result<Value, ArrowError> {
241    if let Some(arr) = array.as_any().downcast_ref::<Decimal128Array>() {
242        let value = arr.value(row);
243        let precision = arr.precision();
244        let scale = arr.scale();
245        return decimal128_to_value(value, precision, scale);
246    }
247
248    if let Some(arr) = array.as_any().downcast_ref::<Decimal64Array>() {
249        let value = arr.value(row);
250        let precision = arr.precision();
251        let scale = arr.scale();
252        return decimal64_to_value(value, precision, scale);
253    }
254
255    if let Some(arr) = array.as_any().downcast_ref::<Decimal256Array>() {
256        let value = arr.value(row);
257        let precision = arr.precision();
258        let scale = arr.scale();
259        return decimal256_to_value(value, precision, scale);
260    }
261
262    Err(type_err(
263        "Decimal64Array, Decimal128Array, or Decimal256Array",
264        "Decimal",
265        array,
266    ))
267}
268
269#[cfg(feature = "with-rust_decimal")]
270fn decimal64_to_value(value: i64, _precision: u8, scale: i8) -> Result<Value, ArrowError> {
271    use sea_query::prelude::Decimal;
272
273    if scale < 0 {
274        #[cfg(feature = "with-bigdecimal")]
275        return decimal64_to_bigdecimal(value, scale);
276
277        #[cfg(not(feature = "with-bigdecimal"))]
278        return Err(ArrowError::Unsupported(format!(
279            "Decimal64 with negative scale={scale} not supported by rust_decimal. \
280             Enable 'with-bigdecimal' feature."
281        )));
282    }
283
284    let decimal = Decimal::from_i128_with_scale(value as i128, scale as u32);
285    Ok(Value::Decimal(Some(decimal)))
286}
287
288#[cfg(not(feature = "with-rust_decimal"))]
289fn decimal64_to_value(_value: i64, _precision: u8, _scale: i8) -> Result<Value, ArrowError> {
290    #[cfg(feature = "with-bigdecimal")]
291    return decimal64_to_bigdecimal(_value, _scale);
292
293    #[cfg(not(feature = "with-bigdecimal"))]
294    Err(ArrowError::Unsupported(
295        "Decimal64Array requires 'with-rust_decimal' or 'with-bigdecimal' feature".into(),
296    ))
297}
298
299#[cfg(feature = "with-bigdecimal")]
300fn decimal64_to_bigdecimal(value: i64, scale: i8) -> Result<Value, ArrowError> {
301    use sea_query::prelude::bigdecimal::{BigDecimal, num_bigint::BigInt};
302
303    let bigint = BigInt::from(value);
304    let decimal = BigDecimal::new(bigint, scale as i64);
305    Ok(Value::BigDecimal(Some(Box::new(decimal))))
306}
307
308#[cfg(feature = "with-rust_decimal")]
309fn decimal128_to_value(value: i128, precision: u8, scale: i8) -> Result<Value, ArrowError> {
310    use sea_query::prelude::Decimal;
311
312    if precision > 28 || scale > 28 || scale < 0 {
313        #[cfg(feature = "with-bigdecimal")]
314        return decimal128_to_bigdecimal(value, scale);
315
316        #[cfg(not(feature = "with-bigdecimal"))]
317        return Err(ArrowError::Unsupported(format!(
318            "Decimal128 with precision={precision}, scale={scale} exceeds rust_decimal limits \
319             (max precision=28, scale=0-28). Enable 'with-bigdecimal' feature for arbitrary precision."
320        )));
321    }
322
323    let decimal = Decimal::from_i128_with_scale(value, scale as u32);
324    Ok(Value::Decimal(Some(decimal)))
325}
326
327#[cfg(not(feature = "with-rust_decimal"))]
328fn decimal128_to_value(_value: i128, _precision: u8, _scale: i8) -> Result<Value, ArrowError> {
329    #[cfg(feature = "with-bigdecimal")]
330    return decimal128_to_bigdecimal(_value, _scale);
331
332    #[cfg(not(feature = "with-bigdecimal"))]
333    Err(ArrowError::Unsupported(
334        "Decimal128Array requires 'with-rust_decimal' or 'with-bigdecimal' feature".into(),
335    ))
336}
337
338#[cfg(feature = "with-bigdecimal")]
339fn decimal128_to_bigdecimal(value: i128, scale: i8) -> Result<Value, ArrowError> {
340    use sea_query::prelude::bigdecimal::{BigDecimal, num_bigint::BigInt};
341
342    let bigint = BigInt::from(value);
343    let decimal = BigDecimal::new(bigint, scale as i64);
344    Ok(Value::BigDecimal(Some(Box::new(decimal))))
345}
346
347fn decimal256_to_value(_value: i256, _precision: u8, _scale: i8) -> Result<Value, ArrowError> {
348    #[cfg(feature = "with-bigdecimal")]
349    {
350        use sea_query::prelude::bigdecimal::{
351            BigDecimal,
352            num_bigint::{BigInt, Sign},
353        };
354
355        let bytes = _value.to_be_bytes();
356
357        let (sign, magnitude) = if _value.is_negative() {
358            let mut abs_bytes = [0u8; 32];
359            let mut carry = true;
360
361            for i in (0..32).rev() {
362                abs_bytes[i] = !bytes[i];
363                if carry {
364                    if abs_bytes[i] == 255 {
365                        abs_bytes[i] = 0;
366                    } else {
367                        abs_bytes[i] += 1;
368                        carry = false;
369                    }
370                }
371            }
372
373            (Sign::Minus, abs_bytes.to_vec())
374        } else if _value == i256::ZERO {
375            (Sign::NoSign, vec![0])
376        } else {
377            let first_nonzero = bytes.iter().position(|&b| b != 0).unwrap_or(31);
378            (Sign::Plus, bytes[first_nonzero..].to_vec())
379        };
380
381        let bigint = BigInt::from_bytes_be(sign, &magnitude);
382        let decimal = BigDecimal::new(bigint, _scale as i64);
383        return Ok(Value::BigDecimal(Some(Box::new(decimal))));
384    }
385
386    #[cfg(not(feature = "with-bigdecimal"))]
387    Err(ArrowError::Unsupported(
388        "Decimal256Array requires 'with-bigdecimal' feature for arbitrary precision support".into(),
389    ))
390}
391
392// ---------------------------------------------------------------------------
393// Chrono date/time helpers
394// ---------------------------------------------------------------------------
395
396#[cfg(feature = "with-chrono")]
397fn arrow_to_chrono_date(array: &dyn Array, row: usize) -> Result<Value, ArrowError> {
398    use sea_query::prelude::chrono::NaiveDate;
399    let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).expect("valid date");
400
401    if let Some(arr) = array.as_any().downcast_ref::<Date32Array>() {
402        let days = arr.value(row);
403        let date = epoch
404            .checked_add_signed(sea_query::prelude::chrono::Duration::days(days as i64))
405            .ok_or_else(|| ArrowError::OutOfRange(format!("Date32 value {days} out of range")))?;
406        Ok(Value::ChronoDate(Some(date)))
407    } else if let Some(arr) = array.as_any().downcast_ref::<Date64Array>() {
408        let ms = arr.value(row);
409        let date = epoch
410            .checked_add_signed(sea_query::prelude::chrono::Duration::milliseconds(ms))
411            .ok_or_else(|| ArrowError::OutOfRange(format!("Date64 value {ms} out of range")))?;
412        Ok(Value::ChronoDate(Some(date)))
413    } else {
414        Err(type_err("Date32Array or Date64Array", "Date", array))
415    }
416}
417
418#[cfg(feature = "with-chrono")]
419fn arrow_to_chrono_time(array: &dyn Array, row: usize) -> Result<Value, ArrowError> {
420    use sea_query::prelude::chrono::NaiveTime;
421
422    if let Some(arr) = array.as_any().downcast_ref::<Time32SecondArray>() {
423        let secs = arr.value(row) as u32;
424        let t = NaiveTime::from_num_seconds_from_midnight_opt(secs, 0).ok_or_else(|| {
425            ArrowError::OutOfRange(format!("Time32Second value {secs} out of range"))
426        })?;
427        Ok(Value::ChronoTime(Some(t)))
428    } else if let Some(arr) = array.as_any().downcast_ref::<Time32MillisecondArray>() {
429        let ms = arr.value(row);
430        let secs = (ms / 1_000) as u32;
431        let nanos = ((ms % 1_000) * 1_000_000) as u32;
432        let t = NaiveTime::from_num_seconds_from_midnight_opt(secs, nanos).ok_or_else(|| {
433            ArrowError::OutOfRange(format!("Time32Millisecond value {ms} out of range"))
434        })?;
435        Ok(Value::ChronoTime(Some(t)))
436    } else if let Some(arr) = array.as_any().downcast_ref::<Time64MicrosecondArray>() {
437        let us = arr.value(row);
438        let secs = (us / 1_000_000) as u32;
439        let nanos = ((us % 1_000_000) * 1_000) as u32;
440        let t = NaiveTime::from_num_seconds_from_midnight_opt(secs, nanos).ok_or_else(|| {
441            ArrowError::OutOfRange(format!("Time64Microsecond value {us} out of range"))
442        })?;
443        Ok(Value::ChronoTime(Some(t)))
444    } else if let Some(arr) = array.as_any().downcast_ref::<Time64NanosecondArray>() {
445        let ns = arr.value(row);
446        let secs = (ns / 1_000_000_000) as u32;
447        let nanos = (ns % 1_000_000_000) as u32;
448        let t = NaiveTime::from_num_seconds_from_midnight_opt(secs, nanos).ok_or_else(|| {
449            ArrowError::OutOfRange(format!("Time64Nanosecond value {ns} out of range"))
450        })?;
451        Ok(Value::ChronoTime(Some(t)))
452    } else {
453        Err(type_err("Time32/Time64 Array", "Time", array))
454    }
455}
456
457#[cfg(feature = "with-chrono")]
458fn arrow_timestamp_to_utc(
459    array: &dyn Array,
460    row: usize,
461) -> Result<sea_query::prelude::chrono::DateTime<sea_query::prelude::chrono::Utc>, ArrowError> {
462    use sea_query::prelude::chrono::{DateTime, Utc};
463
464    if let Some(arr) = array.as_any().downcast_ref::<TimestampSecondArray>() {
465        DateTime::<Utc>::from_timestamp(arr.value(row), 0)
466            .ok_or_else(|| ArrowError::OutOfRange("Timestamp seconds out of range".into()))
467    } else if let Some(arr) = array.as_any().downcast_ref::<TimestampMillisecondArray>() {
468        DateTime::<Utc>::from_timestamp_millis(arr.value(row))
469            .ok_or_else(|| ArrowError::OutOfRange("Timestamp milliseconds out of range".into()))
470    } else if let Some(arr) = array.as_any().downcast_ref::<TimestampMicrosecondArray>() {
471        DateTime::<Utc>::from_timestamp_micros(arr.value(row))
472            .ok_or_else(|| ArrowError::OutOfRange("Timestamp microseconds out of range".into()))
473    } else if let Some(arr) = array.as_any().downcast_ref::<TimestampNanosecondArray>() {
474        let nanos = arr.value(row);
475        let secs = nanos.div_euclid(1_000_000_000);
476        let nsec = nanos.rem_euclid(1_000_000_000) as u32;
477        DateTime::<Utc>::from_timestamp(secs, nsec)
478            .ok_or_else(|| ArrowError::OutOfRange("Timestamp nanoseconds out of range".into()))
479    } else {
480        Err(type_err(
481            "TimestampSecond/Millisecond/Microsecond/NanosecondArray",
482            "DateTime/Timestamp",
483            array,
484        ))
485    }
486}
487
488#[cfg(feature = "with-chrono")]
489fn arrow_to_chrono_datetime(array: &dyn Array, row: usize) -> Result<Value, ArrowError> {
490    let dt = arrow_timestamp_to_utc(array, row)?;
491    Ok(Value::ChronoDateTime(Some(dt.naive_utc())))
492}
493
494#[cfg(feature = "with-chrono")]
495fn arrow_to_chrono_datetime_utc(array: &dyn Array, row: usize) -> Result<Value, ArrowError> {
496    let dt = arrow_timestamp_to_utc(array, row)?;
497    Ok(Value::ChronoDateTimeUtc(Some(dt)))
498}
499
500// ---------------------------------------------------------------------------
501// Time-crate date/time helpers
502// ---------------------------------------------------------------------------
503
504#[cfg(feature = "with-time")]
505fn arrow_to_time_date(array: &dyn Array, row: usize) -> Result<Value, ArrowError> {
506    const EPOCH_JULIAN: i32 = 2_440_588;
507
508    if let Some(arr) = array.as_any().downcast_ref::<Date32Array>() {
509        let days = arr.value(row);
510        let date =
511            sea_query::prelude::time::Date::from_julian_day(EPOCH_JULIAN + days).map_err(|e| {
512                ArrowError::OutOfRange(format!("Date32 value {days} out of range: {e}"))
513            })?;
514        Ok(Value::TimeDate(Some(date)))
515    } else if let Some(arr) = array.as_any().downcast_ref::<Date64Array>() {
516        let ms = arr.value(row);
517        let days = (ms / 86_400_000) as i32;
518        let date = sea_query::prelude::time::Date::from_julian_day(EPOCH_JULIAN + days)
519            .map_err(|e| ArrowError::OutOfRange(format!("Date64 value {ms} out of range: {e}")))?;
520        Ok(Value::TimeDate(Some(date)))
521    } else {
522        Err(type_err("Date32Array or Date64Array", "Date", array))
523    }
524}
525
526#[cfg(feature = "with-time")]
527fn arrow_to_time_time(array: &dyn Array, row: usize) -> Result<Value, ArrowError> {
528    if let Some(arr) = array.as_any().downcast_ref::<Time32SecondArray>() {
529        let secs = arr.value(row);
530        let t = sea_query::prelude::time::Time::from_hms(
531            (secs / 3600) as u8,
532            ((secs % 3600) / 60) as u8,
533            (secs % 60) as u8,
534        )
535        .map_err(|e| {
536            ArrowError::OutOfRange(format!("Time32Second value {secs} out of range: {e}"))
537        })?;
538        Ok(Value::TimeTime(Some(t)))
539    } else if let Some(arr) = array.as_any().downcast_ref::<Time32MillisecondArray>() {
540        let ms = arr.value(row);
541        let total_secs = ms / 1_000;
542        let nanos = ((ms % 1_000) * 1_000_000) as u32;
543        let t = sea_query::prelude::time::Time::from_hms_nano(
544            (total_secs / 3600) as u8,
545            ((total_secs % 3600) / 60) as u8,
546            (total_secs % 60) as u8,
547            nanos,
548        )
549        .map_err(|e| {
550            ArrowError::OutOfRange(format!("Time32Millisecond value {ms} out of range: {e}"))
551        })?;
552        Ok(Value::TimeTime(Some(t)))
553    } else if let Some(arr) = array.as_any().downcast_ref::<Time64MicrosecondArray>() {
554        let us = arr.value(row);
555        let total_secs = us / 1_000_000;
556        let nanos = ((us % 1_000_000) * 1_000) as u32;
557        let t = sea_query::prelude::time::Time::from_hms_nano(
558            (total_secs / 3600) as u8,
559            ((total_secs % 3600) / 60) as u8,
560            (total_secs % 60) as u8,
561            nanos,
562        )
563        .map_err(|e| {
564            ArrowError::OutOfRange(format!("Time64Microsecond value {us} out of range: {e}"))
565        })?;
566        Ok(Value::TimeTime(Some(t)))
567    } else if let Some(arr) = array.as_any().downcast_ref::<Time64NanosecondArray>() {
568        let ns = arr.value(row);
569        let total_secs = ns / 1_000_000_000;
570        let nanos = (ns % 1_000_000_000) as u32;
571        let t = sea_query::prelude::time::Time::from_hms_nano(
572            (total_secs / 3600) as u8,
573            ((total_secs % 3600) / 60) as u8,
574            (total_secs % 60) as u8,
575            nanos,
576        )
577        .map_err(|e| {
578            ArrowError::OutOfRange(format!("Time64Nanosecond value {ns} out of range: {e}"))
579        })?;
580        Ok(Value::TimeTime(Some(t)))
581    } else {
582        Err(type_err("Time32/Time64 Array", "Time", array))
583    }
584}
585
586#[cfg(feature = "with-time")]
587fn arrow_timestamp_to_offset_dt(
588    array: &dyn Array,
589    row: usize,
590) -> Result<sea_query::prelude::time::OffsetDateTime, ArrowError> {
591    if let Some(arr) = array.as_any().downcast_ref::<TimestampSecondArray>() {
592        sea_query::prelude::time::OffsetDateTime::from_unix_timestamp(arr.value(row))
593            .map_err(|e| ArrowError::OutOfRange(format!("Timestamp seconds out of range: {e}")))
594    } else if let Some(arr) = array.as_any().downcast_ref::<TimestampMillisecondArray>() {
595        let ms = arr.value(row);
596        sea_query::prelude::time::OffsetDateTime::from_unix_timestamp_nanos(ms as i128 * 1_000_000)
597            .map_err(|e| {
598                ArrowError::OutOfRange(format!("Timestamp milliseconds out of range: {e}"))
599            })
600    } else if let Some(arr) = array.as_any().downcast_ref::<TimestampMicrosecondArray>() {
601        let us = arr.value(row);
602        sea_query::prelude::time::OffsetDateTime::from_unix_timestamp_nanos(us as i128 * 1_000)
603            .map_err(|e| {
604                ArrowError::OutOfRange(format!("Timestamp microseconds out of range: {e}"))
605            })
606    } else if let Some(arr) = array.as_any().downcast_ref::<TimestampNanosecondArray>() {
607        sea_query::prelude::time::OffsetDateTime::from_unix_timestamp_nanos(arr.value(row) as i128)
608            .map_err(|e| ArrowError::OutOfRange(format!("Timestamp nanoseconds out of range: {e}")))
609    } else {
610        Err(type_err(
611            "TimestampSecond/Millisecond/Microsecond/NanosecondArray",
612            "DateTime/Timestamp",
613            array,
614        ))
615    }
616}
617
618#[cfg(feature = "with-time")]
619fn arrow_to_time_datetime(array: &dyn Array, row: usize) -> Result<Value, ArrowError> {
620    let odt = arrow_timestamp_to_offset_dt(array, row)?;
621    Ok(Value::TimeDateTime(Some(
622        sea_query::prelude::time::PrimitiveDateTime::new(odt.date(), odt.time()),
623    )))
624}
625
626#[cfg(feature = "with-time")]
627fn arrow_to_time_datetime_tz(array: &dyn Array, row: usize) -> Result<Value, ArrowError> {
628    let odt = arrow_timestamp_to_offset_dt(array, row)?;
629    Ok(Value::TimeDateTimeWithTimeZone(Some(odt)))
630}
631
632// ---------------------------------------------------------------------------
633// Null value helpers
634// ---------------------------------------------------------------------------
635
636fn null_value_for_type(col_type: &ColumnType) -> Value {
637    match col_type {
638        ColumnType::TinyInteger => Value::TinyInt(None),
639        ColumnType::SmallInteger => Value::SmallInt(None),
640        ColumnType::Integer => Value::Int(None),
641        ColumnType::BigInteger => Value::BigInt(None),
642        ColumnType::TinyUnsigned => Value::TinyUnsigned(None),
643        ColumnType::SmallUnsigned => Value::SmallUnsigned(None),
644        ColumnType::Unsigned => Value::Unsigned(None),
645        ColumnType::BigUnsigned => Value::BigUnsigned(None),
646        ColumnType::Float => Value::Float(None),
647        ColumnType::Double => Value::Double(None),
648        ColumnType::String(_) | ColumnType::Text | ColumnType::Char(_) => Value::String(None),
649        ColumnType::Binary(_) | ColumnType::VarBinary(_) => Value::Bytes(None),
650        ColumnType::Boolean => Value::Bool(None),
651        #[cfg(feature = "with-rust_decimal")]
652        ColumnType::Decimal(_) | ColumnType::Money(_) => Value::Decimal(None),
653        #[cfg(all(feature = "with-bigdecimal", not(feature = "with-rust_decimal")))]
654        ColumnType::Decimal(_) | ColumnType::Money(_) => Value::BigDecimal(None),
655        #[cfg(feature = "with-chrono")]
656        ColumnType::Date => Value::ChronoDate(None),
657        #[cfg(feature = "with-chrono")]
658        ColumnType::Time => Value::ChronoTime(None),
659        #[cfg(feature = "with-chrono")]
660        ColumnType::DateTime | ColumnType::Timestamp => Value::ChronoDateTime(None),
661        #[cfg(feature = "with-chrono")]
662        ColumnType::TimestampWithTimeZone => Value::ChronoDateTimeUtc(None),
663        #[cfg(all(feature = "with-time", not(feature = "with-chrono")))]
664        ColumnType::Date => Value::TimeDate(None),
665        #[cfg(all(feature = "with-time", not(feature = "with-chrono")))]
666        ColumnType::Time => Value::TimeTime(None),
667        #[cfg(all(feature = "with-time", not(feature = "with-chrono")))]
668        ColumnType::DateTime | ColumnType::Timestamp => Value::TimeDateTime(None),
669        #[cfg(all(feature = "with-time", not(feature = "with-chrono")))]
670        ColumnType::TimestampWithTimeZone => Value::TimeDateTimeWithTimeZone(None),
671        _ => Value::Int(None),
672    }
673}
674
675/// Null values for the time crate variants, used by the alt-value fallback path.
676#[cfg(all(feature = "with-chrono", feature = "with-time"))]
677fn null_value_for_type_time(col_type: &ColumnType) -> Value {
678    match col_type {
679        ColumnType::Date => Value::TimeDate(None),
680        ColumnType::Time => Value::TimeTime(None),
681        ColumnType::DateTime | ColumnType::Timestamp => Value::TimeDateTime(None),
682        ColumnType::TimestampWithTimeZone => Value::TimeDateTimeWithTimeZone(None),
683        _ => null_value_for_type(col_type),
684    }
685}
686
687// ---------------------------------------------------------------------------
688// Value -> Arrow
689// ---------------------------------------------------------------------------
690
691/// Convert a slice of [`Value`]s to an Arrow array matching the
692/// target [`DataType`](arrow::datatypes::DataType).
693///
694/// `Value::Variant(None)` (SQL NULL) entries become null in the array.
695pub fn values_to_arrow_array(
696    values: &[Value],
697    data_type: &arrow::datatypes::DataType,
698) -> Result<std::sync::Arc<dyn Array>, ArrowError> {
699    use arrow::datatypes::{DataType, TimeUnit};
700    use std::sync::Arc;
701
702    match data_type {
703        DataType::Int8 => {
704            let arr: Int8Array = values
705                .iter()
706                .map(|v| match v {
707                    Value::TinyInt(inner) => *inner,
708                    _ => None,
709                })
710                .collect();
711            Ok(Arc::new(arr))
712        }
713        DataType::Int16 => {
714            let arr: Int16Array = values
715                .iter()
716                .map(|v| match v {
717                    Value::SmallInt(inner) => *inner,
718                    _ => None,
719                })
720                .collect();
721            Ok(Arc::new(arr))
722        }
723        DataType::Int32 => {
724            let arr: Int32Array = values
725                .iter()
726                .map(|v| match v {
727                    Value::Int(inner) => *inner,
728                    _ => None,
729                })
730                .collect();
731            Ok(Arc::new(arr))
732        }
733        DataType::Int64 => {
734            let arr: Int64Array = values
735                .iter()
736                .map(|v| match v {
737                    Value::BigInt(inner) => *inner,
738                    _ => None,
739                })
740                .collect();
741            Ok(Arc::new(arr))
742        }
743        DataType::UInt8 => {
744            let arr: UInt8Array = values
745                .iter()
746                .map(|v| match v {
747                    Value::TinyUnsigned(inner) => *inner,
748                    _ => None,
749                })
750                .collect();
751            Ok(Arc::new(arr))
752        }
753        DataType::UInt16 => {
754            let arr: UInt16Array = values
755                .iter()
756                .map(|v| match v {
757                    Value::SmallUnsigned(inner) => *inner,
758                    _ => None,
759                })
760                .collect();
761            Ok(Arc::new(arr))
762        }
763        DataType::UInt32 => {
764            let arr: UInt32Array = values
765                .iter()
766                .map(|v| match v {
767                    Value::Unsigned(inner) => *inner,
768                    _ => None,
769                })
770                .collect();
771            Ok(Arc::new(arr))
772        }
773        DataType::UInt64 => {
774            let arr: UInt64Array = values
775                .iter()
776                .map(|v| match v {
777                    Value::BigUnsigned(inner) => *inner,
778                    _ => None,
779                })
780                .collect();
781            Ok(Arc::new(arr))
782        }
783        DataType::Float32 => {
784            let arr: Float32Array = values
785                .iter()
786                .map(|v| match v {
787                    Value::Float(inner) => *inner,
788                    _ => None,
789                })
790                .collect();
791            Ok(Arc::new(arr))
792        }
793        DataType::Float64 => {
794            let arr: Float64Array = values
795                .iter()
796                .map(|v| match v {
797                    Value::Double(inner) => *inner,
798                    _ => None,
799                })
800                .collect();
801            Ok(Arc::new(arr))
802        }
803        DataType::Boolean => {
804            let arr: BooleanArray = values
805                .iter()
806                .map(|v| match v {
807                    Value::Bool(inner) => *inner,
808                    _ => None,
809                })
810                .collect();
811            Ok(Arc::new(arr))
812        }
813        DataType::Utf8 => {
814            let strs: Vec<Option<&str>> = values
815                .iter()
816                .map(|v| match v {
817                    Value::String(Some(s)) => Some(s.as_str()),
818                    _ => None,
819                })
820                .collect();
821            Ok(Arc::new(StringArray::from(strs)))
822        }
823        DataType::LargeUtf8 => {
824            let strs: Vec<Option<&str>> = values
825                .iter()
826                .map(|v| match v {
827                    Value::String(Some(s)) => Some(s.as_str()),
828                    _ => None,
829                })
830                .collect();
831            Ok(Arc::new(LargeStringArray::from(strs)))
832        }
833        DataType::Binary => {
834            let bufs: Vec<Option<&[u8]>> = values
835                .iter()
836                .map(|v| match v {
837                    Value::Bytes(Some(b)) => Some(b.as_slice()),
838                    _ => None,
839                })
840                .collect();
841            Ok(Arc::new(BinaryArray::from(bufs)))
842        }
843        DataType::LargeBinary => {
844            let bufs: Vec<Option<&[u8]>> = values
845                .iter()
846                .map(|v| match v {
847                    Value::Bytes(Some(b)) => Some(b.as_slice()),
848                    _ => None,
849                })
850                .collect();
851            Ok(Arc::new(LargeBinaryArray::from(bufs)))
852        }
853        DataType::FixedSizeBinary(byte_width) => {
854            let mut builder = FixedSizeBinaryBuilder::with_capacity(values.len(), *byte_width);
855            for v in values {
856                match v {
857                    Value::Bytes(Some(b)) => builder.append_value(b.as_slice()).map_err(|e| {
858                        ArrowError::Unsupported(format!("FixedSizeBinary append error: {e}"))
859                    })?,
860                    _ => builder.append_null(),
861                }
862            }
863            Ok(Arc::new(builder.finish()))
864        }
865        DataType::Date32 => {
866            let arr: Date32Array = values.iter().map(extract_date32).collect();
867            Ok(Arc::new(arr))
868        }
869        DataType::Time32(unit) => {
870            let vals: Vec<Option<i32>> = values.iter().map(|v| extract_time32(v, unit)).collect();
871            let arr: Arc<dyn Array> = match unit {
872                TimeUnit::Second => Arc::new(Time32SecondArray::from(vals)),
873                TimeUnit::Millisecond => Arc::new(Time32MillisecondArray::from(vals)),
874                _ => {
875                    return Err(ArrowError::Unsupported(format!(
876                        "Unsupported Time32 unit: {unit:?}"
877                    )));
878                }
879            };
880            Ok(arr)
881        }
882        DataType::Time64(unit) => {
883            let vals: Vec<Option<i64>> = values.iter().map(|v| extract_time64(v, unit)).collect();
884            let arr: Arc<dyn Array> = match unit {
885                TimeUnit::Microsecond => Arc::new(Time64MicrosecondArray::from(vals)),
886                TimeUnit::Nanosecond => Arc::new(Time64NanosecondArray::from(vals)),
887                _ => {
888                    return Err(ArrowError::Unsupported(format!(
889                        "Unsupported Time64 unit: {unit:?}"
890                    )));
891                }
892            };
893            Ok(arr)
894        }
895        DataType::Timestamp(unit, tz) => {
896            let vals: Vec<Option<i64>> =
897                values.iter().map(|v| extract_timestamp(v, unit)).collect();
898            let arr: Arc<dyn Array> = match unit {
899                TimeUnit::Second => {
900                    let mut a = TimestampSecondArray::from(vals);
901                    if let Some(tz) = tz {
902                        a = a.with_timezone(tz.as_ref());
903                    }
904                    Arc::new(a)
905                }
906                TimeUnit::Millisecond => {
907                    let mut a = TimestampMillisecondArray::from(vals);
908                    if let Some(tz) = tz {
909                        a = a.with_timezone(tz.as_ref());
910                    }
911                    Arc::new(a)
912                }
913                TimeUnit::Microsecond => {
914                    let mut a = TimestampMicrosecondArray::from(vals);
915                    if let Some(tz) = tz {
916                        a = a.with_timezone(tz.as_ref());
917                    }
918                    Arc::new(a)
919                }
920                TimeUnit::Nanosecond => {
921                    let mut a = TimestampNanosecondArray::from(vals);
922                    if let Some(tz) = tz {
923                        a = a.with_timezone(tz.as_ref());
924                    }
925                    Arc::new(a)
926                }
927            };
928            Ok(arr)
929        }
930        DataType::Decimal64(precision, scale) => {
931            let arr: Decimal64Array = values
932                .iter()
933                .map(|v| extract_decimal64(v, *scale))
934                .collect();
935            let arr = arr
936                .with_precision_and_scale(*precision, *scale)
937                .map_err(|e| {
938                    ArrowError::Unsupported(format!("Invalid Decimal64 precision/scale: {e}"))
939                })?;
940            Ok(Arc::new(arr))
941        }
942        DataType::Decimal128(precision, scale) => {
943            let arr: Decimal128Array = values
944                .iter()
945                .map(|v| extract_decimal128(v, *scale))
946                .collect();
947            let arr = arr
948                .with_precision_and_scale(*precision, *scale)
949                .map_err(|e| {
950                    ArrowError::Unsupported(format!("Invalid Decimal128 precision/scale: {e}"))
951                })?;
952            Ok(Arc::new(arr))
953        }
954        DataType::Decimal256(precision, scale) => {
955            let arr: Decimal256Array = values
956                .iter()
957                .map(|v| extract_decimal256(v, *scale))
958                .collect();
959            let arr = arr
960                .with_precision_and_scale(*precision, *scale)
961                .map_err(|e| {
962                    ArrowError::Unsupported(format!("Invalid Decimal256 precision/scale: {e}"))
963                })?;
964            Ok(Arc::new(arr))
965        }
966        _ => Err(ArrowError::Unsupported(format!(
967            "Unsupported Arrow DataType for to_arrow: {data_type:?}"
968        ))),
969    }
970}
971
972/// Convert a slice of optional [`Value`]s to an Arrow array matching the
973/// target [`DataType`](arrow::datatypes::DataType).
974///
975/// `None` entries (from `ActiveValue::NotSet`) become null in the array.
976/// `Some(Value::Variant(None))` (SQL NULL) also become null.
977pub fn option_values_to_arrow_array(
978    values: &[Option<Value>],
979    data_type: &arrow::datatypes::DataType,
980) -> Result<std::sync::Arc<dyn Array>, ArrowError> {
981    use arrow::datatypes::{DataType, TimeUnit};
982    use std::sync::Arc;
983
984    match data_type {
985        DataType::Int8 => {
986            let arr: Int8Array = values
987                .iter()
988                .map(|v| match v {
989                    Some(Value::TinyInt(inner)) => *inner,
990                    _ => None,
991                })
992                .collect();
993            Ok(Arc::new(arr))
994        }
995        DataType::Int16 => {
996            let arr: Int16Array = values
997                .iter()
998                .map(|v| match v {
999                    Some(Value::SmallInt(inner)) => *inner,
1000                    _ => None,
1001                })
1002                .collect();
1003            Ok(Arc::new(arr))
1004        }
1005        DataType::Int32 => {
1006            let arr: Int32Array = values
1007                .iter()
1008                .map(|v| match v {
1009                    Some(Value::Int(inner)) => *inner,
1010                    _ => None,
1011                })
1012                .collect();
1013            Ok(Arc::new(arr))
1014        }
1015        DataType::Int64 => {
1016            let arr: Int64Array = values
1017                .iter()
1018                .map(|v| match v {
1019                    Some(Value::BigInt(inner)) => *inner,
1020                    _ => None,
1021                })
1022                .collect();
1023            Ok(Arc::new(arr))
1024        }
1025        DataType::UInt8 => {
1026            let arr: UInt8Array = values
1027                .iter()
1028                .map(|v| match v {
1029                    Some(Value::TinyUnsigned(inner)) => *inner,
1030                    _ => None,
1031                })
1032                .collect();
1033            Ok(Arc::new(arr))
1034        }
1035        DataType::UInt16 => {
1036            let arr: UInt16Array = values
1037                .iter()
1038                .map(|v| match v {
1039                    Some(Value::SmallUnsigned(inner)) => *inner,
1040                    _ => None,
1041                })
1042                .collect();
1043            Ok(Arc::new(arr))
1044        }
1045        DataType::UInt32 => {
1046            let arr: UInt32Array = values
1047                .iter()
1048                .map(|v| match v {
1049                    Some(Value::Unsigned(inner)) => *inner,
1050                    _ => None,
1051                })
1052                .collect();
1053            Ok(Arc::new(arr))
1054        }
1055        DataType::UInt64 => {
1056            let arr: UInt64Array = values
1057                .iter()
1058                .map(|v| match v {
1059                    Some(Value::BigUnsigned(inner)) => *inner,
1060                    _ => None,
1061                })
1062                .collect();
1063            Ok(Arc::new(arr))
1064        }
1065        DataType::Float32 => {
1066            let arr: Float32Array = values
1067                .iter()
1068                .map(|v| match v {
1069                    Some(Value::Float(inner)) => *inner,
1070                    _ => None,
1071                })
1072                .collect();
1073            Ok(Arc::new(arr))
1074        }
1075        DataType::Float64 => {
1076            let arr: Float64Array = values
1077                .iter()
1078                .map(|v| match v {
1079                    Some(Value::Double(inner)) => *inner,
1080                    _ => None,
1081                })
1082                .collect();
1083            Ok(Arc::new(arr))
1084        }
1085        DataType::Boolean => {
1086            let arr: BooleanArray = values
1087                .iter()
1088                .map(|v| match v {
1089                    Some(Value::Bool(inner)) => *inner,
1090                    _ => None,
1091                })
1092                .collect();
1093            Ok(Arc::new(arr))
1094        }
1095        DataType::Utf8 => {
1096            let strs: Vec<Option<&str>> = values
1097                .iter()
1098                .map(|v| match v {
1099                    Some(Value::String(Some(s))) => Some(s.as_str()),
1100                    _ => None,
1101                })
1102                .collect();
1103            Ok(Arc::new(StringArray::from(strs)))
1104        }
1105        DataType::LargeUtf8 => {
1106            let strs: Vec<Option<&str>> = values
1107                .iter()
1108                .map(|v| match v {
1109                    Some(Value::String(Some(s))) => Some(s.as_str()),
1110                    _ => None,
1111                })
1112                .collect();
1113            Ok(Arc::new(LargeStringArray::from(strs)))
1114        }
1115        DataType::Binary => {
1116            let bufs: Vec<Option<&[u8]>> = values
1117                .iter()
1118                .map(|v| match v {
1119                    Some(Value::Bytes(Some(b))) => Some(b.as_slice()),
1120                    _ => None,
1121                })
1122                .collect();
1123            Ok(Arc::new(BinaryArray::from(bufs)))
1124        }
1125        DataType::LargeBinary => {
1126            let bufs: Vec<Option<&[u8]>> = values
1127                .iter()
1128                .map(|v| match v {
1129                    Some(Value::Bytes(Some(b))) => Some(b.as_slice()),
1130                    _ => None,
1131                })
1132                .collect();
1133            Ok(Arc::new(LargeBinaryArray::from(bufs)))
1134        }
1135        DataType::FixedSizeBinary(byte_width) => {
1136            let mut builder = FixedSizeBinaryBuilder::with_capacity(values.len(), *byte_width);
1137            for v in values {
1138                match v {
1139                    Some(Value::Bytes(Some(b))) => {
1140                        builder.append_value(b.as_slice()).map_err(|e| {
1141                            ArrowError::Unsupported(format!("FixedSizeBinary append error: {e}"))
1142                        })?
1143                    }
1144                    _ => builder.append_null(),
1145                }
1146            }
1147            Ok(Arc::new(builder.finish()))
1148        }
1149        DataType::Date32 => {
1150            let arr: Date32Array = values.iter().map(extract_date32_option).collect();
1151            Ok(Arc::new(arr))
1152        }
1153        DataType::Time32(unit) => {
1154            let vals: Vec<Option<i32>> = values
1155                .iter()
1156                .map(|v| extract_time32_option(v, unit))
1157                .collect();
1158            let arr: Arc<dyn Array> = match unit {
1159                TimeUnit::Second => Arc::new(Time32SecondArray::from(vals)),
1160                TimeUnit::Millisecond => Arc::new(Time32MillisecondArray::from(vals)),
1161                _ => {
1162                    return Err(ArrowError::Unsupported(format!(
1163                        "Unsupported Time32 unit: {unit:?}"
1164                    )));
1165                }
1166            };
1167            Ok(arr)
1168        }
1169        DataType::Time64(unit) => {
1170            let vals: Vec<Option<i64>> = values
1171                .iter()
1172                .map(|v| extract_time64_option(v, unit))
1173                .collect();
1174            let arr: Arc<dyn Array> = match unit {
1175                TimeUnit::Microsecond => Arc::new(Time64MicrosecondArray::from(vals)),
1176                TimeUnit::Nanosecond => Arc::new(Time64NanosecondArray::from(vals)),
1177                _ => {
1178                    return Err(ArrowError::Unsupported(format!(
1179                        "Unsupported Time64 unit: {unit:?}"
1180                    )));
1181                }
1182            };
1183            Ok(arr)
1184        }
1185        DataType::Timestamp(unit, tz) => {
1186            let vals: Vec<Option<i64>> = values
1187                .iter()
1188                .map(|v| extract_timestamp_option(v, unit))
1189                .collect();
1190            let arr: Arc<dyn Array> = match unit {
1191                TimeUnit::Second => {
1192                    let mut a = TimestampSecondArray::from(vals);
1193                    if let Some(tz) = tz {
1194                        a = a.with_timezone(tz.as_ref());
1195                    }
1196                    Arc::new(a)
1197                }
1198                TimeUnit::Millisecond => {
1199                    let mut a = TimestampMillisecondArray::from(vals);
1200                    if let Some(tz) = tz {
1201                        a = a.with_timezone(tz.as_ref());
1202                    }
1203                    Arc::new(a)
1204                }
1205                TimeUnit::Microsecond => {
1206                    let mut a = TimestampMicrosecondArray::from(vals);
1207                    if let Some(tz) = tz {
1208                        a = a.with_timezone(tz.as_ref());
1209                    }
1210                    Arc::new(a)
1211                }
1212                TimeUnit::Nanosecond => {
1213                    let mut a = TimestampNanosecondArray::from(vals);
1214                    if let Some(tz) = tz {
1215                        a = a.with_timezone(tz.as_ref());
1216                    }
1217                    Arc::new(a)
1218                }
1219            };
1220            Ok(arr)
1221        }
1222        DataType::Decimal64(precision, scale) => {
1223            let arr: Decimal64Array = values
1224                .iter()
1225                .map(|v| extract_decimal64_option(v, *scale))
1226                .collect();
1227            let arr = arr
1228                .with_precision_and_scale(*precision, *scale)
1229                .map_err(|e| {
1230                    ArrowError::Unsupported(format!("Invalid Decimal64 precision/scale: {e}"))
1231                })?;
1232            Ok(Arc::new(arr))
1233        }
1234        DataType::Decimal128(precision, scale) => {
1235            let arr: Decimal128Array = values
1236                .iter()
1237                .map(|v| extract_decimal128_option(v, *scale))
1238                .collect();
1239            let arr = arr
1240                .with_precision_and_scale(*precision, *scale)
1241                .map_err(|e| {
1242                    ArrowError::Unsupported(format!("Invalid Decimal128 precision/scale: {e}"))
1243                })?;
1244            Ok(Arc::new(arr))
1245        }
1246        DataType::Decimal256(precision, scale) => {
1247            let arr: Decimal256Array = values
1248                .iter()
1249                .map(|v| extract_decimal256_option(v, *scale))
1250                .collect();
1251            let arr = arr
1252                .with_precision_and_scale(*precision, *scale)
1253                .map_err(|e| {
1254                    ArrowError::Unsupported(format!("Invalid Decimal256 precision/scale: {e}"))
1255                })?;
1256            Ok(Arc::new(arr))
1257        }
1258        _ => Err(ArrowError::Unsupported(format!(
1259            "Unsupported Arrow DataType for to_arrow: {data_type:?}"
1260        ))),
1261    }
1262}
1263
1264// ---------------------------------------------------------------------------
1265// Date extraction helpers
1266// ---------------------------------------------------------------------------
1267
1268fn extract_date32_option(v: &Option<Value>) -> Option<i32> {
1269    extract_date32(v.as_ref()?)
1270}
1271
1272fn extract_date32(v: &Value) -> Option<i32> {
1273    #[cfg(feature = "with-chrono")]
1274    if let Value::ChronoDate(Some(d)) = v {
1275        let epoch = sea_query::prelude::chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
1276        return Some((*d - epoch).num_days() as i32);
1277    }
1278    #[cfg(feature = "with-time")]
1279    if let Value::TimeDate(Some(d)) = v {
1280        return Some(d.to_julian_day() - 2_440_588);
1281    }
1282    let _ = v;
1283    None
1284}
1285
1286// ---------------------------------------------------------------------------
1287// Time extraction helpers
1288// ---------------------------------------------------------------------------
1289
1290fn extract_time32_option(v: &Option<Value>, unit: &arrow::datatypes::TimeUnit) -> Option<i32> {
1291    extract_time32(v.as_ref()?, unit)
1292}
1293
1294fn extract_time32(v: &Value, unit: &arrow::datatypes::TimeUnit) -> Option<i32> {
1295    #[cfg(any(feature = "with-chrono", feature = "with-time"))]
1296    use arrow::datatypes::TimeUnit;
1297
1298    #[cfg(feature = "with-chrono")]
1299    if let Value::ChronoTime(Some(t)) = v {
1300        use sea_query::prelude::chrono::Timelike;
1301        let secs = t.num_seconds_from_midnight() as i32;
1302        return match unit {
1303            TimeUnit::Second => Some(secs),
1304            TimeUnit::Millisecond => {
1305                let ms = (t.nanosecond() / 1_000_000) as i32;
1306                Some(secs * 1_000 + ms)
1307            }
1308            _ => None,
1309        };
1310    }
1311    #[cfg(feature = "with-time")]
1312    if let Value::TimeTime(Some(t)) = v {
1313        let secs = (t.hour() as i32) * 3600 + (t.minute() as i32) * 60 + (t.second() as i32);
1314        return match unit {
1315            TimeUnit::Second => Some(secs),
1316            TimeUnit::Millisecond => {
1317                let ms = (t.nanosecond() / 1_000_000) as i32;
1318                Some(secs * 1_000 + ms)
1319            }
1320            _ => None,
1321        };
1322    }
1323    let _ = (v, unit);
1324    None
1325}
1326
1327fn extract_time64_option(v: &Option<Value>, unit: &arrow::datatypes::TimeUnit) -> Option<i64> {
1328    extract_time64(v.as_ref()?, unit)
1329}
1330
1331fn extract_time64(v: &Value, unit: &arrow::datatypes::TimeUnit) -> Option<i64> {
1332    #[cfg(any(feature = "with-chrono", feature = "with-time"))]
1333    use arrow::datatypes::TimeUnit;
1334
1335    #[cfg(feature = "with-chrono")]
1336    if let Value::ChronoTime(Some(t)) = v {
1337        use sea_query::prelude::chrono::Timelike;
1338        let secs = t.num_seconds_from_midnight() as i64;
1339        let nanos = (t.nanosecond() % 1_000_000_000) as i64;
1340        return match unit {
1341            TimeUnit::Microsecond => Some(secs * 1_000_000 + nanos / 1_000),
1342            TimeUnit::Nanosecond => Some(secs * 1_000_000_000 + nanos),
1343            _ => None,
1344        };
1345    }
1346    #[cfg(feature = "with-time")]
1347    if let Value::TimeTime(Some(t)) = v {
1348        let secs = (t.hour() as i64) * 3600 + (t.minute() as i64) * 60 + (t.second() as i64);
1349        let nanos = t.nanosecond() as i64;
1350        return match unit {
1351            TimeUnit::Microsecond => Some(secs * 1_000_000 + nanos / 1_000),
1352            TimeUnit::Nanosecond => Some(secs * 1_000_000_000 + nanos),
1353            _ => None,
1354        };
1355    }
1356    let _ = (v, unit);
1357    None
1358}
1359
1360// ---------------------------------------------------------------------------
1361// Timestamp extraction helpers
1362// ---------------------------------------------------------------------------
1363
1364fn extract_timestamp_option(v: &Option<Value>, unit: &arrow::datatypes::TimeUnit) -> Option<i64> {
1365    extract_timestamp(v.as_ref()?, unit)
1366}
1367
1368fn extract_timestamp(v: &Value, unit: &arrow::datatypes::TimeUnit) -> Option<i64> {
1369    #[cfg(any(feature = "with-chrono", feature = "with-time"))]
1370    use arrow::datatypes::TimeUnit;
1371
1372    #[cfg(feature = "with-chrono")]
1373    {
1374        if let Value::ChronoDateTime(Some(dt)) = v {
1375            let utc = dt.and_utc();
1376            return Some(match unit {
1377                TimeUnit::Second => utc.timestamp(),
1378                TimeUnit::Millisecond => utc.timestamp_millis(),
1379                TimeUnit::Microsecond => utc.timestamp_micros(),
1380                TimeUnit::Nanosecond => utc.timestamp_nanos_opt().unwrap_or(0),
1381            });
1382        }
1383        if let Value::ChronoDateTimeUtc(Some(dt)) = v {
1384            return Some(match unit {
1385                TimeUnit::Second => dt.timestamp(),
1386                TimeUnit::Millisecond => dt.timestamp_millis(),
1387                TimeUnit::Microsecond => dt.timestamp_micros(),
1388                TimeUnit::Nanosecond => dt.timestamp_nanos_opt().unwrap_or(0),
1389            });
1390        }
1391    }
1392    #[cfg(feature = "with-time")]
1393    {
1394        if let Value::TimeDateTime(Some(dt)) = v {
1395            let odt = dt.assume_utc();
1396            return Some(offset_dt_to_timestamp(&odt, unit));
1397        }
1398        if let Value::TimeDateTimeWithTimeZone(Some(dt)) = v {
1399            return Some(offset_dt_to_timestamp(dt, unit));
1400        }
1401    }
1402    let _ = (v, unit);
1403    None
1404}
1405
1406#[cfg(feature = "with-time")]
1407fn offset_dt_to_timestamp(
1408    dt: &sea_query::prelude::time::OffsetDateTime,
1409    unit: &arrow::datatypes::TimeUnit,
1410) -> i64 {
1411    use arrow::datatypes::TimeUnit;
1412    match unit {
1413        TimeUnit::Second => dt.unix_timestamp(),
1414        TimeUnit::Millisecond => (dt.unix_timestamp_nanos() / 1_000_000) as i64,
1415        TimeUnit::Microsecond => (dt.unix_timestamp_nanos() / 1_000) as i64,
1416        TimeUnit::Nanosecond => dt.unix_timestamp_nanos() as i64,
1417    }
1418}
1419
1420// ---------------------------------------------------------------------------
1421// Decimal extraction helpers
1422// ---------------------------------------------------------------------------
1423
1424fn extract_decimal64_option(v: &Option<Value>, target_scale: i8) -> Option<i64> {
1425    extract_decimal64(v.as_ref()?, target_scale)
1426}
1427
1428fn extract_decimal64(v: &Value, target_scale: i8) -> Option<i64> {
1429    #[cfg(feature = "with-rust_decimal")]
1430    if let Value::Decimal(Some(d)) = v {
1431        let mantissa = d.mantissa();
1432        let current_scale = d.scale() as i8;
1433        let scale_diff = target_scale - current_scale;
1434        let scaled = if scale_diff >= 0 {
1435            mantissa * 10i128.pow(scale_diff as u32)
1436        } else {
1437            mantissa / 10i128.pow((-scale_diff) as u32)
1438        };
1439        return i64::try_from(scaled).ok();
1440    }
1441    #[cfg(feature = "with-bigdecimal")]
1442    if let Value::BigDecimal(Some(d)) = v {
1443        return bigdecimal_to_i64(d, target_scale);
1444    }
1445    let _ = (v, target_scale);
1446    None
1447}
1448
1449#[cfg(feature = "with-bigdecimal")]
1450fn bigdecimal_to_i64(
1451    d: &sea_query::prelude::bigdecimal::BigDecimal,
1452    target_scale: i8,
1453) -> Option<i64> {
1454    use sea_query::prelude::bigdecimal::ToPrimitive;
1455
1456    let rescaled = d.clone().with_scale(target_scale as i64);
1457    let (digits, _) = rescaled.into_bigint_and_exponent();
1458    digits.to_i64()
1459}
1460
1461fn extract_decimal128_option(v: &Option<Value>, target_scale: i8) -> Option<i128> {
1462    extract_decimal128(v.as_ref()?, target_scale)
1463}
1464
1465fn extract_decimal128(v: &Value, target_scale: i8) -> Option<i128> {
1466    #[cfg(feature = "with-rust_decimal")]
1467    if let Value::Decimal(Some(d)) = v {
1468        let mantissa = d.mantissa();
1469        let current_scale = d.scale() as i8;
1470        let scale_diff = target_scale - current_scale;
1471        return if scale_diff >= 0 {
1472            Some(mantissa * 10i128.pow(scale_diff as u32))
1473        } else {
1474            Some(mantissa / 10i128.pow((-scale_diff) as u32))
1475        };
1476    }
1477    #[cfg(feature = "with-bigdecimal")]
1478    if let Value::BigDecimal(Some(d)) = v {
1479        return bigdecimal_to_i128(d, target_scale);
1480    }
1481    let _ = (v, target_scale);
1482    None
1483}
1484
1485#[cfg(feature = "with-bigdecimal")]
1486fn bigdecimal_to_i128(
1487    d: &sea_query::prelude::bigdecimal::BigDecimal,
1488    target_scale: i8,
1489) -> Option<i128> {
1490    use sea_query::prelude::bigdecimal::ToPrimitive;
1491
1492    let rescaled = d.clone().with_scale(target_scale as i64);
1493    let (digits, _) = rescaled.into_bigint_and_exponent();
1494    digits.to_i128()
1495}
1496
1497fn extract_decimal256_option(v: &Option<Value>, target_scale: i8) -> Option<i256> {
1498    extract_decimal256(v.as_ref()?, target_scale)
1499}
1500
1501fn extract_decimal256(v: &Value, target_scale: i8) -> Option<i256> {
1502    #[cfg(feature = "with-bigdecimal")]
1503    if let Value::BigDecimal(Some(d)) = v {
1504        return bigdecimal_to_i256(d, target_scale);
1505    }
1506    #[cfg(feature = "with-rust_decimal")]
1507    if let Value::Decimal(Some(d)) = v {
1508        let mantissa = d.mantissa();
1509        let current_scale = d.scale() as i8;
1510        let scale_diff = target_scale - current_scale;
1511        let scaled = if scale_diff >= 0 {
1512            mantissa * 10i128.pow(scale_diff as u32)
1513        } else {
1514            mantissa / 10i128.pow((-scale_diff) as u32)
1515        };
1516        return Some(i256::from_i128(scaled));
1517    }
1518    let _ = (v, target_scale);
1519    None
1520}
1521
1522#[cfg(feature = "with-bigdecimal")]
1523fn bigdecimal_to_i256(
1524    d: &sea_query::prelude::bigdecimal::BigDecimal,
1525    target_scale: i8,
1526) -> Option<i256> {
1527    let rescaled = d.clone().with_scale(target_scale as i64);
1528    let (digits, _) = rescaled.into_bigint_and_exponent();
1529    bigint_to_i256(&digits)
1530}
1531
1532#[cfg(feature = "with-bigdecimal")]
1533fn bigint_to_i256(bi: &sea_query::prelude::bigdecimal::num_bigint::BigInt) -> Option<i256> {
1534    use sea_query::prelude::bigdecimal::num_bigint::Sign;
1535
1536    let (sign, bytes) = bi.to_bytes_be();
1537    if bytes.len() > 32 {
1538        return None;
1539    }
1540
1541    let mut buf = [0u8; 32];
1542    let start = 32 - bytes.len();
1543    buf[start..].copy_from_slice(&bytes);
1544
1545    let val = i256::from_be_bytes(buf);
1546    match sign {
1547        Sign::Minus => Some(val.wrapping_neg()),
1548        _ => Some(val),
1549    }
1550}