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}