Skip to content

Commit de6c9e9

Browse files
committed
add more odbc types support
This commit introduces decoding implementations for i8, i16, and bool types in the ODBC module, allowing for better handling of these data types. It also adds extensive tests to verify the correct decoding of different integer types, float types, boolean values, and string variations, ensuring robust functionality and type coercion in ODBC interactions.
1 parent c8ff45a commit de6c9e9

File tree

3 files changed

+236
-1
lines changed

3 files changed

+236
-1
lines changed

sqlx-core/src/odbc/type.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ impl Type<Odbc> for Vec<u8> {
9494
OdbcTypeInfo::varbinary(None)
9595
}
9696
fn compatible(ty: &OdbcTypeInfo) -> bool {
97-
ty.data_type().accepts_binary_data()
97+
ty.data_type().accepts_binary_data() || ty.data_type().accepts_character_data()
98+
// Allow decoding from character types too
9899
}
99100
}
100101

sqlx-core/src/odbc/value.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,41 @@ impl<'r> Decode<'r, Odbc> for Vec<u8> {
134134
Err("ODBC: cannot decode Vec<u8>".into())
135135
}
136136
}
137+
138+
impl<'r> Decode<'r, Odbc> for i16 {
139+
fn decode(value: OdbcValueRef<'r>) -> Result<Self, BoxDynError> {
140+
Ok(i64::decode(value)? as i16)
141+
}
142+
}
143+
144+
impl<'r> Decode<'r, Odbc> for i8 {
145+
fn decode(value: OdbcValueRef<'r>) -> Result<Self, BoxDynError> {
146+
Ok(i64::decode(value)? as i8)
147+
}
148+
}
149+
150+
impl<'r> Decode<'r, Odbc> for bool {
151+
fn decode(value: OdbcValueRef<'r>) -> Result<Self, BoxDynError> {
152+
if let Some(i) = value.int {
153+
return Ok(i != 0);
154+
}
155+
if let Some(bytes) = value.blob {
156+
let s = std::str::from_utf8(bytes)?;
157+
let s = s.trim();
158+
return Ok(match s {
159+
"0" | "false" | "FALSE" | "f" | "F" => false,
160+
"1" | "true" | "TRUE" | "t" | "T" => true,
161+
_ => s.parse()?,
162+
});
163+
}
164+
if let Some(text) = value.text {
165+
let text = text.trim();
166+
return Ok(match text {
167+
"0" | "false" | "FALSE" | "f" | "F" => false,
168+
"1" | "true" | "TRUE" | "t" | "T" => true,
169+
_ => text.parse()?,
170+
});
171+
}
172+
Err("ODBC: cannot decode bool".into())
173+
}
174+
}

