Skip to content

Commit f3aa52c

Browse files
committed
refactor(odbc): fix OdbcRow and OdbcValue to contain correctly decoded data types
- Replaced the previous tuple structure in OdbcRow with a more comprehensive OdbcValue struct to encapsulate various data types and nullability. - Updated methods in OdbcRow to utilize the new OdbcValue structure, simplifying value retrieval. - Refactored data extraction functions in the connection worker to support the new OdbcValue format, enhancing type safety and clarity. - Adjusted tests to validate the new data handling logic and ensure compatibility across different database types.
1 parent 55e410b commit f3aa52c

File tree

15 files changed

+286
-200
lines changed

15 files changed

+286
-200
lines changed

sqlx-core/src/odbc/connection/worker.rs

Lines changed: 143 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use either::Either;
1616
#[allow(unused_imports)]
1717
use odbc_api::handles::Statement as OdbcStatementTrait;
1818
use odbc_api::handles::StatementImpl;
19-
use odbc_api::{Cursor, CursorRow, IntoParameter, Preallocated, ResultSetMetadata};
19+
use odbc_api::{Cursor, CursorRow, IntoParameter, Nullable, Preallocated, ResultSetMetadata};
2020

2121
// Type aliases for commonly used types
2222
type OdbcConnection = odbc_api::Connection<'static>;
@@ -562,7 +562,7 @@ where
562562
OdbcColumn {
563563
name: decode_column_name(cd.name, index),
564564
type_info: OdbcTypeInfo::new(cd.data_type),
565-
ordinal: (index - 1) as usize,
565+
ordinal: usize::from(index.checked_sub(1).unwrap()),
566566
}
567567
}
568568

@@ -581,7 +581,7 @@ where
581581
let values = collect_row_values(&mut row, columns)?;
582582
let row_data = OdbcRow {
583583
columns: columns.to_vec(),
584-
values,
584+
values: values.into_iter().map(|(_, value)| value).collect(),
585585
};
586586

