Skip to main content

mssql_client/
from_row.rs

1//! FromRow trait for automatic row-to-struct mapping.
2//!
3//! This module provides the `FromRow` trait which enables automatic conversion
4//! from database rows to Rust structs.
5//!
6//! ## Derive Macro
7//!
8//! The recommended way to implement `FromRow` is via the derive macro from
9//! `mssql-derive`:
10//!
11//! ```rust,no_run
12//! use mssql_derive::FromRow;
13//!
14//! #[derive(FromRow)]
15//! struct User {
16//!     id: i32,
17//!     #[mssql(rename = "user_name")]
18//!     name: String,
19//!     email: Option<String>,
20//! }
21//! ```
22//!
23//! ## Supported Attributes
24//!
25//! - `#[mssql(rename = "column_name")]` - Map field to a different column name
26//! - `#[mssql(skip)]` - Skip field, use Default value
27//! - `#[mssql(default)]` - Use Default if column not found
28//! - `#[mssql(flatten)]` - Flatten nested FromRow structs
29
30use crate::error::Error;
31use crate::row::Row;
32
33/// Trait for types that can be constructed from a database row.
34///
35/// This trait is typically implemented via the `#[derive(FromRow)]` macro,
36/// but can also be implemented manually for custom mapping logic.
37///
38/// # Example
39///
40/// ```rust,no_run
41/// use mssql_client::{FromRow, Row, Error};
42///
43/// struct User {
44///     id: i32,
45///     name: String,
46/// }
47///
48/// impl FromRow for User {
49///     fn from_row(row: &Row) -> Result<Self, Error> {
50///         Ok(Self {
51///             id: row.get_by_name("id")?,
52///             name: row.get_by_name("name")?,
53///         })
54///     }
55/// }
56/// ```
57pub trait FromRow: Sized {
58    /// Construct an instance of this type from a database row.
59    ///
60    /// # Errors
61    ///
62    /// Returns an error if:
63    /// - A required column is missing
64    /// - A column value cannot be converted to the expected Rust type
65    /// - Any other mapping error occurs
66    fn from_row(row: &Row) -> Result<Self, Error>;
67}
68
69/// Extension trait for iterating over query results as typed structs.
70///
71/// This trait is automatically implemented for any iterator of `Result<Row, Error>`.
72pub trait RowIteratorExt: Iterator<Item = Result<Row, Error>> + Sized {
73    /// Map each row to a struct implementing `FromRow`.
74    ///
75    /// # Example
76    ///
77    /// ```rust,no_run
78    /// # async fn ex(client: &mut mssql_client::Client<mssql_client::Ready>) -> Result<(), mssql_client::Error> {
79    /// use mssql_client::{FromRow, RowIteratorExt};
80    ///
81    /// #[derive(mssql_derive::FromRow)]
82    /// struct User { id: i32, name: String }
83    ///
84    /// let users: Vec<User> = client
85    ///     .query("SELECT id, name FROM users", &[])
86    ///     .await?
87    ///     .map_rows::<User>()
88    ///     .collect::<Result<Vec<_>, _>>()?;
89    /// # let _ = users;
90    /// # Ok(())
91    /// # }
92    /// ```
93    fn map_rows<T: FromRow>(self) -> MapRows<Self, T>;
94}
95
96impl<I: Iterator<Item = Result<Row, Error>>> RowIteratorExt for I {
97    fn map_rows<T: FromRow>(self) -> MapRows<Self, T> {
98        MapRows {
99            inner: self,
100            _marker: std::marker::PhantomData,
101        }
102    }
103}
104
105/// Iterator adapter that maps rows to typed structs.
106pub struct MapRows<I, T> {
107    inner: I,
108    _marker: std::marker::PhantomData<T>,
109}
110
111impl<I, T> Iterator for MapRows<I, T>
112where
113    I: Iterator<Item = Result<Row, Error>>,
114    T: FromRow,
115{
116    type Item = Result<T, Error>;
117
118    fn next(&mut self) -> Option<Self::Item> {
119        self.inner
120            .next()
121            .map(|result| result.and_then(|row| T::from_row(&row)))
122    }
123
124    fn size_hint(&self) -> (usize, Option<usize>) {
125        self.inner.size_hint()
126    }
127}
128
129#[cfg(test)]
130#[allow(clippy::unwrap_used)]
131mod tests {
132    use super::*;
133    use crate::row::Column;
134    use mssql_types::SqlValue;
135
136    struct TestUser {
137        id: i32,
138        name: String,
139    }
140
141    impl FromRow for TestUser {
142        fn from_row(row: &Row) -> Result<Self, Error> {
143            Ok(Self {
144                id: row.get_by_name("id").map_err(Error::from)?,
145                name: row.get_by_name("name").map_err(Error::from)?,
146            })
147        }
148    }
149
150    #[test]
151    fn test_from_row_manual_impl() {
152        let columns = vec![
153            Column::new("id", 0, "INT".to_string()),
154            Column::new("name", 1, "NVARCHAR".to_string()),
155        ];
156        let row = Row::from_values(
157            columns,
158            vec![SqlValue::Int(42), SqlValue::String("Alice".to_string())],
159        );
160
161        let user = TestUser::from_row(&row).unwrap();
162        assert_eq!(user.id, 42);
163        assert_eq!(user.name, "Alice");
164    }
165
166    #[test]
167    fn test_map_rows_iterator() {
168        let columns = vec![
169            Column::new("id", 0, "INT".to_string()),
170            Column::new("name", 1, "NVARCHAR".to_string()),
171        ];
172
173        let rows = vec![
174            Ok(Row::from_values(
175                columns.clone(),
176                vec![SqlValue::Int(1), SqlValue::String("Alice".to_string())],
177            )),
178            Ok(Row::from_values(
179                columns.clone(),
180                vec![SqlValue::Int(2), SqlValue::String("Bob".to_string())],
181            )),
182        ];
183
184        let users: Vec<TestUser> = rows
185            .into_iter()
186            .map_rows::<TestUser>()
187            .collect::<Result<Vec<_>, _>>()
188            .unwrap();
189
190        assert_eq!(users.len(), 2);
191        assert_eq!(users[0].id, 1);
192        assert_eq!(users[0].name, "Alice");
193        assert_eq!(users[1].id, 2);
194        assert_eq!(users[1].name, "Bob");
195    }
196}