243 lines
6.6 KiB
Markdown
243 lines
6.6 KiB
Markdown
# sqlx-record UpdateExpr Skill
|
|
|
|
Guide to advanced update operations with eval_* methods.
|
|
|
|
## Triggers
|
|
- "update expression", "update expr"
|
|
- "increment field", "decrement field"
|
|
- "case when update", "conditional update"
|
|
- "arithmetic update", "column arithmetic"
|
|
|
|
## Overview
|
|
|
|
`UpdateExpr` enables complex update operations beyond simple value assignment:
|
|
- Column arithmetic (`count = count + 1`)
|
|
- CASE/WHEN conditional updates
|
|
- Conditional increments/decrements
|
|
- Raw SQL escape hatch
|
|
|
|
## UpdateExpr Enum
|
|
|
|
```rust
|
|
pub enum UpdateExpr {
|
|
Set(Value), // column = value
|
|
Add(Value), // column = column + value
|
|
Sub(Value), // column = column - value
|
|
Mul(Value), // column = column * value
|
|
Div(Value), // column = column / value
|
|
Mod(Value), // column = column % value
|
|
Case {
|
|
branches: Vec<(Filter<'static>, Value)>,
|
|
default: Value,
|
|
},
|
|
AddIf { condition: Filter<'static>, value: Value },
|
|
SubIf { condition: Filter<'static>, value: Value },
|
|
Coalesce(Value), // COALESCE(column, value)
|
|
Greatest(Value), // GREATEST(column, value)
|
|
Least(Value), // LEAST(column, value)
|
|
Raw { sql: String, values: Vec<Value> },
|
|
}
|
|
```
|
|
|
|
## Generated Methods
|
|
|
|
For each non-binary field, an `eval_{field}` method is generated:
|
|
|
|
```rust
|
|
// Generated for: count: i32
|
|
pub fn eval_count(mut self, expr: UpdateExpr) -> Self
|
|
|
|
// Generated for: score: i64
|
|
pub fn eval_score(mut self, expr: UpdateExpr) -> Self
|
|
|
|
// Generated for: status: String
|
|
pub fn eval_status(mut self, expr: UpdateExpr) -> Self
|
|
```
|
|
|
|
Binary fields (`Vec<u8>`) do not get `eval_*` methods.
|
|
|
|
## Precedence
|
|
|
|
`eval_*` methods take precedence over `with_*` if both are set for the same field:
|
|
|
|
```rust
|
|
let form = User::update_form()
|
|
.with_count(100) // This is ignored
|
|
.eval_count(UpdateExpr::Add(1.into())); // This is used
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
### Simple Arithmetic
|
|
```rust
|
|
// Increment
|
|
let form = User::update_form()
|
|
.eval_count(UpdateExpr::Add(1.into())); // count = count + 1
|
|
|
|
// Decrement
|
|
let form = User::update_form()
|
|
.eval_balance(UpdateExpr::Sub(50.into())); // balance = balance - 50
|
|
|
|
// Multiply
|
|
let form = User::update_form()
|
|
.eval_score(UpdateExpr::Mul(2.into())); // score = score * 2
|
|
```
|
|
|
|
### CASE/WHEN Conditional
|
|
```rust
|
|
// Update status based on score
|
|
let form = User::update_form()
|
|
.eval_tier(UpdateExpr::Case {
|
|
branches: vec![
|
|
("score".gt(1000), "platinum".into()),
|
|
("score".gt(500), "gold".into()),
|
|
("score".gt(100), "silver".into()),
|
|
],
|
|
default: "bronze".into(),
|
|
});
|
|
// SQL: tier = CASE
|
|
// WHEN score > ? THEN ?
|
|
// WHEN score > ? THEN ?
|
|
// WHEN score > ? THEN ?
|
|
// ELSE ? END
|
|
```
|
|
|
|
### Conditional Increment
|
|
```rust
|
|
// Add bonus only for premium users
|
|
let form = User::update_form()
|
|
.eval_balance(UpdateExpr::AddIf {
|
|
condition: "is_premium".eq(true),
|
|
value: 100.into(),
|
|
});
|
|
// SQL: balance = CASE WHEN is_premium = ? THEN balance + ? ELSE balance END
|
|
```
|
|
|
|
### Using Filters with Case
|
|
```rust
|
|
use sqlx_record::prelude::*;
|
|
|
|
// Complex condition with AND
|
|
let form = User::update_form()
|
|
.eval_discount(UpdateExpr::Case {
|
|
branches: vec![
|
|
(Filter::And(vec![
|
|
"orders".gt(10),
|
|
"is_vip".eq(true),
|
|
]), 20.into()),
|
|
("orders".gt(5), 10.into()),
|
|
],
|
|
default: 0.into(),
|
|
});
|
|
```
|
|
|
|
### Coalesce (NULL handling)
|
|
```rust
|
|
// Set to value if NULL
|
|
let form = User::update_form()
|
|
.eval_nickname(UpdateExpr::Coalesce("Anonymous".into()));
|
|
// SQL: nickname = COALESCE(nickname, ?)
|
|
```
|
|
|
|
### Greatest/Least
|
|
```rust
|
|
// Ensure minimum value (clamp)
|
|
let form = User::update_form()
|
|
.eval_balance(UpdateExpr::Greatest(0.into())); // balance = GREATEST(balance, 0)
|
|
|
|
// Ensure maximum value (cap)
|
|
let form = User::update_form()
|
|
.eval_score(UpdateExpr::Least(100.into())); // score = LEAST(score, 100)
|
|
```
|
|
|
|
### Raw SQL Escape Hatch
|
|
```rust
|
|
// Simple expression without parameters
|
|
let form = User::update_form()
|
|
.raw("computed", "COALESCE(a, 0) + COALESCE(b, 0)");
|
|
|
|
// Expression with bind parameters
|
|
let form = User::update_form()
|
|
.raw_with_values("adjusted", "ROUND(price * ? * (1 - discount / 100))", values![1.1]);
|
|
|
|
// Multiple placeholders
|
|
let form = User::update_form()
|
|
.raw_with_values("stats", "JSON_SET(stats, '$.views', JSON_EXTRACT(stats, '$.views') + ?)", values![1]);
|
|
```
|
|
|
|
## Combining with Simple Updates
|
|
|
|
```rust
|
|
let form = User::update_form()
|
|
.with_name("Alice") // Simple value update
|
|
.with_email("alice@example.com") // Simple value update
|
|
.eval_login_count(UpdateExpr::Add(1.into())) // Arithmetic
|
|
.eval_last_login(UpdateExpr::Set( // Expression set
|
|
Value::NaiveDateTime(Utc::now().naive_utc())
|
|
));
|
|
|
|
User::update_by_id(&pool, &user_id, form).await?;
|
|
```
|
|
|
|
## Full Update Flow
|
|
|
|
```rust
|
|
use sqlx_record::prelude::*;
|
|
|
|
#[derive(Entity, FromRow)]
|
|
#[table_name = "game_scores"]
|
|
struct GameScore {
|
|
#[primary_key]
|
|
id: Uuid,
|
|
player_id: Uuid,
|
|
score: i64,
|
|
high_score: i64,
|
|
games_played: i32,
|
|
tier: String,
|
|
}
|
|
|
|
async fn record_game(pool: &Pool, id: &Uuid, new_score: i64) -> Result<(), Error> {
|
|
let form = GameScore::update_form()
|
|
// Increment games played
|
|
.eval_games_played(UpdateExpr::Add(1.into()))
|
|
// Update high score if this score is higher
|
|
.eval_high_score(UpdateExpr::Greatest(new_score.into()))
|
|
// Set current score
|
|
.with_score(new_score)
|
|
// Update tier based on high score
|
|
.eval_tier(UpdateExpr::Case {
|
|
branches: vec![
|
|
("high_score".gt(10000), "master".into()),
|
|
("high_score".gt(5000), "expert".into()),
|
|
("high_score".gt(1000), "advanced".into()),
|
|
],
|
|
default: "beginner".into(),
|
|
});
|
|
|
|
GameScore::update_by_id(pool, id, form).await
|
|
}
|
|
```
|
|
|
|
## SQL Generation
|
|
|
|
The `update_stmt_with_values()` method generates SQL and collects bind values:
|
|
|
|
```rust
|
|
let form = User::update_form()
|
|
.with_name("Alice")
|
|
.eval_count(UpdateExpr::Add(5.into()));
|
|
|
|
let (sql, values) = form.update_stmt_with_values();
|
|
// sql: "name = ?, count = count + ?"
|
|
// values: [Value::String("Alice"), Value::Int32(5)]
|
|
```
|
|
|
|
## Database Compatibility
|
|
|
|
All UpdateExpr variants generate standard SQL that works across:
|
|
- MySQL
|
|
- PostgreSQL
|
|
- SQLite
|
|
|
|
Note: `Greatest` and `Least` use SQL functions that are available in all three databases.
|