587587
if send_row(tx, row_data).is_err() {
@@ -601,7 +601,7 @@ where
601601
fn collect_row_values(
602602
row: &mut CursorRow<'_>,
603603
columns: &[OdbcColumn],
604-
) -> Result<Vec<(OdbcTypeInfo, Option<Vec<u8>>)>, Error> {
604+
) -> Result<Vec<(OdbcTypeInfo, crate::odbc::OdbcValue)>, Error> {
605605
columns
606606
.iter()
607607
.enumerate()
@@ -613,37 +613,155 @@ fn collect_column_value(
613613
row: &mut CursorRow<'_>,
614614
index: usize,
615615
column: &OdbcColumn,
616-
) -> Result<(OdbcTypeInfo, Option<Vec<u8>>), Error> {
616+
) -> Result<(OdbcTypeInfo, crate::odbc::OdbcValue), Error> {
617+
use odbc_api::DataType;
618+
617619
let col_idx = (index + 1) as u16;
620+
let type_info = column.type_info.clone();
621+
let data_type = type_info.data_type();
622+
623+
// Extract value based on data type
624+
let value = match data_type {
625+
// Integer types
626+
DataType::TinyInt
627+
| DataType::SmallInt
628+
| DataType::Integer
629+
| DataType::BigInt
630+
| DataType::Bit => extract_int(row, col_idx, &type_info)?,
631+
632+
// Floating point types
633+
DataType::Real => extract_float::<f32>(row, col_idx, &type_info)?,
634+
DataType::Float { .. } | DataType::Double => {
635+
extract_float::<f64>(row, col_idx, &type_info)?
636+
}
637+
638+
// String types
639+
DataType::Char { .. }
640+
| DataType::Varchar { .. }
641+
| DataType::LongVarchar { .. }
642+
| DataType::WChar { .. }
643+
| DataType::WVarchar { .. }
644+
| DataType::WLongVarchar { .. }
645+
| DataType::Date
646+
| DataType::Time { .. }
647+
| DataType::Timestamp { .. }
648+
| DataType::Decimal { .. }
649+
| DataType::Numeric { .. } => extract_text(row, col_idx, &type_info)?,
650+
651+
// Binary types
652+
DataType::Binary { .. } | DataType::Varbinary { .. } | DataType::LongVarbinary { .. } => {
653+
extract_binary(row, col_idx, &type_info)?
654+
}
618655

619-
// Try text first
620-
match try_get_text(row, col_idx) {
621-
Ok(value) => Ok((column.type_info.clone(), value)),
622-
Err(_) => {
623-
// Fall back to binary
624-
match try_get_binary(row, col_idx) {
625-
Ok(value) => Ok((column.type_info.clone(), value)),
626-
Err(e) => Err(Error::from(e)),
656+
// Unknown types - try text first, fall back to binary
657+
DataType::Unknown | DataType::Other { .. } => {
658+
match extract_text(row, col_idx, &type_info) {
659+
Ok(v) => v,
660+
Err(_) => extract_binary(row, col_idx, &type_info)?,
627661
}
628662
}
629-
}
663+
};
664+
665+
Ok((type_info, value))
666+
}
667+
668+
fn extract_int(
669+
row: &mut CursorRow<'_>,
670+
col_idx: u16,
671+
type_info: &OdbcTypeInfo,
672+
) -> Result<crate::odbc::OdbcValue, Error> {
673+
let mut nullable = Nullable::<i64>::null();
674+
row.get_data(col_idx, &mut nullable)?;
675+
676+
let (is_null, int) = match nullable.into_opt() {
677+
None => (true, None),
678+
Some(v) => (false, Some(v.into())),
679+
};
680+
681+
Ok(crate::odbc::OdbcValue {
682+
type_info: type_info.clone(),
683+
is_null,
684+
text: None,
685+
blob: None,
686+
int,
687+
float: None,
688+
})
689+
}
690+
691+
fn extract_float<T>(
692+
row: &mut CursorRow<'_>,
693+
col_idx: u16,
694+
type_info: &OdbcTypeInfo,
695+
) -> Result<crate::odbc::OdbcValue, Error>
696+
where
697+
T: Into<f64> + Default,
698+
odbc_api::Nullable<T>: odbc_api::parameter::CElement + odbc_api::handles::CDataMut,
699+
{
700+
let mut nullable = Nullable::<T>::null();
701+
row.get_data(col_idx, &mut nullable)?;
702+
703+
let (is_null, float) = match nullable.into_opt() {
704+
None => (true, None),
705+
Some(v) => (false, Some(v.into())),
706+
};
707+
708+
Ok(crate::odbc::OdbcValue {
709+
type_info: type_info.clone(),
710+
is_null,
711+
text: None,
712+
blob: None,
713+
int: None,
714+
float,
715+
})
630716
}
631717

632-
fn try_get_text(row: &mut CursorRow<'_>, col_idx: u16) -> Result<Option<Vec<u8>>, odbc_api::Error> {
718+
fn extract_text(
719+
row: &mut CursorRow<'_>,
720+
col_idx: u16,
721+
type_info: &OdbcTypeInfo,
722+
) -> Result<crate::odbc::OdbcValue, Error> {
633723
let mut buf = Vec::new();
634-
match row.get_text(col_idx, &mut buf)? {
635-
true => Ok(Some(buf)),
636-
false => Ok(None),
637-
}
724+
let is_some = row.get_text(col_idx, &mut buf)?;
725+
726+
let (is_null, text) = if !is_some {
727+
(true, None)
728+
} else {
729+
match String::from_utf8(buf) {
730+
Ok(s) => (false, Some(s)),
731+
Err(e) => return Err(Error::Decode(e.into())),
732+
}
733+
};
734+
735+
Ok(crate::odbc::OdbcValue {
736+
type_info: type_info.clone(),
737+
is_null,
738+
text,
739+
blob: None,
740+
int: None,
741+
float: None,
742+
})
638743
}
639744

640-
fn try_get_binary(
745+
fn extract_binary(
641746
row: &mut CursorRow<'_>,
642747
col_idx: u16,
643-
) -> Result<Option<Vec<u8>>, odbc_api::Error> {
748+
type_info: &OdbcTypeInfo,
749+
) -> Result<crate::odbc::OdbcValue, Error> {
644750
let mut buf = Vec::new();
645-
match row.get_binary(col_idx, &mut buf)? {
646-
true => Ok(Some(buf)),
647-
false => Ok(None),
648-
}
751+
let is_some = row.get_binary(col_idx, &mut buf)?;
752+
753+
let (is_null, blob) = if !is_some {
754+
(true, None)
755+
} else {
756+
(false, Some(buf))
757+
};
758+
759+
Ok(crate::odbc::OdbcValue {
760+
type_info: type_info.clone(),
761+
is_null,
762+
text: None,
763+
blob,
764+
int: None,
765+
float: None,
766+
})
649767
}

sqlx-core/src/odbc/row.rs

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use crate::column::ColumnIndex;
22
use crate::database::HasValueRef;
33
use crate::error::Error;
4-
use crate::odbc::{Odbc, OdbcColumn, OdbcTypeInfo, OdbcValueRef};
4+
use crate::odbc::{Odbc, OdbcColumn, OdbcValue};
55
use crate::row::Row;
6+
use crate::value::Value;
67

78
#[derive(Debug, Clone)]
89
pub struct OdbcRow {
910
pub(crate) columns: Vec<OdbcColumn>,
10-
pub(crate) values: Vec<(OdbcTypeInfo, Option<Vec<u8>>)>,
11+
pub(crate) values: Vec<OdbcValue>,
1112
}
1213

1314
impl Row for OdbcRow {
@@ -25,15 +26,8 @@ impl Row for OdbcRow {
2526
I: ColumnIndex<Self>,
2627
{
2728
let idx = index.index(self)?;
28-
let (ti, data) = &self.values[idx];
29-
Ok(OdbcValueRef {
30-
type_info: ti.clone(),
31-
is_null: data.is_none(),
32-
text: None,
33-
blob: data.as_deref(),
34-
int: None,
35-
float: None,
36-
})
29+
let value = &self.values[idx];
30+
Ok(value.as_ref())
3731
}
3832
}
3933

@@ -66,6 +60,8 @@ mod tests {
6660
use odbc_api::DataType;
6761

6862
fn create_test_row() -> OdbcRow {
63+
use crate::odbc::OdbcValue;
64+
6965
OdbcRow {
7066
columns: vec![
7167
OdbcColumn {
@@ -85,15 +81,30 @@ mod tests {
8581
},
8682
],
8783
values: vec![
88-
(OdbcTypeInfo::new(DataType::Integer), Some(vec![1, 2, 3, 4])),
89-
(
90-
OdbcTypeInfo::new(DataType::Varchar { length: None }),
91-
Some(b"test".to_vec()),
92-
),
93-
(
94-
OdbcTypeInfo::new(DataType::Double),
95-
Some(vec![1, 2, 3, 4, 5, 6, 7, 8]),
96-
),
84+
OdbcValue {
85+
type_info: OdbcTypeInfo::new(DataType::Integer),
86+
is_null: false,
87+
text: None,
88+
blob: None,
89+
int: Some(42),
90+
float: None,
91+
},
92+
OdbcValue {
93+
type_info: OdbcTypeInfo::new(DataType::Varchar { length: None }),
94+
is_null: false,
95+
text: Some("test".to_string()),
96+
blob: None,
97+
int: None,
98+
float: None,
99+
},
100+
OdbcValue {
101+
type_info: OdbcTypeInfo::new(DataType::Double),
102+
is_null: false,
103+
text: None,
104+
blob: None,
105+
int: None,
106+
float: Some(std::f64::consts::PI),
107+
},
97108
],
98109
}
99110
}

sqlx-core/src/odbc/types/bigdecimal.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::encode::Encode;
33
use crate::error::BoxDynError;
44
use crate::odbc::{DataTypeExt, Odbc, OdbcArgumentValue, OdbcTypeInfo, OdbcValueRef};
55
use crate::types::Type;
6-
use bigdecimal::BigDecimal;
6+
use bigdecimal::{BigDecimal, FromPrimitive};
77
use odbc_api::DataType;
88
use std::str::FromStr;
99

@@ -36,7 +36,19 @@ impl<'q> Encode<'q, Odbc> for BigDecimal {
3636

3737
impl<'r> Decode<'r, Odbc> for BigDecimal {
3838
fn decode(value: OdbcValueRef<'r>) -> Result<Self, BoxDynError> {
39-
let s = <String as Decode<'r, Odbc>>::decode(value)?;
40-
Ok(BigDecimal::from_str(&s)?)
39+
if let Some(int) = value.int {
40+
return Ok(BigDecimal::from(int));
41+
}
42+
if let Some(float) = value.float {
43+
return Ok(BigDecimal::from_f64(float).ok_or(format!("bad float: {}", float))?);
44+
}
45+
if let Some(text) = value.text {
46+
return Ok(BigDecimal::from_str(&text).map_err(|e| format!("bad decimal text: {}", e))?);
47+
}
48+
if let Some(bytes) = value.blob {
49+
return Ok(BigDecimal::parse_bytes(bytes, 10)
50+
.ok_or(format!("bad base10 bytes: {:?}", bytes))?);
51+
}
52+
Err(format!("ODBC: cannot decode BigDecimal: {:?}", value).into())
4153
}
4254
}

sqlx-core/src/odbc/types/bytes.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,13 +194,7 @@ mod tests {
194194
int: None,
195195
float: None,
196196
};
197-
198-
let result = <Vec<u8> as Decode<Odbc>>::decode(value);
199-
assert!(result.is_err());
200-
assert_eq!(
201-
result.unwrap_err().to_string(),
202-
"ODBC: cannot decode Vec<u8>"
203-
);
197+
assert!(<Vec<u8> as Decode<'_, Odbc>>::decode(value).is_err());
204198
}
205199

206200
#[test]

0 commit comments

Comments
 (0)