1#![allow(clippy::expect_used)]
5
6use crate::error::TypeError;
7use crate::value::SqlValue;
8
9pub trait FromSql: Sized {
14 fn from_sql(value: &SqlValue) -> Result<Self, TypeError>;
16
17 fn from_sql_nullable(value: &SqlValue) -> Result<Option<Self>, TypeError> {
21 if value.is_null() {
22 Ok(None)
23 } else {
24 Self::from_sql(value).map(Some)
25 }
26 }
27}
28
29impl FromSql for bool {
30 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
31 match value {
32 SqlValue::Bool(v) => Ok(*v),
33 SqlValue::TinyInt(v) => Ok(*v != 0),
34 SqlValue::SmallInt(v) => Ok(*v != 0),
35 SqlValue::Int(v) => Ok(*v != 0),
36 SqlValue::Null => Err(TypeError::UnexpectedNull),
37 _ => Err(TypeError::TypeMismatch {
38 expected: "bool",
39 actual: value.type_name().to_string(),
40 }),
41 }
42 }
43}
44
45impl FromSql for u8 {
46 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
47 match value {
48 SqlValue::TinyInt(v) => Ok(*v),
49 SqlValue::Null => Err(TypeError::UnexpectedNull),
50 _ => Err(TypeError::TypeMismatch {
51 expected: "u8",
52 actual: value.type_name().to_string(),
53 }),
54 }
55 }
56}
57
58impl FromSql for i16 {
59 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
60 match value {
61 SqlValue::SmallInt(v) => Ok(*v),
62 SqlValue::TinyInt(v) => Ok(*v as i16),
63 SqlValue::Null => Err(TypeError::UnexpectedNull),
64 _ => Err(TypeError::TypeMismatch {
65 expected: "i16",
66 actual: value.type_name().to_string(),
67 }),
68 }
69 }
70}
71
72impl FromSql for i32 {
73 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
74 match value {
75 SqlValue::Int(v) => Ok(*v),
76 SqlValue::SmallInt(v) => Ok(*v as i32),
77 SqlValue::TinyInt(v) => Ok(*v as i32),
78 SqlValue::Null => Err(TypeError::UnexpectedNull),
79 _ => Err(TypeError::TypeMismatch {
80 expected: "i32",
81 actual: value.type_name().to_string(),
82 }),
83 }
84 }
85}
86
87impl FromSql for i64 {
88 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
89 match value {
90 SqlValue::BigInt(v) => Ok(*v),
91 SqlValue::Int(v) => Ok(*v as i64),
92 SqlValue::SmallInt(v) => Ok(*v as i64),
93 SqlValue::TinyInt(v) => Ok(*v as i64),
94 SqlValue::Null => Err(TypeError::UnexpectedNull),
95 _ => Err(TypeError::TypeMismatch {
96 expected: "i64",
97 actual: value.type_name().to_string(),
98 }),
99 }
100 }
101}
102
103impl FromSql for f32 {
104 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
105 match value {
106 SqlValue::Float(v) => Ok(*v),
107 SqlValue::Null => Err(TypeError::UnexpectedNull),
108 _ => Err(TypeError::TypeMismatch {
109 expected: "f32",
110 actual: value.type_name().to_string(),
111 }),
112 }
113 }
114}
115
116impl FromSql for f64 {
117 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
118 match value {
119 SqlValue::Double(v) => Ok(*v),
120 SqlValue::Float(v) => Ok(*v as f64),
121 #[cfg(feature = "decimal")]
122 SqlValue::Decimal(v) => {
123 use rust_decimal::prelude::ToPrimitive;
124 v.to_f64().ok_or_else(|| TypeError::TypeMismatch {
125 expected: "f64",
126 actual: "Decimal out of range".to_string(),
127 })
128 }
129 SqlValue::Int(v) => Ok(*v as f64),
130 SqlValue::BigInt(v) => Ok(*v as f64),
131 SqlValue::Null => Err(TypeError::UnexpectedNull),
132 _ => Err(TypeError::TypeMismatch {
133 expected: "f64",
134 actual: value.type_name().to_string(),
135 }),
136 }
137 }
138}
139
140impl FromSql for String {
141 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
142 match value {
143 SqlValue::String(v) => Ok(v.clone()),
144 SqlValue::Xml(v) => Ok(v.clone()),
145 SqlValue::Null => Err(TypeError::UnexpectedNull),
146 _ => Err(TypeError::TypeMismatch {
147 expected: "String",
148 actual: value.type_name().to_string(),
149 }),
150 }
151 }
152}
153
154impl FromSql for Vec<u8> {
155 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
156 match value {
157 SqlValue::Binary(v) => Ok(v.to_vec()),
158 SqlValue::Null => Err(TypeError::UnexpectedNull),
159 _ => Err(TypeError::TypeMismatch {
160 expected: "Vec<u8>",
161 actual: value.type_name().to_string(),
162 }),
163 }
164 }
165}
166
167impl<T: FromSql> FromSql for Option<T> {
168 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
169 T::from_sql_nullable(value)
170 }
171}
172
173#[cfg(feature = "uuid")]
174impl FromSql for uuid::Uuid {
175 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
176 match value {
177 SqlValue::Uuid(v) => Ok(*v),
178 SqlValue::Binary(b) if b.len() == 16 => {
179 let bytes: [u8; 16] = b[..]
180 .try_into()
181 .map_err(|_| TypeError::InvalidUuid("invalid UUID length".to_string()))?;
182 Ok(uuid::Uuid::from_bytes(bytes))
183 }
184 SqlValue::String(s) => s
185 .parse()
186 .map_err(|e| TypeError::InvalidUuid(format!("{e}"))),
187 SqlValue::Null => Err(TypeError::UnexpectedNull),
188 _ => Err(TypeError::TypeMismatch {
189 expected: "Uuid",
190 actual: value.type_name().to_string(),
191 }),
192 }
193 }
194}
195
196#[cfg(feature = "decimal")]
197impl FromSql for rust_decimal::Decimal {
198 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
199 match value {
200 SqlValue::Decimal(v) => Ok(*v),
201 SqlValue::Int(v) => Ok(rust_decimal::Decimal::from(*v)),
202 SqlValue::BigInt(v) => Ok(rust_decimal::Decimal::from(*v)),
203 SqlValue::String(s) => s
204 .parse()
205 .map_err(|e| TypeError::InvalidDecimal(format!("{e}"))),
206 SqlValue::Null => Err(TypeError::UnexpectedNull),
207 _ => Err(TypeError::TypeMismatch {
208 expected: "Decimal",
209 actual: value.type_name().to_string(),
210 }),
211 }
212 }
213}
214
215#[cfg(feature = "chrono")]
216impl FromSql for chrono::NaiveDate {
217 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
218 match value {
219 SqlValue::Date(v) => Ok(*v),
220 SqlValue::DateTime(v) => Ok(v.date()),
221 SqlValue::Null => Err(TypeError::UnexpectedNull),
222 _ => Err(TypeError::TypeMismatch {
223 expected: "NaiveDate",
224 actual: value.type_name().to_string(),
225 }),
226 }
227 }
228}
229
230#[cfg(feature = "chrono")]
231impl FromSql for chrono::NaiveTime {
232 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
233 match value {
234 SqlValue::Time(v) => Ok(*v),
235 SqlValue::DateTime(v) => Ok(v.time()),
236 SqlValue::Null => Err(TypeError::UnexpectedNull),
237 _ => Err(TypeError::TypeMismatch {
238 expected: "NaiveTime",
239 actual: value.type_name().to_string(),
240 }),
241 }
242 }
243}
244
245#[cfg(feature = "chrono")]
246impl FromSql for chrono::NaiveDateTime {
247 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
248 match value {
249 SqlValue::DateTime(v) => Ok(*v),
250 SqlValue::DateTimeOffset(v) => Ok(v.naive_utc()),
251 SqlValue::Null => Err(TypeError::UnexpectedNull),
252 _ => Err(TypeError::TypeMismatch {
253 expected: "NaiveDateTime",
254 actual: value.type_name().to_string(),
255 }),
256 }
257 }
258}
259
260#[cfg(feature = "chrono")]
261impl FromSql for chrono::DateTime<chrono::FixedOffset> {
262 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
263 match value {
264 SqlValue::DateTimeOffset(v) => Ok(*v),
265 SqlValue::Null => Err(TypeError::UnexpectedNull),
266 _ => Err(TypeError::TypeMismatch {
267 expected: "DateTime<FixedOffset>",
268 actual: value.type_name().to_string(),
269 }),
270 }
271 }
272}
273
274#[cfg(feature = "chrono")]
275impl FromSql for chrono::DateTime<chrono::Utc> {
276 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
277 match value {
278 SqlValue::DateTimeOffset(v) => Ok(v.to_utc()),
279 SqlValue::DateTime(v) => {
280 Ok(chrono::DateTime::from_naive_utc_and_offset(*v, chrono::Utc))
281 }
282 SqlValue::Null => Err(TypeError::UnexpectedNull),
283 _ => Err(TypeError::TypeMismatch {
284 expected: "DateTime<Utc>",
285 actual: value.type_name().to_string(),
286 }),
287 }
288 }
289}
290
291#[cfg(feature = "json")]
292impl FromSql for serde_json::Value {
293 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
294 match value {
295 SqlValue::Json(v) => Ok(v.clone()),
296 SqlValue::String(s) => serde_json::from_str(s).map_err(|e| TypeError::TypeMismatch {
297 expected: "JSON",
298 actual: format!("invalid JSON: {e}"),
299 }),
300 SqlValue::Null => Ok(serde_json::Value::Null),
301 _ => Err(TypeError::TypeMismatch {
302 expected: "JSON",
303 actual: value.type_name().to_string(),
304 }),
305 }
306 }
307}
308
309#[cfg(test)]
310#[allow(clippy::unwrap_used)]
311mod tests {
312 use super::*;
313
314 #[test]
315 fn test_from_sql_i32() {
316 let value = SqlValue::Int(42);
317 assert_eq!(i32::from_sql(&value).unwrap(), 42);
318 }
319
320 #[test]
321 fn test_from_sql_string() {
322 let value = SqlValue::String("hello".to_string());
323 assert_eq!(String::from_sql(&value).unwrap(), "hello");
324 }
325
326 #[test]
327 fn test_from_sql_null() {
328 let value = SqlValue::Null;
329 assert!(i32::from_sql(&value).is_err());
330 }
331
332 #[test]
333 fn test_from_sql_option() {
334 let value = SqlValue::Int(42);
335 assert_eq!(Option::<i32>::from_sql(&value).unwrap(), Some(42));
336
337 let null = SqlValue::Null;
338 assert_eq!(Option::<i32>::from_sql(&null).unwrap(), None);
339 }
340}