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}