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_has_many_create_returning_loads_relations(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 #[has_many]
155 posts: Vec<Post>,
156 }
157
158 #[derive(Debug, toasty::Model)]
159 struct Post {
160 #[key]
161 #[auto]
162 id: uuid::Uuid,
163 title: String,
164
165 #[index]
166 user_id: uuid::Uuid,
167
168 #[belongs_to(key = user_id, references = id)]
169 user: toasty::Deferred<User>,
170 }
171
172 let mut db = t.setup_db(models!(User, Post)).await;
173
174 let user = User::create()
175 .name("Alice")
176 .posts([Post::create().title("hello")])
177 .exec(&mut db)
178 .await?;
179
180 assert_eq!(user.name, "Alice");
181 assert_eq!(user.posts.len(), 1);
182 assert_eq!(user.posts[0].title, "hello");
183
184 Ok(())
185}
186
187#[driver_test]
188pub async fn eager_belongs_to_create_returning_loads_relation(t: &mut Test) -> Result<()> {
189 #[derive(Debug, toasty::Model)]
190 struct User {
191 #[key]
192 #[auto]
193 id: uuid::Uuid,
194 name: String,
195 }
196
197 #[derive(Debug, toasty::Model)]
198 struct Post {
199 #[key]
200 #[auto]
201 id: uuid::Uuid,
202 title: String,
203
204 #[index]
205 user_id: uuid::Uuid,
206
207 #[belongs_to(key = user_id, references = id)]
208 user: User,
209 }
210
211 let mut db = t.setup_db(models!(User, Post)).await;
212
213 let user = toasty::create!(User { name: "Alice" })
214 .exec(&mut db)
215 .await?;
216 let post = toasty::create!(Post {
217 title: "hello",
218 user: &user
219 })
220 .exec(&mut db)
221 .await?;
222
223 assert_eq!(post.title, "hello");
224 assert_eq!(post.user_id, user.id);
225 assert_eq!(post.user.id, user.id);
226 assert_eq!(post.user.name, "Alice");
227
228 Ok(())
229}
230
231#[driver_test]
232pub async fn eager_belongs_to_batch_create_returning_loads_relation(t: &mut Test) -> Result<()> {
233 #[derive(Debug, toasty::Model)]
234 struct User {
235 #[key]
236 #[auto]
237 id: uuid::Uuid,
238 name: String,
239 }
240
241 #[derive(Debug, toasty::Model)]
242 struct Post {
243 #[key]
244 #[auto]
245 id: uuid::Uuid,
246 title: String,
247
248 #[index]
249 user_id: uuid::Uuid,
250
251 #[belongs_to(key = user_id, references = id)]
252 user: User,
253 }
254
255 let mut db = t.setup_db(models!(User, Post)).await;
256
257 let alice = toasty::create!(User { name: "Alice" })
258 .exec(&mut db)
259 .await?;
260 let bob = toasty::create!(User { name: "Bob" }).exec(&mut db).await?;
261
262 let posts = toasty::create!(Post::[
263 { title: "a", user: &alice },
264 { title: "b", user: &bob },
265 ])
266 .exec(&mut db)
267 .await?;
268
269 assert_eq!(posts.len(), 2);
270 assert_eq!(posts[0].user.name, "Alice");
271 assert_eq!(posts[1].user.name, "Bob");
272
273 Ok(())
274}
275
276#[driver_test(requires(upsert_targeted_ignore))]
277pub async fn eager_belongs_to_upsert_or_ignore_returning_loads_relation(
278 t: &mut Test,
279) -> Result<()> {
280 #[derive(Debug, toasty::Model)]
281 struct User {
282 #[key]
283 #[auto]
284 id: uuid::Uuid,
285 name: String,
286 }
287
288 #[derive(Debug, toasty::Model)]
289 struct Post {
290 #[key]
291 id: uuid::Uuid,
292 title: String,
293
294 #[index]
295 user_id: uuid::Uuid,
296
297 #[belongs_to(key = user_id, references = id)]
298 user: User,
299 }
300
301 let mut db = t.setup_db(models!(User, Post)).await;
302
303 let user = toasty::create!(User { name: "Alice" })
304 .exec(&mut db)
305 .await?;
306
307 let post_id = uuid::Uuid::from_u128(7);
308 let created = Post::upsert_by_id(post_id)
309 .title("hello")
310 .user_id(user.id)
311 .or_ignore()
312 .exec(&mut db)
313 .await?;
314
315 let created = created.expect("insert succeeded");
316 assert_eq!(created.title, "hello");
317 assert_struct!(created.user, _ { id: == user.id, name: "Alice", .. });
318
319 let ignored = Post::upsert_by_id(post_id)
321 .title("other")
322 .user_id(user.id)
323 .or_ignore()
324 .exec(&mut db)
325 .await?;
326 assert_none!(ignored);
327
328 Ok(())
329}
330
331#[driver_test(requires(upsert_targeted_ignore))]
332pub async fn eager_belongs_to_upsert_or_ignore_conflict_issues_no_load(t: &mut Test) -> Result<()> {
333 use toasty_core::driver::Operation;
334
335 #[derive(Debug, toasty::Model)]
336 struct User {
337 #[key]
338 #[auto]
339 id: uuid::Uuid,
340 name: String,
341 }
342
343 #[derive(Debug, toasty::Model)]
344 struct Post {
345 #[key]
346 id: uuid::Uuid,
347 title: String,
348
349 #[index]
350 user_id: uuid::Uuid,
351
352 #[belongs_to(key = user_id, references = id)]
353 user: User,
354 }
355
356 let mut db = t.setup_db(models!(User, Post)).await;
357
358 let user = toasty::create!(User { name: "Alice" })
359 .exec(&mut db)
360 .await?;
361
362 let post_id = uuid::Uuid::from_u128(7);
363 Post::upsert_by_id(post_id)
364 .title("hello")
365 .user_id(user.id)
366 .or_ignore()
367 .exec(&mut db)
368 .await?
369 .expect("insert succeeded");
370
371 t.log().clear();
374 let ignored = Post::upsert_by_id(post_id)
375 .title("hello")
376 .user_id(user.id)
377 .or_ignore()
378 .exec(&mut db)
379 .await?;
380 assert_none!(ignored);
381
382 while !t.log().is_empty() {
383 let op = t.log().pop_op();
384 let is_read = match &op {
385 Operation::QuerySql(op) => op.stmt.is_query(),
386 Operation::GetByKey(_)
387 | Operation::QueryPk(_)
388 | Operation::FindPkByIndex(_)
389 | Operation::Scan(_) => true,
390 _ => false,
391 };
392 assert!(!is_read, "conflict path issued a read: {op:#?}");
393 }
394
395 Ok(())
396}
397
398#[driver_test]
399pub async fn eager_belongs_to_nested_create_returning_loads_relation(t: &mut Test) -> Result<()> {
400 #[derive(Debug, toasty::Model)]
401 struct User {
402 #[key]
403 #[auto]
404 id: uuid::Uuid,
405 name: String,
406
407 #[has_many]
408 posts: toasty::Deferred<Vec<Post>>,
409 }
410
411 #[derive(Debug, toasty::Model)]
412 struct Post {
413 #[key]
414 #[auto]
415 id: uuid::Uuid,
416 title: String,
417
418 #[index]
419 user_id: uuid::Uuid,
420
421 #[belongs_to(key = user_id, references = id)]
422 user: User,
423 }
424
425 let mut db = t.setup_db(models!(User, Post)).await;
426
427 let user = User::create()
430 .name("Alice")
431 .posts([Post::create().title("hello")])
432 .exec(&mut db)
433 .await?;
434
435 let posts: Vec<_> = user.posts().exec(&mut db).await?;
436 assert_eq!(posts.len(), 1);
437 assert_eq!(posts[0].user.name, "Alice");
438
439 Ok(())
440}
441
442#[driver_test]
443pub async fn eager_nested_relations_load_without_include(t: &mut Test) -> Result<()> {
444 #[derive(Debug, toasty::Model)]
445 struct User {
446 #[key]
447 id: uuid::Uuid,
448 name: String,
449
450 #[has_many]
451 posts: Vec<Post>,
452 }
453
454 #[derive(Debug, toasty::Model)]
455 struct Post {
456 #[key]
457 id: uuid::Uuid,
458 title: String,
459
460 #[index]
461 user_id: uuid::Uuid,
462
463 #[belongs_to(key = user_id, references = id)]
464 user: toasty::Deferred<User>,
465
466 #[has_many]
467 comments: Vec<Comment>,
468 }
469
470 #[derive(Debug, toasty::Model)]
471 struct Comment {
472 #[key]
473 id: uuid::Uuid,
474 body: String,
475
476 #[index]
477 post_id: uuid::Uuid,
478
479 #[belongs_to(key = post_id, references = id)]
480 post: toasty::Deferred<Post>,
481 }
482
483 let mut db = t.setup_db(models!(User, Post, Comment)).await;
484 let user_id = uuid::Uuid::from_u128(10);
485 let post_id = uuid::Uuid::from_u128(11);
486
487 insert_row::<User>(
488 &mut db,
489 vec![
490 stmt::Value::Uuid(user_id).into(),
491 stmt::Value::from("Alice").into(),
492 stmt::Value::Null.into(),
493 ],
494 )
495 .await?;
496 insert_row::<Post>(
497 &mut db,
498 vec![
499 stmt::Value::Uuid(post_id).into(),
500 stmt::Value::from("hello").into(),
501 stmt::Value::Uuid(user_id).into(),
502 stmt::Value::Null.into(),
503 stmt::Value::Null.into(),
504 ],
505 )
506 .await?;
507 insert_row::<Comment>(
508 &mut db,
509 vec![
510 stmt::Value::Uuid(uuid::Uuid::from_u128(12)).into(),
511 stmt::Value::from("first").into(),
512 stmt::Value::Uuid(post_id).into(),
513 stmt::Value::Null.into(),
514 ],
515 )
516 .await?;
517
518 let user = User::filter_by_id(user_id).get(&mut db).await?;
519
520 assert_eq!(user.posts.len(), 1);
521 assert_eq!(user.posts[0].title, "hello");
522 assert_eq!(user.posts[0].comments.len(), 1);
523 assert_eq!(user.posts[0].comments[0].body, "first");
524
525 Ok(())
526}
527
528#[driver_test]
529pub async fn eager_relations_reload_after_update(t: &mut Test) -> Result<()> {
530 #[derive(Debug, toasty::Model)]
531 struct User {
532 #[key]
533 id: uuid::Uuid,
534 name: String,
535
536 #[has_many]
537 posts: Vec<Post>,
538 }
539
540 #[derive(Debug, toasty::Model)]
541 struct Post {
542 #[key]
543 #[auto]
544 id: uuid::Uuid,
545 title: String,
546
547 #[index]
548 user_id: uuid::Uuid,
549
550 #[belongs_to(key = user_id, references = id)]
551 user: toasty::Deferred<User>,
552 }
553
554 let mut db = t.setup_db(models!(User, Post)).await;
555 let user_id = uuid::Uuid::from_u128(20);
556
557 insert_row::<User>(
558 &mut db,
559 vec![
560 stmt::Value::Uuid(user_id).into(),
561 stmt::Value::from("Alice").into(),
562 stmt::Value::Null.into(),
563 ],
564 )
565 .await?;
566
567 let mut user = User::filter_by_id(user_id).get(&mut db).await?;
568 assert!(user.posts.is_empty());
569
570 user.update()
571 .name("Alice Updated")
572 .posts(toasty::stmt::insert(Post::create().title("first")))
573 .exec(&mut db)
574 .await?;
575
576 let mut titles = user
577 .posts
578 .iter()
579 .map(|post| post.title.as_str())
580 .collect::<Vec<_>>();
581 titles.sort_unstable();
582
583 assert_eq!(user.name, "Alice Updated");
584 assert_eq!(titles, vec!["first"]);
585
586 Ok(())
587}
588
589#[driver_test]
590pub async fn eager_relation_cycle_is_rejected(t: &mut Test) -> Result<()> {
591 #[derive(Debug, toasty::Model)]
592 struct User {
593 #[key]
594 #[auto]
595 id: uuid::Uuid,
596
597 #[has_many]
598 posts: Vec<Post>,
599 }
600
601 #[derive(Debug, toasty::Model)]
602 struct Post {
603 #[key]
604 #[auto]
605 id: uuid::Uuid,
606
607 #[index]
608 user_id: uuid::Uuid,
609
610 #[belongs_to(key = user_id, references = id)]
611 user: User,
612 }
613
614 let err = t.try_setup_db(models!(User, Post)).await.unwrap_err();
615 let msg = err.to_string();
616
617 assert!(
618 msg.contains("eager relation cycle"),
619 "expected eager relation cycle error, got: {msg}"
620 );
621
622 Ok(())
623}
624
625#[driver_test]
626pub async fn eager_relation_self_cycle_is_rejected(t: &mut Test) -> Result<()> {
627 #[derive(Debug, toasty::Model)]
628 struct Node {
629 #[key]
630 #[auto]
631 id: uuid::Uuid,
632
633 #[index]
634 parent_id: Option<uuid::Uuid>,
635
636 #[belongs_to(key = parent_id, references = id)]
637 parent: toasty::Deferred<Option<Node>>,
638
639 #[has_many(pair = parent)]
640 children: Vec<Node>,
641 }
642
643 let err = t.try_setup_db(models!(Node)).await.unwrap_err();
644 let msg = err.to_string();
645
646 assert!(
647 msg.contains("eager relation cycle"),
648 "expected eager relation cycle error, got: {msg}"
649 );
650
651 Ok(())
652}
653
654#[driver_test]
655pub async fn eager_relation_long_cycle_is_rejected(t: &mut Test) -> Result<()> {
656 #[derive(Debug, toasty::Model)]
657 struct User {
658 #[key]
659 #[auto]
660 id: uuid::Uuid,
661
662 #[has_many]
663 posts: Vec<Post>,
664 }
665
666 #[derive(Debug, toasty::Model)]
667 struct Post {
668 #[key]
669 #[auto]
670 id: uuid::Uuid,
671
672 #[index]
673 user_id: uuid::Uuid,
674
675 #[belongs_to(key = user_id, references = id)]
676 user: toasty::Deferred<User>,
677
678 #[has_one]
679 detail: Option<Detail>,
680 }
681
682 #[derive(Debug, toasty::Model)]
683 struct Detail {
684 #[key]
685 #[auto]
686 id: uuid::Uuid,
687
688 #[unique]
689 post_id: uuid::Uuid,
690
691 #[belongs_to(key = post_id, references = id)]
692 post: toasty::Deferred<Post>,
693
694 #[index]
695 user_id: uuid::Uuid,
696
697 #[belongs_to(key = user_id, references = id)]
698 user: User,
699 }
700
701 let err = t
702 .try_setup_db(models!(User, Post, Detail))
703 .await
704 .unwrap_err();
705 let msg = err.to_string();
706
707 assert!(
708 msg.contains("eager relation cycle"),
709 "expected eager relation cycle error, got: {msg}"
710 );
711
712 Ok(())
713}
714
715async fn insert_row<M: Model>(db: &mut toasty::Db, fields: Vec<stmt::Expr>) -> Result<()> {
716 let insert = stmt::Insert {
717 target: stmt::InsertTarget::Model(<M as toasty::schema::Model>::id()),
718 source: stmt::Query::new_single(vec![stmt::Expr::record(fields)]),
719 upsert: None,
720 returning: None,
721 };
722
723 toasty::Statement::<()>::from_untyped_stmt(insert.into())
724 .exec(db)
725 .await
726}