Skip to main content

sqlx_odbc/
value.rs

1use std::borrow::Cow;
2
3/// A small owned ODBC value representation used by unit tests and Any-mapping work.
4#[derive(Debug, Clone, PartialEq)]
5pub struct OdbcValue {
6    kind: OdbcValueKind,
7}
8
9impl OdbcValue {
10    /// Creates a new value from a raw kind.
11    pub fn new(kind: OdbcValueKind) -> Self {
12        Self { kind }
13    }
14
15    /// Returns the raw value kind.
16    pub fn kind(&self) -> &OdbcValueKind {
17        &self.kind
18    }
19
20    /// Returns whether this value is NULL.
21    pub fn is_null(&self) -> bool {
22        matches!(self.kind, OdbcValueKind::Null)
23    }
24
25    /// Returns this value as a signed integer where possible.
26    pub fn as_i64(&self) -> Option<i64> {
27        match &self.kind {
28            OdbcValueKind::TinyInt(value) => Some(i64::from(*value)),
29            OdbcValueKind::SmallInt(value) => Some(i64::from(*value)),
30            OdbcValueKind::Integer(value) => Some(i64::from(*value)),
31            OdbcValueKind::BigInt(value) => Some(*value),
32            OdbcValueKind::Text(value) => parse_integer_text(value),
33            _ => None,
34        }
35    }
36
37    /// Returns this value as `f64` where possible.
38    pub fn as_f64(&self) -> Option<f64> {
39        match &self.kind {
40            OdbcValueKind::Real(value) => Some(f64::from(*value)),
41            OdbcValueKind::Double(value) => Some(*value),
42            OdbcValueKind::TinyInt(value) => Some(f64::from(*value)),
43            OdbcValueKind::SmallInt(value) => Some(f64::from(*value)),
44            OdbcValueKind::Integer(value) => Some(f64::from(*value)),
45            OdbcValueKind::BigInt(value) => Some(*value as f64),
46            OdbcValueKind::Text(value) => value.trim().parse().ok(),
47            _ => None,
48        }
49    }
50
51    /// Returns this value as text where possible.
52    pub fn as_str(&self) -> Option<Cow<'_, str>> {
53        match &self.kind {
54            OdbcValueKind::Text(value) => Some(Cow::Borrowed(value)),
55            _ => None,
56        }
57    }
58
59    /// Returns this value as bytes where possible.
60    pub fn as_bytes(&self) -> Option<Cow<'_, [u8]>> {
61        match &self.kind {
62            OdbcValueKind::Binary(value) => Some(Cow::Borrowed(value)),
63            _ => None,
64        }
65    }
66}
67
68impl sqlx_core::value::Value for OdbcValue {
69    type Database = crate::Odbc;
70
71    fn as_ref(&self) -> <Self::Database as sqlx_core::database::Database>::ValueRef<'_> {
72        OdbcValueRef { value: self }
73    }
74
75    fn type_info(&self) -> Cow<'_, crate::OdbcTypeInfo> {
76        Cow::Owned(self.kind.type_info())
77    }
78
79    fn is_null(&self) -> bool {
80        self.is_null()
81    }
82}
83
84/// Borrowed ODBC value reference.
85#[derive(Debug, Clone, Copy)]
86pub struct OdbcValueRef<'r> {
87    value: &'r OdbcValue,
88}
89
90impl<'r> OdbcValueRef<'r> {
91    /// Returns this value as a signed integer where possible.
92    pub fn as_i64(&self) -> Option<i64> {
93        self.value.as_i64()
94    }
95
96    /// Returns this value as `f64` where possible.
97    pub fn as_f64(&self) -> Option<f64> {
98        self.value.as_f64()
99    }
100
101    /// Returns this value as borrowed text where possible.
102    pub fn as_str(&self) -> Option<&'r str> {
103        match &self.value.kind {
104            OdbcValueKind::Text(value) => Some(value),
105            _ => None,
106        }
107    }
108
109    /// Returns this value as borrowed bytes where possible.
110    pub fn as_bytes(&self) -> Option<&'r [u8]> {
111        match &self.value.kind {
112            OdbcValueKind::Binary(value) => Some(value),
113            _ => None,
114        }
115    }
116
117    /// Returns this value as a boolean where possible.
118    pub fn as_bool(&self) -> Option<bool> {
119        match &self.value.kind {
120            OdbcValueKind::Bit(value) => Some(*value),
121            OdbcValueKind::TinyInt(value) => Some(*value != 0),
122            OdbcValueKind::SmallInt(value) => Some(*value != 0),
123            OdbcValueKind::Integer(value) => Some(*value != 0),
124            OdbcValueKind::BigInt(value) => Some(*value != 0),
125            OdbcValueKind::Real(value) => Some(*value != 0.0),
126            OdbcValueKind::Double(value) => Some(*value != 0.0),
127            OdbcValueKind::Text(value) => parse_bool_text(value),
128            _ => None,
129        }
130    }
131}
132
133impl<'r> sqlx_core::value::ValueRef<'r> for OdbcValueRef<'r> {
134    type Database = crate::Odbc;
135
136    fn to_owned(&self) -> OdbcValue {
137        self.value.clone()
138    }
139
140    fn type_info(&self) -> Cow<'_, crate::OdbcTypeInfo> {
141        Cow::Owned(self.value.kind.type_info())
142    }
143
144    fn is_null(&self) -> bool {
145        self.value.is_null()
146    }
147}
148
149macro_rules! impl_decode_integer {
150    ($ty:ty) => {
151        impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for $ty {
152            fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
153                value
154                    .as_i64()
155                    .and_then(|value| Self::try_from(value).ok())
156                    .ok_or_else(|| format!("ODBC: cannot decode {}", stringify!($ty)).into())
157            }
158        }
159    };
160}
161
162impl_decode_integer!(i8);
163impl_decode_integer!(i16);
164impl_decode_integer!(i32);
165impl_decode_integer!(i64);
166impl_decode_integer!(u8);
167impl_decode_integer!(u16);
168impl_decode_integer!(u32);
169impl_decode_integer!(u64);
170
171impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for bool {
172    fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
173        value
174            .as_bool()
175            .ok_or_else(|| "ODBC: cannot decode bool".into())
176    }
177}
178
179impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for f32 {
180    fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
181        value
182            .as_f64()
183            .map(|value| value as f32)
184            .ok_or_else(|| "ODBC: cannot decode f32".into())
185    }
186}
187
188impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for f64 {
189    fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
190        value
191            .as_f64()
192            .ok_or_else(|| "ODBC: cannot decode f64".into())
193    }
194}
195
196impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for String {
197    fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
198        if let Some(text) = value.as_str() {
199            return Ok(text.to_owned());
200        }
201
202        if let Some(bytes) = value.as_bytes() {
203            return Ok(String::from_utf8(bytes.to_vec())?);
204        }
205
206        Err("ODBC: cannot decode String".into())
207    }
208}
209
210impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for &'r str {
211    fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
212        if let Some(text) = value.as_str() {
213            return Ok(text);
214        }
215
216        Err("ODBC: cannot decode &str".into())
217    }
218}
219
220impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for Vec<u8> {
221    fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
222        value
223            .as_bytes()
224            .map(<[u8]>::to_vec)
225            .ok_or_else(|| "ODBC: cannot decode Vec<u8>".into())
226    }
227}
228
229impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for &'r [u8] {
230    fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
231        value
232            .as_bytes()
233            .ok_or_else(|| "ODBC: cannot decode &[u8]".into())
234    }
235}
236
237impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for odbc_api::sys::Date {
238    fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
239        match value.value.kind() {
240            OdbcValueKind::Date(value) => Ok(*value),
241            _ => Err("ODBC: cannot decode Date".into()),
242        }
243    }
244}
245
246impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for odbc_api::sys::Time {
247    fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
248        match value.value.kind() {
249            OdbcValueKind::Time(value) => Ok(*value),
250            _ => Err("ODBC: cannot decode Time".into()),
251        }
252    }
253}
254
255impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for odbc_api::sys::Timestamp {
256    fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
257        match value.value.kind() {
258            OdbcValueKind::Timestamp(value) => Ok(*value),
259            _ => Err("ODBC: cannot decode Timestamp".into()),
260        }
261    }
262}
263
264fn parse_bool_text(value: &str) -> Option<bool> {
265    match value.trim() {
266        "0" | "0.0" | "false" | "FALSE" | "f" | "F" => Some(false),
267        "1" | "1.0" | "true" | "TRUE" | "t" | "T" => Some(true),
268        value => value
269            .parse::<f64>()
270            .map(|value| value != 0.0)
271            .or_else(|_| value.parse::<i64>().map(|value| value != 0))
272            .ok(),
273    }
274}
275
276fn parse_integer_text(value: &str) -> Option<i64> {
277    let value = value.trim();
278
279    if let Ok(value) = value.parse() {
280        return Some(value);
281    }
282
283    let (integer, fraction) = value.split_once('.')?;
284
285    if fraction.chars().all(|ch| ch == '0') {
286        integer.parse().ok()
287    } else {
288        None
289    }
290}
291
292/// Supported owned ODBC value kinds.
293#[derive(Debug, Clone, PartialEq)]
294pub enum OdbcValueKind {
295    /// NULL value.
296    Null,
297    /// 8-bit signed integer.
298    TinyInt(i8),
299    /// 16-bit signed integer.
300    SmallInt(i16),
301    /// 32-bit signed integer.
302    Integer(i32),
303    /// 64-bit signed integer.
304    BigInt(i64),
305    /// 32-bit float.
306    Real(f32),
307    /// 64-bit float.
308    Double(f64),
309    /// Boolean value.
310    Bit(bool),
311    /// Text value.
312    Text(String),
313    /// Binary value.
314    Binary(Vec<u8>),
315    /// Date value.
316    Date(odbc_api::sys::Date),
317    /// Time value.
318    Time(odbc_api::sys::Time),
319    /// Timestamp value.
320    Timestamp(odbc_api::sys::Timestamp),
321}
322
323impl OdbcValueKind {
324    fn type_info(&self) -> crate::OdbcTypeInfo {
325        let data_type = match self {
326            Self::Null => odbc_api::DataType::Unknown,
327            Self::TinyInt(_) => odbc_api::DataType::TinyInt,
328            Self::SmallInt(_) => odbc_api::DataType::SmallInt,
329            Self::Integer(_) => odbc_api::DataType::Integer,
330            Self::BigInt(_) => odbc_api::DataType::BigInt,
331            Self::Real(_) => odbc_api::DataType::Real,
332            Self::Double(_) => odbc_api::DataType::Double,
333            Self::Bit(_) => odbc_api::DataType::Bit,
334            Self::Text(_) => odbc_api::DataType::WVarchar { length: None },
335            Self::Binary(_) => odbc_api::DataType::Varbinary { length: None },
336            Self::Date(_) => odbc_api::DataType::Date,
337            Self::Time(_) => odbc_api::DataType::Time { precision: 0 },
338            Self::Timestamp(_) => odbc_api::DataType::Timestamp { precision: 6 },
339        };
340
341        crate::OdbcTypeInfo::new(data_type)
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348
349    #[test]
350    fn integer_values_convert_to_i64() {
351        assert_eq!(OdbcValue::new(OdbcValueKind::TinyInt(1)).as_i64(), Some(1));
352        assert_eq!(OdbcValue::new(OdbcValueKind::SmallInt(2)).as_i64(), Some(2));
353        assert_eq!(OdbcValue::new(OdbcValueKind::Integer(3)).as_i64(), Some(3));
354        assert_eq!(OdbcValue::new(OdbcValueKind::BigInt(4)).as_i64(), Some(4));
355        assert_eq!(
356            OdbcValue::new(OdbcValueKind::Text("42.000".to_owned())).as_i64(),
357            Some(42)
358        );
359        assert_eq!(
360            OdbcValue::new(OdbcValueKind::Text("42.5".to_owned())).as_i64(),
361            None
362        );
363    }
364
365    #[test]
366    fn text_numeric_values_convert_to_float() {
367        assert_eq!(
368            OdbcValue::new(OdbcValueKind::Text("42.5".to_owned())).as_f64(),
369            Some(42.5)
370        );
371    }
372
373    #[test]
374    fn text_and_bytes_borrow_from_value() {
375        let text = OdbcValue::new(OdbcValueKind::Text("hello".to_owned()));
376        assert_eq!(text.as_str().as_deref(), Some("hello"));
377
378        let bytes = OdbcValue::new(OdbcValueKind::Binary(vec![1, 2, 3]));
379        assert_eq!(bytes.as_bytes().as_deref(), Some(&[1, 2, 3][..]));
380    }
381
382    #[test]
383    fn null_reports_null() {
384        assert!(OdbcValue::new(OdbcValueKind::Null).is_null());
385    }
386
387    #[test]
388    fn borrowed_values_decode_basic_scalars() {
389        use sqlx_core::decode::Decode;
390        use sqlx_core::value::Value;
391
392        let int = OdbcValue::new(OdbcValueKind::BigInt(42));
393        assert_eq!(
394            <i32 as Decode<crate::Odbc>>::decode(int.as_ref()).unwrap(),
395            42
396        );
397
398        let truthy = OdbcValue::new(OdbcValueKind::Text("true".to_owned()));
399        assert!(<bool as Decode<crate::Odbc>>::decode(truthy.as_ref()).unwrap());
400
401        let text = OdbcValue::new(OdbcValueKind::Text("hello".to_owned()));
402        assert_eq!(
403            <String as Decode<crate::Odbc>>::decode(text.as_ref()).unwrap(),
404            "hello"
405        );
406
407        let bytes = OdbcValue::new(OdbcValueKind::Binary(vec![1, 2, 3]));
408        assert_eq!(
409            <Vec<u8> as Decode<crate::Odbc>>::decode(bytes.as_ref()).unwrap(),
410            vec![1, 2, 3]
411        );
412    }
413
414    #[test]
415    fn borrowed_values_decode_temporal_scalars() {
416        use sqlx_core::decode::Decode;
417        use sqlx_core::value::Value;
418
419        let date = odbc_api::sys::Date {
420            year: 2026,
421            month: 5,
422            day: 29,
423        };
424        let date_value = OdbcValue::new(OdbcValueKind::Date(date));
425        assert_eq!(
426            <odbc_api::sys::Date as Decode<crate::Odbc>>::decode(date_value.as_ref()).unwrap(),
427            date
428        );
429
430        let time = odbc_api::sys::Time {
431            hour: 12,
432            minute: 30,
433            second: 45,
434        };
435        let time_value = OdbcValue::new(OdbcValueKind::Time(time));
436        assert_eq!(
437            <odbc_api::sys::Time as Decode<crate::Odbc>>::decode(time_value.as_ref()).unwrap(),
438            time
439        );
440
441        let timestamp = odbc_api::sys::Timestamp {
442            year: 2026,
443            month: 5,
444            day: 29,
445            hour: 12,
446            minute: 30,
447            second: 45,
448            fraction: 123_456_000,
449        };
450        let timestamp_value = OdbcValue::new(OdbcValueKind::Timestamp(timestamp));
451        assert_eq!(
452            <odbc_api::sys::Timestamp as Decode<crate::Odbc>>::decode(timestamp_value.as_ref())
453                .unwrap(),
454            timestamp
455        );
456    }
457}