Rust SQLx 2026完全ガイド: コンパイル時クエリ検証とデータベース面接対策

RustとSQLxによるコンパイル時SQL検証の実装方法を解説。型安全なデータベースアクセス、他のORMとの比較、面接で頻出する質問と回答例も紹介します。

Rust SQLx 2026完全ガイド: コンパイル時クエリ検証とデータベース面接対策

Rustでデータベースアプリケーションを構築する際、型安全性とパフォーマンスの両立は開発者にとって重要な課題です。SQLxは、コンパイル時にSQLクエリを検証することで、ランタイムエラーを未然に防ぐ革新的なアプローチを提供します。本記事では、SQLxの基本から高度な使用方法、そして技術面接で頻出する質問への対策まで、包括的に解説します。

SQLxは非同期対応のRust用SQLツールキットで、PostgreSQL、MySQL、SQLiteをサポートしています。最大の特徴は、コンパイル時にSQLクエリの構文と型をデータベーススキーマに対して検証できることです。

SQLxとは何か

SQLxは、純粋なRustで実装されたデータベースドライバーおよびクエリビルダーです。従来のORMとは異なり、SQLxはSQLを直接記述することを前提としており、その代わりにコンパイル時の強力な型チェックを提供します。

2026年現在、SQLxはバージョン0.8系がリリースされており、以下の特徴を備えています:

  • コンパイル時クエリ検証: sqlx::query!マクロによるSQL構文と型の静的検証
  • 非同期ファースト設計: Tokio、async-stdなどの非同期ランタイムに対応
  • マイグレーション管理: 組み込みのマイグレーションシステム
  • コネクションプーリング: 効率的なデータベース接続管理

環境構築とプロジェクトセットアップ

SQLxを使用するプロジェクトを作成するには、まずCargo.tomlに依存関係を追加します:

toml
[dependencies]
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "macros"] }
tokio = { version = "1", features = ["full"] }
dotenvy = "0.15"

[dev-dependencies]
sqlx-cli = "0.8"

環境変数の設定も必要です。.envファイルにデータベース接続文字列を記述します:

bash
DATABASE_URL=postgres://user:password@localhost:5432/myapp

SQLx CLIをインストールすると、マイグレーションの作成と実行が可能になります:

bash
cargo install sqlx-cli --no-default-features --features postgres
sqlx database create
sqlx migrate add create_users_table

コンパイル時クエリ検証の仕組み

SQLxの核心となる機能が、コンパイル時のクエリ検証です。query!マクロを使用すると、ビルド時に実際のデータベースに接続してSQLを検証します:

rust
use sqlx::postgres::PgPoolOptions;
use sqlx::FromRow;

#[derive(Debug, FromRow)]
struct User {
    id: i32,
    email: String,
    name: Option<String>,
    created_at: chrono::DateTime<chrono::Utc>,
}

async fn get_user_by_id(pool: &sqlx::PgPool, user_id: i32) -> Result<Option<User>, sqlx::Error> {
    let user = sqlx::query_as!(
        User,
        r#"
        SELECT id, email, name, created_at
        FROM users
        WHERE id = $1
        "#,
        user_id
    )
    .fetch_optional(pool)
    .await?;

    Ok(user)
}

このコードをコンパイルすると、SQLxは以下を検証します:

  1. usersテーブルが存在するか
  2. 指定したカラムが存在するか
  3. 戻り値の型がUser構造体のフィールドと一致するか
  4. $1プレースホルダーに渡すuser_idの型が適切か

カラム名のタイプミスやスキーマ変更による不整合は、実行時ではなくコンパイル時に検出されます。

オフラインモードの活用

CI/CD環境やデータベースに接続できない状況でもコンパイルを可能にするため、SQLxはオフラインモードを提供します:

bash
cargo sqlx prepare

このコマンドは.sqlxディレクトリにクエリメタデータをJSON形式で保存します。このディレクトリをバージョン管理に含めることで、データベース接続なしでもコンパイル時検証が機能します。

rust
// オフラインモードでも同じコードが動作する
let users = sqlx::query_as!(
    User,
    "SELECT id, email, name, created_at FROM users WHERE active = true"
)
.fetch_all(pool)
.await?;

トランザクション処理の実装

複数のデータベース操作を原子的に実行するには、トランザクションを使用します:

rust
async fn transfer_funds(
    pool: &sqlx::PgPool,
    from_account: i32,
    to_account: i32,
    amount: i64,
) -> Result<(), sqlx::Error> {
    let mut tx = pool.begin().await?;

    sqlx::query!(
        "UPDATE accounts SET balance = balance - $1 WHERE id = $2",
        amount,
        from_account
    )
    .execute(&mut *tx)
    .await?;

    sqlx::query!(
        "UPDATE accounts SET balance = balance + $1 WHERE id = $2",
        amount,
        to_account
    )
    .execute(&mut *tx)
    .await?;

    tx.commit().await?;
    Ok(())
}

トランザクションがスコープを抜ける際にcommit()が呼ばれていない場合、自動的にロールバックされます。これにより、エラー時の整合性が保証されます。

マイグレーション管理

SQLxには組み込みのマイグレーションシステムがあります。マイグレーションファイルはmigrationsディレクトリに配置します:

sql
-- migrations/20260912000001_create_users.sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    name VARCHAR(100),
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_users_email ON users(email);

アプリケーション起動時にマイグレーションを実行することも可能です:

rust
use sqlx::migrate::MigrateDatabase;

