Skip to main content

toasty_driver_integration_suite/tests/
crud_update_arithmetic.rs

1use crate::prelude::*;
2
3use toasty_core::{
4    driver::Operation,
5    stmt::{Assignment, Statement, UpdateTarget},
6};
7
8#[driver_test(scenario(crate::scenarios::counter_value))]
9pub async fn arithmetic_update_cases(t: &mut Test) -> Result<()> {
10    let mut db = setup(t).await;
11    let mut counter = toasty::create!(Counter {
12        id: uuid::Uuid::new_v4(),
13        value: 10,
14    })
15    .exec(&mut db)
16    .await?;
17
18    counter
19        .update()
20        .value(toasty::stmt::increment())
21        .exec(&mut db)
22        .await?;
23
24    let reloaded = Counter::get_by_id(&mut db, &counter.id).await?;
25    assert_eq!(reloaded.value, 11);
26
27    let mut counter = toasty::create!(Counter {
28        id: uuid::Uuid::new_v4(),
29        value: 10,
30    })
31    .exec(&mut db)
32    .await?;
33    counter
34        .update()
35        .value(toasty::stmt::decrement())
36        .exec(&mut db)
37        .await?;
38    let reloaded = Counter::get_by_id(&mut db, &counter.id).await?;
39    assert_eq!(reloaded.value, 9);
40
41    let mut counter = toasty::create!(Counter {
42        id: uuid::Uuid::new_v4(),
43        value: 10,
44    })
45    .exec(&mut db)
46    .await?;
47    counter
48        .update()
49        .value(toasty::stmt::add(25))
50        .exec(&mut db)
51        .await?;
52    let reloaded = Counter::get_by_id(&mut db, &counter.id).await?;
53    assert_eq!(reloaded.value, 35);
54
55    let mut counter = toasty::create!(Counter {
56        id: uuid::Uuid::new_v4(),
57        value: 10,
58    })
59    .exec(&mut db)
60    .await?;
61    counter
62        .update()
63        .value(toasty::stmt::subtract(3))
64        .exec(&mut db)
65        .await?;
66    let reloaded = Counter::get_by_id(&mut db, &counter.id).await?;
67    assert_eq!(reloaded.value, 7);
68
69    let mut counter = toasty::create!(Counter {
70        id: uuid::Uuid::new_v4(),
71        value: 10,
72    })
73    .exec(&mut db)
74    .await?;
75    counter
76        .update()
77        .value(toasty::stmt::add(-4))
78        .exec(&mut db)
79        .await?;
80    let reloaded = Counter::get_by_id(&mut db, &counter.id).await?;
81    assert_eq!(reloaded.value, 6);
82
83    let mut counter = toasty::create!(Counter {
84        id: uuid::Uuid::new_v4(),
85        value: 10,
86    })
87    .exec(&mut db)
88    .await?;
89    // Column index 1 is `value`. Confirm increment lowers to `Assignment::Add`;
90    // the value checks above cover each driver's return path.
91    let counter_table_id = table_id(&db, "counters");
92    let is_sql = t.capability().sql();
93    t.log().clear();
94    counter
95        .update()
96        .value(toasty::stmt::increment())
97        .exec(&mut db)
98        .await?;
99    let (op, _resp) = t.log().pop();
100    if is_sql {
101        assert_struct!(op, Operation::QuerySql({
102            stmt: Statement::Update({
103                target: UpdateTarget::Table(== counter_table_id),
104                assignments: #{ [1]: Assignment::Add(_) },
105            }),
106        }));
107    } else {
108        assert_struct!(op, Operation::UpdateByKey({
109            table: == counter_table_id,
110            assignments: #{ [1]: Assignment::Add(_) },
111        }));
112    }
113
114    // Duplicate assignments once crashed lowering; they must fold into one
115    // arithmetic update.
116    let mut counter = toasty::create!(Counter {
117        id: uuid::Uuid::new_v4(),
118        value: 10,
119    })
120    .exec(&mut db)
121    .await?;
122    counter
123        .update()
124        .value(toasty::stmt::add(2))
125        .value(toasty::stmt::add(3))
126        .exec(&mut db)
127        .await?;
128    let reloaded = Counter::get_by_id(&mut db, &counter.id).await?;
129    assert_eq!(reloaded.value, 15);
130
131    let mut counter = toasty::create!(Counter {
132        id: uuid::Uuid::new_v4(),
133        value: 10,
134    })
135    .exec(&mut db)
136    .await?;
137    // A leading subtract flips later add operands in the batch fold.
138    counter
139        .update()
140        .value(toasty::stmt::subtract(3))
141        .value(toasty::stmt::add(7))
142        .exec(&mut db)
143        .await?;
144    let reloaded = Counter::get_by_id(&mut db, &counter.id).await?;
145    assert_eq!(reloaded.value, 14);
146
147    let mut counter = toasty::create!(Counter {
148        id: uuid::Uuid::new_v4(),
149        value: 10,
150    })
151    .exec(&mut db)
152    .await?;
153    // Set clobbers prior state and absorbs subsequent arithmetic.
154    counter
155        .update()
156        .value(50)
157        .value(toasty::stmt::add(8))
158        .value(toasty::stmt::subtract(3))
159        .exec(&mut db)
160        .await?;
161    let reloaded = Counter::get_by_id(&mut db, &counter.id).await?;
162    assert_eq!(reloaded.value, 55);
163
164    let counter = toasty::create!(Counter {
165        id: uuid::Uuid::new_v4(),
166        value: 10,
167    })
168    .exec(&mut db)
169    .await?;
170    Counter::filter_by_id(counter.id)
171        .update()
172        .value(toasty::stmt::add(100))
173        .exec(&mut db)
174        .await?;
175    let reloaded = Counter::get_by_id(&mut db, &counter.id).await?;
176    assert_eq!(reloaded.value, 110);
177
178    Ok(())
179}
180
181#[driver_test]
182pub async fn arithmetic_chains_with_other_updates(t: &mut Test) -> Result<()> {
183    #[derive(Debug, toasty::Model)]
184    struct Profile {
185        #[key]
186        id: uuid::Uuid,
187
188        name: String,
189        login_count: i64,
190    }
191
192    let mut db = t.setup_db(models!(Profile)).await;
193    let mut profile = toasty::create!(Profile {
194        id: uuid::Uuid::new_v4(),
195        name: "alice",
196        login_count: 5,
197    })
198    .exec(&mut db)
199    .await?;
200
201    profile
202        .update()
203        .name("alice2")
204        .login_count(toasty::stmt::increment())
205        .exec(&mut db)
206        .await?;
207
208    let reloaded = Profile::get_by_id(&mut db, &profile.id).await?;
209    assert_struct!(reloaded, { name: "alice2", login_count: 6 });
210    Ok(())
211}
212
213#[driver_test]
214pub async fn increment_unique_column(t: &mut Test) -> Result<()> {
215    // Regression: DynamoDB's unique-index update path assumes every
216    // assignment on a unique column is `Set` (`let Set(expr) = assignment
217    // else unreachable!()`). Filtering by projection alone lets `Add` /
218    // `Subtract` reach the let-else and panic. Incrementing a unique
219    // numeric column should succeed on every backend.
220    #[derive(Debug, toasty::Model)]
221    struct Slot {
222        #[key]
223        id: uuid::Uuid,
224
225        #[unique]
226        count: i64,
227    }
228
229    let mut db = t.setup_db(models!(Slot)).await;
230    let mut slot = toasty::create!(Slot {
231        id: uuid::Uuid::new_v4(),
232        count: 10,
233    })
234    .exec(&mut db)
235    .await?;
236
237    slot.update()
238        .count(toasty::stmt::increment())
239        .exec(&mut db)
240        .await?;
241
242    let reloaded = Slot::get_by_id(&mut db, &slot.id).await?;
243    assert_eq!(reloaded.count, 11);
244    Ok(())
245}
246
247#[driver_test]
248pub async fn increment_narrow_integer_column(t: &mut Test) -> Result<()> {
249    // Regression: stmt::increment() / stmt::decrement() hardcoded
250    // Value::I64(1), which the PostgreSQL driver had no `(I64, INT2)` arm
251    // for — incrementing an `i8`, `i16`, or `u8` column panicked at value
252    // binding. The literal must encode in a value variant that fits any
253    // integer column on every backend.
254    #[derive(Debug, toasty::Model)]
255    struct Tally {
256        #[key]
257        id: uuid::Uuid,
258
259        count: i16,
260    }
261
262    let mut db = t.setup_db(models!(Tally)).await;
263    let mut tally = toasty::create!(Tally {
264        id: uuid::Uuid::new_v4(),
265        count: 10_i16,
266    })
267    .exec(&mut db)
268    .await?;
269
270    tally
271        .update()
272        .count(toasty::stmt::increment())
273        .exec(&mut db)
274        .await?;
275    assert_eq!(Tally::get_by_id(&mut db, &tally.id).await?.count, 11);
276
277    tally
278        .update()
279        .count(toasty::stmt::decrement())
280        .exec(&mut db)
281        .await?;
282    assert_eq!(Tally::get_by_id(&mut db, &tally.id).await?.count, 10);
283
284    Ok(())
285}