sqlx_core_oldapi/odbc/
row.rs

1use crate::column::ColumnIndex;
2use crate::database::HasValueRef;
3use crate::error::Error;
4use crate::odbc::{Odbc, OdbcColumn, OdbcValue};
5use crate::row::Row;
6use crate::value::Value;
7
8#[derive(Debug, Clone)]
9pub struct OdbcRow {
10    pub(crate) columns: Vec<OdbcColumn>,
11    pub(crate) values: Vec<OdbcValue>,
12}
13
14impl Row for OdbcRow {
15    type Database = Odbc;
16
17    fn columns(&self) -> &[OdbcColumn] {
18        &self.columns
19    }
20
21    fn try_get_raw<I>(
22        &self,
23        index: I,
24    ) -> Result<<Self::Database as HasValueRef<'_>>::ValueRef, Error>
25    where
26        I: ColumnIndex<Self>,
27    {
28        let idx = index.index(self)?;
29        let value = &self.values[idx];
30        Ok(value.as_ref())
31    }
32}
33
34impl ColumnIndex<OdbcRow> for &str {
35    fn index(&self, row: &OdbcRow) -> Result<usize, Error> {
36        // Try exact match first (for performance)
37        if let Some(pos) = row.columns.iter().position(|col| col.name == *self) {
38            return Ok(pos);
39        }
40
41        // Fall back to case-insensitive match (for databases like Snowflake)
42        row.columns
43            .iter()
44            .position(|col| col.name.eq_ignore_ascii_case(self))
45            .ok_or_else(|| Error::ColumnNotFound((*self).into()))
46    }
47}
48
49mod private {
50    use super::OdbcRow;
51    use crate::row::private_row::Sealed;
52    impl Sealed for OdbcRow {}
53}
54
55#[cfg(feature = "any")]
56impl From<OdbcRow> for crate::any::AnyRow {
57    fn from(row: OdbcRow) -> Self {
58        let columns = row
59            .columns
60            .iter()
61            .map(|col| crate::any::AnyColumn {
62                kind: crate::any::column::AnyColumnKind::Odbc(col.clone()),
63                type_info: crate::any::AnyTypeInfo::from(col.type_info.clone()),
64            })
65            .collect();
66
67        crate::any::AnyRow {
68            kind: crate::any::row::AnyRowKind::Odbc(row),
69            columns,
70        }
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use crate::odbc::{OdbcColumn, OdbcTypeInfo};
78    use crate::type_info::TypeInfo;
79    use odbc_api::DataType;
80
81    fn create_test_row() -> OdbcRow {
82        use crate::odbc::OdbcValue;
83
84        OdbcRow {
85            columns: vec![
86                OdbcColumn {
87                    name: "lowercase_col".to_string(),
88                    type_info: OdbcTypeInfo::new(DataType::Integer),
89                    ordinal: 0,
90                },
91                OdbcColumn {
92                    name: "UPPERCASE_COL".to_string(),
93                    type_info: OdbcTypeInfo::new(DataType::Varchar { length: None }),
94                    ordinal: 1,
95                },
96                OdbcColumn {
97                    name: "MixedCase_Col".to_string(),
98                    type_info: OdbcTypeInfo::new(DataType::Double),
99                    ordinal: 2,
100                },
101            ],
102            values: vec![
103                OdbcValue {
104                    type_info: OdbcTypeInfo::new(DataType::Integer),
105                    is_null: false,
106                    text: None,
107                    blob: None,
108                    int: Some(42),
109                    float: None,
110                },
111                OdbcValue {
112                    type_info: OdbcTypeInfo::new(DataType::Varchar { length: None }),
113                    is_null: false,
114                    text: Some("test".to_string()),
115                    blob: None,
116                    int: None,
117                    float: None,
118                },
119                OdbcValue {
120                    type_info: OdbcTypeInfo::new(DataType::Double),
121                    is_null: false,
122                    text: None,
123                    blob: None,
124                    int: None,
125                    float: Some(std::f64::consts::PI),
126                },
127            ],
128        }
129    }
130
131    #[test]
132    fn test_exact_column_match() {
133        let row = create_test_row();
134
135        // Exact matches should work
136        assert_eq!("lowercase_col".index(&row).unwrap(), 0);
137        assert_eq!("UPPERCASE_COL".index(&row).unwrap(), 1);
138        assert_eq!("MixedCase_Col".index(&row).unwrap(), 2);
139    }
140
141    #[test]
142    fn test_case_insensitive_column_match() {
143        let row = create_test_row();
144
145        // Case-insensitive matches should work
146        assert_eq!("LOWERCASE_COL".index(&row).unwrap(), 0);
147        assert_eq!("lowercase_col".index(&row).unwrap(), 0);
148        assert_eq!("uppercase_col".index(&row).unwrap(), 1);
149        assert_eq!("UPPERCASE_COL".index(&row).unwrap(), 1);
150        assert_eq!("mixedcase_col".index(&row).unwrap(), 2);
151        assert_eq!("MIXEDCASE_COL".index(&row).unwrap(), 2);
152        assert_eq!("MixedCase_Col".index(&row).unwrap(), 2);
153    }
154
155    #[test]
156    fn test_column_not_found() {
157        let row = create_test_row();
158
159        let result = "nonexistent_column".index(&row);
160        assert!(result.is_err());
161        if let Err(Error::ColumnNotFound(name)) = result {
162            assert_eq!(name, "nonexistent_column");
163        } else {
164            panic!("Expected ColumnNotFound error");
165        }
166    }
167
168    #[test]
169    fn test_try_get_raw() {
170        let row = create_test_row();
171
172        // Test accessing by exact name
173        let value = row.try_get_raw("lowercase_col").unwrap();
174        assert!(!value.is_null);
175        assert_eq!(value.type_info.name(), "INTEGER");
176
177        // Test accessing by case-insensitive name
178        let value = row.try_get_raw("LOWERCASE_COL").unwrap();
179        assert!(!value.is_null);
180        assert_eq!(value.type_info.name(), "INTEGER");
181
182        // Test accessing uppercase column with lowercase name
183        let value = row.try_get_raw("uppercase_col").unwrap();
184        assert!(!value.is_null);
185        assert_eq!(value.type_info.name(), "VARCHAR");
186    }
187
188    #[test]
189    fn test_columns_method() {
190        let row = create_test_row();
191        let columns = row.columns();
192
193        assert_eq!(columns.len(), 3);
194        assert_eq!(columns[0].name, "lowercase_col");
195        assert_eq!(columns[1].name, "UPPERCASE_COL");
196        assert_eq!(columns[2].name, "MixedCase_Col");
197    }
198}