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