sqlx_core_oldapi/odbc/
row.rs

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