Skip to main content

toasty_driver_integration_suite/tests/
relation_eager.rs

1use crate::prelude::*;
2
3use toasty::schema::Model;
4use toasty_core::stmt;
5
6#[driver_test]
7pub async fn eager_has_many_and_has_one_load_without_include(t: &mut Test) -> Result<()> {
8    #[derive(Debug, toasty::Model)]
9    struct User {
10        #[key]
11        id: uuid::Uuid,
12        name: String,
13
14        #[has_many]
15        posts: Vec<Post>,
16
17        #[has_one]
18        profile: Option<Profile>,
19    }
20
21    #[derive(Debug, toasty::Model)]
22    struct Post {
23        #[key]
24        #[auto]
25        id: uuid::Uuid,
26        title: String,
27
28        #[index]
29        user_id: uuid::Uuid,
30
31        #[belongs_to(key = user_id, references = id)]
32        user: toasty::Deferred<User>,
33    }
34
35    #[derive(Debug, toasty::Model)]
36    struct Profile {
37        #[key]
38        id: uuid::Uuid,
39        bio: String,
40
41        #[unique]
42        user_id: uuid::Uuid,
43
44        #[belongs_to(key = user_id, references = id)]
45        user: toasty::Deferred<Option<User>>,
46    }
47
48    let mut db = t.setup_db(models!(User, Post, Profile)).await;
49    let user_id = uuid::Uuid::from_u128(1);
50
51    insert_row::<User>(
52        &mut db,
53        vec![
54            stmt::Value::Uuid(user_id).into(),
55            stmt::Value::from("Alice").into(),
56            stmt::Value::Null.into(),
57            stmt::Value::Null.into(),
58        ],
59    )
60    .await?;
61    insert_row::<Post>(
62        &mut db,
63        vec![
64            stmt::Value::Uuid(uuid::Uuid::from_u128(2)).into(),
65            stmt::Value::from("hello").into(),
66            stmt::Value::Uuid(user_id).into(),
67            stmt::Value::Null.into(),
68        ],
69    )
70    .await?;
71    insert_row::<Profile>(
72        &mut db,
73        vec![
74            stmt::Value::Uuid(uuid::Uuid::from_u128(3)).into(),
75            stmt::Value::from("writer").into(),
76            stmt::Value::Uuid(user_id).into(),
77            stmt::Value::Null.into(),
78        ],
79    )
80    .await?;
81
82    let user = User::filter_by_id(user_id).get(&mut db).await?;
83
84    assert_eq!(user.posts.len(), 1);
85    assert_eq!(user.posts[0].title, "hello");
86    assert_eq!(user.profile.as_ref().unwrap().bio, "writer");
87
88    Ok(())
89}
90
91#[driver_test]
92pub async fn eager_belongs_to_loads_without_include(t: &mut Test) -> Result<()> {
93    #[derive(Debug, toasty::Model)]
94    struct User {
95        #[key]
96        id: uuid::Uuid,
97        name: String,
98    }
99
100    #[derive(Debug, toasty::Model)]
101    struct Post {
102        #[key]
103        #[auto]
104        id: uuid::Uuid,
105        title: String,
106
107        #[index]
108        user_id: uuid::Uuid,
109
110        #[belongs_to(key = user_id, references = id)]
111        user: User,
112    }
113
114    let mut db = t.setup_db(models!(User, Post)).await;
115    let user_id = uuid::Uuid::from_u128(4);
116    let post_id = uuid::Uuid::from_u128(5);
117
118    insert_row::<User>(
119        &mut db,
120        vec![
121            stmt::Value::Uuid(user_id).into(),
122            stmt::Value::from("Alice").into(),
123        ],
124    )
125    .await?;
126    insert_row::<Post>(
127        &mut db,
128        vec![
129            stmt::Value::Uuid(post_id).into(),
130            stmt::Value::from("hello").into(),
131            stmt::Value::Uuid(user_id).into(),
132            stmt::Value::Null.into(),
133        ],
134    )
135    .await?;
136
137    let post = Post::filter_by_id(post_id).get(&mut db).await?;
138
139    assert_eq!(post.title, "hello");
140    assert_eq!(post.user.name, "Alice");
141
142    Ok(())
143}
144
145#[driver_test(id(ID))]
146pub async fn eager_has_many_create_returning_loads_relations(t: &mut Test) -> Result<()> {
147    #[derive(Debug, toasty::Model)]
148    struct User {
149        #[key]
150        #[auto]
151        id: ID,
152        name: String,
153
154        #[has_many]
155        posts: Vec<Post>,
156    }
157
158    #[derive(Debug, toasty::Model)]
159    struct Post {
160        #[key]
161        #[auto]
162        id: ID,
163        title: String,
164
165        #[index]
166        user_id: ID,
167
168        #[belongs_to(key = user_id, references = id)]
169        user: toasty::Deferred<User>,
170    }
171
172    let mut db = t.setup_db(models!(User, Post)).await;
173
174    let user = User::create()
175        .name("Alice")
176        .posts([Post::create().title("hello")])
177        .exec(&mut db)
178        .await?;
179
180    assert_eq!(user.name, "Alice");
181    assert_eq!(user.posts.len(), 1);
182    assert_eq!(user.posts[0].title, "hello");
183
184    Ok(())
185}
186
187#[driver_test]
188pub async fn eager_nested_relations_load_without_include(t: &mut Test) -> Result<()> {
189    #[derive(Debug, toasty::Model)]
190    struct User {
191        #[key]
192        id: uuid::Uuid,
193        name: String,
194
195        #[has_many]
196        posts: Vec<Post>,
197    }
198
199    #[derive(Debug, toasty::Model)]
200    struct Post {
201        #[key]
202        id: uuid::Uuid,
203        title: String,
204
205        #[index]
206        user_id: uuid::Uuid,
207
208        #[belongs_to(key = user_id, references = id)]
209        user: toasty::Deferred<User>,
210
211        #[has_many]
212        comments: Vec<Comment>,
213    }
214
215    #[derive(Debug, toasty::Model)]
216    struct Comment {
217        #[key]
218        id: uuid::Uuid,
219        body: String,
220
221        #[index]
222        post_id: uuid::Uuid,
223
224        #[belongs_to(key = post_id, references = id)]
225        post: toasty::Deferred<Post>,
226    }
227
228    let mut db = t.setup_db(models!(User, Post, Comment)).await;
229    let user_id = uuid::Uuid::from_u128(10);
230    let post_id = uuid::Uuid::from_u128(11);
231
232    insert_row::<User>(
233        &mut db,
234        vec![
235            stmt::Value::Uuid(user_id).into(),
236            stmt::Value::from("Alice").into(),
237            stmt::Value::Null.into(),
238        ],
239    )
240    .await?;
241    insert_row::<Post>(
242        &mut db,
243        vec![
244            stmt::Value::Uuid(post_id).into(),
245            stmt::Value::from("hello").into(),
246            stmt::Value::Uuid(user_id).into(),
247            stmt::Value::Null.into(),
248            stmt::Value::Null.into(),
249        ],
250    )
251    .await?;
252    insert_row::<Comment>(
253        &mut db,
254        vec![
255            stmt::Value::Uuid(uuid::Uuid::from_u128(12)).into(),
256            stmt::Value::from("first").into(),
257            stmt::Value::Uuid(post_id).into(),
258            stmt::Value::Null.into(),
259        ],
260    )
261    .await?;
262
263    let user = User::filter_by_id(user_id).get(&mut db).await?;
264
265    assert_eq!(user.posts.len(), 1);
266    assert_eq!(user.posts[0].title, "hello");
267    assert_eq!(user.posts[0].comments.len(), 1);
268    assert_eq!(user.posts[0].comments[0].body, "first");
269
270    Ok(())
271}
272
273#[driver_test]
274pub async fn eager_relations_reload_after_update(t: &mut Test) -> Result<()> {
275    #[derive(Debug, toasty::Model)]
276    struct User {
277        #[key]
278        id: uuid::Uuid,
279        name: String,
280
281        #[has_many]
282        posts: Vec<Post>,
283    }
284
285    #[derive(Debug, toasty::Model)]
286    struct Post {
287        #[key]
288        #[auto]
289        id: uuid::Uuid,
290        title: String,
291
292        #[index]
293        user_id: uuid::Uuid,
294
295        #[belongs_to(key = user_id, references = id)]
296        user: toasty::Deferred<User>,
297    }
298
299    let mut db = t.setup_db(models!(User, Post)).await;
300    let user_id = uuid::Uuid::from_u128(20);
301
302    insert_row::<User>(
303        &mut db,
304        vec![
305            stmt::Value::Uuid(user_id).into(),
306            stmt::Value::from("Alice").into(),
307            stmt::Value::Null.into(),
308        ],
309    )
310    .await?;
311
312    let mut user = User::filter_by_id(user_id).get(&mut db).await?;
313    assert!(user.posts.is_empty());
314
315    user.update()
316        .name("Alice Updated")
317        .posts(toasty::stmt::insert(Post::create().title("first")))
318        .exec(&mut db)
319        .await?;
320
321    let mut titles = user
322        .posts
323        .iter()
324        .map(|post| post.title.as_str())
325        .collect::<Vec<_>>();
326    titles.sort_unstable();
327
328    assert_eq!(user.name, "Alice Updated");
329    assert_eq!(titles, vec!["first"]);
330
331    Ok(())
332}
333
334#[driver_test(id(ID))]
335pub async fn eager_relation_cycle_is_rejected(t: &mut Test) -> Result<()> {
336    #[derive(Debug, toasty::Model)]
337    struct User {
338        #[key]
339        #[auto]
340        id: ID,
341
342        #[has_many]
343        posts: Vec<Post>,
344    }
345
346    #[derive(Debug, toasty::Model)]
347    struct Post {
348        #[key]
349        #[auto]
350        id: ID,
351
352        #[index]
353        user_id: ID,
354
355        #[belongs_to(key = user_id, references = id)]
356        user: User,
357    }
358
359    let err = t.try_setup_db(models!(User, Post)).await.unwrap_err();
360    let msg = err.to_string();
361
362    assert!(
363        msg.contains("eager relation cycle"),
364        "expected eager relation cycle error, got: {msg}"
365    );
366
367    Ok(())
368}
369
370#[driver_test(id(ID))]
371pub async fn eager_relation_self_cycle_is_rejected(t: &mut Test) -> Result<()> {
372    #[derive(Debug, toasty::Model)]
373    struct Node {
374        #[key]
375        #[auto]
376        id: ID,
377
378        #[index]
379        parent_id: Option<ID>,
380
381        #[belongs_to(key = parent_id, references = id)]
382        parent: toasty::Deferred<Option<Node>>,
383
384        #[has_many(pair = parent)]
385        children: Vec<Node>,
386    }
387
388    let err = t.try_setup_db(models!(Node)).await.unwrap_err();
389    let msg = err.to_string();
390
391    assert!(
392        msg.contains("eager relation cycle"),
393        "expected eager relation cycle error, got: {msg}"
394    );
395
396    Ok(())
397}
398
399#[driver_test(id(ID))]
400pub async fn eager_relation_long_cycle_is_rejected(t: &mut Test) -> Result<()> {
401    #[derive(Debug, toasty::Model)]
402    struct User {
403        #[key]
404        #[auto]
405        id: ID,
406
407        #[has_many]
408        posts: Vec<Post>,
409    }
410
411    #[derive(Debug, toasty::Model)]
412    struct Post {
413        #[key]
414        #[auto]
415        id: ID,
416
417        #[index]
418        user_id: ID,
419
420        #[belongs_to(key = user_id, references = id)]
421        user: toasty::Deferred<User>,
422
423        #[has_one]
424        detail: Option<Detail>,
425    }
426
427    #[derive(Debug, toasty::Model)]
428    struct Detail {
429        #[key]
430        #[auto]
431        id: ID,
432
433        #[unique]
434        post_id: ID,
435
436        #[belongs_to(key = post_id, references = id)]
437        post: toasty::Deferred<Post>,
438
439        #[index]
440        user_id: ID,
441
442        #[belongs_to(key = user_id, references = id)]
443        user: User,
444    }
445
446    let err = t
447        .try_setup_db(models!(User, Post, Detail))
448        .await
449        .unwrap_err();
450    let msg = err.to_string();
451
452    assert!(
453        msg.contains("eager relation cycle"),
454        "expected eager relation cycle error, got: {msg}"
455    );
456
457    Ok(())
458}
459
460async fn insert_row<M: Model>(db: &mut toasty::Db, fields: Vec<stmt::Expr>) -> Result<()> {
461    let insert = stmt::Insert {
462        target: stmt::InsertTarget::Model(<M as toasty::schema::Model>::id()),
463        source: stmt::Query::new_single(vec![stmt::Expr::record(fields)]),
464        returning: None,
465    };
466
467    toasty::Statement::<()>::from_untyped_stmt(insert.into())
468        .exec(db)
469        .await
470}