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
39/// Trait for database rows that can extract values using `FromSQLiteValue`.
40///
41/// This provides a unified interface for extracting values from database rows
42/// across different SQLite drivers (libsql, turso).
43pub trait DrizzleRow {
44    /// Get a column value by index
45    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError>;
46
47    /// Get a column value by name (optional, not all drivers support this efficiently)
48    fn get_column_by_name<T: FromSQLiteValue>(&self, name: &str) -> Result<T, DrizzleError>;
49}
50
51// =============================================================================
52// Primitive implementations
53// =============================================================================
54
55/// Macro to implement FromSQLiteValue for integer types (handles narrowing conversion from i64)
56macro_rules! impl_from_sqlite_value_int {
57    // Special case for i64 - no conversion needed
58    (i64) => {
59        impl FromSQLiteValue for i64 {
60            fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
61                Ok(value)
62            }
63
64            fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
65                value.parse().map_err(|e| {
66                    DrizzleError::ConversionError(format!("cannot parse '{}' as i64: {}", value, e).into())
67                })
68            }
69
70            fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
71                Ok(value as i64)
72            }
73
74            fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
75                Err(DrizzleError::ConversionError("cannot convert BLOB to i64".into()))
76            }
77        }
78    };
79    // General case for other integer types - uses try_into for narrowing
80    ($($ty:ty),+ $(,)?) => {
81        $(
82            impl FromSQLiteValue for $ty {
83                fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
84                    value.try_into().map_err(|e| {
85                        DrizzleError::ConversionError(
86                            format!("i64 {} out of range for {}: {}", value, stringify!($ty), e).into(),
87                        )
88                    })
89                }
90
91                fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
92                    value.parse().map_err(|e| {
93                        DrizzleError::ConversionError(
94                            format!("cannot parse '{}' as {}: {}", value, stringify!($ty), e).into()
95                        )
96                    })
97                }
98
99                fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
100                    Ok(value as $ty)
101                }
102
103                fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
104                    Err(DrizzleError::ConversionError(
105                        concat!("cannot convert BLOB to ", stringify!($ty)).into()
106                    ))
107                }
108            }
109        )+
110    };
111}
112
113/// Macro to implement FromSQLiteValue for float types
114macro_rules! impl_from_sqlite_value_float {
115    ($($ty:ty),+ $(,)?) => {
116        $(
117            impl FromSQLiteValue for $ty {
118                fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
119                    Ok(value as $ty)
120                }
121
122                fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
123                    value.parse().map_err(|e| {
124                        DrizzleError::ConversionError(
125                            format!("cannot parse '{}' as {}: {}", value, stringify!($ty), e).into()
126                        )
127                    })
128                }
129
130                fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
131                    Ok(value as $ty)
132                }
133
134                fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
135                    Err(DrizzleError::ConversionError(
136                        concat!("cannot convert BLOB to ", stringify!($ty)).into()
137                    ))
138                }
139            }
140        )+
141    };
142}
143
144// Integer types
145impl_from_sqlite_value_int!(i64);
146impl_from_sqlite_value_int!(i8, i16, i32, isize, u8, u16, u32, u64, usize);
147
148// Float types
149impl_from_sqlite_value_float!(f32, f64);
150
151impl FromSQLiteValue for bool {
152    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
153        Ok(value != 0)
154    }
155
156    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
157        match value.to_lowercase().as_str() {
158            "true" | "1" | "yes" | "on" => Ok(true),
159            "false" | "0" | "no" | "off" => Ok(false),
160            _ => Err(DrizzleError::ConversionError(
161                format!("cannot parse '{}' as bool", value).into(),
162            )),
163        }
164    }
165
166    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
167        Ok(value != 0.0)
168    }
169
170    fn from_sqlite_blob(_value: &[u8]) -> Result<Self, DrizzleError> {
171        Err(DrizzleError::ConversionError(
172            "cannot convert BLOB to bool".into(),
173        ))
174    }
175}
176
177impl FromSQLiteValue for String {
178    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
179        Ok(value.to_string())
180    }
181
182    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
183        Ok(value.to_string())
184    }
185
186    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
187        Ok(value.to_string())
188    }
189
190    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
191        String::from_utf8(value.to_vec()).map_err(|e| {
192            DrizzleError::ConversionError(format!("invalid UTF-8 in BLOB: {}", e).into())
193        })
194    }
195}
196
197impl FromSQLiteValue for Vec<u8> {
198    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
199        Ok(value.to_le_bytes().to_vec())
200    }
201
202    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
203        Ok(value.as_bytes().to_vec())
204    }
205
206    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
207        Ok(value.to_le_bytes().to_vec())
208    }
209
210    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
211        Ok(value.to_vec())
212    }
213}
214
215// Option<T> implementation - handles NULL values
216impl<T: FromSQLiteValue> FromSQLiteValue for Option<T> {
217    fn from_sqlite_integer(value: i64) -> Result<Self, DrizzleError> {
218        T::from_sqlite_integer(value).map(Some)
219    }
220
221    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
222        T::from_sqlite_text(value).map(Some)
223    }
224
225    fn from_sqlite_real(value: f64) -> Result<Self, DrizzleError> {
226        T::from_sqlite_real(value).map(Some)
227    }
228
229    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
230        T::from_sqlite_blob(value).map(Some)
231    }
232
233    fn from_sqlite_null() -> Result<Self, DrizzleError> {
234        Ok(None)
235    }
236}
237
238// =============================================================================
239// Driver-specific DrizzleRow implementations
240// =============================================================================
241
242#[cfg(feature = "rusqlite")]
243impl DrizzleRow for rusqlite::Row<'_> {
244    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError> {
245        let value_ref = self.get_ref(idx)?;
246        match value_ref {
247            rusqlite::types::ValueRef::Integer(i) => T::from_sqlite_integer(i),
248            rusqlite::types::ValueRef::Text(s) => {
249                let s = std::str::from_utf8(s).map_err(|e| {
250                    DrizzleError::ConversionError(format!("invalid UTF-8: {}", e).into())
251                })?;
252                T::from_sqlite_text(s)
253            }
254            rusqlite::types::ValueRef::Real(r) => T::from_sqlite_real(r),
255            rusqlite::types::ValueRef::Blob(b) => T::from_sqlite_blob(b),
256            rusqlite::types::ValueRef::Null => T::from_sqlite_null(),
257        }
258    }
259
260    fn get_column_by_name<T: FromSQLiteValue>(&self, name: &str) -> Result<T, DrizzleError> {
261        let idx = self.as_ref().column_index(name)?;
262        self.get_column(idx)
263    }
264}
265
266#[cfg(feature = "libsql")]
267impl DrizzleRow for libsql::Row {
268    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError> {
269        let value = self.get_value(idx as i32)?;
270        match value {
271            libsql::Value::Integer(i) => T::from_sqlite_integer(i),
272            libsql::Value::Text(ref s) => T::from_sqlite_text(s),
273            libsql::Value::Real(r) => T::from_sqlite_real(r),
274            libsql::Value::Blob(ref b) => T::from_sqlite_blob(b),
275            libsql::Value::Null => T::from_sqlite_null(),
276        }
277    }
278
279    fn get_column_by_name<T: FromSQLiteValue>(&self, _name: &str) -> Result<T, DrizzleError> {
280        // libsql doesn't have efficient name-based access, would need to iterate columns
281        Err(DrizzleError::ConversionError(
282            "libsql does not support column access by name in FromRow".into(),
283        ))
284    }
285}
286
287#[cfg(feature = "turso")]
288impl DrizzleRow for turso::Row {
289    fn get_column<T: FromSQLiteValue>(&self, idx: usize) -> Result<T, DrizzleError> {
290        let value = self.get_value(idx)?;
291        if value.is_null() {
292            T::from_sqlite_null()
293        } else if let Some(&i) = value.as_integer() {
294            T::from_sqlite_integer(i)
295        } else if let Some(s) = value.as_text() {
296            T::from_sqlite_text(s)
297        } else if let Some(&r) = value.as_real() {
298            T::from_sqlite_real(r)
299        } else if let Some(b) = value.as_blob() {
300            T::from_sqlite_blob(b)
301        } else {
302            Err(DrizzleError::ConversionError(
303                "unknown SQLite value type".into(),
304            ))
305        }
306    }
307
308    fn get_column_by_name<T: FromSQLiteValue>(&self, _name: &str) -> Result<T, DrizzleError> {
309        Err(DrizzleError::ConversionError(
310            "turso does not support column access by name in FromRow".into(),
311        ))
312    }
313}
314
315// =============================================================================
316// UUID support (when feature enabled)
317// =============================================================================
318
319#[cfg(feature = "uuid")]
320impl FromSQLiteValue for uuid::Uuid {
321    fn from_sqlite_integer(_value: i64) -> Result<Self, DrizzleError> {
322        Err(DrizzleError::ConversionError(
323            "cannot convert INTEGER to UUID".into(),
324        ))
325    }
326
327    fn from_sqlite_text(value: &str) -> Result<Self, DrizzleError> {
328        uuid::Uuid::parse_str(value).map_err(|e| {
329            DrizzleError::ConversionError(format!("invalid UUID string '{}': {}", value, e).into())
330        })
331    }
332
333    fn from_sqlite_real(_value: f64) -> Result<Self, DrizzleError> {
334        Err(DrizzleError::ConversionError(
335            "cannot convert REAL to UUID".into(),
336        ))
337    }
338
339    fn from_sqlite_blob(value: &[u8]) -> Result<Self, DrizzleError> {
340        uuid::Uuid::from_slice(value)
341            .map_err(|e| DrizzleError::ConversionError(format!("invalid UUID bytes: {}", e).into()))
342    }
343}