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 .address(toasty::stmt::patch(Address::fields().city(), "Seattle"))
629 .exec(&mut db)
630 .await?;
631
632 assert_struct!(user.address, {
634 street: "123 Main St",
635 city: "Seattle",
636 zip: "02101",
637 });
638
639 let found = User::get_by_id(&mut db, &user.id).await?;
641 assert_struct!(found.address, {
642 street: "123 Main St",
643 city: "Seattle",
644 zip: "02101",
645 });
646
647 user.update()
649 .address(toasty::stmt::apply([
650 toasty::stmt::patch(Address::fields().city(), "Portland"),
651 toasty::stmt::patch(Address::fields().zip(), "97201"),
652 ]))
653 .exec(&mut db)
654 .await?;
655
656 assert_struct!(user.address, {
658 street: "123 Main St",
659 city: "Portland",
660 zip: "97201",
661 });
662
663 let found = User::get_by_id(&mut db, &user.id).await?;
665 assert_struct!(found.address, {
666 street: "123 Main St",
667 city: "Portland",
668 zip: "97201",
669 });
670
671 user.update()
673 .address(toasty::stmt::patch(
674 Address::fields().street(),
675 "456 Oak Ave",
676 ))
677 .address(toasty::stmt::patch(Address::fields().zip(), "97202"))
678 .exec(&mut db)
679 .await?;
680
681 assert_struct!(user.address, {
683 street: "456 Oak Ave",
684 city: "Portland",
685 zip: "97202",
686 });
687
688 let found = User::get_by_id(&mut db, &user.id).await?;
690 assert_struct!(found.address, {
691 street: "456 Oak Ave",
692 city: "Portland",
693 zip: "97202",
694 });
695 Ok(())
696}
697
698#[driver_test]
706pub async fn deeply_nested_embedded_schema(test: &mut Test) {
707 #[derive(toasty::Embed)]
709 struct Location {
710 lat: i64,
711 lon: i64,
712 }
713
714 #[derive(toasty::Embed)]
715 struct City {
716 name: String,
717 location: Location,
718 }
719
720 #[derive(toasty::Embed)]
721 struct Address {
722 street: String,
723 city: City,
724 }
725
726 #[derive(toasty::Model)]
727 struct User {
728 #[key]
729 id: String,
730 #[allow(dead_code)]
731 address: Address,
732 }
733
734 let db = test.setup_db(models!(User, Address, City, Location)).await;
735 let schema = db.schema();
736
737 assert_struct!(schema.app.models, #{
739 Location::id(): toasty::schema::app::Model::EmbeddedStruct({
740 name.upper_camel_case(): "Location",
741 fields.len(): 2,
742 }),
743 City::id(): toasty::schema::app::Model::EmbeddedStruct({
744 name.upper_camel_case(): "City",
745 fields: [
746 { name.app: Some("name") },
747 {
748 name.app: Some("location"),
749 ty: FieldTy::Embedded({
750 target: == Location::id(),
751 }),
752 },
753 ],
754 }),
755 Address::id(): toasty::schema::app::Model::EmbeddedStruct({
756 name.upper_camel_case(): "Address",
757 fields: [
758 { name.app: Some("street") },
759 {
760 name.app: Some("city"),
761 ty: FieldTy::Embedded({
762 target: == City::id(),
763 }),
764 },
765 ],
766 }),
767 User::id(): toasty::schema::app::Model::Root({
768 name.upper_camel_case(): "User",
769 fields: [
770 { name.app: Some("id") },
771 {
772 name.app: Some("address"),
773 ty: FieldTy::Embedded({
774 target: == Address::id(),
775 }),
776 },
777 ],
778 }),
779 });
780
781 assert_struct!(schema.db.tables, [
789 {
790 name: =~ r"users$",
791 columns: [
792 { name: "id" },
793 { name: "address_street" },
794 { name: "address_city_name" },
795 { name: "address_city_location_lat" },
796 { name: "address_city_location_lon" },
797 ],
798 },
799 ]);
800
801 let user = &schema.app.models[&User::id()];
802 let user_table = schema.table_for(user);
803 let user_mapping = &schema.mapping.models[&User::id()];
804
805 assert_eq!(
818 user_mapping.fields.len(),
819 2,
820 "User should have 2 fields: id and address"
821 );
822
823 let address_field = user_mapping.fields[1]
825 .as_struct()
826 .expect("User.address should be Field::Struct");
827
828 assert_eq!(
829 address_field.fields.len(),
830 2,
831 "Address should have 2 fields: street and city"
832 );
833
834 let street_field = address_field.fields[0]
836 .as_primitive()
837 .expect("Address.street should be Field::Primitive");
838 assert_eq!(
839 street_field.column, user_table.columns[1].id,
840 "street should map to address_street column"
841 );
842
843 let city_field = address_field.fields[1]
845 .as_struct()
846 .expect("Address.city should be Field::Struct");
847
848 assert_eq!(
849 city_field.fields.len(),
850 2,
851 "City should have 2 fields: name and location"
852 );
853
854 let city_name_field = city_field.fields[0]
856 .as_primitive()
857 .expect("City.name should be Field::Primitive");
858 assert_eq!(
859 city_name_field.column, user_table.columns[2].id,
860 "city.name should map to address_city_name column"
861 );
862
863 let location_field = city_field.fields[1]
865 .as_struct()
866 .expect("City.location should be Field::Struct");
867
868 assert_eq!(
869 location_field.fields.len(),
870 2,
871 "Location should have 2 fields: lat and lon"
872 );
873
874 let lat_field = location_field.fields[0]
876 .as_primitive()
877 .expect("Location.lat should be Field::Primitive");
878 assert_eq!(
879 lat_field.column, user_table.columns[3].id,
880 "location.lat should map to address_city_location_lat column"
881 );
882
883 let lon_field = location_field.fields[1]
885 .as_primitive()
886 .expect("Location.lon should be Field::Primitive");
887 assert_eq!(
888 lon_field.column, user_table.columns[4].id,
889 "location.lon should map to address_city_location_lon column"
890 );
891
892 assert_eq!(
895 address_field.columns.len(),
896 4,
897 "Address.columns should have 4 entries"
898 );
899 assert!(
900 address_field
901 .columns
902 .contains_key(&user_table.columns[1].id),
903 "Address.columns should contain address_street"
904 );
905 assert!(
906 address_field
907 .columns
908 .contains_key(&user_table.columns[2].id),
909 "Address.columns should contain address_city_name"
910 );
911 assert!(
912 address_field
913 .columns
914 .contains_key(&user_table.columns[3].id),
915 "Address.columns should contain address_city_location_lat"
916 );
917 assert!(
918 address_field
919 .columns
920 .contains_key(&user_table.columns[4].id),
921 "Address.columns should contain address_city_location_lon"
922 );
923
924 assert_eq!(
926 city_field.columns.len(),
927 3,
928 "City.columns should have 3 entries"
929 );
930 assert!(
931 city_field.columns.contains_key(&user_table.columns[2].id),
932 "City.columns should contain address_city_name"
933 );
934 assert!(
935 city_field.columns.contains_key(&user_table.columns[3].id),
936 "City.columns should contain address_city_location_lat"
937 );
938 assert!(
939 city_field.columns.contains_key(&user_table.columns[4].id),
940 "City.columns should contain address_city_location_lon"
941 );
942
943 assert_eq!(
945 location_field.columns.len(),
946 2,
947 "Location.columns should have 2 entries"
948 );
949 assert!(
950 location_field
951 .columns
952 .contains_key(&user_table.columns[3].id),
953 "Location.columns should contain address_city_location_lat"
954 );
955 assert!(
956 location_field
957 .columns
958 .contains_key(&user_table.columns[4].id),
959 "Location.columns should contain address_city_location_lon"
960 );
961
962 assert_eq!(
965 user_mapping.model_to_table.len(),
966 5,
967 "model_to_table should have 5 expressions"
968 );
969
970 assert_struct!(
972 user_mapping.model_to_table[1],
973 == stmt::Expr::project(
974 stmt::Expr::ref_self_field(user.as_root_unwrap().fields[1].id),
975 [0],
976 )
977 );
978
979 assert_struct!(
981 user_mapping.model_to_table[2],
982 == stmt::Expr::project(
983 stmt::Expr::ref_self_field(user.as_root_unwrap().fields[1].id),
984 [1, 0],
985 )
986 );
987
988 assert_struct!(
990 user_mapping.model_to_table[3],
991 == stmt::Expr::project(
992 stmt::Expr::ref_self_field(user.as_root_unwrap().fields[1].id),
993 [1, 1, 0],
994 )
995 );
996
997 assert_struct!(
999 user_mapping.model_to_table[4],
1000 == stmt::Expr::project(
1001 stmt::Expr::ref_self_field(user.as_root_unwrap().fields[1].id),
1002 [1, 1, 1],
1003 )
1004 );
1005}
1006
1007#[driver_test(id(ID))]
1011pub async fn crud_nested_embedded(t: &mut Test) -> Result<()> {
1012 #[derive(Debug, toasty::Embed)]
1013 struct Address {
1014 street: String,
1015 city: String,
1016 }
1017
1018 #[derive(Debug, toasty::Embed)]
1019 struct Office {
1020 name: String,
1021 address: Address,
1022 }
1023
1024 #[derive(Debug, toasty::Model)]
1025 struct Company {
1026 #[key]
1027 #[auto]
1028 id: ID,
1029 name: String,
1030 headquarters: Office,
1031 }
1032
1033 let mut db = t.setup_db(models!(Company, Office, Address)).await;
1034
1035 let mut company = Company::create()
1037 .name("Acme")
1038 .headquarters(Office {
1039 name: "Main Office".to_string(),
1040 address: Address {
1041 street: "123 Main St".to_string(),
1042 city: "Springfield".to_string(),
1043 },
1044 })
1045 .exec(&mut db)
1046 .await?;
1047
1048 assert_struct!(company.headquarters, {
1049 name: "Main Office",
1050 address: {
1051 street: "123 Main St",
1052 city: "Springfield",
1053 },
1054 });
1055
1056 let found = Company::get_by_id(&mut db, &company.id).await?;
1058 assert_struct!(found.headquarters, {
1059 name: "Main Office",
1060 address: {
1061 street: "123 Main St",
1062 city: "Springfield",
1063 },
1064 });
1065
1066 company
1068 .update()
1069 .headquarters(Office {
1070 name: "West Coast HQ".to_string(),
1071 address: Address {
1072 street: "456 Oak Ave".to_string(),
1073 city: "Seattle".to_string(),
1074 },
1075 })
1076 .exec(&mut db)
1077 .await?;
1078
1079 let found = Company::get_by_id(&mut db, &company.id).await?;
1080 assert_struct!(found.headquarters, {
1081 name: "West Coast HQ",
1082 address: {
1083 street: "456 Oak Ave",
1084 city: "Seattle",
1085 },
1086 });
1087
1088 Company::filter_by_id(company.id)
1090 .update()
1091 .headquarters(Office {
1092 name: "East Coast HQ".to_string(),
1093 address: Address {
1094 street: "789 Pine Rd".to_string(),
1095 city: "Boston".to_string(),
1096 },
1097 })
1098 .exec(&mut db)
1099 .await?;
1100
1101 let found = Company::get_by_id(&mut db, &company.id).await?;
1102 assert_struct!(found.headquarters, {
1103 name: "East Coast HQ",
1104 address: {
1105 street: "789 Pine Rd",
1106 city: "Boston",
1107 },
1108 });
1109
1110 let id = company.id;
1112 company.delete().exec(&mut db).await?;
1113 assert_err!(Company::get_by_id(&mut db, &id).await);
1114 Ok(())
1115}
1116
1117#[driver_test(id(ID))]
1122pub async fn partial_update_nested_embedded(t: &mut Test) -> Result<()> {
1123 #[derive(Debug, toasty::Embed)]
1124 struct Address {
1125 street: String,
1126 city: String,
1127 }
1128
1129 #[derive(Debug, toasty::Embed)]
1130 struct Office {
1131 name: String,
1132 address: Address,
1133 }
1134
1135 #[derive(Debug, toasty::Model)]
1136 struct Company {
1137 #[key]
1138 #[auto]
1139 id: ID,
1140 name: String,
1141 headquarters: Office,
1142 }
1143
1144 let mut db = t.setup_db(models!(Company, Office, Address)).await;
1145
1146 let mut company = Company::create()
1147 .name("Acme")
1148 .headquarters(Office {
1149 name: "Main Office".to_string(),
1150 address: Address {
1151 street: "123 Main St".to_string(),
1152 city: "Boston".to_string(),
1153 },
1154 })
1155 .exec(&mut db)
1156 .await?;
1157
1158 company
1161 .update()
1162 .headquarters(toasty::stmt::patch(
1163 Office::fields().address().city(),
1164 "Seattle",
1165 ))
1166 .exec(&mut db)
1167 .await?;
1168
1169 let found = Company::get_by_id(&mut db, &company.id).await?;
1170 assert_struct!(found.headquarters, {
1171 name: "Main Office",
1172 address: {
1173 street: "123 Main St",
1174 city: "Seattle",
1175 },
1176 });
1177
1178 company
1181 .update()
1182 .headquarters(toasty::stmt::patch(
1183 Office::fields().name(),
1184 "West Coast HQ",
1185 ))
1186 .exec(&mut db)
1187 .await?;
1188
1189 let found = Company::get_by_id(&mut db, &company.id).await?;
1190 assert_struct!(found.headquarters, {
1191 name: "West Coast HQ",
1192 address: {
1193 street: "123 Main St",
1194 city: "Seattle",
1195 },
1196 });
1197
1198 company
1201 .update()
1202 .headquarters(toasty::stmt::apply([
1203 toasty::stmt::patch(Office::fields().name(), "East Coast HQ"),
1204 toasty::stmt::patch(Office::fields().address().city(), "Boston"),
1205 ]))
1206 .exec(&mut db)
1207 .await?;
1208
1209 let found = Company::get_by_id(&mut db, &company.id).await?;
1210 assert_struct!(found.headquarters, {
1211 name: "East Coast HQ",
1212 address: {
1213 street: "123 Main St",
1214 city: "Boston",
1215 },
1216 });
1217 Ok(())
1218}
1219
1220#[driver_test(id(ID))]
1225pub async fn query_based_partial_update_embedded(t: &mut Test) -> Result<()> {
1226 #[derive(Debug, toasty::Embed)]
1227 struct Address {
1228 street: String,
1229 city: String,
1230 zip: String,
1231 }
1232
1233 #[derive(Debug, toasty::Model)]
1234 struct User {
1235 #[key]
1236 #[auto]
1237 id: ID,
1238 name: String,
1239 address: Address,
1240 }
1241
1242 let mut db = t.setup_db(models!(User, Address)).await;
1243
1244 let user = User::create()
1245 .name("Alice")
1246 .address(Address {
1247 street: "123 Main St".to_string(),
1248 city: "Boston".to_string(),
1249 zip: "02101".to_string(),
1250 })
1251 .exec(&mut db)
1252 .await?;
1253
1254 User::filter_by_id(user.id)
1257 .update()
1258 .address(toasty::stmt::patch(Address::fields().city(), "Seattle"))
1259 .exec(&mut db)
1260 .await?;
1261
1262 let found = User::get_by_id(&mut db, &user.id).await?;
1263 assert_struct!(found.address, {
1264 street: "123 Main St",
1265 city: "Seattle",
1266 zip: "02101",
1267 });
1268
1269 User::filter_by_id(user.id)
1271 .update()
1272 .address(toasty::stmt::apply([
1273 toasty::stmt::patch(Address::fields().city(), "Portland"),
1274 toasty::stmt::patch(Address::fields().zip(), "97201"),
1275 ]))
1276 .exec(&mut db)
1277 .await?;
1278
1279 let found = User::get_by_id(&mut db, &user.id).await?;
1280 assert_struct!(found.address, {
1281 street: "123 Main St",
1282 city: "Portland",
1283 zip: "97201",
1284 });
1285 Ok(())
1286}
1287
1288#[driver_test(id(ID))]
1291pub async fn embedded_struct_with_jiff_fields(t: &mut Test) -> Result<()> {
1292 #[derive(Debug, toasty::Embed)]
1293 struct Schedule {
1294 starts_at: jiff::Timestamp,
1295 due_date: jiff::civil::Date,
1296 reminder_time: jiff::civil::Time,
1297 scheduled_at: jiff::civil::DateTime,
1298 }
1299
1300 #[derive(Debug, toasty::Model)]
1301 struct Event {
1302 #[key]
1303 #[auto]
1304 id: ID,
1305 name: String,
1306 schedule: Schedule,
1307 }
1308
1309 let mut db = t.setup_db(models!(Event, Schedule)).await;
1310
1311 let starts_at = jiff::Timestamp::from_second(1_700_000_000).unwrap();
1312 let due_date = jiff::civil::date(2025, 6, 15);
1313 let reminder_time = jiff::civil::time(9, 30, 0, 0);
1314 let scheduled_at = jiff::civil::datetime(2025, 6, 15, 9, 30, 0, 0);
1315
1316 let event = Event::create()
1317 .name("team sync")
1318 .schedule(Schedule {
1319 starts_at,
1320 due_date,
1321 reminder_time,
1322 scheduled_at,
1323 })
1324 .exec(&mut db)
1325 .await?;
1326
1327 let found = Event::get_by_id(&mut db, &event.id).await?;
1328 assert_struct!(found.schedule, {
1329 starts_at: == starts_at,
1330 due_date: == due_date,
1331 reminder_time: == reminder_time,
1332 scheduled_at: == scheduled_at,
1333 });
1334 Ok(())
1335}
1336
1337#[driver_test(id(ID))]
1340pub async fn unit_enum_in_embedded_struct(t: &mut Test) -> Result<()> {
1341 #[derive(Debug, PartialEq, toasty::Embed)]
1342 enum Priority {
1343 #[column(variant = 1)]
1344 Low,
1345 #[column(variant = 2)]
1346 Normal,
1347 #[column(variant = 3)]
1348 High,
1349 }
1350
1351 #[derive(Debug, toasty::Embed)]
1352 struct Meta {
1353 label: String,
1354 priority: Priority,
1355 }
1356
1357 #[derive(Debug, toasty::Model)]
1358 struct Task {
1359 #[key]
1360 #[auto]
1361 id: ID,
1362 meta: Meta,
1363 }
1364
1365 let mut db = t.setup_db(models!(Task, Meta, Priority)).await;
1366
1367 let mut task = Task::create()
1368 .meta(Meta {
1369 label: "fix bug".to_string(),
1370 priority: Priority::High,
1371 })
1372 .exec(&mut db)
1373 .await?;
1374
1375 let found = Task::get_by_id(&mut db, &task.id).await?;
1376 assert_eq!(found.meta.label, "fix bug");
1377 assert_eq!(found.meta.priority, Priority::High);
1378
1379 task.update()
1380 .meta(toasty::stmt::patch(
1381 Meta::fields().priority().into(),
1382 Priority::Normal,
1383 ))
1384 .exec(&mut db)
1385 .await?;
1386
1387 let found = Task::get_by_id(&mut db, &task.id).await?;
1388 assert_eq!(found.meta.priority, Priority::Normal);
1389
1390 Ok(())
1391}
1392
1393#[driver_test(id(ID))]
1398pub async fn embedded_struct_with_uuid_field(t: &mut Test) -> Result<()> {
1399 #[derive(Debug, toasty::Embed)]
1400 struct Meta {
1401 ref_id: Uuid,
1402 label: String,
1403 }
1404
1405 #[derive(Debug, toasty::Model)]
1406 struct Item {
1407 #[key]
1408 #[auto]
1409 id: ID,
1410 name: String,
1411 meta: Meta,
1412 }
1413
1414 let mut db = t.setup_db(models!(Item, Meta)).await;
1415
1416 let ref_id = Uuid::new_v4();
1417
1418 let item = Item::create()
1419 .name("widget")
1420 .meta(Meta {
1421 ref_id,
1422 label: "v1".to_string(),
1423 })
1424 .exec(&mut db)
1425 .await?;
1426
1427 let found = Item::get_by_id(&mut db, &item.id).await?;
1429 assert_eq!(found.meta.ref_id, ref_id);
1430 assert_eq!(found.meta.label, "v1");
1431
1432 Ok(())
1433}