Skip to main content

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