Skip to content

Commit b1369cd

Browse files
committed
feat(odbc): enhance type compatibility and decoding for various data types
implemented after running tests with the snowflake odbc driver - Added support for ODBC compatibility with additional types such as Numeric and Decimal across integer types. - Improved decoding logic for Decimal, JSON, and date/time types to handle various input formats, including Unix timestamps and string representations. - Introduced tests for new functionality and edge cases to ensure robustness and accuracy in data handling.
1 parent bc9d7d3 commit b1369cd

File tree

12 files changed

+1899
-62
lines changed

12 files changed

+1899
-62
lines changed

sqlx-core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ default = ["migrate"]
2020
migrate = ["sha2", "crc"]
2121

2222
# databases
23-
all-databases = ["postgres", "mysql", "sqlite", "mssql", "any"]
23+
all-databases = ["postgres", "mysql", "sqlite", "mssql", "odbc", "any"]
2424
postgres = [
2525
"md-5",
2626
"sha2",

sqlx-core/src/odbc/row.rs

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,15 @@ impl Row for OdbcRow {
3939

4040
impl ColumnIndex<OdbcRow> for &str {
4141
fn index(&self, row: &OdbcRow) -> Result<usize, Error> {
42+
// Try exact match first (for performance)
43+
if let Some(pos) = row.columns.iter().position(|col| col.name == *self) {
44+
return Ok(pos);
45+
}
46+
47+
// Fall back to case-insensitive match (for databases like Snowflake)
4248
row.columns
4349
.iter()
44-
.position(|col| col.name == *self)
50+
.position(|col| col.name.eq_ignore_ascii_case(self))
4551
.ok_or_else(|| Error::ColumnNotFound((*self).into()))
4652
}
4753
}
@@ -52,6 +58,115 @@ mod private {
5258
impl Sealed for OdbcRow {}
5359
}
5460

61+
#[cfg(test)]
62+
mod tests {
63+
use super::*;
64+
use crate::odbc::{OdbcColumn, OdbcTypeInfo};
65+
use crate::type_info::TypeInfo;
66+
use odbc_api::DataType;
67+
68+
fn create_test_row() -> OdbcRow {
69+
OdbcRow {
70+
columns: vec![
71+
OdbcColumn {
72+
name: "lowercase_col".to_string(),
73+
type_info: OdbcTypeInfo::new(DataType::Integer),
74+
ordinal: 0,
75+
},
76+
OdbcColumn {
77+
name: "UPPERCASE_COL".to_string(),
78+
type_info: OdbcTypeInfo::new(DataType::Varchar { length: None }),
79+
ordinal: 1,
80+
},
81+
OdbcColumn {
82+
name: "MixedCase_Col".to_string(),
83+
type_info: OdbcTypeInfo::new(DataType::Double),
84+
ordinal: 2,
85+
},
86+
],
87+
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+
),
97+
],
98+
}
99+
}
100+
101+
#[test]
102+
fn test_exact_column_match() {
103+
let row = create_test_row();
104+
105+
// Exact matches should work
106+
assert_eq!("lowercase_col".index(&row).unwrap(), 0);
107+
assert_eq!("UPPERCASE_COL".index(&row).unwrap(), 1);
108+
assert_eq!("MixedCase_Col".index(&row).unwrap(), 2);
109+
}
110+
111+
#[test]
112+
fn test_case_insensitive_column_match() {
113+
let row = create_test_row();
114+
115+
// Case-insensitive matches should work
116+
assert_eq!("LOWERCASE_COL".index(&row).unwrap(), 0);
117+
assert_eq!("lowercase_col".index(&row).unwrap(), 0);
118+
assert_eq!("uppercase_col".index(&row).unwrap(), 1);
119+
assert_eq!("UPPERCASE_COL".index(&row).unwrap(), 1);
120+
assert_eq!("mixedcase_col".index(&row).unwrap(), 2);
121+
assert_eq!("MIXEDCASE_COL".index(&row).unwrap(), 2);
122+
assert_eq!("MixedCase_Col".index(&row).unwrap(), 2);
123+
}
124+
125+
#[test]
126+
fn test_column_not_found() {
127+
let row = create_test_row();
128+
129+
let result = "nonexistent_column".index(&row);
130+
assert!(result.is_err());
131+
if let Err(Error::ColumnNotFound(name)) = result {
132+
assert_eq!(name, "nonexistent_column");
133+
} else {
134+
panic!("Expected ColumnNotFound error");
135+
}
136+
}
137+
138+
#[test]
139+
fn test_try_get_raw() {
140+
let row = create_test_row();
141+
142+
// Test accessing by exact name
143+
let value = row.try_get_raw("lowercase_col").unwrap();
144+
assert!(!value.is_null);
145+
assert_eq!(value.type_info.name(), "INTEGER");
146+
147+
// Test accessing by case-insensitive name
148+
let value = row.try_get_raw("LOWERCASE_COL").unwrap();
149+
assert!(!value.is_null);
150+
assert_eq!(value.type_info.name(), "INTEGER");
151+
152+
// Test accessing uppercase column with lowercase name
153+
let value = row.try_get_raw("uppercase_col").unwrap();
154+
assert!(!value.is_null);
155+
assert_eq!(value.type_info.name(), "VARCHAR");
156+
}
157+
158+
#[test]
159+
fn test_columns_method() {
160+
let row = create_test_row();
161+
let columns = row.columns();
162+
163+
assert_eq!(columns.len(), 3);
164+
assert_eq!(columns[0].name, "lowercase_col");
165+
assert_eq!(columns[1].name, "UPPERCASE_COL");
166+
assert_eq!(columns[2].name, "MixedCase_Col");
167+
}
168+
}
169+
55170
#[cfg(feature = "any")]
56171
impl From<OdbcRow> for crate::any::AnyRow {
57172
fn from(row: OdbcRow) -> Self {

0 commit comments

Comments
 (0)