toasty_driver_integration_suite/tests/
relation_has_many_crud.rs

1//! Test basic has_many associations without any preloading of associations
2//! during query time. All associations are accessed via queries on demand.
3
4use crate::prelude::*;
5use std::collections::HashMap;
6
7#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
8pub async fn crud_user_todos(test: &mut Test) -> Result<()> {
9    let mut db = setup(test).await;
10
11    // Create a user
12    let user = User::create().name("User 1").exec(&mut db).await?;
13
14    // No TODOs
15    assert_eq!(0, user.todos().exec(&mut db).await?.len());
16
17    // Create a Todo associated with the user
18    let todo = user
19        .todos()
20        .create()
21        .title("hello world")
22        .exec(&mut db)
23        .await?;
24
25    // Find the todo by ID
26    let list = Todo::filter_by_id(todo.id).exec(&mut db).await?;
27
28    assert_eq!(1, list.len());
29    assert_eq!(todo.id, list[0].id);
30
31    // Find the TODO by user ID
32    let list = Todo::filter_by_user_id(user.id).exec(&mut db).await?;
33
34    assert_eq!(1, list.len());
35    assert_eq!(todo.id, list[0].id);
36
37    // Find the User using the Todo
38    let user_reload = User::get_by_id(&mut db, &todo.user_id).await?;
39    assert_eq!(user.id, user_reload.id);
40
41    let mut created = HashMap::new();
42    let mut ids = vec![todo.id];
43    created.insert(todo.id, todo);
44
45    // Create a few more TODOs
46    for i in 0..5 {
47        let title = format!("hello world {i}");
48
49        let todo = if i.is_even() {
50            // Create via user
51            user.todos().create().title(title).exec(&mut db).await?
52        } else {
53            // Create via todo builder
54            Todo::create()
55                .user(&user)
56                .title(title)
57                .exec(&mut db)
58                .await?
59        };
60
61        ids.push(todo.id);
62        assert_none!(created.insert(todo.id, todo));
63    }
64
65    // Load all TODOs
66    let list = user.todos().exec(&mut db).await?;
67
68    assert_eq!(6, list.len());
69
70    let loaded: HashMap<_, _> = list.into_iter().map(|todo| (todo.id, todo)).collect();
71    assert_eq!(6, loaded.len());
72
73    for (id, expect) in &created {
74        assert_eq!(expect.title, loaded[id].title);
75    }
76
77    // Find all TODOs by user (using the belongs_to queries)
78    let list = Todo::filter_by_user_id(user.id).exec(&mut db).await?;
79    assert_eq!(6, list.len());
80
81    let by_id: HashMap<_, _> = list.into_iter().map(|todo| (todo.id, todo)).collect();
82
83    assert_eq!(6, by_id.len());
84
85    for (id, expect) in by_id {
86        assert_eq!(expect.title, loaded[&id].title);
87    }
88
89    // Create a second user
90    let user2 = User::create().name("User 2").exec(&mut db).await?;
91
92    // No TODOs associated with `user2`
93    assert_eq!(0, user2.todos().exec(&mut db).await?.len());
94
95    // Create a TODO for user2
96    let u2_todo = user2
97        .todos()
98        .create()
99        .title("user 2 todo")
100        .exec(&mut db)
101        .await?;
102
103    {
104        let u1_todos = user.todos().exec(&mut db).await?;
105
106        for todo in u1_todos {
107            assert_ne!(u2_todo.id, todo.id);
108        }
109    }
110
111    // Delete a TODO by value
112    let todo = Todo::get_by_id(&mut db, &ids[0]).await?;
113    todo.delete().exec(&mut db).await?;
114
115    // Can no longer get the todo via id
116    assert_err!(Todo::get_by_id(&mut db, &ids[0]).await);
117
118    // Can no longer get the todo scoped
119    assert_err!(user.todos().get_by_id(&mut db, &ids[0]).await);
120
121    // Delete a TODO by scope
122    user.todos()
123        .filter_by_id(ids[1])
124        .delete()
125        .exec(&mut db)
126        .await?;
127
128    // Can no longer get the todo via id
129    assert_err!(Todo::get_by_id(&mut db, &ids[1]).await);
130
131    // Can no longer get the todo scoped
132    assert_err!(user.todos().get_by_id(&mut db, &ids[1]).await);
133
134    // Successfuly a todo by scope
135    user.todos()
136        .filter_by_id(ids[2])
137        .update()
138        .title("batch update 1")
139        .exec(&mut db)
140        .await?;
141
142    let todo = Todo::get_by_id(&mut db, &ids[2]).await?;
143    assert_eq!(todo.title, "batch update 1");
144
145    // Now fail to update it by scoping by other user
146    user2
147        .todos()
148        .filter_by_id(ids[2])
149        .update()
150        .title("batch update 2")
151        .exec(&mut db)
152        .await?;
153
154    let todo = Todo::get_by_id(&mut db, &ids[2]).await?;
155    assert_eq!(todo.title, "batch update 1");
156
157    let id = user.id;
158
159    // Delete the user and associated TODOs are deleted
160    user.delete().exec(&mut db).await?;
161    assert_err!(User::get_by_id(&mut db, &id).await);
162    assert_err!(Todo::get_by_id(&mut db, &ids[2]).await);
163    Ok(())
164}
165
166#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
167pub async fn has_many_insert_on_update(test: &mut Test) -> Result<()> {
168    let mut db = setup(test).await;
169
170    // Create a user, no TODOs
171    let mut user = User::create().name("Alice").exec(&mut db).await?;
172    assert!(user.todos().exec(&mut db).await?.is_empty());
173
174    // Update the user and create a todo in a batch
175    user.update()
176        .name("Bob")
177        .todos(toasty::stmt::insert(Todo::create().title("change name")))
178        .exec(&mut db)
179        .await?;
180
181    assert_eq!("Bob", user.name);
182    let todos: Vec<_> = user.todos().exec(&mut db).await?;
183    assert_eq!(1, todos.len());
184    assert_eq!(todos[0].title, "change name");
185    Ok(())
186}
187
188#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
189pub async fn scoped_find_by_id(test: &mut Test) -> Result<()> {
190    let mut db = setup(test).await;
191
192    // Create a couple of users
193    let user1 = User::create().name("User 1").exec(&mut db).await?;
194    let user2 = User::create().name("User 2").exec(&mut db).await?;
195
196    // Create a todo
197    let todo = user1
198        .todos()
199        .create()
200        .title("hello world")
201        .exec(&mut db)
202        .await?;
203
204    // Find it scoped by user1
205    let reloaded = user1.todos().get_by_id(&mut db, &todo.id).await?;
206    assert_eq!(reloaded.id, todo.id);
207    assert_eq!(reloaded.title, todo.title);
208
209    // Trying to find the same todo scoped by user2 is missing
210    assert_none!(
211        user2
212            .todos()
213            .filter_by_id(todo.id)
214            .first()
215            .exec(&mut db)
216            .await?
217    );
218
219    let reloaded = User::filter_by_id(user1.id)
220        .todos()
221        .get_by_id(&mut db, &todo.id)
222        .await?;
223
224    assert_eq!(reloaded.id, todo.id);
225    assert_eq!(reloaded.title, todo.title);
226
227    // Deleting the TODO from the user 2 scope fails
228    user2
229        .todos()
230        .filter_by_id(todo.id)
231        .delete()
232        .exec(&mut db)
233        .await?;
234    let reloaded = user1.todos().get_by_id(&mut db, &todo.id).await?;
235    assert_eq!(reloaded.id, todo.id);
236    Ok(())
237}
238
239// The has_many association uses the target's primary key as the association's
240// foreign key. In this case, the relation's query should not be duplicated.
241#[driver_test(id(ID))]
242pub async fn has_many_on_target_pk(_test: &mut Test) {}
243
244// The target model has an explicit index on (FK, PK). In this case, the query
245// generated by the (FK, PK) pair should not be duplicated by the relation.
246#[driver_test(id(ID))]
247pub async fn has_many_when_target_indexes_fk_and_pk(_test: &mut Test) {}
248
249// When the FK is composite, things should still work
250#[driver_test(id(ID))]
251pub async fn has_many_when_fk_is_composite(test: &mut Test) -> Result<()> {
252    #[derive(Debug, toasty::Model)]
253    struct User {
254        #[key]
255        #[auto]
256        id: ID,
257
258        #[has_many]
259        todos: toasty::HasMany<Todo>,
260    }
261
262    #[derive(Debug, toasty::Model)]
263    #[key(partition = user_id, local = id)]
264    struct Todo {
265        #[auto]
266        id: uuid::Uuid,
267
268        user_id: ID,
269
270        #[belongs_to(key = user_id, references = id)]
271        user: toasty::BelongsTo<User>,
272
273        title: String,
274    }
275
276    let mut db = test.setup_db(models!(User, Todo)).await;
277
278    // Create a user
279    let user = User::create().exec(&mut db).await?;
280
281    // No TODOs
282    assert_eq!(0, user.todos().exec(&mut db).await?.len());
283
284    // Create a Todo associated with the user
285    let todo = user
286        .todos()
287        .create()
288        .title("hello world")
289        .exec(&mut db)
290        .await?;
291
292    // Find the todo by ID
293    let list = Todo::filter_by_user_id_and_id(user.id, todo.id)
294        .exec(&mut db)
295        .await?;
296
297    assert_eq!(1, list.len());
298    assert_eq!(todo.id, list[0].id);
299
300    // Find the TODO by user ID
301    let list = Todo::filter_by_user_id(user.id).exec(&mut db).await?;
302
303    assert_eq!(1, list.len());
304    assert_eq!(todo.id, list[0].id);
305
306    let mut created = HashMap::new();
307    let mut ids = vec![todo.id];
308    created.insert(todo.id, todo);
309
310    // Create a few more TODOs
311    for i in 0..5 {
312        let title = format!("hello world {i}");
313
314        let todo = if i.is_even() {
315            // Create via user
316            user.todos().create().title(title).exec(&mut db).await?
317        } else {
318            // Create via todo builder
319            Todo::create()
320                .user(&user)
321                .title(title)
322                .exec(&mut db)
323                .await?
324        };
325
326        ids.push(todo.id);
327        assert_none!(created.insert(todo.id, todo));
328    }
329
330    // Load all TODOs
331    let list = user.todos().exec(&mut db).await?;
332
333    assert_eq!(6, list.len());
334
335    let loaded: HashMap<_, _> = list.into_iter().map(|todo| (todo.id, todo)).collect();
336    assert_eq!(6, loaded.len());
337
338    for (id, expect) in &created {
339        assert_eq!(expect.title, loaded[id].title);
340    }
341
342    // Find all TODOs by user (using the belongs_to queries)
343    let list = Todo::filter_by_user_id(user.id).exec(&mut db).await?;
344    assert_eq!(6, list.len());
345
346    let by_id: HashMap<_, _> = list.into_iter().map(|todo| (todo.id, todo)).collect();
347
348    assert_eq!(6, by_id.len());
349
350    for (id, expect) in by_id {
351        assert_eq!(expect.title, loaded[&id].title);
352    }
353
354    // Create a second user
355    let user2 = User::create().exec(&mut db).await?;
356
357    // No TODOs associated with `user2`
358    assert_eq!(0, user2.todos().exec(&mut db).await?.len());
359
360    // Create a TODO for user2
361    let u2_todo = user2
362        .todos()
363        .create()
364        .title("user 2 todo")
365        .exec(&mut db)
366        .await?;
367
368    let u1_todos = user.todos().exec(&mut db).await?;
369
370    for todo in u1_todos {
371        assert_ne!(u2_todo.id, todo.id);
372    }
373
374    // Delete a TODO by value
375    let todo = Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[0]).await?;
376    todo.delete().exec(&mut db).await?;
377
378    // Can no longer get the todo via id
379    assert_err!(Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[0]).await);
380
381    // Can no longer get the todo scoped
382    assert_err!(user.todos().get_by_id(&mut db, &ids[0]).await);
383
384    // Delete a TODO by scope
385    user.todos()
386        .filter_by_id(ids[1])
387        .delete()
388        .exec(&mut db)
389        .await?;
390
391    // Can no longer get the todo via id
392    assert_err!(Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[1]).await);
393
394    // Can no longer get the todo scoped
395    assert_err!(user.todos().get_by_id(&mut db, &ids[1]).await);
396
397    // Successfuly a todo by scope
398    user.todos()
399        .filter_by_id(ids[2])
400        .update()
401        .title("batch update 1")
402        .exec(&mut db)
403        .await?;
404    let todo = Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[2]).await?;
405    assert_eq!(todo.title, "batch update 1");
406
407    // Now fail to update it by scoping by other user
408    user2
409        .todos()
410        .filter_by_id(ids[2])
411        .update()
412        .title("batch update 2")
413        .exec(&mut db)
414        .await?;
415    let todo = Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[2]).await?;
416    assert_eq!(todo.title, "batch update 1");
417    Ok(())
418}
419
420// When the PK is composite, things should still work
421#[driver_test(id(ID))]
422pub async fn has_many_when_pk_is_composite(_test: &mut Test) {}
423
424// When both the FK and PK are composite, things should still work
425#[driver_test(id(ID))]
426pub async fn has_many_when_fk_and_pk_are_composite(_test: &mut Test) {}
427
428#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
429pub async fn belongs_to_required(test: &mut Test) {
430    let mut db = setup(test).await;
431
432    assert_err!(Todo::create().exec(&mut db).await);
433}
434
435#[driver_test(id(ID))]
436pub async fn delete_when_belongs_to_optional(test: &mut Test) -> Result<()> {
437    #[derive(Debug, toasty::Model)]
438    struct User {
439        #[key]
440        #[auto]
441        id: ID,
442
443        #[has_many]
444        todos: toasty::HasMany<Todo>,
445    }
446
447    #[derive(Debug, toasty::Model)]
448    struct Todo {
449        #[key]
450        #[auto]
451        id: ID,
452
453        #[index]
454        user_id: Option<ID>,
455
456        #[belongs_to(key = user_id, references = id)]
457        user: toasty::BelongsTo<Option<User>>,
458    }
459
460    let mut db = test.setup_db(models!(User, Todo)).await;
461
462    let user = User::create().exec(&mut db).await?;
463    let mut ids = vec![];
464
465    for _ in 0..3 {
466        let todo = user.todos().create().exec(&mut db).await?;
467        ids.push(todo.id);
468    }
469
470    // Delete the user
471    user.delete().exec(&mut db).await?;
472
473    // All the todos still exist and `user` is set to `None`.
474    for id in ids {
475        let todo = Todo::get_by_id(&mut db, id).await?;
476        assert_none!(todo.user_id);
477    }
478
479    // Deleting a user leaves the todo in place.
480    Ok(())
481}
482
483#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
484pub async fn associate_new_user_with_todo_on_update_via_creation(test: &mut Test) -> Result<()> {
485    let mut db = setup(test).await;
486
487    // Create a user with a todo
488    let u1 = User::create()
489        .name("User 1")
490        .todo(Todo::create().title("hello world"))
491        .exec(&mut db)
492        .await?;
493
494    // Get the todo
495    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
496    assert_eq!(1, todos.len());
497    let mut todo = todos.into_iter().next().unwrap();
498
499    todo.update()
500        .user(User::create().name("User 2"))
501        .exec(&mut db)
502        .await?;
503    Ok(())
504}
505
506#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
507pub async fn associate_new_user_with_todo_on_update_query_via_creation(
508    test: &mut Test,
509) -> Result<()> {
510    let mut db = setup(test).await;
511
512    // Create a user with a todo
513    let u1 = User::create()
514        .name("User 1")
515        .todo(Todo::create().title("a todo"))
516        .exec(&mut db)
517        .await?;
518
519    // Get the todo
520    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
521    assert_eq!(1, todos.len());
522    let todo = todos.into_iter().next().unwrap();
523
524    Todo::filter_by_id(todo.id)
525        .update()
526        .user(User::create().name("User 2"))
527        .exec(&mut db)
528        .await?;
529    Ok(())
530}
531
532#[driver_test(id(ID))]
533#[should_panic]
534pub async fn update_user_with_null_todo_is_err(test: &mut Test) -> Result<()> {
535    #[derive(Debug, toasty::Model)]
536    struct User {
537        #[key]
538        #[auto]
539        id: ID,
540
541        #[has_many]
542        todos: toasty::HasMany<Todo>,
543    }
544
545    #[derive(Debug, toasty::Model)]
546    struct Todo {
547        #[key]
548        #[auto]
549        id: ID,
550
551        #[index]
552        user_id: ID,
553
554        #[belongs_to(key = user_id, references = id)]
555        user: toasty::BelongsTo<User>,
556    }
557
558    use toasty::stmt::{self, IntoExpr};
559
560    let mut db = test.setup_db(models!(User, Todo)).await;
561
562    // Create a user with a todo
563    let u1 = User::create().todo(Todo::create()).exec(&mut db).await?;
564
565    // Get the todo
566    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
567    assert_eq!(1, todos.len());
568    let todo = todos.into_iter().next().unwrap();
569
570    // Updating the todo w/ null is an error. Thus requires a bit of a hack to make work
571    let mut stmt: stmt::Update<Todo> =
572        stmt::Update::new(stmt::Query::from_expr((&todo).into_expr()));
573    stmt.set(2, toasty_core::stmt::Value::Null);
574    stmt.exec(&mut db).await?;
575
576    // User is not deleted
577    let u1_reloaded = User::get_by_id(&mut db, &u1.id).await?;
578    assert_eq!(u1_reloaded.id, u1.id);
579    Ok(())
580}
581
582#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
583pub async fn assign_todo_that_already_has_user_on_create(test: &mut Test) -> Result<()> {
584    let mut db = setup(test).await;
585
586    let todo = Todo::create()
587        .title("a todo")
588        .user(User::create().name("User 1"))
589        .exec(&mut db)
590        .await?;
591
592    let u1 = todo.user().exec(&mut db).await?;
593
594    let u2 = User::create()
595        .name("User 2")
596        .todo(&todo)
597        .exec(&mut db)
598        .await?;
599
600    let todo_reload = Todo::get_by_id(&mut db, &todo.id).await?;
601
602    assert_eq!(u2.id, todo_reload.user_id);
603
604    // First user has no todos
605    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
606    assert_eq!(0, todos.len());
607
608    // Second user has the todo
609    let todos: Vec<_> = u2.todos().exec(&mut db).await?;
610    assert_eq!(1, todos.len());
611    assert_eq!(todo.id, todos[0].id);
612    Ok(())
613}
614
615#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
616pub async fn assign_todo_that_already_has_user_on_update(test: &mut Test) -> Result<()> {
617    let mut db = setup(test).await;
618
619    let todo = Todo::create()
620        .title("a todo")
621        .user(User::create().name("User 1"))
622        .exec(&mut db)
623        .await?;
624
625    let u1 = todo.user().exec(&mut db).await?;
626
627    let mut u2 = User::create().name("User 2").exec(&mut db).await?;
628
629    // Update the user
630    u2.update()
631        .todos(toasty::stmt::insert(&todo))
632        .exec(&mut db)
633        .await?;
634
635    let todo_reload = Todo::get_by_id(&mut db, &todo.id).await?;
636
637    assert_eq!(u2.id, todo_reload.user_id);
638
639    // First user has no todos
640    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
641    assert_eq!(0, todos.len());
642
643    // Second user has the todo
644    let todos: Vec<_> = u2.todos().exec(&mut db).await?;
645    assert_eq!(1, todos.len());
646    assert_eq!(todo.id, todos[0].id);
647    Ok(())
648}
649
650#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
651pub async fn assign_existing_user_to_todo(test: &mut Test) -> Result<()> {
652    let mut db = setup(test).await;
653
654    let mut todo = Todo::create()
655        .title("hello")
656        .user(User::create().name("User 1"))
657        .exec(&mut db)
658        .await?;
659
660    let u1 = todo.user().exec(&mut db).await?;
661
662    let u2 = User::create().name("User 2").exec(&mut db).await?;
663
664    // Update the todo
665    todo.update().user(&u2).exec(&mut db).await?;
666
667    let todo_reload = Todo::get_by_id(&mut db, &todo.id).await?;
668
669    assert_eq!(u2.id, todo_reload.user_id);
670
671    // First user has no todos
672    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
673    assert_eq!(0, todos.len());
674
675    // Second user has the todo
676    let todos: Vec<_> = u2.todos().exec(&mut db).await?;
677    assert_eq!(1, todos.len());
678    assert_eq!(todo.id, todos[0].id);
679    Ok(())
680}
681
682#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
683pub async fn assign_todo_to_user_on_update_query(test: &mut Test) -> Result<()> {
684    let mut db = setup(test).await;
685
686    let user = User::create().name("User 1").exec(&mut db).await?;
687
688    User::filter_by_id(user.id)
689        .update()
690        .todos(toasty::stmt::insert(Todo::create().title("hello")))
691        .exec(&mut db)
692        .await?;
693
694    let todos: Vec<_> = user.todos().exec(&mut db).await?;
695    assert_eq!(1, todos.len());
696    assert_eq!("hello", todos[0].title);
697    Ok(())
698}
699
700#[driver_test(id(ID))]
701pub async fn has_many_when_fk_is_composite_with_snippets(test: &mut Test) -> Result<()> {
702    #[derive(Debug, toasty::Model)]
703    struct User {
704        #[key]
705        #[auto]
706        id: ID,
707
708        #[has_many]
709        todos: toasty::HasMany<Todo>,
710    }
711
712    #[derive(Debug, toasty::Model)]
713    #[key(partition = user_id, local = id)]
714    struct Todo {
715        #[auto]
716        id: uuid::Uuid,
717
718        user_id: ID,
719
720        #[belongs_to(key = user_id, references = id)]
721        user: toasty::BelongsTo<User>,
722
723        title: String,
724    }
725
726    let mut db = test.setup_db(models!(User, Todo)).await;
727
728    // Create users
729    let user1 = User::create().exec(&mut db).await?;
730    let user2 = User::create().exec(&mut db).await?;
731
732    // Create a Todo associated with the user
733    user1
734        .todos()
735        .create()
736        .title("hello world")
737        .exec(&mut db)
738        .await?;
739
740    let todo2 = user2
741        .todos()
742        .create()
743        .title("hello world")
744        .exec(&mut db)
745        .await?;
746
747    // Update the Todos with the snippets
748    Todo::update_by_user_id(user1.id)
749        .title("Title 2")
750        .exec(&mut db)
751        .await?;
752
753    let todo = Todo::get_by_user_id(&mut db, user1.id).await?;
754    assert!(todo.title == "Title 2");
755
756    Todo::update_by_user_id_and_id(user2.id, todo2.id)
757        .title("Title 3")
758        .exec(&mut db)
759        .await?;
760
761    let todo = Todo::get_by_user_id_and_id(&mut db, user2.id, todo2.id).await?;
762    assert!(todo.title == "Title 3");
763
764    // Delete the Todos with the snippets
765    Todo::delete_by_user_id(&mut db, user1.id).await?;
766    assert_err!(Todo::get_by_user_id(&mut db, user1.id).await);
767
768    Todo::delete_by_user_id_and_id(&mut db, user2.id, todo2.id).await?;
769    assert_err!(Todo::get_by_user_id_and_id(&mut db, user2.id, todo2.id).await);
770
771    Ok(())
772}