tests/odbc/odbc.rs

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,199 @@ async fn it_binds_null_string_parameter() -> anyhow::Result<()> {
228228
assert!(b.is_null());
229229
Ok(())
230230
}
231+
232+
#[tokio::test]
233+
async fn it_handles_different_integer_types() -> anyhow::Result<()> {
234+
let mut conn = new::<Odbc>().await?;
235+
236+
// Test various integer sizes
237+
let mut s = conn.fetch(
238+
"SELECT 127 AS tiny, 32767 AS small, 2147483647 AS regular, 9223372036854775807 AS big",
239+
);
240+
let row = s.try_next().await?.expect("row expected");
241+
242+
let tiny = row.try_get_raw(0)?.to_owned().decode::<i8>();
243+
let small = row.try_get_raw(1)?.to_owned().decode::<i16>();
244+
let regular = row.try_get_raw(2)?.to_owned().decode::<i32>();
245+
let big = row.try_get_raw(3)?.to_owned().decode::<i64>();
246+
247+
assert_eq!(tiny, 127);
248+
assert_eq!(small, 32767);
249+
assert_eq!(regular, 2147483647);
250+
assert_eq!(big, 9223372036854775807);
251+
Ok(())
252+
}
253+
254+
#[tokio::test]
255+
async fn it_handles_negative_integers() -> anyhow::Result<()> {
256+
let mut conn = new::<Odbc>().await?;
257+
258+
let mut s = conn.fetch(
259+
"SELECT -128 AS tiny, -32768 AS small, -2147483648 AS regular, -9223372036854775808 AS big",
260+
);
261+
let row = s.try_next().await?.expect("row expected");
262+
263+
let tiny = row.try_get_raw(0)?.to_owned().decode::<i8>();
264+
let small = row.try_get_raw(1)?.to_owned().decode::<i16>();
265+
let regular = row.try_get_raw(2)?.to_owned().decode::<i32>();
266+
let big = row.try_get_raw(3)?.to_owned().decode::<i64>();
267+
268+
assert_eq!(tiny, -128);
269+
assert_eq!(small, -32768);
270+
assert_eq!(regular, -2147483648);
271+
assert_eq!(big, -9223372036854775808);
272+
Ok(())
273+
}
274+
275+
#[tokio::test]
276+
async fn it_handles_different_float_types() -> anyhow::Result<()> {
277+
let mut conn = new::<Odbc>().await?;
278+
279+
let sql = format!(
280+
"SELECT {} AS f32_val, {} AS f64_val, 1.23456789 AS precise_val",
281+
std::f32::consts::PI,
282+
std::f64::consts::E
283+
);
284+
let mut s = conn.fetch(sql.as_str());
285+
let row = s.try_next().await?.expect("row expected");
286+
287+
let f32_val = row.try_get_raw(0)?.to_owned().decode::<f32>();
288+
let f64_val = row.try_get_raw(1)?.to_owned().decode::<f64>();
289+
let precise_val = row.try_get_raw(2)?.to_owned().decode::<f64>();
290+
291+
assert!((f32_val - std::f32::consts::PI).abs() < 1e-5);
292+
assert!((f64_val - std::f64::consts::E).abs() < 1e-10);
293+
assert!((precise_val - 1.23456789).abs() < 1e-8);
294+
Ok(())
295+
}
296+
297+
#[tokio::test]
298+
async fn it_handles_boolean_values() -> anyhow::Result<()> {
299+
let mut conn = new::<Odbc>().await?;
300+
301+
// Test boolean-like values - some databases represent booleans as 1/0
302+
let mut s = conn.fetch("SELECT 1 AS true_val, 0 AS false_val");
303+
let row = s.try_next().await?.expect("row expected");
304+
305+
let true_val = row.try_get_raw(0)?.to_owned().decode::<bool>();
306+
let false_val = row.try_get_raw(1)?.to_owned().decode::<bool>();
307+
308+
assert!(true_val);
309+
assert!(!false_val);
310+
Ok(())
311+
}
312+
313+
#[tokio::test]
314+
async fn it_handles_zero_and_special_numbers() -> anyhow::Result<()> {
315+
let mut conn = new::<Odbc>().await?;
316+
317+
let mut s = conn.fetch("SELECT 0 AS zero, 0.0 AS zero_float");
318+
let row = s.try_next().await?.expect("row expected");
319+
320+
let zero = row.try_get_raw(0)?.to_owned().decode::<i32>();
321+
let zero_float = row.try_get_raw(1)?.to_owned().decode::<f64>();
322+
323+
assert_eq!(zero, 0);
324+
assert_eq!(zero_float, 0.0);
325+
Ok(())
326+
}
327+
328+
#[tokio::test]
329+
async fn it_handles_string_variations() -> anyhow::Result<()> {
330+
let mut conn = new::<Odbc>().await?;
331+
332+
let mut s = conn.fetch("SELECT '' AS empty, ' ' AS space, 'Hello, World!' AS greeting, 'Unicode: 🦀 Rust' AS unicode");
333+
let row = s.try_next().await?.expect("row expected");
334+
335+
let empty = row.try_get_raw(0)?.to_owned().decode::<String>();
336+
let space = row.try_get_raw(1)?.to_owned().decode::<String>();
337+
let greeting = row.try_get_raw(2)?.to_owned().decode::<String>();
338+
let unicode = row.try_get_raw(3)?.to_owned().decode::<String>();
339+
340+
assert_eq!(empty, "");
341+
assert_eq!(space, " ");
342+
assert_eq!(greeting, "Hello, World!");
343+
assert_eq!(unicode, "Unicode: 🦀 Rust");
344+
Ok(())
345+
}
346+
347+
#[tokio::test]
348+
async fn it_handles_type_coercion_from_strings() -> anyhow::Result<()> {
349+
let mut conn = new::<Odbc>().await?;
350+
351+
// Test that numeric values returned as strings can be parsed
352+
let sql = format!(
353+
"SELECT '42' AS str_int, '{}' AS str_float, '1' AS str_bool",
354+
std::f64::consts::PI
355+
);
356+
let mut s = conn.fetch(sql.as_str());
357+
let row = s.try_next().await?.expect("row expected");
358+
359+
let str_int = row.try_get_raw(0)?.to_owned().decode::<i32>();
360+
let str_float = row.try_get_raw(1)?.to_owned().decode::<f64>();
361+
let str_bool = row.try_get_raw(2)?.to_owned().decode::<bool>();
362+
363+
assert_eq!(str_int, 42);
364+
assert!((str_float - std::f64::consts::PI).abs() < 1e-10);
365+
assert!(str_bool);
366+
Ok(())
367+
}
368+
369+
#[tokio::test]
370+
async fn it_handles_large_strings() -> anyhow::Result<()> {
371+
let mut conn = new::<Odbc>().await?;
372+
373+
// Test a moderately large string
374+
let large_string = "a".repeat(1000);
375+
let stmt = (&mut conn).prepare("SELECT ? AS large_str").await?;
376+
let row = stmt
377+
.query()
378+
.bind(&large_string)
379+
.fetch_one(&mut conn)
380+
.await?;
381+
382+
let result = row.try_get_raw(0)?.to_owned().decode::<String>();
383+
assert_eq!(result, large_string);
384+
assert_eq!(result.len(), 1000);
385+
Ok(())
386+
}
387+
388+
#[tokio::test]
389+
async fn it_handles_binary_data() -> anyhow::Result<()> {
390+
let mut conn = new::<Odbc>().await?;
391+
392+
// Test binary data - use UTF-8 safe bytes for PostgreSQL compatibility
393+
let binary_data = vec![65u8, 66, 67, 68, 69]; // "ABCDE" in ASCII
394+
let stmt = (&mut conn).prepare("SELECT ? AS binary_data").await?;
395+
let row = stmt.query().bind(&binary_data).fetch_one(&mut conn).await?;
396+
397+
let result = row.try_get_raw(0)?.to_owned().decode::<Vec<u8>>();
398+
assert_eq!(result, binary_data);
399+
Ok(())
400+
}
401+
402+
#[tokio::test]
403+
async fn it_handles_mixed_null_and_values() -> anyhow::Result<()> {
404+
let mut conn = new::<Odbc>().await?;
405+
406+
let stmt = (&mut conn).prepare("SELECT ?, ?, ?, ?").await?;
407+
let row = stmt
408+
.query()
409+
.bind(42_i32)
410+
.bind(Option::<i32>::None)
411+
.bind("hello")
412+
.bind(Option::<String>::None)
413+
.fetch_one(&mut conn)
414+
.await?;
415+
416+
let int_val = row.try_get_raw(0)?.to_owned().decode::<i32>();
417+
let null_int = row.try_get_raw(1)?.to_owned();
418+
let str_val = row.try_get_raw(2)?.to_owned().decode::<String>();
419+
let null_str = row.try_get_raw(3)?.to_owned();
420+
421+
assert_eq!(int_val, 42);
422+
assert!(null_int.is_null());
423+
assert_eq!(str_val, "hello");
424+
assert!(null_str.is_null());
425+
Ok(())
426+
}

0 commit comments

Comments
 (0)