1use crate::helpers::column;
16use crate::prelude::*;
17
18use toasty_core::stmt::{Expr, Value};
19
20#[driver_test(scenario(crate::scenarios::document_optional_metadata))]
23pub async fn option_embed_db_schema(test: &mut Test) {
24 let db = setup(test).await;
25 let schema = db.schema();
26
27 assert_struct!(schema.db.tables, [
28 {
29 name: =~ r"documents$",
30 columns: [
31 { name: "id" },
32 { name: "metadata", nullable: true },
33 { name: "metadata_author", nullable: true },
34 { name: "metadata_note", nullable: true },
35 ],
36 },
37 ]);
38}
39
40#[driver_test(scenario(crate::scenarios::document_optional_metadata))]
42pub async fn option_embed_crud(test: &mut Test) -> Result<()> {
43 let mut db = setup(test).await;
44
45 toasty::create!(Document {
46 id: "with",
47 metadata: Some(Metadata {
48 author: "alice".to_string(),
49 note: "hi".to_string(),
50 }),
51 })
52 .exec(&mut db)
53 .await?;
54
55 toasty::create!(Document {
56 id: "without",
57 metadata: None,
58 })
59 .exec(&mut db)
60 .await?;
61
62 let with = Document::get_by_id(&mut db, "with").await?;
63 assert_struct!(with.metadata, Some(_ {
64 author: "alice",
65 note: "hi",
66 }));
67
68 let without = Document::get_by_id(&mut db, "without").await?;
69 assert_none!(without.metadata);
70
71 Ok(())
72}
73
74#[driver_test(requires(scan), scenario(crate::scenarios::document_optional_metadata))]
76pub async fn option_embed_filter_presence(test: &mut Test) -> Result<()> {
77 let mut db = setup(test).await;
78
79 toasty::create!(Document {
80 id: "with",
81 metadata: Some(Metadata {
82 author: "alice".to_string(),
83 note: "hi".to_string(),
84 }),
85 })
86 .exec(&mut db)
87 .await?;
88 toasty::create!(Document {
89 id: "without",
90 metadata: None,
91 })
92 .exec(&mut db)
93 .await?;
94
95 let present = Document::filter(Document::fields().metadata().is_some())
96 .exec(&mut db)
97 .await?;
98 assert_struct!(present, [_ { id: "with", .. }]);
99
100 let absent = Document::filter(Document::fields().metadata().is_none())
101 .exec(&mut db)
102 .await?;
103 assert_struct!(absent, [_ { id: "without", .. }]);
104
105 Ok(())
106}
107
108#[driver_test(requires(scan), scenario(crate::scenarios::document_optional_metadata))]
110pub async fn option_embed_filter_eq(test: &mut Test) -> Result<()> {
111 let mut db = setup(test).await;
112
113 toasty::create!(Document {
114 id: "alice",
115 metadata: Some(Metadata {
116 author: "alice".to_string(),
117 note: "hi".to_string(),
118 }),
119 })
120 .exec(&mut db)
121 .await?;
122 toasty::create!(Document {
123 id: "bob",
124 metadata: Some(Metadata {
125 author: "bob".to_string(),
126 note: "yo".to_string(),
127 }),
128 })
129 .exec(&mut db)
130 .await?;
131 toasty::create!(Document {
132 id: "without",
133 metadata: None,
134 })
135 .exec(&mut db)
136 .await?;
137
138 let matches = Document::filter(Document::fields().metadata().eq(Some(Metadata {
139 author: "alice".to_string(),
140 note: "hi".to_string(),
141 })))
142 .exec(&mut db)
143 .await?;
144 assert_struct!(matches, [_ { id: "alice", .. }]);
145
146 Ok(())
147}
148
149#[driver_test(scenario(crate::scenarios::document_optional_metadata))]
151pub async fn option_embed_update(test: &mut Test) -> Result<()> {
152 let mut db = setup(test).await;
153
154 toasty::create!(Document {
155 id: "a",
156 metadata: Some(Metadata {
157 author: "alice".to_string(),
158 note: "hi".to_string(),
159 }),
160 })
161 .exec(&mut db)
162 .await?;
163 toasty::create!(Document {
164 id: "b",
165 metadata: None,
166 })
167 .exec(&mut db)
168 .await?;
169
170 Document::filter_by_id("a")
172 .update()
173 .metadata(None)
174 .exec(&mut db)
175 .await?;
176 assert_none!(Document::get_by_id(&mut db, "a").await?.metadata);
177
178 Document::filter_by_id("b")
180 .update()
181 .metadata(Some(Metadata {
182 author: "bob".to_string(),
183 note: "yo".to_string(),
184 }))
185 .exec(&mut db)
186 .await?;
187 assert_struct!(Document::get_by_id(&mut db, "b").await?.metadata, Some(_ {
188 author: "bob",
189 note: "yo",
190 }));
191
192 Ok(())
193}
194
195#[driver_test(scenario(crate::scenarios::document_optional_metadata))]
200pub async fn option_embed_create_ops(test: &mut Test) -> Result<()> {
201 let mut db = setup(test).await;
202 let presence = column(&db, "documents", "metadata").index;
203 let author = column(&db, "documents", "metadata_author").index;
204 let note = column(&db, "documents", "metadata_note").index;
205 test.log().clear();
206
207 toasty::create!(Document {
208 id: "a",
209 metadata: Some(Metadata {
210 author: "alice".to_string(),
211 note: "hi".to_string(),
212 }),
213 })
214 .exec(&mut db)
215 .await?;
216 let row = pop_insert(test);
217 assert_eq!(row[&presence], Value::from(true));
218 assert_eq!(row[&author], Value::from("alice"));
219 assert_eq!(row[¬e], Value::from("hi"));
220
221 toasty::create!(Document {
222 id: "b",
223 metadata: None,
224 })
225 .exec(&mut db)
226 .await?;
227 let row = pop_insert(test);
228 assert_eq!(row[&presence], Value::Null);
229 assert_eq!(row[&author], Value::Null);
230 assert_eq!(row[¬e], Value::Null);
231
232 Ok(())
233}
234
235#[driver_test(requires(scan), scenario(crate::scenarios::document_optional_metadata))]
239pub async fn option_embed_filter_ops(test: &mut Test) -> Result<()> {
240 let mut db = setup(test).await;
241 let presence = column(&db, "documents", "metadata").index;
242 test.log().clear();
243
244 let _ = Document::filter(Document::fields().metadata().is_none())
246 .exec(&mut db)
247 .await?;
248 assert_struct!(pop_filter(test), Expr::IsNull({
249 expr.as_expr_column_unwrap().column: == presence,
250 }));
251
252 let _ = Document::filter(Document::fields().metadata().is_some())
254 .exec(&mut db)
255 .await?;
256 let Expr::Not(not) = pop_filter(test) else {
257 panic!("expected NOT");
258 };
259 assert_struct!(*not.expr, Expr::IsNull({
260 expr.as_expr_column_unwrap().column: == presence,
261 }));
262
263 Ok(())
264}
265
266#[driver_test(scenario(crate::scenarios::document_optional_metadata))]
269pub async fn option_embed_update_ops(test: &mut Test) -> Result<()> {
270 let mut db = setup(test).await;
271 let presence = column(&db, "documents", "metadata").index;
272 let author = column(&db, "documents", "metadata_author").index;
273 let note = column(&db, "documents", "metadata_note").index;
274
275 toasty::create!(Document {
276 id: "a",
277 metadata: None,
278 })
279 .exec(&mut db)
280 .await?;
281
282 test.log().clear();
284 Document::filter_by_id("a")
285 .update()
286 .metadata(Some(Metadata {
287 author: "alice".to_string(),
288 note: "hi".to_string(),
289 }))
290 .exec(&mut db)
291 .await?;
292 let set = pop_update(test);
293 assert_eq!(set[&presence], Value::from(true));
294 assert_eq!(set[&author], Value::from("alice"));
295 assert_eq!(set[¬e], Value::from("hi"));
296
297 test.log().clear();
299 Document::filter_by_id("a")
300 .update()
301 .metadata(None)
302 .exec(&mut db)
303 .await?;
304 let set = pop_update(test);
305 assert_eq!(set[&presence], Value::Null);
306 assert_eq!(set[&author], Value::Null);
307 assert_eq!(set[¬e], Value::Null);
308
309 Ok(())
310}
311
312#[driver_test(scenario(crate::scenarios::document_optional_code))]
324pub async fn option_newtype_db_schema(test: &mut Test) {
325 let db = setup(test).await;
326 let schema = db.schema();
327
328 assert_struct!(schema.db.tables, [
329 {
330 name: =~ r"documents$",
331 columns: [
332 { name: "id" },
333 { name: "code", nullable: true },
334 ],
335 },
336 ]);
337}
338
339#[driver_test(scenario(crate::scenarios::document_optional_code))]
341pub async fn option_newtype_crud(test: &mut Test) -> Result<()> {
342 let mut db = setup(test).await;
343
344 toasty::create!(Document {
345 id: "with",
346 code: Some(Code("abc".to_string())),
347 })
348 .exec(&mut db)
349 .await?;
350 toasty::create!(Document {
351 id: "without",
352 code: None,
353 })
354 .exec(&mut db)
355 .await?;
356
357 let with = Document::get_by_id(&mut db, "with").await?;
358 assert_struct!(with.code, Some(== Code("abc".to_string())));
359
360 let without = Document::get_by_id(&mut db, "without").await?;
361 assert_none!(without.code);
362
363 Ok(())
364}
365
366#[driver_test(requires(scan), scenario(crate::scenarios::document_optional_code))]
369pub async fn option_newtype_filter_presence(test: &mut Test) -> Result<()> {
370 let mut db = setup(test).await;
371
372 toasty::create!(Document {
373 id: "with",
374 code: Some(Code("abc".to_string())),
375 })
376 .exec(&mut db)
377 .await?;
378 toasty::create!(Document {
379 id: "without",
380 code: None,
381 })
382 .exec(&mut db)
383 .await?;
384
385 let present = Document::filter(Document::fields().code().is_some())
386 .exec(&mut db)
387 .await?;
388 assert_struct!(present, [_ { id: "with", .. }]);
389
390 let absent = Document::filter(Document::fields().code().is_none())
391 .exec(&mut db)
392 .await?;
393 assert_struct!(absent, [_ { id: "without", .. }]);
394
395 Ok(())
396}
397
398#[driver_test(scenario(crate::scenarios::document_optional_code))]
401pub async fn option_newtype_create_ops(test: &mut Test) -> Result<()> {
402 let mut db = setup(test).await;
403 let code = column(&db, "documents", "code").index;
404 test.log().clear();
405
406 toasty::create!(Document {
407 id: "a",
408 code: Some(Code("xyz".to_string())),
409 })
410 .exec(&mut db)
411 .await?;
412 let row = pop_insert(test);
413 assert_eq!(row[&code], Value::from("xyz"));
414
415 toasty::create!(Document {
416 id: "b",
417 code: None,
418 })
419 .exec(&mut db)
420 .await?;
421 let row = pop_insert(test);
422 assert_eq!(row[&code], Value::Null);
423
424 Ok(())
425}
426
427#[driver_test(requires(scan), scenario(crate::scenarios::document_optional_code))]
430pub async fn option_newtype_filter_ops(test: &mut Test) -> Result<()> {
431 let mut db = setup(test).await;
432 let code = column(&db, "documents", "code").index;
433 test.log().clear();
434
435 let _ = Document::filter(Document::fields().code().is_none())
436 .exec(&mut db)
437 .await?;
438 assert_struct!(pop_filter(test), Expr::IsNull({
439 expr.as_expr_column_unwrap().column: == code,
440 }));
441
442 Ok(())
443}
444
445#[driver_test(scenario(crate::scenarios::document_optional_code))]
448pub async fn option_newtype_update(test: &mut Test) -> Result<()> {
449 let mut db = setup(test).await;
450
451 toasty::create!(Document {
452 id: "a",
453 code: Some(Code("abc".to_string())),
454 })
455 .exec(&mut db)
456 .await?;
457
458 Document::filter_by_id("a")
460 .update()
461 .code(None)
462 .exec(&mut db)
463 .await?;
464 assert_none!(Document::get_by_id(&mut db, "a").await?.code);
465
466 Document::filter_by_id("a")
468 .update()
469 .code(Some(Code("xyz".to_string())))
470 .exec(&mut db)
471 .await?;
472 assert_struct!(
473 Document::get_by_id(&mut db, "a").await?.code,
474 Some(== Code("xyz".to_string()))
475 );
476
477 Ok(())
478}
479
480#[driver_test(requires(scan), scenario(crate::scenarios::document_optional_code))]
483pub async fn option_newtype_filter_eq(test: &mut Test) -> Result<()> {
484 let mut db = setup(test).await;
485
486 toasty::create!(Document {
487 id: "alice",
488 code: Some(Code("abc".to_string())),
489 })
490 .exec(&mut db)
491 .await?;
492 toasty::create!(Document {
493 id: "bob",
494 code: Some(Code("xyz".to_string())),
495 })
496 .exec(&mut db)
497 .await?;
498 toasty::create!(Document {
499 id: "without",
500 code: None,
501 })
502 .exec(&mut db)
503 .await?;
504
505 let matches = Document::filter(Document::fields().code().eq(Some(Code("abc".to_string()))))
506 .exec(&mut db)
507 .await?;
508 assert_struct!(matches, [_ { id: "alice", .. }]);
509
510 Ok(())
511}
512
513#[driver_test]
519pub async fn option_newtype_wrapping_option_rejected(test: &mut Test) {
520 #[derive(Debug, toasty::Model)]
521 #[allow(dead_code)]
522 struct Document {
523 #[key]
524 id: String,
525
526 body: Option<MaybeBody>,
527 }
528
529 #[derive(Debug, toasty::Embed)]
530 struct MaybeBody(Option<String>);
531
532 let err = assert_err!(test.try_setup_db(models!(Document)).await);
533 let msg = err.to_string();
534 assert!(
535 msg.contains("`Document::body`") && msg.contains("newtype wrapping an optional value"),
536 "unexpected error: {msg}"
537 );
538}
539
540#[driver_test]
545pub async fn option_struct_nullable_inner_disambiguates(test: &mut Test) -> Result<()> {
546 #[derive(Debug, toasty::Model)]
547 #[allow(dead_code)]
548 struct Document {
549 #[key]
550 id: String,
551
552 wrapper: Option<Wrapper>,
553 }
554
555 #[derive(Debug, toasty::Embed)]
556 struct Wrapper {
557 value: Option<String>,
558 }
559
560 let mut db = test.setup_db(models!(Document)).await;
561
562 assert_struct!(db.schema().db.tables, [
565 {
566 name: =~ r"documents$",
567 columns: [
568 { name: "id" },
569 { name: "wrapper", nullable: true },
570 { name: "wrapper_value", nullable: true },
571 ],
572 },
573 ]);
574
575 toasty::create!(Document {
576 id: "some_none",
577 wrapper: Some(Wrapper { value: None }),
578 })
579 .exec(&mut db)
580 .await?;
581 toasty::create!(Document {
582 id: "none",
583 wrapper: None,
584 })
585 .exec(&mut db)
586 .await?;
587
588 assert_struct!(
589 Document::get_by_id(&mut db, "some_none").await?.wrapper,
590 Some(_ { value: None })
591 );
592 assert_none!(Document::get_by_id(&mut db, "none").await?.wrapper);
593
594 Ok(())
595}