Skip to main content

toasty_driver_integration_suite/tests/
deferred_embed.rs

1use crate::prelude::*;
2
3// ---------- Deferred<Embed> on a struct embed ----------
4
5#[driver_test(id(ID), scenario(crate::scenarios::document_deferred_metadata))]
6pub async fn deferred_embed_struct(t: &mut Test) -> Result<()> {
7    let mut db = setup(t).await;
8
9    let created = toasty::create!(Document {
10        title: "Hello".to_string(),
11        metadata: Metadata {
12            author: "Alice".to_string(),
13            notes: "Important".to_string(),
14        },
15    })
16    .exec(&mut db)
17    .await?;
18
19    // Created records have the deferred embed loaded with the value the caller
20    // just supplied.
21    assert_eq!("Hello", created.title);
22    assert_eq!("Alice", created.metadata.get().author);
23    assert_eq!("Important", created.metadata.get().notes);
24
25    // A separate query leaves the deferred embed unloaded.
26    let read = Document::filter_by_id(created.id).get(&mut db).await?;
27    assert_eq!("Hello", read.title);
28    assert!(read.metadata.is_unloaded());
29
30    // `.include()` preloads the embed onto the parent query.
31    let read_with = Document::filter_by_id(created.id)
32        .include(Document::fields().metadata())
33        .get(&mut db)
34        .await?;
35    assert!(!read_with.metadata.is_unloaded());
36    assert_eq!("Alice", read_with.metadata.get().author);
37    assert_eq!("Important", read_with.metadata.get().notes);
38
39    Ok(())
40}
41
42// `Option<Embed>` is now supported, and a deferred sub-field *inside* an
43// `Option<Embed>` is covered by `deferred_field_inside_option_embed` (and
44// `deferred_newtype_inside_option_embed`) below. `Deferred<Option<Embed>>` (a
45// deferred wrapping the whole option) is a separate, orthogonal case still to
46// be covered.
47
48// ---------- Deferred<T> inside an embed struct that's nested in an enum variant ----------
49//
50// A struct embedded as a variant field is allowed to carry deferred
51// sub-fields. The lowering has to descend through the enum's `Match`
52// expression to mask / wrap those sub-fields.
53
54#[driver_test(id(ID), scenario(crate::scenarios::person_contact_deferred_metadata))]
55pub async fn deferred_inside_embed_in_enum_variant(t: &mut Test) -> Result<()> {
56    let mut db = setup(t).await;
57
58    // INSERT...RETURNING must echo back the deferred sub-field nested two
59    // levels deep (through the enum variant and through the embed struct).
60    let created = toasty::create!(Person {
61        name: "Alice".to_string(),
62        contact: ContactInfo::Email {
63            address: "alice@example.com".to_string(),
64            metadata: Metadata {
65                author: "Alice".to_string(),
66                notes: "Important".to_string().into(),
67            },
68        },
69    })
70    .exec(&mut db)
71    .await?;
72
73    let ContactInfo::Email {
74        address, metadata, ..
75    } = &created.contact
76    else {
77        panic!("expected Email variant");
78    };
79    assert_eq!("alice@example.com", address);
80    assert_eq!("Alice", metadata.author);
81    assert_eq!("Important", metadata.notes.get());
82
83    // Default load: contact loaded, but the deferred sub-field nested inside
84    // the variant's Metadata embed is unloaded.
85    let read = Person::filter_by_id(created.id).get(&mut db).await?;
86    let ContactInfo::Email { metadata, .. } = &read.contact else {
87        panic!("expected Email variant");
88    };
89    assert_eq!("Alice", metadata.author);
90    assert!(metadata.notes.is_unloaded());
91
92    Ok(())
93}
94
95// `.include()` reaching a deferred sub-field that lives inside a struct embed
96// nested inside an enum variant. The variant handle exposes the same field
97// accessors as a struct embed, returning variant-rooted Paths that the
98// engine flattens into `[contact_idx, variant_idx, …]` projections and
99// dispatches into the matching arm of the embed enum's `Match`.
100
101#[driver_test(id(ID), scenario(crate::scenarios::person_contact_deferred_metadata))]
102pub async fn include_deferred_inside_embed_in_enum_variant(t: &mut Test) -> Result<()> {
103    let mut db = setup(t).await;
104
105    let alice = toasty::create!(Person {
106        name: "Alice".to_string(),
107        contact: ContactInfo::Email {
108            address: "alice@example.com".to_string(),
109            metadata: Metadata {
110                author: "Alice".to_string(),
111                notes: "Important".to_string().into(),
112            },
113        },
114    })
115    .exec(&mut db)
116    .await?;
117
118    // Bob's contact is a different variant, used to verify that an include
119    // routed through Email doesn't activate anything for him.
120    let bob = toasty::create!(Person {
121        name: "Bob".to_string(),
122        contact: ContactInfo::Phone {
123            number: "555-0100".to_string(),
124        },
125    })
126    .exec(&mut db)
127    .await?;
128
129    // Alice's variant matches the path — `notes` arrives loaded.
130    let alice_read = Person::filter_by_id(alice.id)
131        .include(Person::fields().contact().email().metadata().notes())
132        .get(&mut db)
133        .await?;
134    let ContactInfo::Email { metadata, .. } = &alice_read.contact else {
135        panic!("expected Email variant");
136    };
137    assert_eq!("Alice", metadata.author);
138    assert!(!metadata.notes.is_unloaded());
139    assert_eq!("Important", metadata.notes.get());
140
141    // Bob's variant doesn't match the include path's arm — the include is a
142    // no-op for him: the row still loads cleanly with the Phone variant.
143    let bob_read = Person::filter_by_id(bob.id)
144        .include(Person::fields().contact().email().metadata().notes())
145        .get(&mut db)
146        .await?;
147    let ContactInfo::Phone { number } = &bob_read.contact else {
148        panic!("expected Phone variant");
149    };
150    assert_eq!("555-0100", number);
151
152    Ok(())
153}
154
155// ---------- Deferred<UnitEnum> ----------
156
157#[driver_test(id(ID))]
158pub async fn deferred_embed_unit_enum(t: &mut Test) -> Result<()> {
159    #[derive(Debug, PartialEq, toasty::Embed)]
160    enum Status {
161        Draft,
162        Published,
163        Archived,
164    }
165
166    #[derive(Debug, toasty::Model)]
167    struct Document {
168        #[key]
169        #[auto]
170        id: ID,
171
172        title: String,
173        status: toasty::Deferred<Status>,
174    }
175
176    let mut db = t.setup_db(models!(Document)).await;
177
178    let created = toasty::create!(Document {
179        title: "Hello".to_string(),
180        status: Status::Published,
181    })
182    .exec(&mut db)
183    .await?;
184
185    assert_eq!(&Status::Published, created.status.get());
186
187    let read = Document::filter_by_id(created.id).get(&mut db).await?;
188    assert!(read.status.is_unloaded());
189
190    let inc = Document::filter_by_id(created.id)
191        .include(Document::fields().status())
192        .get(&mut db)
193        .await?;
194    assert!(!inc.status.is_unloaded());
195    assert_eq!(&Status::Published, inc.status.get());
196
197    Ok(())
198}
199
200// ---------- Deferred<DataCarryingEnum> ----------
201
202#[driver_test(id(ID))]
203pub async fn deferred_embed_data_enum(t: &mut Test) -> Result<()> {
204    #[derive(Debug, PartialEq, toasty::Embed)]
205    enum ContactInfo {
206        Email { address: String },
207        Phone { number: String },
208        Mail,
209    }
210
211    #[derive(Debug, toasty::Model)]
212    struct Person {
213        #[key]
214        #[auto]
215        id: ID,
216
217        name: String,
218        contact: toasty::Deferred<ContactInfo>,
219    }
220
221    let mut db = t.setup_db(models!(Person)).await;
222
223    let alice = toasty::create!(Person {
224        name: "Alice".to_string(),
225        contact: ContactInfo::Email {
226            address: "alice@example.com".to_string(),
227        },
228    })
229    .exec(&mut db)
230    .await?;
231
232    let bob = toasty::create!(Person {
233        name: "Bob".to_string(),
234        contact: ContactInfo::Mail,
235    })
236    .exec(&mut db)
237    .await?;
238
239    assert_eq!(
240        &ContactInfo::Email {
241            address: "alice@example.com".to_string()
242        },
243        alice.contact.get()
244    );
245
246    let read = Person::filter_by_id(alice.id).get(&mut db).await?;
247    assert!(read.contact.is_unloaded());
248
249    let inc = Person::filter_by_id(alice.id)
250        .include(Person::fields().contact())
251        .get(&mut db)
252        .await?;
253    assert!(!inc.contact.is_unloaded());
254    assert_eq!(
255        &ContactInfo::Email {
256            address: "alice@example.com".to_string()
257        },
258        inc.contact.get()
259    );
260
261    let inc_bob = Person::filter_by_id(bob.id)
262        .include(Person::fields().contact())
263        .get(&mut db)
264        .await?;
265    assert_eq!(&ContactInfo::Mail, inc_bob.contact.get());
266
267    Ok(())
268}
269
270// ---------- Updating a deferred embed reloads with the new value ----------
271
272#[driver_test(id(ID), scenario(crate::scenarios::document_deferred_metadata))]
273pub async fn deferred_embed_update_reloads(t: &mut Test) -> Result<()> {
274    let mut db = setup(t).await;
275
276    let created = toasty::create!(Document {
277        title: "Hello".to_string(),
278        metadata: Metadata {
279            author: "Alice".to_string(),
280            notes: "old".to_string(),
281        },
282    })
283    .exec(&mut db)
284    .await?;
285
286    let mut doc = Document::filter_by_id(created.id).get(&mut db).await?;
287    assert!(doc.metadata.is_unloaded());
288
289    doc.update()
290        .metadata(Metadata {
291            author: "Bob".to_string(),
292            notes: "new".to_string(),
293        })
294        .exec(&mut db)
295        .await?;
296
297    // The caller supplied the value, so the field becomes loaded post-update.
298    assert!(!doc.metadata.is_unloaded());
299    assert_eq!("Bob", doc.metadata.get().author);
300    assert_eq!("new", doc.metadata.get().notes);
301
302    Ok(())
303}
304
305// ---------- Deferred<Embed> with Deferred<T> inside the embed ----------
306//
307// The combined shape: the embed itself is deferred at the parent, AND the
308// embed has its own deferred sub-field. `.include(metadata())` loads the
309// outer wrapper but leaves the inner deferred sub-field unloaded;
310// `.include(metadata().notes())` loads both.
311
312#[driver_test(id(ID))]
313pub async fn deferred_embed_with_deferred_sub_field(t: &mut Test) -> Result<()> {
314    #[derive(Debug, toasty::Embed)]
315    struct Metadata {
316        author: String,
317        notes: toasty::Deferred<String>,
318    }
319
320    #[derive(Debug, toasty::Model)]
321    struct Document {
322        #[key]
323        #[auto]
324        id: ID,
325
326        title: String,
327        metadata: toasty::Deferred<Metadata>,
328    }
329
330    let mut db = t.setup_db(models!(Document)).await;
331
332    let created = toasty::create!(Document {
333        title: "Hello".to_string(),
334        metadata: Metadata {
335            author: "Alice".to_string(),
336            notes: "Important".to_string().into(),
337        },
338    })
339    .exec(&mut db)
340    .await?;
341
342    // INSERT...RETURNING returns everything loaded.
343    assert_eq!("Alice", created.metadata.get().author);
344    assert_eq!("Important", created.metadata.get().notes.get());
345
346    // Default load: metadata itself unloaded.
347    let read = Document::filter_by_id(created.id).get(&mut db).await?;
348    assert!(read.metadata.is_unloaded());
349
350    // Including just the outer embed loads `author` but leaves the inner
351    // deferred sub-field unloaded.
352    let inc_outer = Document::filter_by_id(created.id)
353        .include(Document::fields().metadata())
354        .get(&mut db)
355        .await?;
356    assert!(!inc_outer.metadata.is_unloaded());
357    assert_eq!("Alice", inc_outer.metadata.get().author);
358    assert!(inc_outer.metadata.get().notes.is_unloaded());
359
360    // Including the inner deferred sub-field implies the outer is loaded too,
361    // and the inner sub-field arrives loaded.
362    let inc_inner = Document::filter_by_id(created.id)
363        .include(Document::fields().metadata().notes())
364        .get(&mut db)
365        .await?;
366    assert!(!inc_inner.metadata.is_unloaded());
367    assert_eq!("Alice", inc_inner.metadata.get().author);
368    assert!(!inc_inner.metadata.get().notes.is_unloaded());
369    assert_eq!("Important", inc_inner.metadata.get().notes.get());
370
371    Ok(())
372}
373
374// ---------- Deferred<T> inside an Embed (per-column) ----------
375
376#[driver_test(id(ID), scenario(crate::scenarios::document_metadata_deferred_notes))]
377pub async fn deferred_field_inside_embed(t: &mut Test) -> Result<()> {
378    let mut db = setup(t).await;
379
380    let created = toasty::create!(Document {
381        title: "Hello".to_string(),
382        metadata: Metadata {
383            author: "Alice".to_string(),
384            notes: "Important".to_string().into(),
385        },
386    })
387    .exec(&mut db)
388    .await?;
389
390    // Created records carry the just-supplied value loaded.
391    assert_eq!("Alice", created.metadata.author);
392    assert_eq!("Important", created.metadata.notes.get());
393
394    // Default load: embed eager fields are loaded, deferred sub-field is not.
395    let read = Document::filter_by_id(created.id).get(&mut db).await?;
396    assert_eq!("Alice", read.metadata.author);
397    assert!(read.metadata.notes.is_unloaded());
398
399    // Including the deferred sub-field loads it on the same query.
400    let inc = Document::filter_by_id(created.id)
401        .include(Document::fields().metadata().notes())
402        .get(&mut db)
403        .await?;
404    assert!(!inc.metadata.notes.is_unloaded());
405    assert_eq!("Important", inc.metadata.notes.get());
406
407    Ok(())
408}
409
410// Updating an eager embed by whole-value when that embed contains a
411// deferred sub-field. The struct literal supplies the deferred sub-field
412// via `From<T>` (`.into()`), the encoder unwraps it through
413// `Deferred<T>: IntoExpr<T>`, and the column is written.
414
415#[driver_test(id(ID), scenario(crate::scenarios::document_metadata_deferred_notes))]
416pub async fn update_embed_by_value_with_deferred_sub_field(t: &mut Test) -> Result<()> {
417    let mut db = setup(t).await;
418
419    let mut doc = toasty::create!(Document {
420        title: "Hello".to_string(),
421        metadata: Metadata {
422            author: "Alice".to_string(),
423            notes: "old".to_string().into(),
424        },
425    })
426    .exec(&mut db)
427    .await?;
428
429    doc.update()
430        .metadata(Metadata {
431            author: "Bob".to_string(),
432            notes: "new".to_string().into(),
433        })
434        .exec(&mut db)
435        .await?;
436
437    // Both columns are written and the in-memory record reflects the update.
438    assert_eq!("Bob", doc.metadata.author);
439    assert_eq!("new", doc.metadata.notes.get());
440
441    // Re-read with the deferred sub-field included to confirm both columns
442    // were persisted.
443    let reread = Document::filter_by_id(doc.id)
444        .include(Document::fields().metadata().notes())
445        .get(&mut db)
446        .await?;
447    assert_eq!("Bob", reread.metadata.author);
448    assert_eq!("new", reread.metadata.notes.get());
449
450    Ok(())
451}
452
453// A deferred sub-field inside an `Option<Embed>` must behave like one inside a
454// non-optional embed: loaded on create (the just-supplied value echoes back via
455// `INSERT … RETURNING`), unloaded on a default read. Regression test — the
456// presence `Match` that wraps a nullable embed's `default_returning` previously
457// hid the record from `process_embed`, leaving the deferred slot unloaded after
458// create.
459#[driver_test(
460    id(ID),
461    scenario(crate::scenarios::document_optional_metadata_deferred_notes)
462)]
463pub async fn deferred_field_inside_option_embed(t: &mut Test) -> Result<()> {
464    let mut db = setup(t).await;
465
466    let created = toasty::create!(Document {
467        title: "Hello".to_string(),
468        metadata: Some(Metadata {
469            author: "Alice".to_string(),
470            notes: "Important".to_string().into(),
471        }),
472    })
473    .exec(&mut db)
474    .await?;
475
476    // Created records carry the just-supplied deferred value loaded.
477    let md = created.metadata.as_ref().expect("Some");
478    assert_eq!("Alice", md.author);
479    assert_eq!("Important", md.notes.get());
480
481    // Default load: embed eager field is loaded, deferred sub-field is not.
482    let read = Document::filter_by_id(created.id).get(&mut db).await?;
483    let md = read.metadata.as_ref().expect("Some");
484    assert_eq!("Alice", md.author);
485    assert!(md.notes.is_unloaded());
486
487    // `None` round-trips as `None`.
488    let empty = toasty::create!(Document {
489        title: "Empty".to_string(),
490        metadata: None,
491    })
492    .exec(&mut db)
493    .await?;
494    assert!(empty.metadata.is_none());
495
496    Ok(())
497}
498
499// A deferred field is also the *single* leaf of a newtype embed
500// (`Option<Body>` over `struct Body(Deferred<String>)`), which reuses that one
501// leaf as its head column. The deferred sub-field must still behave like one in
502// a multi-field embed: loaded on create (echoed via `INSERT … RETURNING`),
503// unloaded on a default read. Guards the reuse path's presence `Match` wrapping
504// of the deferred record.
505#[driver_test(id(ID), scenario(crate::scenarios::document_optional_body_deferred))]
506pub async fn deferred_newtype_inside_option_embed(t: &mut Test) -> Result<()> {
507    let mut db = setup(t).await;
508
509    let created = toasty::create!(Document {
510        title: "Hello".to_string(),
511        body: Some(Body("Important".to_string().into())),
512    })
513    .exec(&mut db)
514    .await?;
515
516    // Created records carry the just-supplied deferred value loaded.
517    let body = created.body.as_ref().expect("Some");
518    assert_eq!("Important", body.0.get());
519
520    // Default load: the deferred newtype leaf is not loaded.
521    let read = Document::filter_by_id(created.id).get(&mut db).await?;
522    let body = read.body.as_ref().expect("Some");
523    assert!(body.0.is_unloaded());
524
525    // `None` round-trips as `None`.
526    let empty = toasty::create!(Document {
527        title: "Empty".to_string(),
528        body: None,
529    })
530    .exec(&mut db)
531    .await?;
532    assert!(empty.body.is_none());
533
534    Ok(())
535}