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