Skip to main content

sqlx_odbc/
row.rs

1use crate::{Odbc, OdbcColumn, OdbcValue};
2use std::sync::Arc;
3
4/// Minimal ODBC row container used by the SQLx-core skeleton.
5#[derive(Debug, Clone, Default)]
6pub struct OdbcRow {
7    columns: Arc<[OdbcColumn]>,
8    values: Vec<OdbcValue>,
9}
10
11impl OdbcRow {
12    /// Creates a row from column metadata and values.
13    pub fn new(columns: Vec<OdbcColumn>, values: Vec<OdbcValue>) -> Self {
14        Self::new_shared(columns.into(), values)
15    }
16
17    pub(crate) fn new_shared(columns: Arc<[OdbcColumn]>, values: Vec<OdbcValue>) -> Self {
18        Self { columns, values }
19    }
20}
21
22impl sqlx_core::row::Row for OdbcRow {
23    type Database = Odbc;
24
25    fn columns(&self) -> &[OdbcColumn] {
26        self.columns.as_ref()
27    }
28
29    fn try_get_raw<I>(
30        &self,
31        index: I,
32    ) -> Result<<Self::Database as sqlx_core::database::Database>::ValueRef<'_>, sqlx_core::Error>
33    where
34        I: sqlx_core::column::ColumnIndex<Self>,
35    {
36        let index = index.index(self)?;
37        let value = self
38            .values
39            .get(index)
40            .ok_or(sqlx_core::Error::ColumnIndexOutOfBounds {
41                index,
42                len: self.values.len(),
43            })?;
44
45        Ok(sqlx_core::value::Value::as_ref(value))
46    }
47}
48
49impl sqlx_core::column::ColumnIndex<OdbcRow> for usize {
50    fn index(&self, row: &OdbcRow) -> Result<usize, sqlx_core::Error> {
51        if *self >= row.columns.len() {
52            return Err(sqlx_core::Error::ColumnIndexOutOfBounds {
53                index: *self,
54                len: row.columns.len(),
55            });
56        }
57
58        Ok(*self)
59    }
60}
61
62impl sqlx_core::column::ColumnIndex<OdbcRow> for &str {
63    fn index(&self, row: &OdbcRow) -> Result<usize, sqlx_core::Error> {
64        if let Some(index) = row
65            .columns
66            .iter()
67            .position(|column| sqlx_core::column::Column::name(column) == *self)
68        {
69            return Ok(index);
70        }
71
72        row.columns
73            .iter()
74            .position(|column| sqlx_core::column::Column::name(column).eq_ignore_ascii_case(self))
75            .ok_or_else(|| sqlx_core::Error::ColumnNotFound((*self).to_owned()))
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use crate::{OdbcTypeInfo, OdbcValueKind};
83
84    fn create_test_row() -> OdbcRow {
85        OdbcRow::new(
86            vec![
87                OdbcColumn::new(
88                    0,
89                    "lowercase_col",
90                    OdbcTypeInfo::new(odbc_api::DataType::Integer),
91                ),
92                OdbcColumn::new(
93                    1,
94                    "UPPERCASE_COL",
95                    OdbcTypeInfo::new(odbc_api::DataType::Varchar { length: None }),
96                ),
97                OdbcColumn::new(
98                    2,
99                    "MixedCase_Col",
100                    OdbcTypeInfo::new(odbc_api::DataType::Double),
101                ),
102            ],
103            vec![
104                OdbcValue::new(OdbcValueKind::Integer(42)),
105                OdbcValue::new(OdbcValueKind::Text("test".to_owned())),
106                OdbcValue::new(OdbcValueKind::Double(std::f64::consts::PI)),
107            ],
108        )
109    }
110
111    #[test]
112    fn exact_column_match_works() {
113        let row = create_test_row();
114
115        assert_eq!(
116            sqlx_core::column::ColumnIndex::<OdbcRow>::index(&"lowercase_col", &row).unwrap(),
117            0
118        );
119        assert_eq!(
120            sqlx_core::column::ColumnIndex::<OdbcRow>::index(&"UPPERCASE_COL", &row).unwrap(),
121            1
122        );
123        assert_eq!(
124            sqlx_core::column::ColumnIndex::<OdbcRow>::index(&"MixedCase_Col", &row).unwrap(),
125            2
126        );
127    }
128
129    #[test]
130    fn case_insensitive_column_match_works() {
131        let row = create_test_row();
132
133        assert_eq!(
134            sqlx_core::column::ColumnIndex::<OdbcRow>::index(&"LOWERCASE_COL", &row).unwrap(),
135            0
136        );
137        assert_eq!(
138            sqlx_core::column::ColumnIndex::<OdbcRow>::index(&"uppercase_col", &row).unwrap(),
139            1
140        );
141        assert_eq!(
142            sqlx_core::column::ColumnIndex::<OdbcRow>::index(&"mixedcase_col", &row).unwrap(),
143            2
144        );
145    }
146
147    #[test]
148    fn missing_column_reports_name() {
149        let row = create_test_row();
150        let error = sqlx_core::column::ColumnIndex::<OdbcRow>::index(&"missing", &row).unwrap_err();
151
152        assert!(matches!(error, sqlx_core::Error::ColumnNotFound(name) if name == "missing"));
153    }
154}