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