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,ignore
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,ignore
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,ignore
78    /// use mssql_client::{FromRow, RowIteratorExt};
79    ///
80    /// #[derive(FromRow)]
81    /// struct User { id: i32, name: String }
82    ///
83    /// let users: Vec<User> = client
84    ///     .query("SELECT id, name FROM users", &[])
85    ///     .await?
86    ///     .map_rows::<User>()
87    ///     .collect::<Result<Vec<_>, _>>()?;
88    /// ```
89    fn map_rows<T: FromRow>(self) -> MapRows<Self, T>;
90}
91
92impl<I: Iterator<Item = Result<Row, Error>>> RowIteratorExt for I {
93    fn map_rows<T: FromRow>(self) -> MapRows<Self, T> {
94        MapRows {
95            inner: self,
96            _marker: std::marker::PhantomData,
97        }
98    }
99}
100
101/// Iterator adapter that maps rows to typed structs.
102pub struct MapRows<I, T> {
103    inner: I,
104    _marker: std::marker::PhantomData<T>,
105}
106
107impl<I, T> Iterator for MapRows<I, T>
108where
109    I: Iterator<Item = Result<Row, Error>>,
110    T: FromRow,
111{
112    type Item = Result<T, Error>;
113
114    fn next(&mut self) -> Option<Self::Item> {
115        self.inner
116            .next()
117            .map(|result| result.and_then(|row| T::from_row(&row)))
118    }
119
120    fn size_hint(&self) -> (usize, Option<usize>) {
121        self.inner.size_hint()
122    }
123}
124
125#[cfg(test)]
126#[allow(clippy::unwrap_used)]
127mod tests {
128    use super::*;
129    use crate::row::Column;
130    use mssql_types::SqlValue;
131
132    struct TestUser {
133        id: i32,
134        name: String,
135    }
136
137    impl FromRow for TestUser {
138        fn from_row(row: &Row) -> Result<Self, Error> {
139            Ok(Self {
140                id: row.get_by_name("id").map_err(Error::from)?,
141                name: row.get_by_name("name").map_err(Error::from)?,
142            })
143        }
144    }
145
146    #[test]
147    fn test_from_row_manual_impl() {
148        let columns = vec![
149            Column::new("id", 0, "INT".to_string()),
150            Column::new("name", 1, "NVARCHAR".to_string()),
151        ];
152        let row = Row::from_values(
153            columns,
154            vec![SqlValue::Int(42), SqlValue::String("Alice".to_string())],
155        );
156
157        let user = TestUser::from_row(&row).unwrap();
158        assert_eq!(user.id, 42);
159        assert_eq!(user.name, "Alice");
160    }
161
162    #[test]
163    fn test_map_rows_iterator() {
164        let columns = vec![
165            Column::new("id", 0, "INT".to_string()),
166            Column::new("name", 1, "NVARCHAR".to_string()),
167        ];
168
169        let rows = vec![
170            Ok(Row::from_values(
171                columns.clone(),
172                vec![SqlValue::Int(1), SqlValue::String("Alice".to_string())],
173            )),
174            Ok(Row::from_values(
175                columns.clone(),
176                vec![SqlValue::Int(2), SqlValue::String("Bob".to_string())],
177            )),
178        ];
179
180        let users: Vec<TestUser> = rows
181            .into_iter()
182            .map_rows::<TestUser>()
183            .collect::<Result<Vec<_>, _>>()
184            .unwrap();
185
186        assert_eq!(users.len(), 2);
187        assert_eq!(users[0].id, 1);
188        assert_eq!(users[0].name, "Alice");
189        assert_eq!(users[1].id, 2);
190        assert_eq!(users[1].name, "Bob");
191    }
192}