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(scenario(crate::scenarios::user_with_address))]
13pub async fn basic_embedded_struct(test: &mut Test) {
14 let db = setup(test).await;
15 let schema = db.schema();
16
17 let address = &schema.app.models[&Address::id()];
19 assert_struct!(address, toasty::schema::app::Model::EmbeddedStruct({
20 name.upper_camel_case(): "Address",
21 fields: [
22 { name.app: Some("street") },
23 { name.app: Some("city") },
24 ],
25 }));
26}
27
28#[driver_test(scenario(crate::scenarios::user_with_address))]
33pub async fn root_model_with_embedded_field(test: &mut Test) {
34 let db = setup(test).await;
35 let schema = db.schema();
36
37 assert_struct!(schema.app.models, #{
39 Address::id(): toasty::schema::app::Model::EmbeddedStruct({
40 name.upper_camel_case(): "Address",
41 fields: [
42 { name.app: Some("street") },
43 { name.app: Some("city") },
44 ],
45 }),
46 User::id(): toasty::schema::app::Model::Root({
47 name.upper_camel_case(): "User",
48 fields: [
49 { name.app: Some("id") },
50 {
51 name.app: Some("address"),
52 ty: FieldTy::Embedded({
53 target: == Address::id(),
54 }),
55 },
56 ],
57 }),
58 });
59
60 assert_struct!(schema.db.tables, [
63 {
64 name: =~ r"users$",
65 columns: [
66 { name: "id" },
67 { name: "address_street" },
68 { name: "address_city" },
69 ],
70 },
71 ]);
72
73 let user = &schema.app.models[&User::id()];
74 let user_table = schema.table_for(user);
75 let user_mapping = &schema.mapping.models[&User::id()];
76
77 assert_struct!(user_mapping, {
81 columns.len(): 3,
82 fields: [
83 mapping::Field::Primitive(FieldPrimitive {
84 column: == user_table.columns[0].id,
85 lowering: 0,
86 ..
87 }),
88 mapping::Field::Struct(FieldStruct {
89 fields: [
90 mapping::Field::Primitive(FieldPrimitive {
91 column: == user_table.columns[1].id,
92 lowering: 1,
93 ..
94 }),
95 mapping::Field::Primitive(FieldPrimitive {
96 column: == user_table.columns[2].id,
97 lowering: 2,
98 ..
99 }),
100 ],
101 ..
102 }),
103 ],
104 model_to_table.fields: [
105 _,
106 == stmt::Expr::project(
107 stmt::Expr::ref_self_field(user.as_root_unwrap().fields[1].id),
108 [0],
109 ),
110 == stmt::Expr::project(
111 stmt::Expr::ref_self_field(user.as_root_unwrap().fields[1].id),
112 [1],
113 ),
114 ],
115 });
116
117 let table_to_model = user_mapping
120 .table_to_model
121 .lower_returning_model()
122 .into_record();
123
124 assert_struct!(
125 table_to_model.fields,
126 [
127 _,
128 stmt::Expr::Record(stmt::ExprRecord {
129 fields: [
130 == stmt::Expr::column(user_table.columns[1].id),
131 == stmt::Expr::column(user_table.columns[2].id),
132 ],
133 }),
134 ]
135 );
136}
137
138#[driver_test(id(ID))]
141pub async fn create_and_query_embedded(t: &mut Test) -> Result<()> {
142 #[derive(Debug, toasty::Embed)]
143 struct Address {
144 street: String,
145 city: String,
146 }
147
148 #[derive(Debug, toasty::Model)]
149 struct User {
150 #[key]
151 #[auto]
152 id: ID,
153 name: String,
154 address: Address,
155 }
156
157 let mut db = t.setup_db(models!(User)).await;
158
159 let mut user = User::create()
160 .name("Alice")
161 .address(Address {
162 street: "123 Main St".to_string(),
163 city: "Springfield".to_string(),
164 })
165 .exec(&mut db)
166 .await?;
167
168 let found = User::get_by_id(&mut db, &user.id).await?;
170 assert_eq!(found.address.street, "123 Main St");
171 assert_eq!(found.address.city, "Springfield");
172
173 user.update()
175 .address(Address {
176 street: "456 Oak Ave".to_string(),
177 city: "Shelbyville".to_string(),
178 })
179 .exec(&mut db)
180 .await?;
181
182 let found = User::get_by_id(&mut db, &user.id).await?;
183 assert_eq!(found.address.street, "456 Oak Ave");
184
185 User::filter_by_id(user.id)
187 .update()
188 .address(Address {
189 street: "789 Pine Rd".to_string(),
190 city: "Capital City".to_string(),
191 })
192 .exec(&mut db)
193 .await?;
194
195 let found = User::get_by_id(&mut db, &user.id).await?;
196 assert_eq!(found.address.street, "789 Pine Rd");
197
198 let id = user.id;
200 user.delete().exec(&mut db).await?;
201 assert_err!(User::get_by_id(&mut db, &id).await);
202 Ok(())
203}
204
205#[driver_test(scenario(crate::scenarios::user_with_zip_address::id_uuid))]
211pub async fn embedded_struct_fields_codegen(test: &mut Test) {
212 let _db = setup(test).await;
213
214 let _city_path = User::fields().address().city();
216
217 let address_fields = User::fields().address();
219 let _city_path_2 = address_fields.city();
220
221 let _address_city = Address::fields().city();
223
224 let _query = User::all().filter(User::fields().address().city().eq("Seattle"));
226}
227
228#[driver_test]
235pub async fn query_embedded_struct_fields(t: &mut Test) -> Result<()> {
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 #[key(partition = country, local = id)]
245 #[allow(dead_code)]
246 struct User {
247 #[auto]
248 id: uuid::Uuid,
249 country: String,
250 name: String,
251 address: Address,
252 }
253
254 let mut db = t.setup_db(models!(User)).await;
255
256 let users_data = [
258 ("USA", "Alice", "123 Main St", "Seattle", "98101"),
259 ("USA", "Bob", "456 Oak Ave", "Seattle", "98102"),
260 ("USA", "Charlie", "789 Pine Rd", "Portland", "97201"),
261 ("USA", "Diana", "321 Elm St", "Portland", "97202"),
262 ("CAN", "Eve", "111 Maple Dr", "Vancouver", "V6B 1A1"),
263 ("CAN", "Frank", "222 Cedar Ln", "Vancouver", "V6B 2B2"),
264 ("CAN", "Grace", "333 Birch Way", "Toronto", "M5H 1A1"),
265 ];
266
267 for (country, name, street, city, zip) in users_data {
268 User::create()
269 .country(country)
270 .name(name)
271 .address(Address {
272 street: street.to_string(),
273 city: city.to_string(),
274 zip: zip.to_string(),
275 })
276 .exec(&mut db)
277 .await?;
278 }
279
280 let mut all_users = Vec::new();
282 for country in ["USA", "CAN"] {
283 let mut users = User::filter(User::fields().country().eq(country))
284 .exec(&mut db)
285 .await?;
286 all_users.append(&mut users);
287 }
288 assert_eq!(all_users.len(), 7);
289
290 let seattle_users = User::filter(
293 User::fields()
294 .country()
295 .eq("USA")
296 .and(User::fields().address().city().eq("Seattle")),
297 )
298 .exec(&mut db)
299 .await?;
300
301 assert_eq!(seattle_users.len(), 2);
302 let mut names: Vec<_> = seattle_users.iter().map(|u| u.name.as_str()).collect();
303 names.sort();
304 assert_eq!(names, ["Alice", "Bob"]);
305
306 let vancouver_users = User::filter(
308 User::fields()
309 .country()
310 .eq("CAN")
311 .and(User::fields().address().city().eq("Vancouver")),
312 )
313 .exec(&mut db)
314 .await?;
315
316 assert_eq!(vancouver_users.len(), 2);
317
318 let user_98101 = User::filter(
320 User::fields()
321 .country()
322 .eq("USA")
323 .and(User::fields().address().zip().eq("98101")),
324 )
325 .exec(&mut db)
326 .await?;
327
328 assert_eq!(user_98101.len(), 1);
329 assert_eq!(user_98101[0].name, "Alice");
330 Ok(())
331}
332
333#[driver_test(requires(scan))]
337pub async fn query_embedded_fields_comparison_ops(t: &mut Test) -> Result<()> {
338 #[derive(Debug, toasty::Embed)]
339 struct Stats {
340 score: i64,
341 rank: i64,
342 }
343
344 #[derive(Debug, toasty::Model)]
345 #[allow(dead_code)]
346 struct Player {
347 #[key]
348 #[auto]
349 id: uuid::Uuid,
350 name: String,
351 stats: Stats,
352 }
353
354 let mut db = t.setup_db(models!(Player)).await;
355
356 for (name, score, rank) in [
357 ("Alice", 100, 1),
358 ("Bob", 85, 2),
359 ("Charlie", 70, 3),
360 ("Diana", 55, 4),
361 ("Eve", 40, 5),
362 ] {
363 Player::create()
364 .name(name)
365 .stats(Stats { score, rank })
366 .exec(&mut db)
367 .await?;
368 }
369
370 let high_scorers = Player::filter(Player::fields().stats().score().gt(80))
372 .exec(&mut db)
373 .await?;
374 assert_eq!(high_scorers.len(), 2);
375
376 let low_scorers = Player::filter(Player::fields().stats().score().le(55))
378 .exec(&mut db)
379 .await?;
380 assert_eq!(low_scorers.len(), 2);
381
382 let not_charlie = Player::filter(Player::fields().stats().score().ne(70))
384 .exec(&mut db)
385 .await?;
386 assert_eq!(not_charlie.len(), 4);
387
388 let mid_to_high = Player::filter(Player::fields().stats().score().ge(70))
390 .exec(&mut db)
391 .await?;
392 assert_eq!(mid_to_high.len(), 3);
393 Ok(())
394}
395
396#[driver_test(requires(scan))]
400pub async fn query_embedded_multiple_fields(t: &mut Test) -> Result<()> {
401 #[derive(Debug, toasty::Embed)]
402 struct Coordinates {
403 x: i64,
404 y: i64,
405 z: i64,
406 }
407
408 #[derive(Debug, toasty::Model)]
409 #[allow(dead_code)]
410 struct Location {
411 #[key]
412 #[auto]
413 id: uuid::Uuid,
414 name: String,
415 coords: Coordinates,
416 }
417
418 let mut db = t.setup_db(models!(Location)).await;
419
420 for (name, x, y, z) in [
421 ("Origin", 0, 0, 0),
422 ("Point A", 10, 20, 0),
423 ("Point B", 10, 30, 0),
424 ("Point C", 10, 20, 5),
425 ("Point D", 20, 20, 0),
426 ] {
427 Location::create()
428 .name(name)
429 .coords(Coordinates { x, y, z })
430 .exec(&mut db)
431 .await?;
432 }
433
434 let matching = Location::filter(
436 Location::fields()
437 .coords()
438 .x()
439 .eq(10)
440 .and(Location::fields().coords().y().eq(20)),
441 )
442 .exec(&mut db)
443 .await?;
444
445 assert_eq!(matching.len(), 2);
446 let mut names: Vec<_> = matching.iter().map(|l| l.name.as_str()).collect();
447 names.sort();
448 assert_eq!(names, ["Point A", "Point C"]);
449
450 let exact_match = Location::filter(
453 Location::fields()
454 .coords()
455 .x()
456 .eq(10)
457 .and(Location::fields().coords().y().eq(20))
458 .and(Location::fields().coords().z().eq(0)),
459 )
460 .exec(&mut db)
461 .await?;
462
463 assert_eq!(exact_match.len(), 1);
464 assert_eq!(exact_match[0].name, "Point A");
465 Ok(())
466}
467
468#[driver_test(requires(sql))]
472pub async fn update_with_embedded_field_filter(t: &mut Test) -> Result<()> {
473 #[derive(Debug, toasty::Embed)]
474 struct Metadata {
475 version: i64,
476 status: String,
477 }
478
479 #[derive(Debug, toasty::Model)]
480 #[allow(dead_code)]
481 struct Document {
482 #[key]
483 #[auto]
484 id: uuid::Uuid,
485 title: String,
486 meta: Metadata,
487 }
488
489 let mut db = t.setup_db(models!(Document)).await;
490
491 for (title, version, status) in [
493 ("Doc A", 1, "draft"),
494 ("Doc B", 2, "draft"),
495 ("Doc C", 1, "published"),
496 ] {
497 Document::create()
498 .title(title)
499 .meta(Metadata {
500 version,
501 status: status.to_string(),
502 })
503 .exec(&mut db)
504 .await?;
505 }
506
507 Document::filter(
510 Document::fields()
511 .meta()
512 .status()
513 .eq("draft")
514 .and(Document::fields().meta().version().eq(1)),
515 )
516 .update()
517 .meta(Metadata {
518 version: 2,
519 status: "draft".to_string(),
520 })
521 .exec(&mut db)
522 .await?;
523
524 let doc_a = Document::filter(Document::fields().title().eq("Doc A"))
526 .exec(&mut db)
527 .await?;
528 assert_eq!(doc_a[0].meta.version, 2);
529
530 let doc_b = Document::filter(Document::fields().title().eq("Doc B"))
532 .exec(&mut db)
533 .await?;
534 assert_eq!(doc_b[0].meta.version, 2);
535
536 let doc_c = Document::filter(Document::fields().title().eq("Doc C"))
538 .exec(&mut db)
539 .await?;
540 assert_eq!(doc_c[0].meta.version, 1);
541 Ok(())
542}
543
544#[driver_test(id(ID), scenario(crate::scenarios::user_with_zip_address))]
548pub async fn partial_update_embedded_fields(t: &mut Test) -> Result<()> {
549 let mut db = setup(t).await;
550
551 let mut user = User::create()
553 .name("Alice")
554 .address(Address {
555 street: "123 Main St".to_string(),
556 city: "Boston".to_string(),
557 zip: "02101".to_string(),
558 })
559 .exec(&mut db)
560 .await?;
561
562 assert_struct!(user.address, {
564 street: "123 Main St",
565 city: "Boston",
566 zip: "02101",
567 });
568
569 user.update()
571 .address(toasty::stmt::patch(Address::fields().city(), "Seattle"))
572 .exec(&mut db)
573 .await?;
574
575 assert_struct!(user.address, {
577 street: "123 Main St",
578 city: "Seattle",
579 zip: "02101",
580 });
581
582 let found = User::get_by_id(&mut db, &user.id).await?;
584 assert_struct!(found.address, {
585 street: "123 Main St",
586 city: "Seattle",
587 zip: "02101",
588 });
589
590 user.update()
592 .address(toasty::stmt::apply([
593 toasty::stmt::patch(Address::fields().city(), "Portland"),
594 toasty::stmt::patch(Address::fields().zip(), "97201"),
595 ]))
596 .exec(&mut db)
597 .await?;
598
599 assert_struct!(user.address, {
601 street: "123 Main St",
602 city: "Portland",
603 zip: "97201",
604 });
605
606 let found = User::get_by_id(&mut db, &user.id).await?;
608 assert_struct!(found.address, {
609 street: "123 Main St",
610 city: "Portland",
611 zip: "97201",
612 });
613
614 user.update()
616 .address(toasty::stmt::patch(
617 Address::fields().street(),
618 "456 Oak Ave",
619 ))
620 .address(toasty::stmt::patch(Address::fields().zip(), "97202"))
621 .exec(&mut db)
622 .await?;
623
624 assert_struct!(user.address, {
626 street: "456 Oak Ave",
627 city: "Portland",
628 zip: "97202",
629 });
630
631 let found = User::get_by_id(&mut db, &user.id).await?;
633 assert_struct!(found.address, {
634 street: "456 Oak Ave",
635 city: "Portland",
636 zip: "97202",
637 });
638 Ok(())
639}
640
641#[driver_test]
649pub async fn deeply_nested_embedded_schema(test: &mut Test) {
650 #[derive(toasty::Embed)]
652 struct Location {
653 lat: i64,
654 lon: i64,
655 }
656
657 #[derive(toasty::Embed)]
658 struct City {
659 name: String,
660 location: Location,
661 }
662
663 #[derive(toasty::Embed)]
664 struct Address {
665 street: String,
666 city: City,
667 }
668
669 #[derive(toasty::Model)]
670 struct User {
671 #[key]
672 id: String,
673 #[allow(dead_code)]
674 address: Address,
675 }
676
677 let db = test.setup_db(models!(User)).await;
678 let schema = db.schema();
679
680 assert_struct!(schema.app.models, #{
682 Location::id(): toasty::schema::app::Model::EmbeddedStruct({
683 name.upper_camel_case(): "Location",
684 fields.len(): 2,
685 }),
686 City::id(): toasty::schema::app::Model::EmbeddedStruct({
687 name.upper_camel_case(): "City",
688 fields: [
689 { name.app: Some("name") },
690 {
691 name.app: Some("location"),
692 ty: FieldTy::Embedded({
693 target: == Location::id(),
694 }),
695 },
696 ],
697 }),
698 Address::id(): toasty::schema::app::Model::EmbeddedStruct({
699 name.upper_camel_case(): "Address",
700 fields: [
701 { name.app: Some("street") },
702 {
703 name.app: Some("city"),
704 ty: FieldTy::Embedded({
705 target: == City::id(),
706 }),
707 },
708 ],
709 }),
710 User::id(): toasty::schema::app::Model::Root({
711 name.upper_camel_case(): "User",
712 fields: [
713 { name.app: Some("id") },
714 {
715 name.app: Some("address"),
716 ty: FieldTy::Embedded({
717 target: == Address::id(),
718 }),
719 },
720 ],
721 }),
722 });
723
724 assert_struct!(schema.db.tables, [
732 {
733 name: =~ r"users$",
734 columns: [
735 { name: "id" },
736 { name: "address_street" },
737 { name: "address_city_name" },
738 { name: "address_city_location_lat" },
739 { name: "address_city_location_lon" },
740 ],
741 },
742 ]);
743
744 let user = &schema.app.models[&User::id()];
745 let user_table = schema.table_for(user);
746 let user_mapping = &schema.mapping.models[&User::id()];
747
748 assert_eq!(
761 user_mapping.fields.len(),
762 2,
763 "User should have 2 fields: id and address"
764 );
765
766 let address_field = user_mapping.fields[1]
768 .as_struct()
769 .expect("User.address should be Field::Struct");
770
771 assert_eq!(
772 address_field.fields.len(),
773 2,
774 "Address should have 2 fields: street and city"
775 );
776
777 let street_field = address_field.fields[0]
779 .as_primitive()
780 .expect("Address.street should be Field::Primitive");
781 assert_eq!(
782 street_field.column, user_table.columns[1].id,
783 "street should map to address_street column"
784 );
785
786 let city_field = address_field.fields[1]
788 .as_struct()
789 .expect("Address.city should be Field::Struct");
790
791 assert_eq!(
792 city_field.fields.len(),
793 2,
794 "City should have 2 fields: name and location"
795 );
796
797 let city_name_field = city_field.fields[0]
799 .as_primitive()
800 .expect("City.name should be Field::Primitive");
801 assert_eq!(
802 city_name_field.column, user_table.columns[2].id,
803 "city.name should map to address_city_name column"
804 );
805
806 let location_field = city_field.fields[1]
808 .as_struct()
809 .expect("City.location should be Field::Struct");
810
811 assert_eq!(
812 location_field.fields.len(),
813 2,
814 "Location should have 2 fields: lat and lon"
815 );
816
817 let lat_field = location_field.fields[0]
819 .as_primitive()
820 .expect("Location.lat should be Field::Primitive");
821 assert_eq!(
822 lat_field.column, user_table.columns[3].id,
823 "location.lat should map to address_city_location_lat column"
824 );
825
826 let lon_field = location_field.fields[1]
828 .as_primitive()
829 .expect("Location.lon should be Field::Primitive");
830 assert_eq!(
831 lon_field.column, user_table.columns[4].id,
832 "location.lon should map to address_city_location_lon column"
833 );
834
835 assert_eq!(
838 address_field.columns.len(),
839 4,
840 "Address.columns should have 4 entries"
841 );
842 assert!(
843 address_field
844 .columns
845 .contains_key(&user_table.columns[1].id),
846 "Address.columns should contain address_street"
847 );
848 assert!(
849 address_field
850 .columns
851 .contains_key(&user_table.columns[2].id),
852 "Address.columns should contain address_city_name"
853 );
854 assert!(
855 address_field
856 .columns
857 .contains_key(&user_table.columns[3].id),
858 "Address.columns should contain address_city_location_lat"
859 );
860 assert!(
861 address_field
862 .columns
863 .contains_key(&user_table.columns[4].id),
864 "Address.columns should contain address_city_location_lon"
865 );
866
867 assert_eq!(
869 city_field.columns.len(),
870 3,
871 "City.columns should have 3 entries"
872 );
873 assert!(
874 city_field.columns.contains_key(&user_table.columns[2].id),
875 "City.columns should contain address_city_name"
876 );
877 assert!(
878 city_field.columns.contains_key(&user_table.columns[3].id),
879 "City.columns should contain address_city_location_lat"
880 );
881 assert!(
882 city_field.columns.contains_key(&user_table.columns[4].id),
883 "City.columns should contain address_city_location_lon"
884 );
885
886 assert_eq!(
888 location_field.columns.len(),
889 2,
890 "Location.columns should have 2 entries"
891 );
892 assert!(
893 location_field
894 .columns
895 .contains_key(&user_table.columns[3].id),
896 "Location.columns should contain address_city_location_lat"
897 );
898 assert!(
899 location_field
900 .columns
901 .contains_key(&user_table.columns[4].id),
902 "Location.columns should contain address_city_location_lon"
903 );
904
905 assert_eq!(
908 user_mapping.model_to_table.len(),
909 5,
910 "model_to_table should have 5 expressions"
911 );
912
913 assert_struct!(
915 user_mapping.model_to_table[1],
916 == stmt::Expr::project(
917 stmt::Expr::ref_self_field(user.as_root_unwrap().fields[1].id),
918 [0],
919 )
920 );
921
922 assert_struct!(
924 user_mapping.model_to_table[2],
925 == stmt::Expr::project(
926 stmt::Expr::ref_self_field(user.as_root_unwrap().fields[1].id),
927 [1, 0],
928 )
929 );
930
931 assert_struct!(
933 user_mapping.model_to_table[3],
934 == stmt::Expr::project(
935 stmt::Expr::ref_self_field(user.as_root_unwrap().fields[1].id),
936 [1, 1, 0],
937 )
938 );
939
940 assert_struct!(
942 user_mapping.model_to_table[4],
943 == stmt::Expr::project(
944 stmt::Expr::ref_self_field(user.as_root_unwrap().fields[1].id),
945 [1, 1, 1],
946 )
947 );
948}
949
950#[driver_test(id(ID), scenario(crate::scenarios::company_office_address))]
954pub async fn crud_nested_embedded(t: &mut Test) -> Result<()> {
955 let mut db = setup(t).await;
956
957 let mut company = Company::create()
959 .name("Acme")
960 .headquarters(Office {
961 name: "Main Office".to_string(),
962 address: Address {
963 street: "123 Main St".to_string(),
964 city: "Springfield".to_string(),
965 },
966 })
967 .exec(&mut db)
968 .await?;
969
970 assert_struct!(company.headquarters, {
971 name: "Main Office",
972 address: {
973 street: "123 Main St",
974 city: "Springfield",
975 },
976 });
977
978 let found = Company::get_by_id(&mut db, &company.id).await?;
980 assert_struct!(found.headquarters, {
981 name: "Main Office",
982 address: {
983 street: "123 Main St",
984 city: "Springfield",
985 },
986 });
987
988 company
990 .update()
991 .headquarters(Office {
992 name: "West Coast HQ".to_string(),
993 address: Address {
994 street: "456 Oak Ave".to_string(),
995 city: "Seattle".to_string(),
996 },
997 })
998 .exec(&mut db)
999 .await?;
1000
1001 let found = Company::get_by_id(&mut db, &company.id).await?;
1002 assert_struct!(found.headquarters, {
1003 name: "West Coast HQ",
1004 address: {
1005 street: "456 Oak Ave",
1006 city: "Seattle",
1007 },
1008 });
1009
1010 Company::filter_by_id(company.id)
1012 .update()
1013 .headquarters(Office {
1014 name: "East Coast HQ".to_string(),
1015 address: Address {
1016 street: "789 Pine Rd".to_string(),
1017 city: "Boston".to_string(),
1018 },
1019 })
1020 .exec(&mut db)
1021 .await?;
1022
1023 let found = Company::get_by_id(&mut db, &company.id).await?;
1024 assert_struct!(found.headquarters, {
1025 name: "East Coast HQ",
1026 address: {
1027 street: "789 Pine Rd",
1028 city: "Boston",
1029 },
1030 });
1031
1032 let id = company.id;
1034 company.delete().exec(&mut db).await?;
1035 assert_err!(Company::get_by_id(&mut db, &id).await);
1036 Ok(())
1037}
1038
1039#[driver_test(id(ID), scenario(crate::scenarios::company_office_address))]
1044pub async fn partial_update_nested_embedded(t: &mut Test) -> Result<()> {
1045 let mut db = setup(t).await;
1046
1047 let mut company = Company::create()
1048 .name("Acme")
1049 .headquarters(Office {
1050 name: "Main Office".to_string(),
1051 address: Address {
1052 street: "123 Main St".to_string(),
1053 city: "Boston".to_string(),
1054 },
1055 })
1056 .exec(&mut db)
1057 .await?;
1058
1059 company
1062 .update()
1063 .headquarters(toasty::stmt::patch(
1064 Office::fields().address().city(),
1065 "Seattle",
1066 ))
1067 .exec(&mut db)
1068 .await?;
1069
1070 let found = Company::get_by_id(&mut db, &company.id).await?;
1071 assert_struct!(found.headquarters, {
1072 name: "Main Office",
1073 address: {
1074 street: "123 Main St",
1075 city: "Seattle",
1076 },
1077 });
1078
1079 company
1082 .update()
1083 .headquarters(toasty::stmt::patch(
1084 Office::fields().name(),
1085 "West Coast HQ",
1086 ))
1087 .exec(&mut db)
1088 .await?;
1089
1090 let found = Company::get_by_id(&mut db, &company.id).await?;
1091 assert_struct!(found.headquarters, {
1092 name: "West Coast HQ",
1093 address: {
1094 street: "123 Main St",
1095 city: "Seattle",
1096 },
1097 });
1098
1099 company
1102 .update()
1103 .headquarters(toasty::stmt::apply([
1104 toasty::stmt::patch(Office::fields().name(), "East Coast HQ"),
1105 toasty::stmt::patch(Office::fields().address().city(), "Boston"),
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: "East Coast HQ",
1113 address: {
1114 street: "123 Main St",
1115 city: "Boston",
1116 },
1117 });
1118 Ok(())
1119}
1120
1121#[driver_test(id(ID), scenario(crate::scenarios::user_with_zip_address))]
1126pub async fn query_based_partial_update_embedded(t: &mut Test) -> Result<()> {
1127 let mut db = setup(t).await;
1128
1129 let user = User::create()
1130 .name("Alice")
1131 .address(Address {
1132 street: "123 Main St".to_string(),
1133 city: "Boston".to_string(),
1134 zip: "02101".to_string(),
1135 })
1136 .exec(&mut db)
1137 .await?;
1138
1139 User::filter_by_id(user.id)
1142 .update()
1143 .address(toasty::stmt::patch(Address::fields().city(), "Seattle"))
1144 .exec(&mut db)
1145 .await?;
1146
1147 let found = User::get_by_id(&mut db, &user.id).await?;
1148 assert_struct!(found.address, {
1149 street: "123 Main St",
1150 city: "Seattle",
1151 zip: "02101",
1152 });
1153
1154 User::filter_by_id(user.id)
1156 .update()
1157 .address(toasty::stmt::apply([
1158 toasty::stmt::patch(Address::fields().city(), "Portland"),
1159 toasty::stmt::patch(Address::fields().zip(), "97201"),
1160 ]))
1161 .exec(&mut db)
1162 .await?;
1163
1164 let found = User::get_by_id(&mut db, &user.id).await?;
1165 assert_struct!(found.address, {
1166 street: "123 Main St",
1167 city: "Portland",
1168 zip: "97201",
1169 });
1170 Ok(())
1171}
1172
1173#[driver_test(id(ID))]
1176pub async fn embedded_struct_with_jiff_fields(t: &mut Test) -> Result<()> {
1177 #[derive(Debug, toasty::Embed)]
1178 struct Schedule {
1179 starts_at: jiff::Timestamp,
1180 due_date: jiff::civil::Date,
1181 reminder_time: jiff::civil::Time,
1182 scheduled_at: jiff::civil::DateTime,
1183 }
1184
1185 #[derive(Debug, toasty::Model)]
1186 struct Event {
1187 #[key]
1188 #[auto]
1189 id: ID,
1190 name: String,
1191 schedule: Schedule,
1192 }
1193
1194 let mut db = t.setup_db(models!(Event)).await;
1195
1196 let starts_at = jiff::Timestamp::from_second(1_700_000_000).unwrap();
1197 let due_date = jiff::civil::date(2025, 6, 15);
1198 let reminder_time = jiff::civil::time(9, 30, 0, 0);
1199 let scheduled_at = jiff::civil::datetime(2025, 6, 15, 9, 30, 0, 0);
1200
1201 let event = Event::create()
1202 .name("team sync")
1203 .schedule(Schedule {
1204 starts_at,
1205 due_date,
1206 reminder_time,
1207 scheduled_at,
1208 })
1209 .exec(&mut db)
1210 .await?;
1211
1212 let found = Event::get_by_id(&mut db, &event.id).await?;
1213 assert_struct!(found.schedule, {
1214 starts_at: == starts_at,
1215 due_date: == due_date,
1216 reminder_time: == reminder_time,
1217 scheduled_at: == scheduled_at,
1218 });
1219 Ok(())
1220}
1221
1222#[driver_test(id(ID))]
1225pub async fn unit_enum_in_embedded_struct(t: &mut Test) -> Result<()> {
1226 #[derive(Debug, PartialEq, toasty::Embed)]
1227 enum Priority {
1228 #[column(variant = 1)]
1229 Low,
1230 #[column(variant = 2)]
1231 Normal,
1232 #[column(variant = 3)]
1233 High,
1234 }
1235
1236 #[derive(Debug, toasty::Embed)]
1237 struct Meta {
1238 label: String,
1239 priority: Priority,
1240 }
1241
1242 #[derive(Debug, toasty::Model)]
1243 struct Task {
1244 #[key]
1245 #[auto]
1246 id: ID,
1247 meta: Meta,
1248 }
1249
1250 let mut db = t.setup_db(models!(Task)).await;
1251
1252 let mut task = Task::create()
1253 .meta(Meta {
1254 label: "fix bug".to_string(),
1255 priority: Priority::High,
1256 })
1257 .exec(&mut db)
1258 .await?;
1259
1260 let found = Task::get_by_id(&mut db, &task.id).await?;
1261 assert_eq!(found.meta.label, "fix bug");
1262 assert_eq!(found.meta.priority, Priority::High);
1263
1264 task.update()
1265 .meta(toasty::stmt::patch(
1266 Meta::fields().priority().into(),
1267 Priority::Normal,
1268 ))
1269 .exec(&mut db)
1270 .await?;
1271
1272 let found = Task::get_by_id(&mut db, &task.id).await?;
1273 assert_eq!(found.meta.priority, Priority::Normal);
1274
1275 Ok(())
1276}
1277
1278#[driver_test(id(ID))]
1283pub async fn embedded_struct_with_uuid_field(t: &mut Test) -> Result<()> {
1284 #[derive(Debug, toasty::Embed)]
1285 struct Meta {
1286 ref_id: Uuid,
1287 label: String,
1288 }
1289
1290 #[derive(Debug, toasty::Model)]
1291 struct Item {
1292 #[key]
1293 #[auto]
1294 id: ID,
1295 name: String,
1296 meta: Meta,
1297 }
1298
1299 let mut db = t.setup_db(models!(Item)).await;
1300
1301 let ref_id = Uuid::new_v4();
1302
1303 let item = Item::create()
1304 .name("widget")
1305 .meta(Meta {
1306 ref_id,
1307 label: "v1".to_string(),
1308 })
1309 .exec(&mut db)
1310 .await?;
1311
1312 let found = Item::get_by_id(&mut db, &item.id).await?;
1314 assert_eq!(found.meta.ref_id, ref_id);
1315 assert_eq!(found.meta.label, "v1");
1316
1317 Ok(())
1318}