sqlx について
sqlxは、Rust開発者向けの非同期優先SQLツールキットです。PostgreSQL、MySQL、SQLiteに対応し、クエリマクロでコンパイル時にSQLと型を検証します。
主な機能
- 01コンパイル時のSQL・型チェック
- 02PostgreSQL、MySQL、SQLite対応
- 03コネクションプールとトランザクション
- 04動的なクエリ構築
- 05マイグレーションとテスト支援
活用シーン
- 01Rustのコンパイル時にSQLエラーを検出
- 02対応データベースへの非同期クエリ
- 03実行時条件に応じたクエリ構築
- 04データベースのマイグレーションとテスト管理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ melonask/ sqlx-skills --skill sqlxインストール後、Claude Code で/sqlxを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
known-issues.mdREADME.mdreferences/connection-pooling.mdtesting-mocking.mdfeature-flags.mderror-handling.mdcompile-time-checking.mddatabase-specifics.mdtype-mapping.mdtransactions-migrations.md詳細
sqlx — Rust 向け非同期 SQL ツールキット
sqlx は、コンパイル時のクエリチェック機能を備えた、非同期を前提とする純粋な Rust 製 SQL ツールキットです。DSL を使わずに PostgreSQL、MySQL、SQLite をサポートします。生の SQL を記述すると、sqlx がコンパイル時に稼働中のデータベースに対して検証します。
クレートのアーキテクチャ
| モジュール | 目的 |
|---|---|
sqlx::postgres | PostgreSQL ドライバー(PgPool、PgConnection、PgListener) |
sqlx::mysql | MySQL ドライバー(MySqlPool、MySqlConnection) |
sqlx::sqlite | SQLite ドライバー(SqlitePool、SqliteConnection) |
sqlx::any | データベース非依存のドライバー(AnyPool)— コンパイル時チェックなし |
sqlx::query_builder | 実行時の動的なクエリ構築(QueryBuilder) |
sqlx::migrate | マイグレーション用フレームワーク(migrate! マクロ、Migrator) |
sqlx::types | 型ラッパー(Json<T>、Text<T>) |
クイックスタート:最小限の依存関係の設定
データベースドライバーを少なくとも 1 つと、ランタイム機能を必ず含めてください。ランタイム機能がない場合、プールは実行時にパニックを起こします。
# Cargo.toml
[dependencies]
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls", "postgres", "chrono", "uuid"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
dotenvy = "0.15"
# .env
DATABASE_URL=postgres://postgres:password@localhost:5432/mydb
use sqlx::PgPool;
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
dotenvy::dotenv().ok();
let pool = PgPool::connect(&std::env::var("DATABASE_URL").unwrap()).await?;
let users = sqlx::query_as!(User, "SELECT id, name FROM users")
.fetch_all(&pool).await?;
Ok(())
}
重要:query!() マクロを使用するには、コンパイル時に DATABASE_URL を設定する必要があります。.env で設定するか、準備済みメタデータとともに SQLX_OFFLINE=true を使用してください(オフラインモードのセクションを参照)。
クイックリファレンス:どのガイドを読めばよいですか?
- コンパイル時クエリマクロ、型オーバーライド、オフラインモード →
references/compile-time-checking.mdをお読みください - コネクションプール、PgConnectOptions、プールの調整 →
references/connection-pooling.mdをお読みください - Rust と SQL の型マッピング、FromRow、カスタム型 →
references/type-mapping.mdをお読みください - トランザクション、ネストされたトランザクション、セーブポイント →
references/transactions-migrations.mdをお読みください - #[sqlx::test]、フィクスチャ、テストパターン →
references/testing-mocking.mdをお読みください - PostgreSQL 固有の機能(PgListener、COPY、配列、範囲) →
references/database-specifics.mdをお読みください - エラー処理、sqlx::Error enum →
references/error-handling.mdをお読みください - 機能フラグ、Cargo.toml のパターン →
references/feature-flags.mdをお読みください
主なパターンの概要
1. コンパイル時チェック付きクエリ(推奨)
query!() マクロはコンパイル時にデータベースへ接続し、SQL 構文を検証して、パラメーターの型が一致するかを確認し、戻り値のカラム型を検証します。
// Returns an anonymous struct with typed fields (no FromRow needed)
let record = sqlx::query!("SELECT id, name, email FROM users WHERE id = $1", user_id)
.fetch_one(&pool)
.await?;
// record.id: i64, record.name: String, record.email: String
// query_as! maps to a named struct (needs #[derive(FromRow)])
let user: User = sqlx::query_as!(User, "SELECT id, name FROM users WHERE id = $1", user_id)
.fetch_one(&pool)
.await?;
// query_scalar! returns a single column value
let count: i64 = sqlx::query_scalar!("SELECT COUNT(*)::BIGINT FROM users")
.fetch_one(&pool)
.await?;
2. 実行時クエリ関数(コンパイル時のデータベース不要)
コンパイル時に DATABASE_URL を設定できない場合に使用してください。これらの関数は型チェックされません。
// query() returns anonymous PgRow — access columns by name or index
// Note: `sqlx::Row` trait must be in scope for .get()
use sqlx::Row;
let row = sqlx::query("SELECT id, name FROM users WHERE id = $1")
.bind(1)
.fetch_one(&pool)
.await?;
let id: i64 = row.get("id");
let name: String = row.get("name");
3. 結果の取得方法
すべてのクエリで、複数の取得方法を利用できます。予想される結果件数に応じて選択してください。
// .fetch_one() — Exactly one row. Error if 0 rows (RowNotFound) or >1 rows
// .fetch_optional() — 0 or 1 row. Returns Option<T>. None if no rows.
// .fetch_all() — All rows as Vec<T>. Empty vec if no rows.
// .fetch(_) — Returns a Stream (row by row, lazy). Use with futures::StreamExt.
4. コネクションプールの設定
use sqlx::postgres::PgPoolOptions;
use std::time::Duration;
let pool = PgPoolOptions::new()
.max_connections(20)
.min_connections(5)
.acquire_timeout(Duration::from_secs(3))
.idle_timeout(Duration::from_secs(600))
.max_lifetime(Duration::from_secs(1800))
.connect("postgres://user:pass@localhost/db")
.await?;
5. トランザクション
トランザクションは Executor を実装しているため、プールまたはコネクションを受け取る任意の関数に渡せます。
use sqlx::Acquire;
// Manual transaction
let mut tx = pool.begin().await?;
sqlx::query!("INSERT INTO users (name) VALUES ($1)", "Alice")
.execute(&mut *tx)
.await?;
sqlx::query!("INSERT INTO audit_log (action) VALUES ($1)", "user created")
.execute(&mut *tx)
.await?;
tx.commit().await?;
// Dropping tx without commit() triggers automatic rollback.
// Closure-based (auto-commit on Ok, auto-rollback on Err)
// Requires acquiring a connection first — .transaction() is on Connection, not Pool.
let mut conn = pool.acquire().await?;
let result = conn.transaction::<_, _, sqlx::Error>(|tx| {
Box::pin(async move {
sqlx::query!("INSERT INTO users (name) VALUES ($1)", "Bob")
.execute(&mut **tx).await?;
Ok(())
})
}).await?;
// Nested transactions (SAVEPOINTs)
let mut tx = pool.begin().await?;
let mut nested = tx.begin().await?; // Creates a SAVEPOINT — requires `Acquire`
nested.rollback().await?; // Only rolls back to savepoint
tx.commit().await?; // Outer transaction unaffected
6. Executor を使ったジェネリック関数
Executor トレイトを使うと、プール、コネクション、トランザクションのいずれでも動作する関数を記述できます。
use sqlx::{Executor, PgExecutor};
async fn find_user_by_id<'e, E>(executor: E, id: i64) -> sqlx::Result<User>
where
E: PgExecutor<'e>,
{
sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
.fetch_one(executor)
.await
}
// Works with pool, connection, or transaction:
let user = find_user_by_id(&pool, 1).await?;
let user = find_user_by_id(&mut tx, 1).await?;
7. FromRow derive
#[derive(FromRow)] を使って、SQL の行を Rust の構造体にマッピングします。構造体のフィールド名は SQL のカラム名と一致している必要があります。
use sqlx::FromRow;
#[derive(Debug, FromRow)]
struct User {
id: i64,
name: String,
email: String,
created_at: chrono::DateTime<chrono::Utc>,
}
// Column renaming when names don't match
#[derive(Debug, FromRow)]
struct User {
id: i64,
#[sqlx(rename = "full_name")]
name: String,
}
// Flatten for JOINs (combines fields from multiple tables)
#[derive(Debug, FromRow)]
struct UserWithPost {
#[sqlx(flatten)]
user: User,
#[sqlx(flatten)]
post: Post,
}
// Skip columns from the query that you don't need
#[derive(Debug, FromRow)]
struct UserSummary {
id: i64,
name: String,
#[sqlx(skip)]
_ignored_field: String,
}
8. JSON カラム
use sqlx::types::Json;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
struct User {
id: i64,
#[sqlx(json)]
metadata: Json<Metadata>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Metadata {
role: String,
preferences: std::collections::HashMap<String, String>,
}
// Insert with JSON value
sqlx::query!(
"INSERT INTO users (name, metadata) VALUES ($1, $2)",
"Alice",
Json(Metadata { role: "admin".into(), preferences: Default::default() })
).execute(&pool).await?;
// Read — Json<T> automatically deserializes
let user: User = sqlx::query_as(
"SELECT id, name, metadata FROM users WHERE id = $1"
).bind(1).fetch_one(&pool).await?;
println!("Role: {}", user.metadata.0.role);
9. 動的クエリ向けの QueryBuilder
SQL の構造が実行時の条件に依存する場合は、文字列連結ではなく QueryBuilder を使用してください。
use sqlx::QueryBuilder;
let mut qb = QueryBuilder::new("SELECT * FROM users WHERE ");
// Conditionally add clauses
if let Some(name) = &filter.name {
qb.push("name LIKE ").push_bind(format!("%{}%", name));
}
if filter.active_only {
if filter.name.is_some() {
qb.push(" AND ");
}
qb.push("active = ").push_bind(true);
}
let users: Vec<User> = qb.build_query_as::<User>()
.fetch_all(&pool)
.await?;
10. マイグレーション
# Create a new migration
sqlx migrate add create_users
# Run all pending migrations
sqlx migrate run
# Rollback the last migration
sqlx migrate revert
# Show migration status
sqlx migrate info
マイグレーションをコードに埋め込み、実行します。
let migrator = sqlx::migrate!(); // Reads from ./migrations by default
migrator.run(&pool).await?;
11. #[sqlx::test] 属性
分離されたテストデータベースを自動的に作成し、マイグレーションを実行して、後片付けを行います。
#[sqlx::test(migrations = "./migrations")]
async fn test_create_user(pool: PgPool) -> sqlx::Result<()> {
sqlx::query!("INSERT INTO users (name) VALUES ($1)", "Alice")
.execute(&pool).await?;
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE name = $1", "Alice")
.fetch_one(&pool).await?;
assert_eq!(user.name, "Alice");
Ok(())
}
データベース別のプレースホルダー構文
これはよくあるエラーの原因です。データベースによってプレースホルダーが異なります。
| データベース | プレースホルダー | 例 |
|---|---|---|
| PostgreSQL | $1, $2, $3... | WHERE id = $1 AND name = $2 |
| MySQL | ? | WHERE id = ? AND name = ? |
| SQLite | ? | WHERE id = ? AND name = ? |
コンパイル時マクロは、DATABASE_URL に基づいてこの処理を自動的に行います。実行時クエリでは、対象のデータベースに適した構文を使用する必要があります。
主なインポートのリファレンス
use sqlx::{
// Query functions (runtime, no compile-time checking)
query, query_as, query_scalar,
// Traits
Executor, FromRow, Decode, Encode, Type,
// Pool types
PgPool, MySqlPool, SqlitePool, AnyPool, Pool,
// Connection types
PgConnectOptions, Transaction,
// Error types
Error, Result,
// JSON support
types::Json,
// PostgreSQL extras
postgres::{PgConnectOptions, PgListener, PgPoolOptions, PgSslMode},
// MySQL extras
mysql::{MySqlConnectOptions, MySqlPoolOptions},
// SQLite extras
sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteJournalMode},
};
よくある落とし穴
-
コンパイル時に DATABASE_URL が設定されていない —
query!()マクロはcargo buildの実行中に DB に接続します。.envでDATABASE_URLを設定するか、cargo sqlx prepareを使ってオフラインモードにしてください。 -
実行時機能フラグが設定されていない — 機能には必ず
runtime-tokioまたはruntime-async-stdを含めてください。設定しないと、プールの作成時にパニックが発生します。 -
データベース機能フラグが設定されていない — DB 機能を指定せずに
sqlx = "0.8"を追加した場合、有効になるのはany、json、macros、migrate、deriveのみです。必ずfeatures = ["postgres"]のように追加してください。 -
プレースホルダー構文が間違っている — PostgreSQL では
$1、MySQL/SQLite では?を使います。コンパイル時マクロはこれを強制しますが、実行時クエリでは強制されません。 -
トランザクションのデリファレンス — トランザクションでクエリを実行するには、
&mut *txを使います(&txではありません)。 -
SQLite のブール値のマッピング — ブール値は INTEGER(0/1)として保存されます。SQL の比較には整数を使ってください。
-
クエリが多い場合のコンパイル時間の遅延 — オフラインモードを使うか、重要でないクエリには
query_unchecked!()の使用を検討してください。 -
RETURNING 句 — PostgreSQL では INSERT/UPDATE で
RETURNING *をサポートしています。MySQL はサポートしていません(代わりにLAST_INSERT_ID()を使ってください)。
リファレンスファイル
各トピックの詳細については、該当するリファレンスファイルをお読みください。
references/compile-time-checking.md— query!、query_as!、query_scalar!、型の上書き、未検証のバリアント、オフラインモード、.sqlx/ ディレクトリ、クエリファイルreferences/connection-pooling.md— プールのオプション、接続オプション、PgConnectOptions、SSL モード、after_connect フック、プールのライフサイクルreferences/type-mapping.md— Rust から SQL への型対応表、機能フラグで有効になる型、FromRow 属性、カスタム Encode/Decode、透過型、列挙型のマッピングreferences/transactions-migrations.md— トランザクション(手動、クロージャー、ネスト)、ロールバック可能なマイグレーション、migrate! マクロ、マイグレーション CLI コマンドreferences/testing-mocking.md— #[sqlx::test] マクロ、フィクスチャ、モック可能にするためのリポジトリパターン、テスト用のインメモリ SQLitereferences/database-specifics.md— PostgreSQL(PgListener、COPY、配列、範囲、複合型)、MySQL 固有の情報、SQLite(インメモリ、ジャーナルモード、PRAGMA、拡張機能)references/error-handling.md— sqlx::Error 列挙型、データベースエラーコード、RowNotFound、デコードエラー、プールエラーreferences/feature-flags.md— 機能フラグの完全な一覧、ユースケース別の Cargo.toml パターン、TLS オプション、実行時オプション