1use toasty::schema::{
2 app::FieldTy,
3 mapping::{self, FieldPrimitive, FieldStruct},
4};
5use toasty_core::stmt;
6use uuid::Uuid;
7
8use crate::prelude::*;
9
10#[driver_test]
13pub async fn basic_embedded_struct(test: &mut Test) {
14 #[derive(toasty::Embed)]
15 struct Address {
16 street: String,
17 city: String,
18 }
19
20 let db = test.setup_db(models!(Address)).await;
21 let schema = db.schema();
22
23 assert_struct!(schema.app.models, #{
25 Address::id(): toasty::schema::app::Model::EmbeddedStruct(_ {
26 name.upper_camel_case(): "Address",
27 fields: [
28 _ { name.app_name: "street", .. },
29 _ { name.app_name: "city", .. },
30 ],
31 ..
32 }),
33 });
34
35 assert!(schema.db.tables.is_empty());
37}
38
39#[driver_test]
44pub async fn root_model_with_embedded_field(test: &mut Test) {
45 #[derive(toasty::Embed)]
46 struct Address {
47 street: String,
48 city: String,
49 }
50
51 #[derive(toasty::Model)]
52 struct User {
53 #[key]
54 id: String,
55 #[allow(dead_code)]
56 address: Address,
57 }
58
59 let db = test.setup_db(models!(User, Address)).await;
60 let schema = db.schema();
61
62 assert_struct!(schema.app.models, #{
64 Address::id(): toasty::schema::app::Model::EmbeddedStruct(_ {
65 name.upper_camel_case(): "Address",
66 fields: [
67 _ { name.app_name: "street", .. },
68 _ { name.app_name: "city", .. },
69 ],
70 ..
71 }),
72 User::id(): toasty::schema::app::Model::Root(_ {
73 name.upper_camel_case(): "User",
74 fields: [
75 _ { name.app_name: "id", .. },
76 _ {
77 name.app_name: "address",
78 ty: FieldTy::Embedded(_ {
79 target: == Address::id(),
80 ..
81 }),
82 ..
83 },
84 ],
85 ..
86 }),
87 });
88
89 assert_struct!(schema.db.tables, [
92 _ {
93 name: =~ r"users$",
94 columns: [
95 _ { name: "id", .. },
96 _ { name: "address_street", .. },
97 _ { name: "address_city", .. },
98 ],
99 ..
100 },
101 ]);
102
103 let user = &schema.app.models[&User::id()];
104 let user_table = schema.table_for(user);
105 let user_mapping = &schema.mapping.models[&User::id()];
106
107 assert_struct!(user_mapping, _ {
111 columns.len(): 3,
112 fields: [
113 mapping::Field::Primitive(FieldPrimitive {
114 column: == user_table.columns[0].id,
115 lowering: 0,
116 ..
117 }),
118 mapping::Field::Struct(FieldStruct {
119 fields: [
120 mapping::Field::Primitive(FieldPrimitive {
121 column: == user_table.columns[1].id,
122 lowering: 1,
123 ..
124 }),
125 mapping::Field::Primitive(FieldPrimitive {
126 column: == user_table.columns[2].id,
127 lowering: 2,
128 ..
129 }),
130 ],
131 ..
132 }),
133 ],
134 model_to_table.fields: [
135 _,
136 == stmt::Expr::project(
137 stmt::Expr::ref_self_field(user.expect_root().fields[1].id),
138 [0],
139 ),
140 == stmt::Expr::project(
141 stmt::Expr::ref_self_field(user.expect_root().fields[1].id),
142 [1],
143 ),
144 ],
145 ..
146 });
147
148 let table_to_model = user_mapping
151 .table_to_model
152 .lower_returning_model()
153 .into_record();
154
155 assert_struct!(
156 table_to_model.fields,
157 [
158 _,
159 stmt::Expr::Record(stmt::ExprRecord {
160 fields: [
161 == stmt::Expr::column(user_table.columns[1].id),
162 == stmt::Expr::column(user_table.columns[2].id),
163 ],
164 }),
165 ]
166 );
167}
168
169#[driver_test(id(ID))]
172pub async fn create_and_query_embedded(t: &mut Test) -> Result<()> {
173 #[derive(Debug, toasty::Embed)]
174 struct Address {
175 street: String,
176 city: String,
177 }
178
179 #[derive(Debug, toasty::Model)]
180 struct User {
181 #[key]
182 #[auto]
183 id: ID,
184 name: String,
185 address: Address,
186 }
187
188 let mut db = t.setup_db(models!(User, Address)).await;
189
190 let mut user = User::create()
191 .name("Alice")
192 .address(Address {
193 street: "123 Main St".to_string(),
194 city: "Springfield".to_string(),
195 })
196 .exec(&mut db)
197 .await?;
198
199 let found = User::get_by_id(&mut db, &user.id).await?;
201 assert_eq!(found.address.street, "123 Main St");
202 assert_eq!(found.address.city, "Springfield");
203
204 user.update()
206 .address(Address {
207 street: "456 Oak Ave".to_string(),
208 city: "Shelbyville".to_string(),
209 })
210 .exec(&mut db)
211 .await?;
212
213 let found = User::get_by_id(&mut db, &user.id).await?;
214 assert_eq!(found.address.street, "456 Oak Ave");
215
216 User::filter_by_id(user.id)
218 .update()
219 .address(Address {
220 street: "789 Pine Rd".to_string(),
221 city: "Capital City".to_string(),
222 })
223 .exec(&mut db)
224 .await?;
225
226 let found = User::get_by_id(&mut db, &user.id).await?;
227 assert_eq!(found.address.street, "789 Pine Rd");
228
229 let id = user.id;
231 user.delete().exec(&mut db).await?;
232 assert_err!(User::get_by_id(&mut db, &id).await);
233 Ok(())
234}
235
236#[driver_test]
242pub async fn embedded_struct_fields_codegen(test: &mut Test) {
243 #[derive(Debug, toasty::Embed)]
244 struct Address {
245 street: String,
246 city: String,
247 zip: String,
248 }
249
250 #[derive(Debug, toasty::Model)]
251 #[allow(dead_code)]
252 struct User {
253 #[key]
254 #[auto]
255 id: uuid::Uuid,
256 name: String,
257 address: Address,
258 }
259
260 let _db = test.setup_db(models!(User, Address)).await;
261
262 let _city_path = User::fields().address().city();
264
265 let address_fields = User::fields().address();
267 let _city_path_2 = address_fields.city();
268
269 let _address_city = Address::fields().city();
271
272 let _query = User::all().filter(User::fields().address().city().eq("Seattle"));
274}
275
276#[driver_test]
283pub async fn query_embedded_struct_fields(t: &mut Test) -> Result<()> {
284 #[derive(Debug, toasty::Embed)]
285 struct Address {
286 street: String,
287 city: String,
288 zip: String,
289 }
290
291 #[derive(Debug, toasty::Model)]
292 #[key(partition = country, local = id)]
293 #[allow(dead_code)]
294 struct User {
295 #[auto]
296 id: uuid::Uuid,
297 country: String,
298 name: String,
299 address: Address,
300 }
301
302 let mut db = t.setup_db(models!(User, Address)).await;
303
304 let users_data = [
306 ("USA", "Alice", "123 Main St", "Seattle", "98101"),
307 ("USA", "Bob", "456 Oak Ave", "Seattle", "98102"),
308 ("USA", "Charlie", "789 Pine Rd", "Portland", "97201"),
309 ("USA", "Diana", "321 Elm St", "Portland", "97202"),
310 ("CAN", "Eve", "111 Maple Dr", "Vancouver", "V6B 1A1"),
311 ("CAN", "Frank", "222 Cedar Ln", "Vancouver", "V6B 2B2"),
312 ("CAN", "Grace", "333 Birch Way", "Toronto", "M5H 1A1"),
313 ];
314
315 for (country, name, street, city, zip) in users_data {
316 User::create()
317 .country(country)
318 .name(name)
319 .address(Address {
320 street: street.to_string(),
321 city: city.to_string(),
322 zip: zip.to_string(),
323 })
324 .exec(&mut db)
325 .await?;
326 }
327
328 let mut all_users = Vec::new();
330 for country in ["USA", "CAN"] {
331 let mut users = User::filter(User::fields().country().eq(country))
332 .exec(&mut db)
333 .await?;
334 all_users.append(&mut users);
335 }
336 assert_eq!(all_users.len(), 7);
337
338 let seattle_users = User::filter(
341 User::fields()
342 .country()
343 .eq("USA")
344 .and(User::fields().address().city().eq("Seattle")),
345 )
346 .exec(&mut db)
347 .await?;
348
349 assert_eq!(seattle_users.len(), 2);
350 let mut names: Vec<_> = seattle_users.iter().map(|u| u.name.as_str()).collect();
351 names.sort();
352 assert_eq!(names, ["Alice", "Bob"]);
353
354 let vancouver_users = User::filter(
356 User::fields()
357 .country()
358 .eq("CAN")
359 .and(User::fields().address().city().eq("Vancouver")),
360 )
361 .exec(&mut db)
362 .await?;
363
364 assert_eq!(vancouver_users.len(), 2);
365
366 let user_98101 = User::filter(
368 User::fields()
369 .country()
370 .eq("USA")
371 .and(User::fields().address().zip().eq("98101")),
372 )
373 .exec(&mut db)
374 .await?;
375
376 assert_eq!(user_98101.len(), 1);
377 assert_eq!(user_98101[0].name, "Alice");
378 Ok(())
379}
380
381#[driver_test(requires(sql))]
385pub async fn query_embedded_fields_comparison_ops(t: &mut Test) -> Result<()> {
386 #[derive(Debug, toasty::Embed)]
387 struct Stats {
388 score: i64,
389 rank: i64,
390 }
391
392 #[derive(Debug, toasty::Model)]
393 #[allow(dead_code)]
394 struct Player {
395 #[key]
396 #[auto]
397 id: uuid::Uuid,
398 name: String,
399 stats: Stats,
400 }
401
402 let mut db = t.setup_db(models!(Player, Stats)).await;
403
404 for (name, score, rank) in [
405 ("Alice", 100, 1),
406 ("Bob", 85, 2),
407 ("Charlie", 70, 3),
408 ("Diana", 55, 4),
409 ("Eve", 40, 5),
410 ] {
411 Player::create()
412 .name(name)
413 .stats(Stats { score, rank })
414 .exec(&mut db)
415 .await?;
416 }
417
418 let high_scorers = Player::filter(Player::fields().stats().score().gt(80))
420 .exec(&mut db)
421 .await?;
422 assert_eq!(high_scorers.len(), 2);
423
424 let low_scorers = Player::filter(Player::fields().stats().score().le(55))
426 .exec(&mut db)
427 .await?;
428 assert_eq!(low_scorers.len(), 2);
429
430 let not_charlie = Player::filter(Player::fields().stats().score().ne(70))
432 .exec(&mut db)
433 .await?;
434 assert_eq!(not_charlie.len(), 4);
435
436 let mid_to_high = Player::filter(Player::fields().stats().score().ge(70))
438 .exec(&mut db)
439 .await?;
440 assert_eq!(mid_to_high.len(), 3);
441 Ok(())
442}
443
444#[driver_test(requires(sql))]
448pub async fn query_embedded_multiple_fields(t: &mut Test) -> Result<()> {
449 #[derive(Debug, toasty::Embed)]
450 struct Coordinates {
451 x: i64,
452 y: i64,
453 z: i64,
454 }
455
456 #[derive(Debug, toasty::Model)]
457 #[allow(dead_code)]
458 struct Location {
459 #[key]
460 #[auto]
461 id: uuid::Uuid,
462 name: String,
463 coords: Coordinates,
464 }
465
466 let mut db = t.setup_db(models!(Location, Coordinates)).await;
467
468 for (name, x, y, z) in [
469 ("Origin", 0, 0, 0),
470 ("Point A", 10, 20, 0),
471 ("Point B", 10, 30, 0),
472 ("Point C", 10, 20, 5),
473 ("Point D", 20, 20, 0),
474 ] {
475 Location::create()
476 .name(name)
477 .coords(Coordinates { x, y, z })
478 .exec(&mut db)
479 .await?;
480 }
481
482 let matching = Location::filter(
484 Location::fields()
485 .coords()
486 .x()
487 .eq(10)
488 .and(Location::fields().coords().y().eq(20)),
489 )
490 .exec(&mut db)
491 .await?;
492
493 assert_eq!(matching.len(), 2);
494 let mut names: Vec<_> = matching.iter().map(|l| l.name.as_str()).collect();
495 names.sort();
496 assert_eq!(names, ["Point A", "Point C"]);
497
498 let exact_match = Location::filter(
501 Location::fields()
502 .coords()
503 .x()
504 .eq(10)
505 .and(Location::fields().coords().y().eq(20))
506 .and(Location::fields().coords().z().eq(0)),
507 )
508 .exec(&mut db)
509 .await?;
510
511 assert_eq!(exact_match.len(), 1);
512 assert_eq!(exact_match[0].name, "Point A");
513 Ok(())
514}
515
516#[driver_test(requires(sql))]
520pub async fn update_with_embedded_field_filter(t: &mut Test) -> Result<()> {
521 #[derive(Debug, toasty::Embed)]
522 struct Metadata {
523 version: i64,
524 status: String,
525 }
526
527 #[derive(Debug, toasty::Model)]
528 #[allow(dead_code)]
529 struct Document {
530 #[key]
531 #[auto]
532 id: uuid::Uuid,
533 title: String,
534 meta: Metadata,
535 }
536
537 let mut db = t.setup_db(models!(Document, Metadata)).await;
538
539 for (title, version, status) in [
541 ("Doc A", 1, "draft"),
542 ("Doc B", 2, "draft"),
543 ("Doc C", 1, "published"),
544 ] {
545 Document::create()
546 .title(title)
547 .meta(Metadata {
548 version,
549 status: status.to_string(),
550 })
551 .exec(&mut db)
552 .await?;
553 }
554
555 Document::filter(
558 Document::fields()
559 .meta()
560 .status()
561 .eq("draft")
562 .and(Document::fields().meta().version().eq(1)),
563 )
564 .update()
565 .meta(Metadata {
566 version: 2,
567 status: "draft".to_string(),
568 })
569 .exec(&mut db)
570 .await?;
571
572 let doc_a = Document::filter(Document::fields().title().eq("Doc A"))
574 .exec(&mut db)
575 .await?;
576 assert_eq!(doc_a[0].meta.version, 2);
577
578 let doc_b = Document::filter(Document::fields().title().eq("Doc B"))
580 .exec(&mut db)
581 .await?;
582 assert_eq!(doc_b[0].meta.version, 2);
583
584 let doc_c = Document::filter(Document::fields().title().eq("Doc C"))
586 .exec(&mut db)
587 .await?;
588 assert_eq!(doc_c[0].meta.version, 1);
589 Ok(())
590}
591
592#[driver_test(id(ID))]
596pub async fn partial_update_embedded_fields(t: &mut Test) -> Result<()> {
597 #[derive(Debug, toasty::Embed)]
598 struct Address {
599 street: String,
600 city: String,
601 zip: String,
602 }
603
604 #[derive(Debug, toasty::Model)]
605 struct User {
606 #[key]
607 #[auto]
608 id: ID,
609 name: String,
610 address: Address,
611 }
612
613 let mut db = t.setup_db(models!(User, Address)).await;
614
615 let mut user = User::create()
617 .name("Alice")
618 .address(Address {
619 street: "123 Main St".to_string(),
620 city: "Boston".to_string(),
621 zip: "02101".to_string(),
622 })
623 .exec(&mut db)
624 .await?;
625
626 assert_struct!(user.address, _ {
628 street: "123 Main St",
629 city: "Boston",
630 zip: "02101",
631 ..
632 });
633
634 user.update()
636 .with_address(|a| {
637 a.city("Seattle");
638 })
639 .exec(&mut db)
640 .await?;
641
642 assert_struct!(user.address, _ {
644 street: "123 Main St",
645 city: "Seattle",
646 zip: "02101",
647 ..
648 });
649
650 let found = User::get_by_id(&mut db, &user.id).await?;
652 assert_struct!(found.address, _ {
653 street: "123 Main St",
654 city: "Seattle",
655 zip: "02101",
656 ..
657 });
658
659 user.update()
661 .with_address(|a| {
662 a.city("Portland").zip("97201");
663 })
664 .exec(&mut db)
665 .await?;
666
667 assert_struct!(user.address, _ {
669 street: "123 Main St",
670 city: "Portland",
671 zip: "97201",
672 ..
673 });
674
675 let found = User::get_by_id(&mut db, &user.id).await?;
677 assert_struct!(found.address, _ {
678 street: "123 Main St",
679 city: "Portland",
680 zip: "97201",
681 ..
682 });
683
684 user.update()
686 .with_address(|a| {
687 a.street("456 Oak Ave");
688 })
689 .with_address(|a| {
690 a.zip("97202");
691 })
692 .exec(&mut db)
693 .await?;
694
695 assert_struct!(user.address, _ {
697 street: "456 Oak Ave",
698 city: "Portland",
699 zip: "97202",
700 ..
701 });
702
703 let found = User::get_by_id(&mut db, &user.id).await?;
705 assert_struct!(found.address, _ {
706 street: "456 Oak Ave",
707 city: "Portland",
708 zip: "97202",
709 ..
710 });
711 Ok(())
712}
713
714#[driver_test]
722pub async fn deeply_nested_embedded_schema(test: &mut Test) {
723 #[derive(toasty::Embed)]
725 struct Location {
726 lat: i64,
727 lon: i64,
728 }
729
730 #[derive(toasty::Embed)]
731 struct City {
732 name: String,
733 location: Location,
734 }
735
736 #[derive(toasty::Embed)]
737 struct Address {
738 street: String,
739 city: City,
740 }
741
742 #[derive(toasty::Model)]
743 struct User {
744 #[key]
745 id: String,
746 #[allow(dead_code)]
747 address: Address,
748 }
749
750 let db = test.setup_db(models!(User, Address, City, Location)).await;
751 let schema = db.schema();
752
753 assert_struct!(schema.app.models, #{
755 Location::id(): toasty::schema::app::Model::EmbeddedStruct(_ {
756 name.upper_camel_case(): "Location",
757 fields.len(): 2,
758 ..
759 }),
760 City::id(): toasty::schema::app::Model::EmbeddedStruct(_ {
761 name.upper_camel_case(): "City",
762 fields: [
763 _ { name.app_name: "name", .. },
764 _ {
765 name.app_name: "location",
766 ty: FieldTy::Embedded(_ {
767 target: == Location::id(),
768 ..
769 }),
770 ..
771 },
772 ],
773 ..
774 }),
775 Address::id(): toasty::schema::app::Model::EmbeddedStruct(_ {
776 name.upper_camel_case(): "Address",
777 fields: [
778 _ { name.app_name: "street", .. },
779 _ {
780 name.app_name: "city",
781 ty: FieldTy::Embedded(_ {
782 target: == City::id(),
783 ..
784 }),
785 ..
786 },
787 ],
788 ..
789 }),
790 User::id(): toasty::schema::app::Model::Root(_ {
791 name.upper_camel_case(): "User",
792 fields: [
793 _ { name.app_name: "id", .. },
794 _ {
795 name.app_name: "address",
796 ty: FieldTy::Embedded(_ {
797 target: == Address::id(),
798 ..
799 }),
800 ..
801 },
802 ],
803 ..
804 }),
805 });
806
807 assert_struct!(schema.db.tables, [
815 _ {
816 name: =~ r"users$",
817 columns: [
818 _ { name: "id", .. },
819 _ { name: "address_street", .. },
820 _ { name: "address_city_name", .. },
821 _ { name: "address_city_location_lat", .. },
822 _ { name: "address_city_location_lon", .. },
823 ],
824 ..
825 },
826 ]);
827
828 let user = &schema.app.models[&User::id()];
829 let user_table = schema.table_for(user);
830 let user_mapping = &schema.mapping.models[&User::id()];
831
832 assert_eq!(
845 user_mapping.fields.len(),
846 2,
847 "User should have 2 fields: id and address"
848 );
849
850 let address_field = user_mapping.fields[1]
852 .as_struct()
853 .expect("User.address should be Field::Struct");
854
855 assert_eq!(
856 address_field.fields.len(),
857 2,
858 "Address should have 2 fields: street and city"
859 );
860
861 let street_field = address_field.fields[0]
863 .as_primitive()
864 .expect("Address.street should be Field::Primitive");
865 assert_eq!(
866 street_field.column, user_table.columns[1].id,
867 "street should map to address_street column"
868 );
869
870 let city_field = address_field.fields[1]
872 .as_struct()
873 .expect("Address.city should be Field::Struct");
874
875 assert_eq!(
876 city_field.fields.len(),
877 2,
878 "City should have 2 fields: name and location"
879 );
880
881 let city_name_field = city_field.fields[0]
883 .as_primitive()
884 .expect("City.name should be Field::Primitive");
885 assert_eq!(
886 city_name_field.column, user_table.columns[2].id,
887 "city.name should map to address_city_name column"
888 );
889
890 let location_field = city_field.fields[1]
892 .as_struct()
893 .expect("City.location should be Field::Struct");
894
895 assert_eq!(
896 location_field.fields.len(),
897 2,
898 "Location should have 2 fields: lat and lon"
899 );
900
901 let lat_field = location_field.fields[0]
903 .as_primitive()
904 .expect("Location.lat should be Field::Primitive");
905 assert_eq!(
906 lat_field.column, user_table.columns[3].id,
907 "location.lat should map to address_city_location_lat column"
908 );
909
910 let lon_field = location_field.fields[1]
912 .as_primitive()
913 .expect("Location.lon should be Field::Primitive");
914 assert_eq!(
915 lon_field.column, user_table.columns[4].id,
916 "location.lon should map to address_city_location_lon column"
917 );
918
919 assert_eq!(
922 address_field.columns.len(),
923 4,
924 "Address.columns should have 4 entries"
925 );
926 assert!(
927 address_field
928 .columns
929 .contains_key(&user_table.columns[1].id),
930 "Address.columns should contain address_street"
931 );
932 assert!(
933 address_field
934 .columns
935 .contains_key(&user_table.columns[2].id),
936 "Address.columns should contain address_city_name"
937 );
938 assert!(
939 address_field
940 .columns
941 .contains_key(&user_table.columns[3].id),
942 "Address.columns should contain address_city_location_lat"
943 );
944 assert!(
945 address_field
946 .columns
947 .contains_key(&user_table.columns[4].id),
948 "Address.columns should contain address_city_location_lon"
949 );
950
951 assert_eq!(
953 city_field.columns.len(),
954 3,
955 "City.columns should have 3 entries"
956 );
957 assert!(
958 city_field.columns.contains_key(&user_table.columns[2].id),
959 "City.columns should contain address_city_name"
960 );
961 assert!(
962 city_field.columns.contains_key(&user_table.columns[3].id),
963 "City.columns should contain address_city_location_lat"
964 );
965 assert!(
966 city_field.columns.contains_key(&user_table.columns[4].id),
967 "City.columns should contain address_city_location_lon"
968 );
969
970 assert_eq!(
972 location_field.columns.len(),
973 2,
974 "Location.columns should have 2 entries"
975 );
976 assert!(
977 location_field
978 .columns
979 .contains_key(&user_table.columns[3].id),
980 "Location.columns should contain address_city_location_lat"
981 );
982 assert!(
983 location_field
984 .columns
985 .contains_key(&user_table.columns[4].id),
986 "Location.columns should contain address_city_location_lon"
987 );
988
989 assert_eq!(
992 user_mapping.model_to_table.len(),
993 5,
994 "model_to_table should have 5 expressions"
995 );
996
997 assert_struct!(
999 user_mapping.model_to_table[1],
1000 == stmt::Expr::project(
1001 stmt::Expr::ref_self_field(user.expect_root().fields[1].id),
1002 [0],
1003 )
1004 );
1005
1006 assert_struct!(
1008 user_mapping.model_to_table[2],
1009 == stmt::Expr::project(
1010 stmt::Expr::ref_self_field(user.expect_root().fields[1].id),
1011 [1, 0],
1012 )
1013 );
1014
1015 assert_struct!(
1017 user_mapping.model_to_table[3],
1018 == stmt::Expr::project(
1019 stmt::Expr::ref_self_field(user.expect_root().fields[1].id),
1020 [1, 1, 0],
1021 )
1022 );
1023
1024 assert_struct!(
1026 user_mapping.model_to_table[4],
1027 == stmt::Expr::project(
1028 stmt::Expr::ref_self_field(user.expect_root().fields[1].id),
1029 [1, 1, 1],
1030 )
1031 );
1032}
1033
1034#[driver_test(id(ID))]
1038pub async fn crud_nested_embedded(t: &mut Test) -> Result<()> {
1039 #[derive(Debug, toasty::Embed)]
1040 struct Address {
1041 street: String,
1042 city: String,
1043 }
1044
1045 #[derive(Debug, toasty::Embed)]
1046 struct Office {
1047 name: String,
1048 address: Address,
1049 }
1050
1051 #[derive(Debug, toasty::Model)]
1052 struct Company {
1053 #[key]
1054 #[auto]
1055 id: ID,
1056 name: String,
1057 headquarters: Office,
1058 }
1059
1060 let mut db = t.setup_db(models!(Company, Office, Address)).await;
1061
1062 let mut company = Company::create()
1064 .name("Acme")
1065 .headquarters(Office {
1066 name: "Main Office".to_string(),
1067 address: Address {
1068 street: "123 Main St".to_string(),
1069 city: "Springfield".to_string(),
1070 },
1071 })
1072 .exec(&mut db)
1073 .await?;
1074
1075 assert_struct!(company.headquarters, _ {
1076 name: "Main Office",
1077 address: _ {
1078 street: "123 Main St",
1079 city: "Springfield",
1080 ..
1081 },
1082 ..
1083 });
1084
1085 let found = Company::get_by_id(&mut db, &company.id).await?;
1087 assert_struct!(found.headquarters, _ {
1088 name: "Main Office",
1089 address: _ {
1090 street: "123 Main St",
1091 city: "Springfield",
1092 ..
1093 },
1094 ..
1095 });
1096
1097 company
1099 .update()
1100 .headquarters(Office {
1101 name: "West Coast HQ".to_string(),
1102 address: Address {
1103 street: "456 Oak Ave".to_string(),
1104 city: "Seattle".to_string(),
1105 },
1106 })
1107 .exec(&mut db)
1108 .await?;
1109
1110 let found = Company::get_by_id(&mut db, &company.id).await?;
1111 assert_struct!(found.headquarters, _ {
1112 name: "West Coast HQ",
1113 address: _ {
1114 street: "456 Oak Ave",
1115 city: "Seattle",
1116 ..
1117 },
1118 ..
1119 });
1120
1121 Company::filter_by_id(company.id)
1123 .update()
1124 .headquarters(Office {
1125 name: "East Coast HQ".to_string(),
1126 address: Address {
1127 street: "789 Pine Rd".to_string(),
1128 city: "Boston".to_string(),
1129 },
1130 })
1131 .exec(&mut db)
1132 .await?;
1133
1134 let found = Company::get_by_id(&mut db, &company.id).await?;
1135 assert_struct!(found.headquarters, _ {
1136 name: "East Coast HQ",
1137 address: _ {
1138 street: "789 Pine Rd",
1139 city: "Boston",
1140 ..
1141 },
1142 ..
1143 });
1144
1145 let id = company.id;
1147 company.delete().exec(&mut db).await?;
1148 assert_err!(Company::get_by_id(&mut db, &id).await);
1149 Ok(())
1150}
1151
1152#[driver_test(id(ID))]
1156pub async fn partial_update_nested_embedded(t: &mut Test) -> Result<()> {
1157 #[derive(Debug, toasty::Embed)]
1158 struct Address {
1159 street: String,
1160 city: String,
1161 }
1162
1163 #[derive(Debug, toasty::Embed)]
1164 struct Office {
1165 name: String,
1166 address: Address,
1167 }
1168
1169 #[derive(Debug, toasty::Model)]
1170 struct Company {
1171 #[key]
1172 #[auto]
1173 id: ID,
1174 name: String,
1175 headquarters: Office,
1176 }
1177
1178 let mut db = t.setup_db(models!(Company, Office, Address)).await;
1179
1180 let mut company = Company::create()
1181 .name("Acme")
1182 .headquarters(Office {
1183 name: "Main Office".to_string(),
1184 address: Address {
1185 street: "123 Main St".to_string(),
1186 city: "Boston".to_string(),
1187 },
1188 })
1189 .exec(&mut db)
1190 .await?;
1191
1192 company
1195 .update()
1196 .with_headquarters(|h| {
1197 h.with_address(|a| {
1198 a.city("Seattle");
1199 });
1200 })
1201 .exec(&mut db)
1202 .await?;
1203
1204 let found = Company::get_by_id(&mut db, &company.id).await?;
1205 assert_struct!(found.headquarters, _ {
1206 name: "Main Office",
1207 address: _ {
1208 street: "123 Main St",
1209 city: "Seattle",
1210 ..
1211 },
1212 ..
1213 });
1214
1215 company
1218 .update()
1219 .with_headquarters(|h| {
1220 h.name("West Coast HQ");
1221 })
1222 .exec(&mut db)
1223 .await?;
1224
1225 let found = Company::get_by_id(&mut db, &company.id).await?;
1226 assert_struct!(found.headquarters, _ {
1227 name: "West Coast HQ",
1228 address: _ {
1229 street: "123 Main St",
1230 city: "Seattle",
1231 ..
1232 },
1233 ..
1234 });
1235
1236 company
1239 .update()
1240 .with_headquarters(|h| {
1241 h.name("East Coast HQ").with_address(|a| {
1242 a.city("Boston");
1243 });
1244 })
1245 .exec(&mut db)
1246 .await?;
1247
1248 let found = Company::get_by_id(&mut db, &company.id).await?;
1249 assert_struct!(found.headquarters, _ {
1250 name: "East Coast HQ",
1251 address: _ {
1252 street: "123 Main St",
1253 city: "Boston",
1254 ..
1255 },
1256 ..
1257 });
1258 Ok(())
1259}
1260
1261#[driver_test(id(ID))]
1265pub async fn query_based_partial_update_embedded(t: &mut Test) -> Result<()> {
1266 #[derive(Debug, toasty::Embed)]
1267 struct Address {
1268 street: String,
1269 city: String,
1270 zip: String,
1271 }
1272
1273 #[derive(Debug, toasty::Model)]
1274 struct User {
1275 #[key]
1276 #[auto]
1277 id: ID,
1278 name: String,
1279 address: Address,
1280 }
1281
1282 let mut db = t.setup_db(models!(User, Address)).await;
1283
1284 let user = User::create()
1285 .name("Alice")
1286 .address(Address {
1287 street: "123 Main St".to_string(),
1288 city: "Boston".to_string(),
1289 zip: "02101".to_string(),
1290 })
1291 .exec(&mut db)
1292 .await?;
1293
1294 User::filter_by_id(user.id)
1297 .update()
1298 .with_address(|a| {
1299 a.city("Seattle");
1300 })
1301 .exec(&mut db)
1302 .await?;
1303
1304 let found = User::get_by_id(&mut db, &user.id).await?;
1305 assert_struct!(found.address, _ {
1306 street: "123 Main St",
1307 city: "Seattle",
1308 zip: "02101",
1309 ..
1310 });
1311
1312 User::filter_by_id(user.id)
1314 .update()
1315 .with_address(|a| {
1316 a.city("Portland").zip("97201");
1317 })
1318 .exec(&mut db)
1319 .await?;
1320
1321 let found = User::get_by_id(&mut db, &user.id).await?;
1322 assert_struct!(found.address, _ {
1323 street: "123 Main St",
1324 city: "Portland",
1325 zip: "97201",
1326 ..
1327 });
1328 Ok(())
1329}
1330
1331#[driver_test(id(ID))]
1334pub async fn embedded_struct_with_jiff_fields(t: &mut Test) -> Result<()> {
1335 #[derive(Debug, toasty::Embed)]
1336 struct Schedule {
1337 starts_at: jiff::Timestamp,
1338 due_date: jiff::civil::Date,
1339 reminder_time: jiff::civil::Time,
1340 scheduled_at: jiff::civil::DateTime,
1341 }
1342
1343 #[derive(Debug, toasty::Model)]
1344 struct Event {
1345 #[key]
1346 #[auto]
1347 id: ID,
1348 name: String,
1349 schedule: Schedule,
1350 }
1351
1352 let mut db = t.setup_db(models!(Event, Schedule)).await;
1353
1354 let starts_at = jiff::Timestamp::from_second(1_700_000_000).unwrap();
1355 let due_date = jiff::civil::date(2025, 6, 15);
1356 let reminder_time = jiff::civil::time(9, 30, 0, 0);
1357 let scheduled_at = jiff::civil::datetime(2025, 6, 15, 9, 30, 0, 0);
1358
1359 let event = Event::create()
1360 .name("team sync")
1361 .schedule(Schedule {
1362 starts_at,
1363 due_date,
1364 reminder_time,
1365 scheduled_at,
1366 })
1367 .exec(&mut db)
1368 .await?;
1369
1370 let found = Event::get_by_id(&mut db, &event.id).await?;
1371 assert_struct!(found.schedule, _ {
1372 starts_at: == starts_at,
1373 due_date: == due_date,
1374 reminder_time: == reminder_time,
1375 scheduled_at: == scheduled_at,
1376 });
1377 Ok(())
1378}
1379
1380#[driver_test(id(ID))]
1383pub async fn unit_enum_in_embedded_struct(t: &mut Test) -> Result<()> {
1384 #[derive(Debug, PartialEq, toasty::Embed)]
1385 enum Priority {
1386 #[column(variant = 1)]
1387 Low,
1388 #[column(variant = 2)]
1389 Normal,
1390 #[column(variant = 3)]
1391 High,
1392 }
1393
1394 #[derive(Debug, toasty::Embed)]
1395 struct Meta {
1396 label: String,
1397 priority: Priority,
1398 }
1399
1400 #[derive(Debug, toasty::Model)]
1401 struct Task {
1402 #[key]
1403 #[auto]
1404 id: ID,
1405 meta: Meta,
1406 }
1407
1408 let mut db = t.setup_db(models!(Task, Meta, Priority)).await;
1409
1410 let mut task = Task::create()
1411 .meta(Meta {
1412 label: "fix bug".to_string(),
1413 priority: Priority::High,
1414 })
1415 .exec(&mut db)
1416 .await?;
1417
1418 let found = Task::get_by_id(&mut db, &task.id).await?;
1419 assert_eq!(found.meta.label, "fix bug");
1420 assert_eq!(found.meta.priority, Priority::High);
1421
1422 task.update()
1423 .with_meta(|m| {
1424 m.priority(Priority::Normal);
1425 })
1426 .exec(&mut db)
1427 .await?;
1428
1429 let found = Task::get_by_id(&mut db, &task.id).await?;
1430 assert_eq!(found.meta.priority, Priority::Normal);
1431
1432 Ok(())
1433}
1434
1435#[driver_test(id(ID))]
1440pub async fn embedded_struct_with_uuid_field(t: &mut Test) -> Result<()> {
1441 #[derive(Debug, toasty::Embed)]
1442 struct Meta {
1443 ref_id: Uuid,
1444 label: String,
1445 }
1446
1447 #[derive(Debug, toasty::Model)]
1448 struct Item {
1449 #[key]
1450 #[auto]
1451 id: ID,
1452 name: String,
1453 meta: Meta,
1454 }
1455
1456 let mut db = t.setup_db(models!(Item, Meta)).await;
1457
1458 let ref_id = Uuid::new_v4();
1459
1460 let item = Item::create()
1461 .name("widget")
1462 .meta(Meta {
1463 ref_id,
1464 label: "v1".to_string(),
1465 })
1466 .exec(&mut db)
1467 .await?;
1468
1469 let found = Item::get_by_id(&mut db, &item.id).await?;
1471 assert_eq!(found.meta.ref_id, ref_id);
1472 assert_eq!(found.meta.label, "v1");
1473
1474 Ok(())
1475}