1use crate::prelude::*;
2
3use toasty::schema::Model;
4use toasty_core::stmt;
5
6#[driver_test]
7pub async fn eager_has_many_and_has_one_load_without_include(t: &mut Test) -> Result<()> {
8 #[derive(Debug, toasty::Model)]
9 struct User {
10 #[key]
11 id: uuid::Uuid,
12 name: String,
13
14 #[has_many]
15 posts: Vec<Post>,
16
17 #[has_one]
18 profile: Option<Profile>,
19 }
20
21 #[derive(Debug, toasty::Model)]
22 struct Post {
23 #[key]
24 #[auto]
25 id: uuid::Uuid,
26 title: String,
27
28 #[index]
29 user_id: uuid::Uuid,
30
31 #[belongs_to(key = user_id, references = id)]
32 user: toasty::Deferred<User>,
33 }
34
35 #[derive(Debug, toasty::Model)]
36 struct Profile {
37 #[key]
38 id: uuid::Uuid,
39 bio: String,
40
41 #[unique]
42 user_id: uuid::Uuid,
43
44 #[belongs_to(key = user_id, references = id)]
45 user: toasty::Deferred<Option<User>>,
46 }
47
48 let mut db = t.setup_db(models!(User, Post, Profile)).await;
49 let user_id = uuid::Uuid::from_u128(1);
50
51 insert_row::<User>(
52 &mut db,
53 vec![
54 stmt::Value::Uuid(user_id).into(),
55 stmt::Value::from("Alice").into(),
56 stmt::Value::Null.into(),
57 stmt::Value::Null.into(),
58 ],
59 )
60 .await?;
61 insert_row::<Post>(
62 &mut db,
63 vec![
64 stmt::Value::Uuid(uuid::Uuid::from_u128(2)).into(),
65 stmt::Value::from("hello").into(),
66 stmt::Value::Uuid(user_id).into(),
67 stmt::Value::Null.into(),
68 ],
69 )
70 .await?;
71 insert_row::<Profile>(
72 &mut db,
73 vec![
74 stmt::Value::Uuid(uuid::Uuid::from_u128(3)).into(),
75 stmt::Value::from("writer").into(),
76 stmt::Value::Uuid(user_id).into(),
77 stmt::Value::Null.into(),
78 ],
79 )
80 .await?;
81
82 let user = User::filter_by_id(user_id).get(&mut db).await?;
83
84 assert_eq!(user.posts.len(), 1);
85 assert_eq!(user.posts[0].title, "hello");
86 assert_eq!(user.profile.as_ref().unwrap().bio, "writer");
87
88 Ok(())
89}
90
91#[driver_test]
92pub async fn eager_belongs_to_loads_without_include(t: &mut Test) -> Result<()> {
93 #[derive(Debug, toasty::Model)]
94 struct User {
95 #[key]
96 id: uuid::Uuid,
97 name: String,
98 }
99
100 #[derive(Debug, toasty::Model)]
101 struct Post {
102 #[key]
103 #[auto]
104 id: uuid::Uuid,
105 title: String,
106
107 #[index]
108 user_id: uuid::Uuid,
109
110 #[belongs_to(key = user_id, references = id)]
111 user: User,
112 }
113
114 let mut db = t.setup_db(models!(User, Post)).await;
115 let user_id = uuid::Uuid::from_u128(4);
116 let post_id = uuid::Uuid::from_u128(5);
117
118 insert_row::<User>(
119 &mut db,
120 vec![
121 stmt::Value::Uuid(user_id).into(),
122 stmt::Value::from("Alice").into(),
123 ],
124 )
125 .await?;
126 insert_row::<Post>(
127 &mut db,
128 vec![
129 stmt::Value::Uuid(post_id).into(),
130 stmt::Value::from("hello").into(),
131 stmt::Value::Uuid(user_id).into(),
132 stmt::Value::Null.into(),
133 ],
134 )
135 .await?;
136
137 let post = Post::filter_by_id(post_id).get(&mut db).await?;
138
139 assert_eq!(post.title, "hello");
140 assert_eq!(post.user.name, "Alice");
141
142 Ok(())
143}
144
145#[driver_test]
146pub async fn eager_belongs_to_create_by_fk_returning_loads_relation(t: &mut Test) -> Result<()> {
147 #[derive(Debug, toasty::Model)]
148 struct User {
149 #[key]
150 #[auto]
151 id: uuid::Uuid,
152 name: String,
153 }
154
155 #[derive(Debug, toasty::Model)]
156 struct Post {
157 #[key]
158 #[auto]
159 id: uuid::Uuid,
160
161 #[index]
162 user_id: uuid::Uuid,
163
164 #[belongs_to(key = user_id, references = id)]
165 user: User,
166 }
167
168 let mut db = t.setup_db(models!(User, Post)).await;
169
170 let user = toasty::create!(User { name: "Alice" })
171 .exec(&mut db)
172 .await?;
173
174 let post = toasty::create!(Post { user_id: user.id })
177 .exec(&mut db)
178 .await?;
179
180 assert_eq!(post.user.id, user.id);
181 assert_eq!(post.user.name, "Alice");
182
183 Ok(())
184}
185
186#[driver_test]
187pub async fn eager_has_many_create_returning_loads_relations(t: &mut Test) -> Result<()> {
188 #[derive(Debug, toasty::Model)]
189 struct User {
190 #[key]
191 #[auto]
192 id: uuid::Uuid,
193 name: String,
194
195 #[has_many]
196 posts: Vec<Post>,
197 }
198
199 #[derive(Debug, toasty::Model)]
200 struct Post {
201 #[key]
202 #[auto]
203 id: uuid::Uuid,
204 title: String,
205
206 #[index]
207 user_id: uuid::Uuid,
208
209 #[belongs_to(key = user_id, references = id)]
210 user: toasty::Deferred<User>,
211 }
212
213 let mut db = t.setup_db(models!(User, Post)).await;
214
215 let user = User::create()
216 .name("Alice")
217 .posts([Post::create().title("hello")])
218 .exec(&mut db)
219 .await?;
220
221 assert_eq!(user.name, "Alice");
222 assert_eq!(user.posts.len(), 1);
223 assert_eq!(user.posts[0].title, "hello");
224
225 Ok(())
226}
227
228#[driver_test]
229pub async fn eager_belongs_to_create_returning_loads_relation(t: &mut Test) -> Result<()> {
230 #[derive(Debug, toasty::Model)]
231 struct User {
232 #[key]
233 #[auto]
234 id: uuid::Uuid,
235 name: String,
236 }
237
238 #[derive(Debug, toasty::Model)]
239 struct Post {
240 #[key]
241 #[auto]
242 id: uuid::Uuid,
243 title: String,
244
245 #[index]
246 user_id: uuid::Uuid,
247
248 #[belongs_to(key = user_id, references = id)]
249 user: User,
250 }
251
252 let mut db = t.setup_db(models!(User, Post)).await;
253
254 let user = toasty::create!(User { name: "Alice" })
255 .exec(&mut db)
256 .await?;
257 let post = toasty::create!(Post {
258 title: "hello",
259 user: &user
260 })
261 .exec(&mut db)
262 .await?;
263
264 assert_eq!(post.title, "hello");
265 assert_eq!(post.user_id, user.id);
266 assert_eq!(post.user.id, user.id);
267 assert_eq!(post.user.name, "Alice");
268
269 Ok(())
270}
271
272#[driver_test]
273pub async fn eager_belongs_to_batch_create_returning_loads_relation(t: &mut Test) -> Result<()> {
274 #[derive(Debug, toasty::Model)]
275 struct User {
276 #[key]
277 #[auto]
278 id: uuid::Uuid,
279 name: String,
280 }
281
282 #[derive(Debug, toasty::Model)]
283 struct Post {
284 #[key]
285 #[auto]
286 id: uuid::Uuid,
287 title: String,
288
289 #[index]
290 user_id: uuid::Uuid,
291
292 #[belongs_to(key = user_id, references = id)]
293 user: User,
294 }
295
296 let mut db = t.setup_db(models!(User, Post)).await;
297
298 let alice = toasty::create!(User { name: "Alice" })
299 .exec(&mut db)
300 .await?;
301 let bob = toasty::create!(User { name: "Bob" }).exec(&mut db).await?;
302
303 let posts = toasty::create!(Post::[
304 { title: "a", user: &alice },
305 { title: "b", user: &bob },
306 ])
307 .exec(&mut db)
308 .await?;
309
310 assert_eq!(posts.len(), 2);
311 assert_eq!(posts[0].user.name, "Alice");
312 assert_eq!(posts[1].user.name, "Bob");
313
314 Ok(())
315}
316
317#[driver_test(requires(upsert_targeted_ignore))]
318pub async fn eager_belongs_to_upsert_or_ignore_returning_loads_relation(
319 t: &mut Test,
320) -> Result<()> {
321 #[derive(Debug, toasty::Model)]
322 struct User {
323 #[key]
324 #[auto]
325 id: uuid::Uuid,
326 name: String,
327 }
328
329 #[derive(Debug, toasty::Model)]
330 struct Post {
331 #[key]
332 id: uuid::Uuid,
333 title: String,
334
335 #[index]
336 user_id: uuid::Uuid,
337
338 #[belongs_to(key = user_id, references = id)]
339 user: User,
340 }
341
342 let mut db = t.setup_db(models!(User, Post)).await;
343
344 let user = toasty::create!(User { name: "Alice" })
345 .exec(&mut db)
346 .await?;
347
348 let post_id = uuid::Uuid::from_u128(7);
349 let created = Post::upsert_by_id(post_id)
350 .title("hello")
351 .user_id(user.id)
352 .or_ignore()
353 .exec(&mut db)
354 .await?;
355
356 let created = created.expect("insert succeeded");
357 assert_eq!(created.title, "hello");
358 assert_struct!(created.user, { id: == user.id, name: "Alice" });
359
360 let ignored = Post::upsert_by_id(post_id)
362 .title("other")
363 .user_id(user.id)
364 .or_ignore()
365 .exec(&mut db)
366 .await?;
367 assert_none!(ignored);
368
369 Ok(())
370}
371
372#[driver_test(requires(upsert_targeted_ignore))]
373pub async fn eager_belongs_to_upsert_or_ignore_conflict_issues_no_load(t: &mut Test) -> Result<()> {
374 use toasty_core::driver::Operation;
375
376 #[derive(Debug, toasty::Model)]
377 struct User {
378 #[key]
379 #[auto]
380 id: uuid::Uuid,
381 name: String,
382 }
383
384 #[derive(Debug, toasty::Model)]
385 struct Post {
386 #[key]
387 id: uuid::Uuid,
388 title: String,
389
390 #[index]
391 user_id: uuid::Uuid,
392
393 #[belongs_to(key = user_id, references = id)]
394 user: User,
395 }
396
397 let mut db = t.setup_db(models!(User, Post)).await;
398
399 let user = toasty::create!(User { name: "Alice" })
400 .exec(&mut db)
401 .await?;
402
403 let post_id = uuid::Uuid::from_u128(7);
404 Post::upsert_by_id(post_id)
405 .title("hello")
406 .user_id(user.id)
407 .or_ignore()
408 .exec(&mut db)
409 .await?
410 .expect("insert succeeded");
411
412 t.log().clear();
415 let ignored = Post::upsert_by_id(post_id)
416 .title("hello")
417 .user_id(user.id)
418 .or_ignore()
419 .exec(&mut db)
420 .await?;
421 assert_none!(ignored);
422
423 while !t.log().is_empty() {
424 let op = t.log().pop_op();
425 let is_read = match &op {
426 Operation::QuerySql(op) => op.stmt.is_query(),
427 Operation::GetByKey(_)
428 | Operation::QueryPk(_)
429 | Operation::FindPkByIndex(_)
430 | Operation::Scan(_) => true,
431 _ => false,
432 };
433 assert!(!is_read, "conflict path issued a read: {op:#?}");
434 }
435
436 Ok(())
437}
438
439#[driver_test]
440pub async fn eager_belongs_to_nested_create_returning_loads_relation(t: &mut Test) -> Result<()> {
441 #[derive(Debug, toasty::Model)]
442 struct User {
443 #[key]
444 #[auto]
445 id: uuid::Uuid,
446 name: String,
447
448 #[has_many]
449 posts: toasty::Deferred<Vec<Post>>,
450 }
451
452 #[derive(Debug, toasty::Model)]
453 struct Post {
454 #[key]
455 #[auto]
456 id: uuid::Uuid,
457 title: String,
458
459 #[index]
460 user_id: uuid::Uuid,
461
462 #[belongs_to(key = user_id, references = id)]
463 user: User,
464 }
465
466 let mut db = t.setup_db(models!(User, Post)).await;
467
468 let user = User::create()
471 .name("Alice")
472 .posts([Post::create().title("hello")])
473 .exec(&mut db)
474 .await?;
475
476 let posts: Vec<_> = user.posts().exec(&mut db).await?;
477 assert_eq!(posts.len(), 1);
478 assert_eq!(posts[0].user.name, "Alice");
479
480 Ok(())
481}
482
483#[driver_test]
484pub async fn eager_nested_relations_load_without_include(t: &mut Test) -> Result<()> {
485 #[derive(Debug, toasty::Model)]
486 struct User {
487 #[key]
488 id: uuid::Uuid,
489 name: String,
490
491 #[has_many]
492 posts: Vec<Post>,
493 }
494
495 #[derive(Debug, toasty::Model)]
496 struct Post {
497 #[key]
498 id: uuid::Uuid,
499 title: String,
500
501 #[index]
502 user_id: uuid::Uuid,
503
504 #[belongs_to(key = user_id, references = id)]
505 user: toasty::Deferred<User>,
506
507 #[has_many]
508 comments: Vec<Comment>,
509 }
510
511 #[derive(Debug, toasty::Model)]
512 struct Comment {
513 #[key]
514 id: uuid::Uuid,
515 body: String,
516
517 #[index]
518 post_id: uuid::Uuid,
519
520 #[belongs_to(key = post_id, references = id)]
521 post: toasty::Deferred<Post>,
522 }
523
524 let mut db = t.setup_db(models!(User, Post, Comment)).await;
525 let user_id = uuid::Uuid::from_u128(10);
526 let post_id = uuid::Uuid::from_u128(11);
527
528 insert_row::<User>(
529 &mut db,
530 vec![
531 stmt::Value::Uuid(user_id).into(),
532 stmt::Value::from("Alice").into(),
533 stmt::Value::Null.into(),
534 ],
535 )
536 .await?;
537 insert_row::<Post>(
538 &mut db,
539 vec![
540 stmt::Value::Uuid(post_id).into(),
541 stmt::Value::from("hello").into(),
542 stmt::Value::Uuid(user_id).into(),
543 stmt::Value::Null.into(),
544 stmt::Value::Null.into(),
545 ],
546 )
547 .await?;
548 insert_row::<Comment>(
549 &mut db,
550 vec![
551 stmt::Value::Uuid(uuid::Uuid::from_u128(12)).into(),
552 stmt::Value::from("first").into(),
553 stmt::Value::Uuid(post_id).into(),
554 stmt::Value::Null.into(),
555 ],
556 )
557 .await?;
558
559 let user = User::filter_by_id(user_id).get(&mut db).await?;
560
561 assert_eq!(user.posts.len(), 1);
562 assert_eq!(user.posts[0].title, "hello");
563 assert_eq!(user.posts[0].comments.len(), 1);
564 assert_eq!(user.posts[0].comments[0].body, "first");
565
566 Ok(())
567}
568
569#[driver_test]
570pub async fn eager_relations_reload_after_update(t: &mut Test) -> Result<()> {
571 #[derive(Debug, toasty::Model)]
572 struct User {
573 #[key]
574 id: uuid::Uuid,
575 name: String,
576
577 #[has_many]
578 posts: Vec<Post>,
579 }
580
581 #[derive(Debug, toasty::Model)]
582 struct Post {
583 #[key]
584 #[auto]
585 id: uuid::Uuid,
586 title: String,
587
588 #[index]
589 user_id: uuid::Uuid,
590
591 #[belongs_to(key = user_id, references = id)]
592 user: toasty::Deferred<User>,
593 }
594
595 let mut db = t.setup_db(models!(User, Post)).await;
596 let user_id = uuid::Uuid::from_u128(20);
597
598 insert_row::<User>(
599 &mut db,
600 vec![
601 stmt::Value::Uuid(user_id).into(),
602 stmt::Value::from("Alice").into(),
603 stmt::Value::Null.into(),
604 ],
605 )
606 .await?;
607
608 let mut user = User::filter_by_id(user_id).get(&mut db).await?;
609 assert!(user.posts.is_empty());
610
611 user.update()
612 .name("Alice Updated")
613 .posts(toasty::stmt::insert(Post::create().title("first")))
614 .exec(&mut db)
615 .await?;
616
617 let mut titles = user
618 .posts
619 .iter()
620 .map(|post| post.title.as_str())
621 .collect::<Vec<_>>();
622 titles.sort_unstable();
623
624 assert_eq!(user.name, "Alice Updated");
625 assert_eq!(titles, vec!["first"]);
626
627 Ok(())
628}
629
630#[driver_test]
631pub async fn eager_relation_cycle_is_rejected(t: &mut Test) -> Result<()> {
632 #[derive(Debug, toasty::Model)]
633 struct User {
634 #[key]
635 #[auto]
636 id: uuid::Uuid,
637
638 #[has_many]
639 posts: Vec<Post>,
640 }
641
642 #[derive(Debug, toasty::Model)]
643 struct Post {
644 #[key]
645 #[auto]
646 id: uuid::Uuid,
647
648 #[index]
649 user_id: uuid::Uuid,
650
651 #[belongs_to(key = user_id, references = id)]
652 user: User,
653 }
654
655 let err = t.try_setup_db(models!(User, Post)).await.unwrap_err();
656 let msg = err.to_string();
657
658 assert!(
659 msg.contains("eager relation cycle"),
660 "expected eager relation cycle error, got: {msg}"
661 );
662
663 Ok(())
664}
665
666#[driver_test]
667pub async fn eager_relation_self_cycle_is_rejected(t: &mut Test) -> Result<()> {
668 #[derive(Debug, toasty::Model)]
669 struct Node {
670 #[key]
671 #[auto]
672 id: uuid::Uuid,
673
674 #[index]
675 parent_id: Option<uuid::Uuid>,
676
677 #[belongs_to(key = parent_id, references = id)]
678 parent: toasty::Deferred<Option<Node>>,
679
680 #[has_many(pair = parent)]
681 children: Vec<Node>,
682 }
683
684 let err = t.try_setup_db(models!(Node)).await.unwrap_err();
685 let msg = err.to_string();
686
687 assert!(
688 msg.contains("eager relation cycle"),
689 "expected eager relation cycle error, got: {msg}"
690 );
691
692 Ok(())
693}
694
695#[driver_test]
696pub async fn eager_relation_long_cycle_is_rejected(t: &mut Test) -> Result<()> {
697 #[derive(Debug, toasty::Model)]
698 struct User {
699 #[key]
700 #[auto]
701 id: uuid::Uuid,
702
703 #[has_many]
704 posts: Vec<Post>,
705 }
706
707 #[derive(Debug, toasty::Model)]
708 struct Post {
709 #[key]
710 #[auto]
711 id: uuid::Uuid,
712
713 #[index]
714 user_id: uuid::Uuid,
715
716 #[belongs_to(key = user_id, references = id)]
717 user: toasty::Deferred<User>,
718
719 #[has_one]
720 detail: Option<Detail>,
721 }
722
723 #[derive(Debug, toasty::Model)]
724 struct Detail {
725 #[key]
726 #[auto]
727 id: uuid::Uuid,
728
729 #[unique]
730 post_id: uuid::Uuid,
731
732 #[belongs_to(key = post_id, references = id)]
733 post: toasty::Deferred<Post>,
734
735 #[index]
736 user_id: uuid::Uuid,
737
738 #[belongs_to(key = user_id, references = id)]
739 user: User,
740 }
741
742 let err = t
743 .try_setup_db(models!(User, Post, Detail))
744 .await
745 .unwrap_err();
746 let msg = err.to_string();
747
748 assert!(
749 msg.contains("eager relation cycle"),
750 "expected eager relation cycle error, got: {msg}"
751 );
752
753 Ok(())
754}
755
756async fn insert_row<M: Model>(db: &mut toasty::Db, fields: Vec<stmt::Expr>) -> Result<()> {
757 let insert = stmt::Insert {
758 target: stmt::InsertTarget::Model(<M as toasty::schema::Model>::id()),
759 source: stmt::Query::new_single(vec![stmt::Expr::record(fields)]),
760 upsert: None,
761 returning: None,
762 };
763
764 toasty::Statement::<()>::from_untyped_stmt(insert.into())
765 .exec(db)
766 .await
767}