async fn run_migrations(pool: &sqlx::PgPool) -> Result<(), sqlx::Error> {
    sqlx::migrate!("./migrations")
        .run(pool)
        .await?;
    Ok(())
}

動的クエリの構築

検索条件が動的に変わる場合、QueryBuilderを使用します:

rust
use sqlx::QueryBuilder;

async fn search_users(
    pool: &sqlx::PgPool,
    name_filter: Option<&str>,
    email_filter: Option<&str>,
    limit: i64,
) -> Result<Vec<User>, sqlx::Error> {
    let mut builder: QueryBuilder<sqlx::Postgres> = QueryBuilder::new(
        "SELECT id, email, name, created_at FROM users WHERE 1=1"
    );

    if let Some(name) = name_filter {
        builder.push(" AND name ILIKE ");
        builder.push_bind(format!("%{}%", name));
    }

    if let Some(email) = email_filter {
        builder.push(" AND email ILIKE ");
        builder.push_bind(format!("%{}%", email));
    }

    builder.push(" ORDER BY created_at DESC LIMIT ");
    builder.push_bind(limit);

    let query = builder.build_query_as::<User>();
    let users = query.fetch_all(pool).await?;

    Ok(users)
}

SQLxと他のORMとの比較

Rustのデータベースライブラリを選択する際、SQLx以外にもDieselやSeaORMが候補に挙がります。それぞれの特徴を理解しておくことは、技術選定や面接対策において重要です。

SQLxは、生のSQLを記述しつつコンパイル時検証の恩恵を受けたい場合に最適です。既存のSQLスキルを活かせる点、複雑なクエリを自由に記述できる点が強みです。

Dieselは、Rust型システムを最大限に活用したDSLを提供します。クエリをRustコードで構築するため、IDEの補完が効きやすく、リファクタリングも容易です。ただし、学習曲線はやや急です。

SeaORMは、非同期対応のフルORMで、ActiveRecordパターンを採用しています。Rails風のAPIに慣れた開発者には親しみやすい設計です。

rust
// SQLx: 生SQL + コンパイル時検証
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
    .fetch_one(pool).await?;

// Diesel: Rust DSL
let user = users::table
    .filter(users::id.eq(id))
    .first::<User>(conn)?;

// SeaORM: ActiveRecordパターン
let user = User::find_by_id(id).one(db).await?;

面接でよく問われる質問と回答例

技術面接では、SQLxに関する理解度を測る質問が頻出します。以下に代表的な質問と模範回答を示します。

Q1: SQLxのコンパイル時クエリ検証はどのように機能しますか?

回答例: SQLxのquery!マクロは、ビルド時にDATABASE_URL環境変数で指定されたデータベースに接続し、SQLクエリを実際に検証します。クエリの構文、参照するテーブルやカラムの存在、型の互換性がチェックされ、問題があればコンパイルエラーとして報告されます。CIでデータベース接続が難しい場合は、cargo sqlx prepareでメタデータを事前生成し、オフラインモードでビルドできます。

Q2: SQLxを選択する場面とDieselを選択する場面を説明してください

回答例: SQLxは、複雑なSQL(ウィンドウ関数、再帰CTE、データベース固有の機能)を多用する場合や、既存のSQLスキルを活かしたい場合に適しています。一方、Dieselは型安全なDSLを好む場合や、クエリをRustコードとして管理したい場合に有利です。非同期処理が必須の場合はSQLx、同期処理で十分な場合はDieselも選択肢になります。

Q3: N+1問題をSQLxでどう回避しますか?

回答例: N+1問題は、関連データを取得する際に発生します。SQLxでは、JOINを使用した単一クエリで関連データを取得するか、WHERE IN句を使用してバッチ取得を行います。例えば、ユーザーとその注文を取得する場合、まずユーザーIDのリストを取得し、次にWHERE user_id = ANY($1)で全注文を一度に取得する方法が効果的です。

Rustの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

本番環境でのベストプラクティス

本番環境でSQLxを運用する際のベストプラクティスを紹介します。

コネクションプールの設定

rust
let pool = PgPoolOptions::new()
    .max_connections(20)
    .min_connections(5)
    .acquire_timeout(std::time::Duration::from_secs(3))
    .idle_timeout(std::time::Duration::from_secs(600))
    .connect(&database_url)
    .await?;

エラーハンドリング

rust
use sqlx::Error as SqlxError;

fn handle_db_error(err: SqlxError) -> AppError {
    match err {
        SqlxError::RowNotFound => AppError::NotFound,
        SqlxError::Database(db_err) => {
            if db_err.is_unique_violation() {
                AppError::Conflict("Resource already exists".into())
            } else {
                AppError::Internal(db_err.to_string())
            }
        }
        _ => AppError::Internal(err.to_string()),
    }
}

まとめ

SQLxは、Rustの型安全性とSQLの柔軟性を両立させる優れたツールです。コンパイル時クエリ検証により、データベース関連のバグを開発段階で発見でき、本番環境での障害リスクを大幅に低減できます。

技術面接においては、SQLxの仕組みだけでなく、トレードオフを理解し、適切な技術選定ができることを示すことが重要です。本記事で紹介したコード例と質問への回答例を参考に、実践的な知識を身につけることで、面接での評価向上につながります。

Rustでのデータベース開発を検討している場合、SQLxは確実に検討すべき選択肢の一つです。

今日のチャレンジ

Rust のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年9月12日 更新

共有

関連記事