🔨

SQLxのクエリビルダーでよく使うパターン

に公開

RustのライブラリであるSQLxのクエリビルダーは文字列を結合していくだけの低レベルな機能しか提供しません。

毎回組み立てるのは大変なので、いくつかのよく使うパターンをここにメモすることにします。データベースはMariaDB(MySQL)を使いましたがPostgreSQLやSQLiteもたぶん同じです。

VALUES句でまとめてINSERTしたいとき

いわゆるバルクインサートです。

出力
INSERT INTO users(id, name) VALUES (?, ?), (?, ?)
コード
use sqlx::QueryBuilder;
use sqlx::MySql;

fn main() {
    let users = vec![
        (1, "一郎"),
        (2, "太郎"),
    ];

    let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new("INSERT INTO users(id, name) ");
    query_builder
        .push_values(users, |mut separated, user| {
            separated
                .push_bind(user.0)
                .push_bind(user.1);
        });

    println!("{}", query_builder.sql());

    let _query = query_builder.build();
}

作ったクエリはいつものように.execute()とかできます。

IN句で複数の値に一致するレコードを検索する

クエリビルダーを使っていますが、format!マクロを使ってベクターの数だけ?を展開する方法もあるようです。

出力
SELECT * FROM users WHERE name IN (?, ?)
コード
use sqlx::QueryBuilder;
use sqlx::MySql;

fn main() {
    let names = vec!["一郎", "太郎"];

    let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new("SELECT * FROM users WHERE name IN (");
    let mut separated = query_builder.separated(", ");
    for value_type in names.iter() {
        separated.push_bind(*value_type);
    }
    query_builder.push(")");

    println!("{}", query_builder.sql());

    let _query = query_builder.build();
}

作ったクエリはいつものように.fetch_optional()とか.fetch_all()とかできます。

パラメータによってWHERE句の条件をON/OFFする

検索機能を設けるときに便利です。フロントエンドからJSONで検索条件を投げられた状況を想定しています。

出力
SELECT * FROM users WHERE id = ? AND name = ?
コード
use sqlx::QueryBuilder;
use sqlx::MySql;

fn main() {
    let params = serde_json::json!({ "id": 1, "name": "一郎" });

    let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new("SELECT * FROM users WHERE ");
    let mut separated = query_builder.separated(" AND ");
    if let Some(id) = params.get("id") {
        separated.push("id = ").push_bind_unseparated(id.as_number().unwrap().as_i64());
    }
    if let Some(name) = params.get("name") {
        separated.push("name = ").push_bind_unseparated(name.as_str());
    }

    println!("{}", query_builder.sql());

    let _query = query_builder.build();
}

作ったクエリはいつものように.fetch_optional()とか.fetch_all()とかできます。

パラメータによってUPDATEする項目をON/OFFする

HTTPメソッドのPATCHエンドポイントを作るときを想定しています。

出力
UPDATE users SET id = ?, name = ? WHERE id = ?
コード
use sqlx::QueryBuilder;
use sqlx::MySql;

fn main() {
    let params = serde_json::json!({ "id": 5, "name": "一郎" });
    let map = params.as_object().unwrap();

    let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new("UPDATE users SET ");
    let mut separated = query_builder.separated(", ");
    for (key, value) in map.iter() {
        separated
            .push(key)
            .push_unseparated(" = ")
            .push_bind_unseparated(value);
    }
    query_builder.push(" WHERE id = ").push_bind(1);

    println!("{}", query_builder.sql()); // -> UPDATE users SET id = ?, name = ? WHERE id = ?

    let _query = query_builder.build();
}

作ったクエリはいつものように.execute()とかできます。

Discussion