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>> {
193 SelectorRaw::<SelectModel<Self>>::from_statement(stmt)
194 }
195}
196
197impl<T: FromQueryResult> FromQueryResult for Option<T> {
198 fn from_query_result(res: &QueryResult, pre: &str) -> Result<Self, DbErr> {
199 Ok(Self::from_query_result_nullable(res, pre)?)
200 }
201
202 fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result<Option<Self>, DbErr> {
203 match Self::from_query_result_nullable(res, pre) {
204 Ok(v) => Ok(Some(v)),
205 Err(TryGetError::Null(_)) => Ok(None),
206 Err(TryGetError::DbErr(err)) => Err(err),
207 }
208 }
209
210 fn from_query_result_nullable(res: &QueryResult, pre: &str) -> Result<Self, TryGetError> {
211 match T::from_query_result_nullable(res, pre) {
212 Ok(v) => Ok(Some(v)),
213 Err(TryGetError::Null(_)) => Ok(None),
214 Err(err @ TryGetError::DbErr(_)) => Err(err),
215 }
216 }
217}
218
219pub trait TryIntoModel<M>
221where
222 M: ModelTrait,
223{
224 fn try_into_model(self) -> Result<M, DbErr>;
226}
227
228impl<M> TryIntoModel<M> for M
229where
230 M: ModelTrait,
231{
232 fn try_into_model(self) -> Result<M, DbErr> {
233 Ok(self)
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use crate::{
240 IntoActiveModel, Unchanged,
241 prelude::*,
242 tests_cfg::{cake, filling, fruit, post},
243 };
244
245 #[test]
246 fn test_model() {
247 fn filter_by_column(col: post::Column) -> Expr {
248 col.eq("attribute")
249 }
250
251 fn get_value_from(model: &post::Model, col: post::Column) {
252 let value: i32 = model.get(col).unwrap();
253 assert_eq!(value, 12);
254 }
255
256 let model = post::Model {
257 id: 12,
258 user_id: 14,
259 title: "hello".into(),
260 };
261
262 get_value_from(&model, post::COLUMN.id.0);
263 filter_by_column(post::COLUMN.title.0);
264
265 let filling = filling::Model {
266 id: 12,
267 name: "".into(),
268 vendor_id: None,
269 ignored_attr: 24,
270 };
271
272 let filling_am = filling::ActiveModel {
273 id: Unchanged(12),
274 name: Unchanged("".into()),
275 vendor_id: Unchanged(None),
276 };
277
278 assert_eq!(filling.into_active_model(), filling_am);
279
280 let filling_ex = filling::ActiveModelEx {
281 id: Unchanged(12),
282 name: Unchanged("".into()),
283 vendor_id: Unchanged(None),
284 ingredients: HasManyModel::NotSet,
285 };
286
287 assert_eq!(filling_am.into_ex(), filling_ex);
288
289 let cake_ex = cake::ModelEx {
290 id: 12,
291 name: "C".into(),
292 fruit: HasOne::loaded(fruit::Model {
293 id: 13,
294 name: "F".into(),
295 cake_id: Some(12),
296 }),
297 fillings: HasMany::Loaded(vec![
298 filling::Model {
299 id: 14,
300 name: "FF".into(),
301 vendor_id: None,
302 ignored_attr: 1,
303 }
304 .into(),
305 ]),
306 };
307
308 let cake_am = cake::ActiveModelEx {
309 id: Unchanged(12),
310 name: Unchanged("C".into()),
311 fruit: HasOneModel::Set(
312 fruit::ActiveModelEx {
313 id: Unchanged(13),
314 name: Unchanged("F".into()),
315 cake_id: Unchanged(Some(12)),
316 }
317 .into(),
318 ),
319 fillings: HasManyModel::Append(vec![filling::ActiveModelEx {
320 id: Unchanged(14),
321 name: Unchanged("FF".into()),
322 vendor_id: Unchanged(None),
323 ingredients: HasManyModel::NotSet,
324 }]),
325 };
326
327 assert_eq!(cake_ex.into_active_model(), cake_am);
328 }
329}