ゲスト 27人 と メンバー0人 がオンラインです

rustでmysqlを使用してみる


 

使用するクレート


sqlx:0.7.1
・mysqlの他にmssql,postgresをサポートしている

anyhow:1.0.75
・エラー処理を楽するために使用する

chrono:0.4.31
・日時関係のデータを取り扱うために使用する

dotenv:0.15.0
・「.env」ファイルで環境変数を管理するために使用する

tokio:1.32.0
・非同期処理のために使用する

 

準備


sql-cliのインストール

cargo install --version="0.5.10" sqlx-cli

 

プロジェクトの作成


cargo new mysql_sample

 

依存関係の追加(クレートの追加)


// sqlxの追加
//  featuresで、使用するRDBMSやその他機能を指定する
//  ・runtime-tokio:これを指定しないと、tokioの非同期処理でsqlxを実行した場合にパニックになる
//  ・chrono:これを指定しないと、chronoの構造体をsqlxで使用した際にパニックになる
//  SQLは「mysql」、非同期クレートとして「tokio」、日時データを扱うために「chrono」も追加
cargo add sqlx --features mysql,runtime-tokio,chrono

// tokioの追加
cargo add tokio --features macros,rt-multi-thread

// anyhowの追加
cargo add anyhow

// dotenvの追加
cargo add dotenv

 

.envファイルをプロジェクト直下に作成して環境変数を追加する


// DB接続文字列
DATABASE_URL=mysql://ユーザ名:パスワード@サーバ名orIP/DB名

// RUSTのトレースを有効にする(デバッグ用)
RUST_BACKTRACE=1

 

テーブルの作成


前提:DB及びユーザがすでに作成済であること

 

1.sqlx-cliを利用してテンプレートを作成する

sqlx migrate add -r マイグレーションファイルの名前

コマンド実行後、migrationというフォルダが作成され、
その中に「日時_ファイル名_up.sql」「日時_ファイル名_down.sql」
という2つの空ファイルが作成される。
※-rを指定すると「テーブル作成用+テーブル削除用」の二つのファイルが作成される

2.sqlファイルの更新

upにCREATE TABLE、downにDROP TABLEを記入する

~_up.sql

CREATE TABLE user(
    id varchar(16) primary key,
    pass_hash varchar(256) not null,
    name varchar(64) not null,
    reason varchar(256),
    insert_date date not null,
    update_date date not null,
    delete_date date
);

~down.sql

DROP TABLE IF EXISTS user;

3.migrationを実行してテーブルを作成する

sqlx migrate run

※テーブルを削除したい場合は「run」の代わりに「revert」を指定する

4.テーブルが作成されていることを確認する

 

処理を書く


transactionの扱いについてはまだ確認していないので、
確認後に別途記入する

1.コードサンプル

main.rs

use anyhow;
use chrono::{NaiveDate};
use dotenv;
use sqlx::{prelude::*};
use sqlx::mysql::{MySqlPoolOptions};
use tokio;

// INSERT文のテスト
async fn insert() -> Result<(),anyhow::Error> {
    println!("### insert ###");

    let url = dotenv::var("DATABASE_URL")?;
    let pool = MySqlPoolOptions::new().connect(&url).await?;
    let now = chrono::Local::now();

    let result = sqlx::query(
    r#"
            insert into user values(?,?,?,?,?,?,?)            
        "#
    )
    .bind("myid")
    .bind("password")
    .bind("myname")
    .bind("new")
    .bind(&now)
    .bind(&now)
    .bind(&now)
    .execute(&pool)
    .await?;

    println!("insert rows_affected:{}",result.rows_affected());

    Ok(())
}

// SELECT文のテスト
async fn select() -> Result<(),anyhow::Error> {
    println!("### select ###");

    let url = dotenv::var("DATABASE_URL")?;
    let pool = MySqlPoolOptions::new().connect(&url).await?;    
    let result = sqlx::query("select * from user").fetch_all(&pool).await?; 

    for row in result {   
        let id: String = row.get(0);
        let pass: String = row.get(1);
        let name: String = row.get(2);
        let reason: String = row.get(3);
        let insert_date: NaiveDate = row.get(4);
        let update_date: NaiveDate = row.get(5);
        let delete_date: NaiveDate = row.get(6);

        println!("id={}",id);
        println!("pass={}",pass);
        println!("name={}",name);
        println!("reason={}",reason);
        println!("insert_date={}",insert_date);
        println!("update_date={}",update_date);
        println!("delete_date={}",delete_date);
    }

    Ok(())
}

// UPDATE文のテスト
async fn update() -> Result<(),anyhow::Error> {
    println!("### update ###");

    let url = dotenv::var("DATABASE_URL")?;
    let pool = MySqlPoolOptions::new().connect(&url).await?;
    let now = chrono::Local::now();

    let result = sqlx::query(
    r#"
            update user set pass_hash=?,reason=?,update_date=? where id=?
        "#        
    )
    .bind("new password")
    .bind("forgot password")
    .bind(&now)
    .bind("myid")
    .execute(&pool)
    .await?;

    println!("update rows_affected:{}",result.rows_affected());

    Ok(())
}

// DELETE文のテスト
async fn delete() -> Result<(),anyhow::Error> {
    println!("### delete ###");

    let url = dotenv::var("DATABASE_URL")?;
    let pool = MySqlPoolOptions::new().connect(&url).await?;

    let result = sqlx::query(
    r#"
            delete from user where id=?
        "#
    )
    .bind("myid")
    .execute(&pool)
    .await?;

    println!("delete rows_affected:{}",result.rows_affected());

    Ok(())
}

// TRUNCATE
async fn delete_all() -> Result<(),anyhow::Error> {
    println!("### delete all ###");

    let url = dotenv::var("DATABASE_URL")?;
    let pool = MySqlPoolOptions::new().connect(&url).await?;

    let result = sqlx::query(
    r#"
            truncate table user
        "#
    )
    .execute(&pool)
    .await?;

    println!("delete all rows_affected:{}",result.rows_affected());

    Ok(())
}

#[tokio::main]
async fn main(){
    let _ = delete_all().await;

    match insert().await {
        Err(e) => {
            println!("insert error :{}",e);
            return;
        }
        _ => {}
    };

    match select().await {
        Err(e) => {
            println!("select error :{}",e);
            return;
        }
        _ => {}
    };

    match update().await {
        Err(e) => {
            println!("update error :{}",e);
            return;
        }
        _ => {}
    };
    let _ = select().await;

    match delete().await {
        Err(e) => {
            println!("delete error :{}",e);
            return;
        }
        _ => {}
    };    
}

 

2.実行結果


### delete all ###
delete all rows_affected:0
### insert ###
insert rows_affected:1
### select ###
id=myid
pass=password
name=myname
reason=new
insert_date=2023-09-24
update_date=2023-09-24
delete_date=2023-09-24
### update ###
update rows_affected:1
### select ###
id=myid
pass=new password
name=myname
reason=forgot password
insert_date=2023-09-24
update_date=2023-09-24
delete_date=2023-09-24
### delete ###
delete rows_affected:1
PS C:\Users\kimura\Desktop\rust\mysql_sample>