【Rust】SQLxのqueryとquery_asの違いと使い分け

# はじめに
RustのDB接続用ライブラリ [SQLx](https://github.com/launchbadge/sqlx) を使っているのですが、`query`と`query_as` の仕様や両者の違い、使い分けが曖昧だったので整理しました。

# TL;DR

`query`と`query_as`関数にはそれぞれマクロ版もあるので以下4パターンになる。

## function
| function | 動的/静的SQL | コンパイル時チェック | 構造体へのマッピング | 用途 |
|------|--------------|----------------------|----------------------|----------|
| `query` | 動的 | なし | 手動 | 柔軟性が必要な動的クエリ |
| `query_as` | 動的 | なし | 自動（指定構造体） | 結果を指定構造体にマッピングする動的クエリ |

動的なSQLが必要な場合は`query`や`query_as`を使う。
柔軟性は高いが、型安全性は低い。

## macro
| macro | 動的/静的SQL | コンパイル時チェック | 構造体へのマッピング | 用途 |
|------|--------------|----------------------|----------------------|----------|
| `query!` | 静的 | あり | 自動（匿名構造体） | 型安全性が必要な静的クエリ |
| `query_as!` | 静的 | あり | 自動（指定構造体） | 型安全性と指定構造体へのマッピングが必要な静的クエリ |

静的なSQLで型安全性が必要な場合は`query!`や`query_as!`を使う。柔軟性は低いが、型安全性は高い。

なお、マクロを使えばコンパイル時に以下のチェックを行ってくれる。
- SQLの構文チェック
- DBスキーマとの整合性チェック
- 型チェック

:::note info
コンパイル時チェックでは、DBにアクセスしてスキーマ定義の解析が行われるため、コンパイル時にDBへのリーチャビリティがなければエラーとなる。
ただし、[`.sqlx`ファイルを使えばオフラインでもコンパイル時チェックが可能。](https://zenn.dev/lecto/articles/8eb2472ca7233d#query_as%E3%81%A7%E6%A7%8B%E9%80%A0%E4%BD%93%E3%81%AB%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0%E3%81%99%E3%82%8B%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E5%95%8F%E9%A1%8C%E7%82%B9)
:::

具体的なコード例と使い分けは以下。

# `query`

動的なSQLクエリを実行する際に利用する。
『動的』であることから、コンパイラは構文や型のチェックができないので安全性は低い。

```rust
let dynamic_column = "email"; // WHERE句のField名がユーザー入力によって変わるとか
let user_id = 1;
let user_info: (String,) = sqlx::query(
    &format!("SELECT {} FROM users WHERE id = $1", dynamic_column)
)
.bind(user_id)
.fetch_one(&pool)
.await
.expect("Failed to fetch user")
.try_get(0)
.expect("Failed to get column value");

println!("User {}: {}", user_id, user_info.0);
```

## 戻り値の型
`Query<'q, DB, <DB as HasArguments<'q>>::Arguments>`

`Query`オブジェクトは `fetch` や `execute` などのメソッドを持っている。これらを呼び出すことで実際のデータベース操作が行える。

## 適用ケース
動的なWHERE句などを持つクエリを実行したい場合。

# `query!`

`query`のマクロ版。静的なSQLクエリを実行する際に利用する。
コンパイル時チェックが行われるので安全。

```rust
let user_id = 1;
let user = sqlx::query!("SELECT id, name FROM users WHERE id = $1", user_id)
    .fetch_one(&pool)
    .await?;
println!("User {}: {}", user.id, user.name);
```

## 戻り値の型
コンパイラが生成する匿名の構造体。
具体的には、`query!`マクロはDBのスキーマ定義に基づいた匿名構造体を生成してくれる。
上記の例でいうと `user` は概念的に以下のような匿名構造体を持つ。
```rust
struct AnonymousUser {
     id: i32,
     name: String,
}
```


## 適用ケース
SQLクエリ結果をマッピングするための構造体を使わず、静的なクエリを実行して結果を型安全に取得したい場合。

# `query_as`

`query`の拡張版であり、**SQL実行結果を指定した構造体にマッピングしてくれる。**
ただし、マッピング対象の構造体に`sqlx::FromRow`をderiveする必要がある。`sqlx::FromRow`は内部的にSQL実行結果のレコードを構造体オブジェクトにデシリアライズしてくれる。

なお、`query_as` はコンパイル時のチェックはされないので、例えばマッピング対象の構造体とクエリの実行結果の型が不一致だったとしてもエラーは検出できない点に注意。

```rust
#[derive(sqlx::FromRow)]
struct User {
    id: i32,
    name: String,
    email: String,
}
let user_id = 1;
let user = sqlx::query_as::<_, User>("SELECT id, name, email FROM users WHERE id = $1")
    .bind(user_id)
    .fetch_one(&pool)
    .await?;
println!("User {}: {} ({})", user.id, user.name, user.email);
```

## 戻り値の型
`QueryAs<'q, DB, T, <DB as HasArguments<'q>>::Arguments>`

`T` は指定した構造体の型になる。

## 適用ケース
- クエリ結果を事前に定義した構造体に直接マッピングしたい場合
- 動的なSQLクエリ（実行時に構築されるクエリ）を使用する場合
- CI環境やオフライン開発時に「DBへの接続はできないけどコンパイルを通したい」場合



# `query_as!`

`query!`と`query_as`の組み合わせで、**コンパイル時のチェックと構造体へのマッピングを同時に行える。**
静的なSQLクエリを実行する際に利用する。

```rust
struct User {
    id: i32,
    name: String,
    email: String,
}
let user_id = 1;
let user = sqlx::query_as!(User, "SELECT id, name, email FROM users WHERE id = $1", user_id)
    .fetch_one(&pool)
    .await?;
println!("User {}: {} ({})", user.id, user.name, user.email);
```

## 戻り値の型
指定した構造体型 `T` を返す `Query` オブジェクト。

## 適用ケース
- クエリ結果を事前に定義した構造体に直接マッピングしたい場合
- コンパイル時チェックを行いたい場合



:::note info
`query_as!`マクロを使用する場合、構造体に`#[derive(sqlx::FromRow)]`を付ける必要はない。自動的に必要な実装（フィールドの値のデシリアライズなど）が生成される。
:::

# おわりに
可能な限りマクロ（`query!`, `query_as!`）を使って型安全性を担保するのがいいですね。

# 参考リンク
https://zenn.dev/collabostyle/articles/9747c20a516f34

https://zenn.dev/southan/articles/e59a2aa59ad1cd

https://zenn.dev/lecto/articles/8eb2472ca7233d#query_as%E3%81%A7%E6%A7%8B%E9%80%A0%E4%BD%93%E3%81%AB%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0%E3%81%99%E3%82%8B%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E5%95%8F%E9%A1%8C%E7%82%B9

https://sqlx.dev/