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]
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: uuid::Uuid,
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))]
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 whole_embedded_struct_eq_ne(t: &mut Test) -> Result<()> {
401 #[derive(Debug, toasty::Embed)]
402 struct Point {
403 x: i64,
404 y: i64,
405 }
406
407 #[derive(Debug, toasty::Model)]
408 struct Pin {
409 #[key]
410 #[auto]
411 id: uuid::Uuid,
412 label: String,
413 location: Point,
414 }
415
416 let mut db = t.setup_db(models!(Pin)).await;
417
418 for (label, x, y) in [("a", 1, 1), ("b", 1, 2), ("c", 2, 1)] {
419 toasty::create!(Pin {
420 label,
421 location: Point { x, y },
422 })
423 .exec(&mut db)
424 .await?;
425 }
426
427 let hits = Pin::filter(Pin::fields().location().eq(Point { x: 1, y: 2 }))
428 .exec(&mut db)
429 .await?;
430 assert_eq!(hits.len(), 1);
431 assert_eq!(hits[0].label, "b");
432
433 let hits = Pin::filter(Pin::fields().location().ne(Point { x: 1, y: 2 }))
434 .exec(&mut db)
435 .await?;
436 let mut labels: Vec<_> = hits.iter().map(|p| p.label.as_str()).collect();
437 labels.sort();
438 assert_eq!(labels, ["a", "c"]);
439
440 Ok(())
441}
442
443#[driver_test(requires(scan))]
447pub async fn query_embedded_multiple_fields(t: &mut Test) -> Result<()> {
448 #[derive(Debug, toasty::Embed)]
449 struct Coordinates {
450 x: i64,
451 y: i64,
452 z: i64,
453 }
454
455 #[derive(Debug, toasty::Model)]
456 #[allow(dead_code)]
457 struct Location {
458 #[key]
459 #[auto]
460 id: uuid::Uuid,
461 name: String,
462 coords: Coordinates,
463 }
464
465 let mut db = t.setup_db(models!(Location)).await;
466
467 for (name, x, y, z) in [
468 ("Origin", 0, 0, 0),
469 ("Point A", 10, 20, 0),
470 ("Point B", 10, 30, 0),
471 ("Point C", 10, 20, 5),
472 ("Point D", 20, 20, 0),
473 ] {
474 Location::create()
475 .name(name)
476 .coords(Coordinates { x, y, z })
477 .exec(&mut db)
478 .await?;
479 }
480
481 let matching = Location::filter(
483 Location::fields()
484 .coords()
485 .x()
486 .eq(10)
487 .and(Location::fields().coords().y().eq(20)),
488 )
489 .exec(&mut db)
490 .await?;
491
492 assert_eq!(matching.len(), 2);
493 let mut names: Vec<_> = matching.iter().map(|l| l.name.as_str()).collect();
494 names.sort();
495 assert_eq!(names, ["Point A", "Point C"]);
496
497 let exact_match = Location::filter(
500 Location::fields()
501 .coords()
502 .x()
503 .eq(10)
504 .and(Location::fields().coords().y().eq(20))
505 .and(Location::fields().coords().z().eq(0)),
506 )
507 .exec(&mut db)
508 .await?;
509
510 assert_eq!(exact_match.len(), 1);
511 assert_eq!(exact_match[0].name, "Point A");
512 Ok(())
513}
514
515#[driver_test(requires(sql))]
519pub async fn update_with_embedded_field_filter(t: &mut Test) -> Result<()> {
520 #[derive(Debug, toasty::Embed)]
521 struct Metadata {
522 version: i64,
523 status: String,
524 }
525
526 #[derive(Debug, toasty::Model)]
527 #[allow(dead_code)]
528 struct Document {
529 #[key]
530 #[auto]
531 id: uuid::Uuid,
532 title: String,
533 meta: Metadata,
534 }
535
536 let mut db = t.setup_db(models!(Document)).await;
537
538 for (title, version, status) in [
540 ("Doc A", 1, "draft"),
541 ("Doc B", 2, "draft"),
542 ("Doc C", 1, "published"),
543 ] {
544 Document::create()
545 .title(title)
546 .meta(Metadata {
547 version,
548 status: status.to_string(),
549 })
550 .exec(&mut db)
551 .await?;
552 }
553
554 Document::filter(
557 Document::fields()
558 .meta()
559 .status()
560 .eq("draft")
561 .and(Document::fields().meta().version().eq(1)),
562 )
563 .update()
564 .meta(Metadata {
565 version: 2,
566 status: "draft".to_string(),
567 })
568 .exec(&mut db)
569 .await?;
570
571 let doc_a = Document::filter(Document::fields().title().eq("Doc A"))
573 .exec(&mut db)
574 .await?;
575 assert_eq!(doc_a[0].meta.version, 2);
576
577 let doc_b = Document::filter(Document::fields().title().eq("Doc B"))
579 .exec(&mut db)
580 .await?;
581 assert_eq!(doc_b[0].meta.version, 2);
582
583 let doc_c = Document::filter(Document::fields().title().eq("Doc C"))
585 .exec(&mut db)
586 .await?;
587 assert_eq!(doc_c[0].meta.version, 1);
588 Ok(())
589}
590
591#[driver_test(scenario(crate::scenarios::user_with_zip_address))]
595pub async fn partial_update_embedded_fields(t: &mut Test) -> Result<()> {
596 let mut db = setup(t).await;
597
598 let mut user = User::create()
600 .name("Alice")
601 .address(Address {
602 street: "123 Main St".to_string(),
603 city: "Boston".to_string(),
604 zip: "02101".to_string(),
605 })
606 .exec(&mut db)
607 .await?;
608
609 assert_struct!(user.address, {
611 street: "123 Main St",
612 city: "Boston",
613 zip: "02101",
614 });
615
616 user.update()
618 .address(toasty::stmt::patch(Address::fields().city(), "Seattle"))
619 .exec(&mut db)
620 .await?;
621
622 assert_struct!(user.address, {
624 street: "123 Main St",
625 city: "Seattle",
626 zip: "02101",
627 });
628
629 let found = User::get_by_id(&mut db, &user.id).await?;
631 assert_struct!(found.address, {
632 street: "123 Main St",
633 city: "Seattle",
634 zip: "02101",
635 });
636
637 user.update()
639 .address(toasty::stmt::apply([
640 toasty::stmt::patch(Address::fields().city(), "Portland"),
641 toasty::stmt::patch(Address::fields().zip(), "97201"),
642 ]))
643 .exec(&mut db)
644 .await?;
645
646 assert_struct!(user.address, {
648 street: "123 Main St",
649 city: "Portland",
650 zip: "97201",
651 });
652
653 let found = User::get_by_id(&mut db, &user.id).await?;
655 assert_struct!(found.address, {
656 street: "123 Main St",
657 city: "Portland",
658 zip: "97201",
659 });
660
661 user.update()
663 .address(toasty::stmt::patch(
664 Address::fields().street(),
665 "456 Oak Ave",
666 ))
667 .address(toasty::stmt::patch(Address::fields().zip(), "97202"))
668 .exec(&mut db)
669 .await?;
670
671 assert_struct!(user.address, {
673 street: "456 Oak Ave",
674 city: "Portland",
675 zip: "97202",
676 });
677
678 let found = User::get_by_id(&mut db, &user.id).await?;
680 assert_struct!(found.address, {
681 street: "456 Oak Ave",
682 city: "Portland",
683 zip: "97202",
684 });
685 Ok(())
686}
687
688#[driver_test]
696pub async fn deeply_nested_embedded_schema(test: &mut Test) {
697 #[derive(toasty::Embed)]
699 struct Location {
700 lat: i64,
701 lon: i64,
702 }
703
704 #[derive(toasty::Embed)]
705 struct City {
706 name: String,
707 location: Location,
708 }
709
710 #[derive(toasty::Embed)]
711 struct Address {
712 street: String,
713 city: City,
714 }
715
716 #[derive(toasty::Model)]
717 struct User {
718 #[key]
719 id: String,
720 #[allow(dead_code)]
721 address: Address,
722 }
723
724 let db = test.setup_db(models!(User)).await;
725 let schema = db.schema();
726
727 assert_struct!(schema.app.models, #{
729 Location::id(): toasty::schema::app::Model::EmbeddedStruct({
730 name.upper_camel_case(): "Location",
731 fields.len(): 2,
732 }),
733 City::id(): toasty::schema::app::Model::EmbeddedStruct({
734 name.upper_camel_case(): "City",
735 fields: [
736 { name.app: Some("name") },
737 {
738 name.app: Some("location"),
739 ty: FieldTy::Embedded({
740 target: == Location::id(),
741 }),
742 },
743 ],
744 }),
745 Address::id(): toasty::schema::app::Model::EmbeddedStruct({
746 name.upper_camel_case(): "Address",
747 fields: [
748 { name.app: Some("street") },
749 {
750 name.app: Some("city"),
751 ty: FieldTy::Embedded({
752 target: == City::id(),
753 }),
754 },
755 ],
756 }),
757 User::id(): toasty::schema::app::Model::Root({
758 name.upper_camel_case(): "User",
759 fields: [
760 { name.app: Some("id") },
761 {
762 name.app: Some("address"),
763 ty: FieldTy::Embedded({
764 target: == Address::id(),
765 }),
766 },
767 ],
768 }),
769 });
770
771 assert_struct!(schema.db.tables, [
779 {
780 name: =~ r"users$",
781 columns: [
782 { name: "id" },
783 { name: "address_street" },
784 { name: "address_city_name" },
785 { name: "address_city_location_lat" },
786 { name: "address_city_location_lon" },
787 ],
788 },
789 ]);
790
791 let user = &schema.app.models[&User::id()];
792 let user_table = schema.table_for(user);
793 let user_mapping = &schema.mapping.models[&User::id()];
794
795 assert_eq!(
808 user_mapping.fields.len(),
809 2,
810 "User should have 2 fields: id and address"
811 );
812
813 let address_field = user_mapping.fields[1]
815 .as_struct()
816 .expect("User.address should be Field::Struct");
817
818 assert_eq!(
819 address_field.fields.len(),
820 2,
821 "Address should have 2 fields: street and city"
822 );
823
824 let street_field = address_field.fields[0]
826 .as_primitive()
827 .expect("Address.street should be Field::Primitive");
828 assert_eq!(
829 street_field.column, user_table.columns[1].id,
830 "street should map to address_street column"
831 );
832
833 let city_field = address_field.fields[1]
835 .as_struct()
836 .expect("Address.city should be Field::Struct");
837
838 assert_eq!(
839 city_field.fields.len(),
840 2,
841 "City should have 2 fields: name and location"
842 );
843
844 let city_name_field = city_field.fields[0]
846 .as_primitive()
847 .expect("City.name should be Field::Primitive");
848 assert_eq!(
849 city_name_field.column, user_table.columns[2].id,
850 "city.name should map to address_city_name column"
851 );
852
853 let location_field = city_field.fields[1]
855 .as_struct()
856 .expect("City.location should be Field::Struct");
857
858 assert_eq!(
859 location_field.fields.len(),
860 2,
861 "Location should have 2 fields: lat and lon"
862 );
863
864 let lat_field = location_field.fields[0]
866 .as_primitive()
867 .expect("Location.lat should be Field::Primitive");
868 assert_eq!(
869 lat_field.column, user_table.columns[3].id,
870 "location.lat should map to address_city_location_lat column"
871 );
872
873 let lon_field = location_field.fields[1]
875 .as_primitive()
876 .expect("Location.lon should be Field::Primitive");
877 assert_eq!(
878 lon_field.column, user_table.columns[4].id,
879 "location.lon should map to address_city_location_lon column"
880 );
881
882 assert_eq!(
885 address_field.columns.len(),
886 4,
887 "Address.columns should have 4 entries"
888 );
889 assert!(
890 address_field
891 .columns
892 .contains_key(&user_table.columns[1].id),
893 "Address.columns should contain address_street"
894 );
895 assert!(
896 address_field
897 .columns
898 .contains_key(&user_table.columns[2].id),
899 "Address.columns should contain address_city_name"
900 );
901 assert!(
902 address_field
903 .columns
904 .contains_key(&user_table.columns[3].id),
905 "Address.columns should contain address_city_location_lat"
906 );
907 assert!(
908 address_field
909 .columns
910 .contains_key(&user_table.columns[4].id),
911 "Address.columns should contain address_city_location_lon"
912 );
913
914 assert_eq!(
916 city_field.columns.len(),
917 3,
918 "City.columns should have 3 entries"
919 );
920 assert!(
921 city_field.columns.contains_key(&user_table.columns[2].id),
922 "City.columns should contain address_city_name"
923 );
924 assert!(
925 city_field.columns.contains_key(&user_table.columns[3].id),
926 "City.columns should contain address_city_location_lat"
927 );
928 assert!(
929 city_field.columns.contains_key(&user_table.columns[4].id),
930 "City.columns should contain address_city_location_lon"
931 );
932
933 assert_eq!(
935 location_field.columns.len(),
936 2,
937 "Location.columns should have 2 entries"
938 );
939 assert!(
940 location_field
941 .columns
942 .contains_key(&user_table.columns[3].id),
943 "Location.columns should contain address_city_location_lat"
944 );
945 assert!(
946 location_field
947 .columns
948 .contains_key(&user_table.columns[4].id),
949 "Location.columns should contain address_city_location_lon"
950 );
951
952 assert_eq!(
955 user_mapping.model_to_table.len(),
956 5,
957 "model_to_table should have 5 expressions"
958 );
959
960 assert_struct!(
962 user_mapping.model_to_table[1],
963 == stmt::Expr::project(
964 stmt::Expr::ref_self_field(user.as_root_unwrap().fields[1].id),
965 [0],
966 )
967 );
968
969 assert_struct!(
971 user_mapping.model_to_table[2],
972 == stmt::Expr::project(
973 stmt::Expr::ref_self_field(user.as_root_unwrap().fields[1].id),
974 [1, 0],
975 )
976 );
977
978 assert_struct!(
980 user_mapping.model_to_table[3],
981 == stmt::Expr::project(
982 stmt::Expr::ref_self_field(user.as_root_unwrap().fields[1].id),
983 [1, 1, 0],
984 )
985 );
986
987 assert_struct!(
989 user_mapping.model_to_table[4],
990 == stmt::Expr::project(
991 stmt::Expr::ref_self_field(user.as_root_unwrap().fields[1].id),
992 [1, 1, 1],
993 )
994 );
995}
996
997#[driver_test(scenario(crate::scenarios::company_office_address))]
1001pub async fn crud_nested_embedded(t: &mut Test) -> Result<()> {
1002 let mut db = setup(t).await;
1003
1004 let mut company = Company::create()
1006 .name("Acme")
1007 .headquarters(Office {
1008 name: "Main Office".to_string(),
1009 address: Address {
1010 street: "123 Main St".to_string(),
1011 city: "Springfield".to_string(),
1012 },
1013 })
1014 .exec(&mut db)
1015 .await?;
1016
1017 assert_struct!(company.headquarters, {
1018 name: "Main Office",
1019 address: {
1020 street: "123 Main St",
1021 city: "Springfield",
1022 },
1023 });
1024
1025 let found = Company::get_by_id(&mut db, &company.id).await?;
1027 assert_struct!(found.headquarters, {
1028 name: "Main Office",
1029 address: {
1030 street: "123 Main St",
1031 city: "Springfield",
1032 },
1033 });
1034
1035 company
1037 .update()
1038 .headquarters(Office {
1039 name: "West Coast HQ".to_string(),
1040 address: Address {
1041 street: "456 Oak Ave".to_string(),
1042 city: "Seattle".to_string(),
1043 },
1044 })
1045 .exec(&mut db)
1046 .await?;
1047
1048 let found = Company::get_by_id(&mut db, &company.id).await?;
1049 assert_struct!(found.headquarters, {
1050 name: "West Coast HQ",
1051 address: {
1052 street: "456 Oak Ave",
1053 city: "Seattle",
1054 },
1055 });
1056
1057 Company::filter_by_id(company.id)
1059 .update()
1060 .headquarters(Office {
1061 name: "East Coast HQ".to_string(),
1062 address: Address {
1063 street: "789 Pine Rd".to_string(),
1064 city: "Boston".to_string(),
1065 },
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: "East Coast HQ",
1073 address: {
1074 street: "789 Pine Rd",
1075 city: "Boston",
1076 },
1077 });
1078
1079 let id = company.id;
1081 company.delete().exec(&mut db).await?;
1082 assert_err!(Company::get_by_id(&mut db, &id).await);
1083 Ok(())
1084}
1085
1086#[driver_test(scenario(crate::scenarios::company_office_address))]
1091pub async fn partial_update_nested_embedded(t: &mut Test) -> Result<()> {
1092 let mut db = setup(t).await;
1093
1094 let mut company = Company::create()
1095 .name("Acme")
1096 .headquarters(Office {
1097 name: "Main Office".to_string(),
1098 address: Address {
1099 street: "123 Main St".to_string(),
1100 city: "Boston".to_string(),
1101 },
1102 })
1103 .exec(&mut db)
1104 .await?;
1105
1106 company
1109 .update()
1110 .headquarters(toasty::stmt::patch(
1111 Office::fields().address().city(),
1112 "Seattle",
1113 ))
1114 .exec(&mut db)
1115 .await?;
1116
1117 let found = Company::get_by_id(&mut db, &company.id).await?;
1118 assert_struct!(found.headquarters, {
1119 name: "Main Office",
1120 address: {
1121 street: "123 Main St",
1122 city: "Seattle",
1123 },
1124 });
1125
1126 company
1129 .update()
1130 .headquarters(toasty::stmt::patch(
1131 Office::fields().name(),
1132 "West Coast HQ",
1133 ))
1134 .exec(&mut db)
1135 .await?;
1136
1137 let found = Company::get_by_id(&mut db, &company.id).await?;
1138 assert_struct!(found.headquarters, {
1139 name: "West Coast HQ",
1140 address: {
1141 street: "123 Main St",
1142 city: "Seattle",
1143 },
1144 });
1145
1146 company
1149 .update()
1150 .headquarters(toasty::stmt::apply([
1151 toasty::stmt::patch(Office::fields().name(), "East Coast HQ"),
1152 toasty::stmt::patch(Office::fields().address().city(), "Boston"),
1153 ]))
1154 .exec(&mut db)
1155 .await?;
1156
1157 let found = Company::get_by_id(&mut db, &company.id).await?;
1158 assert_struct!(found.headquarters, {
1159 name: "East Coast HQ",
1160 address: {
1161 street: "123 Main St",
1162 city: "Boston",
1163 },
1164 });
1165 Ok(())
1166}
1167
1168#[driver_test(scenario(crate::scenarios::user_with_zip_address))]
1173pub async fn query_based_partial_update_embedded(t: &mut Test) -> Result<()> {
1174 let mut db = setup(t).await;
1175
1176 let user = User::create()
1177 .name("Alice")
1178 .address(Address {
1179 street: "123 Main St".to_string(),
1180 city: "Boston".to_string(),
1181 zip: "02101".to_string(),
1182 })
1183 .exec(&mut db)
1184 .await?;
1185
1186 User::filter_by_id(user.id)
1189 .update()
1190 .address(toasty::stmt::patch(Address::fields().city(), "Seattle"))
1191 .exec(&mut db)
1192 .await?;
1193
1194 let found = User::get_by_id(&mut db, &user.id).await?;
1195 assert_struct!(found.address, {
1196 street: "123 Main St",
1197 city: "Seattle",
1198 zip: "02101",
1199 });
1200
1201 User::filter_by_id(user.id)
1203 .update()
1204 .address(toasty::stmt::apply([
1205 toasty::stmt::patch(Address::fields().city(), "Portland"),
1206 toasty::stmt::patch(Address::fields().zip(), "97201"),
1207 ]))
1208 .exec(&mut db)
1209 .await?;
1210
1211 let found = User::get_by_id(&mut db, &user.id).await?;
1212 assert_struct!(found.address, {
1213 street: "123 Main St",
1214 city: "Portland",
1215 zip: "97201",
1216 });
1217 Ok(())
1218}
1219
1220#[driver_test]
1223pub async fn embedded_struct_with_jiff_fields(t: &mut Test) -> Result<()> {
1224 #[derive(Debug, toasty::Embed)]
1225 struct Schedule {
1226 starts_at: jiff::Timestamp,
1227 due_date: jiff::civil::Date,
1228 reminder_time: jiff::civil::Time,
1229 scheduled_at: jiff::civil::DateTime,
1230 }
1231
1232 #[derive(Debug, toasty::Model)]
1233 struct Event {
1234 #[key]
1235 #[auto]
1236 id: uuid::Uuid,
1237 name: String,
1238 schedule: Schedule,
1239 }
1240
1241 let mut db = t.setup_db(models!(Event)).await;
1242
1243 let starts_at = jiff::Timestamp::from_second(1_700_000_000).unwrap();
1244 let due_date = jiff::civil::date(2025, 6, 15);
1245 let reminder_time = jiff::civil::time(9, 30, 0, 0);
1246 let scheduled_at = jiff::civil::datetime(2025, 6, 15, 9, 30, 0, 0);
1247
1248 let event = Event::create()
1249 .name("team sync")
1250 .schedule(Schedule {
1251 starts_at,
1252 due_date,
1253 reminder_time,
1254 scheduled_at,
1255 })
1256 .exec(&mut db)
1257 .await?;
1258
1259 let found = Event::get_by_id(&mut db, &event.id).await?;
1260 assert_struct!(found.schedule, {
1261 starts_at: == starts_at,
1262 due_date: == due_date,
1263 reminder_time: == reminder_time,
1264 scheduled_at: == scheduled_at,
1265 });
1266 Ok(())
1267}
1268
1269#[driver_test]
1272pub async fn unit_enum_in_embedded_struct(t: &mut Test) -> Result<()> {
1273 #[derive(Debug, PartialEq, toasty::Embed)]
1274 enum Priority {
1275 #[column(variant = 1)]
1276 Low,
1277 #[column(variant = 2)]
1278 Normal,
1279 #[column(variant = 3)]
1280 High,
1281 }
1282
1283 #[derive(Debug, toasty::Embed)]
1284 struct Meta {
1285 label: String,
1286 priority: Priority,
1287 }
1288
1289 #[derive(Debug, toasty::Model)]
1290 struct Task {
1291 #[key]
1292 #[auto]
1293 id: uuid::Uuid,
1294 meta: Meta,
1295 }
1296
1297 let mut db = t.setup_db(models!(Task)).await;
1298
1299 let mut task = Task::create()
1300 .meta(Meta {
1301 label: "fix bug".to_string(),
1302 priority: Priority::High,
1303 })
1304 .exec(&mut db)
1305 .await?;
1306
1307 let found = Task::get_by_id(&mut db, &task.id).await?;
1308 assert_eq!(found.meta.label, "fix bug");
1309 assert_eq!(found.meta.priority, Priority::High);
1310
1311 task.update()
1312 .meta(toasty::stmt::patch(
1313 Meta::fields().priority().into(),
1314 Priority::Normal,
1315 ))
1316 .exec(&mut db)
1317 .await?;
1318
1319 let found = Task::get_by_id(&mut db, &task.id).await?;
1320 assert_eq!(found.meta.priority, Priority::Normal);
1321
1322 Ok(())
1323}
1324
1325#[driver_test]
1330pub async fn embedded_struct_with_uuid_field(t: &mut Test) -> Result<()> {
1331 #[derive(Debug, toasty::Embed)]
1332 struct Meta {
1333 ref_id: Uuid,
1334 label: String,
1335 }
1336
1337 #[derive(Debug, toasty::Model)]
1338 struct Item {
1339 #[key]
1340 #[auto]
1341 id: uuid::Uuid,
1342 name: String,
1343 meta: Meta,
1344 }
1345
1346 let mut db = t.setup_db(models!(Item)).await;
1347
1348 let ref_id = Uuid::new_v4();
1349
1350 let item = Item::create()
1351 .name("widget")
1352 .meta(Meta {
1353 ref_id,
1354 label: "v1".to_string(),
1355 })
1356 .exec(&mut db)
1357 .await?;
1358
1359 let found = Item::get_by_id(&mut db, &item.id).await?;
1361 assert_eq!(found.meta.ref_id, ref_id);
1362 assert_eq!(found.meta.label, "v1");
1363
1364 Ok(())
1365}