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