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]
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: uuid::Uuid,
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: uuid::Uuid,
163        title: String,
164
165        #[index]
166        user_id: uuid::Uuid,
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_belongs_to_create_returning_loads_relation(t: &mut Test) -> Result<()> {
189    #[derive(Debug, toasty::Model)]
190    struct User {
191        #[key]
192        #[auto]
193        id: uuid::Uuid,
194        name: String,
195    }
196
197    #[derive(Debug, toasty::Model)]
198    struct Post {
199        #[key]
200        #[auto]
201        id: uuid::Uuid,
202        title: String,
203
204        #[index]
205        user_id: uuid::Uuid,
206
207        #[belongs_to(key = user_id, references = id)]
208        user: User,
209    }
210
211    let mut db = t.setup_db(models!(User, Post)).await;
212
213    let user = toasty::create!(User { name: "Alice" })
214        .exec(&mut db)
215        .await?;
216    let post = toasty::create!(Post {
217        title: "hello",
218        user: &user
219    })
220    .exec(&mut db)
221    .await?;
222
223    assert_eq!(post.title, "hello");
224    assert_eq!(post.user_id, user.id);
225    assert_eq!(post.user.id, user.id);
226    assert_eq!(post.user.name, "Alice");
227
228    Ok(())
229}
230
231#[driver_test]
232pub async fn eager_belongs_to_batch_create_returning_loads_relation(t: &mut Test) -> Result<()> {
233    #[derive(Debug, toasty::Model)]
234    struct User {
235        #[key]
236        #[auto]
237        id: uuid::Uuid,
238        name: String,
239    }
240
241    #[derive(Debug, toasty::Model)]
242    struct Post {
243        #[key]
244        #[auto]
245        id: uuid::Uuid,
246        title: String,
247
248        #[index]
249        user_id: uuid::Uuid,
250
251        #[belongs_to(key = user_id, references = id)]
252        user: User,
253    }
254
255    let mut db = t.setup_db(models!(User, Post)).await;
256
257    let alice = toasty::create!(User { name: "Alice" })
258        .exec(&mut db)
259        .await?;
260    let bob = toasty::create!(User { name: "Bob" }).exec(&mut db).await?;
261
262    let posts = toasty::create!(Post::[
263        { title: "a", user: &alice },
264        { title: "b", user: &bob },
265    ])
266    .exec(&mut db)
267    .await?;
268
269    assert_eq!(posts.len(), 2);
270    assert_eq!(posts[0].user.name, "Alice");
271    assert_eq!(posts[1].user.name, "Bob");
272
273    Ok(())
274}
275
276#[driver_test(requires(upsert_targeted_ignore))]
277pub async fn eager_belongs_to_upsert_or_ignore_returning_loads_relation(
278    t: &mut Test,
279) -> Result<()> {
280    #[derive(Debug, toasty::Model)]
281    struct User {
282        #[key]
283        #[auto]
284        id: uuid::Uuid,
285        name: String,
286    }
287
288    #[derive(Debug, toasty::Model)]
289    struct Post {
290        #[key]
291        id: uuid::Uuid,
292        title: String,
293
294        #[index]
295        user_id: uuid::Uuid,
296
297        #[belongs_to(key = user_id, references = id)]
298        user: User,
299    }
300
301    let mut db = t.setup_db(models!(User, Post)).await;
302
303    let user = toasty::create!(User { name: "Alice" })
304        .exec(&mut db)
305        .await?;
306
307    let post_id = uuid::Uuid::from_u128(7);
308    let created = Post::upsert_by_id(post_id)
309        .title("hello")
310        .user_id(user.id)
311        .or_ignore()
312        .exec(&mut db)
313        .await?;
314
315    let created = created.expect("insert succeeded");
316    assert_eq!(created.title, "hello");
317    assert_struct!(created.user, _ { id: == user.id, name: "Alice", .. });
318
319    // On conflict the insert is ignored and the upsert returns `None`.
320    let ignored = Post::upsert_by_id(post_id)
321        .title("other")
322        .user_id(user.id)
323        .or_ignore()
324        .exec(&mut db)
325        .await?;
326    assert_none!(ignored);
327
328    Ok(())
329}
330
331#[driver_test(requires(upsert_targeted_ignore))]
332pub async fn eager_belongs_to_upsert_or_ignore_conflict_issues_no_load(t: &mut Test) -> Result<()> {
333    use toasty_core::driver::Operation;
334
335    #[derive(Debug, toasty::Model)]
336    struct User {
337        #[key]
338        #[auto]
339        id: uuid::Uuid,
340        name: String,
341    }
342
343    #[derive(Debug, toasty::Model)]
344    struct Post {
345        #[key]
346        id: uuid::Uuid,
347        title: String,
348
349        #[index]
350        user_id: uuid::Uuid,
351
352        #[belongs_to(key = user_id, references = id)]
353        user: User,
354    }
355
356    let mut db = t.setup_db(models!(User, Post)).await;
357
358    let user = toasty::create!(User { name: "Alice" })
359        .exec(&mut db)
360        .await?;
361
362    let post_id = uuid::Uuid::from_u128(7);
363    Post::upsert_by_id(post_id)
364        .title("hello")
365        .user_id(user.id)
366        .or_ignore()
367        .exec(&mut db)
368        .await?
369        .expect("insert succeeded");
370
371    // On conflict the upsert's RETURNING is empty, so the relation-load read
372    // is skipped: its block is guarded on the upsert producing a row.
373    t.log().clear();
374    let ignored = Post::upsert_by_id(post_id)
375        .title("hello")
376        .user_id(user.id)
377        .or_ignore()
378        .exec(&mut db)
379        .await?;
380    assert_none!(ignored);
381
382    while !t.log().is_empty() {
383        let op = t.log().pop_op();
384        let is_read = match &op {
385            Operation::QuerySql(op) => op.stmt.is_query(),
386            Operation::GetByKey(_)
387            | Operation::QueryPk(_)
388            | Operation::FindPkByIndex(_)
389            | Operation::Scan(_) => true,
390            _ => false,
391        };
392        assert!(!is_read, "conflict path issued a read: {op:#?}");
393    }
394
395    Ok(())
396}
397
398#[driver_test]
399pub async fn eager_belongs_to_nested_create_returning_loads_relation(t: &mut Test) -> Result<()> {
400    #[derive(Debug, toasty::Model)]
401    struct User {
402        #[key]
403        #[auto]
404        id: uuid::Uuid,
405        name: String,
406
407        #[has_many]
408        posts: toasty::Deferred<Vec<Post>>,
409    }
410
411    #[derive(Debug, toasty::Model)]
412    struct Post {
413        #[key]
414        #[auto]
415        id: uuid::Uuid,
416        title: String,
417
418        #[index]
419        user_id: uuid::Uuid,
420
421        #[belongs_to(key = user_id, references = id)]
422        user: User,
423    }
424
425    let mut db = t.setup_db(models!(User, Post)).await;
426
427    // The nested post insert's RETURNING includes its eager `user`, whose FK
428    // value comes from the parent user insert.
429    let user = User::create()
430        .name("Alice")
431        .posts([Post::create().title("hello")])
432        .exec(&mut db)
433        .await?;
434
435    let posts: Vec<_> = user.posts().exec(&mut db).await?;
436    assert_eq!(posts.len(), 1);
437    assert_eq!(posts[0].user.name, "Alice");
438
439    Ok(())
440}
441
442#[driver_test]
443pub async fn eager_nested_relations_load_without_include(t: &mut Test) -> Result<()> {
444    #[derive(Debug, toasty::Model)]
445    struct User {
446        #[key]
447        id: uuid::Uuid,
448        name: String,
449
450        #[has_many]
451        posts: Vec<Post>,
452    }
453
454    #[derive(Debug, toasty::Model)]
455    struct Post {
456        #[key]
457        id: uuid::Uuid,
458        title: String,
459
460        #[index]
461        user_id: uuid::Uuid,
462
463        #[belongs_to(key = user_id, references = id)]
464        user: toasty::Deferred<User>,
465
466        #[has_many]
467        comments: Vec<Comment>,
468    }
469
470    #[derive(Debug, toasty::Model)]
471    struct Comment {
472        #[key]
473        id: uuid::Uuid,
474        body: String,
475
476        #[index]
477        post_id: uuid::Uuid,
478
479        #[belongs_to(key = post_id, references = id)]
480        post: toasty::Deferred<Post>,
481    }
482
483    let mut db = t.setup_db(models!(User, Post, Comment)).await;
484    let user_id = uuid::Uuid::from_u128(10);
485    let post_id = uuid::Uuid::from_u128(11);
486
487    insert_row::<User>(
488        &mut db,
489        vec![
490            stmt::Value::Uuid(user_id).into(),
491            stmt::Value::from("Alice").into(),
492            stmt::Value::Null.into(),
493        ],
494    )
495    .await?;
496    insert_row::<Post>(
497        &mut db,
498        vec![
499            stmt::Value::Uuid(post_id).into(),
500            stmt::Value::from("hello").into(),
501            stmt::Value::Uuid(user_id).into(),
502            stmt::Value::Null.into(),
503            stmt::Value::Null.into(),
504        ],
505    )
506    .await?;
507    insert_row::<Comment>(
508        &mut db,
509        vec![
510            stmt::Value::Uuid(uuid::Uuid::from_u128(12)).into(),
511            stmt::Value::from("first").into(),
512            stmt::Value::Uuid(post_id).into(),
513            stmt::Value::Null.into(),
514        ],
515    )
516    .await?;
517
518    let user = User::filter_by_id(user_id).get(&mut db).await?;
519
520    assert_eq!(user.posts.len(), 1);
521    assert_eq!(user.posts[0].title, "hello");
522    assert_eq!(user.posts[0].comments.len(), 1);
523    assert_eq!(user.posts[0].comments[0].body, "first");
524
525    Ok(())
526}
527
528#[driver_test]
529pub async fn eager_relations_reload_after_update(t: &mut Test) -> Result<()> {
530    #[derive(Debug, toasty::Model)]
531    struct User {
532        #[key]
533        id: uuid::Uuid,
534        name: String,
535
536        #[has_many]
537        posts: Vec<Post>,
538    }
539
540    #[derive(Debug, toasty::Model)]
541    struct Post {
542        #[key]
543        #[auto]
544        id: uuid::Uuid,
545        title: String,
546
547        #[index]
548        user_id: uuid::Uuid,
549
550        #[belongs_to(key = user_id, references = id)]
551        user: toasty::Deferred<User>,
552    }
553
554    let mut db = t.setup_db(models!(User, Post)).await;
555    let user_id = uuid::Uuid::from_u128(20);
556
557    insert_row::<User>(
558        &mut db,
559        vec![
560            stmt::Value::Uuid(user_id).into(),
561            stmt::Value::from("Alice").into(),
562            stmt::Value::Null.into(),
563        ],
564    )
565    .await?;
566
567    let mut user = User::filter_by_id(user_id).get(&mut db).await?;
568    assert!(user.posts.is_empty());
569
570    user.update()
571        .name("Alice Updated")
572        .posts(toasty::stmt::insert(Post::create().title("first")))
573        .exec(&mut db)
574        .await?;
575
576    let mut titles = user
577        .posts
578        .iter()
579        .map(|post| post.title.as_str())
580        .collect::<Vec<_>>();
581    titles.sort_unstable();
582
583    assert_eq!(user.name, "Alice Updated");
584    assert_eq!(titles, vec!["first"]);
585
586    Ok(())
587}
588
589#[driver_test]
590pub async fn eager_relation_cycle_is_rejected(t: &mut Test) -> Result<()> {
591    #[derive(Debug, toasty::Model)]
592    struct User {
593        #[key]
594        #[auto]
595        id: uuid::Uuid,
596
597        #[has_many]
598        posts: Vec<Post>,
599    }
600
601    #[derive(Debug, toasty::Model)]
602    struct Post {
603        #[key]
604        #[auto]
605        id: uuid::Uuid,
606
607        #[index]
608        user_id: uuid::Uuid,
609
610        #[belongs_to(key = user_id, references = id)]
611        user: User,
612    }
613
614    let err = t.try_setup_db(models!(User, Post)).await.unwrap_err();
615    let msg = err.to_string();
616
617    assert!(
618        msg.contains("eager relation cycle"),
619        "expected eager relation cycle error, got: {msg}"
620    );
621
622    Ok(())
623}
624
625#[driver_test]
626pub async fn eager_relation_self_cycle_is_rejected(t: &mut Test) -> Result<()> {
627    #[derive(Debug, toasty::Model)]
628    struct Node {
629        #[key]
630        #[auto]
631        id: uuid::Uuid,
632
633        #[index]
634        parent_id: Option<uuid::Uuid>,
635
636        #[belongs_to(key = parent_id, references = id)]
637        parent: toasty::Deferred<Option<Node>>,
638
639        #[has_many(pair = parent)]
640        children: Vec<Node>,
641    }
642
643    let err = t.try_setup_db(models!(Node)).await.unwrap_err();
644    let msg = err.to_string();
645
646    assert!(
647        msg.contains("eager relation cycle"),
648        "expected eager relation cycle error, got: {msg}"
649    );
650
651    Ok(())
652}
653
654#[driver_test]
655pub async fn eager_relation_long_cycle_is_rejected(t: &mut Test) -> Result<()> {
656    #[derive(Debug, toasty::Model)]
657    struct User {
658        #[key]
659        #[auto]
660        id: uuid::Uuid,
661
662        #[has_many]
663        posts: Vec<Post>,
664    }
665
666    #[derive(Debug, toasty::Model)]
667    struct Post {
668        #[key]
669        #[auto]
670        id: uuid::Uuid,
671
672        #[index]
673        user_id: uuid::Uuid,
674
675        #[belongs_to(key = user_id, references = id)]
676        user: toasty::Deferred<User>,
677
678        #[has_one]
679        detail: Option<Detail>,
680    }
681
682    #[derive(Debug, toasty::Model)]
683    struct Detail {
684        #[key]
685        #[auto]
686        id: uuid::Uuid,
687
688        #[unique]
689        post_id: uuid::Uuid,
690
691        #[belongs_to(key = post_id, references = id)]
692        post: toasty::Deferred<Post>,
693
694        #[index]
695        user_id: uuid::Uuid,
696
697        #[belongs_to(key = user_id, references = id)]
698        user: User,
699    }
700
701    let err = t
702        .try_setup_db(models!(User, Post, Detail))
703        .await
704        .unwrap_err();
705    let msg = err.to_string();
706
707    assert!(
708        msg.contains("eager relation cycle"),
709        "expected eager relation cycle error, got: {msg}"
710    );
711
712    Ok(())
713}
714
715async fn insert_row<M: Model>(db: &mut toasty::Db, fields: Vec<stmt::Expr>) -> Result<()> {
716    let insert = stmt::Insert {
717        target: stmt::InsertTarget::Model(<M as toasty::schema::Model>::id()),
718        source: stmt::Query::new_single(vec![stmt::Expr::record(fields)]),
719        upsert: None,
720        returning: None,
721    };
722
723    toasty::Statement::<()>::from_untyped_stmt(insert.into())
724        .exec(db)
725        .await
726}