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#[async_trait::async_trait]
13pub trait ModelTrait: Clone + Send + Debug {
14 #[allow(missing_docs)]
15 type Entity: EntityTrait;
16
17 fn get(&self, c: <Self::Entity as EntityTrait>::Column) -> Value;
19
20 fn get_value_type(c: <Self::Entity as EntityTrait>::Column) -> ArrayType;
22
23 fn set(&mut self, c: <Self::Entity as EntityTrait>::Column, v: Value) {
25 self.try_set(c, v)
26 .unwrap_or_else(|e| panic!("Failed to set value for {:?}: {e:?}", c.as_column_ref()))
27 }
28
29 fn try_set(&mut self, c: <Self::Entity as EntityTrait>::Column, v: Value) -> Result<(), DbErr>;
31
32 fn find_related<R>(&self, _: R) -> Select<R>
34 where
35 R: EntityTrait,
36 Self::Entity: Related<R>,
37 {
38 <Self::Entity as Related<R>>::find_related().belongs_to(self)
39 }
40
41 fn find_linked<L>(&self, l: L) -> Select<L::ToEntity>
43 where
44 L: Linked<FromEntity = Self::Entity>,
45 {
46 let tbl_alias = &format!("r{}", l.link().len() - 1);
47 l.find_linked().belongs_to_tbl_alias(self, tbl_alias)
48 }
49
50 #[doc(hidden)]
51 fn find_linked_recursive<L>(&self, l: L) -> Select<L::ToEntity>
53 where
54 L: Linked<FromEntity = Self::Entity, ToEntity = Self::Entity>,
55 {
56 let link = l.link();
58 let initial_query = self.find_linked(l);
59 find_linked_recursive(initial_query, link)
60 }
61
62 async fn delete<'a, A, C>(self, db: &'a C) -> Result<DeleteResult, DbErr>
64 where
65 Self: IntoActiveModel<A>,
66 C: ConnectionTrait,
67 A: ActiveModelTrait<Entity = Self::Entity> + ActiveModelBehavior + Send + 'a,
68 {
69 self.into_active_model().delete(db).await
70 }
71
72 fn get_primary_key_value(&self) -> ValueTuple {
74 let mut cols = <Self::Entity as EntityTrait>::PrimaryKey::iter();
75 macro_rules! next {
76 () => {
77 self.get(cols.next().expect("Already checked arity").into_column())
78 };
79 }
80 match <<<Self::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType as PrimaryKeyArity>::ARITY {
81 1 => {
82 let s1 = next!();
83 ValueTuple::One(s1)
84 }
85 2 => {
86 let s1 = next!();
87 let s2 = next!();
88 ValueTuple::Two(s1, s2)
89 }
90 3 => {
91 let s1 = next!();
92 let s2 = next!();
93 let s3 = next!();
94 ValueTuple::Three(s1, s2, s3)
95 }
96 len => {
97 let mut vec = Vec::with_capacity(len);
98 for _ in 0..len {
99 let s = next!();
100 vec.push(s);
101 }
102 ValueTuple::Many(vec)
103 }
104 }
105 }
106}
107
108pub trait FromQueryResult: Sized {
110 fn from_query_result(res: &QueryResult, pre: &str) -> Result<Self, DbErr>;
121
122 fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result<Option<Self>, DbErr> {
125 Ok(Self::from_query_result(res, pre).ok())
126
127 }
134
135 fn from_query_result_nullable(res: &QueryResult, pre: &str) -> Result<Self, TryGetError> {
140 Self::from_query_result(res, pre).map_err(TryGetError::DbErr)
141 }
142
143 fn find_by_statement(stmt: Statement) -> SelectorRaw<SelectModel<Self>> {
196 SelectorRaw::<SelectModel<Self>>::from_statement(stmt)
197 }
198}
199
200impl<T: FromQueryResult> FromQueryResult for Option<T> {
201 fn from_query_result(res: &QueryResult, pre: &str) -> Result<Self, DbErr> {
202 Ok(Self::from_query_result_nullable(res, pre)?)
203 }
204
205 fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result<Option<Self>, DbErr> {
206 match Self::from_query_result_nullable(res, pre) {
207 Ok(v) => Ok(Some(v)),
208 Err(TryGetError::Null(_)) => Ok(None),
209 Err(TryGetError::DbErr(err)) => Err(err),
210 }
211 }
212
213 fn from_query_result_nullable(res: &QueryResult, pre: &str) -> Result<Self, TryGetError> {
214 match T::from_query_result_nullable(res, pre) {
215 Ok(v) => Ok(Some(v)),
216 Err(TryGetError::Null(_)) => Ok(None),
217 Err(err @ TryGetError::DbErr(_)) => Err(err),
218 }
219 }
220}
221
222pub trait TryIntoModel<M>
224where
225 M: ModelTrait,
226{
227 fn try_into_model(self) -> Result<M, DbErr>;
229}
230
231impl<M> TryIntoModel<M> for M
232where
233 M: ModelTrait,
234{
235 fn try_into_model(self) -> Result<M, DbErr> {
236 Ok(self)
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use crate::{
243 IntoActiveModel, Unchanged,
244 prelude::*,
245 tests_cfg::{cake, filling, fruit, post},
246 };
247
248 #[test]
249 fn test_model() {
250 fn filter_by_column(col: post::Column) -> Expr {
251 col.eq("attribute")
252 }
253
254 fn get_value_from(model: &post::Model, col: post::Column) {
255 let value: i32 = model.get(col).unwrap();
256 assert_eq!(value, 12);
257 }
258
259 let model = post::Model {
260 id: 12,
261 user_id: 14,
262 title: "hello".into(),
263 };
264
265 get_value_from(&model, post::COLUMN.id.0);
266 filter_by_column(post::COLUMN.title.0);
267
268 let filling = filling::Model {
269 id: 12,
270 name: "".into(),
271 vendor_id: None,
272 ignored_attr: 24,
273 };
274
275 let filling_am = filling::ActiveModel {
276 id: Unchanged(12),
277 name: Unchanged("".into()),
278 vendor_id: Unchanged(None),
279 };
280
281 assert_eq!(filling.into_active_model(), filling_am);
282
283 let filling_ex = filling::ActiveModelEx {
284 id: Unchanged(12),
285 name: Unchanged("".into()),
286 vendor_id: Unchanged(None),
287 ingredients: HasManyModel::NotSet,
288 };
289
290 assert_eq!(filling_am.into_ex(), filling_ex);
291
292 let cake_ex = cake::ModelEx {
293 id: 12,
294 name: "C".into(),
295 fruit: HasOne::loaded(fruit::Model {
296 id: 13,
297 name: "F".into(),
298 cake_id: Some(12),
299 }),
300 fillings: HasMany::Loaded(vec![
301 filling::Model {
302 id: 14,
303 name: "FF".into(),
304 vendor_id: None,
305 ignored_attr: 1,
306 }
307 .into(),
308 ]),
309 };
310
311 let cake_am = cake::ActiveModelEx {
312 id: Unchanged(12),
313 name: Unchanged("C".into()),
314 fruit: HasOneModel::Set(
315 fruit::ActiveModelEx {
316 id: Unchanged(13),
317 name: Unchanged("F".into()),
318 cake_id: Unchanged(Some(12)),
319 }
320 .into(),
321 ),
322 fillings: HasManyModel::Append(vec![filling::ActiveModelEx {
323 id: Unchanged(14),
324 name: Unchanged("FF".into()),
325 vendor_id: Unchanged(None),
326 ingredients: HasManyModel::NotSet,
327 }]),
328 };
329
330 assert_eq!(cake_ex.into_active_model(), cake_am);
331 }
332}