Skip to main content

drizzle_sqlite/traits/
value.rs

1//! Value conversion traits for `SQLite` types
2//!
3//! This module provides the `FromSQLiteValue` trait for converting `SQLite` values
4//! to Rust types, and row capability traits for unified access across drivers.
5
6use crate::prelude::*;
7use crate::values::SQLiteValue;
8use drizzle_core::conv::checked_float_to_int;
9use drizzle_core::error::DrizzleError;
10
11/// Marker trait for custom Rust types that map to a `SQLite` column.
12///
13/// Generated by `#[derive(SQLiteEnum)]`. The table macro uses this trait to
14/// auto-detect enum types without requiring `#[column(enum)]`.
15///
16/// # Associated Constants
17///
18/// - `SQL_TYPE`: The `SQLite` column affinity (`"TEXT"` or `"INTEGER"`)
19///
20/// # Required Methods
21///
22/// - `to_sqlite_value`: Convert self to a `SQLiteValue` for insertion/updates
23#[diagnostic::on_unimplemented(
24    message = "`{Self}` cannot be used as a SQLite column type",
25    note = "add #[derive(SQLiteEnum)] for enum types, or use a supported primitive type"
26)]
27pub trait DrizzleSQLiteColumn: FromSQLiteValue + Sized {
28    /// `SQLite` column affinity: `"TEXT"` or `"INTEGER"`
29    const SQL_TYPE: &'static str;
30
31    /// Convert self to a `SQLiteValue` for insertion/updates.
32    fn to_sqlite_value(&self) -> SQLiteValue<'static>;
33}
34
35/// Trait for types that can be converted from `SQLite` values.
36///
37/// `SQLite` has 5 storage classes: NULL, INTEGER, REAL, TEXT, BLOB.
38/// This trait provides conversion methods for each type.
39///
40/// # Implementation Notes
41///
42/// - Implement the methods that make sense for your type
43/// - Return `Err` for unsupported conversions
44/// - `SQLiteEnum` derive automatically implements this trait
45pub trait FromSQLiteValue: Sized {
46    /// Convert from a 64-bit integer value.
47    ///
48    /// # Errors
49    ///
50    /// Returns [`DrizzleError::ConversionError`] if `value` cannot be represented as `Self`.
51    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError>;
52
53    /// Convert from a text/string value.
54    ///
55    /// # Errors
56    ///
57    /// Returns [`DrizzleError::ConversionError`] if `value` cannot be parsed or represented as `Self`.
58    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError>;
59
60    /// Convert from a real/float value.
61    ///
62    /// # Errors
63    ///
64    /// Returns [`DrizzleError::ConversionError`] if `value` cannot be represented as `Self`.
65    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError>;
66
67    /// Convert from a blob/binary value.
68    ///
69    /// # Errors
70    ///
71    /// Returns [`DrizzleError::ConversionError`] if `value` cannot be interpreted as `Self`.
72    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError>;
73
74    /// Convert from a NULL value (default returns error).
75    ///
76    /// # Errors
77    ///
78    /// The default implementation always returns [`DrizzleError::ConversionError`];
79    /// override for nullable-aware types (e.g. [`Option`]).
80    fn from_sqlite_null() -> Result<Self, DrizzleError> {
81        Err(DrizzleError::ConversionError(
82            "unexpected NULL value".into(),
83        ))
84    }
85
86    /// Helper function to convert from rusqlite's `ValueRef` using `FromSQLiteValue`.
87    ///
88    /// # Errors
89    ///
90    /// Returns [`DrizzleError::ConversionError`] if the underlying conversion fails,
91    /// including UTF-8 decoding failures for TEXT values.
92    #[cfg(feature = "rusqlite")]
93    fn from_value_ref(value: ::rusqlite::types::ValueRef<'_>) -> Result<Self, DrizzleError> {
94        match value {
95            ::rusqlite::types::ValueRef::Null => Self::from_sqlite_null(),
96            ::rusqlite::types::ValueRef::Integer(i) => Self::from_sqlite_integer(i),
97            ::rusqlite::types::ValueRef::Real(r) => Self::from_sqlite_real(r),
98            ::rusqlite::types::ValueRef::Text(text) => {
99                let s = core::str::from_utf8(text).map_err(|e| {
100                    DrizzleError::ConversionError(format!("invalid UTF-8: {e}").into())
101                })?;
102                Self::from_sqlite_text(s)
103            }
104            ::rusqlite::types::ValueRef::Blob(blob) => Self::from_sqlite_blob(blob),
105        }
106    }
107}
108
109/// Row capability for index-based extraction.
110pub trait DrizzleRowByIndex {
111    /// Get a column value by index.
112    ///
113    /// # Errors
114    ///
115    /// Returns [`DrizzleError`] if the index is out of range or the column value
116    /// cannot be converted to `T`.
117    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError>;
118}
119
120/// Optional row capability for name-based extraction.
121pub trait DrizzleRowByName: DrizzleRowByIndex {
122    /// Get a column value by name.
123    ///
124    /// # Errors
125    ///
126    /// Returns [`DrizzleError`] if the name does not resolve to a column or the
127    /// column value cannot be converted to `T`.
128    fn get_column_by_name<T: FromSQLiteValue>(&self, name: &str) -> Result<T, DrizzleError>;
129}
130
131// =============================================================================
132// Primitive implementations
133// =============================================================================
134
135/// Macro to implement `FromSQLiteValue` for integer types (handles narrowing conversion from i64)
136macro_rules! impl_from_sqlite_value_int {
137    // Special case for i64 - no conversion needed
138    (i64) => {
139        impl FromSQLiteValue for i64 {
140            fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
141                Ok(value)
142            }
143
144            fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
145                value.parse().map_err(|e| {
146                    DrizzleError::ConversionError(format!("cannot parse '{}' as i64: {}", value, e).into())
147                })
148            }
149
150            fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
151                checked_float_to_int(value, "i64")
152            }
153
154            fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
155                Err(DrizzleError::ConversionError("cannot convert BLOB to i64".into()))
156            }
157        }
158    };
159    // General case for other integer types - uses try_into for narrowing
160    ($($ty:ty),+ $(,)?) => {
161        $(
162            impl FromSQLiteValue for $ty {
163                fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
164                    value.try_into().map_err(|e| {
165                        DrizzleError::ConversionError(
166                            format!("i64 {} out of range for {}: {}", value, stringify!($ty), e).into(),
167                        )
168                    })
169                }
170
171                fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
172                    value.parse().map_err(|e| {
173                        DrizzleError::ConversionError(
174                            format!("cannot parse '{}' as {}: {}", value, stringify!($ty), e).into()
175                        )
176                    })
177                }
178
179                fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
180                    checked_float_to_int(value, stringify!($ty))
181                }
182
183                fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
184                    Err(DrizzleError::ConversionError(
185                        concat!("cannot convert BLOB to ", stringify!($ty)).into()
186                    ))
187                }
188            }
189        )+
190    };
191}
192
193/// IEEE 754 `f64` representation of an `i64` built from two exact halves via
194/// [`From`], matching the semantics of `i as f64` without an `as`-cast.
195#[inline]
196fn i64_to_f64(i: i64) -> f64 {
197    let high = i32::try_from(i >> 32).expect("sign-extended high word fits in i32");
198    let low = u32::try_from(i & 0xFFFF_FFFF).expect("masked low word fits in u32");
199    f64::from(high) * 4_294_967_296.0_f64 + f64::from(low)
200}
201
202// Integer types
203impl_from_sqlite_value_int!(i64);
204impl_from_sqlite_value_int!(i8, i16, i32, isize, u8, u16, u32, u64, usize);
205
206// f64 — `i64` widening via split-halves is exact in the mantissa range and
207// matches direct `as` cast beyond it; no cast is needed for f64→f64.
208impl FromSQLiteValue for f64 {
209    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
210        Ok(i64_to_f64(value))
211    }
212
213    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
214        value.parse().map_err(|e| {
215            DrizzleError::ConversionError(format!("cannot parse '{value}' as f64: {e}").into())
216        })
217    }
218
219    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
220        Ok(value)
221    }
222
223    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
224        Err(DrizzleError::ConversionError(
225            "cannot convert BLOB to f64".into(),
226        ))
227    }
228}
229
230fn narrow_to_f32(value: f64) -> Result<f32, DrizzleError> {
231    // Round-trip through a decimal string so IEEE 754 round-to-nearest is
232    // applied explicitly (and clippy doesn't flag the conversion as a lossy
233    // numeric cast).
234    format!("{value}").parse::<f32>().map_err(|e| {
235        DrizzleError::ConversionError(format!("cannot convert REAL {value} to f32: {e}").into())
236    })
237}
238
239// f32 — narrowing from both `i64` and `f64` is intentionally lossy; SQLite
240// round-trips the value through `f64` storage so we defer to the standard
241// IEEE 754 narrowing conversion. These casts are the only correct semantics.
242impl FromSQLiteValue for f32 {
243    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
244        // Go via f64 (exact) then narrow to f32.
245        narrow_to_f32(i64_to_f64(value))
246    }
247
248    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
249        value.parse().map_err(|e| {
250            DrizzleError::ConversionError(format!("cannot parse '{value}' as f32: {e}").into())
251        })
252    }
253
254    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
255        narrow_to_f32(value)
256    }
257
258    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
259        Err(DrizzleError::ConversionError(
260            "cannot convert BLOB to f32".into(),
261        ))
262    }
263}
264
265impl FromSQLiteValue for bool {
266    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
267        Ok(value != 0)
268    }
269
270    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
271        match value.to_lowercase().as_str() {
272            "true" | "1" | "yes" | "on" => Ok(true),
273            "false" | "0" | "no" | "off" => Ok(false),
274            _ => Err(DrizzleError::ConversionError(
275                format!("cannot parse '{value}' as bool").into(),
276            )),
277        }
278    }
279
280    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
281        Ok(value != 0.0)
282    }
283
284    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
285        Err(DrizzleError::ConversionError(
286            "cannot convert BLOB to bool".into(),
287        ))
288    }
289}
290
291impl FromSQLiteValue for String {
292    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
293        Ok(value.to_string())
294    }
295
296    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
297        Ok(value.to_string())
298    }
299
300    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
301        Ok(value.to_string())
302    }
303
304    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
305        Self::from_utf8(value.to_vec()).map_err(|e| {
306            DrizzleError::ConversionError(format!("invalid UTF-8 in BLOB: {e}").into())
307        })
308    }
309}
310
311impl FromSQLiteValue for compact_str::CompactString {
312    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
313        Ok(Self::new(value.to_string()))
314    }
315
316    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
317        Ok(Self::new(value))
318    }
319
320    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
321        Ok(Self::new(value.to_string()))
322    }
323
324    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
325        let s = String::from_utf8(value.to_vec()).map_err(|e| {
326            DrizzleError::ConversionError(format!("invalid UTF-8 in BLOB: {e}").into())
327        })?;
328        Ok(Self::new(s))
329    }
330}
331
332impl FromSQLiteValue for Box<String> {
333    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
334        String::from_sqlite_integer(value).map(Self::new)
335    }
336
337    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
338        String::from_sqlite_text(value).map(Self::new)
339    }
340
341    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
342        String::from_sqlite_real(value).map(Self::new)
343    }
344
345    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
346        String::from_sqlite_blob(value).map(Self::new)
347    }
348}
349
350impl FromSQLiteValue for Rc<String> {
351    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
352        String::from_sqlite_integer(value).map(Self::new)
353    }
354
355    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
356        String::from_sqlite_text(value).map(Self::new)
357    }
358
359    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
360        String::from_sqlite_real(value).map(Self::new)
361    }
362
363    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
364        String::from_sqlite_blob(value).map(Self::new)
365    }
366}
367
368impl FromSQLiteValue for Arc<String> {
369    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
370        String::from_sqlite_integer(value).map(Self::new)
371    }
372
373    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
374        String::from_sqlite_text(value).map(Self::new)
375    }
376
377    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
378        String::from_sqlite_real(value).map(Self::new)
379    }
380
381    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
382        String::from_sqlite_blob(value).map(Self::new)
383    }
384}
385
386impl FromSQLiteValue for Box<str> {
387    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
388        String::from_sqlite_integer(value).map(String::into_boxed_str)
389    }
390
391    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
392        String::from_sqlite_text(value).map(String::into_boxed_str)
393    }
394
395    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
396        String::from_sqlite_real(value).map(String::into_boxed_str)
397    }
398
399    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
400        String::from_sqlite_blob(value).map(String::into_boxed_str)
401    }
402}
403
404impl FromSQLiteValue for Rc<str> {
405    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
406        String::from_sqlite_integer(value).map(Self::from)
407    }
408
409    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
410        String::from_sqlite_text(value).map(Self::from)
411    }
412
413    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
414        String::from_sqlite_real(value).map(Self::from)
415    }
416
417    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
418        String::from_sqlite_blob(value).map(Self::from)
419    }
420}
421
422impl FromSQLiteValue for Arc<str> {
423    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
424        String::from_sqlite_integer(value).map(Self::from)
425    }
426
427    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
428        String::from_sqlite_text(value).map(Self::from)
429    }
430
431    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
432        String::from_sqlite_real(value).map(Self::from)
433    }
434
435    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
436        String::from_sqlite_blob(value).map(Self::from)
437    }
438}
439
440impl FromSQLiteValue for Vec<u8> {
441    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
442        Ok(value.to_le_bytes().to_vec())
443    }
444
445    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
446        Ok(value.as_bytes().to_vec())
447    }
448
449    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
450        Ok(value.to_le_bytes().to_vec())
451    }
452
453    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
454        Ok(value.to_vec())
455    }
456}
457
458#[cfg(feature = "bytes")]
459impl FromSQLiteValue for bytes::Bytes {
460    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
461        Vec::<u8>::from_sqlite_integer(value).map(Self::from)
462    }
463
464    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
465        Vec::<u8>::from_sqlite_text(value).map(Self::from)
466    }
467
468    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
469        Vec::<u8>::from_sqlite_real(value).map(Self::from)
470    }
471
472    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
473        Ok(Self::copy_from_slice(value))
474    }
475}
476
477#[cfg(feature = "bytes")]
478impl FromSQLiteValue for bytes::BytesMut {
479    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
480        Vec::<u8>::from_sqlite_integer(value).map(|v| Self::from(v.as_slice()))
481    }
482
483    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
484        Vec::<u8>::from_sqlite_text(value).map(|v| Self::from(v.as_slice()))
485    }
486
487    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
488        Vec::<u8>::from_sqlite_real(value).map(|v| Self::from(v.as_slice()))
489    }
490
491    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
492        Ok(Self::from(value))
493    }
494}
495
496#[cfg(feature = "smallvec")]
497impl<const N: usize> FromSQLiteValue for smallvec::SmallVec<[u8; N]> {
498    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
499        Err(DrizzleError::ConversionError(
500            "cannot convert INTEGER to SmallVec<u8>, use BLOB".into(),
501        ))
502    }
503
504    fn from_sqlite_text(_value: &str) -> Result<Self, DrizzleError> {
505        Err(DrizzleError::ConversionError(
506            "cannot convert TEXT to SmallVec<u8>, use BLOB".into(),
507        ))
508    }
509
510    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
511        Err(DrizzleError::ConversionError(
512            "cannot convert REAL to SmallVec<u8>, use BLOB".into(),
513        ))
514    }
515
516    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
517        let mut out = Self::new();
518        out.extend_from_slice(value);
519        Ok(out)
520    }
521}
522
523impl FromSQLiteValue for Box<Vec<u8>> {
524    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
525        Vec::<u8>::from_sqlite_integer(value).map(Self::new)
526    }
527
528    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
529        Vec::<u8>::from_sqlite_text(value).map(Self::new)
530    }
531
532    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
533        Vec::<u8>::from_sqlite_real(value).map(Self::new)
534    }
535
536    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
537        Vec::<u8>::from_sqlite_blob(value).map(Self::new)
538    }
539}
540
541impl FromSQLiteValue for Rc<Vec<u8>> {
542    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
543        Vec::<u8>::from_sqlite_integer(value).map(Self::new)
544    }
545
546    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
547        Vec::<u8>::from_sqlite_text(value).map(Self::new)
548    }
549
550    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
551        Vec::<u8>::from_sqlite_real(value).map(Self::new)
552    }
553
554    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
555        Vec::<u8>::from_sqlite_blob(value).map(Self::new)
556    }
557}
558
559impl FromSQLiteValue for Arc<Vec<u8>> {
560    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
561        Vec::<u8>::from_sqlite_integer(value).map(Self::new)
562    }
563
564    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
565        Vec::<u8>::from_sqlite_text(value).map(Self::new)
566    }
567
568    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
569        Vec::<u8>::from_sqlite_real(value).map(Self::new)
570    }
571
572    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
573        Vec::<u8>::from_sqlite_blob(value).map(Self::new)
574    }
575}
576
577// Option<T> implementation - handles NULL values
578impl<T: FromSQLiteValue> FromSQLiteValue for Option<T> {
579    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
580        T::from_sqlite_integer(value).map(Some)
581    }
582
583    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
584        T::from_sqlite_text(value).map(Some)
585    }
586
587    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
588        T::from_sqlite_real(value).map(Some)
589    }
590
591    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
592        T::from_sqlite_blob(value).map(Some)
593    }
594
595    fn from_sqlite_null() -> Result<Self, DrizzleError> {
596        Ok(None)
597    }
598}
599
600// =============================================================================
601// Driver-specific DrizzleRow implementations
602// =============================================================================
603
604#[cfg(feature = "rusqlite")]
605impl DrizzleRowByIndex for rusqlite::Row<'_> {
606    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError> {
607        let value_ref = self.get_ref(idx)?;
608        match value_ref {
609            rusqlite::types::ValueRef::Integer(i) => T::from_sqlite_integer(i),
610            rusqlite::types::ValueRef::Text(s) => {
611                let s = core::str::from_utf8(s).map_err(|e| {
612                    DrizzleError::ConversionError(format!("invalid UTF-8: {e}").into())
613                })?;
614                T::from_sqlite_text(s)
615            }
616            rusqlite::types::ValueRef::Real(r) => T::from_sqlite_real(r),
617            rusqlite::types::ValueRef::Blob(b) => T::from_sqlite_blob(b),
618            rusqlite::types::ValueRef::Null => T::from_sqlite_null(),
619        }
620    }
621}
622
623#[cfg(feature = "rusqlite")]
624impl DrizzleRowByName for rusqlite::Row<'_> {
625    fn get_column_by_name<T: FromSQLiteValue>(&self, name: &str) -> Result<T, DrizzleError> {
626        let idx = self.as_ref().column_index(name)?;
627        DrizzleRowByIndex::get_column(self, idx)
628    }
629}
630
631#[cfg(feature = "libsql")]
632impl DrizzleRowByIndex for libsql::Row {
633    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError> {
634        let idx_i32 = i32::try_from(idx).map_err(|e| {
635            DrizzleError::ConversionError(format!("column index {idx} out of range: {e}").into())
636        })?;
637        let value = self.get_value(idx_i32)?;
638        match value {
639            libsql::Value::Integer(i) => T::from_sqlite_integer(i),
640            libsql::Value::Text(ref s) => T::from_sqlite_text(s),
641            libsql::Value::Real(r) => T::from_sqlite_real(r),
642            libsql::Value::Blob(ref b) => T::from_sqlite_blob(b),
643            libsql::Value::Null => T::from_sqlite_null(),
644        }
645    }
646}
647
648#[cfg(feature = "libsql")]
649impl DrizzleRowByName for libsql::Row {
650    fn get_column_by_name<T: FromSQLiteValue>(&self, name: &str) -> Result<T, DrizzleError> {
651        let idx = (0..self.column_count())
652            .find(|&i| self.column_name(i) == Some(name))
653            .ok_or_else(|| {
654                DrizzleError::ConversionError(format!("column '{name}' not found").into())
655            })?;
656
657        let idx_usize = usize::try_from(idx).map_err(|e| {
658            DrizzleError::ConversionError(format!("column index {idx} negative: {e}").into())
659        })?;
660        DrizzleRowByIndex::get_column(self, idx_usize)
661    }
662}
663
664#[cfg(feature = "turso")]
665impl DrizzleRowByIndex for turso::Row {
666    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError> {
667        let value = self.get_value(idx)?;
668        if value.is_null() {
669            T::from_sqlite_null()
670        } else if let Some(&i) = value.as_integer() {
671            T::from_sqlite_integer(i)
672        } else if let Some(s) = value.as_text() {
673            T::from_sqlite_text(s)
674        } else if let Some(&r) = value.as_real() {
675            T::from_sqlite_real(r)
676        } else if let Some(b) = value.as_blob() {
677            T::from_sqlite_blob(b)
678        } else {
679            Err(DrizzleError::ConversionError(
680                "unknown SQLite value type".into(),
681            ))
682        }
683    }
684}
685
686// =============================================================================
687// UUID support (when feature enabled)
688// =============================================================================
689
690#[cfg(feature = "uuid")]
691impl FromSQLiteValue for uuid::Uuid {
692    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
693        Err(DrizzleError::ConversionError(
694            "cannot convert INTEGER to UUID".into(),
695        ))
696    }
697
698    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
699        Self::parse_str(value).map_err(|e| {
700            DrizzleError::ConversionError(format!("invalid UUID string '{value}': {e}").into())
701        })
702    }
703
704    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
705        Err(DrizzleError::ConversionError(
706            "cannot convert REAL to UUID".into(),
707        ))
708    }
709
710    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
711        Self::from_slice(value)
712            .map_err(|e| DrizzleError::ConversionError(format!("invalid UUID bytes: {e}").into()))
713    }
714}
715
716// =============================================================================
717// Chrono date/time types (parse from ISO-8601 text)
718// =============================================================================
719
720#[cfg(feature = "chrono")]
721impl FromSQLiteValue for chrono::NaiveDate {
722    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
723        Err(DrizzleError::ConversionError(
724            "cannot convert INTEGER to NaiveDate".into(),
725        ))
726    }
727
728    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
729        value.parse().map_err(|e| {
730            DrizzleError::ConversionError(
731                format!("cannot parse '{value}' as NaiveDate: {e}").into(),
732            )
733        })
734    }
735
736    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
737        Err(DrizzleError::ConversionError(
738            "cannot convert REAL to NaiveDate".into(),
739        ))
740    }
741
742    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
743        Err(DrizzleError::ConversionError(
744            "cannot convert BLOB to NaiveDate".into(),
745        ))
746    }
747}
748
749#[cfg(feature = "chrono")]
750impl FromSQLiteValue for chrono::NaiveTime {
751    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
752        Err(DrizzleError::ConversionError(
753            "cannot convert INTEGER to NaiveTime".into(),
754        ))
755    }
756
757    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
758        value.parse().map_err(|e| {
759            DrizzleError::ConversionError(
760                format!("cannot parse '{value}' as NaiveTime: {e}").into(),
761            )
762        })
763    }
764
765    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
766        Err(DrizzleError::ConversionError(
767            "cannot convert REAL to NaiveTime".into(),
768        ))
769    }
770
771    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
772        Err(DrizzleError::ConversionError(
773            "cannot convert BLOB to NaiveTime".into(),
774        ))
775    }
776}
777
778#[cfg(feature = "chrono")]
779impl FromSQLiteValue for chrono::NaiveDateTime {
780    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
781        Err(DrizzleError::ConversionError(
782            "cannot convert INTEGER to NaiveDateTime".into(),
783        ))
784    }
785
786    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
787        value.parse().map_err(|e| {
788            DrizzleError::ConversionError(
789                format!("cannot parse '{value}' as NaiveDateTime: {e}").into(),
790            )
791        })
792    }
793
794    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
795        Err(DrizzleError::ConversionError(
796            "cannot convert REAL to NaiveDateTime".into(),
797        ))
798    }
799
800    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
801        Err(DrizzleError::ConversionError(
802            "cannot convert BLOB to NaiveDateTime".into(),
803        ))
804    }
805}
806
807#[cfg(feature = "chrono")]
808impl FromSQLiteValue for chrono::DateTime<chrono::FixedOffset> {
809    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
810        Err(DrizzleError::ConversionError(
811            "cannot convert INTEGER to DateTime<FixedOffset>".into(),
812        ))
813    }
814
815    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
816        Self::parse_from_rfc3339(value).map_err(|e| {
817            DrizzleError::ConversionError(
818                format!("cannot parse '{value}' as DateTime<FixedOffset>: {e}").into(),
819            )
820        })
821    }
822
823    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
824        Err(DrizzleError::ConversionError(
825            "cannot convert REAL to DateTime<FixedOffset>".into(),
826        ))
827    }
828
829    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
830        Err(DrizzleError::ConversionError(
831            "cannot convert BLOB to DateTime<FixedOffset>".into(),
832        ))
833    }
834}
835
836#[cfg(feature = "chrono")]
837impl FromSQLiteValue for chrono::DateTime<chrono::Utc> {
838    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
839        Err(DrizzleError::ConversionError(
840            "cannot convert INTEGER to DateTime<Utc>".into(),
841        ))
842    }
843
844    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
845        value.parse().map_err(|e| {
846            DrizzleError::ConversionError(
847                format!("cannot parse '{value}' as DateTime<Utc>: {e}").into(),
848            )
849        })
850    }
851
852    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
853        Err(DrizzleError::ConversionError(
854            "cannot convert REAL to DateTime<Utc>".into(),
855        ))
856    }
857
858    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
859        Err(DrizzleError::ConversionError(
860            "cannot convert BLOB to DateTime<Utc>".into(),
861        ))
862    }
863}
864
865// =============================================================================
866// Time crate date/time types (parse from ISO-8601 text)
867// =============================================================================
868
869#[cfg(feature = "time")]
870impl FromSQLiteValue for time::Date {
871    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
872        Err(DrizzleError::ConversionError(
873            "cannot convert INTEGER to time::Date".into(),
874        ))
875    }
876
877    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
878        Self::parse(value, &time::format_description::well_known::Iso8601::DATE).map_err(|e| {
879            DrizzleError::ConversionError(
880                format!("cannot parse '{value}' as time::Date: {e}").into(),
881            )
882        })
883    }
884
885    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
886        Err(DrizzleError::ConversionError(
887            "cannot convert REAL to time::Date".into(),
888        ))
889    }
890
891    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
892        Err(DrizzleError::ConversionError(
893            "cannot convert BLOB to time::Date".into(),
894        ))
895    }
896}
897
898#[cfg(feature = "time")]
899impl FromSQLiteValue for time::Time {
900    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
901        Err(DrizzleError::ConversionError(
902            "cannot convert INTEGER to time::Time".into(),
903        ))
904    }
905
906    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
907        Self::parse(value, &time::format_description::well_known::Iso8601::TIME).map_err(|e| {
908            DrizzleError::ConversionError(
909                format!("cannot parse '{value}' as time::Time: {e}").into(),
910            )
911        })
912    }
913
914    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
915        Err(DrizzleError::ConversionError(
916            "cannot convert REAL to time::Time".into(),
917        ))
918    }
919
920    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
921        Err(DrizzleError::ConversionError(
922            "cannot convert BLOB to time::Time".into(),
923        ))
924    }
925}
926
927#[cfg(feature = "time")]
928impl FromSQLiteValue for time::PrimitiveDateTime {
929    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
930        Err(DrizzleError::ConversionError(
931            "cannot convert INTEGER to time::PrimitiveDateTime".into(),
932        ))
933    }
934
935    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
936        Self::parse(
937            value,
938            &time::format_description::well_known::Iso8601::DATE_TIME,
939        )
940        .map_err(|e| {
941            DrizzleError::ConversionError(
942                format!("cannot parse '{value}' as time::PrimitiveDateTime: {e}").into(),
943            )
944        })
945    }
946
947    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
948        Err(DrizzleError::ConversionError(
949            "cannot convert REAL to time::PrimitiveDateTime".into(),
950        ))
951    }
952
953    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
954        Err(DrizzleError::ConversionError(
955            "cannot convert BLOB to time::PrimitiveDateTime".into(),
956        ))
957    }
958}
959
960#[cfg(feature = "time")]
961impl FromSQLiteValue for time::OffsetDateTime {
962    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
963        Err(DrizzleError::ConversionError(
964            "cannot convert INTEGER to time::OffsetDateTime".into(),
965        ))
966    }
967
968    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
969        Self::parse(value, &time::format_description::well_known::Rfc3339).map_err(|e| {
970            DrizzleError::ConversionError(
971                format!("cannot parse '{value}' as time::OffsetDateTime: {e}").into(),
972            )
973        })
974    }
975
976    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
977        Err(DrizzleError::ConversionError(
978            "cannot convert REAL to time::OffsetDateTime".into(),
979        ))
980    }
981
982    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
983        Err(DrizzleError::ConversionError(
984            "cannot convert BLOB to time::OffsetDateTime".into(),
985        ))
986    }
987}
988
989// =============================================================================
990// Decimal (parse from text)
991// =============================================================================
992
993#[cfg(feature = "rust-decimal")]
994impl FromSQLiteValue for rust_decimal::Decimal {
995    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
996        Ok(Self::from(value))
997    }
998
999    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
1000        value.parse().map_err(|e| {
1001            DrizzleError::ConversionError(format!("cannot parse '{value}' as Decimal: {e}").into())
1002        })
1003    }
1004
1005    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
1006        Self::try_from(value).map_err(|e| {
1007            DrizzleError::ConversionError(
1008                format!("cannot convert REAL {value} to Decimal: {e}").into(),
1009            )
1010        })
1011    }
1012
1013    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
1014        Err(DrizzleError::ConversionError(
1015            "cannot convert BLOB to Decimal".into(),
1016        ))
1017    }
1018}
1019
1020// =============================================================================
1021// Duration types
1022// =============================================================================
1023
1024#[cfg(feature = "chrono")]
1025impl FromSQLiteValue for chrono::Duration {
1026    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
1027        Ok(Self::seconds(value))
1028    }
1029
1030    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
1031        // Parse seconds from the text representation
1032        let secs: i64 = value.trim_end_matches('s').parse().map_err(|e| {
1033            DrizzleError::ConversionError(
1034                format!("cannot parse '{value}' as chrono::Duration: {e}").into(),
1035            )
1036        })?;
1037        Ok(Self::seconds(secs))
1038    }
1039
1040    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
1041        let millis: i64 = format!("{:.0}", value * 1000.0).parse().map_err(|e| {
1042            DrizzleError::ConversionError(
1043                format!("cannot convert REAL {value} to chrono::Duration: {e}").into(),
1044            )
1045        })?;
1046        Ok(Self::milliseconds(millis))
1047    }
1048
1049    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
1050        Err(DrizzleError::ConversionError(
1051            "cannot convert BLOB to chrono::Duration".into(),
1052        ))
1053    }
1054}
1055
1056#[cfg(feature = "time")]
1057impl FromSQLiteValue for time::Duration {
1058    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
1059        Ok(Self::seconds(value))
1060    }
1061
1062    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
1063        // Parse seconds from the "Ns" text representation
1064        let secs: i64 = value.trim_end_matches('s').parse().map_err(|e| {
1065            DrizzleError::ConversionError(
1066                format!("cannot parse '{value}' as time::Duration: {e}").into(),
1067            )
1068        })?;
1069        Ok(Self::seconds(secs))
1070    }
1071
1072    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
1073        Ok(Self::seconds_f64(value))
1074    }
1075
1076    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
1077        Err(DrizzleError::ConversionError(
1078            "cannot convert BLOB to time::Duration".into(),
1079        ))
1080    }
1081}
1082
1083// =============================================================================
1084// JSON support (serde_json::Value)
1085// =============================================================================
1086
1087#[cfg(feature = "serde")]
1088impl FromSQLiteValue for serde_json::Value {
1089    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
1090        Ok(Self::Number(value.into()))
1091    }
1092
1093    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
1094        serde_json::from_str(value).map_err(|e| {
1095            DrizzleError::ConversionError(format!("cannot parse '{value}' as JSON: {e}").into())
1096        })
1097    }
1098
1099    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
1100        serde_json::Number::from_f64(value)
1101            .map(serde_json::Value::Number)
1102            .ok_or_else(|| {
1103                DrizzleError::ConversionError(
1104                    format!("cannot convert non-finite REAL {value} to JSON").into(),
1105                )
1106            })
1107    }
1108
1109    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
1110        serde_json::from_slice(value).map_err(|e| {
1111            DrizzleError::ConversionError(format!("cannot parse BLOB as JSON: {e}").into())
1112        })
1113    }
1114}
1115
1116// =============================================================================
1117// ArrayVec/ArrayString support
1118// =============================================================================
1119
1120#[cfg(feature = "arrayvec")]
1121impl<const N: usize> FromSQLiteValue for arrayvec::ArrayString<N> {
1122    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
1123        let s = value.to_string();
1124        Self::from(&s).map_err(|_| {
1125            DrizzleError::ConversionError(
1126                format!(
1127                    "String length {} exceeds ArrayString capacity {}",
1128                    s.len(),
1129                    N
1130                )
1131                .into(),
1132            )
1133        })
1134    }
1135
1136    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
1137        Self::from(value).map_err(|_| {
1138            DrizzleError::ConversionError(
1139                format!(
1140                    "Text length {} exceeds ArrayString capacity {}",
1141                    value.len(),
1142                    N
1143                )
1144                .into(),
1145            )
1146        })
1147    }
1148
1149    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
1150        let s = value.to_string();
1151        Self::from(&s).map_err(|_| {
1152            DrizzleError::ConversionError(
1153                format!(
1154                    "String length {} exceeds ArrayString capacity {}",
1155                    s.len(),
1156                    N
1157                )
1158                .into(),
1159            )
1160        })
1161    }
1162
1163    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
1164        let s = String::from_utf8(value.to_vec())
1165            .map_err(|e| DrizzleError::ConversionError(format!("invalid UTF-8: {e}").into()))?;
1166        Self::from(&s).map_err(|_| {
1167            DrizzleError::ConversionError(
1168                format!(
1169                    "String length {} exceeds ArrayString capacity {}",
1170                    s.len(),
1171                    N
1172                )
1173                .into(),
1174            )
1175        })
1176    }
1177}
1178
1179#[cfg(feature = "arrayvec")]
1180impl<const N: usize> FromSQLiteValue for arrayvec::ArrayVec<u8, N> {
1181    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
1182        Err(DrizzleError::ConversionError(
1183            "cannot convert INTEGER to ArrayVec<u8>, use BLOB".into(),
1184        ))
1185    }
1186
1187    fn from_sqlite_text(_value: &str) -> Result<Self, DrizzleError> {
1188        Err(DrizzleError::ConversionError(
1189            "cannot convert TEXT to ArrayVec<u8>, use BLOB".into(),
1190        ))
1191    }
1192
1193    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
1194        Err(DrizzleError::ConversionError(
1195            "cannot convert REAL to ArrayVec<u8>, use BLOB".into(),
1196        ))
1197    }
1198
1199    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
1200        Self::try_from(value).map_err(|_| {
1201            DrizzleError::ConversionError(
1202                format!(
1203                    "Blob length {} exceeds ArrayVec capacity {}",
1204                    value.len(),
1205                    N
1206                )
1207                .into(),
1208            )
1209        })
1210    }
1211}