ユニークビジョン株式会社 Advent Calendar 2025 15日目の記事です!
SQLx とは
SQLx は Rust 向けの非同期 SQL クライアントライブラリです。PostgreSQL、MySQL、SQLite に対応しており、以下の特徴があります。
- コンパイル時のクエリチェック: SQL クエリの構文エラーや型の不一致をコンパイル時に検出できる
- 生の SQL を直接記述: ORM のような独自構文を覚える必要がなく、SQL をそのまま書ける
- 非同期対応: tokio などの非同期ランタイムと組み合わせて使用できる
- マイグレーション機能: SQL ファイルによるスキーマ管理が可能
最大の特徴は「コンパイル時のクエリチェック」です。SQLx では query! マクロを使用することで、ビルド時にデータベースへ接続し、クエリの妥当性を検証します。
今回は、SQLx のコンパイル時チェック機能を試してみるべく、最小構成のプロジェクトを作成してみました!
プロジェクト構成
今回作成した最小構成のプロジェクトを紹介します。
sqlx-playground/
├── Cargo.toml
├── Dockerfile
├── compose.yaml
├── .env
├── .vscode/
│ └── settings.json
├── migrations/
│ └── 20251215000000_create_users.sql
└── src/
└── main.rs
Cargo.toml
[package]
name = "sqlx-playground"
version = "0.1.0"
edition = "2024"
[dependencies]
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "macros", "migrate"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
dotenvy = "0.15"
sqlx クレートの features には以下を指定しています。
-
runtime-tokio: 非同期ランタイムとして tokio を使用 -
postgres: PostgreSQL に対応 -
macros:query!やquery_as!マクロを使用可能にする -
migrate: マイグレーション機能を有効化
compose.yaml
services:
postgres:
image: postgres:18
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: sqlx_playground
volumes:
- postgres_data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
dev:
build: .
volumes:
- .:/app
- cargo_cache:/usr/local/cargo/registry
environment:
DATABASE_URL: postgres://postgres:postgres@postgres:5432/sqlx_playground
depends_on:
postgres:
condition: service_healthy
stdin_open: true
tty: true
volumes:
postgres_data:
cargo_cache:
PostgreSQL コンテナと開発用コンテナを定義しています。
Dockerfile
FROM rust:1.88
RUN cargo install sqlx-cli --no-default-features --features postgres
WORKDIR /app
sqlx-cli をインストールした Rust 環境を構築します。sqlx-cli はマイグレーションの実行やオフラインモード用のキャッシュ生成に使用します。
マイグレーションファイル
migrations/20251215000000_create_users.sql:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE
);
main.rs
use sqlx::postgres::PgPoolOptions;
#[derive(Debug)]
struct User {
id: i32,
name: String,
email: String,
}
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
dotenvy::dotenv().ok();
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&database_url)
.await?;
// コンパイル時チェック付きでユーザーを挿入
let user = sqlx::query_as!(
User,
r#"
INSERT INTO users (name, email)
VALUES ($1, $2)
RETURNING id, name, email
"#,
"Alice",
"alice@example.com"
)
.fetch_one(&pool)
.await?;
println!("Inserted user: {:?}", user);
// コンパイル時チェック付きで全ユーザーを取得
let users = sqlx::query_as!(
User,
r#"
SELECT id, name, email
FROM users
ORDER BY id
"#
)
.fetch_all(&pool)
.await?;
println!("All users: {:?}", users);
Ok(())
}
使い方
# 開発コンテナに入る
docker compose run --rm dev bash
# マイグレーションを実行
sqlx migrate run
# アプリケーションを実行
cargo run
コンパイル時チェックで検出できるエラーの例
SQLx の最大の利点は、SQL の誤りをコンパイル時に検出できることです。いくつか試してみました!
1. カラム名のタイポ
sqlx::query!("SELECT emial FROM users") // email ではなく emial
コンパイルすると以下のエラーが発生します。
error: error returned from database: column "emial" does not exist
--> src/main.rs:38:17
|
38 | let users = sqlx::query_as!(
| _________________^
...
文字列ベースの SQL では見つけにくいタイポも、SQLx ならコンパイル時にエラーになります。
2. 存在しないカラムの参照
sqlx::query!("SELECT deleted_column FROM users")
コンパイルすると以下のエラーが発生します。
error: error returned from database: column "deleted_column" does not exist
--> src/main.rs:38:17
|
38 | let users = sqlx::query_as!(
| _________________^
...
スキーマ変更後に発生しやすいエラーです。カラムを削除したにもかかわらず、SQL の修正を忘れていた場合でも、コンパイル時に検出できます。
3. 型の不一致
struct User {
id: String, // データベースでは INTEGER 型
}
sqlx::query_as!(User, "SELECT id FROM users")
コンパイルすると以下のエラーが発生します。
error[E0277]: the trait bound `std::string::String: From<i32>` is not satisfied
--> src/main.rs:22:16
|
22 | let user = sqlx::query_as!(
| ________________^
...
| |_____^ the trait `From<i32>` is not implemented for `std::string::String`
データベースの id カラムは INTEGER 型(Rust では i32)ですが、構造体では String として定義しているため、型変換ができずエラーになります。実行時の予期しないパニックを防ぎ、Rust の型安全性をデータベースアクセスにまで拡張できます。
VSCode (rust-analyzer) でのオフラインモード設定
SQLx の query! マクロはコンパイル時にデータベースへ接続してクエリを検証します。しかし、rust-analyzer を使用する場合は、オフラインモードを使用するのが使いやすそうです。
オフラインモードとは
通常、SQLx はコンパイル時に DATABASE_URL で指定されたデータベースへ接続し、クエリの妥当性を検証します。一方、オフラインモードでは、事前に生成したキャッシュファイル(.sqlx/ ディレクトリ)を参照することで、データベースに接続せずにコンパイル時チェックを行います。
通常モード:
query!マクロ → データベースに接続 → クエリを検証
オフラインモード:
query!マクロ → .sqlx/ のキャッシュを参照 → クエリを検証
キャッシュは cargo sqlx prepare コマンドで生成します。このコマンドはデータベースに接続し、プロジェクト内のすべてのクエリを解析して、その結果を JSON ファイルとして保存します。
設定方法
まず、開発コンテナ内でキャッシュを生成します。
docker compose run --rm dev cargo sqlx prepare
これにより .sqlx/ ディレクトリにクエリのメタデータが保存されます。
次に、.vscode/settings.json を作成します。
{
"rust-analyzer.cargo.extraEnv": {
"SQLX_OFFLINE": "true"
}
}
この設定により、rust-analyzer は .sqlx/ ディレクトリのキャッシュを参照してクエリを検証してくれました。
こんな運用がよさそう
- 開発時: オフラインモードで静的解析
-
スキーマ変更時:
cargo sqlx prepareでキャッシュを更新 -
コミット時:
.sqlx/ディレクトリをバージョン管理に含める - CI: オフラインモードでビルド・テスト
感想
生のSQLを書きつつ開発体験もよさそうで好きです!