Skip to main content

sqlx_mssql_odbc_core/
row.rs

1use crate::{Mssql, MssqlColumn, MssqlValue};
2use std::sync::Arc;
3
4/// Minimal MSSQL row container used by the SQLx-core skeleton.
5#[derive(Debug, Clone, Default)]
6pub struct MssqlRow {
7    columns: Arc<[MssqlColumn]>,
8    values: Vec<MssqlValue>,
9}
10
11impl MssqlRow {
12    /// Creates a row from column metadata and values.
13    pub fn new(columns: Vec<MssqlColumn>, values: Vec<MssqlValue>) -> Self {
14        Self::new_shared(columns.into(), values)
15    }
16
17    pub(crate) fn new_shared(columns: Arc<[MssqlColumn]>, values: Vec<MssqlValue>) -> Self {
18        Self { columns, values }
19    }
20}
21
22impl sqlx_core::row::Row for MssqlRow {
23    type Database = Mssql;
24
25    fn columns(&self) -> &[MssqlColumn] {
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<MssqlRow> for usize {
50    fn index(&self, row: &MssqlRow) -> 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<MssqlRow> for &str {
63    fn index(&self, row: &MssqlRow) -> 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::{MssqlTypeInfo, MssqlValueKind};
83
84    fn create_test_row() -> MssqlRow {
85        MssqlRow::new(
86            vec![
87                MssqlColumn::new(
88                    0,
89                    "lowercase_col",
90                    MssqlTypeInfo::new(odbc_api::DataType::Integer),
91                    None,
92                ),
93                MssqlColumn::new(
94                    1,
95                    "UPPERCASE_COL",
96                    MssqlTypeInfo::new(odbc_api::DataType::Varchar { length: None }),
97                    None,
98                ),
99                MssqlColumn::new(
100                    2,
101                    "MixedCase_Col",
102                    MssqlTypeInfo::new(odbc_api::DataType::Double),
103                    None,
104                ),
105            ],
106            vec![
107                MssqlValue::new(MssqlValueKind::Integer(42)),
108                MssqlValue::new(MssqlValueKind::Text("test".to_owned())),
109                MssqlValue::new(MssqlValueKind::Double(std::f64::consts::PI)),
110            ],
111        )
112    }
113
114    #[test]
115    fn exact_column_match_works() {
116        let row = create_test_row();
117
118        assert_eq!(
119            sqlx_core::column::ColumnIndex::<MssqlRow>::index(&"lowercase_col", &row).unwrap(),
120            0
121        );
122        assert_eq!(
123            sqlx_core::column::ColumnIndex::<MssqlRow>::index(&"UPPERCASE_COL", &row).unwrap(),
124            1
125        );
126        assert_eq!(
127            sqlx_core::column::ColumnIndex::<MssqlRow>::index(&"MixedCase_Col", &row).unwrap(),
128            2
129        );
130    }
131
132    #[test]
133    fn case_insensitive_column_match_works() {
134        let row = create_test_row();
135
136        assert_eq!(
137            sqlx_core::column::ColumnIndex::<MssqlRow>::index(&"LOWERCASE_COL", &row).unwrap(),
138            0
139        );
140        assert_eq!(
141            sqlx_core::column::ColumnIndex::<MssqlRow>::index(&"uppercase_col", &row).unwrap(),
142            1
143        );
144        assert_eq!(
145            sqlx_core::column::ColumnIndex::<MssqlRow>::index(&"mixedcase_col", &row).unwrap(),
146            2
147        );
148    }
149
150    #[test]
151    fn missing_column_reports_name() {
152        let row = create_test_row();
153        let error = sqlx_core::column::ColumnIndex::<MssqlRow>::index(&"missing", &row).unwrap_err();
154
155        assert!(matches!(error, sqlx_core::Error::ColumnNotFound(name) if name == "missing"));
156    }
157
158    #[test]
159    fn try_get_raw_uses_case_insensitive_column_lookup() {
160        use sqlx_core::row::Row;
161
162        let row = create_test_row();
163        let value = row.try_get_raw("mixedcase_col").unwrap();
164
165        assert_eq!(value.as_f64(), Some(std::f64::consts::PI));
166    }
167
168    #[test]
169    fn columns_returns_metadata_in_order() {
170        use sqlx_core::column::Column;
171        use sqlx_core::row::Row;
172
173        let row = create_test_row();
174        let columns = row.columns();
175
176        assert_eq!(columns.len(), 3);
177        assert_eq!(columns[0].ordinal(), 0);
178        assert_eq!(columns[0].name(), "lowercase_col");
179        assert_eq!(columns[1].ordinal(), 1);
180        assert_eq!(columns[1].name(), "UPPERCASE_COL");
181        assert_eq!(columns[2].ordinal(), 2);
182        assert_eq!(columns[2].name(), "MixedCase_Col");
183    }
184}