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) | SqlValue::Money(v) | SqlValue::SmallMoney(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::Money(v) | SqlValue::SmallMoney(v) => Ok(*v),
202 SqlValue::Int(v) => Ok(rust_decimal::Decimal::from(*v)),
203 SqlValue::BigInt(v) => Ok(rust_decimal::Decimal::from(*v)),
204 SqlValue::String(s) => s
205 .parse()
206 .map_err(|e| TypeError::InvalidDecimal(format!("{e}"))),
207 SqlValue::Null => Err(TypeError::UnexpectedNull),
208 _ => Err(TypeError::TypeMismatch {
209 expected: "Decimal",
210 actual: value.type_name().to_string(),
211 }),
212 }
213 }
214}
215
216#[cfg(feature = "chrono")]
217impl FromSql for chrono::NaiveDate {
218 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
219 match value {
220 SqlValue::Date(v) => Ok(*v),
221 SqlValue::DateTime(v) | SqlValue::SmallDateTime(v) => Ok(v.date()),
222 SqlValue::Null => Err(TypeError::UnexpectedNull),
223 _ => Err(TypeError::TypeMismatch {
224 expected: "NaiveDate",
225 actual: value.type_name().to_string(),
226 }),
227 }
228 }
229}
230
231#[cfg(feature = "chrono")]
232impl FromSql for chrono::NaiveTime {
233 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
234 match value {
235 SqlValue::Time(v) => Ok(*v),
236 SqlValue::DateTime(v) | SqlValue::SmallDateTime(v) => Ok(v.time()),
237 SqlValue::Null => Err(TypeError::UnexpectedNull),
238 _ => Err(TypeError::TypeMismatch {
239 expected: "NaiveTime",
240 actual: value.type_name().to_string(),
241 }),
242 }
243 }
244}
245
246#[cfg(feature = "chrono")]
247impl FromSql for chrono::NaiveDateTime {
248 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
249 match value {
250 SqlValue::DateTime(v) | SqlValue::SmallDateTime(v) => Ok(*v),
251 SqlValue::DateTimeOffset(v) => Ok(v.naive_utc()),
252 SqlValue::Null => Err(TypeError::UnexpectedNull),
253 _ => Err(TypeError::TypeMismatch {
254 expected: "NaiveDateTime",
255 actual: value.type_name().to_string(),
256 }),
257 }
258 }
259}
260
261#[cfg(feature = "chrono")]
262impl FromSql for chrono::DateTime<chrono::FixedOffset> {
263 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
264 match value {
265 SqlValue::DateTimeOffset(v) => Ok(*v),
266 SqlValue::Null => Err(TypeError::UnexpectedNull),
267 _ => Err(TypeError::TypeMismatch {
268 expected: "DateTime<FixedOffset>",
269 actual: value.type_name().to_string(),
270 }),
271 }
272 }
273}
274
275#[cfg(feature = "chrono")]
276impl FromSql for chrono::DateTime<chrono::Utc> {
277 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
278 match value {
279 SqlValue::DateTimeOffset(v) => Ok(v.to_utc()),
280 SqlValue::DateTime(v) | SqlValue::SmallDateTime(v) => {
281 Ok(chrono::DateTime::from_naive_utc_and_offset(*v, chrono::Utc))
282 }
283 SqlValue::Null => Err(TypeError::UnexpectedNull),
284 _ => Err(TypeError::TypeMismatch {
285 expected: "DateTime<Utc>",
286 actual: value.type_name().to_string(),
287 }),
288 }
289 }
290}
291
292#[cfg(feature = "json")]
293impl FromSql for serde_json::Value {
294 fn from_sql(value: &SqlValue) -> Result<Self, TypeError> {
295 match value {
296 SqlValue::Json(v) => Ok(v.clone()),
297 SqlValue::String(s) => serde_json::from_str(s).map_err(|e| TypeError::TypeMismatch {
298 expected: "JSON",
299 actual: format!("invalid JSON: {e}"),
300 }),
301 SqlValue::Null => Ok(serde_json::Value::Null),
302 _ => Err(TypeError::TypeMismatch {
303 expected: "JSON",
304 actual: value.type_name().to_string(),
305 }),
306 }
307 }
308}
309
310#[cfg(test)]
311#[allow(clippy::unwrap_used)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn test_from_sql_i32() {
317 let value = SqlValue::Int(42);
318 assert_eq!(i32::from_sql(&value).unwrap(), 42);
319 }
320
321 #[test]
322 fn test_from_sql_string() {
323 let value = SqlValue::String("hello".to_string());
324 assert_eq!(String::from_sql(&value).unwrap(), "hello");
325 }
326
327 #[test]
328 fn test_from_sql_null() {
329 let value = SqlValue::Null;
330 assert!(i32::from_sql(&value).is_err());
331 }
332
333 #[test]
334 fn test_from_sql_option() {
335 let value = SqlValue::Int(42);
336 assert_eq!(Option::<i32>::from_sql(&value).unwrap(), Some(42));
337
338 let null = SqlValue::Null;
339 assert_eq!(Option::<i32>::from_sql(&null).unwrap(), None);
340 }
341}