Skip to main content

toasty_driver_integration_suite/tests/
embed_relation_include.rs

1use crate::prelude::*;
2use toasty_core::driver::Operation;
3
4#[derive(Debug, toasty::Model)]
5struct Person {
6    #[key]
7    #[auto]
8    id: uuid::Uuid,
9    name: String,
10}
11
12#[derive(Debug, toasty::Embed)]
13struct Membership {
14    id: uuid::Uuid,
15    #[belongs_to(key = id)]
16    person: Person,
17}
18
19#[derive(Debug, toasty::Embed)]
20enum EagerOwner {
21    Member {
22        membership: Membership,
23    },
24    Person {
25        id: uuid::Uuid,
26        #[belongs_to(key = id)]
27        person: Person,
28    },
29    Nobody,
30}
31
32#[derive(Debug, toasty::Model)]
33struct Document {
34    #[key]
35    #[auto]
36    id: uuid::Uuid,
37    primary: Option<EagerOwner>,
38    secondary: Option<EagerOwner>,
39}
40
41#[driver_test]
42pub async fn eager_nested_embedded_relations(t: &mut Test) -> Result<()> {
43    let mut db = t.setup_db(models!(Document, Person)).await;
44    let alice = toasty::create!(Person { name: "Alice" })
45        .exec(&mut db)
46        .await?;
47    let bob = toasty::create!(Person { name: "Bob" })
48        .exec(&mut db)
49        .await?;
50    let mut doc = toasty::create!(Document {
51        primary: EagerOwner::Member {
52            membership: Membership {
53                id: alice.id,
54                person: alice
55            }
56        },
57        secondary: EagerOwner::Person { person: &bob },
58    })
59    .exec(&mut db)
60    .await?;
61    assert_document_owners(&doc);
62    let loaded = Document::filter_by_id(doc.id).get(&mut db).await?;
63    assert_document_owners(&loaded);
64    toasty::create!(Document {
65        primary: None,
66        secondary: EagerOwner::Nobody
67    })
68    .exec(&mut db)
69    .await?;
70    let docs = Document::all().exec(&mut db).await?;
71    assert_eq!(docs.len(), 2);
72    assert_document_owners(docs.iter().find(|d| d.id == doc.id).unwrap());
73    doc.update()
74        .primary(EagerOwner::Person {
75            id: bob.id,
76            person: bob,
77        })
78        .exec(&mut db)
79        .await?;
80    assert_struct!(doc.primary, Some(EagerOwner::Person { person.name: "Bob", .. }));
81    Ok(())
82}
83
84fn assert_document_owners(doc: &Document) {
85    assert_struct!(doc.primary, Some(EagerOwner::Member { membership.person.name: "Alice", .. }));
86    assert_struct!(doc.secondary, Some(EagerOwner::Person { person.name: "Bob", .. }));
87}
88
89#[driver_test]
90pub async fn eager_embed_projection(t: &mut Test) -> Result<()> {
91    #[derive(Debug, toasty::Model)]
92    struct Card {
93        #[key]
94        #[auto]
95        id: uuid::Uuid,
96        owner: EagerOwner,
97    }
98    let mut db = t.setup_db(models!(Document, Person, Card)).await;
99    let alice = toasty::create!(Person { name: "Alice" })
100        .exec(&mut db)
101        .await?;
102    let doc = toasty::create!(Document {
103        primary: EagerOwner::Person { person: &alice },
104        secondary: None,
105    })
106    .exec(&mut db)
107    .await?;
108    let owner = Document::filter_by_id(doc.id)
109        .select(Document::fields().primary())
110        .exec(&mut db)
111        .await?
112        .pop()
113        .unwrap();
114    assert_struct!(owner, Some(EagerOwner::Person { person.name: "Alice", .. }));
115    let (id, owner) = Document::filter_by_id(doc.id)
116        .select((Document::fields().id(), Document::fields().primary()))
117        .exec(&mut db)
118        .await?
119        .pop()
120        .unwrap();
121    assert_eq!(id, doc.id);
122    assert_struct!(owner, Some(EagerOwner::Person { person.name: "Alice", .. }));
123    let absent = Document::filter_by_id(doc.id)
124        .select(Document::fields().secondary())
125        .exec(&mut db)
126        .await?
127        .pop()
128        .unwrap();
129    assert!(absent.is_none());
130    let member = toasty::create!(Card {
131        owner: EagerOwner::Member {
132            membership: Membership {
133                id: alice.id,
134                person: alice
135            }
136        },
137    })
138    .exec(&mut db)
139    .await?;
140    let membership = Card::filter_by_id(member.id)
141        .select(Card::fields().owner().member().membership())
142        .exec(&mut db)
143        .await?
144        .pop()
145        .unwrap();
146    assert_eq!(membership.person.name, "Alice");
147    Ok(())
148}
149
150#[driver_test]
151pub async fn eager_embed_key_patch(t: &mut Test) -> Result<()> {
152    #[derive(Debug, toasty::Embed)]
153    struct Attribution {
154        author_id: Option<uuid::Uuid>,
155        #[belongs_to(key = author_id)]
156        author: Option<Person>,
157        label: String,
158    }
159    #[derive(Debug, toasty::Model)]
160    struct Article {
161        #[key]
162        #[auto]
163        id: uuid::Uuid,
164        attribution: Attribution,
165    }
166    let mut db = t.setup_db(models!(Article, Person)).await;
167    let alice = toasty::create!(Person { name: "Alice" })
168        .exec(&mut db)
169        .await?;
170    let bob = toasty::create!(Person { name: "Bob" })
171        .exec(&mut db)
172        .await?;
173    let mut article = toasty::create!(Article {
174        attribution: Attribution {
175            author_id: Some(alice.id),
176            author: Some(alice),
177            label: "by".into()
178        },
179    })
180    .exec(&mut db)
181    .await?;
182    article
183        .update()
184        .attribution(toasty::stmt::patch(
185            Attribution::fields().author_id(),
186            Some(bob.id),
187        ))
188        .exec(&mut db)
189        .await?;
190    assert_eq!(article.attribution.author_id, Some(bob.id));
191    assert_eq!(article.attribution.author.as_ref().unwrap().name, "Bob");
192    assert_eq!(article.attribution.label, "by");
193    let stored = Article::filter_by_id(article.id).get(&mut db).await?;
194    assert_eq!(stored.attribution.author.as_ref().unwrap().name, "Bob");
195    t.log().clear();
196    article
197        .update()
198        .attribution(toasty::stmt::patch(
199            Attribution::fields().label(),
200            "written by",
201        ))
202        .exec(&mut db)
203        .await?;
204    assert_eq!(article.attribution.author.as_ref().unwrap().name, "Bob");
205    assert_query_count(t, 1);
206    article
207        .update()
208        .attribution(toasty::stmt::patch(Attribution::fields().author_id(), None))
209        .exec(&mut db)
210        .await?;
211    assert!(article.attribution.author_id.is_none());
212    assert!(article.attribution.author.is_none());
213    Ok(())
214}
215
216#[driver_test]
217pub async fn eager_embedded_relations_batch_create(t: &mut Test) -> Result<()> {
218    let mut db = t.setup_db(models!(Document, Person)).await;
219    let alice = toasty::create!(Person { name: "Alice" })
220        .exec(&mut db)
221        .await?;
222    let bob = toasty::create!(Person { name: "Bob" })
223        .exec(&mut db)
224        .await?;
225    let docs = toasty::create!(Document::[
226        { primary: EagerOwner::Person { person: &alice }, secondary: None },
227        { primary: None, secondary: EagerOwner::Person { person: &bob } },
228    ])
229    .exec(&mut db)
230    .await?;
231    assert_struct!(docs[0].primary, Some(EagerOwner::Person { person.name: "Alice", .. }));
232    assert!(docs[0].secondary.is_none());
233    assert!(docs[1].primary.is_none());
234    assert_struct!(docs[1].secondary, Some(EagerOwner::Person { person.name: "Bob", .. }));
235    Ok(())
236}
237
238#[driver_test]
239pub async fn eager_optional_struct_relation(t: &mut Test) -> Result<()> {
240    #[derive(Debug, toasty::Embed)]
241    struct Member {
242        id: Option<uuid::Uuid>,
243        #[belongs_to(key = id)]
244        person: Option<Person>,
245    }
246
247    #[derive(Debug, toasty::Model)]
248    struct Group {
249        #[key]
250        #[auto]
251        id: uuid::Uuid,
252        member: Option<Member>,
253    }
254
255    let mut db = t.setup_db(models!(Group, Person)).await;
256    let alice = toasty::create!(Person { name: "Alice" })
257        .exec(&mut db)
258        .await?;
259    let group = toasty::create!(Group {
260        member: Member {
261            id: Some(alice.id),
262            person: None
263        },
264    })
265    .exec(&mut db)
266    .await?;
267    assert_eq!(group.member.unwrap().person.unwrap().name, "Alice");
268    let no_person = toasty::create!(Group {
269        member: Member {
270            id: None,
271            person: None
272        }
273    })
274    .exec(&mut db)
275    .await?;
276    assert!(no_person.member.unwrap().person.is_none());
277    let absent = toasty::create!(Group { member: None })
278        .exec(&mut db)
279        .await?;
280    assert!(absent.member.is_none());
281    let groups = Group::all().exec(&mut db).await?;
282    assert_eq!(groups.len(), 3);
283    assert_eq!(
284        groups.into_iter().filter_map(|g| g.member?.person).count(),
285        1
286    );
287    Ok(())
288}
289
290#[driver_test]
291pub async fn include_nested_relation_path(t: &mut Test) -> Result<()> {
292    #[derive(Debug, toasty::Embed)]
293    struct Link {
294        id: Option<uuid::Uuid>,
295        #[belongs_to(key = id)]
296        person: toasty::Deferred<Option<Person>>,
297        reviewer_id: Option<uuid::Uuid>,
298        #[belongs_to(key = reviewer_id)]
299        reviewer: toasty::Deferred<Option<Person>>,
300        notes: toasty::Deferred<String>,
301    }
302
303    #[derive(Debug, toasty::Embed)]
304    enum Target {
305        Assigned { link: Link },
306        Nobody,
307    }
308
309    #[derive(Debug, toasty::Model)]
310    struct Ticket {
311        #[key]
312        #[auto]
313        id: uuid::Uuid,
314        owner: Target,
315        backup: toasty::Deferred<Target>,
316    }
317
318    let mut db = t.setup_db(models!(Ticket, Person)).await;
319    let alice = toasty::create!(Person { name: "Alice" })
320        .exec(&mut db)
321        .await?;
322    let bob = toasty::create!(Person { name: "Bob" })
323        .exec(&mut db)
324        .await?;
325    let target = |id, reviewer_id| Target::Assigned {
326        link: Link {
327            id,
328            person: Default::default(),
329            reviewer_id,
330            reviewer: Default::default(),
331            notes: "notes".to_string().into(),
332        },
333    };
334    let ticket = toasty::create!(Ticket {
335        owner: target(Some(alice.id), Some(bob.id)),
336        backup: target(Some(alice.id), Some(bob.id))
337    })
338    .exec(&mut db)
339    .await?;
340
341    t.log().clear();
342    let loaded = Ticket::filter_by_id(ticket.id)
343        .include(Ticket::fields().owner().assigned().link().person())
344        .get(&mut db)
345        .await?;
346    let Target::Assigned { link } = loaded.owner else {
347        panic!("expected assigned")
348    };
349    assert_eq!(link.person.get().as_ref().unwrap().name, "Alice");
350    assert!(link.reviewer.is_unloaded());
351    assert!(link.notes.is_unloaded());
352    assert!(loaded.backup.is_unloaded());
353    assert_query_count(t, 2);
354
355    t.log().clear();
356    let loaded = Ticket::filter_by_id(ticket.id)
357        .include(Ticket::fields().backup())
358        .get(&mut db)
359        .await?;
360    let Target::Assigned { link } = loaded.backup.get() else {
361        panic!("expected assigned")
362    };
363    assert_eq!(link.id, Some(alice.id));
364    assert_eq!(link.reviewer_id, Some(bob.id));
365    assert!(link.person.is_unloaded());
366    assert!(link.reviewer.is_unloaded());
367    assert!(link.notes.is_unloaded());
368    assert_query_count(t, 1);
369
370    t.log().clear();
371    let loaded = Ticket::filter_by_id(ticket.id)
372        .include(Ticket::fields().backup().assigned().link().person())
373        .get(&mut db)
374        .await?;
375    let Target::Assigned { link } = loaded.backup.get() else {
376        panic!("expected assigned")
377    };
378    assert_eq!(link.person.get().as_ref().unwrap().name, "Alice");
379    assert!(link.reviewer.is_unloaded());
380    assert!(link.notes.is_unloaded());
381    assert_query_count(t, 2);
382    Ok(())
383}
384
385#[driver_test]
386pub async fn include_deferred_embed_loads_eager_relation(t: &mut Test) -> Result<()> {
387    #[derive(Debug, toasty::Embed)]
388    struct Member {
389        id: uuid::Uuid,
390        #[belongs_to(key = id)]
391        person: Person,
392    }
393
394    #[derive(Debug, toasty::Model)]
395    struct Group {
396        #[key]
397        #[auto]
398        id: uuid::Uuid,
399        member: toasty::Deferred<Member>,
400    }
401
402    let mut db = t.setup_db(models!(Group, Person)).await;
403    let alice = toasty::create!(Person { name: "Alice" })
404        .exec(&mut db)
405        .await?;
406    let group = toasty::create!(Group {
407        member: Member {
408            id: alice.id,
409            person: alice
410        }
411    })
412    .exec(&mut db)
413    .await?;
414
415    let unloaded = Group::filter_by_id(group.id).get(&mut db).await?;
416    assert!(unloaded.member.is_unloaded());
417
418    t.log().clear();
419    let loaded = Group::filter_by_id(group.id)
420        .include(Group::fields().member())
421        .get(&mut db)
422        .await?;
423    assert_eq!(loaded.member.get().person.name, "Alice");
424    assert_query_count(t, 2);
425    Ok(())
426}
427
428#[driver_test]
429pub async fn include_polymorphic_owner(t: &mut Test) -> Result<()> {
430    #[derive(Debug, toasty::Model)]
431    struct Human {
432        #[key]
433        id: uuid::Uuid,
434        name: String,
435    }
436
437    #[derive(Debug, toasty::Model)]
438    struct Animal {
439        #[key]
440        id: uuid::Uuid,
441        name: String,
442    }
443
444    #[derive(Debug, toasty::Model)]
445    struct Bot {
446        #[key]
447        serial: String,
448    }
449
450    #[derive(Debug, toasty::Embed)]
451    enum Owner {
452        Human {
453            #[shared(id)]
454            id: uuid::Uuid,
455            #[belongs_to(key = id)]
456            human: toasty::Deferred<Human>,
457        },
458        Animal {
459            #[shared(id)]
460            id: uuid::Uuid,
461            #[belongs_to(key = id)]
462            animal: toasty::Deferred<Animal>,
463        },
464        Bot {
465            serial: String,
466            #[belongs_to(key = serial, references = serial)]
467            bot: toasty::Deferred<Bot>,
468        },
469    }
470
471    #[derive(Debug, toasty::Model)]
472    struct Object {
473        #[key]
474        #[auto]
475        id: uuid::Uuid,
476        owner: Owner,
477    }
478
479    let mut db = t.setup_db(models!(Object, Human, Animal, Bot)).await;
480    let id = uuid::Uuid::new_v4();
481    let human = toasty::create!(Human { id, name: "Alice" })
482        .exec(&mut db)
483        .await?;
484    let animal = toasty::create!(Animal { id, name: "Cat" })
485        .exec(&mut db)
486        .await?;
487    let bot = toasty::create!(Bot { serial: "robot" })
488        .exec(&mut db)
489        .await?;
490    let a = toasty::create!(Object {
491        owner: Owner::Human { human: &human }
492    })
493    .exec(&mut db)
494    .await?;
495    toasty::create!(Object {
496        owner: Owner::Animal { animal: &animal }
497    })
498    .exec(&mut db)
499    .await?;
500    toasty::create!(Object {
501        owner: Owner::Bot { bot: &bot }
502    })
503    .exec(&mut db)
504    .await?;
505
506    t.log().clear();
507    let objects = Object::all()
508        .include(Object::fields().owner().human().human())
509        .include(Object::fields().owner().animal().animal())
510        .include(Object::fields().owner().bot().bot())
511        .exec(&mut db)
512        .await?;
513    assert_eq!(objects.len(), 3);
514    for object in objects {
515        match object.owner {
516            Owner::Human { human, .. } => assert_eq!(human.get().name, "Alice"),
517            Owner::Animal { animal, .. } => assert_eq!(animal.get().name, "Cat"),
518            Owner::Bot { bot, .. } => assert_eq!(bot.get().serial, "robot"),
519        }
520    }
521    assert_query_count(t, 4);
522
523    t.log().clear();
524    let object = Object::filter_by_id(a.id)
525        .include(Object::fields().owner())
526        .get(&mut db)
527        .await?;
528    let Owner::Human { human, .. } = object.owner else {
529        panic!("expected human")
530    };
531    assert!(human.is_unloaded());
532    assert_query_count(t, 1);
533
534    t.log().clear();
535    let missing = Object::filter_by_id(uuid::Uuid::new_v4())
536        .include(Object::fields().owner().human().human())
537        .exec(&mut db)
538        .await?;
539    assert!(missing.is_empty());
540    assert_query_count(t, 1);
541    Ok(())
542}
543
544fn assert_query_count(t: &Test, expected: usize) {
545    let mut count = 0;
546    while !t.log().is_empty() {
547        if !matches!(t.log().pop_op(), Operation::Transaction(_)) {
548            count += 1;
549        }
550    }
551    assert_eq!(count, expected);
552}
553
554#[driver_test]
555pub async fn create_eager_root_and_embedded_relations(t: &mut Test) -> Result<()> {
556    use toasty::stmt::IntoExpr;
557
558    #[derive(Debug, toasty::Model)]
559    struct Card {
560        #[key]
561        #[auto]
562        id: uuid::Uuid,
563        person_id: uuid::Uuid,
564        #[belongs_to(key = person_id)]
565        person: Person,
566        membership: Membership,
567    }
568
569    let mut db = t.setup_db(models!(Card, Person)).await;
570    let member: toasty::stmt::Expr<Person> = toasty::create!(Person { name: "Bob" }).into_expr();
571    let membership =
572        toasty::stmt::Expr::<Membership>::from_untyped(toasty_core::stmt::Expr::record([
573            toasty_core::stmt::Expr::null(),
574            member.into(),
575        ]));
576    let card = toasty::create!(Card {
577        person: toasty::create!(Person { name: "Alice" }),
578        membership,
579    })
580    .exec(&mut db)
581    .await?;
582
583    assert_eq!(card.person.name, "Alice");
584    assert_eq!(card.membership.person.name, "Bob");
585    assert_eq!(card.person_id, card.person.id);
586    assert_eq!(card.membership.id, card.membership.person.id);
587    let stored = Card::get_by_id(&mut db, card.id).await?;
588    assert_eq!(stored.person.id, card.person.id);
589    assert_eq!(stored.membership.person.id, card.membership.person.id);
590    Ok(())
591}
592
593#[driver_test]
594pub async fn create_eager_relation_without_key(t: &mut Test) -> Result<()> {
595    #[derive(Debug, toasty::Model)]
596    struct Card {
597        #[key]
598        #[auto]
599        id: uuid::Uuid,
600        person_id: Option<uuid::Uuid>,
601        #[belongs_to(key = person_id)]
602        person: Option<Person>,
603    }
604
605    let mut db = t.setup_db(models!(Card, Person)).await;
606    let card = toasty::create!(Card {}).exec(&mut db).await?;
607    assert_none!(card.person);
608    let stored = Card::get_by_id(&mut db, card.id).await?;
609    assert_none!(stored.person);
610    Ok(())
611}