Open6
sqlx で列を更新しない場合とNULLに更新する場合を使い分ける
更新対象となるデータが次のような形式だとすると、None
の場合に列を更新したくないのか、NULLに更新したいのか区別がつかない
pub struct UpdateUser {
pub id: Uuid,
pub name: Option<String>,
pub profile: Option<String>,
}
Option の代わりにシリアライズした結果、SQL側で NULL でも更新したいか、更新したくないかを判断できる情報を渡せるような enum を自前で作る
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", content = "value")]
#[serde(rename_all = "lowercase")]
pub enum Updator<T> {
NoUpdates,
Update(Option<T>),
}
serde は enum をシリアライズする時に色々な形式で変換できて、上記のようにすると Updator::NoUpdates
は
{
"type": "noupdates"
}
となり、Updator::Update(None)
は
{
"type": "updates",
"value": null
}
のように、SQL に渡した時に使いやすい形になる
あとは UPDATE 文で CASE による分岐を書くだけ
let user = sqlx::query_as::<_, User>(
r#"
UPDATE users
SET
name = CASE $2 ->> 'type'
WHEN 'noupdates' THEN name
WHEN 'update' THEN $2 ->> 'value'
END
, profile = CASE $3 ->> 'type'
WHEN 'noupdates' THEN profile
WHEN 'update' THEN $3 ->> 'value'
END
WHERE
id = $1
RETURNING
id
, name
, profile
"#,
)
.bind(id)
.bind(Json::from(Updator::Update("John".into())))
.bind(Json::from(Updator::NoUpdates))
.fetch_optional(pool)
.await
上記は PostgreSQL の想定。MySQL の場合などデータベースエンジンに応じてクエリは変わる。知らんけど。
マクロで実行するには、もっと型を合わせないと難しい。
- SQL でパラメータに json の型ヒントを書く
-
Json<T>
ではなくJsonValue
でパラメータを渡す
UPDATE users
SET
name = CASE $2::json ->> 'type'
WHEN 'noupdates' THEN name
WHEN 'update' THEN $2::json ->> 'value'
END
, profile = CASE $3::json ->> 'type'
WHEN 'noupdates' THEN profile
WHEN 'update' THEN $3::json ->> 'value'
END
WHERE
id = $1
RETURNING
id
, name
, profile;
let user = sqlx::query_file_as!(
User,
"queries/update_user.sql",
id,
json!(Updator::Update("John".into())),
json!(Updator::NoUpdates)
)
.fetch_optional(pool)
.await
.change_context(AppError)?;