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}