Rustで直接SQLを書いてMySQLからデータを抽出する方法


はじめに

RustでMySQLを使う場合、事前に構造体を定義する方法が主流ですが、
直接SQLを書きたいケースも多いのではないでしょうか。
今回はDieselを使って直SQLでデータを抽出する方法を紹介します。

用意したデータ

ソースはこちらから

テーブル

monster

monster_id name type1_id type2_id
1 フシギダネ 5 8
2 フシギソウ 5 8
3 フシギバナ 5 8
4 ヒトカゲ 2 NULL
5 リザード 2 NULL
6 リザードン 2 10
7 ゼニガメ 3 NULL
8 カメール 3 NULL
9 カメックス 3 NULL

type

type_id type_name
1 ノーマル
2 ほのお
3 みず
4 でんき
5 くさ
6 こおり
7 かくとう
8 どく
9 じめん
10 ひこう
11 エスパー
12 むし
13 いわ
14 ゴースト
15 ドラゴン
16 あく
17 はがね
18 フェアリー

MySQLに接続

Dieselというライブラリを使用します。
https://github.com/diesel-rs/diesel

cargo.toml
[dependencies]
diesel = { version = "*", features = ["mysql"] }

DBに接続するための関数を作ります。

utils.rs
use diesel::mysql::MysqlConnection;
use diesel::prelude::*;
use dotenv::dotenv;

pub fn establish_connection() -> MysqlConnection {
    let database_url = "mysql://[user]:[password]@host[:port][/database]";
    MysqlConnection::establish(&database_url)
        .expect(&format!("Error connecting to {}", database_url))
}

※mysql://[user]:[password]@host[:port][/database]には実際の接続情報を入力してください。

シンプルなSQLを書いてみる

use crate::utils::establish_connection;

use diesel::deserialize::QueryableByName;
use diesel::mysql::MysqlConnection;
use diesel::prelude::*;
use diesel::sql_query;

mod utils;

type DB = diesel::mysql::Mysql;

#[derive(Debug)]
pub struct Monster {
    monster_id: i32,
    name: String,
    type1_id: i32,
    type2_id: Option<i32>,
}

impl QueryableByName<DB> for Monster {
    fn build<R: diesel::row::NamedRow<diesel::mysql::Mysql>>(
        row: &R,
    ) -> diesel::deserialize::Result<Self> {
        Ok(Monster {
            monster_id: row.get("monster_id")?,
            name: row.get("name")?,
            type1_id: row.get("type1_id")?,
            type2_id: row.get("type2_id")?,
        })
    }
}

fn simple_sql() {
    let connection: MysqlConnection = establish_connection();
    let monsters: Vec<Monster> = sql_query(
        "
        SELECT
            monster_id,
            name,
            type1_id,
            type2_id
        FROM
            monster
        ",
    )
    .load(&connection)
    .unwrap();

    println!("{:?}", monsters)
}

直接SQLを使うには、diesel::sql_queryを使います。
diesel::deserialize::QueryableByNameを使ってデータを受け取るための型を定義します。

PreparedStatementっぽいこと

use diesel::sql_types::Text;
use diesel::sql_types::Integer;

fn prepared_statement_sql() {
    let connection: MysqlConnection = establish_connection();
    let monsters: Vec<Monster> = sql_query(
        "
        SELECT
            monster_id,
            name,
            type1_id,
            type2_id
        FROM
            monster
        WHERE
            monster_id = ?
            OR name = ?
        ",
    )
    .bind::<Integer, _>(4)
    .bind::<Text, _>("ヒトカゲ")
    .load(&connection)
    .unwrap();

    println!("{:?}", monsters[0])
}

SQLの中に?を書いて、
.bind::<Integer, _>(4).bind::<Text, _>("ヒトカゲ")と書くことで安全にSQLの中に数値や文字列を挿入することができます。

JOIN

#[derive(Debug)]
pub struct MonsterFull {
    monster_id: i32,
    name: String,
    type1_id: i32,
    type2_id: Option<i32>,
    type1_name: String,
    type2_name: Option<String>,
}

impl QueryableByName<DB> for MonsterFull {
    fn build<R: diesel::row::NamedRow<diesel::mysql::Mysql>>(
        row: &R,
    ) -> diesel::deserialize::Result<Self> {
        Ok(MonsterFull {
            monster_id: row.get("monster_id")?,
            name: row.get("name")?,
            type1_id: row.get("type1_id")?,
            type2_id: row.get("type2_id")?,
            type1_name: row.get("type1_name")?,
            type2_name: row.get("type2_name")?,
        })
    }
}

fn complex_sql() {
    let connection: MysqlConnection = establish_connection();
    let monsters: Vec<MonsterFull> = sql_query(
        "
        SELECT
            m.monster_id,
            m.name,
            m.type1_id,
            m.type2_id,
            t1.type_name AS type1_name,
            t2.type_name AS type2_name
        FROM
            monster m
        LEFT JOIN
            type t1
        ON
            m.type1_id = t1.type_id
        LEFT JOIN
            type t2
        ON
            m.type2_id = t2.type_id
        ",
    )
    .load(&connection)
    .unwrap();

    println!("{:?}", monsters);
}

JOINなどの複雑な操作も受け取るための型さえ用意すれば簡単にできます。

おわりに

この方法を使えば、Rustから直SQLを書いてデータを抽出することができました。
直接SQLを書きたい方は是非参考にしてもらえればと思います。