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