Skip to main content

nautilus_core/column/
from_value.rs

1//! `FromValue` trait and standard scalar/collection implementations.
2
3use std::collections::BTreeMap;
4
5use crate::error::Result;
6use crate::value::{Geography, Geometry, Value};
7
8/// Trait for converting database values to Rust types.
9///
10/// This trait enables type-safe decoding of individual column values
11/// during row deserialization in the selection API.
12pub trait FromValue: Sized {
13    /// Convert a Value reference to this type.
14    ///
15    /// Returns an error if the value is NULL, has the wrong type,
16    /// or cannot be converted.
17    fn from_value(value: &Value) -> Result<Self>;
18
19    /// Convert an owned Value to this type, avoiding clones when possible.
20    ///
21    /// The default implementation delegates to `from_value`. Types that hold
22    /// heap data (String, Json, Bytes) override this to take ownership
23    /// instead of cloning.
24    fn from_value_owned(value: Value) -> Result<Self> {
25        Self::from_value(&value)
26    }
27}
28
29impl FromValue for i64 {
30    fn from_value(value: &Value) -> Result<Self> {
31        match value {
32            Value::I64(v) => Ok(*v),
33            Value::Null => Err(crate::Error::TypeError("NULL value for i64".to_string())),
34            _ => Err(crate::Error::TypeError(format!(
35                "expected i64, got {:?}",
36                value
37            ))),
38        }
39    }
40}
41
42impl FromValue for i32 {
43    fn from_value(value: &Value) -> Result<Self> {
44        match value {
45            Value::I32(v) => Ok(*v),
46            Value::I64(v) => (*v).try_into().map_err(|_| {
47                crate::Error::TypeError(format!("i64 value {} doesn't fit in i32", v))
48            }),
49            Value::Null => Err(crate::Error::TypeError("NULL value for i32".to_string())),
50            _ => Err(crate::Error::TypeError(format!(
51                "expected i32, got {:?}",
52                value
53            ))),
54        }
55    }
56}
57
58impl FromValue for String {
59    fn from_value(value: &Value) -> Result<Self> {
60        match value {
61            Value::String(v) => Ok(v.clone()),
62            Value::Null => Err(crate::Error::TypeError("NULL value for String".to_string())),
63            _ => Err(crate::Error::TypeError(format!(
64                "expected String, got {:?}",
65                value
66            ))),
67        }
68    }
69
70    fn from_value_owned(value: Value) -> Result<Self> {
71        match value {
72            Value::String(v) => Ok(v),
73            Value::Null => Err(crate::Error::TypeError("NULL value for String".to_string())),
74            other => Err(crate::Error::TypeError(format!(
75                "expected String, got {:?}",
76                other
77            ))),
78        }
79    }
80}
81
82impl FromValue for bool {
83    fn from_value(value: &Value) -> Result<Self> {
84        match value {
85            Value::Bool(v) => Ok(*v),
86            Value::I32(v) => match *v {
87                0 => Ok(false),
88                1 => Ok(true),
89                other => Err(crate::Error::TypeError(format!(
90                    "expected bool-compatible i32 (0 or 1), got {}",
91                    other
92                ))),
93            },
94            Value::I64(v) => match *v {
95                0 => Ok(false),
96                1 => Ok(true),
97                other => Err(crate::Error::TypeError(format!(
98                    "expected bool-compatible i64 (0 or 1), got {}",
99                    other
100                ))),
101            },
102            Value::Null => Err(crate::Error::TypeError("NULL value for bool".to_string())),
103            _ => Err(crate::Error::TypeError(format!(
104                "expected bool, got {:?}",
105                value
106            ))),
107        }
108    }
109}
110
111impl FromValue for f64 {
112    fn from_value(value: &Value) -> Result<Self> {
113        match value {
114            Value::F64(v) => Ok(*v),
115            Value::Null => Err(crate::Error::TypeError("NULL value for f64".to_string())),
116            _ => Err(crate::Error::TypeError(format!(
117                "expected f64, got {:?}",
118                value
119            ))),
120        }
121    }
122}
123
124impl FromValue for f32 {
125    fn from_value(value: &Value) -> Result<Self> {
126        let value = match value {
127            Value::F64(v) if v.is_finite() => *v,
128            Value::I32(v) => *v as f64,
129            Value::I64(v) => *v as f64,
130            Value::Null => return Err(crate::Error::TypeError("NULL value for f32".to_string())),
131            _ => {
132                return Err(crate::Error::TypeError(format!(
133                    "expected f32-compatible number, got {:?}",
134                    value
135                )));
136            }
137        };
138
139        if value < f32::MIN as f64 || value > f32::MAX as f64 {
140            return Err(crate::Error::TypeError(format!(
141                "number {} does not fit in f32",
142                value
143            )));
144        }
145
146        Ok(value as f32)
147    }
148}
149
150impl FromValue for rust_decimal::Decimal {
151    fn from_value(value: &Value) -> Result<Self> {
152        match value {
153            Value::Decimal(v) => Ok(*v),
154            Value::String(v) => v.parse::<rust_decimal::Decimal>().map_err(|e| {
155                crate::Error::TypeError(format!(
156                    "failed to parse Decimal from string {:?}: {}",
157                    v, e
158                ))
159            }),
160            Value::Null => Err(crate::Error::TypeError(
161                "NULL value for Decimal".to_string(),
162            )),
163            _ => Err(crate::Error::TypeError(format!(
164                "expected Decimal, got {:?}",
165                value
166            ))),
167        }
168    }
169}
170
171impl FromValue for chrono::NaiveDateTime {
172    fn from_value(value: &Value) -> Result<Self> {
173        match value {
174            Value::DateTime(v) => Ok(*v),
175            Value::String(v) => chrono::DateTime::parse_from_rfc3339(v)
176                .map(|dt| dt.naive_utc())
177                .or_else(|_| chrono::NaiveDateTime::parse_from_str(v, "%Y-%m-%dT%H:%M:%S%.f"))
178                .or_else(|_| chrono::NaiveDateTime::parse_from_str(v, "%Y-%m-%d %H:%M:%S%.f"))
179                .map_err(|_| {
180                    crate::Error::TypeError(format!("failed to parse DateTime from string {:?}", v))
181                }),
182            Value::Null => Err(crate::Error::TypeError(
183                "NULL value for DateTime".to_string(),
184            )),
185            _ => Err(crate::Error::TypeError(format!(
186                "expected DateTime, got {:?}",
187                value
188            ))),
189        }
190    }
191}
192
193impl FromValue for uuid::Uuid {
194    fn from_value(value: &Value) -> Result<Self> {
195        match value {
196            Value::Uuid(v) => Ok(*v),
197            Value::String(v) => uuid::Uuid::parse_str(v).map_err(|e| {
198                crate::Error::TypeError(format!("failed to parse Uuid from string {:?}: {}", v, e))
199            }),
200            Value::Null => Err(crate::Error::TypeError("NULL value for Uuid".to_string())),
201            _ => Err(crate::Error::TypeError(format!(
202                "expected Uuid, got {:?}",
203                value
204            ))),
205        }
206    }
207}
208
209impl FromValue for serde_json::Value {
210    fn from_value(value: &Value) -> Result<Self> {
211        match value {
212            Value::Null => Err(crate::Error::TypeError("NULL value for Json".to_string())),
213            other => Ok(other.to_json_plain()),
214        }
215    }
216
217    fn from_value_owned(value: Value) -> Result<Self> {
218        match value {
219            Value::Null => Err(crate::Error::TypeError("NULL value for Json".to_string())),
220            other => Ok(other.to_json_plain()),
221        }
222    }
223}
224
225impl FromValue for BTreeMap<String, Option<String>> {
226    fn from_value(value: &Value) -> Result<Self> {
227        match value {
228            Value::Hstore(map) => Ok(map.clone()),
229            Value::Json(serde_json::Value::Object(map)) => decode_hstore_json_object(map),
230            Value::Null => Err(crate::Error::TypeError("NULL value for Hstore".to_string())),
231            other => Err(crate::Error::TypeError(format!(
232                "expected Hstore or Json object, got {:?}",
233                other
234            ))),
235        }
236    }
237
238    fn from_value_owned(value: Value) -> Result<Self> {
239        match value {
240            Value::Hstore(map) => Ok(map),
241            Value::Json(serde_json::Value::Object(map)) => decode_hstore_json_object(&map),
242            Value::Null => Err(crate::Error::TypeError("NULL value for Hstore".to_string())),
243            other => Err(crate::Error::TypeError(format!(
244                "expected Hstore or Json object, got {:?}",
245                other
246            ))),
247        }
248    }
249}
250
251impl FromValue for Geometry {
252    fn from_value(value: &Value) -> Result<Self> {
253        match value {
254            Value::Geometry(v) | Value::String(v) => Ok(Geometry::new(v.clone())),
255            Value::Null => Err(crate::Error::TypeError(
256                "NULL value for Geometry".to_string(),
257            )),
258            other => Err(crate::Error::TypeError(format!(
259                "expected Geometry or String, got {:?}",
260                other
261            ))),
262        }
263    }
264
265    fn from_value_owned(value: Value) -> Result<Self> {
266        match value {
267            Value::Geometry(v) | Value::String(v) => Ok(Geometry::new(v)),
268            Value::Null => Err(crate::Error::TypeError(
269                "NULL value for Geometry".to_string(),
270            )),
271            other => Err(crate::Error::TypeError(format!(
272                "expected Geometry or String, got {:?}",
273                other
274            ))),
275        }
276    }
277}
278
279impl FromValue for Geography {
280    fn from_value(value: &Value) -> Result<Self> {
281        match value {
282            Value::Geography(v) | Value::String(v) => Ok(Geography::new(v.clone())),
283            Value::Null => Err(crate::Error::TypeError(
284                "NULL value for Geography".to_string(),
285            )),
286            other => Err(crate::Error::TypeError(format!(
287                "expected Geography or String, got {:?}",
288                other
289            ))),
290        }
291    }
292
293    fn from_value_owned(value: Value) -> Result<Self> {
294        match value {
295            Value::Geography(v) | Value::String(v) => Ok(Geography::new(v)),
296            Value::Null => Err(crate::Error::TypeError(
297                "NULL value for Geography".to_string(),
298            )),
299            other => Err(crate::Error::TypeError(format!(
300                "expected Geography or String, got {:?}",
301                other
302            ))),
303        }
304    }
305}
306
307impl FromValue for Vec<u8> {
308    fn from_value(value: &Value) -> Result<Self> {
309        match value {
310            Value::Bytes(v) => Ok(v.clone()),
311            Value::Null => Err(crate::Error::TypeError("NULL value for Bytes".to_string())),
312            _ => Err(crate::Error::TypeError(format!(
313                "expected Bytes, got {:?}",
314                value
315            ))),
316        }
317    }
318
319    fn from_value_owned(value: Value) -> Result<Self> {
320        match value {
321            Value::Bytes(v) => Ok(v),
322            Value::Null => Err(crate::Error::TypeError("NULL value for Bytes".to_string())),
323            other => Err(crate::Error::TypeError(format!(
324                "expected Bytes, got {:?}",
325                other
326            ))),
327        }
328    }
329}
330
331impl FromValue for Vec<f32> {
332    fn from_value(value: &Value) -> Result<Self> {
333        match value {
334            Value::Vector(values) => Ok(values.clone()),
335            Value::Array(items) => items.iter().map(f32::from_value).collect(),
336            Value::Json(json_value) => decode_json_array(json_value, f32::from_value),
337            Value::Null => Err(crate::Error::TypeError(
338                "NULL value for Vec<f32>".to_string(),
339            )),
340            _ => Err(crate::Error::TypeError(format!(
341                "expected Vector, Array, or Json for Vec<f32>, got {:?}",
342                value
343            ))),
344        }
345    }
346
347    fn from_value_owned(value: Value) -> Result<Self> {
348        match value {
349            Value::Vector(values) => Ok(values),
350            other => Self::from_value(&other),
351        }
352    }
353}
354
355impl<T: FromValue> FromValue for Option<T> {
356    fn from_value(value: &Value) -> Result<Self> {
357        match value {
358            Value::Null => Ok(None),
359            _ => T::from_value(value).map(Some),
360        }
361    }
362
363    fn from_value_owned(value: Value) -> Result<Self> {
364        match value {
365            Value::Null => Ok(None),
366            _ => T::from_value_owned(value).map(Some),
367        }
368    }
369}
370
371/// Generates `FromValue` implementations for `Vec<T>` and `Vec<Vec<T>>` for
372/// scalar types that already implement `FromValue`.
373///
374/// Both variants support:
375/// - Native PostgreSQL array values (`Value::Array` / `Value::Array2D`)
376/// - JSON-encoded arrays from MySQL and SQLite (`Value::Json`)
377macro_rules! impl_vec_from_value {
378    ($T:ty) => {
379        impl FromValue for Vec<$T> {
380            fn from_value(value: &Value) -> Result<Self> {
381                match value {
382                    Value::Array(items) => items.iter().map(<$T>::from_value).collect(),
383                    Value::Json(json_value) => decode_json_array(json_value, <$T>::from_value),
384                    Value::Null => Err(crate::Error::TypeError(
385                        concat!("NULL value for Vec<", stringify!($T), ">").to_string(),
386                    )),
387                    _ => Err(crate::Error::TypeError(format!(
388                        concat!(
389                            "expected Array or Json for Vec<",
390                            stringify!($T),
391                            ">, got {:?}"
392                        ),
393                        value
394                    ))),
395                }
396            }
397        }
398
399        impl FromValue for Vec<Vec<$T>> {
400            fn from_value(value: &Value) -> Result<Self> {
401                match value {
402                    Value::Array2D(rows) => rows
403                        .iter()
404                        .map(|row| row.iter().map(<$T>::from_value).collect())
405                        .collect(),
406                    Value::Json(json_value) => decode_json_2d_array(json_value, <$T>::from_value),
407                    Value::Null => Err(crate::Error::TypeError(
408                        concat!("NULL value for Vec<Vec<", stringify!($T), ">>").to_string(),
409                    )),
410                    _ => Err(crate::Error::TypeError(format!(
411                        concat!(
412                            "expected Array2D or Json for Vec<Vec<",
413                            stringify!($T),
414                            ">>, got {:?}"
415                        ),
416                        value
417                    ))),
418                }
419            }
420        }
421    };
422}
423
424impl_vec_from_value!(String);
425impl_vec_from_value!(i32);
426impl_vec_from_value!(i64);
427impl_vec_from_value!(f64);
428impl_vec_from_value!(bool);
429impl_vec_from_value!(rust_decimal::Decimal);
430impl_vec_from_value!(chrono::NaiveDateTime);
431impl_vec_from_value!(uuid::Uuid);
432impl_vec_from_value!(serde_json::Value);
433impl_vec_from_value!(BTreeMap<String, Option<String>>);
434
435fn decode_json_array<T, F>(json_value: &serde_json::Value, decoder: F) -> Result<Vec<T>>
436where
437    F: Fn(&Value) -> Result<T>,
438{
439    if let serde_json::Value::Array(arr) = json_value {
440        let mut result = Vec::with_capacity(arr.len());
441        for json_item in arr {
442            let value = crate::value::json_to_value_ref(json_item);
443            result.push(decoder(&value)?);
444        }
445        Ok(result)
446    } else {
447        Err(crate::Error::TypeError(format!(
448            "expected JSON array, got {:?}",
449            json_value
450        )))
451    }
452}
453
454fn decode_json_2d_array<T, F>(json_value: &serde_json::Value, decoder: F) -> Result<Vec<Vec<T>>>
455where
456    F: Fn(&Value) -> Result<T>,
457{
458    if let serde_json::Value::Array(outer_arr) = json_value {
459        let mut result = Vec::with_capacity(outer_arr.len());
460        for json_row in outer_arr {
461            if let serde_json::Value::Array(inner_arr) = json_row {
462                let mut row = Vec::with_capacity(inner_arr.len());
463                for json_item in inner_arr {
464                    let value = crate::value::json_to_value_ref(json_item);
465                    row.push(decoder(&value)?);
466                }
467                result.push(row);
468            } else {
469                return Err(crate::Error::TypeError(format!(
470                    "expected inner JSON array, got {:?}",
471                    json_row
472                )));
473            }
474        }
475        Ok(result)
476    } else {
477        Err(crate::Error::TypeError(format!(
478            "expected JSON 2D array, got {:?}",
479            json_value
480        )))
481    }
482}
483
484fn decode_hstore_json_object(
485    object: &serde_json::Map<String, serde_json::Value>,
486) -> Result<BTreeMap<String, Option<String>>> {
487    let mut decoded = BTreeMap::new();
488    for (key, value) in object {
489        let mapped = match value {
490            serde_json::Value::String(item) => Some(item.clone()),
491            serde_json::Value::Null => None,
492            other => {
493                return Err(crate::Error::TypeError(format!(
494                    "expected Hstore JSON value to be string or null for key {:?}, got {:?}",
495                    key, other
496                )));
497            }
498        };
499        decoded.insert(key.clone(), mapped);
500    }
501    Ok(decoded)
502}
503
504#[cfg(test)]
505mod tests {
506    use std::collections::BTreeMap;
507
508    use super::FromValue;
509    use crate::Value;
510
511    #[test]
512    fn decimal_uuid_datetime_and_json_arrays_decode_from_json_storage() {
513        let decimals = Value::Json(serde_json::json!(["12.34", "56.78"]));
514        let uuids = Value::Json(serde_json::json!([
515            "550e8400-e29b-41d4-a716-446655440000",
516            "123e4567-e89b-12d3-a456-426614174000"
517        ]));
518        let datetimes = Value::Json(serde_json::json!([
519            "2026-02-18T10:30:45Z",
520            "2026-02-19T11:31:46Z"
521        ]));
522        let jsons = Value::Json(serde_json::json!([
523            {"name": "Alice"},
524            42,
525            true
526        ]));
527
528        let decimals: Vec<rust_decimal::Decimal> = Vec::from_value(&decimals).unwrap();
529        let uuids: Vec<uuid::Uuid> = Vec::from_value(&uuids).unwrap();
530        let datetimes: Vec<chrono::NaiveDateTime> = Vec::from_value(&datetimes).unwrap();
531        let jsons: Vec<serde_json::Value> = Vec::from_value(&jsons).unwrap();
532
533        assert_eq!(decimals.len(), 2);
534        assert_eq!(uuids.len(), 2);
535        assert_eq!(datetimes.len(), 2);
536        assert_eq!(
537            jsons,
538            vec![
539                serde_json::json!({"name": "Alice"}),
540                serde_json::json!(42),
541                serde_json::json!(true),
542            ]
543        );
544    }
545
546    #[test]
547    fn uuid_and_decimal_scalars_accept_string_values() {
548        let uuid = uuid::Uuid::from_value(&Value::String(
549            "550e8400-e29b-41d4-a716-446655440000".to_string(),
550        ))
551        .unwrap();
552        let decimal =
553            rust_decimal::Decimal::from_value(&Value::String("12.34".to_string())).unwrap();
554
555        assert_eq!(uuid.to_string(), "550e8400-e29b-41d4-a716-446655440000");
556        assert_eq!(decimal.to_string(), "12.34");
557    }
558
559    #[test]
560    fn hstore_scalar_accepts_native_and_json_object_values() {
561        let native = Value::Hstore(BTreeMap::from([
562            ("display_name".to_string(), Some("Bob".to_string())),
563            ("nickname".to_string(), None),
564        ]));
565        let json = Value::Json(serde_json::json!({
566            "display_name": "Bob",
567            "nickname": null
568        }));
569
570        let native_map = BTreeMap::<String, Option<String>>::from_value(&native).unwrap();
571        let json_map = BTreeMap::<String, Option<String>>::from_value(&json).unwrap();
572
573        assert_eq!(native_map, json_map);
574        assert_eq!(native_map["display_name"], Some("Bob".to_string()));
575        assert_eq!(native_map["nickname"], None);
576    }
577
578    #[test]
579    fn hstore_arrays_decode_from_json_storage() {
580        let json = Value::Json(serde_json::json!([
581            {"display_name": "Bob", "nickname": null},
582            {"display_name": "OpenAI", "nickname": "oai"}
583        ]));
584
585        let decoded: Vec<BTreeMap<String, Option<String>>> = Vec::from_value(&json).unwrap();
586
587        assert_eq!(decoded.len(), 2);
588        assert_eq!(decoded[0]["display_name"], Some("Bob".to_string()));
589        assert_eq!(decoded[0]["nickname"], None);
590        assert_eq!(decoded[1]["nickname"], Some("oai".to_string()));
591    }
592
593    #[test]
594    fn vector_decodes_from_native_and_json_values() {
595        let native = Value::Vector(vec![0.1, 0.2, 0.3]);
596        let json = Value::Json(serde_json::json!([0.1, 0.2, 0.3]));
597
598        assert_eq!(
599            Vec::<f32>::from_value(&native).unwrap(),
600            vec![0.1, 0.2, 0.3]
601        );
602        assert_eq!(Vec::<f32>::from_value(&json).unwrap(), vec![0.1, 0.2, 0.3]);
603    }
604}