mcpField
S
sqlx

sqlx

github.com/melonask開発445 回インストール27 週間前に更新
リポジトリ

sqlx について

sqlxは、Rust開発者向けの非同期優先SQLツールキットです。PostgreSQL、MySQL、SQLiteに対応し、クエリマクロでコンパイル時にSQLと型を検証します。

主な機能

  1. 01コンパイル時のSQL・型チェック
  2. 02PostgreSQL、MySQL、SQLite対応
  3. 03コネクションプールとトランザクション
  4. 04動的なクエリ構築
  5. 05マイグレーションとテスト支援

活用シーン

  1. 01Rustのコンパイル時にSQLエラーを検出
  2. 02対応データベースへの非同期クエリ
  3. 03実行時条件に応じたクエリ構築
  4. 04データベースのマイグレーションとテスト管理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/melonask/sqlx-skills --skill sqlx

インストール後、Claude Code で/sqlxを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

known-issues.md
README.md
references/
connection-pooling.md
testing-mocking.md
feature-flags.md
error-handling.md
compile-time-checking.md
database-specifics.md
type-mapping.md
transactions-migrations.md

詳細

sqlx — Rust 向け非同期 SQL ツールキット

sqlx は、コンパイル時のクエリチェック機能を備えた、非同期を前提とする純粋な Rust 製 SQL ツールキットです。DSL を使わずに PostgreSQL、MySQL、SQLite をサポートします。生の SQL を記述すると、sqlx がコンパイル時に稼働中のデータベースに対して検証します。

クレートのアーキテクチャ

モジュール目的
sqlx::postgresPostgreSQL ドライバー(PgPool、PgConnection、PgListener)
sqlx::mysqlMySQL ドライバー(MySqlPool、MySqlConnection)
sqlx::sqliteSQLite ドライバー(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},
};

よくある落とし穴

  1. コンパイル時に DATABASE_URL が設定されていない — query!() マクロは cargo build の実行中に DB に接続します。.env で DATABASE_URL を設定するか、cargo sqlx prepare を使ってオフラインモードにしてください。

  2. 実行時機能フラグが設定されていない — 機能には必ず runtime-tokio または runtime-async-std を含めてください。設定しないと、プールの作成時にパニックが発生します。

  3. データベース機能フラグが設定されていない — DB 機能を指定せずに sqlx = "0.8" を追加した場合、有効になるのは any、json、macros、migrate、derive のみです。必ず features = ["postgres"] のように追加してください。

  4. プレースホルダー構文が間違っている — PostgreSQL では $1、MySQL/SQLite では ? を使います。コンパイル時マクロはこれを強制しますが、実行時クエリでは強制されません。

  5. トランザクションのデリファレンス — トランザクションでクエリを実行するには、&mut *tx を使います(&tx ではありません)。

  6. SQLite のブール値のマッピング — ブール値は INTEGER(0/1)として保存されます。SQL の比較には整数を使ってください。

  7. クエリが多い場合のコンパイル時間の遅延 — オフラインモードを使うか、重要でないクエリには query_unchecked!() の使用を検討してください。

  8. 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] マクロ、フィクスチャ、モック可能にするためのリポジトリパターン、テスト用のインメモリ SQLite
  • references/database-specifics.md — PostgreSQL(PgListener、COPY、配列、範囲、複合型)、MySQL 固有の情報、SQLite(インメモリ、ジャーナルモード、PRAGMA、拡張機能)
  • references/error-handling.md — sqlx::Error 列挙型、データベースエラーコード、RowNotFound、デコードエラー、プールエラー
  • references/feature-flags.md — 機能フラグの完全な一覧、ユースケース別の Cargo.toml パターン、TLS オプション、実行時オプション

コメント