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,
6};
7use async_trait::async_trait;
8pub use sea_query::Value;
9use sea_query::{ArrayType, ValueTuple};
10use std::fmt::Debug;
11
12/// The interface for Model, implemented by data structs
13#[async_trait]
14pub trait ModelTrait: Clone + Send + Debug {
15    #[allow(missing_docs)]
16    type Entity: EntityTrait;
17
18    /// Get the [Value] of a column from a Model
19    fn get(&self, c: <Self::Entity as EntityTrait>::Column) -> Value;
20
21    /// Get the Value Type of a column from the Model
22    fn get_value_type(c: <Self::Entity as EntityTrait>::Column) -> ArrayType;
23
24    /// Set the Value of a Model field, panic if failed
25    fn set(&mut self, c: <Self::Entity as EntityTrait>::Column, v: Value) {
26        self.try_set(c, v)
27            .unwrap_or_else(|e| panic!("Failed to set value for {:?}: {e:?}", c.as_column_ref()))
28    }
29
30    /// Set the Value of a Model field, return error if failed
31    fn try_set(&mut self, c: <Self::Entity as EntityTrait>::Column, v: Value) -> Result<(), DbErr>;
32
33    /// Find related Models
34    fn find_related<R>(&self, _: R) -> Select<R>
35    where
36        R: EntityTrait,
37        Self::Entity: Related<R>,
38    {
39        <Self::Entity as Related<R>>::find_related().belongs_to(self)
40    }
41
42    /// Find linked Models
43    fn find_linked<L>(&self, l: L) -> Select<L::ToEntity>
44    where
45        L: Linked<FromEntity = Self::Entity>,
46    {
47        let tbl_alias = &format!("r{}", l.link().len() - 1);
48        l.find_linked().belongs_to_tbl_alias(self, tbl_alias)
49    }
50
51    /// Delete a model
52    async fn delete<'a, A, C>(self, db: &'a C) -> Result<DeleteResult, DbErr>
53    where
54        Self: IntoActiveModel<A>,
55        C: ConnectionTrait,
56        A: ActiveModelTrait<Entity = Self::Entity> + ActiveModelBehavior + Send + 'a,
57    {
58        self.into_active_model().delete(db).await
59    }
60
61    /// Get the primary key value of the Model
62    fn get_primary_key_value(&self) -> ValueTuple {
63        let mut cols = <Self::Entity as EntityTrait>::PrimaryKey::iter();
64        macro_rules! next {
65            () => {
66                self.get(cols.next().expect("Already checked arity").into_column())
67            };
68        }
69        match <<<Self::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType as PrimaryKeyArity>::ARITY {
70            1 => {
71                let s1 = next!();
72                ValueTuple::One(s1)
73            }
74            2 => {
75                let s1 = next!();
76                let s2 = next!();
77                ValueTuple::Two(s1, s2)
78            }
79            3 => {
80                let s1 = next!();
81                let s2 = next!();
82                let s3 = next!();
83                ValueTuple::Three(s1, s2, s3)
84            }
85            len => {
86                let mut vec = Vec::with_capacity(len);
87                for _ in 0..len {
88                    let s = next!();
89                    vec.push(s);
90                }
91                ValueTuple::Many(vec)
92            }
93        }
94    }
95}
96
97/// A Trait for implementing a [QueryResult]
98pub trait FromQueryResult: Sized {
99    /// Instantiate a Model from a [QueryResult]
100    ///
101    /// NOTE: Please also override `from_query_result_nullable` when manually implementing.
102    ///       The future default implementation will be along the lines of:
103    ///
104    /// ```rust,ignore
105    /// fn from_query_result(res: &QueryResult, pre: &str) -> Result<Self, DbErr> {
106    ///     (Self::from_query_result_nullable(res, pre)?)
107    /// }
108    /// ```
109    fn from_query_result(res: &QueryResult, pre: &str) -> Result<Self, DbErr>;
110
111    /// Transform the error from instantiating a Model from a [QueryResult]
112    /// and converting it to an [Option]
113    fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result<Option<Self>, DbErr> {
114        Ok(Self::from_query_result(res, pre).ok())
115
116        // would really like to do the following, but can't without version bump:
117        // match Self::from_query_result_nullable(res, pre) {
118        //     Ok(v) => Ok(Some(v)),
119        //     Err(TryGetError::Null(_)) => Ok(None),
120        //     Err(TryGetError::DbErr(err)) => Err(err),
121        // }
122    }
123
124    /// Transform the error from instantiating a Model from a [QueryResult]
125    /// and converting it to an [Option]
126    ///
127    /// NOTE: This will most likely stop being a provided method in the next major version!
128    fn from_query_result_nullable(res: &QueryResult, pre: &str) -> Result<Self, TryGetError> {
129        Self::from_query_result(res, pre).map_err(TryGetError::DbErr)
130    }
131
132    /// ```
133    /// # use sea_orm::{error::*, tests_cfg::*, *};
134    /// #
135    /// # #[smol_potat::main]
136    /// # #[cfg(feature = "mock")]
137    /// # pub async fn main() -> Result<(), DbErr> {
138    /// #
139    /// # let db = MockDatabase::new(DbBackend::Postgres)
140    /// #     .append_query_results([[
141    /// #         maplit::btreemap! {
142    /// #             "name" => Into::<Value>::into("Chocolate Forest"),
143    /// #             "num_of_cakes" => Into::<Value>::into(2),
144    /// #         },
145    /// #     ]])
146    /// #     .into_connection();
147    /// #
148    /// use sea_orm::{FromQueryResult, query::*};
149    ///
150    /// #[derive(Debug, PartialEq, FromQueryResult)]
151    /// struct SelectResult {
152    ///     name: String,
153    ///     num_of_cakes: i32,
154    /// }
155    ///
156    /// let res: Vec<SelectResult> = SelectResult::find_by_statement(Statement::from_sql_and_values(
157    ///     DbBackend::Postgres,
158    ///     r#"SELECT "name", COUNT(*) AS "num_of_cakes" FROM "cake" GROUP BY("name")"#,
159    ///     [],
160    /// ))
161    /// .all(&db)
162    /// .await?;
163    ///
164    /// assert_eq!(
165    ///     res,
166    ///     [SelectResult {
167    ///         name: "Chocolate Forest".to_owned(),
168    ///         num_of_cakes: 2,
169    ///     },]
170    /// );
171    /// #
172    /// # assert_eq!(
173    /// #     db.into_transaction_log(),
174    /// #     [Transaction::from_sql_and_values(
175    /// #         DbBackend::Postgres,
176    /// #         r#"SELECT "name", COUNT(*) AS "num_of_cakes" FROM "cake" GROUP BY("name")"#,
177    /// #         []
178    /// #     ),]
179    /// # );
180    /// #
181    /// # Ok(())
182    /// # }
183    /// ```
184    fn find_by_statement(stmt: Statement) -> SelectorRaw<SelectModel<Self>> {
185        SelectorRaw::<SelectModel<Self>>::from_statement(stmt)
186    }
187}
188
189impl<T: FromQueryResult> FromQueryResult for Option<T> {
190    fn from_query_result(res: &QueryResult, pre: &str) -> Result<Self, DbErr> {
191        Ok(Self::from_query_result_nullable(res, pre)?)
192    }
193
194    fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result<Option<Self>, DbErr> {
195        match Self::from_query_result_nullable(res, pre) {
196            Ok(v) => Ok(Some(v)),
197            Err(TryGetError::Null(_)) => Ok(None),
198            Err(TryGetError::DbErr(err)) => Err(err),
199        }
200    }
201
202    fn from_query_result_nullable(res: &QueryResult, pre: &str) -> Result<Self, TryGetError> {
203        match T::from_query_result_nullable(res, pre) {
204            Ok(v) => Ok(Some(v)),
205            Err(TryGetError::Null(_)) => Ok(None),
206            Err(err @ TryGetError::DbErr(_)) => Err(err),
207        }
208    }
209}
210
211/// A Trait for any type that can be converted into an Model
212pub trait TryIntoModel<M>
213where
214    M: ModelTrait,
215{
216    /// Method to call to perform the conversion
217    fn try_into_model(self) -> Result<M, DbErr>;
218}
219
220impl<M> TryIntoModel<M> for M
221where
222    M: ModelTrait,
223{
224    fn try_into_model(self) -> Result<M, DbErr> {
225        Ok(self)
226    }
227}