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>