Skip to main content

toasty_driver_integration_suite/tests/
relation_has_many_composite_key.rs

1//! Tests for `has_many` / `belongs_to` relationships whose foreign key spans
2//! multiple columns. These parallel the single-key relation tests so we can
3//! make sure composite-key behavior matches.
4//!
5//! Two scenarios are used:
6//!
7//! - [`crate::scenarios::composite_has_many_belongs_to`] — `User` with a
8//!   single-column auto PK and a `Todo` whose PK is
9//!   `#[key(partition = user_id, local = id)]`. The FK column is part of
10//!   the child's identity, so reassignment isn't supported on backends that
11//!   can't mutate primary-key columns (DynamoDB).
12//! - [`crate::scenarios::composite_fk_has_many_belongs_to`] — `User` with a
13//!   composite PK `(id, revision)`, and `Todo` with single PK and a
14//!   two-column indexed FK. Used by the reassignment tests, which need the
15//!   FK to be a plain updatable column.
16//!
17//! See: https://github.com/tokio-rs/toasty/discussions/904
18
19use crate::prelude::*;
20use hashbrown::HashMap;
21
22/// When a composite-key `belongs_to` has no covering index on the parent
23/// side, schema verification must return a helpful invalid-schema error
24/// rather than panicking with `failed to find relation index`.
25#[driver_test]
26pub async fn composite_belongs_to_missing_index_is_error(test: &mut Test) -> Result<()> {
27    #[derive(Debug, toasty::Model)]
28    #[key(id, revision)]
29    struct Parent {
30        id: String,
31        revision: i64,
32
33        #[has_many]
34        children: toasty::Deferred<Vec<Child>>,
35    }
36
37    #[derive(Debug, toasty::Model)]
38    struct Child {
39        #[key]
40        id: String,
41
42        // Two field-level `#[index]` annotations create two separate
43        // single-column indexes — neither covers the composite foreign key.
44        #[index]
45        parent_id: String,
46        #[index]
47        parent_revision: i64,
48
49        #[belongs_to(key = [parent_id, parent_revision], references = [id, revision])]
50        parent: toasty::Deferred<Parent>,
51    }
52
53    let err = test
54        .try_setup_db(models!(Parent, Child))
55        .await
56        .expect_err("schema verification should reject this layout");
57
58    assert!(
59        err.is_invalid_schema(),
60        "expected invalid_schema error, got: {err}",
61    );
62
63    let msg = err.to_string();
64    assert!(
65        msg.contains("parent_id") && msg.contains("parent_revision"),
66        "error should mention the foreign-key fields, got: {msg}",
67    );
68    assert!(
69        msg.contains("#[index(parent_id, parent_revision)]"),
70        "error should suggest adding a composite index, got: {msg}",
71    );
72    // Both FK fields are individually `#[index]`-annotated, so the verifier
73    // should detect that and explain why two single-column indexes don't
74    // satisfy a composite foreign key.
75    assert!(
76        msg.contains("each foreign-key field already has its own `#[index]`"),
77        "error should call out the per-field `#[index]` annotations, got: {msg}",
78    );
79
80    Ok(())
81}
82
83// =====================================================================
84// Tier A — fundamental CRUD on a composite-FK has_many. These mirror the
85// single-key tests in `relation_has_many_crud.rs`.
86// =====================================================================
87
88#[driver_test(id(ID), scenario(crate::scenarios::composite_has_many_belongs_to))]
89pub async fn composite_crud_user_todos(test: &mut Test) -> Result<()> {
90    let mut db = setup(test).await;
91
92    let user = User::create().name("User 1").exec(&mut db).await?;
93
94    assert_eq!(0, user.todos().exec(&mut db).await?.len());
95
96    let todo = user
97        .todos()
98        .create()
99        .title("hello world")
100        .exec(&mut db)
101        .await?;
102
103    // Find the todo by its composite key
104    let list = Todo::filter_by_user_id_and_id(user.id, todo.id)
105        .exec(&mut db)
106        .await?;
107    assert_eq!(1, list.len());
108    assert_eq!(todo.id, list[0].id);
109
110    // Find by partition (user_id) only
111    let list = Todo::filter_by_user_id(user.id).exec(&mut db).await?;
112    assert_eq!(1, list.len());
113    assert_eq!(todo.id, list[0].id);
114
115    // Find the user via the todo's FK
116    let user_reload = User::get_by_id(&mut db, &todo.user_id).await?;
117    assert_eq!(user.id, user_reload.id);
118
119    let mut created = HashMap::new();
120    let mut ids = vec![todo.id];
121    created.insert(todo.id, todo);
122
123    for i in 0..5 {
124        let title = format!("hello world {i}");
125        let todo = if i.is_even() {
126            user.todos().create().title(title).exec(&mut db).await?
127        } else {
128            Todo::create()
129                .user(&user)
130                .title(title)
131                .exec(&mut db)
132                .await?
133        };
134        ids.push(todo.id);
135        assert_none!(created.insert(todo.id, todo));
136    }
137
138    let list = user.todos().exec(&mut db).await?;
139    assert_eq!(6, list.len());
140
141    let loaded: HashMap<_, _> = list.into_iter().map(|t| (t.id, t)).collect();
142    assert_eq!(6, loaded.len());
143    for (id, expect) in &created {
144        assert_eq!(expect.title, loaded[id].title);
145    }
146
147    let list = Todo::filter_by_user_id(user.id).exec(&mut db).await?;
148    assert_eq!(6, list.len());
149
150    let user2 = User::create().name("User 2").exec(&mut db).await?;
151    assert_eq!(0, user2.todos().exec(&mut db).await?.len());
152
153    let u2_todo = user2
154        .todos()
155        .create()
156        .title("user 2 todo")
157        .exec(&mut db)
158        .await?;
159
160    for todo in user.todos().exec(&mut db).await? {
161        assert_ne!(u2_todo.id, todo.id);
162    }
163
164    // Delete a TODO by value (lookup by composite PK)
165    let todo = Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[0]).await?;
166    todo.delete().exec(&mut db).await?;
167    assert_err!(Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[0]).await);
168    assert_err!(user.todos().get_by_id(&mut db, &ids[0]).await);
169
170    // Delete a TODO by scope
171    user.todos()
172        .filter_by_id(ids[1])
173        .delete()
174        .exec(&mut db)
175        .await?;
176    assert_err!(Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[1]).await);
177    assert_err!(user.todos().get_by_id(&mut db, &ids[1]).await);
178
179    // Update a TODO via scope
180    user.todos()
181        .filter_by_id(ids[2])
182        .update()
183        .title("batch update 1")
184        .exec(&mut db)
185        .await?;
186    let todo = Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[2]).await?;
187    assert_eq!(todo.title, "batch update 1");
188
189    // Updating via the wrong user's scope must NOT touch the todo
190    user2
191        .todos()
192        .filter_by_id(ids[2])
193        .update()
194        .title("batch update 2")
195        .exec(&mut db)
196        .await?;
197    let todo = Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[2]).await?;
198    assert_eq!(todo.title, "batch update 1");
199
200    // Deleting the parent deletes its todos
201    let id = user.id;
202    user.delete().exec(&mut db).await?;
203    assert_err!(User::get_by_id(&mut db, &id).await);
204    assert_err!(Todo::get_by_user_id_and_id(&mut db, &id, &ids[2]).await);
205    Ok(())
206}
207
208#[driver_test(id(ID), scenario(crate::scenarios::composite_has_many_belongs_to))]
209pub async fn composite_has_many_insert_on_update(test: &mut Test) -> Result<()> {
210    let mut db = setup(test).await;
211
212    let mut user = User::create().name("Alice").exec(&mut db).await?;
213    assert!(user.todos().exec(&mut db).await?.is_empty());
214
215    user.update()
216        .name("Bob")
217        .todos(toasty::stmt::insert(Todo::create().title("change name")))
218        .exec(&mut db)
219        .await?;
220
221    assert_eq!("Bob", user.name);
222    let todos: Vec<_> = user.todos().exec(&mut db).await?;
223    assert_eq!(1, todos.len());
224    assert_eq!(todos[0].title, "change name");
225    Ok(())
226}
227
228#[driver_test(id(ID), scenario(crate::scenarios::composite_has_many_belongs_to))]
229pub async fn composite_scoped_find_by_id(test: &mut Test) -> Result<()> {
230    let mut db = setup(test).await;
231
232    let user1 = User::create().name("User 1").exec(&mut db).await?;
233    let user2 = User::create().name("User 2").exec(&mut db).await?;
234
235    let todo = user1
236        .todos()
237        .create()
238        .title("hello world")
239        .exec(&mut db)
240        .await?;
241
242    let reloaded = user1.todos().get_by_id(&mut db, &todo.id).await?;
243    assert_eq!(reloaded.id, todo.id);
244    assert_eq!(reloaded.title, todo.title);
245
246    // Other user's scope: must not find
247    assert_none!(
248        user2
249            .todos()
250            .filter_by_id(todo.id)
251            .first()
252            .exec(&mut db)
253            .await?
254    );
255
256    let reloaded = User::filter_by_id(user1.id)
257        .todos()
258        .get_by_id(&mut db, &todo.id)
259        .await?;
260    assert_eq!(reloaded.id, todo.id);
261
262    // Delete in the wrong scope is a no-op
263    user2
264        .todos()
265        .filter_by_id(todo.id)
266        .delete()
267        .exec(&mut db)
268        .await?;
269    let reloaded = user1.todos().get_by_id(&mut db, &todo.id).await?;
270    assert_eq!(reloaded.id, todo.id);
271    Ok(())
272}
273
274#[driver_test(id(ID), scenario(crate::scenarios::composite_has_many_belongs_to))]
275pub async fn composite_belongs_to_required(test: &mut Test) {
276    let mut db = setup(test).await;
277    assert_err!(Todo::create().title("orphan").exec(&mut db).await);
278}
279
280#[driver_test(id(ID), scenario(crate::scenarios::has_many_nullable_fk))]
281pub async fn composite_delete_when_belongs_to_optional(test: &mut Test) -> Result<()> {
282    let mut db = setup(test).await;
283
284    let user = User::create().exec(&mut db).await?;
285    let mut ids = vec![];
286
287    for _ in 0..3 {
288        let todo = user.todos().create().title("dummy").exec(&mut db).await?;
289        ids.push(todo.id);
290    }
291
292    user.delete().exec(&mut db).await?;
293
294    // Todos still exist; user_id is None
295    for id in ids {
296        let todo = Todo::get_by_id(&mut db, id).await?;
297        assert_none!(todo.user_id);
298    }
299    Ok(())
300}
301
302#[driver_test(scenario(crate::scenarios::composite_fk_has_many_belongs_to))]
303pub async fn composite_associate_new_user_with_todo_on_update_via_creation(
304    test: &mut Test,
305) -> Result<()> {
306    let mut db = setup(test).await;
307
308    let u1 = User::create()
309        .revision(1)
310        .name("User 1")
311        .todos([Todo::create().title("hello world")])
312        .exec(&mut db)
313        .await?;
314
315    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
316    assert_eq!(1, todos.len());
317    let mut todo = todos.into_iter().next().unwrap();
318
319    todo.update()
320        .user(User::create().revision(1).name("User 2"))
321        .exec(&mut db)
322        .await?;
323    Ok(())
324}
325
326#[driver_test(scenario(crate::scenarios::composite_fk_has_many_belongs_to))]
327pub async fn composite_associate_new_user_with_todo_on_update_query_via_creation(
328    test: &mut Test,
329) -> Result<()> {
330    let mut db = setup(test).await;
331
332    let u1 = User::create()
333        .revision(1)
334        .name("User 1")
335        .todos([Todo::create().title("a todo")])
336        .exec(&mut db)
337        .await?;
338
339    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
340    assert_eq!(1, todos.len());
341    let todo = todos.into_iter().next().unwrap();
342
343    Todo::filter_by_id(todo.id)
344        .update()
345        .user(User::create().revision(1).name("User 2"))
346        .exec(&mut db)
347        .await?;
348    Ok(())
349}
350
351#[driver_test(scenario(crate::scenarios::composite_fk_has_many_belongs_to))]
352pub async fn composite_assign_todo_that_already_has_user_on_create(test: &mut Test) -> Result<()> {
353    let mut db = setup(test).await;
354
355    let todo = Todo::create()
356        .title("a todo")
357        .user(User::create().revision(1).name("User 1"))
358        .exec(&mut db)
359        .await?;
360
361    let u1 = todo.user().exec(&mut db).await?;
362
363    let u2 = User::create()
364        .revision(1)
365        .name("User 2")
366        .todos([&todo])
367        .exec(&mut db)
368        .await?;
369
370    let todo_reload = Todo::get_by_id(&mut db, &todo.id).await?;
371    assert_eq!(u2.id, todo_reload.user_id);
372    assert_eq!(u2.revision, todo_reload.user_revision);
373
374    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
375    assert_eq!(0, todos.len());
376
377    let todos: Vec<_> = u2.todos().exec(&mut db).await?;
378    assert_eq!(1, todos.len());
379    assert_eq!(todo.id, todos[0].id);
380    Ok(())
381}
382
383#[driver_test(scenario(crate::scenarios::composite_fk_has_many_belongs_to))]
384pub async fn composite_assign_todo_that_already_has_user_on_update(test: &mut Test) -> Result<()> {
385    let mut db = setup(test).await;
386
387    let todo = Todo::create()
388        .title("a todo")
389        .user(User::create().revision(1).name("User 1"))
390        .exec(&mut db)
391        .await?;
392
393    let u1 = todo.user().exec(&mut db).await?;
394
395    let mut u2 = User::create()
396        .revision(1)
397        .name("User 2")
398        .exec(&mut db)
399        .await?;
400
401    u2.update()
402        .todos(toasty::stmt::insert(&todo))
403        .exec(&mut db)
404        .await?;
405
406    let todo_reload = Todo::get_by_id(&mut db, &todo.id).await?;
407    assert_eq!(u2.id, todo_reload.user_id);
408    assert_eq!(u2.revision, todo_reload.user_revision);
409
410    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
411    assert_eq!(0, todos.len());
412
413    let todos: Vec<_> = u2.todos().exec(&mut db).await?;
414    assert_eq!(1, todos.len());
415    assert_eq!(todo.id, todos[0].id);
416    Ok(())
417}
418
419#[driver_test(scenario(crate::scenarios::composite_fk_has_many_belongs_to))]
420pub async fn composite_assign_existing_user_to_todo(test: &mut Test) -> Result<()> {
421    let mut db = setup(test).await;
422
423    let mut todo = Todo::create()
424        .title("hello")
425        .user(User::create().revision(1).name("User 1"))
426        .exec(&mut db)
427        .await?;
428
429    let u1 = todo.user().exec(&mut db).await?;
430    let u2 = User::create()
431        .revision(1)
432        .name("User 2")
433        .exec(&mut db)
434        .await?;
435
436    todo.update().user(&u2).exec(&mut db).await?;
437
438    let todo_reload = Todo::get_by_id(&mut db, &todo.id).await?;
439    assert_eq!(u2.id, todo_reload.user_id);
440    assert_eq!(u2.revision, todo_reload.user_revision);
441
442    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
443    assert_eq!(0, todos.len());
444
445    let todos: Vec<_> = u2.todos().exec(&mut db).await?;
446    assert_eq!(1, todos.len());
447    assert_eq!(todo.id, todos[0].id);
448    Ok(())
449}
450
451#[driver_test(id(ID), scenario(crate::scenarios::composite_has_many_belongs_to))]
452pub async fn composite_assign_todo_to_user_on_update_query(test: &mut Test) -> Result<()> {
453    let mut db = setup(test).await;
454
455    let user = User::create().name("User 1").exec(&mut db).await?;
456
457    User::filter_by_id(user.id)
458        .update()
459        .todos(toasty::stmt::insert(Todo::create().title("hello")))
460        .exec(&mut db)
461        .await?;
462
463    let todos: Vec<_> = user.todos().exec(&mut db).await?;
464    assert_eq!(1, todos.len());
465    assert_eq!("hello", todos[0].title);
466    Ok(())
467}
468
469// =====================================================================
470// Tier B — batch / link / unlink. Mirrors `relation_has_many_batch_create.rs`
471// and `relation_has_many_link_unlink.rs`.
472// =====================================================================
473
474#[driver_test(id(ID), scenario(crate::scenarios::composite_has_many_belongs_to))]
475pub async fn composite_user_batch_create_todos_one_level(test: &mut Test) -> Result<()> {
476    let mut db = setup(test).await;
477
478    let user = User::create()
479        .name("Ann Chovey")
480        .todos([Todo::create().title("Make pizza")])
481        .exec(&mut db)
482        .await?;
483
484    assert_eq!(user.name, "Ann Chovey");
485
486    let todos: Vec<_> = user.todos().exec(&mut db).await?;
487    assert_eq!(1, todos.len());
488    assert_eq!("Make pizza", todos[0].title);
489
490    let todo = Todo::get_by_user_id_and_id(&mut db, &user.id, &todos[0].id).await?;
491    assert_eq!("Make pizza", todo.title);
492    Ok(())
493}
494
495#[driver_test(id(ID), scenario(crate::scenarios::composite_has_many_belongs_to))]
496pub async fn composite_user_batch_create_two_todos_simple(test: &mut Test) -> Result<()> {
497    let mut db = setup(test).await;
498
499    let user = User::create()
500        .name("Ann Chovey")
501        .todos([Todo::create().title("Make pizza")])
502        .todos([Todo::create().title("Sleep")])
503        .exec(&mut db)
504        .await?;
505
506    let todos: Vec<_> = user.todos().exec(&mut db).await?;
507    assert_eq!(2, todos.len());
508
509    let mut titles: Vec<_> = todos.iter().map(|t| &t.title[..]).collect();
510    titles.sort();
511    assert_eq!(titles, vec!["Make pizza", "Sleep"]);
512    Ok(())
513}
514
515#[driver_test(id(ID))]
516pub async fn composite_user_batch_create_todos_with_optional_field(test: &mut Test) -> Result<()> {
517    #[derive(Debug, toasty::Model)]
518    struct User {
519        #[key]
520        #[auto]
521        id: ID,
522
523        name: String,
524
525        #[has_many]
526        todos: toasty::Deferred<Vec<Todo>>,
527
528        // Optional field exercises the RETURNING/constantize path that
529        // regressed in #user_batch_create_todos_with_optional_field.
530        #[allow(dead_code)]
531        moto: Option<String>,
532    }
533
534    #[derive(Debug, toasty::Model)]
535    #[key(partition = user_id, local = id)]
536    struct Todo {
537        #[auto]
538        id: uuid::Uuid,
539
540        user_id: ID,
541
542        #[belongs_to(key = user_id, references = id)]
543        user: toasty::Deferred<User>,
544
545        title: String,
546    }
547
548    let mut db = test.setup_db(models!(User, Todo)).await;
549
550    let user = User::create()
551        .name("Ann Chovey")
552        .todos([Todo::create().title("Make pizza")])
553        .todos([Todo::create().title("Sleep")])
554        .exec(&mut db)
555        .await?;
556
557    let todos: Vec<_> = user.todos().exec(&mut db).await?;
558    assert_eq!(2, todos.len());
559
560    let mut titles: Vec<_> = todos.iter().map(|t| &t.title[..]).collect();
561    titles.sort();
562    assert_eq!(titles, vec!["Make pizza", "Sleep"]);
563    Ok(())
564}
565
566#[driver_test(id(ID), scenario(crate::scenarios::has_many_nullable_fk))]
567pub async fn composite_remove_add_single_relation_option_belongs_to(test: &mut Test) -> Result<()> {
568    let mut db = setup(test).await;
569
570    let user = User::create()
571        .todos([Todo::create().title("dummy")])
572        .todos([Todo::create().title("dummy")])
573        .exec(&mut db)
574        .await?;
575
576    let todos: Vec<_> = user.todos().exec(&mut db).await?;
577    assert_eq!(2, todos.len());
578
579    user.todos().remove(&mut db, &todos[0]).await?;
580
581    let todos_reloaded: Vec<_> = user.todos().exec(&mut db).await?;
582    assert_eq!(1, todos_reloaded.len());
583    assert_eq!(todos[1].id, todos_reloaded[0].id);
584
585    assert_err!(user.todos().get_by_id(&mut db, &todos[0].id).await);
586
587    let todo = Todo::get_by_id(&mut db, todos[0].id).await?;
588    assert_none!(todo.user_id);
589
590    user.todos().insert(&mut db, &todos[0]).await?;
591
592    let todos_reloaded: Vec<_> = user.todos().exec(&mut db).await?;
593    assert!(todos_reloaded.iter().any(|t| t.id == todos[0].id));
594    assert_ok!(user.todos().get_by_id(&mut db, todos[0].id).await);
595    Ok(())
596}
597
598#[driver_test(id(ID), scenario(crate::scenarios::composite_has_many_belongs_to))]
599pub async fn composite_add_remove_single_relation_required_belongs_to(
600    test: &mut Test,
601) -> Result<()> {
602    let mut db = setup(test).await;
603
604    let user = User::create().name("User 1").exec(&mut db).await?;
605
606    let t1 = user.todos().create().title("todo 1").exec(&mut db).await?;
607    let t2 = user.todos().create().title("todo 2").exec(&mut db).await?;
608    let t3 = user.todos().create().title("todo 3").exec(&mut db).await?;
609
610    let ids = vec![t1.id, t2.id, t3.id];
611
612    let todos_reloaded: Vec<_> = user.todos().exec(&mut db).await?;
613    assert_eq!(todos_reloaded.len(), 3);
614
615    for id in ids {
616        assert!(todos_reloaded.iter().any(|t| t.id == id));
617    }
618
619    // Unlinking a required belongs_to is a delete
620    user.todos().remove(&mut db, &todos_reloaded[0]).await?;
621
622    assert_err!(Todo::get_by_user_id_and_id(&mut db, &user.id, &todos_reloaded[0].id).await);
623
624    let todos_reloaded: Vec<_> = user.todos().exec(&mut db).await?;
625    assert_eq!(todos_reloaded.len(), 2);
626    Ok(())
627}
628
629#[driver_test(scenario(crate::scenarios::composite_fk_has_many_belongs_to))]
630pub async fn composite_reassign_relation_required_belongs_to(test: &mut Test) -> Result<()> {
631    let mut db = setup(test).await;
632
633    let u1 = User::create()
634        .revision(1)
635        .name("User 1")
636        .exec(&mut db)
637        .await?;
638    let u2 = User::create()
639        .revision(1)
640        .name("User 2")
641        .exec(&mut db)
642        .await?;
643
644    let t1 = u1.todos().create().title("a todo").exec(&mut db).await?;
645
646    u2.todos().insert(&mut db, &t1).await?;
647
648    assert!(u1.todos().exec(&mut db).await?.is_empty());
649
650    let todos = u2.todos().exec(&mut db).await?;
651    assert_eq!(1, todos.len());
652    assert_eq!(t1.id, todos[0].id);
653    Ok(())
654}
655
656#[driver_test(id(ID), scenario(crate::scenarios::has_many_nullable_fk))]
657pub async fn composite_add_remove_multiple_relation_option_belongs_to(
658    test: &mut Test,
659) -> Result<()> {
660    let mut db = setup(test).await;
661
662    let user = User::create().exec(&mut db).await?;
663
664    let t1 = Todo::create().title("dummy").exec(&mut db).await?;
665    let t2 = Todo::create().title("dummy").exec(&mut db).await?;
666    let t3 = Todo::create().title("dummy").exec(&mut db).await?;
667
668    let ids = vec![t1.id, t2.id, t3.id];
669
670    user.todos().insert(&mut db, &t1).await?;
671    user.todos().insert(&mut db, &t2).await?;
672    user.todos().insert(&mut db, &t3).await?;
673
674    let todos_reloaded: Vec<_> = user.todos().exec(&mut db).await?;
675    assert_eq!(todos_reloaded.len(), 3);
676
677    for id in ids {
678        assert!(todos_reloaded.iter().any(|t| t.id == id));
679    }
680    Ok(())
681}
682
683// =====================================================================
684// Tier C — preload (.include) over a composite FK. Mirrors
685// `relation_preload.rs`.
686// =====================================================================
687
688#[driver_test(id(ID), scenario(crate::scenarios::composite_has_many_belongs_to))]
689pub async fn composite_basic_has_many_and_belongs_to_preload(test: &mut Test) -> Result<()> {
690    let mut db = setup(test).await;
691
692    let user = User::create()
693        .name("Alice")
694        .todos([Todo::create().title("todo 1")])
695        .todos([Todo::create().title("todo 2")])
696        .todos([Todo::create().title("todo 3")])
697        .exec(&mut db)
698        .await?;
699
700    let user = User::filter_by_id(user.id)
701        .include(User::fields().todos())
702        .get(&mut db)
703        .await?;
704
705    assert_eq!(3, user.todos.get().len());
706
707    let id = user.todos.get()[0].id;
708    let user_id = user.todos.get()[0].user_id;
709
710    let todo = Todo::filter_by_user_id_and_id(user_id, id)
711        .include(Todo::fields().user())
712        .get(&mut db)
713        .await?;
714
715    assert_eq!(user.id, todo.user.get().id);
716    assert_eq!(user.id, todo.user_id);
717    Ok(())
718}
719
720/// Preload a `belongs_to` whose foreign key spans two columns. Seeds two
721/// users so a filter that doesn't actually compare child FK columns against
722/// parent key columns (e.g. one comparing the child's own columns to
723/// themselves, which matches every user) is caught by the assertions.
724#[driver_test(scenario(crate::scenarios::composite_fk_has_many_belongs_to))]
725pub async fn composite_fk_belongs_to_preload(test: &mut Test) -> Result<()> {
726    let mut db = setup(test).await;
727
728    let u1 = toasty::create!(User {
729        revision: 1,
730        name: "User 1"
731    })
732    .exec(&mut db)
733    .await?;
734    let u2 = toasty::create!(User {
735        revision: 1,
736        name: "User 2"
737    })
738    .exec(&mut db)
739    .await?;
740
741    for user in [&u1, &u2] {
742        let todo = toasty::create!(in user.todos() { title: "a todo" })
743            .exec(&mut db)
744            .await?;
745
746        let todo = Todo::filter_by_id(todo.id)
747            .include(Todo::fields().user())
748            .get(&mut db)
749            .await?;
750
751        assert_struct!(todo.user.get(), _ { id: == user.id, name: == user.name, .. });
752    }
753    Ok(())
754}
755
756#[driver_test(id(ID), scenario(crate::scenarios::composite_has_many_belongs_to))]
757pub async fn composite_preload_on_empty_query(test: &mut Test) -> Result<()> {
758    let mut db = setup(test).await;
759
760    User::create().name("Alice").exec(&mut db).await?;
761
762    let users: Vec<_> = User::filter(User::fields().name().eq("Nope"))
763        .include(User::fields().todos())
764        .exec(&mut db)
765        .await?;
766    assert!(users.is_empty());
767    Ok(())
768}
769
770#[driver_test(id(ID), scenario(crate::scenarios::has_many_nullable_fk))]
771pub async fn composite_preload_has_many_with_optional_belongs_to(test: &mut Test) -> Result<()> {
772    let mut db = setup(test).await;
773
774    let user = User::create()
775        .todos([Todo::create().title("alpha")])
776        .todos([Todo::create().title("beta")])
777        .exec(&mut db)
778        .await?;
779
780    let user = User::filter_by_id(user.id)
781        .include(User::fields().todos())
782        .get(&mut db)
783        .await?;
784
785    assert_eq!(2, user.todos.get().len());
786    Ok(())
787}
788
789#[driver_test(id(ID), scenario(crate::scenarios::composite_has_many_belongs_to))]
790pub async fn composite_nested_has_many_then_belongs_to_required(test: &mut Test) -> Result<()> {
791    let mut db = setup(test).await;
792
793    let user = User::create()
794        .name("Alice")
795        .todos([Todo::create().title("alpha")])
796        .todos([Todo::create().title("beta")])
797        .exec(&mut db)
798        .await?;
799
800    let user = User::filter_by_id(user.id)
801        .include(User::fields().todos().user())
802        .get(&mut db)
803        .await?;
804
805    let todos = user.todos.get();
806    assert_eq!(2, todos.len());
807    for todo in todos {
808        assert_eq!(user.id, todo.user.get().id);
809        assert_eq!("Alice", todo.user.get().name);
810    }
811    Ok(())
812}
813
814// =====================================================================
815// Tier D — has_one / belongs_to topologies with composite FK on the child.
816// =====================================================================
817
818#[driver_test(id(ID))]
819pub async fn composite_crud_has_one_required(test: &mut Test) -> Result<()> {
820    // User has a single auto-PK and a `has_one` Profile.
821    // Profile's PK is composite (`partition = user_id, local = id`), making the
822    // FK part of the row's key.
823    #[derive(Debug, toasty::Model)]
824    struct User {
825        #[key]
826        #[auto]
827        id: ID,
828
829        #[has_one]
830        profile: toasty::Deferred<Option<Profile>>,
831    }
832
833    #[derive(Debug, toasty::Model)]
834    #[key(partition = user_id, local = id)]
835    struct Profile {
836        #[auto]
837        id: uuid::Uuid,
838
839        user_id: ID,
840
841        #[belongs_to(key = user_id, references = id)]
842        user: toasty::Deferred<User>,
843
844        bio: String,
845    }
846
847    let mut db = test.setup_db(models!(User, Profile)).await;
848
849    let user = User::create()
850        .profile(Profile::create().bio("an apple a day"))
851        .exec(&mut db)
852        .await?;
853
854    let profile = user.profile().exec(&mut db).await?.unwrap();
855    assert_eq!(profile.bio, "an apple a day");
856
857    assert_eq!(user.id, profile.user().exec(&mut db).await?.id);
858
859    // Deleting the user also deletes the profile.
860    user.delete().exec(&mut db).await?;
861    assert_err!(Profile::get_by_user_id_and_id(&mut db, &profile.user_id, &profile.id).await);
862    Ok(())
863}
864
865// =====================================================================
866// Tier E — filters and projections through associations.
867// =====================================================================
868
869#[driver_test(id(ID), requires(sql))]
870pub async fn composite_filter_by_belongs_to_field(test: &mut Test) -> Result<()> {
871    #[derive(Debug, toasty::Model)]
872    struct User {
873        #[key]
874        #[auto]
875        id: ID,
876
877        name: String,
878    }
879
880    #[derive(Debug, toasty::Model)]
881    #[key(partition = user_id, local = id)]
882    struct Profile {
883        #[auto]
884        id: uuid::Uuid,
885
886        user_id: ID,
887
888        #[belongs_to(key = user_id, references = id)]
889        user: toasty::Deferred<User>,
890
891        bio: String,
892    }
893
894    let mut db = test.setup_db(models!(User, Profile)).await;
895
896    let alice = User::create().name("alice").exec(&mut db).await?;
897    let bob = User::create().name("bob").exec(&mut db).await?;
898
899    Profile::create()
900        .user(&alice)
901        .bio("alice's bio")
902        .exec(&mut db)
903        .await?;
904    Profile::create()
905        .user(&bob)
906        .bio("bob's bio")
907        .exec(&mut db)
908        .await?;
909
910    let profiles: Vec<Profile> = Profile::filter(Profile::fields().user().name().eq("alice"))
911        .exec(&mut db)
912        .await?;
913    assert_eq!(profiles.len(), 1);
914    assert_eq!(profiles[0].bio, "alice's bio");
915    Ok(())
916}
917
918#[driver_test(
919    id(ID),
920    requires(scan),
921    scenario(crate::scenarios::composite_has_many_belongs_to)
922)]
923pub async fn composite_filter_parent_by_child_field(test: &mut Test) -> Result<()> {
924    let mut db = setup(test).await;
925
926    let alice = User::create().name("Alice").exec(&mut db).await?;
927    let bob = User::create().name("Bob").exec(&mut db).await?;
928    let carol = User::create().name("Carol").exec(&mut db).await?;
929
930    alice.todos().create().title("urgent").exec(&mut db).await?;
931    bob.todos().create().title("later").exec(&mut db).await?;
932    carol.todos().create().title("urgent").exec(&mut db).await?;
933    carol.todos().create().title("later").exec(&mut db).await?;
934
935    let users: Vec<_> = User::filter(
936        User::fields()
937            .todos()
938            .any(Todo::fields().title().eq("urgent")),
939    )
940    .exec(&mut db)
941    .await?;
942
943    assert_eq_unordered!(users.iter().map(|u| &u.name[..]), ["Alice", "Carol"]);
944    Ok(())
945}
946
947#[driver_test(id(ID), requires(scan))]
948pub async fn composite_select_belongs_to_basic(test: &mut Test) -> Result<()> {
949    #[derive(Debug, toasty::Model)]
950    struct User {
951        #[key]
952        #[auto]
953        id: ID,
954        name: String,
955    }
956
957    #[derive(Debug, toasty::Model)]
958    #[key(partition = user_id, local = id)]
959    struct Post {
960        #[auto]
961        id: uuid::Uuid,
962        title: String,
963
964        user_id: ID,
965
966        #[belongs_to(key = user_id, references = id)]
967        author: toasty::Deferred<User>,
968    }
969
970    let mut db = test.setup_db(models!(User, Post)).await;
971
972    let alice = User::create().name("Alice").exec(&mut db).await?;
973    Post::create()
974        .title("Hello")
975        .author(&alice)
976        .exec(&mut db)
977        .await?;
978
979    let users: Vec<User> = Post::all()
980        .select(Post::fields().author())
981        .exec(&mut db)
982        .await?;
983
984    assert_eq!(users.len(), 1);
985    assert_eq!(users[0].name, "Alice");
986    Ok(())
987}