1use std::borrow::Cow;
2
3#[derive(Debug, Clone, PartialEq)]
5pub struct OdbcValue {
6 kind: OdbcValueKind,
7}
8
9impl OdbcValue {
10 pub fn new(kind: OdbcValueKind) -> Self {
12 Self { kind }
13 }
14
15 pub fn kind(&self) -> &OdbcValueKind {
17 &self.kind
18 }
19
20 pub fn is_null(&self) -> bool {
22 matches!(self.kind, OdbcValueKind::Null)
23 }
24
25 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 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 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 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#[derive(Debug, Clone, Copy)]
86pub struct OdbcValueRef<'r> {
87 value: &'r OdbcValue,
88}
89
90impl<'r> OdbcValueRef<'r> {
91 pub fn as_i64(&self) -> Option<i64> {
93 self.value.as_i64()
94 }
95
96 pub fn as_f64(&self) -> Option<f64> {
98 self.value.as_f64()
99 }
100
101 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 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 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 let Some(integer) = value.as_i64() else {
154 return Err(decode_error(
155 value,
156 stringify!($ty),
157 "source value is not an integer",
158 )
159 .into());
160 };
161
162 Self::try_from(integer).map_err(|_| {
163 decode_error(
164 value,
165 stringify!($ty),
166 format!("integer value {integer} is outside the target range"),
167 )
168 .into()
169 })
170 }
171 }
172 };
173}
174
175impl_decode_integer!(i8);
176impl_decode_integer!(i16);
177impl_decode_integer!(i32);
178impl_decode_integer!(i64);
179impl_decode_integer!(u8);
180impl_decode_integer!(u16);
181impl_decode_integer!(u32);
182impl_decode_integer!(u64);
183
184impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for bool {
185 fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
186 value.as_bool().ok_or_else(|| {
187 decode_error(value, "bool", "source value is not boolean-compatible").into()
188 })
189 }
190}
191
192impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for f32 {
193 fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
194 value
195 .as_f64()
196 .map(|value| value as f32)
197 .ok_or_else(|| decode_error(value, "f32", "source value is not numeric").into())
198 }
199}
200
201impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for f64 {
202 fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
203 value
204 .as_f64()
205 .ok_or_else(|| decode_error(value, "f64", "source value is not numeric").into())
206 }
207}
208
209impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for String {
210 fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
211 if let Some(text) = value.as_str() {
212 return Ok(text.to_owned());
213 }
214
215 if let Some(bytes) = value.as_bytes() {
216 return Ok(String::from_utf8(bytes.to_vec())?);
217 }
218
219 Err(decode_error(
220 value,
221 "String",
222 "source value is neither text nor UTF-8 bytes",
223 )
224 .into())
225 }
226}
227
228impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for &'r str {
229 fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
230 if let Some(text) = value.as_str() {
231 return Ok(text);
232 }
233
234 Err(decode_error(value, "&str", "source value is not text").into())
235 }
236}
237
238impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for Vec<u8> {
239 fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
240 value
241 .as_bytes()
242 .map(<[u8]>::to_vec)
243 .ok_or_else(|| decode_error(value, "Vec<u8>", "source value is not binary").into())
244 }
245}
246
247impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for &'r [u8] {
248 fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
249 value
250 .as_bytes()
251 .ok_or_else(|| decode_error(value, "&[u8]", "source value is not binary").into())
252 }
253}
254
255impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for odbc_api::sys::Date {
256 fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
257 match value.value.kind() {
258 OdbcValueKind::Date(value) => Ok(*value),
259 _ => Err(decode_error(value, "Date", "source value is not an ODBC date").into()),
260 }
261 }
262}
263
264impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for odbc_api::sys::Time {
265 fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
266 match value.value.kind() {
267 OdbcValueKind::Time(value) => Ok(*value),
268 _ => Err(decode_error(value, "Time", "source value is not an ODBC time").into()),
269 }
270 }
271}
272
273impl<'r> sqlx_core::decode::Decode<'r, crate::Odbc> for odbc_api::sys::Timestamp {
274 fn decode(value: OdbcValueRef<'r>) -> Result<Self, sqlx_core::error::BoxDynError> {
275 match value.value.kind() {
276 OdbcValueKind::Timestamp(value) => Ok(*value),
277 _ => Err(
278 decode_error(value, "Timestamp", "source value is not an ODBC timestamp").into(),
279 ),
280 }
281 }
282}
283
284fn decode_error(value: OdbcValueRef<'_>, target: &str, reason: impl std::fmt::Display) -> String {
285 format!(
286 "ODBC cannot decode value kind {:?} as {target}: {reason}",
287 value.value.kind()
288 )
289}
290
291fn parse_bool_text(value: &str) -> Option<bool> {
292 match value.trim() {
293 "0" | "0.0" | "false" | "FALSE" | "f" | "F" => Some(false),
294 "1" | "1.0" | "true" | "TRUE" | "t" | "T" => Some(true),
295 value => value
296 .parse::<f64>()
297 .map(|value| value != 0.0)
298 .or_else(|_| value.parse::<i64>().map(|value| value != 0))
299 .ok(),
300 }
301}
302
303fn parse_integer_text(value: &str) -> Option<i64> {
304 let value = value.trim();
305
306 if let Ok(value) = value.parse() {
307 return Some(value);
308 }
309
310 let (integer, fraction) = value.split_once('.')?;
311
312 if fraction.chars().all(|ch| ch == '0') {
313 integer.parse().ok()
314 } else {
315 None
316 }
317}
318
319#[derive(Debug, Clone, PartialEq)]
321pub enum OdbcValueKind {
322 Null,
324 TinyInt(i8),
326 SmallInt(i16),
328 Integer(i32),
330 BigInt(i64),
332 Real(f32),
334 Double(f64),
336 Bit(bool),
338 Text(String),
340 Binary(Vec<u8>),
342 Date(odbc_api::sys::Date),
344 Time(odbc_api::sys::Time),
346 Timestamp(odbc_api::sys::Timestamp),
348}
349
350impl OdbcValueKind {
351 fn type_info(&self) -> crate::OdbcTypeInfo {
352 let data_type = match self {
353 Self::Null => odbc_api::DataType::Unknown,
354 Self::TinyInt(_) => odbc_api::DataType::TinyInt,
355 Self::SmallInt(_) => odbc_api::DataType::SmallInt,
356 Self::Integer(_) => odbc_api::DataType::Integer,
357 Self::BigInt(_) => odbc_api::DataType::BigInt,
358 Self::Real(_) => odbc_api::DataType::Real,
359 Self::Double(_) => odbc_api::DataType::Double,
360 Self::Bit(_) => odbc_api::DataType::Bit,
361 Self::Text(_) => odbc_api::DataType::WVarchar { length: None },
362 Self::Binary(_) => odbc_api::DataType::Varbinary { length: None },
363 Self::Date(_) => odbc_api::DataType::Date,
364 Self::Time(_) => odbc_api::DataType::Time { precision: 0 },
365 Self::Timestamp(_) => odbc_api::DataType::Timestamp { precision: 6 },
366 };
367
368 crate::OdbcTypeInfo::new(data_type)
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[test]
377 fn integer_values_convert_to_i64() {
378 assert_eq!(OdbcValue::new(OdbcValueKind::TinyInt(1)).as_i64(), Some(1));
379 assert_eq!(OdbcValue::new(OdbcValueKind::SmallInt(2)).as_i64(), Some(2));
380 assert_eq!(OdbcValue::new(OdbcValueKind::Integer(3)).as_i64(), Some(3));
381 assert_eq!(OdbcValue::new(OdbcValueKind::BigInt(4)).as_i64(), Some(4));
382 assert_eq!(
383 OdbcValue::new(OdbcValueKind::Text("42.000".to_owned())).as_i64(),
384 Some(42)
385 );
386 assert_eq!(
387 OdbcValue::new(OdbcValueKind::Text("42.5".to_owned())).as_i64(),
388 None
389 );
390 }
391
392 #[test]
393 fn text_numeric_values_convert_to_float() {
394 assert_eq!(
395 OdbcValue::new(OdbcValueKind::Text("42.5".to_owned())).as_f64(),
396 Some(42.5)
397 );
398 }
399
400 #[test]
401 fn text_and_bytes_borrow_from_value() {
402 let text = OdbcValue::new(OdbcValueKind::Text("hello".to_owned()));
403 assert_eq!(text.as_str().as_deref(), Some("hello"));
404
405 let bytes = OdbcValue::new(OdbcValueKind::Binary(vec![1, 2, 3]));
406 assert_eq!(bytes.as_bytes().as_deref(), Some(&[1, 2, 3][..]));
407 }
408
409 #[test]
410 fn null_reports_null() {
411 assert!(OdbcValue::new(OdbcValueKind::Null).is_null());
412 }
413
414 #[test]
415 fn borrowed_values_decode_basic_scalars() {
416 use sqlx_core::decode::Decode;
417 use sqlx_core::value::Value;
418
419 let int = OdbcValue::new(OdbcValueKind::BigInt(42));
420 assert_eq!(
421 <i32 as Decode<crate::Odbc>>::decode(int.as_ref()).unwrap(),
422 42
423 );
424
425 let truthy = OdbcValue::new(OdbcValueKind::Text("true".to_owned()));
426 assert!(<bool as Decode<crate::Odbc>>::decode(truthy.as_ref()).unwrap());
427
428 let text = OdbcValue::new(OdbcValueKind::Text("hello".to_owned()));
429 assert_eq!(
430 <String as Decode<crate::Odbc>>::decode(text.as_ref()).unwrap(),
431 "hello"
432 );
433
434 let bytes = OdbcValue::new(OdbcValueKind::Binary(vec![1, 2, 3]));
435 assert_eq!(
436 <Vec<u8> as Decode<crate::Odbc>>::decode(bytes.as_ref()).unwrap(),
437 vec![1, 2, 3]
438 );
439 }
440
441 #[test]
442 fn borrowed_values_decode_temporal_scalars() {
443 use sqlx_core::decode::Decode;
444 use sqlx_core::value::Value;
445
446 let date = odbc_api::sys::Date {
447 year: 2026,
448 month: 5,
449 day: 29,
450 };
451 let date_value = OdbcValue::new(OdbcValueKind::Date(date));
452 assert_eq!(
453 <odbc_api::sys::Date as Decode<crate::Odbc>>::decode(date_value.as_ref()).unwrap(),
454 date
455 );
456
457 let time = odbc_api::sys::Time {
458 hour: 12,
459 minute: 30,
460 second: 45,
461 };
462 let time_value = OdbcValue::new(OdbcValueKind::Time(time));
463 assert_eq!(
464 <odbc_api::sys::Time as Decode<crate::Odbc>>::decode(time_value.as_ref()).unwrap(),
465 time
466 );
467
468 let timestamp = odbc_api::sys::Timestamp {
469 year: 2026,
470 month: 5,
471 day: 29,
472 hour: 12,
473 minute: 30,
474 second: 45,
475 fraction: 123_456_000,
476 };
477 let timestamp_value = OdbcValue::new(OdbcValueKind::Timestamp(timestamp));
478 assert_eq!(
479 <odbc_api::sys::Timestamp as Decode<crate::Odbc>>::decode(timestamp_value.as_ref())
480 .unwrap(),
481 timestamp
482 );
483 }
484}