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 the `DrizzleRow` trait for unified row access across drivers.
5
6use drizzle_core::error::DrizzleError;
7
8/// Trait for types that can be converted from SQLite values.
9///
10/// SQLite has 5 storage classes: NULL, INTEGER, REAL, TEXT, BLOB.
11/// This trait provides conversion methods for each type.
12///
13/// # Implementation Notes
14///
15/// - Implement the methods that make sense for your type
16/// - Return `Err` for unsupported conversions
17/// - `SQLiteEnum` derive automatically implements this trait
18pub trait FromSQLiteValue: Sized {
19    /// Convert from a 64-bit integer value
20    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError>;
21
22    /// Convert from a text/string value
23    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError>;
24
25    /// Convert from a real/float value
26    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError>;
27
28    /// Convert from a blob/binary value
29    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError>;
30
31    /// Convert from a NULL value (default returns error)
32    fn from_sqlite_null() -> Result<Self, DrizzleError> {
33        Err(DrizzleError::ConversionError(
34            "unexpected NULL value".into(),
35        ))
36    }
37
38    /// Helper function to convert from rusqlite's ValueRef using FromSQLiteValue
39    #[cfg(feature = "rusqlite")]
40    fn from_value_ref(value: ::rusqlite::types::ValueRef<'_>) -> Result<Self, DrizzleError> {
41        match value {
42            ::rusqlite::types::ValueRef::Null => Self::from_sqlite_null(),
43            ::rusqlite::types::ValueRef::Integer(i) => Self::from_sqlite_integer(i),
44            ::rusqlite::types::ValueRef::Real(r) => Self::from_sqlite_real(r),
45            ::rusqlite::types::ValueRef::Text(text) => {
46                let s = std::str::from_utf8(text).map_err(|e| {
47                    DrizzleError::ConversionError(format!("invalid UTF-8: {}", e).into())
48                })?;
49                Self::from_sqlite_text(s)
50            }
51            ::rusqlite::types::ValueRef::Blob(blob) => Self::from_sqlite_blob(blob),
52        }
53    }
54}
55
56/// Trait for database rows that can extract values using `FromSQLiteValue`.
57///
58/// This provides a unified interface for extracting values from database rows
59/// across different SQLite drivers (libsql, turso).
60pub trait DrizzleRow {
61    /// Get a column value by index
62    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError>;
63
64    /// Get a column value by name (optional, not all drivers support this efficiently)
65    fn get_column_by_name<T: FromSQLiteValue>(&self, name: &str) -> Result<T, DrizzleError>;
66}
67
68// =============================================================================
69// Primitive implementations
70// =============================================================================
71
72/// Macro to implement FromSQLiteValue for integer types (handles narrowing conversion from i64)
73macro_rules! impl_from_sqlite_value_int {
74    // Special case for i64 - no conversion needed
75    (i64) => {
76        impl FromSQLiteValue for i64 {
77            fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
78                Ok(value)
79            }
80
81            fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
82                value.parse().map_err(|e| {
83                    DrizzleError::ConversionError(format!("cannot parse '{}' as i64: {}", value, e).into())
84                })
85            }
86
87            fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
88                Ok(value as i64)
89            }
90
91            fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
92                Err(DrizzleError::ConversionError("cannot convert BLOB to i64".into()))
93            }
94        }
95    };
96    // General case for other integer types - uses try_into for narrowing
97    ($($ty:ty),+ $(,)?) => {
98        $(
99            impl FromSQLiteValue for $ty {
100                fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
101                    value.try_into().map_err(|e| {
102                        DrizzleError::ConversionError(
103                            format!("i64 {} out of range for {}: {}", value, stringify!($ty), e).into(),
104                        )
105                    })
106                }
107
108                fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
109                    value.parse().map_err(|e| {
110                        DrizzleError::ConversionError(
111                            format!("cannot parse '{}' as {}: {}", value, stringify!($ty), e).into()
112                        )
113                    })
114                }
115
116                fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
117                    Ok(value as $ty)
118                }
119
120                fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
121                    Err(DrizzleError::ConversionError(
122                        concat!("cannot convert BLOB to ", stringify!($ty)).into()
123                    ))
124                }
125            }
126        )+
127    };
128}
129
130/// Macro to implement FromSQLiteValue for float types
131macro_rules! impl_from_sqlite_value_float {
132    ($($ty:ty),+ $(,)?) => {
133        $(
134            impl FromSQLiteValue for $ty {
135                fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
136                    Ok(value as $ty)
137                }
138
139                fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
140                    value.parse().map_err(|e| {
141                        DrizzleError::ConversionError(
142                            format!("cannot parse '{}' as {}: {}", value, stringify!($ty), e).into()
143                        )
144                    })
145                }
146
147                fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
148                    Ok(value as $ty)
149                }
150
151                fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
152                    Err(DrizzleError::ConversionError(
153                        concat!("cannot convert BLOB to ", stringify!($ty)).into()
154                    ))
155                }
156            }
157        )+
158    };
159}
160
161// Integer types
162impl_from_sqlite_value_int!(i64);
163impl_from_sqlite_value_int!(i8, i16, i32, isize, u8, u16, u32, u64, usize);
164
165// Float types
166impl_from_sqlite_value_float!(f32, f64);
167
168impl FromSQLiteValue for bool {
169    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
170        Ok(value != 0)
171    }
172
173    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
174        match value.to_lowercase().as_str() {
175            "true" | "1" | "yes" | "on" => Ok(true),
176            "false" | "0" | "no" | "off" => Ok(false),
177            _ => Err(DrizzleError::ConversionError(
178                format!("cannot parse '{}' as bool", value).into(),
179            )),
180        }
181    }
182
183    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
184        Ok(value != 0.0)
185    }
186
187    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
188        Err(DrizzleError::ConversionError(
189            "cannot convert BLOB to bool".into(),
190        ))
191    }
192}
193
194impl FromSQLiteValue for String {
195    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
196        Ok(value.to_string())
197    }
198
199    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
200        Ok(value.to_string())
201    }
202
203    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
204        Ok(value.to_string())
205    }
206
207    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
208        String::from_utf8(value.to_vec()).map_err(|e| {
209            DrizzleError::ConversionError(format!("invalid UTF-8 in BLOB: {}", e).into())
210        })
211    }
212}
213
214impl FromSQLiteValue for Vec<u8> {
215    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
216        Ok(value.to_le_bytes().to_vec())
217    }
218
219    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
220        Ok(value.as_bytes().to_vec())
221    }
222
223    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
224        Ok(value.to_le_bytes().to_vec())
225    }
226
227    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
228        Ok(value.to_vec())
229    }
230}
231
232// Option<T> implementation - handles NULL values
233impl<T: FromSQLiteValue> FromSQLiteValue for Option<T> {
234    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
235        T::from_sqlite_integer(value).map(Some)
236    }
237
238    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
239        T::from_sqlite_text(value).map(Some)
240    }
241
242    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
243        T::from_sqlite_real(value).map(Some)
244    }
245
246    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
247        T::from_sqlite_blob(value).map(Some)
248    }
249
250    fn from_sqlite_null() -> Result<Self, DrizzleError> {
251        Ok(None)
252    }
253}
254
255// =============================================================================
256// Driver-specific DrizzleRow implementations
257// =============================================================================
258
259#[cfg(feature = "rusqlite")]
260impl DrizzleRow for rusqlite::Row<'_> {
261    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError> {
262        let value_ref = self.get_ref(idx)?;
263        match value_ref {
264            rusqlite::types::ValueRef::Integer(i) => T::from_sqlite_integer(i),
265            rusqlite::types::ValueRef::Text(s) => {
266                let s = std::str::from_utf8(s).map_err(|e| {
267                    DrizzleError::ConversionError(format!("invalid UTF-8: {}", e).into())
268                })?;
269                T::from_sqlite_text(s)
270            }
271            rusqlite::types::ValueRef::Real(r) => T::from_sqlite_real(r),
272            rusqlite::types::ValueRef::Blob(b) => T::from_sqlite_blob(b),
273            rusqlite::types::ValueRef::Null => T::from_sqlite_null(),
274        }
275    }
276
277    fn get_column_by_name<T: FromSQLiteValue>(&self, name: &str) -> Result<T, DrizzleError> {
278        let idx = self.as_ref().column_index(name)?;
279        self.get_column(idx)
280    }
281}
282
283#[cfg(feature = "libsql")]
284impl DrizzleRow for libsql::Row {
285    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError> {
286        let value = self.get_value(idx as i32)?;
287        match value {
288            libsql::Value::Integer(i) => T::from_sqlite_integer(i),
289            libsql::Value::Text(ref s) => T::from_sqlite_text(s),
290            libsql::Value::Real(r) => T::from_sqlite_real(r),
291            libsql::Value::Blob(ref b) => T::from_sqlite_blob(b),
292            libsql::Value::Null => T::from_sqlite_null(),
293        }
294    }
295
296    fn get_column_by_name<T: FromSQLiteValue>(&self, _name: &str) -> Result<T, DrizzleError> {
297        // libsql doesn't have efficient name-based access, would need to iterate columns
298        Err(DrizzleError::ConversionError(
299            "libsql does not support column access by name in FromRow".into(),
300        ))
301    }
302}
303
304#[cfg(feature = "turso")]
305impl DrizzleRow for turso::Row {
306    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError> {
307        let value = self.get_value(idx)?;
308        if value.is_null() {
309            T::from_sqlite_null()
310        } else if let Some(&i) = value.as_integer() {
311            T::from_sqlite_integer(i)
312        } else if let Some(s) = value.as_text() {
313            T::from_sqlite_text(s)
314        } else if let Some(&r) = value.as_real() {
315            T::from_sqlite_real(r)
316        } else if let Some(b) = value.as_blob() {
317            T::from_sqlite_blob(b)
318        } else {
319            Err(DrizzleError::ConversionError(
320                "unknown SQLite value type".into(),
321            ))
322        }
323    }
324
325    fn get_column_by_name<T: FromSQLiteValue>(&self, _name: &str) -> Result<T, DrizzleError> {
326        Err(DrizzleError::ConversionError(
327            "turso does not support column access by name in FromRow".into(),
328        ))
329    }
330}
331
332// =============================================================================
333// UUID support (when feature enabled)
334// =============================================================================
335
336#[cfg(feature = "uuid")]
337impl FromSQLiteValue for uuid::Uuid {
338    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
339        Err(DrizzleError::ConversionError(
340            "cannot convert INTEGER to UUID".into(),
341        ))
342    }
343
344    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
345        uuid::Uuid::parse_str(value).map_err(|e| {
346            DrizzleError::ConversionError(format!("invalid UUID string '{}': {}", value, e).into())
347        })
348    }
349
350    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
351        Err(DrizzleError::ConversionError(
352            "cannot convert REAL to UUID".into(),
353        ))
354    }
355
356    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
357        uuid::Uuid::from_slice(value)
358            .map_err(|e| DrizzleError::ConversionError(format!("invalid UUID bytes: {}", e).into()))
359    }
360}
361
362#[cfg(feature = "arrayvec")]
363impl<const N: usize> FromSQLiteValue for arrayvec::ArrayString<N> {
364    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
365        let s = value.to_string();
366        arrayvec::ArrayString::from(&s).map_err(|_| {
367            DrizzleError::ConversionError(
368                format!(
369                    "String length {} exceeds ArrayString capacity {}",
370                    s.len(),
371                    N
372                )
373                .into(),
374            )
375        })
376    }
377
378    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
379        arrayvec::ArrayString::from(value).map_err(|_| {
380            DrizzleError::ConversionError(
381                format!(
382                    "Text length {} exceeds ArrayString capacity {}",
383                    value.len(),
384                    N
385                )
386                .into(),
387            )
388        })
389    }
390
391    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
392        let s = value.to_string();
393        arrayvec::ArrayString::from(&s).map_err(|_| {
394            DrizzleError::ConversionError(
395                format!(
396                    "String length {} exceeds ArrayString capacity {}",
397                    s.len(),
398                    N
399                )
400                .into(),
401            )
402        })
403    }
404
405    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
406        let s = String::from_utf8(value.to_vec())
407            .map_err(|e| DrizzleError::ConversionError(format!("invalid UTF-8: {}", e).into()))?;
408        arrayvec::ArrayString::from(&s).map_err(|_| {
409            DrizzleError::ConversionError(
410                format!(
411                    "String length {} exceeds ArrayString capacity {}",
412                    s.len(),
413                    N
414                )
415                .into(),
416            )
417        })
418    }
419}
420
421#[cfg(feature = "arrayvec")]
422impl<const N: usize> FromSQLiteValue for arrayvec::ArrayVec<u8, N> {
423    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
424        Err(DrizzleError::ConversionError(
425            "cannot convert INTEGER to ArrayVec<u8>, use BLOB".into(),
426        ))
427    }
428
429    fn from_sqlite_text(_value: &str) -> Result<Self, DrizzleError> {
430        Err(DrizzleError::ConversionError(
431            "cannot convert TEXT to ArrayVec<u8>, use BLOB".into(),
432        ))
433    }
434
435    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
436        Err(DrizzleError::ConversionError(
437            "cannot convert REAL to ArrayVec<u8>, use BLOB".into(),
438        ))
439    }
440
441    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
442        arrayvec::ArrayVec::try_from(value).map_err(|_| {
443            DrizzleError::ConversionError(
444                format!(
445                    "Blob length {} exceeds ArrayVec capacity {}",
446                    value.len(),
447                    N
448                )
449                .into(),
450            )
451        })
452    }
453}