sea_orm/entity/
model.rs

1use crate::{
2    ActiveModelBehavior, ActiveModelTrait, ColumnTrait, ConnectionTrait, DbErr, DeleteResult,
3    EntityTrait, IntoActiveModel, Iterable, Linked, PrimaryKeyArity, PrimaryKeyToColumn,
4    PrimaryKeyTrait, QueryFilter, QueryResult, Related, Select, SelectModel, SelectorRaw,
5    Statement, TryGetError, find_linked_recursive,
6};
7pub use sea_query::Value;
8use sea_query::{ArrayType, ValueTuple};
9use std::fmt::Debug;
10
11/// The interface for Model, implemented by data structs
12pub trait ModelTrait: Clone + Debug {
13    #[allow(missing_docs)]
14    type Entity: EntityTrait;
15
16    /// Get the [Value] of a column from a Model
17    fn get(&self, c: <Self::Entity as EntityTrait>::Column) -> Value;
18
19    /// Get the Value Type of a column from the Model
20    fn get_value_type(c: <Self::Entity as EntityTrait>::Column) -> ArrayType;
21
22    /// Set the Value of a Model field, panic if failed
23    fn set(&mut self, c: <Self::Entity as EntityTrait>::Column, v: Value) {
24        self.try_set(c, v)
25            .unwrap_or_else(|e| panic!("Failed to set value for {:?}: {e:?}", c.as_column_ref()))
26    }
27
28    /// Set the Value of a Model field, return error if failed
29    fn try_set(&mut self, c: <Self::Entity as EntityTrait>::Column, v: Value) -> Result<(), DbErr>;
30
31    /// Find related Models belonging to self
32    fn find_related<R>(&self, _: R) -> Select<R>
33    where
34        R: EntityTrait,
35        Self::Entity: Related<R>,
36    {
37        <Self::Entity as Related<R>>::find_related().belongs_to(self)
38    }
39
40    /// Find linked Models belonging to self
41    fn find_linked<L>(&self, l: L) -> Select<L::ToEntity>
42    where
43        L: Linked<FromEntity = Self::Entity>,
44    {
45        let tbl_alias = &format!("r{}", l.link().len() - 1);
46        l.find_linked().belongs_to_tbl_alias(self, tbl_alias)
47    }
48
49    #[doc(hidden)]
50    /// Find linked Models with a recursive CTE for self-referencing relation chains
51    fn find_linked_recursive<L>(&self, l: L) -> Select<L::ToEntity>
52    where
53        L: Linked<FromEntity = Self::Entity, ToEntity = Self::Entity>,
54    {
55        // Have to do this because L is not Clone
56        let link = l.link();
57        let initial_query = self.find_linked(l);
58        find_linked_recursive(initial_query, link)
59    }
60
61    /// Delete a model
62    fn delete<'a, A, C>(self, db: &'a C) -> Result<DeleteResult, DbErr>
63    where
64        Self: IntoActiveModel<A>,
65        C: ConnectionTrait,
66        A: ActiveModelTrait<Entity = Self::Entity> + ActiveModelBehavior + 'a,
67    {
68        self.into_active_model().delete(db)
69    }
70
71    /// Get the primary key value of the Model
72    fn get_primary_key_value(&self) -> ValueTuple {
73        let mut cols = <Self::Entity as EntityTrait>::PrimaryKey::iter();
74        macro_rules! next {
75            () => {
76                self.get(cols.next().expect("Already checked arity").into_column())
77            };
78        }
79        match <<<Self::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType as PrimaryKeyArity>::ARITY {
80            1 => {
81                let s1 = next!();
82                ValueTuple::One(s1)
83            }
84            2 => {
85                let s1 = next!();
86                let s2 = next!();
87                ValueTuple::Two(s1, s2)
88            }
89            3 => {
90                let s1 = next!();
91                let s2 = next!();
92                let s3 = next!();
93                ValueTuple::Three(s1, s2, s3)
94            }
95            len => {
96                let mut vec = Vec::with_capacity(len);
97                for _ in 0..len {
98                    let s = next!();
99                    vec.push(s);
100                }
101                ValueTuple::Many(vec)
102            }
103        }
104    }
105}
106
107/// A Trait for implementing a [QueryResult]
108pub trait FromQueryResult: Sized {
109    /// Instantiate a Model from a [QueryResult]
110    ///
111    /// NOTE: Please also override `from_query_result_nullable` when manually implementing.
112    ///       The future default implementation will be along the lines of:
113    ///
114    /// ```rust,ignore
115    /// fn from_query_result(res: &QueryResult, pre: &str) -> Result<Self, DbErr> {
116    ///     (Self::from_query_result_nullable(res, pre)?)
117    /// }
118    /// ```
119    fn from_query_result(res: &QueryResult, pre: &str) -> Result<Self, DbErr>;
120
121    /// Transform the error from instantiating a Model from a [QueryResult]
122    /// and converting it to an [Option]
123    fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result<Option<Self>, DbErr> {
124        Ok(Self::from_query_result(res, pre).ok())
125
126        // would really like to do the following, but can't without version bump:
127        // match Self::from_query_result_nullable(res, pre) {
128        //     Ok(v) => Ok(Some(v)),
129        //     Err(TryGetError::Null(_)) => Ok(None),
130        //     Err(TryGetError::DbErr(err)) => Err(err),
131        // }
132    }
133
134    /// Transform the error from instantiating a Model from a [QueryResult]
135    /// and converting it to an [Option]
136    ///
137    /// NOTE: This will most likely stop being a provided method in the next major version!
138    fn from_query_result_nullable(res: &QueryResult, pre: &str) -> Result<Self, TryGetError> {
139        Self::from_query_result(res, pre).map_err(TryGetError::DbErr)
140    }
141
142    /// ```
143    /// # use sea_orm::{error::*, tests_cfg::*, *};
144    /// #
145    /// # #[cfg(feature = "mock")]
146    /// # pub fn main() -> Result<(), DbErr> {
147    /// #
148    /// # let db = MockDatabase::new(DbBackend::Postgres)
149    /// #     .append_query_results([[
150    /// #         maplit::btreemap! {
151    /// #             "name" => Into::<Value>::into("Chocolate Forest"),
152    /// #             "num_of_cakes" => Into::<Value>::into(2),
153    /// #         },
154    /// #     ]])
155    /// #     .into_connection();
156    /// #
157    /// use sea_orm::{FromQueryResult, query::*};
158    ///
159    /// #[derive(Debug, PartialEq, FromQueryResult)]
160    /// struct SelectResult {
161    ///     name: String,
162    ///     num_of_cakes: i32,
163    /// }
164    ///
165    /// let res: Vec<SelectResult> = SelectResult::find_by_statement(Statement::from_sql_and_values(
166    ///     DbBackend::Postgres,
167    ///     r#"SELECT "name", COUNT(*) AS "num_of_cakes" FROM "cake" GROUP BY("name")"#,
168    ///     [],
169    /// ))
170    /// .all(&db)
171    /// ?;
172    ///
173    /// assert_eq!(
174    ///     res,
175    ///     [SelectResult {
176    ///         name: "Chocolate Forest".to_owned(),
177    ///         num_of_cakes: 2,
178    ///     },]
179    /// );
180    /// #
181    /// # assert_eq!(
182    /// #     db.into_transaction_log(),
183    /// #     [Transaction::from_sql_and_values(
184    /// #         DbBackend::Postgres,
185    /// #         r#"SELECT "name", COUNT(*) AS "num_of_cakes" FROM "cake" GROUP BY("name")"#,
186    /// #         []
187    /// #     ),]
188    /// # );
189    /// #
190    /// # Ok(())
191    /// # }
192    /// ```
193    fn find_by_statement(stmt: Statement) -> SelectorRaw<SelectModel<Self>> {
194        SelectorRaw::<SelectModel<Self>>::from_statement(stmt)
195    }
196}
197
198impl<T: FromQueryResult> FromQueryResult for Option<T> {
199    fn from_query_result(res: &QueryResult, pre: &str) -> Result<Self, DbErr> {
200        Ok(Self::from_query_result_nullable(res, pre)?)
201    }
202
203    fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result<Option<Self>, DbErr> {
204        match Self::from_query_result_nullable(res, pre) {
205            Ok(v) => Ok(Some(v)),
206            Err(TryGetError::Null(_)) => Ok(None),
207            Err(TryGetError::DbErr(err)) => Err(err),
208        }
209    }
210
211    fn from_query_result_nullable(res: &QueryResult, pre: &str) -> Result<Self, TryGetError> {
212        match T::from_query_result_nullable(res, pre) {
213            Ok(v) => Ok(Some(v)),
214            Err(TryGetError::Null(_)) => Ok(None),
215            Err(err @ TryGetError::DbErr(_)) => Err(err),
216        }
217    }
218}
219
220/// A Trait for any type that can be converted into an Model
221pub trait TryIntoModel<M>
222where
223    M: ModelTrait,
224{
225    /// Method to call to perform the conversion
226    fn try_into_model(self) -> Result<M, DbErr>;
227}
228
229impl<M> TryIntoModel<M> for M
230where
231    M: ModelTrait,
232{
233    fn try_into_model(self) -> Result<M, DbErr> {
234        Ok(self)
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use crate::{
241        IntoActiveModel, Unchanged,
242        prelude::*,
243        tests_cfg::{cake, filling, fruit, post},
244    };
245
246    #[test]
247    fn test_model() {
248        fn filter_by_column(col: post::Column) -> Expr {
249            col.eq("attribute")
250        }
251
252        fn get_value_from(model: &post::Model, col: post::Column) {
253            let value: i32 = model.get(col).unwrap();
254            assert_eq!(value, 12);
255        }
256
257        let model = post::Model {
258            id: 12,
259            user_id: 14,
260            title: "hello".into(),
261        };
262
263        get_value_from(&model, post::COLUMN.id.0);
264        filter_by_column(post::COLUMN.title.0);
265
266        let filling = filling::Model {
267            id: 12,
268            name: "".into(),
269            vendor_id: None,
270            ignored_attr: 24,
271        };
272
273        let filling_am = filling::ActiveModel {
274            id: Unchanged(12),
275            name: Unchanged("".into()),
276            vendor_id: Unchanged(None),
277        };
278
279        assert_eq!(filling.into_active_model(), filling_am);
280
281        let filling_ex = filling::ActiveModelEx {
282            id: Unchanged(12),
283            name: Unchanged("".into()),
284            vendor_id: Unchanged(None),
285            ingredients: HasManyModel::NotSet,
286        };
287
288        assert_eq!(filling_am.into_ex(), filling_ex);
289
290        let cake_ex = cake::ModelEx {
291            id: 12,
292            name: "C".into(),
293            fruit: HasOne::loaded(fruit::Model {
294                id: 13,
295                name: "F".into(),
296                cake_id: Some(12),
297            }),
298            fillings: HasMany::Loaded(vec![
299                filling::Model {
300                    id: 14,
301                    name: "FF".into(),
302                    vendor_id: None,
303                    ignored_attr: 1,
304                }
305                .into(),
306            ]),
307        };
308
309        let cake_am = cake::ActiveModelEx {
310            id: Unchanged(12),
311            name: Unchanged("C".into()),
312            fruit: HasOneModel::Set(
313                fruit::ActiveModelEx {
314                    id: Unchanged(13),
315                    name: Unchanged("F".into()),
316                    cake_id: Unchanged(Some(12)),
317                }
318                .into(),
319            ),
320            fillings: HasManyModel::Append(vec![filling::ActiveModelEx {
321                id: Unchanged(14),
322                name: Unchanged("FF".into()),
323                vendor_id: Unchanged(None),
324                ingredients: HasManyModel::NotSet,
325            }]),
326        };
327
328        assert_eq!(cake_ex.into_active_model(), cake_am);
329    }
330}