Skip to main content

siderite_orm/
value.rs

1//! Backend-neutral scalar values used for bind parameters and decoded rows.
2
3use chrono::{DateTime, NaiveDate, NaiveTime, Utc};
4use rust_decimal::Decimal;
5use serde::Serialize;
6use uuid::Uuid;
7
8/// A bindable, backend-neutral value.
9///
10/// Values are **always** sent to the database as bind parameters, never
11/// interpolated into query text.
12///
13/// Backends without a native type for a variant store a canonical text form
14/// (see `BACKENDS.md`); [`DbType::from_value`](crate::DbType::from_value)
15/// accepts both the native and the text form, so decoding does not depend on
16/// the backend.
17///
18/// `Value` serializes (untagged) for JSON output of dynamic rows. It does not
19/// deserialize: an untagged `Text` and `Timestamp` would be ambiguous.
20#[derive(Debug, Clone, PartialEq, Serialize)]
21#[serde(untagged)]
22pub enum Value {
23    /// SQL `NULL` / missing value.
24    Null,
25    /// Boolean.
26    Bool(bool),
27    /// 64-bit signed integer.
28    Int(i64),
29    /// 64-bit float.
30    Float(f64),
31    /// Exact decimal.
32    Decimal(Decimal),
33    /// UTF-8 text.
34    Text(String),
35    /// Raw bytes.
36    Bytes(Vec<u8>),
37    /// UUID.
38    Uuid(Uuid),
39    /// Calendar date.
40    Date(NaiveDate),
41    /// Time of day.
42    Time(NaiveTime),
43    /// Instant in UTC.
44    Timestamp(DateTime<Utc>),
45    /// Structured JSON document.
46    Json(serde_json::Value),
47}
48
49impl Value {
50    /// Whether this is [`Value::Null`].
51    pub fn is_null(&self) -> bool {
52        matches!(self, Value::Null)
53    }
54
55    /// Short name of the variant, for decode error messages.
56    pub fn kind(&self) -> &'static str {
57        match self {
58            Value::Null => "null",
59            Value::Bool(_) => "bool",
60            Value::Int(_) => "int",
61            Value::Float(_) => "float",
62            Value::Decimal(_) => "decimal",
63            Value::Text(_) => "text",
64            Value::Bytes(_) => "bytes",
65            Value::Uuid(_) => "uuid",
66            Value::Date(_) => "date",
67            Value::Time(_) => "time",
68            Value::Timestamp(_) => "timestamp",
69            Value::Json(_) => "json",
70        }
71    }
72}
73
74macro_rules! impl_from {
75    ($($t:ty => $variant:ident $(as $cast:ty)?),* $(,)?) => {$(
76        impl From<$t> for Value {
77            fn from(v: $t) -> Self { Value::$variant(v $(as $cast)?) }
78        }
79    )*};
80}
81
82impl_from!(
83    bool => Bool,
84    i8 => Int as i64, i16 => Int as i64, i32 => Int as i64, i64 => Int,
85    u8 => Int as i64, u16 => Int as i64, u32 => Int as i64,
86    f32 => Float as f64, f64 => Float,
87    String => Text, Vec<u8> => Bytes, serde_json::Value => Json,
88    Decimal => Decimal, Uuid => Uuid, NaiveDate => Date, NaiveTime => Time,
89    DateTime<Utc> => Timestamp,
90);
91
92impl From<&str> for Value {
93    fn from(v: &str) -> Self {
94        Value::Text(v.to_owned())
95    }
96}
97
98impl<T: Into<Value>> From<Option<T>> for Value {
99    fn from(v: Option<T>) -> Self {
100        v.map_or(Value::Null, Into::into)
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn conversions() {
110        assert_eq!(Value::from(3_i32), Value::Int(3));
111        assert_eq!(Value::from("a"), Value::Text("a".into()));
112        assert_eq!(Value::from(None::<i64>), Value::Null);
113        assert_eq!(Value::from(Some(true)), Value::Bool(true));
114        assert_eq!(Value::from(Uuid::nil()).kind(), "uuid");
115    }
116}