Skip to main content

toasty_driver_integration_suite/tests/
relation_preload.rs

1use crate::prelude::*;
2
3/// Tests that preloading a `Deferred<Option<_>>` correctly distinguishes between
4/// "not loaded" and "loaded as None" when the relation does not exist.
5#[driver_test(id(ID), scenario(crate::scenarios::has_one_optional_belongs_to))]
6pub async fn preload_has_one_option_none_then_some(test: &mut Test) -> Result<()> {
7    let mut db = setup(test).await;
8
9    // Create a user WITHOUT a profile
10    let user_no_profile = User::create().name("No Profile").exec(&mut db).await?;
11
12    // Preload the profile — no profile exists, so it should be `None` (loaded)
13    let user_no_profile = User::filter_by_id(user_no_profile.id)
14        .include(User::fields().profile())
15        .get(&mut db)
16        .await?;
17
18    // `.get()` must not panic — the relation was preloaded and is None
19    assert!(user_no_profile.profile.get().is_none());
20
21    // Create a user WITH a profile
22    let user_with_profile = User::create()
23        .name("Has Profile")
24        .profile(Profile::create().bio("A bio"))
25        .exec(&mut db)
26        .await?;
27
28    // Preload the profile — a profile exists, so it should be `Some`
29    let user_with_profile = User::filter_by_id(user_with_profile.id)
30        .include(User::fields().profile())
31        .get(&mut db)
32        .await?;
33
34    let profile = user_with_profile.profile.get().as_ref().unwrap();
35    assert_eq!("A bio", profile.bio);
36    assert_eq!(user_with_profile.id, *profile.user_id.as_ref().unwrap());
37
38    Ok(())
39}
40
41#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
42pub async fn basic_has_many_and_belongs_to_preload(test: &mut Test) -> Result<()> {
43    let mut db = setup(test).await;
44
45    // Create a user with a few todos
46    let user = User::create()
47        .name("Alice")
48        .todos([Todo::create().title("todo 1")])
49        .todos([Todo::create().title("todo 2")])
50        .todos([Todo::create().title("todo 3")])
51        .exec(&mut db)
52        .await?;
53
54    // Find the user, include TODOs
55    let user = User::filter_by_id(user.id)
56        .include(User::fields().todos())
57        .get(&mut db)
58        .await?;
59
60    // This will panic
61    assert_eq!(3, user.todos.get().len());
62
63    let id = user.todos.get()[0].id;
64
65    let todo = Todo::filter_by_id(id)
66        .include(Todo::fields().user())
67        .get(&mut db)
68        .await?;
69
70    assert_eq!(user.id, todo.user.get().id);
71    assert_eq!(user.id, todo.user_id);
72    Ok(())
73}
74
75#[driver_test(id(ID), scenario(crate::scenarios::user_two_children))]
76pub async fn multiple_includes_same_model(test: &mut Test) -> Result<()> {
77    let mut db = setup(test).await;
78
79    // Create a user
80    let user = User::create().name("Test User").exec(&mut db).await?;
81
82    // Create posts associated with the user
83    Post::create()
84        .title("Post 1")
85        .user(&user)
86        .exec(&mut db)
87        .await?;
88
89    Post::create()
90        .title("Post 2")
91        .user(&user)
92        .exec(&mut db)
93        .await?;
94
95    // Create comments associated with the user
96    Comment::create()
97        .text("Comment 1")
98        .user(&user)
99        .exec(&mut db)
100        .await?;
101
102    Comment::create()
103        .text("Comment 2")
104        .user(&user)
105        .exec(&mut db)
106        .await?;
107
108    Comment::create()
109        .text("Comment 3")
110        .user(&user)
111        .exec(&mut db)
112        .await?;
113
114    // Test individual includes work (baseline)
115    let user_with_posts = User::filter_by_id(user.id)
116        .include(User::fields().posts())
117        .get(&mut db)
118        .await?;
119    assert_eq!(2, user_with_posts.posts.get().len());
120
121    let user_with_comments = User::filter_by_id(user.id)
122        .include(User::fields().comments())
123        .get(&mut db)
124        .await?;
125    assert_eq!(3, user_with_comments.comments.get().len());
126
127    // Test multiple includes in one query
128    let loaded_user = User::filter_by_id(user.id)
129        .include(User::fields().posts()) // First include
130        .include(User::fields().comments()) // Second include
131        .get(&mut db)
132        .await?;
133
134    assert_eq!(2, loaded_user.posts.get().len());
135    assert_eq!(3, loaded_user.comments.get().len());
136    Ok(())
137}
138
139#[driver_test(id(ID), scenario(crate::scenarios::has_one_optional_belongs_to))]
140pub async fn basic_has_one_and_belongs_to_preload(test: &mut Test) -> Result<()> {
141    let mut db = setup(test).await;
142
143    // Create a user with a profile
144    let user = User::create()
145        .name("John Doe")
146        .profile(Profile::create().bio("A person"))
147        .exec(&mut db)
148        .await?;
149
150    // Find the user, include profile
151    let user = User::filter_by_id(user.id)
152        .include(User::fields().profile())
153        .get(&mut db)
154        .await?;
155
156    // Verify the profile is preloaded
157    let profile = user.profile.get().as_ref().unwrap();
158    assert_eq!("A person", profile.bio);
159    assert_eq!(user.id, *profile.user_id.as_ref().unwrap());
160
161    let profile_id = profile.id;
162
163    // Test the reciprocal belongs_to preload
164    let profile = Profile::filter_by_id(profile_id)
165        .include(Profile::fields().user())
166        .get(&mut db)
167        .await?;
168
169    assert_eq!(user.id, profile.user.get().as_ref().unwrap().id);
170    assert_eq!("John Doe", profile.user.get().as_ref().unwrap().name);
171    Ok(())
172}
173
174#[driver_test(id(ID), scenario(crate::scenarios::user_profile_settings))]
175pub async fn multiple_includes_with_has_one(test: &mut Test) -> Result<()> {
176    let mut db = setup(test).await;
177
178    // Create a user with both profile and settings
179    let user = User::create()
180        .name("Jane Doe")
181        .profile(Profile::create().bio("Software engineer"))
182        .settings(Settings::create().theme("dark"))
183        .exec(&mut db)
184        .await?;
185
186    // Test individual includes work (baseline)
187    let user_with_profile = User::filter_by_id(user.id)
188        .include(User::fields().profile())
189        .get(&mut db)
190        .await?;
191    assert!(user_with_profile.profile.get().is_some());
192    assert_eq!(
193        "Software engineer",
194        user_with_profile.profile.get().as_ref().unwrap().bio
195    );
196
197    let user_with_settings = User::filter_by_id(user.id)
198        .include(User::fields().settings())
199        .get(&mut db)
200        .await?;
201    assert!(user_with_settings.settings.get().is_some());
202    assert_eq!(
203        "dark",
204        user_with_settings.settings.get().as_ref().unwrap().theme
205    );
206
207    // Test multiple includes in one query
208    let loaded_user = User::filter_by_id(user.id)
209        .include(User::fields().profile()) // First include
210        .include(User::fields().settings()) // Second include
211        .get(&mut db)
212        .await?;
213
214    assert!(loaded_user.profile.get().is_some());
215    assert_eq!(
216        "Software engineer",
217        loaded_user.profile.get().as_ref().unwrap().bio
218    );
219    assert!(loaded_user.settings.get().is_some());
220    assert_eq!("dark", loaded_user.settings.get().as_ref().unwrap().theme);
221    Ok(())
222}
223
224#[driver_test(id(ID), scenario(crate::scenarios::user_profile_and_todos))]
225pub async fn combined_has_many_and_has_one_preload(test: &mut Test) -> Result<()> {
226    let mut db = setup(test).await;
227
228    // Create a user with a profile and multiple todos
229    let user = User::create()
230        .name("Bob Smith")
231        .profile(Profile::create().bio("Developer"))
232        .todos([Todo::create().title("Task 1")])
233        .todos([Todo::create().title("Task 2")])
234        .todos([Todo::create().title("Task 3")])
235        .exec(&mut db)
236        .await?;
237
238    // Test combined has_one and has_many preload in a single query
239    let loaded_user = User::filter_by_id(user.id)
240        .include(User::fields().profile()) // has_one include
241        .include(User::fields().todos()) // has_many include
242        .get(&mut db)
243        .await?;
244
245    // Verify has_one association is preloaded
246    assert!(loaded_user.profile.get().is_some());
247    assert_eq!("Developer", loaded_user.profile.get().as_ref().unwrap().bio);
248
249    // Verify has_many association is preloaded
250    assert_eq!(3, loaded_user.todos.get().len());
251    let todo_titles: Vec<&str> = loaded_user
252        .todos
253        .get()
254        .iter()
255        .map(|t| t.title.as_str())
256        .collect();
257    assert!(todo_titles.contains(&"Task 1"));
258    assert!(todo_titles.contains(&"Task 2"));
259    assert!(todo_titles.contains(&"Task 3"));
260    Ok(())
261}
262
263#[driver_test(
264    id(ID),
265    requires(scan),
266    scenario(crate::scenarios::has_many_belongs_to)
267)]
268pub async fn preload_on_empty_table(test: &mut Test) -> Result<()> {
269    let mut db = setup(test).await;
270
271    // Query with include on empty table - should return empty result, not SQL error
272    let users: Vec<User> = User::all()
273        .include(User::fields().todos())
274        .exec(&mut db)
275        .await?;
276
277    assert_eq!(0, users.len());
278    Ok(())
279}
280
281#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
282pub async fn preload_on_empty_query(test: &mut Test) -> Result<()> {
283    let mut db = setup(test).await;
284
285    // Query with include on empty table - should return empty result, not SQL error
286    let users: Vec<User> = User::filter_by_name("foo")
287        .include(User::fields().todos())
288        .exec(&mut db)
289        .await?;
290
291    assert_eq!(0, users.len());
292    Ok(())
293}
294
295/// `Deferred<Vec<T>>` + `Deferred<Option<T>>`: nullable FK allows children to
296/// exist without a parent. Tests preloading from both directions.
297#[driver_test(id(ID), scenario(crate::scenarios::has_many_nullable_fk))]
298pub async fn preload_has_many_with_optional_belongs_to(test: &mut Test) -> Result<()> {
299    let mut db = setup(test).await;
300
301    // Create a user with linked todos
302    let user = User::create()
303        .todos([Todo::create().title("Task 1")])
304        .todos([Todo::create().title("Task 2")])
305        .exec(&mut db)
306        .await?;
307
308    // Preload HasMany from parent side
309    let user = User::filter_by_id(user.id)
310        .include(User::fields().todos())
311        .get(&mut db)
312        .await?;
313
314    assert_eq!(2, user.todos.get().len());
315
316    let todo_id = user.todos.get()[0].id;
317
318    // Preload Deferred<Option<User>> from child side — linked todo
319    let todo = Todo::filter_by_id(todo_id)
320        .include(Todo::fields().user())
321        .get(&mut db)
322        .await?;
323
324    assert_eq!(user.id, todo.user.get().as_ref().unwrap().id);
325
326    // Create an orphan todo (no user)
327    let orphan = Todo::create().title("Orphan").exec(&mut db).await?;
328
329    // Preload Deferred<Option<User>> on orphan — should be None
330    let orphan = Todo::filter_by_id(orphan.id)
331        .include(Todo::fields().user())
332        .get(&mut db)
333        .await?;
334
335    assert!(orphan.user.get().is_none());
336
337    Ok(())
338}
339
340/// `Deferred<Option<T>>` + `Deferred<T>` (required FK): the child always points
341/// to a parent, but the parent may or may not have a child. Tests preloading
342/// from both directions.
343#[driver_test(id(ID))]
344pub async fn preload_has_one_optional_with_required_belongs_to(test: &mut Test) -> Result<()> {
345    #[derive(Debug, toasty::Model)]
346    struct User {
347        #[key]
348        #[auto]
349        id: ID,
350
351        name: String,
352
353        #[has_one]
354        profile: toasty::Deferred<Option<Profile>>,
355    }
356
357    #[derive(Debug, toasty::Model)]
358    struct Profile {
359        #[key]
360        #[auto]
361        id: ID,
362
363        bio: String,
364
365        #[unique]
366        user_id: ID,
367
368        #[belongs_to(key = user_id, references = id)]
369        user: toasty::Deferred<User>,
370    }
371
372    let mut db = test.setup_db(models!(User, Profile)).await;
373
374    // Create a user WITH a profile
375    let user_with = User::create()
376        .name("Has Profile")
377        .profile(Profile::create().bio("hello"))
378        .exec(&mut db)
379        .await?;
380
381    // Create a user WITHOUT a profile
382    let user_without = User::create().name("No Profile").exec(&mut db).await?;
383
384    // Preload Deferred<Option<Profile>> — profile exists
385    let loaded = User::filter_by_id(user_with.id)
386        .include(User::fields().profile())
387        .get(&mut db)
388        .await?;
389
390    let profile = loaded.profile.get().as_ref().unwrap();
391    assert_eq!("hello", profile.bio);
392    assert_eq!(user_with.id, profile.user_id);
393
394    // Preload Deferred<Option<Profile>> — no profile
395    let loaded = User::filter_by_id(user_without.id)
396        .include(User::fields().profile())
397        .get(&mut db)
398        .await?;
399
400    assert!(loaded.profile.get().is_none());
401
402    // Preload Deferred<User> (required) from child side
403    let profile = Profile::filter_by_user_id(user_with.id)
404        .include(Profile::fields().user())
405        .get(&mut db)
406        .await?;
407
408    assert_eq!(user_with.id, profile.user.get().id);
409    assert_eq!("Has Profile", profile.user.get().name);
410
411    Ok(())
412}
413
414/// `Deferred<T>` (required) + `Deferred<Option<T>>`: creating a parent requires
415/// providing a child, but the child FK is nullable. Tests preloading from both
416/// directions.
417#[driver_test(id(ID))]
418pub async fn preload_has_one_required_with_optional_belongs_to(test: &mut Test) -> Result<()> {
419    #[derive(Debug, toasty::Model)]
420    struct User {
421        #[key]
422        #[auto]
423        id: ID,
424
425        name: String,
426
427        #[has_one]
428        profile: toasty::Deferred<Profile>,
429    }
430
431    #[derive(Debug, toasty::Model)]
432    struct Profile {
433        #[key]
434        #[auto]
435        id: ID,
436
437        bio: String,
438
439        #[unique]
440        user_id: Option<ID>,
441
442        #[belongs_to(key = user_id, references = id)]
443        user: toasty::Deferred<Option<User>>,
444    }
445
446    let mut db = test.setup_db(models!(User, Profile)).await;
447
448    // Create a user (must provide a profile since Deferred<T> is required)
449    let user = User::create()
450        .name("Alice")
451        .profile(Profile::create().bio("a bio"))
452        .exec(&mut db)
453        .await?;
454
455    // Preload Deferred<Profile> (required) from parent side
456    let loaded = User::filter_by_id(user.id)
457        .include(User::fields().profile())
458        .get(&mut db)
459        .await?;
460
461    let profile = loaded.profile.get();
462    assert_eq!("a bio", profile.bio);
463    assert_eq!(user.id, *profile.user_id.as_ref().unwrap());
464
465    // Preload Deferred<Option<User>> from child side
466    let profile = Profile::filter_by_id(profile.id)
467        .include(Profile::fields().user())
468        .get(&mut db)
469        .await?;
470
471    assert_eq!(user.id, profile.user.get().as_ref().unwrap().id);
472    assert_eq!("Alice", profile.user.get().as_ref().unwrap().name);
473
474    Ok(())
475}
476
477#[driver_test(id(ID), scenario(crate::scenarios::user_todo_step))]
478pub async fn nested_has_many_preload(test: &mut Test) {
479    let mut db = setup(test).await;
480
481    // Create a user with todos, each with steps
482    let user = User::create()
483        .name("Alice")
484        .todos([Todo::create()
485            .title("Todo 1")
486            .steps([Step::create().description("Step 1a")])
487            .steps([Step::create().description("Step 1b")])])
488        .todos([Todo::create()
489            .title("Todo 2")
490            .steps([Step::create().description("Step 2a")])
491            .steps([Step::create().description("Step 2b")])
492            .steps([Step::create().description("Step 2c")])])
493        .exec(&mut db)
494        .await
495        .unwrap();
496
497    // Load user with nested include: todos AND their steps
498    let user = User::filter_by_id(user.id)
499        .include(User::fields().todos().steps())
500        .get(&mut db)
501        .await
502        .unwrap();
503
504    // Verify todos are loaded
505    let todos = user.todos.get();
506    assert_eq!(2, todos.len());
507
508    // Verify steps are loaded on each todo
509    let mut all_step_descriptions: Vec<&str> = Vec::new();
510    for todo in todos {
511        let steps = todo.steps.get();
512        for step in steps {
513            all_step_descriptions.push(&step.description);
514        }
515    }
516    all_step_descriptions.sort();
517    assert_eq!(
518        all_step_descriptions,
519        vec!["Step 1a", "Step 1b", "Step 2a", "Step 2b", "Step 2c"]
520    );
521}
522
523// ===== HasMany -> Deferred<Option<T>> =====
524// User has_many Posts, each Post has_one optional Detail
525#[driver_test(id(ID))]
526pub async fn nested_has_many_then_has_one_optional(test: &mut Test) -> Result<()> {
527    #[derive(Debug, toasty::Model)]
528    #[allow(dead_code)]
529    struct User {
530        #[key]
531        #[auto]
532        id: ID,
533
534        name: String,
535
536        #[has_many]
537        posts: toasty::Deferred<Vec<Post>>,
538    }
539
540    #[derive(Debug, toasty::Model)]
541    #[allow(dead_code)]
542    struct Post {
543        #[key]
544        #[auto]
545        id: ID,
546
547        title: String,
548
549        #[index]
550        user_id: ID,
551
552        #[belongs_to(key = user_id, references = id)]
553        user: toasty::Deferred<User>,
554
555        #[has_one]
556        detail: toasty::Deferred<Option<Detail>>,
557    }
558
559    #[derive(Debug, toasty::Model)]
560    #[allow(dead_code)]
561    struct Detail {
562        #[key]
563        #[auto]
564        id: ID,
565
566        body: String,
567
568        #[unique]
569        post_id: Option<ID>,
570
571        #[belongs_to(key = post_id, references = id)]
572        post: toasty::Deferred<Option<Post>>,
573    }
574
575    let mut db = test.setup_db(models!(User, Post, Detail)).await;
576
577    let user = User::create()
578        .name("Alice")
579        .posts([Post::create()
580            .title("P1")
581            .detail(Detail::create().body("D1"))])
582        .posts([Post::create().title("P2")]) // no detail
583        .exec(&mut db)
584        .await?;
585
586    let user = User::filter_by_id(user.id)
587        .include(User::fields().posts().detail())
588        .get(&mut db)
589        .await?;
590
591    let posts = user.posts.get();
592    assert_eq!(2, posts.len());
593
594    let mut with_detail = 0;
595    let mut without_detail = 0;
596    for post in posts {
597        match post.detail.get() {
598            Some(d) => {
599                assert_eq!("D1", d.body);
600                with_detail += 1;
601            }
602            None => without_detail += 1,
603        }
604    }
605    assert_eq!(1, with_detail);
606    assert_eq!(1, without_detail);
607
608    Ok(())
609}
610
611// ===== HasMany -> Deferred<T> (required) =====
612// User has_many Accounts, each Account has_one required Settings
613#[driver_test(id(ID))]
614pub async fn nested_has_many_then_has_one_required(test: &mut Test) -> Result<()> {
615    #[derive(Debug, toasty::Model)]
616    #[allow(dead_code)]
617    struct User {
618        #[key]
619        #[auto]
620        id: ID,
621
622        name: String,
623
624        #[has_many]
625        accounts: toasty::Deferred<Vec<Account>>,
626    }
627
628    #[derive(Debug, toasty::Model)]
629    #[allow(dead_code)]
630    struct Account {
631        #[key]
632        #[auto]
633        id: ID,
634
635        label: String,
636
637        #[index]
638        user_id: ID,
639
640        #[belongs_to(key = user_id, references = id)]
641        user: toasty::Deferred<User>,
642
643        #[has_one]
644        settings: toasty::Deferred<Settings>,
645    }
646
647    #[derive(Debug, toasty::Model)]
648    #[allow(dead_code)]
649    struct Settings {
650        #[key]
651        #[auto]
652        id: ID,
653
654        theme: String,
655
656        #[unique]
657        account_id: Option<ID>,
658
659        #[belongs_to(key = account_id, references = id)]
660        account: toasty::Deferred<Option<Account>>,
661    }
662
663    let mut db = test.setup_db(models!(User, Account, Settings)).await;
664
665    let user = User::create()
666        .name("Bob")
667        .accounts([Account::create()
668            .label("A1")
669            .settings(Settings::create().theme("dark"))])
670        .accounts([Account::create()
671            .label("A2")
672            .settings(Settings::create().theme("light"))])
673        .exec(&mut db)
674        .await?;
675
676    let user = User::filter_by_id(user.id)
677        .include(User::fields().accounts().settings())
678        .get(&mut db)
679        .await?;
680
681    let accounts = user.accounts.get();
682    assert_eq!(2, accounts.len());
683
684    let mut themes: Vec<&str> = accounts
685        .iter()
686        .map(|a| a.settings.get().theme.as_str())
687        .collect();
688    themes.sort();
689    assert_eq!(themes, vec!["dark", "light"]);
690
691    Ok(())
692}
693
694// ===== HasMany -> Deferred<T> (required) =====
695// Category has_many Items, each Item belongs_to a Brand
696#[driver_test(id(ID))]
697pub async fn nested_has_many_then_belongs_to_required(test: &mut Test) -> Result<()> {
698    #[derive(Debug, toasty::Model)]
699    #[allow(dead_code)]
700    struct Category {
701        #[key]
702        #[auto]
703        id: ID,
704
705        name: String,
706
707        #[has_many]
708        items: toasty::Deferred<Vec<Item>>,
709    }
710
711    #[derive(Debug, toasty::Model)]
712    #[allow(dead_code)]
713    struct Brand {
714        #[key]
715        #[auto]
716        id: ID,
717
718        name: String,
719    }
720
721    #[derive(Debug, toasty::Model)]
722    #[allow(dead_code)]
723    struct Item {
724        #[key]
725        #[auto]
726        id: ID,
727
728        title: String,
729
730        #[index]
731        category_id: ID,
732
733        #[belongs_to(key = category_id, references = id)]
734        category: toasty::Deferred<Category>,
735
736        #[index]
737        brand_id: ID,
738
739        #[belongs_to(key = brand_id, references = id)]
740        brand: toasty::Deferred<Brand>,
741    }
742
743    let mut db = test.setup_db(models!(Category, Brand, Item)).await;
744
745    let brand_a = Brand::create().name("BrandA").exec(&mut db).await?;
746    let brand_b = Brand::create().name("BrandB").exec(&mut db).await?;
747
748    let cat = Category::create()
749        .name("Electronics")
750        .items([Item::create().title("Phone").brand(&brand_a)])
751        .items([Item::create().title("Laptop").brand(&brand_b)])
752        .exec(&mut db)
753        .await?;
754
755    let cat = Category::filter_by_id(cat.id)
756        .include(Category::fields().items().brand())
757        .get(&mut db)
758        .await?;
759
760    let items = cat.items.get();
761    assert_eq!(2, items.len());
762
763    let mut brand_names: Vec<&str> = items.iter().map(|i| i.brand.get().name.as_str()).collect();
764    brand_names.sort();
765    assert_eq!(brand_names, vec!["BrandA", "BrandB"]);
766
767    Ok(())
768}
769
770// ===== HasMany -> Deferred<T> where multiple items share the same target =====
771// Sibling rows with the same foreign key must not break the nested preload.
772// Regression for #701: the DDB nested merge used to panic with
773// "HashIndex: duplicate key detected" when two Items pointed at one Brand.
774#[driver_test(id(ID))]
775pub async fn nested_has_many_then_shared_belongs_to(test: &mut Test) -> Result<()> {
776    #[derive(Debug, toasty::Model)]
777    #[allow(dead_code)]
778    struct Category {
779        #[key]
780        #[auto]
781        id: ID,
782
783        name: String,
784
785        #[has_many]
786        items: toasty::Deferred<Vec<Item>>,
787    }
788
789    #[derive(Debug, toasty::Model)]
790    #[allow(dead_code)]
791    struct Brand {
792        #[key]
793        #[auto]
794        id: ID,
795
796        name: String,
797    }
798
799    #[derive(Debug, toasty::Model)]
800    #[allow(dead_code)]
801    struct Item {
802        #[key]
803        #[auto]
804        id: ID,
805
806        title: String,
807
808        #[index]
809        category_id: ID,
810
811        #[belongs_to(key = category_id, references = id)]
812        category: toasty::Deferred<Category>,
813
814        #[index]
815        brand_id: ID,
816
817        #[belongs_to(key = brand_id, references = id)]
818        brand: toasty::Deferred<Brand>,
819    }
820
821    let mut db = test.setup_db(models!(Category, Brand, Item)).await;
822
823    let brand = Brand::create().name("BrandA").exec(&mut db).await?;
824    let cat = Category::create()
825        .name("Electronics")
826        .items([Item::create().title("Phone").brand(&brand)])
827        .items([Item::create().title("Laptop").brand(&brand)])
828        .exec(&mut db)
829        .await?;
830
831    let cat = Category::filter_by_id(cat.id)
832        .include(Category::fields().items().brand())
833        .get(&mut db)
834        .await?;
835
836    let items = cat.items.get();
837    assert_eq!(2, items.len());
838    for item in items {
839        assert_eq!("BrandA", item.brand.get().name);
840    }
841
842    Ok(())
843}
844
845// ===== HasMany -> Deferred<Option<T>> =====
846// Team has_many Tasks, each Task optionally belongs_to an Assignee
847#[driver_test(id(ID))]
848pub async fn nested_has_many_then_belongs_to_optional(test: &mut Test) -> Result<()> {
849    #[derive(Debug, toasty::Model)]
850    #[allow(dead_code)]
851    struct Team {
852        #[key]
853        #[auto]
854        id: ID,
855
856        name: String,
857
858        #[has_many]
859        tasks: toasty::Deferred<Vec<Task>>,
860    }
861
862    #[derive(Debug, toasty::Model)]
863    #[allow(dead_code)]
864    struct Assignee {
865        #[key]
866        #[auto]
867        id: ID,
868
869        name: String,
870    }
871
872    #[derive(Debug, toasty::Model)]
873    #[allow(dead_code)]
874    struct Task {
875        #[key]
876        #[auto]
877        id: ID,
878
879        title: String,
880
881        #[index]
882        team_id: ID,
883
884        #[belongs_to(key = team_id, references = id)]
885        team: toasty::Deferred<Team>,
886
887        #[index]
888        assignee_id: Option<ID>,
889
890        #[belongs_to(key = assignee_id, references = id)]
891        assignee: toasty::Deferred<Option<Assignee>>,
892    }
893
894    let mut db = test.setup_db(models!(Team, Assignee, Task)).await;
895
896    let person = Assignee::create().name("Alice").exec(&mut db).await?;
897
898    let team = Team::create()
899        .name("Engineering")
900        .tasks([Task::create().title("Assigned").assignee(&person)])
901        .tasks([Task::create().title("Unassigned")])
902        .exec(&mut db)
903        .await?;
904
905    let team = Team::filter_by_id(team.id)
906        .include(Team::fields().tasks().assignee())
907        .get(&mut db)
908        .await?;
909
910    let tasks = team.tasks.get();
911    assert_eq!(2, tasks.len());
912
913    let mut assigned = 0;
914    let mut unassigned = 0;
915    for task in tasks {
916        match task.assignee.get() {
917            Some(a) => {
918                assert_eq!("Alice", a.name);
919                assigned += 1;
920            }
921            None => unassigned += 1,
922        }
923    }
924    assert_eq!(1, assigned);
925    assert_eq!(1, unassigned);
926
927    Ok(())
928}
929
930// ===== Deferred<Option<T>> -> HasMany =====
931// User has_one optional Profile, Profile has_many Badges
932#[driver_test(id(ID))]
933pub async fn nested_has_one_optional_then_has_many(test: &mut Test) -> Result<()> {
934    #[derive(Debug, toasty::Model)]
935    #[allow(dead_code)]
936    struct User {
937        #[key]
938        #[auto]
939        id: ID,
940
941        name: String,
942
943        #[has_one]
944        profile: toasty::Deferred<Option<Profile>>,
945    }
946
947    #[derive(Debug, toasty::Model)]
948    #[allow(dead_code)]
949    struct Profile {
950        #[key]
951        #[auto]
952        id: ID,
953
954        bio: String,
955
956        #[unique]
957        user_id: Option<ID>,
958
959        #[belongs_to(key = user_id, references = id)]
960        user: toasty::Deferred<Option<User>>,
961
962        #[has_many]
963        badges: toasty::Deferred<Vec<Badge>>,
964    }
965
966    #[derive(Debug, toasty::Model)]
967    #[allow(dead_code)]
968    struct Badge {
969        #[key]
970        #[auto]
971        id: ID,
972
973        label: String,
974
975        #[index]
976        profile_id: ID,
977
978        #[belongs_to(key = profile_id, references = id)]
979        profile: toasty::Deferred<Profile>,
980    }
981
982    let mut db = test.setup_db(models!(User, Profile, Badge)).await;
983
984    // User with profile and badges
985    let user = User::create()
986        .name("Alice")
987        .profile(
988            Profile::create()
989                .bio("hi")
990                .badges([Badge::create().label("Gold")])
991                .badges([Badge::create().label("Silver")]),
992        )
993        .exec(&mut db)
994        .await?;
995
996    let user = User::filter_by_id(user.id)
997        .include(User::fields().profile().badges())
998        .get(&mut db)
999        .await?;
1000
1001    let profile = user.profile.get().as_ref().unwrap();
1002    assert_eq!("hi", profile.bio);
1003    let mut labels: Vec<&str> = profile
1004        .badges
1005        .get()
1006        .iter()
1007        .map(|b| b.label.as_str())
1008        .collect();
1009    labels.sort();
1010    assert_eq!(labels, vec!["Gold", "Silver"]);
1011
1012    // User without profile - nested preload should handle gracefully
1013    let user2 = User::create().name("Bob").exec(&mut db).await?;
1014
1015    let user2 = User::filter_by_id(user2.id)
1016        .include(User::fields().profile().badges())
1017        .get(&mut db)
1018        .await?;
1019
1020    assert!(user2.profile.get().is_none());
1021
1022    Ok(())
1023}
1024
1025// ===== Deferred<T> (required) -> HasMany =====
1026// Order has_one required Invoice, Invoice has_many LineItems
1027#[driver_test(id(ID))]
1028pub async fn nested_has_one_required_then_has_many(test: &mut Test) -> Result<()> {
1029    #[derive(Debug, toasty::Model)]
1030    #[allow(dead_code)]
1031    struct Order {
1032        #[key]
1033        #[auto]
1034        id: ID,
1035
1036        label: String,
1037
1038        #[has_one]
1039        invoice: toasty::Deferred<Invoice>,
1040    }
1041
1042    #[derive(Debug, toasty::Model)]
1043    #[allow(dead_code)]
1044    struct Invoice {
1045        #[key]
1046        #[auto]
1047        id: ID,
1048
1049        code: String,
1050
1051        #[unique]
1052        order_id: Option<ID>,
1053
1054        #[belongs_to(key = order_id, references = id)]
1055        order: toasty::Deferred<Option<Order>>,
1056
1057        #[has_many]
1058        line_items: toasty::Deferred<Vec<LineItem>>,
1059    }
1060
1061    #[derive(Debug, toasty::Model)]
1062    #[allow(dead_code)]
1063    struct LineItem {
1064        #[key]
1065        #[auto]
1066        id: ID,
1067
1068        description: String,
1069
1070        #[index]
1071        invoice_id: ID,
1072
1073        #[belongs_to(key = invoice_id, references = id)]
1074        invoice: toasty::Deferred<Invoice>,
1075    }
1076
1077    let mut db = test.setup_db(models!(Order, Invoice, LineItem)).await;
1078
1079    let order = Order::create()
1080        .label("Order1")
1081        .invoice(
1082            Invoice::create()
1083                .code("INV-001")
1084                .line_items([LineItem::create().description("Widget")])
1085                .line_items([LineItem::create().description("Gadget")]),
1086        )
1087        .exec(&mut db)
1088        .await?;
1089
1090    let order = Order::filter_by_id(order.id)
1091        .include(Order::fields().invoice().line_items())
1092        .get(&mut db)
1093        .await?;
1094
1095    let invoice = order.invoice.get();
1096    assert_eq!("INV-001", invoice.code);
1097    let mut descs: Vec<&str> = invoice
1098        .line_items
1099        .get()
1100        .iter()
1101        .map(|li| li.description.as_str())
1102        .collect();
1103    descs.sort();
1104    assert_eq!(descs, vec!["Gadget", "Widget"]);
1105
1106    Ok(())
1107}
1108
1109// ===== Deferred<Option<T>> -> Deferred<Option<T>> =====
1110// User has_one optional Profile, Profile has_one optional Avatar
1111#[driver_test(id(ID))]
1112pub async fn nested_has_one_optional_then_has_one_optional(test: &mut Test) -> Result<()> {
1113    #[derive(Debug, toasty::Model)]
1114    #[allow(dead_code)]
1115    struct User {
1116        #[key]
1117        #[auto]
1118        id: ID,
1119
1120        name: String,
1121
1122        #[has_one]
1123        profile: toasty::Deferred<Option<Profile>>,
1124    }
1125
1126    #[derive(Debug, toasty::Model)]
1127    #[allow(dead_code)]
1128    struct Profile {
1129        #[key]
1130        #[auto]
1131        id: ID,
1132
1133        bio: String,
1134
1135        #[unique]
1136        user_id: Option<ID>,
1137
1138        #[belongs_to(key = user_id, references = id)]
1139        user: toasty::Deferred<Option<User>>,
1140
1141        #[has_one]
1142        avatar: toasty::Deferred<Option<Avatar>>,
1143    }
1144
1145    #[derive(Debug, toasty::Model)]
1146    #[allow(dead_code)]
1147    struct Avatar {
1148        #[key]
1149        #[auto]
1150        id: ID,
1151
1152        url: String,
1153
1154        #[unique]
1155        profile_id: Option<ID>,
1156
1157        #[belongs_to(key = profile_id, references = id)]
1158        profile: toasty::Deferred<Option<Profile>>,
1159    }
1160
1161    let mut db = test.setup_db(models!(User, Profile, Avatar)).await;
1162
1163    // User -> Profile -> Avatar (all present)
1164    let user = User::create()
1165        .name("Alice")
1166        .profile(
1167            Profile::create()
1168                .bio("hi")
1169                .avatar(Avatar::create().url("pic.png")),
1170        )
1171        .exec(&mut db)
1172        .await?;
1173
1174    let user = User::filter_by_id(user.id)
1175        .include(User::fields().profile().avatar())
1176        .get(&mut db)
1177        .await?;
1178
1179    let profile = user.profile.get().as_ref().unwrap();
1180    assert_eq!("hi", profile.bio);
1181    let avatar = profile.avatar.get().as_ref().unwrap();
1182    assert_eq!("pic.png", avatar.url);
1183
1184    // User -> Profile (present) -> Avatar (missing)
1185    let user2 = User::create()
1186        .name("Bob")
1187        .profile(Profile::create().bio("no pic"))
1188        .exec(&mut db)
1189        .await?;
1190
1191    let user2 = User::filter_by_id(user2.id)
1192        .include(User::fields().profile().avatar())
1193        .get(&mut db)
1194        .await?;
1195
1196    let profile2 = user2.profile.get().as_ref().unwrap();
1197    assert_eq!("no pic", profile2.bio);
1198    assert!(profile2.avatar.get().is_none());
1199
1200    // User -> Profile (missing) - nested preload short-circuits
1201    let user3 = User::create().name("Carol").exec(&mut db).await?;
1202
1203    let user3 = User::filter_by_id(user3.id)
1204        .include(User::fields().profile().avatar())
1205        .get(&mut db)
1206        .await?;
1207
1208    assert!(user3.profile.get().is_none());
1209
1210    Ok(())
1211}
1212
1213// ===== Deferred<T> (required) -> Deferred<T> (required) =====
1214// User has_one required Profile, Profile has_one required Avatar
1215#[driver_test(id(ID))]
1216pub async fn nested_has_one_required_then_has_one_required(test: &mut Test) -> Result<()> {
1217    #[derive(Debug, toasty::Model)]
1218    #[allow(dead_code)]
1219    struct User {
1220        #[key]
1221        #[auto]
1222        id: ID,
1223
1224        name: String,
1225
1226        #[has_one]
1227        profile: toasty::Deferred<Profile>,
1228    }
1229
1230    #[derive(Debug, toasty::Model)]
1231    #[allow(dead_code)]
1232    struct Profile {
1233        #[key]
1234        #[auto]
1235        id: ID,
1236
1237        bio: String,
1238
1239        #[unique]
1240        user_id: Option<ID>,
1241
1242        #[belongs_to(key = user_id, references = id)]
1243        user: toasty::Deferred<Option<User>>,
1244
1245        #[has_one]
1246        avatar: toasty::Deferred<Avatar>,
1247    }
1248
1249    #[derive(Debug, toasty::Model)]
1250    #[allow(dead_code)]
1251    struct Avatar {
1252        #[key]
1253        #[auto]
1254        id: ID,
1255
1256        url: String,
1257
1258        #[unique]
1259        profile_id: Option<ID>,
1260
1261        #[belongs_to(key = profile_id, references = id)]
1262        profile: toasty::Deferred<Option<Profile>>,
1263    }
1264
1265    let mut db = test.setup_db(models!(User, Profile, Avatar)).await;
1266
1267    let user = User::create()
1268        .name("Alice")
1269        .profile(
1270            Profile::create()
1271                .bio("engineer")
1272                .avatar(Avatar::create().url("alice.jpg")),
1273        )
1274        .exec(&mut db)
1275        .await?;
1276
1277    let user = User::filter_by_id(user.id)
1278        .include(User::fields().profile().avatar())
1279        .get(&mut db)
1280        .await?;
1281
1282    let profile = user.profile.get();
1283    assert_eq!("engineer", profile.bio);
1284    let avatar = profile.avatar.get();
1285    assert_eq!("alice.jpg", avatar.url);
1286
1287    Ok(())
1288}
1289
1290// ===== Deferred<Option<T>> -> Deferred<T> (required) =====
1291// User has_one optional Review, Review belongs_to a Product
1292#[driver_test(id(ID))]
1293pub async fn nested_has_one_optional_then_belongs_to_required(test: &mut Test) -> Result<()> {
1294    #[derive(Debug, toasty::Model)]
1295    #[allow(dead_code)]
1296    struct User {
1297        #[key]
1298        #[auto]
1299        id: ID,
1300
1301        name: String,
1302
1303        #[has_one]
1304        review: toasty::Deferred<Option<Review>>,
1305    }
1306
1307    #[derive(Debug, toasty::Model)]
1308    #[allow(dead_code)]
1309    struct Product {
1310        #[key]
1311        #[auto]
1312        id: ID,
1313
1314        name: String,
1315    }
1316
1317    #[derive(Debug, toasty::Model)]
1318    #[allow(dead_code)]
1319    struct Review {
1320        #[key]
1321        #[auto]
1322        id: ID,
1323
1324        body: String,
1325
1326        #[unique]
1327        user_id: Option<ID>,
1328
1329        #[belongs_to(key = user_id, references = id)]
1330        user: toasty::Deferred<Option<User>>,
1331
1332        #[index]
1333        product_id: ID,
1334
1335        #[belongs_to(key = product_id, references = id)]
1336        product: toasty::Deferred<Product>,
1337    }
1338
1339    let mut db = test.setup_db(models!(User, Product, Review)).await;
1340
1341    let product = Product::create().name("Widget").exec(&mut db).await?;
1342
1343    let user = User::create()
1344        .name("Alice")
1345        .review(Review::create().body("Great!").product(&product))
1346        .exec(&mut db)
1347        .await?;
1348
1349    // User with review -> preload nested product
1350    let user = User::filter_by_id(user.id)
1351        .include(User::fields().review().product())
1352        .get(&mut db)
1353        .await?;
1354
1355    let review = user.review.get().as_ref().unwrap();
1356    assert_eq!("Great!", review.body);
1357    assert_eq!("Widget", review.product.get().name);
1358
1359    // User without review
1360    let user2 = User::create().name("Bob").exec(&mut db).await?;
1361
1362    let user2 = User::filter_by_id(user2.id)
1363        .include(User::fields().review().product())
1364        .get(&mut db)
1365        .await?;
1366
1367    assert!(user2.review.get().is_none());
1368
1369    Ok(())
1370}
1371
1372// ===== Deferred<T> (required) -> HasMany =====
1373// Comment belongs_to a Post, Post has_many Tags
1374#[driver_test(id(ID))]
1375pub async fn nested_belongs_to_required_then_has_many(test: &mut Test) -> Result<()> {
1376    #[derive(Debug, toasty::Model)]
1377    #[allow(dead_code)]
1378    struct Post {
1379        #[key]
1380        #[auto]
1381        id: ID,
1382
1383        title: String,
1384
1385        #[has_many]
1386        tags: toasty::Deferred<Vec<Tag>>,
1387    }
1388
1389    #[derive(Debug, toasty::Model)]
1390    #[allow(dead_code)]
1391    struct Tag {
1392        #[key]
1393        #[auto]
1394        id: ID,
1395
1396        label: String,
1397
1398        #[index]
1399        post_id: ID,
1400
1401        #[belongs_to(key = post_id, references = id)]
1402        post: toasty::Deferred<Post>,
1403    }
1404
1405    #[derive(Debug, toasty::Model)]
1406    #[allow(dead_code)]
1407    struct Comment {
1408        #[key]
1409        #[auto]
1410        id: ID,
1411
1412        body: String,
1413
1414        #[index]
1415        post_id: ID,
1416
1417        #[belongs_to(key = post_id, references = id)]
1418        post: toasty::Deferred<Post>,
1419    }
1420
1421    let mut db = test.setup_db(models!(Post, Tag, Comment)).await;
1422
1423    let post = Post::create()
1424        .title("Hello")
1425        .tags([Tag::create().label("rust")])
1426        .tags([Tag::create().label("orm")])
1427        .exec(&mut db)
1428        .await?;
1429
1430    let comment = Comment::create()
1431        .body("Nice post")
1432        .post(&post)
1433        .exec(&mut db)
1434        .await?;
1435
1436    // From comment, preload post's tags
1437    let comment = Comment::filter_by_id(comment.id)
1438        .include(Comment::fields().post().tags())
1439        .get(&mut db)
1440        .await?;
1441
1442    assert_eq!("Hello", comment.post.get().title);
1443    let mut labels: Vec<&str> = comment
1444        .post
1445        .get()
1446        .tags
1447        .get()
1448        .iter()
1449        .map(|t| t.label.as_str())
1450        .collect();
1451    labels.sort();
1452    assert_eq!(labels, vec!["orm", "rust"]);
1453
1454    Ok(())
1455}
1456
1457// ===== Deferred<T> (required) -> Deferred<Option<T>> =====
1458// Todo belongs_to a User, User has_one optional Profile
1459#[driver_test(id(ID))]
1460pub async fn nested_belongs_to_required_then_has_one_optional(test: &mut Test) -> Result<()> {
1461    #[derive(Debug, toasty::Model)]
1462    #[allow(dead_code)]
1463    struct User {
1464        #[key]
1465        #[auto]
1466        id: ID,
1467
1468        name: String,
1469
1470        #[has_one]
1471        profile: toasty::Deferred<Option<Profile>>,
1472
1473        #[has_many]
1474        todos: toasty::Deferred<Vec<Todo>>,
1475    }
1476
1477    #[derive(Debug, toasty::Model)]
1478    #[allow(dead_code)]
1479    struct Profile {
1480        #[key]
1481        #[auto]
1482        id: ID,
1483
1484        bio: String,
1485
1486        #[unique]
1487        user_id: Option<ID>,
1488
1489        #[belongs_to(key = user_id, references = id)]
1490        user: toasty::Deferred<Option<User>>,
1491    }
1492
1493    #[derive(Debug, toasty::Model)]
1494    #[allow(dead_code)]
1495    struct Todo {
1496        #[key]
1497        #[auto]
1498        id: ID,
1499
1500        title: String,
1501
1502        #[index]
1503        user_id: ID,
1504
1505        #[belongs_to(key = user_id, references = id)]
1506        user: toasty::Deferred<User>,
1507    }
1508
1509    let mut db = test.setup_db(models!(User, Profile, Todo)).await;
1510
1511    // User with profile
1512    let user = User::create()
1513        .name("Alice")
1514        .profile(Profile::create().bio("developer"))
1515        .todos([Todo::create().title("Task 1")])
1516        .exec(&mut db)
1517        .await?;
1518
1519    let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
1520
1521    let todo = Todo::filter_by_id(todo_id)
1522        .include(Todo::fields().user().profile())
1523        .get(&mut db)
1524        .await?;
1525
1526    assert_eq!("Alice", todo.user.get().name);
1527    let profile = todo.user.get().profile.get().as_ref().unwrap();
1528    assert_eq!("developer", profile.bio);
1529
1530    // User without profile
1531    let user2 = User::create()
1532        .name("Bob")
1533        .todos([Todo::create().title("Task 2")])
1534        .exec(&mut db)
1535        .await?;
1536
1537    let todo2_id = Todo::get_by_user_id(&mut db, user2.id).await?.id;
1538
1539    let todo2 = Todo::filter_by_id(todo2_id)
1540        .include(Todo::fields().user().profile())
1541        .get(&mut db)
1542        .await?;
1543
1544    assert_eq!("Bob", todo2.user.get().name);
1545    assert!(todo2.user.get().profile.get().is_none());
1546
1547    Ok(())
1548}
1549
1550// ===== Deferred<T> (required) -> Deferred<T> (required) =====
1551// Step belongs_to a Todo, Todo belongs_to a User (chain of belongs_to going up)
1552#[driver_test(id(ID), scenario(crate::scenarios::user_todo_step))]
1553pub async fn nested_belongs_to_required_then_belongs_to_required(test: &mut Test) -> Result<()> {
1554    let mut db = setup(test).await;
1555
1556    let user = User::create()
1557        .name("Alice")
1558        .todos([Todo::create()
1559            .title("T1")
1560            .steps([Step::create().description("S1")])])
1561        .exec(&mut db)
1562        .await?;
1563
1564    let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
1565    let step_id = Step::get_by_todo_id(&mut db, todo_id).await?.id;
1566
1567    // From step, preload todo and then todo's user
1568    let step = Step::filter_by_id(step_id)
1569        .include(Step::fields().todo().user())
1570        .get(&mut db)
1571        .await?;
1572
1573    assert_eq!("T1", step.todo.get().title);
1574    assert_eq!("Alice", step.todo.get().user.get().name);
1575
1576    Ok(())
1577}
1578
1579// ===== Deferred<Option<T>> -> HasMany =====
1580// Task optionally belongs_to a Project, Project has_many Members
1581#[driver_test(id(ID))]
1582pub async fn nested_belongs_to_optional_then_has_many(test: &mut Test) -> Result<()> {
1583    #[derive(Debug, toasty::Model)]
1584    #[allow(dead_code)]
1585    struct Project {
1586        #[key]
1587        #[auto]
1588        id: ID,
1589
1590        name: String,
1591
1592        #[has_many]
1593        members: toasty::Deferred<Vec<Member>>,
1594    }
1595
1596    #[derive(Debug, toasty::Model)]
1597    #[allow(dead_code)]
1598    struct Member {
1599        #[key]
1600        #[auto]
1601        id: ID,
1602
1603        name: String,
1604
1605        #[index]
1606        project_id: ID,
1607
1608        #[belongs_to(key = project_id, references = id)]
1609        project: toasty::Deferred<Project>,
1610    }
1611
1612    #[derive(Debug, toasty::Model)]
1613    #[allow(dead_code)]
1614    struct Task {
1615        #[key]
1616        #[auto]
1617        id: ID,
1618
1619        title: String,
1620
1621        #[index]
1622        project_id: Option<ID>,
1623
1624        #[belongs_to(key = project_id, references = id)]
1625        project: toasty::Deferred<Option<Project>>,
1626    }
1627
1628    let mut db = test.setup_db(models!(Project, Member, Task)).await;
1629
1630    let project = Project::create()
1631        .name("Proj1")
1632        .members([Member::create().name("Alice")])
1633        .members([Member::create().name("Bob")])
1634        .exec(&mut db)
1635        .await?;
1636
1637    // Task with project
1638    let task = Task::create()
1639        .title("Linked")
1640        .project(&project)
1641        .exec(&mut db)
1642        .await?;
1643
1644    let task = Task::filter_by_id(task.id)
1645        .include(Task::fields().project().members())
1646        .get(&mut db)
1647        .await?;
1648
1649    let proj = task.project.get().as_ref().unwrap();
1650    assert_eq!("Proj1", proj.name);
1651    let mut names: Vec<&str> = proj.members.get().iter().map(|m| m.name.as_str()).collect();
1652    names.sort();
1653    assert_eq!(names, vec!["Alice", "Bob"]);
1654
1655    // Task without project
1656    let orphan = Task::create().title("Orphan").exec(&mut db).await?;
1657
1658    let orphan = Task::filter_by_id(orphan.id)
1659        .include(Task::fields().project().members())
1660        .get(&mut db)
1661        .await?;
1662
1663    assert!(orphan.project.get().is_none());
1664
1665    Ok(())
1666}
1667
1668// ===== Deferred<Option<T>> -> Deferred<Option<T>> =====
1669// Comment optionally belongs_to a Post, Post optionally belongs_to a Category
1670#[driver_test(id(ID))]
1671pub async fn nested_belongs_to_optional_then_belongs_to_optional(test: &mut Test) -> Result<()> {
1672    #[derive(Debug, toasty::Model)]
1673    #[allow(dead_code)]
1674    struct Category {
1675        #[key]
1676        #[auto]
1677        id: ID,
1678
1679        name: String,
1680    }
1681
1682    #[derive(Debug, toasty::Model)]
1683    #[allow(dead_code)]
1684    struct Post {
1685        #[key]
1686        #[auto]
1687        id: ID,
1688
1689        title: String,
1690
1691        #[index]
1692        category_id: Option<ID>,
1693
1694        #[belongs_to(key = category_id, references = id)]
1695        category: toasty::Deferred<Option<Category>>,
1696    }
1697
1698    #[derive(Debug, toasty::Model)]
1699    #[allow(dead_code)]
1700    struct Comment {
1701        #[key]
1702        #[auto]
1703        id: ID,
1704
1705        body: String,
1706
1707        #[index]
1708        post_id: Option<ID>,
1709
1710        #[belongs_to(key = post_id, references = id)]
1711        post: toasty::Deferred<Option<Post>>,
1712    }
1713
1714    let mut db = test.setup_db(models!(Category, Post, Comment)).await;
1715
1716    let cat = Category::create().name("Tech").exec(&mut db).await?;
1717    let post = Post::create()
1718        .title("Hello")
1719        .category(&cat)
1720        .exec(&mut db)
1721        .await?;
1722
1723    // Comment -> Post (present) -> Category (present)
1724    let c1 = Comment::create()
1725        .body("Nice")
1726        .post(&post)
1727        .exec(&mut db)
1728        .await?;
1729
1730    let c1 = Comment::filter_by_id(c1.id)
1731        .include(Comment::fields().post().category())
1732        .get(&mut db)
1733        .await?;
1734
1735    let loaded_post = c1.post.get().as_ref().unwrap();
1736    assert_eq!("Hello", loaded_post.title);
1737    let loaded_cat = loaded_post.category.get().as_ref().unwrap();
1738    assert_eq!("Tech", loaded_cat.name);
1739
1740    // Post without category
1741    let post2 = Post::create().title("Uncategorized").exec(&mut db).await?;
1742    let c2 = Comment::create()
1743        .body("Hmm")
1744        .post(&post2)
1745        .exec(&mut db)
1746        .await?;
1747
1748    let c2 = Comment::filter_by_id(c2.id)
1749        .include(Comment::fields().post().category())
1750        .get(&mut db)
1751        .await?;
1752
1753    let loaded_post2 = c2.post.get().as_ref().unwrap();
1754    assert_eq!("Uncategorized", loaded_post2.title);
1755    assert!(loaded_post2.category.get().is_none());
1756
1757    // Comment without post
1758    let c3 = Comment::create().body("Orphan").exec(&mut db).await?;
1759
1760    let c3 = Comment::filter_by_id(c3.id)
1761        .include(Comment::fields().post().category())
1762        .get(&mut db)
1763        .await?;
1764
1765    assert!(c3.post.get().is_none());
1766
1767    Ok(())
1768}
1769
1770// ===== Deferred<T> -> Deferred<T> (required) =====
1771// Todo belongs_to a User, User has_one required Config
1772#[driver_test(id(ID))]
1773pub async fn nested_belongs_to_required_then_has_one_required(test: &mut Test) -> Result<()> {
1774    #[derive(Debug, toasty::Model)]
1775    #[allow(dead_code)]
1776    struct User {
1777        #[key]
1778        #[auto]
1779        id: ID,
1780
1781        name: String,
1782
1783        #[has_one]
1784        config: toasty::Deferred<Config>,
1785
1786        #[has_many]
1787        todos: toasty::Deferred<Vec<Todo>>,
1788    }
1789
1790    #[derive(Debug, toasty::Model)]
1791    #[allow(dead_code)]
1792    struct Config {
1793        #[key]
1794        #[auto]
1795        id: ID,
1796
1797        theme: String,
1798
1799        #[unique]
1800        user_id: Option<ID>,
1801
1802        #[belongs_to(key = user_id, references = id)]
1803        user: toasty::Deferred<Option<User>>,
1804    }
1805
1806    #[derive(Debug, toasty::Model)]
1807    #[allow(dead_code)]
1808    struct Todo {
1809        #[key]
1810        #[auto]
1811        id: ID,
1812
1813        title: String,
1814
1815        #[index]
1816        user_id: ID,
1817
1818        #[belongs_to(key = user_id, references = id)]
1819        user: toasty::Deferred<User>,
1820    }
1821
1822    let mut db = test.setup_db(models!(User, Config, Todo)).await;
1823
1824    let user = User::create()
1825        .name("Alice")
1826        .config(Config::create().theme("dark"))
1827        .todos([Todo::create().title("Task")])
1828        .exec(&mut db)
1829        .await?;
1830
1831    let todo_id = Todo::get_by_user_id(&mut db, user.id).await?.id;
1832
1833    let todo = Todo::filter_by_id(todo_id)
1834        .include(Todo::fields().user().config())
1835        .get(&mut db)
1836        .await?;
1837
1838    assert_eq!("Alice", todo.user.get().name);
1839    assert_eq!("dark", todo.user.get().config.get().theme);
1840
1841    Ok(())
1842}
1843
1844// ===== HasMany -> HasMany (with empty nested collections) =====
1845// Ensures that when some parents have children and others don't, nested preload
1846// correctly assigns empty collections rather than panicking.
1847#[driver_test(id(ID), scenario(crate::scenarios::user_todo_step))]
1848pub async fn nested_has_many_then_has_many_with_empty_leaves(test: &mut Test) {
1849    let mut db = setup(test).await;
1850
1851    let user = User::create()
1852        .name("Alice")
1853        .todos([Todo::create()
1854            .title("With Steps")
1855            .steps([Step::create().description("S1")])])
1856        .todos([Todo::create().title("No Steps")]) // empty nested
1857        .exec(&mut db)
1858        .await
1859        .unwrap();
1860
1861    let user = User::filter_by_id(user.id)
1862        .include(User::fields().todos().steps())
1863        .get(&mut db)
1864        .await
1865        .unwrap();
1866
1867    let todos = user.todos.get();
1868    assert_eq!(2, todos.len());
1869
1870    let mut total_steps = 0;
1871    for todo in todos {
1872        let steps = todo.steps.get();
1873        if todo.title == "With Steps" {
1874            assert_eq!(1, steps.len());
1875            assert_eq!("S1", steps[0].description);
1876        } else {
1877            assert_eq!(0, steps.len());
1878        }
1879        total_steps += steps.len();
1880    }
1881    assert_eq!(1, total_steps);
1882}
1883
1884// ===== Issue #691: multiple nested includes sharing a prefix =====
1885// When several `.include()` calls share a common prefix (e.g. `todos()`), each
1886// sibling nested include must be preserved — previously the second overwrote
1887// the first at the shared field slot.
1888#[driver_test(
1889    id(ID),
1890    requires(sql),
1891    scenario(crate::scenarios::has_many_multi_relation)
1892)]
1893pub async fn sibling_nested_includes_on_shared_prefix(test: &mut Test) -> Result<()> {
1894    let mut db = setup(test).await;
1895
1896    let category = toasty::create!(Category { name: "Food" })
1897        .exec(&mut db)
1898        .await?;
1899    let user = toasty::create!(User {
1900        name: "Alice",
1901        todos: [
1902            { title: "T1", category: &category },
1903            { title: "T2", category: &category },
1904        ],
1905    })
1906    .exec(&mut db)
1907    .await?;
1908
1909    // Two sibling nested includes under the `todos()` prefix. Both must be
1910    // preloaded — neither should be silently clobbered by the other.
1911    let loaded = User::filter_by_id(user.id)
1912        .include(User::fields().todos().user())
1913        .include(User::fields().todos().category())
1914        .get(&mut db)
1915        .await?;
1916
1917    let todos = loaded.todos.get();
1918    assert_eq!(2, todos.len());
1919    for todo in todos {
1920        assert_eq!("Alice", todo.user.get().name);
1921        assert_eq!("Food", todo.category.get().name);
1922    }
1923
1924    Ok(())
1925}
1926
1927// Mirrors the exact pattern from issue #691: a bare top-level include plus
1928// two sibling nested includes sharing that same top-level prefix. All three
1929// paths must be honored.
1930#[driver_test(
1931    id(ID),
1932    requires(sql),
1933    scenario(crate::scenarios::has_many_multi_relation)
1934)]
1935pub async fn bare_and_nested_includes_on_shared_prefix(test: &mut Test) -> Result<()> {
1936    let mut db = setup(test).await;
1937
1938    let category = toasty::create!(Category { name: "Food" })
1939        .exec(&mut db)
1940        .await?;
1941    let user = toasty::create!(User {
1942        name: "Alice",
1943        todos: [{ title: "T1", category: &category }],
1944    })
1945    .exec(&mut db)
1946    .await?;
1947
1948    let loaded = User::filter_by_id(user.id)
1949        .include(User::fields().todos()) // bare
1950        .include(User::fields().todos().user()) // sibling 1
1951        .include(User::fields().todos().category()) // sibling 2
1952        .get(&mut db)
1953        .await?;
1954
1955    let todos = loaded.todos.get();
1956    assert_eq!(1, todos.len());
1957    assert_eq!("Alice", todos[0].user.get().name);
1958    assert_eq!("Food", todos[0].category.get().name);
1959
1960    Ok(())
1961}
1962
1963// DDB-compatible variant of the issue #691 repro. Two sibling nested includes
1964// under `items()` — each item has a distinct brand and supplier so the
1965// per-item belongs_to batches stay unique (DDB's nested merge uses a HashIndex
1966// that requires unique keys).
1967#[driver_test(id(ID))]
1968pub async fn sibling_nested_includes_on_shared_prefix_non_sql(test: &mut Test) -> Result<()> {
1969    #[derive(Debug, toasty::Model)]
1970    #[allow(dead_code)]
1971    struct Category {
1972        #[key]
1973        #[auto]
1974        id: ID,
1975
1976        name: String,
1977
1978        #[has_many]
1979        items: toasty::Deferred<Vec<Item>>,
1980    }
1981
1982    #[derive(Debug, toasty::Model)]
1983    #[allow(dead_code)]
1984    struct Brand {
1985        #[key]
1986        #[auto]
1987        id: ID,
1988
1989        name: String,
1990    }
1991
1992    #[derive(Debug, toasty::Model)]
1993    #[allow(dead_code)]
1994    struct Supplier {
1995        #[key]
1996        #[auto]
1997        id: ID,
1998
1999        name: String,
2000    }
2001
2002    #[derive(Debug, toasty::Model)]
2003    #[allow(dead_code)]
2004    struct Item {
2005        #[key]
2006        #[auto]
2007        id: ID,
2008
2009        title: String,
2010
2011        #[index]
2012        category_id: ID,
2013
2014        #[belongs_to(key = category_id, references = id)]
2015        category: toasty::Deferred<Category>,
2016
2017        #[index]
2018        brand_id: ID,
2019
2020        #[belongs_to(key = brand_id, references = id)]
2021        brand: toasty::Deferred<Brand>,
2022
2023        #[index]
2024        supplier_id: ID,
2025
2026        #[belongs_to(key = supplier_id, references = id)]
2027        supplier: toasty::Deferred<Supplier>,
2028    }
2029
2030    let mut db = test
2031        .setup_db(models!(Category, Brand, Supplier, Item))
2032        .await;
2033
2034    let brand_a = toasty::create!(Brand { name: "BrandA" })
2035        .exec(&mut db)
2036        .await?;
2037    let brand_b = toasty::create!(Brand { name: "BrandB" })
2038        .exec(&mut db)
2039        .await?;
2040    let sup_a = toasty::create!(Supplier { name: "SupA" })
2041        .exec(&mut db)
2042        .await?;
2043    let sup_b = toasty::create!(Supplier { name: "SupB" })
2044        .exec(&mut db)
2045        .await?;
2046
2047    let cat = toasty::create!(Category {
2048        name: "Electronics",
2049        items: [
2050            { title: "Phone", brand: &brand_a, supplier: &sup_a },
2051            { title: "Laptop", brand: &brand_b, supplier: &sup_b },
2052        ],
2053    })
2054    .exec(&mut db)
2055    .await?;
2056
2057    // Two sibling nested includes under the `items()` prefix. Without the
2058    // fix, the second would overwrite the first.
2059    let loaded = Category::filter_by_id(cat.id)
2060        .include(Category::fields().items().brand())
2061        .include(Category::fields().items().supplier())
2062        .get(&mut db)
2063        .await?;
2064
2065    let items = loaded.items.get();
2066    assert_eq!(2, items.len());
2067    let mut pairs: Vec<(&str, &str)> = items
2068        .iter()
2069        .map(|i| (i.brand.get().name.as_str(), i.supplier.get().name.as_str()))
2070        .collect();
2071    pairs.sort();
2072    assert_eq!(pairs, vec![("BrandA", "SupA"), ("BrandB", "SupB")]);
2073
2074    Ok(())
2075}