Skip to main content

toasty_driver_integration_suite/tests/
crud_upsert.rs

1use crate::prelude::*;
2
3#[driver_test(requires(upsert_primary_key), scenario(crate::scenarios::two_models))]
4pub async fn upsert_by_primary_key_creates_then_updates(test: &mut Test) -> Result<()> {
5    let mut db = setup(test).await;
6    let seed = toasty::create!(User { name: "seed" }).exec(&mut db).await?;
7    let id = seed.id;
8    seed.delete().exec(&mut db).await?;
9
10    let created = User::upsert_by_id(id).name("created").exec(&mut db).await?;
11    assert_eq!(created.id, id);
12    assert_eq!(created.name, "created");
13
14    let updated = User::upsert_by_id(id).name("updated").exec(&mut db).await?;
15    assert_eq!(updated.id, id);
16    assert_eq!(updated.name, "updated");
17    assert_eq!(User::get_by_id(&mut db, id).await?.name, "updated");
18    Ok(())
19}
20
21/// Upsert updates advance the OCC version, so a stale instance cannot overwrite
22/// the updated row.
23#[driver_test(
24    requires(upsert_primary_key),
25    scenario(crate::scenarios::versioned_item)
26)]
27pub async fn upsert_update_increments_version(test: &mut Test) -> Result<()> {
28    let mut db = setup(test).await;
29    let seed = toasty::create!(Item { name: "seed" }).exec(&mut db).await?;
30    let id = seed.id;
31    seed.delete().exec(&mut db).await?;
32
33    let mut stale = Item::upsert_by_id(id).name("first").exec(&mut db).await?;
34    assert_struct!(stale, { name: "first", version: 1 });
35
36    let updated = Item::upsert_by_id(id).name("second").exec(&mut db).await?;
37    assert_struct!(updated, { name: "second", version: 2 });
38
39    let result: Result<()> = stale.update().name("stale").exec(&mut db).await;
40    assert!(result.is_err(), "expected stale update to fail");
41    Ok(())
42}
43
44#[driver_test(
45    requires(upsert_branch_assignments),
46    scenario(crate::scenarios::two_models)
47)]
48pub async fn upsert_branch_overrides(test: &mut Test) -> Result<()> {
49    let mut db = setup(test).await;
50    let seed = toasty::create!(User { name: "seed" }).exec(&mut db).await?;
51    let id = seed.id;
52    seed.delete().exec(&mut db).await?;
53
54    let created = User::upsert_by_id(id)
55        .name("shared")
56        .on_create(|create| create.name("created"))
57        .on_update(|update| update.name("updated"))
58        .exec(&mut db)
59        .await?;
60    assert_eq!(created.name, "created");
61
62    let updated = User::upsert_by_id(id)
63        .name("shared again")
64        .on_create(|create| create.name("created again"))
65        .on_update(|update| update.name("updated"))
66        .exec(&mut db)
67        .await?;
68    assert_eq!(updated.name, "updated");
69    Ok(())
70}
71
72#[driver_test(
73    requires(upsert_branch_assignments),
74    scenario(crate::scenarios::two_models)
75)]
76pub async fn upsert_branch_overrides_are_order_independent(test: &mut Test) -> Result<()> {
77    let mut db = setup(test).await;
78    let seed = toasty::create!(User { name: "seed" }).exec(&mut db).await?;
79    let id = seed.id;
80    seed.delete().exec(&mut db).await?;
81
82    let created = User::upsert_by_id(id)
83        .on_create(|create| create.name("created"))
84        .on_update(|update| update.name("updated"))
85        .name("shared")
86        .exec(&mut db)
87        .await?;
88    assert_eq!(created.name, "created");
89
90    let updated = User::upsert_by_id(id)
91        .on_create(|create| create.name("created again"))
92        .on_update(|update| update.name("updated"))
93        .name("shared again")
94        .exec(&mut db)
95        .await?;
96    assert_eq!(updated.name, "updated");
97    Ok(())
98}
99
100#[driver_test(
101    requires(upsert_branch_assignments),
102    scenario(crate::scenarios::two_models)
103)]
104pub async fn upsert_single_branch_override_keeps_shared_other_branch(
105    test: &mut Test,
106) -> Result<()> {
107    let mut db = setup(test).await;
108    let create_seed = toasty::create!(User { name: "seed" }).exec(&mut db).await?;
109    let create_id = create_seed.id;
110    create_seed.delete().exec(&mut db).await?;
111
112    let created = User::upsert_by_id(create_id)
113        .on_create(|create| create.name("created"))
114        .name("shared")
115        .exec(&mut db)
116        .await?;
117    assert_eq!(created.name, "created");
118
119    let updated = User::upsert_by_id(create_id)
120        .on_create(|create| create.name("created again"))
121        .name("shared")
122        .exec(&mut db)
123        .await?;
124    assert_eq!(updated.name, "shared");
125
126    let update_seed = toasty::create!(User { name: "seed" }).exec(&mut db).await?;
127    let update_id = update_seed.id;
128    update_seed.delete().exec(&mut db).await?;
129
130    let created = User::upsert_by_id(update_id)
131        .on_update(|update| update.name("updated"))
132        .name("shared")
133        .exec(&mut db)
134        .await?;
135    assert_eq!(created.name, "shared");
136
137    let updated = User::upsert_by_id(update_id)
138        .on_update(|update| update.name("updated"))
139        .name("shared again")
140        .exec(&mut db)
141        .await?;
142    assert_eq!(updated.name, "updated");
143    Ok(())
144}
145
146#[driver_test(
147    requires(upsert_targeted_ignore),
148    scenario(crate::scenarios::two_models)
149)]
150pub async fn upsert_or_ignore_returns_option(test: &mut Test) -> Result<()> {
151    let mut db = setup(test).await;
152    let seed = toasty::create!(User { name: "seed" }).exec(&mut db).await?;
153    let id = seed.id;
154    seed.delete().exec(&mut db).await?;
155
156    let created = User::upsert_by_id(id)
157        .name("first")
158        .or_ignore()
159        .exec(&mut db)
160        .await?;
161    assert_eq!(created.unwrap().name, "first");
162
163    let ignored = User::upsert_by_id(id)
164        .name("second")
165        .or_ignore()
166        .exec(&mut db)
167        .await?;
168    assert!(ignored.is_none());
169    assert_eq!(User::get_by_id(&mut db, id).await?.name, "first");
170    Ok(())
171}
172
173#[driver_test(
174    requires(and(upsert_primary_key, upsert_targeted_ignore)),
175    scenario(crate::scenarios::user_unique_email_with_name)
176)]
177pub async fn upsert_or_ignore_suppresses_only_the_selected_conflict(test: &mut Test) -> Result<()> {
178    let mut db = setup(test).await;
179    let first_seed = toasty::create!(User {
180        email: "first-seed@example.com",
181        name: "seed",
182    })
183    .exec(&mut db)
184    .await?;
185    let first_id = first_seed.id;
186    first_seed.delete().exec(&mut db).await?;
187
188    let second_seed = toasty::create!(User {
189        email: "second-seed@example.com",
190        name: "seed",
191    })
192    .exec(&mut db)
193    .await?;
194    let second_id = second_seed.id;
195    second_seed.delete().exec(&mut db).await?;
196
197    let created = User::upsert_by_id(first_id)
198        .email("alice@example.com")
199        .name("Alice")
200        .or_ignore()
201        .exec(&mut db)
202        .await?;
203    assert_eq!(created.unwrap().email, "alice@example.com");
204
205    let ignored = User::upsert_by_id(first_id)
206        .email("other@example.com")
207        .name("Other")
208        .or_ignore()
209        .exec(&mut db)
210        .await?;
211    assert!(ignored.is_none());
212    assert_eq!(
213        User::get_by_id(&mut db, first_id).await?.email,
214        "alice@example.com"
215    );
216
217    let other_conflict = User::upsert_by_id(second_id)
218        .email("alice@example.com")
219        .name("Duplicate")
220        .or_ignore()
221        .exec(&mut db)
222        .await;
223    assert!(other_conflict.is_err());
224    Ok(())
225}
226
227#[driver_test(
228    requires(upsert_unique),
229    scenario(crate::scenarios::user_unique_email_with_name)
230)]
231pub async fn upsert_by_unique_field(test: &mut Test) -> Result<()> {
232    let mut db = setup(test).await;
233    let created = User::upsert_by_email("alice@example.com")
234        .name("Alice")
235        .exec(&mut db)
236        .await?;
237    let updated = User::upsert_by_email("alice@example.com")
238        .name("Alicia")
239        .exec(&mut db)
240        .await?;
241
242    assert_eq!(updated.id, created.id);
243    assert_eq!(updated.email, "alice@example.com");
244    assert_eq!(updated.name, "Alicia");
245    Ok(())
246}
247
248#[driver_test(requires(upsert_unique), scenario(crate::scenarios::upsert_models))]
249pub async fn upsert_by_composite_unique_constraint(test: &mut Test) -> Result<()> {
250    let mut db = setup_entry(test).await;
251    let created = Entry::upsert_by_tenant_and_slug("acme", "home")
252        .value("one")
253        .exec(&mut db)
254        .await?;
255    let updated = Entry::upsert_by_tenant_and_slug("acme", "home")
256        .value("two")
257        .exec(&mut db)
258        .await?;
259
260    assert_eq!(updated.id, created.id);
261    assert_eq!(updated.value, "two");
262    Ok(())
263}
264
265#[driver_test(
266    requires(upsert_primary_key),
267    scenario(crate::scenarios::upsert_models)
268)]
269pub async fn upsert_applies_model_defaults(test: &mut Test) -> Result<()> {
270    let mut db = setup_defaulted_item(test).await;
271    let seed = toasty::create!(DefaultedItem { value: "seed" })
272        .exec(&mut db)
273        .await?;
274    let id = seed.id;
275    seed.delete().exec(&mut db).await?;
276
277    let created = DefaultedItem::upsert_by_id(id)
278        .value("one")
279        .exec(&mut db)
280        .await?;
281    assert_eq!(created.created_only, "created");
282    assert_eq!(created.updated_always, "always");
283
284    let updated = DefaultedItem::upsert_by_id(id)
285        .value("two")
286        .exec(&mut db)
287        .await?;
288    assert_eq!(updated.created_only, "created");
289    assert_eq!(updated.updated_always, "always");
290    Ok(())
291}
292
293#[driver_test(requires(upsert_primary_key))]
294pub async fn upsert_explicit_assignments_override_model_defaults(test: &mut Test) -> Result<()> {
295    #[derive(Debug, toasty::Model)]
296    struct Item {
297        #[key]
298        #[auto]
299        id: uuid::Uuid,
300
301        #[default("create default".to_string())]
302        default_only: String,
303
304        #[update("update default".to_string())]
305        update_only: String,
306
307        #[default("create branch default".to_string())]
308        #[update("update branch default".to_string())]
309        branch_defaults: String,
310    }
311
312    let mut db = test.setup_db(models!(Item)).await;
313    let seed = toasty::create!(Item {}).exec(&mut db).await?;
314    let id = seed.id;
315    seed.delete().exec(&mut db).await?;
316
317    let created = Item::upsert_by_id(id)
318        .default_only("created explicitly")
319        .update_only("created explicitly")
320        .branch_defaults("created explicitly")
321        .exec(&mut db)
322        .await?;
323    assert_struct!(created, {
324        default_only: "created explicitly",
325        update_only: "created explicitly",
326        branch_defaults: "created explicitly",
327    });
328
329    let updated = Item::upsert_by_id(id)
330        .default_only("updated explicitly")
331        .update_only("updated explicitly")
332        .branch_defaults("updated explicitly")
333        .exec(&mut db)
334        .await?;
335    assert_struct!(updated, {
336        default_only: "updated explicitly",
337        update_only: "updated explicitly",
338        branch_defaults: "updated explicitly",
339    });
340
341    assert_none!(Item::upsert_by_id(id).or_ignore().exec(&mut db).await?);
342    Ok(())
343}
344
345#[driver_test(
346    requires(upsert_primary_key),
347    scenario(crate::scenarios::upsert_models)
348)]
349pub async fn upsert_create_assignment_initializes_only_new_record(test: &mut Test) -> Result<()> {
350    let mut db = setup_defaulted_item(test).await;
351    let seed = toasty::create!(DefaultedItem { value: "seed" })
352        .exec(&mut db)
353        .await?;
354    let id = seed.id;
355    seed.delete().exec(&mut db).await?;
356
357    let created = DefaultedItem::upsert_by_id(id)
358        .value("one")
359        .on_create(|create| create.created_only("initial"))
360        .exec(&mut db)
361        .await?;
362    assert_eq!(created.created_only, "initial");
363
364    let updated = DefaultedItem::upsert_by_id(id)
365        .value("two")
366        .on_create(|create| create.created_only("replacement"))
367        .exec(&mut db)
368        .await?;
369    assert_eq!(updated.created_only, "initial");
370    Ok(())
371}
372
373#[driver_test(
374    requires(upsert_branch_assignments),
375    scenario(crate::scenarios::two_models)
376)]
377pub async fn upsert_update_can_reference_incoming_value(test: &mut Test) -> Result<()> {
378    let mut db = setup(test).await;
379    let seed = toasty::create!(User { name: "seed" }).exec(&mut db).await?;
380    let id = seed.id;
381
382    let updated = User::upsert_by_id(id)
383        .name("shared")
384        .on_create(|create| create.name("created"))
385        .on_update(|update| {
386            let incoming = update.incoming();
387            update.name(incoming.name())
388        })
389        .exec(&mut db)
390        .await?;
391    assert_eq!(updated.name, "created");
392    Ok(())
393}
394
395#[driver_test(
396    requires(upsert_branch_assignments),
397    scenario(crate::scenarios::two_models)
398)]
399pub async fn upsert_update_can_reference_stored_value(test: &mut Test) -> Result<()> {
400    let mut db = setup(test).await;
401    let seed = toasty::create!(User { name: "stored" })
402        .exec(&mut db)
403        .await?;
404
405    let updated = User::upsert_by_id(seed.id)
406        .name("incoming")
407        .on_update(|update| update.name(toasty::stmt::set(User::fields().name())))
408        .exec(&mut db)
409        .await?;
410    assert_eq!(updated.name, "stored");
411    Ok(())
412}
413
414#[driver_test(
415    requires(upsert_primary_key),
416    scenario(crate::scenarios::upsert_models)
417)]
418pub async fn upsert_shared_assignment_operators(test: &mut Test) -> Result<()> {
419    let mut db = setup_assigned_item(test).await;
420    let seed = toasty::create!(AssignedItem {
421        count: 0,
422        tags: Vec::<String>::new(),
423    })
424    .exec(&mut db)
425    .await?;
426    let id = seed.id;
427    seed.delete().exec(&mut db).await?;
428
429    let created = AssignedItem::upsert_by_id(id)
430        .count(toasty::stmt::increment())
431        .tags(toasty::stmt::push("a"))
432        .note(Some("present".to_string()))
433        .exec(&mut db)
434        .await?;
435    assert_eq!(created.count, 1);
436    assert_eq!(created.tags, ["a"]);
437    assert_eq!(created.note.as_deref(), Some("present"));
438
439    let incremented = AssignedItem::upsert_by_id(id)
440        .count(toasty::stmt::increment())
441        .tags(toasty::stmt::push("b"))
442        .note(None::<String>)
443        .exec(&mut db)
444        .await?;
445    assert_eq!(incremented.count, 2);
446    assert_eq!(incremented.tags, ["a", "b"]);
447    assert_eq!(incremented.note, None);
448
449    let decremented = AssignedItem::upsert_by_id(id)
450        .count(toasty::stmt::subtract(1_i64))
451        .tags(toasty::stmt::extend(Vec::<String>::new()))
452        .exec(&mut db)
453        .await?;
454    assert_eq!(decremented.count, 1);
455    assert_eq!(decremented.tags, ["a", "b"]);
456    Ok(())
457}
458
459#[driver_test(requires(upsert_primary_key))]
460pub async fn upsert_shared_mutations_apply_declared_defaults(test: &mut Test) -> Result<()> {
461    #[derive(Debug, toasty::Model)]
462    struct Item {
463        #[key]
464        id: String,
465
466        #[default(10)]
467        count: i64,
468
469        #[default(vec!["base".to_string()])]
470        tags: Vec<String>,
471    }
472
473    let mut db = test.setup_db(models!(Item)).await;
474
475    let created = Item::upsert_by_id("item")
476        .count(toasty::stmt::subtract(3))
477        .tags(toasty::stmt::push("a"))
478        .exec(&mut db)
479        .await?;
480    assert_struct!(created, { count: 7, tags: ["base", "a"] });
481
482    let updated = Item::upsert_by_id("item")
483        .count(toasty::stmt::subtract(3))
484        .tags(toasty::stmt::push("b"))
485        .exec(&mut db)
486        .await?;
487    assert_struct!(updated, { count: 4, tags: ["base", "a", "b"] });
488    Ok(())
489}
490
491#[driver_test(requires(upsert_primary_key))]
492pub async fn upsert_shared_mutation_requires_declared_default(test: &mut Test) -> Result<()> {
493    #[derive(Debug, toasty::Model)]
494    struct Item {
495        #[key]
496        id: String,
497
498        count: i64,
499    }
500
501    let mut db = test.setup_db(models!(Item)).await;
502    let error = Item::upsert_by_id("item")
503        .count(toasty::stmt::increment())
504        .exec(&mut db)
505        .await
506        .unwrap_err();
507    assert!(error.is_invalid_statement(), "unexpected error: {error}");
508    assert!(error.to_string().contains("#[default]"));
509    Ok(())
510}
511
512#[driver_test(requires(and(upsert_primary_key, vec_scalar)))]
513pub async fn upsert_shared_replacement_does_not_require_default(test: &mut Test) -> Result<()> {
514    #[derive(Debug, toasty::Model)]
515    struct Item {
516        #[key]
517        id: String,
518
519        tags: Vec<String>,
520    }
521
522    let mut db = test.setup_db(models!(Item)).await;
523    let created = Item::upsert_by_id("item")
524        .tags(toasty::stmt::clear())
525        .exec(&mut db)
526        .await?;
527    assert!(created.tags.is_empty());
528    Ok(())
529}
530
531#[driver_test(requires(upsert_branch_assignments))]
532pub async fn upsert_update_mutation_does_not_require_default(test: &mut Test) -> Result<()> {
533    #[derive(Debug, toasty::Model)]
534    struct Item {
535        #[key]
536        id: String,
537
538        count: i64,
539    }
540
541    let mut db = test.setup_db(models!(Item)).await;
542
543    let created = Item::upsert_by_id("item")
544        .on_create(|item| item.count(10))
545        .on_update(|item| item.count(toasty::stmt::subtract(3)))
546        .exec(&mut db)
547        .await?;
548    assert_eq!(created.count, 10);
549
550    let updated = Item::upsert_by_id("item")
551        .on_create(|item| item.count(10))
552        .on_update(|item| item.count(toasty::stmt::subtract(3)))
553        .exec(&mut db)
554        .await?;
555    assert_eq!(updated.count, 7);
556    Ok(())
557}
558
559#[driver_test(requires(and(upsert_primary_key, vec_pop)))]
560pub async fn upsert_pop_applies_declared_default_on_create(test: &mut Test) -> Result<()> {
561    #[derive(Debug, toasty::Model)]
562    struct Item {
563        #[key]
564        id: String,
565
566        #[default(vec!["a".to_string(), "b".to_string()])]
567        tags: Vec<String>,
568    }
569
570    let mut db = test.setup_db(models!(Item)).await;
571
572    let created = Item::upsert_by_id("item")
573        .tags(toasty::stmt::pop())
574        .exec(&mut db)
575        .await?;
576    assert_eq!(created.tags, ["a"]);
577
578    let updated = Item::upsert_by_id("item")
579        .tags(toasty::stmt::pop())
580        .exec(&mut db)
581        .await?;
582    assert!(updated.tags.is_empty());
583    Ok(())
584}
585
586#[driver_test(
587    requires(upsert_primary_key),
588    scenario(crate::scenarios::has_many_nullable_fk)
589)]
590pub async fn upsert_without_update_assignments_is_invalid(test: &mut Test) -> Result<()> {
591    let mut db = setup(test).await;
592    let seed = toasty::create!(User {}).exec(&mut db).await?;
593    let error = User::upsert_by_id(seed.id).exec(&mut db).await.unwrap_err();
594    assert!(error.is_invalid_statement(), "unexpected error: {error}");
595    Ok(())
596}
597
598#[driver_test(
599    requires(and(upsert_primary_key, not(upsert_branch_assignments))),
600    scenario(crate::scenarios::two_models)
601)]
602pub async fn unsupported_upsert_branches_are_reported_before_dispatch(
603    test: &mut Test,
604) -> Result<()> {
605    let mut db = setup(test).await;
606    let seed = toasty::create!(User { name: "seed" }).exec(&mut db).await?;
607    let error = User::upsert_by_id(seed.id)
608        .name("shared")
609        .on_update(|update| update.name("updated"))
610        .exec(&mut db)
611        .await
612        .unwrap_err();
613    assert!(error.is_unsupported_feature(), "unexpected error: {error}");
614    assert!(
615        error.to_string().contains(test.capability().driver_name),
616        "error does not identify the driver: {error}"
617    );
618    Ok(())
619}
620
621#[driver_test(
622    requires(and(upsert_primary_key, not(upsert_unique))),
623    scenario(crate::scenarios::user_unique_email_with_name)
624)]
625pub async fn unsupported_unique_upsert_is_reported_before_dispatch(test: &mut Test) -> Result<()> {
626    let mut db = setup(test).await;
627    let error = User::upsert_by_email("alice@example.com")
628        .name("Alice")
629        .exec(&mut db)
630        .await
631        .unwrap_err();
632    assert!(error.is_unsupported_feature(), "unexpected error: {error}");
633    assert!(
634        error.to_string().contains(test.capability().driver_name),
635        "error does not identify the driver: {error}"
636    );
637    Ok(())
638}
639
640#[driver_test(
641    requires(not(upsert_primary_key)),
642    scenario(crate::scenarios::two_models)
643)]
644pub async fn unsupported_upsert_is_reported_before_dispatch(test: &mut Test) -> Result<()> {
645    let mut db = setup(test).await;
646    let seed = toasty::create!(User { name: "seed" }).exec(&mut db).await?;
647    let error = User::upsert_by_id(seed.id)
648        .name("updated")
649        .exec(&mut db)
650        .await
651        .unwrap_err();
652    assert!(error.is_unsupported_feature(), "unexpected error: {error}");
653    assert!(
654        error.to_string().contains(test.capability().driver_name),
655        "error does not identify the driver: {error}"
656    );
657    Ok(())
658}