Skip to main content

rust_ef/provider/
db_value.rs

1//! `DbValue` — typed database parameter value and conversion error type.
2
3use std::fmt;
4
5/// A typed database parameter value for parameterized queries.
6///
7/// Native variants (`DateTime`/`NaiveDateTime`/`NaiveDate`/`Uuid`/`Decimal`)
8/// are enabled by the `chrono`/`uuid`/`decimal` Cargo features. When enabled,
9/// the PostgreSQL provider binds these via `tokio_postgres`'s binary protocol
10/// (`with-chrono-0_4`/`with-uuid-1` features) for type-safe, lossless
11/// parameter transmission. SQLite and MySQL providers collapse native
12/// variants to their canonical string representation (matching v1.0 behavior)
13/// since neither driver requires native type binding.
14#[derive(Debug, Clone, PartialEq)]
15pub enum DbValue {
16    Null,
17    Bool(bool),
18    I16(i16),
19    I32(i32),
20    I64(i64),
21    F32(f32),
22    F64(f64),
23    String(String),
24    Bytes(Vec<u8>),
25    /// UTC timestamp — bound natively as `TIMESTAMPTZ` on PostgreSQL.
26    #[cfg(feature = "chrono")]
27    DateTime(chrono::DateTime<chrono::Utc>),
28    /// Naive (timezone-less) timestamp — bound natively as `TIMESTAMP` on PG.
29    #[cfg(feature = "chrono")]
30    NaiveDateTime(chrono::NaiveDateTime),
31    /// Calendar date — bound natively as `DATE` on PostgreSQL.
32    #[cfg(feature = "chrono")]
33    NaiveDate(chrono::NaiveDate),
34    /// UUID — bound natively as `UUID` on PostgreSQL.
35    #[cfg(feature = "uuid")]
36    Uuid(uuid::Uuid),
37    /// Fixed-precision decimal — bound as `NUMERIC` string on PostgreSQL
38    /// (tokio_postgres lacks a native `rust_decimal` adapter; the string
39    /// form round-trips losslessly through PG's `NUMERIC` type).
40    #[cfg(feature = "decimal")]
41    Decimal(rust_decimal::Decimal),
42}
43
44impl fmt::Display for DbValue {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        match self {
47            DbValue::Null => write!(f, "NULL"),
48            DbValue::Bool(v) => write!(f, "{}", if *v { "TRUE" } else { "FALSE" }),
49            DbValue::I16(v) => write!(f, "{}", v),
50            DbValue::I32(v) => write!(f, "{}", v),
51            DbValue::I64(v) => write!(f, "{}", v),
52            DbValue::F32(v) => write!(f, "{}", v),
53            DbValue::F64(v) => write!(f, "{}", v),
54            DbValue::String(v) => write!(f, "'{}'", v.replace('\'', "''")),
55            DbValue::Bytes(v) => write!(f, "{}", hex::encode(v)),
56            #[cfg(feature = "chrono")]
57            DbValue::DateTime(v) => write!(f, "'{}'", v.to_rfc3339()),
58            #[cfg(feature = "chrono")]
59            DbValue::NaiveDateTime(v) => write!(f, "'{}'", v),
60            #[cfg(feature = "chrono")]
61            DbValue::NaiveDate(v) => write!(f, "'{}'", v),
62            #[cfg(feature = "uuid")]
63            DbValue::Uuid(v) => write!(f, "'{}'", v),
64            #[cfg(feature = "decimal")]
65            DbValue::Decimal(v) => write!(f, "'{}'", v),
66        }
67    }
68}
69
70mod hex {
71    pub fn encode(bytes: &[u8]) -> String {
72        bytes.iter().map(|b| format!("{:02x}", b)).collect()
73    }
74}
75
76impl From<i32> for DbValue {
77    fn from(v: i32) -> Self {
78        DbValue::I32(v)
79    }
80}
81impl From<&i32> for DbValue {
82    fn from(v: &i32) -> Self {
83        DbValue::I32(*v)
84    }
85}
86impl From<i64> for DbValue {
87    fn from(v: i64) -> Self {
88        DbValue::I64(v)
89    }
90}
91impl From<&i64> for DbValue {
92    fn from(v: &i64) -> Self {
93        DbValue::I64(*v)
94    }
95}
96impl From<String> for DbValue {
97    fn from(v: String) -> Self {
98        DbValue::String(v)
99    }
100}
101impl From<&str> for DbValue {
102    fn from(v: &str) -> Self {
103        DbValue::String(v.to_string())
104    }
105}
106impl From<bool> for DbValue {
107    fn from(v: bool) -> Self {
108        DbValue::Bool(v)
109    }
110}
111impl From<f64> for DbValue {
112    fn from(v: f64) -> Self {
113        DbValue::F64(v)
114    }
115}
116impl From<f32> for DbValue {
117    fn from(v: f32) -> Self {
118        DbValue::F32(v)
119    }
120}
121impl From<i16> for DbValue {
122    fn from(v: i16) -> Self {
123        DbValue::I16(v)
124    }
125}
126impl From<Vec<u8>> for DbValue {
127    fn from(v: Vec<u8>) -> Self {
128        DbValue::Bytes(v)
129    }
130}
131
132// --- Feature-gated From impls for chrono / uuid / decimal ---
133//
134// These construct native `DbValue` variants (not `String`) so that the
135// PostgreSQL provider can bind them via tokio_postgres's binary protocol.
136// SQLite and MySQL providers collapse these variants to their canonical
137// string form in their respective `type_conversion.rs`.
138
139#[cfg(feature = "chrono")]
140impl From<chrono::DateTime<chrono::Utc>> for DbValue {
141    fn from(dt: chrono::DateTime<chrono::Utc>) -> Self {
142        DbValue::DateTime(dt)
143    }
144}
145
146#[cfg(feature = "chrono")]
147impl From<chrono::NaiveDateTime> for DbValue {
148    fn from(ndt: chrono::NaiveDateTime) -> Self {
149        DbValue::NaiveDateTime(ndt)
150    }
151}
152
153#[cfg(feature = "chrono")]
154impl From<chrono::NaiveDate> for DbValue {
155    fn from(nd: chrono::NaiveDate) -> Self {
156        DbValue::NaiveDate(nd)
157    }
158}
159
160#[cfg(feature = "uuid")]
161impl From<uuid::Uuid> for DbValue {
162    fn from(u: uuid::Uuid) -> Self {
163        DbValue::Uuid(u)
164    }
165}
166
167#[cfg(feature = "decimal")]
168impl From<rust_decimal::Decimal> for DbValue {
169    fn from(d: rust_decimal::Decimal) -> Self {
170        DbValue::Decimal(d)
171    }
172}
173impl<T> From<Option<T>> for DbValue
174where
175    T: Into<DbValue>,
176{
177    fn from(v: Option<T>) -> Self {
178        match v {
179            Some(val) => val.into(),
180            None => DbValue::Null,
181        }
182    }
183}
184
185/// Error returned when a [`DbValue`] cannot be converted to the requested type.
186///
187/// Used by `TryFrom<DbValue>` impls for `i32`/`i64`/`f64`/`String`/`bool`/...
188/// and by `QueryBuilder::min_internal` / `max_internal` to surface type
189/// mismatches when reading aggregation results.
190#[derive(Debug, Clone, PartialEq)]
191pub struct DbValueConvertError {
192    /// The [`DbValue`] that could not be converted.
193    pub source: DbValue,
194    /// The target Rust type name (e.g. `"i32"`).
195    pub target_type: &'static str,
196}
197
198impl fmt::Display for DbValueConvertError {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        write!(
201            f,
202            "cannot convert {:?} to {}",
203            self.source, self.target_type
204        )
205    }
206}
207
208impl std::error::Error for DbValueConvertError {}
209
210impl From<DbValueConvertError> for crate::error::EFError {
211    fn from(e: DbValueConvertError) -> Self {
212        crate::error::EFError::type_conversion(e.to_string())
213    }
214}