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 drizzle_core::error::DrizzleError;
7use std::{rc::Rc, sync::Arc};
8
9/// Trait for types that can be converted from SQLite values.
10///
11/// SQLite has 5 storage classes: NULL, INTEGER, REAL, TEXT, BLOB.
12/// This trait provides conversion methods for each type.
13///
14/// # Implementation Notes
15///
16/// - Implement the methods that make sense for your type
17/// - Return `Err` for unsupported conversions
18/// - `SQLiteEnum` derive automatically implements this trait
19pub trait FromSQLiteValue: Sized {
20    /// Convert from a 64-bit integer value
21    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError>;
22
23    /// Convert from a text/string value
24    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError>;
25
26    /// Convert from a real/float value
27    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError>;
28
29    /// Convert from a blob/binary value
30    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError>;
31
32    /// Convert from a NULL value (default returns error)
33    fn from_sqlite_null() -> Result<Self, DrizzleError> {
34        Err(DrizzleError::ConversionError(
35            "unexpected NULL value".into(),
36        ))
37    }
38
39    /// Helper function to convert from rusqlite's ValueRef using FromSQLiteValue
40    #[cfg(feature = "rusqlite")]
41    fn from_value_ref(value: ::rusqlite::types::ValueRef<'_>) -> Result<Self, DrizzleError> {
42        match value {
43            ::rusqlite::types::ValueRef::Null => Self::from_sqlite_null(),
44            ::rusqlite::types::ValueRef::Integer(i) => Self::from_sqlite_integer(i),
45            ::rusqlite::types::ValueRef::Real(r) => Self::from_sqlite_real(r),
46            ::rusqlite::types::ValueRef::Text(text) => {
47                let s = std::str::from_utf8(text).map_err(|e| {
48                    DrizzleError::ConversionError(format!("invalid UTF-8: {}", e).into())
49                })?;
50                Self::from_sqlite_text(s)
51            }
52            ::rusqlite::types::ValueRef::Blob(blob) => Self::from_sqlite_blob(blob),
53        }
54    }
55}
56
57/// Row capability for index-based extraction.
58pub trait DrizzleRowByIndex {
59    /// Get a column value by index
60    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError>;
61}
62
63/// Optional row capability for name-based extraction.
64pub trait DrizzleRowByName: DrizzleRowByIndex {
65    /// Get a column value by name.
66    fn get_column_by_name<T: FromSQLiteValue>(&self, name: &str) -> Result<T, DrizzleError>;
67}
68
69fn checked_real_to_int<T>(value: f64, type_name: &str) -> Result<T, DrizzleError>
70where
71    T: TryFrom<i128>,
72    <T as TryFrom<i128>>::Error: core::fmt::Display,
73{
74    if !value.is_finite() {
75        return Err(DrizzleError::ConversionError(
76            format!("cannot convert non-finite REAL {} to {}", value, type_name).into(),
77        ));
78    }
79
80    if value.fract() != 0.0 {
81        return Err(DrizzleError::ConversionError(
82            format!("cannot convert non-integer REAL {} to {}", value, type_name).into(),
83        ));
84    }
85
86    if value < i128::MIN as f64 || value > i128::MAX as f64 {
87        return Err(DrizzleError::ConversionError(
88            format!("REAL {} out of range for {}", value, type_name).into(),
89        ));
90    }
91
92    let int_value = value as i128;
93    int_value.try_into().map_err(|e| {
94        DrizzleError::ConversionError(
95            format!("REAL {} out of range for {}: {}", value, type_name, e).into(),
96        )
97    })
98}
99
100// =============================================================================
101// Primitive implementations
102// =============================================================================
103
104/// Macro to implement FromSQLiteValue for integer types (handles narrowing conversion from i64)
105macro_rules! impl_from_sqlite_value_int {
106    // Special case for i64 - no conversion needed
107    (i64) => {
108        impl FromSQLiteValue for i64 {
109            fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
110                Ok(value)
111            }
112
113            fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
114                value.parse().map_err(|e| {
115                    DrizzleError::ConversionError(format!("cannot parse '{}' as i64: {}", value, e).into())
116                })
117            }
118
119            fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
120                checked_real_to_int(value, "i64")
121            }
122
123            fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
124                Err(DrizzleError::ConversionError("cannot convert BLOB to i64".into()))
125            }
126        }
127    };
128    // General case for other integer types - uses try_into for narrowing
129    ($($ty:ty),+ $(,)?) => {
130        $(
131            impl FromSQLiteValue for $ty {
132                fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
133                    value.try_into().map_err(|e| {
134                        DrizzleError::ConversionError(
135                            format!("i64 {} out of range for {}: {}", value, stringify!($ty), e).into(),
136                        )
137                    })
138                }
139
140                fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
141                    value.parse().map_err(|e| {
142                        DrizzleError::ConversionError(
143                            format!("cannot parse '{}' as {}: {}", value, stringify!($ty), e).into()
144                        )
145                    })
146                }
147
148                fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
149                    checked_real_to_int(value, stringify!($ty))
150                }
151
152                fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
153                    Err(DrizzleError::ConversionError(
154                        concat!("cannot convert BLOB to ", stringify!($ty)).into()
155                    ))
156                }
157            }
158        )+
159    };
160}
161
162/// Macro to implement FromSQLiteValue for float types
163macro_rules! impl_from_sqlite_value_float {
164    ($($ty:ty),+ $(,)?) => {
165        $(
166            impl FromSQLiteValue for $ty {
167                fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
168                    Ok(value as $ty)
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                    Ok(value as $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// Integer types
194impl_from_sqlite_value_int!(i64);
195impl_from_sqlite_value_int!(i8, i16, i32, isize, u8, u16, u32, u64, usize);
196
197// Float types
198impl_from_sqlite_value_float!(f32, f64);
199
200impl FromSQLiteValue for bool {
201    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
202        Ok(value != 0)
203    }
204
205    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
206        match value.to_lowercase().as_str() {
207            "true" | "1" | "yes" | "on" => Ok(true),
208            "false" | "0" | "no" | "off" => Ok(false),
209            _ => Err(DrizzleError::ConversionError(
210                format!("cannot parse '{}' as bool", value).into(),
211            )),
212        }
213    }
214
215    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
216        Ok(value != 0.0)
217    }
218
219    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
220        Err(DrizzleError::ConversionError(
221            "cannot convert BLOB to bool".into(),
222        ))
223    }
224}
225
226impl FromSQLiteValue for String {
227    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
228        Ok(value.to_string())
229    }
230
231    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
232        Ok(value.to_string())
233    }
234
235    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
236        Ok(value.to_string())
237    }
238
239    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
240        String::from_utf8(value.to_vec()).map_err(|e| {
241            DrizzleError::ConversionError(format!("invalid UTF-8 in BLOB: {}", e).into())
242        })
243    }
244}
245
246impl FromSQLiteValue for Box<String> {
247    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
248        String::from_sqlite_integer(value).map(Box::new)
249    }
250
251    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
252        String::from_sqlite_text(value).map(Box::new)
253    }
254
255    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
256        String::from_sqlite_real(value).map(Box::new)
257    }
258
259    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
260        String::from_sqlite_blob(value).map(Box::new)
261    }
262}
263
264impl FromSQLiteValue for Rc<String> {
265    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
266        String::from_sqlite_integer(value).map(Rc::new)
267    }
268
269    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
270        String::from_sqlite_text(value).map(Rc::new)
271    }
272
273    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
274        String::from_sqlite_real(value).map(Rc::new)
275    }
276
277    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
278        String::from_sqlite_blob(value).map(Rc::new)
279    }
280}
281
282impl FromSQLiteValue for Arc<String> {
283    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
284        String::from_sqlite_integer(value).map(Arc::new)
285    }
286
287    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
288        String::from_sqlite_text(value).map(Arc::new)
289    }
290
291    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
292        String::from_sqlite_real(value).map(Arc::new)
293    }
294
295    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
296        String::from_sqlite_blob(value).map(Arc::new)
297    }
298}
299
300impl FromSQLiteValue for Box<str> {
301    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
302        String::from_sqlite_integer(value).map(String::into_boxed_str)
303    }
304
305    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
306        String::from_sqlite_text(value).map(String::into_boxed_str)
307    }
308
309    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
310        String::from_sqlite_real(value).map(String::into_boxed_str)
311    }
312
313    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
314        String::from_sqlite_blob(value).map(String::into_boxed_str)
315    }
316}
317
318impl FromSQLiteValue for Rc<str> {
319    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
320        String::from_sqlite_integer(value).map(Rc::from)
321    }
322
323    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
324        String::from_sqlite_text(value).map(Rc::from)
325    }
326
327    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
328        String::from_sqlite_real(value).map(Rc::from)
329    }
330
331    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
332        String::from_sqlite_blob(value).map(Rc::from)
333    }
334}
335
336impl FromSQLiteValue for Arc<str> {
337    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
338        String::from_sqlite_integer(value).map(Arc::from)
339    }
340
341    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
342        String::from_sqlite_text(value).map(Arc::from)
343    }
344
345    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
346        String::from_sqlite_real(value).map(Arc::from)
347    }
348
349    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
350        String::from_sqlite_blob(value).map(Arc::from)
351    }
352}
353
354impl FromSQLiteValue for Vec<u8> {
355    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
356        Ok(value.to_le_bytes().to_vec())
357    }
358
359    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
360        Ok(value.as_bytes().to_vec())
361    }
362
363    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
364        Ok(value.to_le_bytes().to_vec())
365    }
366
367    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
368        Ok(value.to_vec())
369    }
370}
371
372impl FromSQLiteValue for Box<Vec<u8>> {
373    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
374        Vec::<u8>::from_sqlite_integer(value).map(Box::new)
375    }
376
377    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
378        Vec::<u8>::from_sqlite_text(value).map(Box::new)
379    }
380
381    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
382        Vec::<u8>::from_sqlite_real(value).map(Box::new)
383    }
384
385    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
386        Vec::<u8>::from_sqlite_blob(value).map(Box::new)
387    }
388}
389
390impl FromSQLiteValue for Rc<Vec<u8>> {
391    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
392        Vec::<u8>::from_sqlite_integer(value).map(Rc::new)
393    }
394
395    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
396        Vec::<u8>::from_sqlite_text(value).map(Rc::new)
397    }
398
399    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
400        Vec::<u8>::from_sqlite_real(value).map(Rc::new)
401    }
402
403    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
404        Vec::<u8>::from_sqlite_blob(value).map(Rc::new)
405    }
406}
407
408impl FromSQLiteValue for Arc<Vec<u8>> {
409    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
410        Vec::<u8>::from_sqlite_integer(value).map(Arc::new)
411    }
412
413    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
414        Vec::<u8>::from_sqlite_text(value).map(Arc::new)
415    }
416
417    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
418        Vec::<u8>::from_sqlite_real(value).map(Arc::new)
419    }
420
421    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
422        Vec::<u8>::from_sqlite_blob(value).map(Arc::new)
423    }
424}
425
426// Option<T> implementation - handles NULL values
427impl<T: FromSQLiteValue> FromSQLiteValue for Option<T> {
428    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
429        T::from_sqlite_integer(value).map(Some)
430    }
431
432    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
433        T::from_sqlite_text(value).map(Some)
434    }
435
436    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
437        T::from_sqlite_real(value).map(Some)
438    }
439
440    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
441        T::from_sqlite_blob(value).map(Some)
442    }
443
444    fn from_sqlite_null() -> Result<Self, DrizzleError> {
445        Ok(None)
446    }
447}
448
449// =============================================================================
450// Driver-specific DrizzleRow implementations
451// =============================================================================
452
453#[cfg(feature = "rusqlite")]
454impl DrizzleRowByIndex for rusqlite::Row<'_> {
455    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError> {
456        let value_ref = self.get_ref(idx)?;
457        match value_ref {
458            rusqlite::types::ValueRef::Integer(i) => T::from_sqlite_integer(i),
459            rusqlite::types::ValueRef::Text(s) => {
460                let s = std::str::from_utf8(s).map_err(|e| {
461                    DrizzleError::ConversionError(format!("invalid UTF-8: {}", e).into())
462                })?;
463                T::from_sqlite_text(s)
464            }
465            rusqlite::types::ValueRef::Real(r) => T::from_sqlite_real(r),
466            rusqlite::types::ValueRef::Blob(b) => T::from_sqlite_blob(b),
467            rusqlite::types::ValueRef::Null => T::from_sqlite_null(),
468        }
469    }
470}
471
472#[cfg(feature = "rusqlite")]
473impl DrizzleRowByName for rusqlite::Row<'_> {
474    fn get_column_by_name<T: FromSQLiteValue>(&self, name: &str) -> Result<T, DrizzleError> {
475        let idx = self.as_ref().column_index(name)?;
476        DrizzleRowByIndex::get_column(self, idx)
477    }
478}
479
480#[cfg(feature = "libsql")]
481impl DrizzleRowByIndex for libsql::Row {
482    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError> {
483        let value = self.get_value(idx as i32)?;
484        match value {
485            libsql::Value::Integer(i) => T::from_sqlite_integer(i),
486            libsql::Value::Text(ref s) => T::from_sqlite_text(s),
487            libsql::Value::Real(r) => T::from_sqlite_real(r),
488            libsql::Value::Blob(ref b) => T::from_sqlite_blob(b),
489            libsql::Value::Null => T::from_sqlite_null(),
490        }
491    }
492}
493
494#[cfg(feature = "turso")]
495impl DrizzleRowByIndex for turso::Row {
496    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError> {
497        let value = self.get_value(idx)?;
498        if value.is_null() {
499            T::from_sqlite_null()
500        } else if let Some(&i) = value.as_integer() {
501            T::from_sqlite_integer(i)
502        } else if let Some(s) = value.as_text() {
503            T::from_sqlite_text(s)
504        } else if let Some(&r) = value.as_real() {
505            T::from_sqlite_real(r)
506        } else if let Some(b) = value.as_blob() {
507            T::from_sqlite_blob(b)
508        } else {
509            Err(DrizzleError::ConversionError(
510                "unknown SQLite value type".into(),
511            ))
512        }
513    }
514}
515
516// =============================================================================
517// UUID support (when feature enabled)
518// =============================================================================
519
520#[cfg(feature = "uuid")]
521impl FromSQLiteValue for uuid::Uuid {
522    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
523        Err(DrizzleError::ConversionError(
524            "cannot convert INTEGER to UUID".into(),
525        ))
526    }
527
528    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
529        uuid::Uuid::parse_str(value).map_err(|e| {
530            DrizzleError::ConversionError(format!("invalid UUID string '{}': {}", value, e).into())
531        })
532    }
533
534    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
535        Err(DrizzleError::ConversionError(
536            "cannot convert REAL to UUID".into(),
537        ))
538    }
539
540    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
541        uuid::Uuid::from_slice(value)
542            .map_err(|e| DrizzleError::ConversionError(format!("invalid UUID bytes: {}", e).into()))
543    }
544}
545
546#[cfg(feature = "arrayvec")]
547impl<const N: usize> FromSQLiteValue for arrayvec::ArrayString<N> {
548    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
549        let s = value.to_string();
550        arrayvec::ArrayString::from(&s).map_err(|_| {
551            DrizzleError::ConversionError(
552                format!(
553                    "String length {} exceeds ArrayString capacity {}",
554                    s.len(),
555                    N
556                )
557                .into(),
558            )
559        })
560    }
561
562    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
563        arrayvec::ArrayString::from(value).map_err(|_| {
564            DrizzleError::ConversionError(
565                format!(
566                    "Text length {} exceeds ArrayString capacity {}",
567                    value.len(),
568                    N
569                )
570                .into(),
571            )
572        })
573    }
574
575    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
576        let s = value.to_string();
577        arrayvec::ArrayString::from(&s).map_err(|_| {
578            DrizzleError::ConversionError(
579                format!(
580                    "String length {} exceeds ArrayString capacity {}",
581                    s.len(),
582                    N
583                )
584                .into(),
585            )
586        })
587    }
588
589    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
590        let s = String::from_utf8(value.to_vec())
591            .map_err(|e| DrizzleError::ConversionError(format!("invalid UTF-8: {}", e).into()))?;
592        arrayvec::ArrayString::from(&s).map_err(|_| {
593            DrizzleError::ConversionError(
594                format!(
595                    "String length {} exceeds ArrayString capacity {}",
596                    s.len(),
597                    N
598                )
599                .into(),
600            )
601        })
602    }
603}
604
605#[cfg(feature = "arrayvec")]
606impl<const N: usize> FromSQLiteValue for arrayvec::ArrayVec<u8, N> {
607    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
608        Err(DrizzleError::ConversionError(
609            "cannot convert INTEGER to ArrayVec<u8>, use BLOB".into(),
610        ))
611    }
612
613    fn from_sqlite_text(_value: &str) -> Result<Self, DrizzleError> {
614        Err(DrizzleError::ConversionError(
615            "cannot convert TEXT to ArrayVec<u8>, use BLOB".into(),
616        ))
617    }
618
619    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
620        Err(DrizzleError::ConversionError(
621            "cannot convert REAL to ArrayVec<u8>, use BLOB".into(),
622        ))
623    }
624
625    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
626        arrayvec::ArrayVec::try_from(value).map_err(|_| {
627            DrizzleError::ConversionError(
628                format!(
629                    "Blob length {} exceeds ArrayVec capacity {}",
630                    value.len(),
631                    N
632                )
633                .into(),
634            )
635        })
636    }
637}