Skip to main content

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 hashbrown::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/// `stmt::apply([])` on a has-many is a no-op: the surface API's empty
189/// Apply loop adds no entry to the assignments map, so the relation
190/// field is treated as unchanged. Run alongside a separate scalar
191/// change because the engine verifier rejects updates with no
192/// assignments at all.
193#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
194pub async fn has_many_apply_empty_is_noop(test: &mut Test) -> Result<()> {
195    let mut db = setup(test).await;
196
197    let mut user = User::create().name("Alice").exec(&mut db).await?;
198    user.todos()
199        .create()
200        .title("existing")
201        .exec(&mut db)
202        .await?;
203
204    user.update()
205        .name("Bob")
206        .todos(toasty::stmt::apply::<toasty::stmt::List<Todo>>([]))
207        .exec(&mut db)
208        .await?;
209
210    assert_eq!(user.name, "Bob");
211    let todos: Vec<_> = user.todos().exec(&mut db).await?;
212    assert_eq!(todos.len(), 1);
213    assert_eq!(todos[0].title, "existing");
214    Ok(())
215}
216
217#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
218pub async fn has_many_apply_multiple_inserts(test: &mut Test) -> Result<()> {
219    let mut db = setup(test).await;
220
221    let mut user = User::create().name("Alice").exec(&mut db).await?;
222
223    user.update()
224        .todos(toasty::stmt::apply([
225            toasty::stmt::insert(Todo::create().title("Buy groceries")),
226            toasty::stmt::insert(Todo::create().title("Walk the dog")),
227        ]))
228        .exec(&mut db)
229        .await?;
230
231    let mut titles: Vec<_> = user
232        .todos()
233        .exec(&mut db)
234        .await?
235        .into_iter()
236        .map(|t| t.title)
237        .collect();
238    titles.sort();
239    assert_eq!(titles, ["Buy groceries", "Walk the dog"]);
240    Ok(())
241}
242
243/// Sanity check for plain `update().todos(stmt::remove(..))` — no
244/// `apply` involved. With a required FK, Remove deletes the child row.
245#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
246pub async fn has_many_update_remove(test: &mut Test) -> Result<()> {
247    let mut db = setup(test).await;
248
249    let mut user = User::create().name("Alice").exec(&mut db).await?;
250    let old_todo = user.todos().create().title("old").exec(&mut db).await?;
251
252    user.update()
253        .todos(toasty::stmt::remove(&old_todo))
254        .exec(&mut db)
255        .await?;
256
257    assert_eq!(0, user.todos().exec(&mut db).await?.len());
258    Ok(())
259}
260
261/// `stmt::apply([insert(..), remove(..)])` mixes Insert and Remove on a
262/// has-many in one update. Each entry dispatches as its own Mutation:
263/// the Insert associates the new child; the Remove dissociates the old
264/// one (and for a required FK, deletes it).
265#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
266pub async fn has_many_apply_insert_and_remove(test: &mut Test) -> Result<()> {
267    let mut db = setup(test).await;
268
269    let mut user = User::create().name("Alice").exec(&mut db).await?;
270    let old_todo = user.todos().create().title("old").exec(&mut db).await?;
271
272    user.update()
273        .todos(toasty::stmt::apply([
274            toasty::stmt::insert(Todo::create().title("new")),
275            toasty::stmt::remove(&old_todo),
276        ]))
277        .exec(&mut db)
278        .await?;
279
280    let titles: Vec<_> = user
281        .todos()
282        .exec(&mut db)
283        .await?
284        .into_iter()
285        .map(|t| t.title)
286        .collect();
287    assert_eq!(titles, ["new"]);
288    Ok(())
289}
290
291/// `stmt::apply([remove(..), insert(..)])` — the reverse of
292/// `has_many_apply_insert_and_remove`. `flatten_relation_batch` always
293/// emits the merged Insert first, so the final state is order-independent:
294/// the new child is associated and the old one is removed.
295#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
296pub async fn has_many_apply_remove_then_insert(test: &mut Test) -> Result<()> {
297    let mut db = setup(test).await;
298
299    let mut user = User::create().name("Alice").exec(&mut db).await?;
300    let old_todo = user.todos().create().title("old").exec(&mut db).await?;
301
302    user.update()
303        .todos(toasty::stmt::apply([
304            toasty::stmt::remove(&old_todo),
305            toasty::stmt::insert(Todo::create().title("new")),
306        ]))
307        .exec(&mut db)
308        .await?;
309
310    let titles: Vec<_> = user
311        .todos()
312        .exec(&mut db)
313        .await?
314        .into_iter()
315        .map(|t| t.title)
316        .collect();
317    assert_eq!(titles, ["new"]);
318    Ok(())
319}
320
321/// `stmt::apply([insert(a), insert(b), remove(c)])` — multiple inserts
322/// merge into one multi-row INSERT, dispatched alongside a separate
323/// Remove. Exercises the Insert-merge path plus a sibling disassociate.
324#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
325pub async fn has_many_apply_two_inserts_and_remove(test: &mut Test) -> Result<()> {
326    let mut db = setup(test).await;
327
328    let mut user = User::create().name("Alice").exec(&mut db).await?;
329    let old_todo = user.todos().create().title("old").exec(&mut db).await?;
330
331    user.update()
332        .todos(toasty::stmt::apply([
333            toasty::stmt::insert(Todo::create().title("a")),
334            toasty::stmt::insert(Todo::create().title("b")),
335            toasty::stmt::remove(&old_todo),
336        ]))
337        .exec(&mut db)
338        .await?;
339
340    let mut titles: Vec<_> = user
341        .todos()
342        .exec(&mut db)
343        .await?
344        .into_iter()
345        .map(|t| t.title)
346        .collect();
347    titles.sort();
348    assert_eq!(titles, ["a", "b"]);
349    Ok(())
350}
351
352/// `stmt::apply([remove(a), remove(b)])` — only disassociations, no
353/// Insert. `flatten_relation_batch` pushes no merged Insert, so both
354/// entries dispatch as standalone Disassociate mutations.
355#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
356pub async fn has_many_apply_multiple_removes(test: &mut Test) -> Result<()> {
357    let mut db = setup(test).await;
358
359    let mut user = User::create().name("Alice").exec(&mut db).await?;
360    let t1 = user.todos().create().title("t1").exec(&mut db).await?;
361    let t2 = user.todos().create().title("t2").exec(&mut db).await?;
362    let t3 = user.todos().create().title("keep").exec(&mut db).await?;
363
364    user.update()
365        .todos(toasty::stmt::apply([
366            toasty::stmt::remove(&t1),
367            toasty::stmt::remove(&t2),
368        ]))
369        .exec(&mut db)
370        .await?;
371
372    let titles: Vec<_> = user
373        .todos()
374        .exec(&mut db)
375        .await?
376        .into_iter()
377        .map(|t| t.title)
378        .collect();
379    assert_eq!(titles, ["keep"]);
380
381    // Required FK: removed todos are deleted, not just unlinked.
382    assert_err!(Todo::get_by_id(&mut db, &t1.id).await);
383    assert_err!(Todo::get_by_id(&mut db, &t2.id).await);
384    assert_ok!(Todo::get_by_id(&mut db, &t3.id).await);
385    Ok(())
386}
387
388/// `stmt::apply([insert(..), remove(..)])` on a has-many with a *nullable*
389/// foreign key. Unlike the required-FK case (which deletes the child),
390/// Remove here takes the disassociate-nullify branch: the old todo
391/// persists with its FK set to NULL.
392#[driver_test(id(ID), scenario(crate::scenarios::has_many_nullable_fk))]
393pub async fn has_many_apply_insert_and_remove_nullable_fk(test: &mut Test) -> Result<()> {
394    let mut db = setup(test).await;
395
396    let mut user = User::create().exec(&mut db).await?;
397    let old_todo = user.todos().create().title("old").exec(&mut db).await?;
398
399    user.update()
400        .todos(toasty::stmt::apply([
401            toasty::stmt::insert(Todo::create().title("new")),
402            toasty::stmt::remove(&old_todo),
403        ]))
404        .exec(&mut db)
405        .await?;
406
407    let titles: Vec<_> = user
408        .todos()
409        .exec(&mut db)
410        .await?
411        .into_iter()
412        .map(|t| t.title)
413        .collect();
414    assert_eq!(titles, ["new"]);
415
416    // Nullable FK: the removed todo is unlinked, not deleted.
417    let reloaded = Todo::get_by_id(&mut db, &old_todo.id).await?;
418    assert_none!(reloaded.user_id);
419    Ok(())
420}
421
422/// Order-sensitive swap: the child has a `#[unique]` title and we replace
423/// the "X" todo by removing the old one and inserting a fresh "X". With a
424/// required FK, `remove` deletes the old row (freeing the unique title), so
425/// the insert can reuse it — but only if the delete runs first.
426///
427/// `flatten_relation_batch` dispatches the merged Insert after the batch's
428/// removes, so the delete lands before the insert and the swap succeeds
429/// regardless of the order the caller wrote the entries.
430#[driver_test(id(ID), scenario(crate::scenarios::has_many_unique_title))]
431pub async fn has_many_apply_swap_unique_required_fk(test: &mut Test) -> Result<()> {
432    let mut db = setup(test).await;
433
434    let mut user = User::create().exec(&mut db).await?;
435    let old = user.todos().create().title("X").exec(&mut db).await?;
436
437    user.update()
438        .todos(toasty::stmt::apply([
439            toasty::stmt::remove(&old),
440            toasty::stmt::insert(Todo::create().title("X")),
441        ]))
442        .exec(&mut db)
443        .await?;
444
445    let titles: Vec<_> = user
446        .todos()
447        .exec(&mut db)
448        .await?
449        .into_iter()
450        .map(|t| t.title)
451        .collect();
452    assert_eq!(titles, ["X"]);
453    Ok(())
454}
455
456/// Same unique-title swap, but with an unrelated `insert` at the *front* of
457/// the batch. `flatten_relation_batch` merges all inserts into one multi-row
458/// INSERT, so the unrelated "Y" insert and the swap's new "X" insert become a
459/// single statement. That merged INSERT must still be dispatched after the
460/// `remove`, or the new "X" collides with the old one on the unique
461/// constraint — i.e. coalescing inserts must not pull them ahead of removes.
462#[driver_test(id(ID), scenario(crate::scenarios::has_many_unique_title))]
463pub async fn has_many_apply_swap_unique_with_extra_insert(test: &mut Test) -> Result<()> {
464    let mut db = setup(test).await;
465
466    let mut user = User::create().exec(&mut db).await?;
467    let old = user.todos().create().title("X").exec(&mut db).await?;
468
469    user.update()
470        .todos(toasty::stmt::apply([
471            toasty::stmt::insert(Todo::create().title("Y")),
472            toasty::stmt::remove(&old),
473            toasty::stmt::insert(Todo::create().title("X")),
474        ]))
475        .exec(&mut db)
476        .await?;
477
478    let mut titles: Vec<_> = user
479        .todos()
480        .exec(&mut db)
481        .await?
482        .into_iter()
483        .map(|t| t.title)
484        .collect();
485    titles.sort();
486    assert_eq!(titles, ["X", "Y"]);
487    Ok(())
488}
489
490/// Inserting and removing the *same existing* record in one batch honors
491/// entry order. `insert(&t)` (associate an existing row) and `remove(&t)`
492/// (dissociate it) both lower to UPDATEs on the same row; the batch sequences
493/// its entries so the last-written op wins, instead of the two UPDATEs racing
494/// in the dependency graph.
495///
496/// Note this is orthogonal to `flatten_relation_batch`'s insert-last reorder —
497/// that only moves create-new inserts (`Todo::create()`), not
498/// associate-existing inserts (`&todo`), which keep their written position.
499#[driver_test(id(ID), scenario(crate::scenarios::has_many_nullable_fk))]
500pub async fn has_many_apply_insert_remove_same_item(test: &mut Test) -> Result<()> {
501    use toasty_core::{
502        driver::Operation,
503        stmt::{Assignment, ExprSet, Statement, Update},
504    };
505
506    // Drain the op log into one marker per FK-writing UPDATE: "unlink" for
507    // `Set(NULL)` (dissociate), "link" otherwise (associate). Transaction,
508    // savepoint, and read (COUNT/EXISTS) ops are ignored. SQL drivers only —
509    // key-value drivers emit a different op shape.
510    fn fk_writes(test: &Test) -> Vec<&'static str> {
511        fn classify(update: &Update, out: &mut Vec<&'static str>) {
512            for (_, assignment) in update.assignments.iter() {
513                if let Assignment::Set(expr) = assignment {
514                    out.push(if expr.is_value_null() {
515                        "unlink"
516                    } else {
517                        "link"
518                    });
519                }
520            }
521        }
522
523        let mut out = vec![];
524        while !test.log().is_empty() {
525            let Operation::QuerySql(q) = test.log().pop().0 else {
526                continue;
527            };
528            match &q.stmt {
529                // The associate update, plus the dissociate's write half on
530                // drivers without `cte_with_update` (its conditional update
531                // lowers to a read-modify-write with a bare UPDATE).
532                Statement::Update(update) => classify(update, &mut out),
533                // On a `cte_with_update` driver (e.g. PostgreSQL), the
534                // conditional dissociate folds its count check and UPDATE into
535                // a single CTE query; the UPDATE lives in a `With` CTE.
536                Statement::Query(query) => {
537                    for cte in query.with.iter().flat_map(|with| &with.ctes) {
538                        if let ExprSet::Update(update) = &cte.query.body {
539                            classify(update, &mut out);
540                        }
541                    }
542                }
543                _ => {}
544            }
545        }
546        out
547    }
548
549    let mut db = setup(test).await;
550
551    // remove then insert → insert wins → still associated.
552    let mut keep = User::create().exec(&mut db).await?;
553    let kt = keep.todos().create().title("t").exec(&mut db).await?;
554    test.log().clear();
555    keep.update()
556        .todos(toasty::stmt::apply([
557            toasty::stmt::remove(&kt),
558            toasty::stmt::insert(&kt),
559        ]))
560        .exec(&mut db)
561        .await?;
562    if test.capability().sql {
563        // Dissociate executes before associate.
564        assert_eq!(fk_writes(test), ["unlink", "link"]);
565    }
566    assert_eq!(keep.todos().exec(&mut db).await?.len(), 1);
567
568    // insert then remove → remove wins → dissociated.
569    let mut drop = User::create().exec(&mut db).await?;
570    let dt = drop.todos().create().title("t").exec(&mut db).await?;
571    test.log().clear();
572    drop.update()
573        .todos(toasty::stmt::apply([
574            toasty::stmt::insert(&dt),
575            toasty::stmt::remove(&dt),
576        ]))
577        .exec(&mut db)
578        .await?;
579    if test.capability().sql {
580        // Associate executes before dissociate.
581        assert_eq!(fk_writes(test), ["link", "unlink"]);
582    }
583    assert_eq!(drop.todos().exec(&mut db).await?.len(), 0);
584    Ok(())
585}
586
587/// Nested `stmt::apply([apply([..]), ..])`. The surface API flattens
588/// nested applies into a single flat `Batch` (the engine never sees
589/// nesting), so this must behave identically to the equivalent flat
590/// batch. Guards the flattening contract — if nesting ever stopped
591/// flattening, the engine's `flatten_relation_batch` dispatch would hit
592/// its `unreachable!` arm.
593#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
594pub async fn has_many_apply_nested(test: &mut Test) -> Result<()> {
595    let mut db = setup(test).await;
596
597    let mut user = User::create().name("Alice").exec(&mut db).await?;
598    let old1 = user.todos().create().title("old1").exec(&mut db).await?;
599    let old2 = user.todos().create().title("old2").exec(&mut db).await?;
600
601    user.update()
602        .todos(toasty::stmt::apply([
603            toasty::stmt::apply([
604                toasty::stmt::insert(Todo::create().title("a")),
605                toasty::stmt::insert(Todo::create().title("b")),
606            ]),
607            toasty::stmt::apply([toasty::stmt::remove(&old1), toasty::stmt::remove(&old2)]),
608            toasty::stmt::insert(Todo::create().title("c")),
609        ]))
610        .exec(&mut db)
611        .await?;
612
613    let mut titles: Vec<_> = user
614        .todos()
615        .exec(&mut db)
616        .await?
617        .into_iter()
618        .map(|t| t.title)
619        .collect();
620    titles.sort();
621    assert_eq!(titles, ["a", "b", "c"]);
622    Ok(())
623}
624
625/// Deterministic matrix over batch shapes. For each
626/// `(existing, insert, remove)` combination, build one
627/// `stmt::apply([...])` carrying `insert` new children plus `remove`
628/// dissociations, then compare the resulting association set against a
629/// reference computed in memory. This covers larger batches and more
630/// combinations than the targeted tests above without the
631/// non-determinism (and sync/async friction) of a `proptest` runner.
632#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
633pub async fn has_many_apply_combinations(test: &mut Test) -> Result<()> {
634    let mut db = setup(test).await;
635
636    for num_existing in 0..=3usize {
637        for num_insert in 0..=3usize {
638            for num_remove in 0..=num_existing {
639                // An empty batch produces no assignments, which the engine
640                // rejects as an empty update. Covered by
641                // `has_many_apply_empty_is_noop`.
642                if num_insert == 0 && num_remove == 0 {
643                    continue;
644                }
645
646                let mut user = User::create()
647                    .name(format!("u-{num_existing}-{num_insert}-{num_remove}"))
648                    .exec(&mut db)
649                    .await?;
650
651                // Seed existing children: e0..e{num_existing}.
652                let mut existing = Vec::new();
653                for i in 0..num_existing {
654                    existing.push(
655                        user.todos()
656                            .create()
657                            .title(format!("e{i}"))
658                            .exec(&mut db)
659                            .await?,
660                    );
661                }
662
663                // One batch: `num_insert` inserts + `num_remove` removes.
664                let mut ops: Vec<toasty::stmt::Assignment<toasty::stmt::List<Todo>>> = Vec::new();
665                for i in 0..num_insert {
666                    ops.push(toasty::stmt::insert(Todo::create().title(format!("n{i}"))));
667                }
668                for todo in &existing[..num_remove] {
669                    ops.push(toasty::stmt::remove(todo));
670                }
671
672                user.update()
673                    .todos(toasty::stmt::apply(ops))
674                    .exec(&mut db)
675                    .await?;
676
677                // Reference: surviving existing + inserted.
678                let mut expected: Vec<String> = existing[num_remove..]
679                    .iter()
680                    .map(|t| t.title.clone())
681                    .collect();
682                for i in 0..num_insert {
683                    expected.push(format!("n{i}"));
684                }
685                expected.sort();
686
687                let mut actual: Vec<String> = user
688                    .todos()
689                    .exec(&mut db)
690                    .await?
691                    .into_iter()
692                    .map(|t| t.title)
693                    .collect();
694                actual.sort();
695
696                assert_eq!(
697                    actual, expected,
698                    "existing={num_existing} insert={num_insert} remove={num_remove}"
699                );
700            }
701        }
702    }
703    Ok(())
704}
705
706#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
707pub async fn scoped_find_by_id(test: &mut Test) -> Result<()> {
708    let mut db = setup(test).await;
709
710    // Create a couple of users
711    let user1 = User::create().name("User 1").exec(&mut db).await?;
712    let user2 = User::create().name("User 2").exec(&mut db).await?;
713
714    // Create a todo
715    let todo = user1
716        .todos()
717        .create()
718        .title("hello world")
719        .exec(&mut db)
720        .await?;
721
722    // Find it scoped by user1
723    let reloaded = user1.todos().get_by_id(&mut db, &todo.id).await?;
724    assert_eq!(reloaded.id, todo.id);
725    assert_eq!(reloaded.title, todo.title);
726
727    // Trying to find the same todo scoped by user2 is missing
728    assert_none!(
729        user2
730            .todos()
731            .filter_by_id(todo.id)
732            .first()
733            .exec(&mut db)
734            .await?
735    );
736
737    let reloaded = User::filter_by_id(user1.id)
738        .todos()
739        .get_by_id(&mut db, &todo.id)
740        .await?;
741
742    assert_eq!(reloaded.id, todo.id);
743    assert_eq!(reloaded.title, todo.title);
744
745    // Deleting the TODO from the user 2 scope fails
746    user2
747        .todos()
748        .filter_by_id(todo.id)
749        .delete()
750        .exec(&mut db)
751        .await?;
752    let reloaded = user1.todos().get_by_id(&mut db, &todo.id).await?;
753    assert_eq!(reloaded.id, todo.id);
754    Ok(())
755}
756
757// The has_many association uses the target's primary key as the association's
758// foreign key. In this case, the relation's query should not be duplicated.
759#[driver_test(id(ID))]
760pub async fn has_many_on_target_pk(_test: &mut Test) {}
761
762// The target model has an explicit index on (FK, PK). In this case, the query
763// generated by the (FK, PK) pair should not be duplicated by the relation.
764#[driver_test(id(ID))]
765pub async fn has_many_when_target_indexes_fk_and_pk(_test: &mut Test) {}
766
767// When the FK is composite, things should still work
768#[driver_test(id(ID), scenario(crate::scenarios::composite_has_many_belongs_to))]
769pub async fn has_many_when_fk_is_composite(test: &mut Test) -> Result<()> {
770    let mut db = setup(test).await;
771
772    // Create a user
773    let user = User::create().name("User 1").exec(&mut db).await?;
774
775    // No TODOs
776    assert_eq!(0, user.todos().exec(&mut db).await?.len());
777
778    // Create a Todo associated with the user
779    let todo = user
780        .todos()
781        .create()
782        .title("hello world")
783        .exec(&mut db)
784        .await?;
785
786    // Find the todo by ID
787    let list = Todo::filter_by_user_id_and_id(user.id, todo.id)
788        .exec(&mut db)
789        .await?;
790
791    assert_eq!(1, list.len());
792    assert_eq!(todo.id, list[0].id);
793
794    // Find the TODO by user ID
795    let list = Todo::filter_by_user_id(user.id).exec(&mut db).await?;
796
797    assert_eq!(1, list.len());
798    assert_eq!(todo.id, list[0].id);
799
800    let mut created = HashMap::new();
801    let mut ids = vec![todo.id];
802    created.insert(todo.id, todo);
803
804    // Create a few more TODOs
805    for i in 0..5 {
806        let title = format!("hello world {i}");
807
808        let todo = if i.is_even() {
809            // Create via user
810            user.todos().create().title(title).exec(&mut db).await?
811        } else {
812            // Create via todo builder
813            Todo::create()
814                .user(&user)
815                .title(title)
816                .exec(&mut db)
817                .await?
818        };
819
820        ids.push(todo.id);
821        assert_none!(created.insert(todo.id, todo));
822    }
823
824    // Load all TODOs
825    let list = user.todos().exec(&mut db).await?;
826
827    assert_eq!(6, list.len());
828
829    let loaded: HashMap<_, _> = list.into_iter().map(|todo| (todo.id, todo)).collect();
830    assert_eq!(6, loaded.len());
831
832    for (id, expect) in &created {
833        assert_eq!(expect.title, loaded[id].title);
834    }
835
836    // Find all TODOs by user (using the belongs_to queries)
837    let list = Todo::filter_by_user_id(user.id).exec(&mut db).await?;
838    assert_eq!(6, list.len());
839
840    let by_id: HashMap<_, _> = list.into_iter().map(|todo| (todo.id, todo)).collect();
841
842    assert_eq!(6, by_id.len());
843
844    for (id, expect) in by_id {
845        assert_eq!(expect.title, loaded[&id].title);
846    }
847
848    // Create a second user
849    let user2 = User::create().name("User 2").exec(&mut db).await?;
850
851    // No TODOs associated with `user2`
852    assert_eq!(0, user2.todos().exec(&mut db).await?.len());
853
854    // Create a TODO for user2
855    let u2_todo = user2
856        .todos()
857        .create()
858        .title("user 2 todo")
859        .exec(&mut db)
860        .await?;
861
862    let u1_todos = user.todos().exec(&mut db).await?;
863
864    for todo in u1_todos {
865        assert_ne!(u2_todo.id, todo.id);
866    }
867
868    // Delete a TODO by value
869    let todo = Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[0]).await?;
870    todo.delete().exec(&mut db).await?;
871
872    // Can no longer get the todo via id
873    assert_err!(Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[0]).await);
874
875    // Can no longer get the todo scoped
876    assert_err!(user.todos().get_by_id(&mut db, &ids[0]).await);
877
878    // Delete a TODO by scope
879    user.todos()
880        .filter_by_id(ids[1])
881        .delete()
882        .exec(&mut db)
883        .await?;
884
885    // Can no longer get the todo via id
886    assert_err!(Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[1]).await);
887
888    // Can no longer get the todo scoped
889    assert_err!(user.todos().get_by_id(&mut db, &ids[1]).await);
890
891    // Successfuly a todo by scope
892    user.todos()
893        .filter_by_id(ids[2])
894        .update()
895        .title("batch update 1")
896        .exec(&mut db)
897        .await?;
898    let todo = Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[2]).await?;
899    assert_eq!(todo.title, "batch update 1");
900
901    // Now fail to update it by scoping by other user
902    user2
903        .todos()
904        .filter_by_id(ids[2])
905        .update()
906        .title("batch update 2")
907        .exec(&mut db)
908        .await?;
909    let todo = Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[2]).await?;
910    assert_eq!(todo.title, "batch update 1");
911    Ok(())
912}
913
914// When the PK is composite, things should still work
915#[driver_test(id(ID))]
916pub async fn has_many_when_pk_is_composite(_test: &mut Test) {}
917
918// When both the FK and PK are composite, things should still work
919#[driver_test(id(ID))]
920pub async fn has_many_when_fk_and_pk_are_composite(_test: &mut Test) {}
921
922#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
923pub async fn belongs_to_required(test: &mut Test) {
924    let mut db = setup(test).await;
925
926    assert_err!(Todo::create().exec(&mut db).await);
927}
928
929#[driver_test(id(ID), scenario(crate::scenarios::has_many_nullable_fk))]
930pub async fn delete_when_belongs_to_optional(test: &mut Test) -> Result<()> {
931    let mut db = setup(test).await;
932
933    let user = User::create().exec(&mut db).await?;
934    let mut ids = vec![];
935
936    for _ in 0..3 {
937        let todo = user.todos().create().title("todo").exec(&mut db).await?;
938        ids.push(todo.id);
939    }
940
941    // Delete the user
942    user.delete().exec(&mut db).await?;
943
944    // All the todos still exist and `user` is set to `None`.
945    for id in ids {
946        let todo = Todo::get_by_id(&mut db, id).await?;
947        assert_none!(todo.user_id);
948    }
949
950    // Deleting a user leaves the todo in place.
951    Ok(())
952}
953
954#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
955pub async fn associate_new_user_with_todo_on_update_via_creation(test: &mut Test) -> Result<()> {
956    let mut db = setup(test).await;
957
958    // Create a user with a todo
959    let u1 = User::create()
960        .name("User 1")
961        .todos([Todo::create().title("hello world")])
962        .exec(&mut db)
963        .await?;
964
965    // Get the todo
966    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
967    assert_eq!(1, todos.len());
968    let mut todo = todos.into_iter().next().unwrap();
969
970    todo.update()
971        .user(User::create().name("User 2"))
972        .exec(&mut db)
973        .await?;
974    Ok(())
975}
976
977#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
978pub async fn associate_new_user_with_todo_on_update_query_via_creation(
979    test: &mut Test,
980) -> Result<()> {
981    let mut db = setup(test).await;
982
983    // Create a user with a todo
984    let u1 = User::create()
985        .name("User 1")
986        .todos([Todo::create().title("a todo")])
987        .exec(&mut db)
988        .await?;
989
990    // Get the todo
991    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
992    assert_eq!(1, todos.len());
993    let todo = todos.into_iter().next().unwrap();
994
995    Todo::filter_by_id(todo.id)
996        .update()
997        .user(User::create().name("User 2"))
998        .exec(&mut db)
999        .await?;
1000    Ok(())
1001}
1002
1003#[driver_test(id(ID))]
1004#[should_panic]
1005pub async fn update_user_with_null_todo_is_err(test: &mut Test) -> Result<()> {
1006    #[derive(Debug, toasty::Model)]
1007    struct User {
1008        #[key]
1009        #[auto]
1010        id: ID,
1011
1012        #[has_many]
1013        todos: toasty::Deferred<Vec<Todo>>,
1014    }
1015
1016    #[derive(Debug, toasty::Model)]
1017    struct Todo {
1018        #[key]
1019        #[auto]
1020        id: ID,
1021
1022        #[index]
1023        user_id: ID,
1024
1025        #[belongs_to(key = user_id, references = id)]
1026        user: toasty::Deferred<User>,
1027    }
1028
1029    use toasty::stmt::{self, IntoExpr};
1030
1031    let mut db = test.setup_db(models!(User, Todo)).await;
1032
1033    // Create a user with a todo
1034    let u1 = User::create().todos([Todo::create()]).exec(&mut db).await?;
1035
1036    // Get the todo
1037    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
1038    assert_eq!(1, todos.len());
1039    let todo = todos.into_iter().next().unwrap();
1040
1041    // Updating the todo w/ null is an error. Thus requires a bit of a hack to make work
1042    let mut stmt: stmt::Update<Todo> =
1043        stmt::Update::new(stmt::Query::from_expr((&todo).into_expr()));
1044    stmt.set(2, toasty_core::stmt::Value::Null);
1045    stmt.exec(&mut db).await?;
1046
1047    // User is not deleted
1048    let u1_reloaded = User::get_by_id(&mut db, &u1.id).await?;
1049    assert_eq!(u1_reloaded.id, u1.id);
1050    Ok(())
1051}
1052
1053#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
1054pub async fn assign_todo_that_already_has_user_on_create(test: &mut Test) -> Result<()> {
1055    let mut db = setup(test).await;
1056
1057    let todo = Todo::create()
1058        .title("a todo")
1059        .user(User::create().name("User 1"))
1060        .exec(&mut db)
1061        .await?;
1062
1063    let u1 = todo.user().exec(&mut db).await?;
1064
1065    let u2 = User::create()
1066        .name("User 2")
1067        .todos([&todo])
1068        .exec(&mut db)
1069        .await?;
1070
1071    let todo_reload = Todo::get_by_id(&mut db, &todo.id).await?;
1072
1073    assert_eq!(u2.id, todo_reload.user_id);
1074
1075    // First user has no todos
1076    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
1077    assert_eq!(0, todos.len());
1078
1079    // Second user has the todo
1080    let todos: Vec<_> = u2.todos().exec(&mut db).await?;
1081    assert_eq!(1, todos.len());
1082    assert_eq!(todo.id, todos[0].id);
1083    Ok(())
1084}
1085
1086#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
1087pub async fn assign_todo_that_already_has_user_on_update(test: &mut Test) -> Result<()> {
1088    let mut db = setup(test).await;
1089
1090    let todo = Todo::create()
1091        .title("a todo")
1092        .user(User::create().name("User 1"))
1093        .exec(&mut db)
1094        .await?;
1095
1096    let u1 = todo.user().exec(&mut db).await?;
1097
1098    let mut u2 = User::create().name("User 2").exec(&mut db).await?;
1099
1100    // Update the user
1101    u2.update()
1102        .todos(toasty::stmt::insert(&todo))
1103        .exec(&mut db)
1104        .await?;
1105
1106    let todo_reload = Todo::get_by_id(&mut db, &todo.id).await?;
1107
1108    assert_eq!(u2.id, todo_reload.user_id);
1109
1110    // First user has no todos
1111    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
1112    assert_eq!(0, todos.len());
1113
1114    // Second user has the todo
1115    let todos: Vec<_> = u2.todos().exec(&mut db).await?;
1116    assert_eq!(1, todos.len());
1117    assert_eq!(todo.id, todos[0].id);
1118    Ok(())
1119}
1120
1121#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
1122pub async fn assign_existing_user_to_todo(test: &mut Test) -> Result<()> {
1123    let mut db = setup(test).await;
1124
1125    let mut todo = Todo::create()
1126        .title("hello")
1127        .user(User::create().name("User 1"))
1128        .exec(&mut db)
1129        .await?;
1130
1131    let u1 = todo.user().exec(&mut db).await?;
1132
1133    let u2 = User::create().name("User 2").exec(&mut db).await?;
1134
1135    // Update the todo
1136    todo.update().user(&u2).exec(&mut db).await?;
1137
1138    let todo_reload = Todo::get_by_id(&mut db, &todo.id).await?;
1139
1140    assert_eq!(u2.id, todo_reload.user_id);
1141
1142    // First user has no todos
1143    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
1144    assert_eq!(0, todos.len());
1145
1146    // Second user has the todo
1147    let todos: Vec<_> = u2.todos().exec(&mut db).await?;
1148    assert_eq!(1, todos.len());
1149    assert_eq!(todo.id, todos[0].id);
1150    Ok(())
1151}
1152
1153#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
1154pub async fn assign_todo_to_user_on_update_query(test: &mut Test) -> Result<()> {
1155    let mut db = setup(test).await;
1156
1157    let user = User::create().name("User 1").exec(&mut db).await?;
1158
1159    User::filter_by_id(user.id)
1160        .update()
1161        .todos(toasty::stmt::insert(Todo::create().title("hello")))
1162        .exec(&mut db)
1163        .await?;
1164
1165    let todos: Vec<_> = user.todos().exec(&mut db).await?;
1166    assert_eq!(1, todos.len());
1167    assert_eq!("hello", todos[0].title);
1168    Ok(())
1169}
1170
1171#[driver_test(id(ID), scenario(crate::scenarios::composite_has_many_belongs_to))]
1172pub async fn has_many_when_fk_is_composite_with_snippets(test: &mut Test) -> Result<()> {
1173    let mut db = setup(test).await;
1174
1175    // Create users
1176    let user1 = User::create().name("User 1").exec(&mut db).await?;
1177    let user2 = User::create().name("User 2").exec(&mut db).await?;
1178
1179    // Create a Todo associated with the user
1180    user1
1181        .todos()
1182        .create()
1183        .title("hello world")
1184        .exec(&mut db)
1185        .await?;
1186
1187    let todo2 = user2
1188        .todos()
1189        .create()
1190        .title("hello world")
1191        .exec(&mut db)
1192        .await?;
1193
1194    // Update the Todos with the snippets
1195    Todo::update_by_user_id(user1.id)
1196        .title("Title 2")
1197        .exec(&mut db)
1198        .await?;
1199
1200    let todo = Todo::get_by_user_id(&mut db, user1.id).await?;
1201    assert!(todo.title == "Title 2");
1202
1203    Todo::update_by_user_id_and_id(user2.id, todo2.id)
1204        .title("Title 3")
1205        .exec(&mut db)
1206        .await?;
1207
1208    let todo = Todo::get_by_user_id_and_id(&mut db, user2.id, todo2.id).await?;
1209    assert!(todo.title == "Title 3");
1210
1211    // Delete the Todos with the snippets
1212    Todo::delete_by_user_id(&mut db, user1.id).await?;
1213    assert_err!(Todo::get_by_user_id(&mut db, user1.id).await);
1214
1215    Todo::delete_by_user_id_and_id(&mut db, user2.id, todo2.id).await?;
1216    assert_err!(Todo::get_by_user_id_and_id(&mut db, user2.id, todo2.id).await);
1217
1218    Ok(())
1219}