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(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(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(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(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(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(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(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(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]
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: uuid::Uuid,
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: uuid::Uuid,
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(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(&todos[0]).exec(&mut db).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(&todos[0]).exec(&mut db).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(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()
621        .remove(&todos_reloaded[0])
622        .exec(&mut db)
623        .await?;
624
625    assert_err!(Todo::get_by_user_id_and_id(&mut db, &user.id, &todos_reloaded[0].id).await);
626
627    let todos_reloaded: Vec<_> = user.todos().exec(&mut db).await?;
628    assert_eq!(todos_reloaded.len(), 2);
629    Ok(())
630}
631
632#[driver_test(scenario(crate::scenarios::composite_fk_has_many_belongs_to))]
633pub async fn composite_reassign_relation_required_belongs_to(test: &mut Test) -> Result<()> {
634    let mut db = setup(test).await;
635
636    let u1 = User::create()
637        .revision(1)
638        .name("User 1")
639        .exec(&mut db)
640        .await?;
641    let u2 = User::create()
642        .revision(1)
643        .name("User 2")
644        .exec(&mut db)
645        .await?;
646
647    let t1 = u1.todos().create().title("a todo").exec(&mut db).await?;
648
649    u2.todos().insert(&t1).exec(&mut db).await?;
650
651    assert!(u1.todos().exec(&mut db).await?.is_empty());
652
653    let todos = u2.todos().exec(&mut db).await?;
654    assert_eq!(1, todos.len());
655    assert_eq!(t1.id, todos[0].id);
656    Ok(())
657}
658
659#[driver_test(scenario(crate::scenarios::has_many_nullable_fk))]
660pub async fn composite_add_remove_multiple_relation_option_belongs_to(
661    test: &mut Test,
662) -> Result<()> {
663    let mut db = setup(test).await;
664
665    let user = User::create().exec(&mut db).await?;
666
667    let t1 = Todo::create().title("dummy").exec(&mut db).await?;
668    let t2 = Todo::create().title("dummy").exec(&mut db).await?;
669    let t3 = Todo::create().title("dummy").exec(&mut db).await?;
670
671    let ids = vec![t1.id, t2.id, t3.id];
672
673    user.todos().insert(&t1).exec(&mut db).await?;
674    user.todos().insert(&t2).exec(&mut db).await?;
675    user.todos().insert(&t3).exec(&mut db).await?;
676
677    let todos_reloaded: Vec<_> = user.todos().exec(&mut db).await?;
678    assert_eq!(todos_reloaded.len(), 3);
679
680    for id in ids {
681        assert!(todos_reloaded.iter().any(|t| t.id == id));
682    }
683    Ok(())
684}
685
686// =====================================================================
687// Tier C — preload (.include) over a composite FK. Mirrors
688// `relation_preload.rs`.
689// =====================================================================
690
691#[driver_test(scenario(crate::scenarios::composite_has_many_belongs_to))]
692pub async fn composite_basic_has_many_and_belongs_to_preload(test: &mut Test) -> Result<()> {
693    let mut db = setup(test).await;
694
695    let user = User::create()
696        .name("Alice")
697        .todos([Todo::create().title("todo 1")])
698        .todos([Todo::create().title("todo 2")])
699        .todos([Todo::create().title("todo 3")])
700        .exec(&mut db)
701        .await?;
702
703    let user = User::filter_by_id(user.id)
704        .include(User::fields().todos())
705        .get(&mut db)
706        .await?;
707
708    assert_eq!(3, user.todos.get().len());
709
710    let id = user.todos.get()[0].id;
711    let user_id = user.todos.get()[0].user_id;
712
713    let todo = Todo::filter_by_user_id_and_id(user_id, id)
714        .include(Todo::fields().user())
715        .get(&mut db)
716        .await?;
717
718    assert_eq!(user.id, todo.user.get().id);
719    assert_eq!(user.id, todo.user_id);
720    Ok(())
721}
722
723/// Preload a `belongs_to` whose foreign key spans two columns. Seeds two
724/// users so a filter that doesn't actually compare child FK columns against
725/// parent key columns (e.g. one comparing the child's own columns to
726/// themselves, which matches every user) is caught by the assertions.
727#[driver_test(scenario(crate::scenarios::composite_fk_has_many_belongs_to))]
728pub async fn composite_fk_belongs_to_preload(test: &mut Test) -> Result<()> {
729    let mut db = setup(test).await;
730
731    let u1 = toasty::create!(User {
732        revision: 1,
733        name: "User 1"
734    })
735    .exec(&mut db)
736    .await?;
737    let u2 = toasty::create!(User {
738        revision: 1,
739        name: "User 2"
740    })
741    .exec(&mut db)
742    .await?;
743
744    for user in [&u1, &u2] {
745        let todo = toasty::create!(in user.todos() { title: "a todo" })
746            .exec(&mut db)
747            .await?;
748
749        let todo = Todo::filter_by_id(todo.id)
750            .include(Todo::fields().user())
751            .get(&mut db)
752            .await?;
753
754        assert_struct!(todo.user.get(), { id: == user.id, name: == user.name });
755    }
756    Ok(())
757}
758
759#[driver_test(scenario(crate::scenarios::composite_has_many_belongs_to))]
760pub async fn composite_preload_on_empty_query(test: &mut Test) -> Result<()> {
761    let mut db = setup(test).await;
762
763    User::create().name("Alice").exec(&mut db).await?;
764
765    let users: Vec<_> = User::filter(User::fields().name().eq("Nope"))
766        .include(User::fields().todos())
767        .exec(&mut db)
768        .await?;
769    assert!(users.is_empty());
770    Ok(())
771}
772
773#[driver_test(scenario(crate::scenarios::has_many_nullable_fk))]
774pub async fn composite_preload_has_many_with_optional_belongs_to(test: &mut Test) -> Result<()> {
775    let mut db = setup(test).await;
776
777    let user = User::create()
778        .todos([Todo::create().title("alpha")])
779        .todos([Todo::create().title("beta")])
780        .exec(&mut db)
781        .await?;
782
783    let user = User::filter_by_id(user.id)
784        .include(User::fields().todos())
785        .get(&mut db)
786        .await?;
787
788    assert_eq!(2, user.todos.get().len());
789    Ok(())
790}
791
792#[driver_test(scenario(crate::scenarios::composite_has_many_belongs_to))]
793pub async fn composite_nested_has_many_then_belongs_to_required(test: &mut Test) -> Result<()> {
794    let mut db = setup(test).await;
795
796    let user = User::create()
797        .name("Alice")
798        .todos([Todo::create().title("alpha")])
799        .todos([Todo::create().title("beta")])
800        .exec(&mut db)
801        .await?;
802
803    let user = User::filter_by_id(user.id)
804        .include(User::fields().todos().user())
805        .get(&mut db)
806        .await?;
807
808    let todos = user.todos.get();
809    assert_eq!(2, todos.len());
810    for todo in todos {
811        assert_eq!(user.id, todo.user.get().id);
812        assert_eq!("Alice", todo.user.get().name);
813    }
814    Ok(())
815}
816
817// =====================================================================
818// Tier D — has_one / belongs_to topologies with composite FK on the child.
819// =====================================================================
820
821#[driver_test]
822pub async fn composite_crud_has_one_required(test: &mut Test) -> Result<()> {
823    // User has a single auto-PK and a `has_one` Profile.
824    // Profile's PK is composite (`partition = user_id, local = id`), making the
825    // FK part of the row's key.
826    #[derive(Debug, toasty::Model)]
827    struct User {
828        #[key]
829        #[auto]
830        id: uuid::Uuid,
831
832        #[has_one]
833        profile: toasty::Deferred<Option<Profile>>,
834    }
835
836    #[derive(Debug, toasty::Model)]
837    #[key(partition = user_id, local = id)]
838    struct Profile {
839        #[auto]
840        id: uuid::Uuid,
841
842        user_id: uuid::Uuid,
843
844        #[belongs_to(key = user_id, references = id)]
845        user: toasty::Deferred<User>,
846
847        bio: String,
848    }
849
850    let mut db = test.setup_db(models!(User, Profile)).await;
851
852    let user = User::create()
853        .profile(Profile::create().bio("an apple a day"))
854        .exec(&mut db)
855        .await?;
856
857    let profile = user.profile().exec(&mut db).await?.unwrap();
858    assert_eq!(profile.bio, "an apple a day");
859
860    assert_eq!(user.id, profile.user().exec(&mut db).await?.id);
861
862    // Deleting the user also deletes the profile.
863    user.delete().exec(&mut db).await?;
864    assert_err!(Profile::get_by_user_id_and_id(&mut db, &profile.user_id, &profile.id).await);
865    Ok(())
866}
867
868// =====================================================================
869// Tier E — filters and projections through associations.
870// =====================================================================
871
872#[driver_test(requires(sql))]
873pub async fn composite_filter_by_belongs_to_field(test: &mut Test) -> Result<()> {
874    #[derive(Debug, toasty::Model)]
875    struct User {
876        #[key]
877        #[auto]
878        id: uuid::Uuid,
879
880        name: String,
881    }
882
883    #[derive(Debug, toasty::Model)]
884    #[key(partition = user_id, local = id)]
885    struct Profile {
886        #[auto]
887        id: uuid::Uuid,
888
889        user_id: uuid::Uuid,
890
891        #[belongs_to(key = user_id, references = id)]
892        user: toasty::Deferred<User>,
893
894        bio: String,
895    }
896
897    let mut db = test.setup_db(models!(User, Profile)).await;
898
899    let alice = User::create().name("alice").exec(&mut db).await?;
900    let bob = User::create().name("bob").exec(&mut db).await?;
901
902    Profile::create()
903        .user(&alice)
904        .bio("alice's bio")
905        .exec(&mut db)
906        .await?;
907    Profile::create()
908        .user(&bob)
909        .bio("bob's bio")
910        .exec(&mut db)
911        .await?;
912
913    let profiles: Vec<Profile> = Profile::filter(Profile::fields().user().name().eq("alice"))
914        .exec(&mut db)
915        .await?;
916    assert_eq!(profiles.len(), 1);
917    assert_eq!(profiles[0].bio, "alice's bio");
918    Ok(())
919}
920
921#[driver_test(
922    requires(scan),
923    scenario(crate::scenarios::composite_has_many_belongs_to)
924)]
925pub async fn composite_filter_parent_by_child_field(test: &mut Test) -> Result<()> {
926    let mut db = setup(test).await;
927
928    let alice = User::create().name("Alice").exec(&mut db).await?;
929    let bob = User::create().name("Bob").exec(&mut db).await?;
930    let carol = User::create().name("Carol").exec(&mut db).await?;
931
932    alice.todos().create().title("urgent").exec(&mut db).await?;
933    bob.todos().create().title("later").exec(&mut db).await?;
934    carol.todos().create().title("urgent").exec(&mut db).await?;
935    carol.todos().create().title("later").exec(&mut db).await?;
936
937    let users: Vec<_> = User::filter(
938        User::fields()
939            .todos()
940            .any(Todo::fields().title().eq("urgent")),
941    )
942    .exec(&mut db)
943    .await?;
944
945    assert_eq_unordered!(users.iter().map(|u| &u.name[..]), ["Alice", "Carol"]);
946    Ok(())
947}
948
949#[driver_test(requires(scan))]
950pub async fn composite_select_belongs_to_basic(test: &mut Test) -> Result<()> {
951    #[derive(Debug, toasty::Model)]
952    struct User {
953        #[key]
954        #[auto]
955        id: uuid::Uuid,
956        name: String,
957    }
958
959    #[derive(Debug, toasty::Model)]
960    #[key(partition = user_id, local = id)]
961    struct Post {
962        #[auto]
963        id: uuid::Uuid,
964        title: String,
965
966        user_id: uuid::Uuid,
967
968        #[belongs_to(key = user_id, references = id)]
969        author: toasty::Deferred<User>,
970    }
971
972    let mut db = test.setup_db(models!(User, Post)).await;
973
974    let alice = User::create().name("Alice").exec(&mut db).await?;
975    Post::create()
976        .title("Hello")
977        .author(&alice)
978        .exec(&mut db)
979        .await?;
980
981    let users: Vec<User> = Post::all()
982        .select(Post::fields().author())
983        .exec(&mut db)
984        .await?;
985
986    assert_eq!(users.len(), 1);
987    assert_eq!(users[0].name, "Alice");
988    Ok(())
989}