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}