【Rust】dieselを使ったMySQLのデータ操作

## diesel クレート

https://crates.io/crates/diesel

バージョン：2.2.5

## 事前準備

Rustのバージョンは以下の通り、MySQL8.0 を利用

```
# cargo -V
cargo 1.82.0 (8f40fc59f 2024-08-21)
# rustc -V
rustc 1.82.0 (f6e511eec 2024-10-15)
```

下記のテーブルが存在している前提（diesel のマイグレーションは使わない）

```sql
CREATE TABLE articles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```

## 操作

- `.env` に`DATABASE_URL` を設定
    - dieselコマンドの使用時に`--database_url` を指定するか、`DATABASE_URL` 環境変数をセットしておく必要がある
- diesel cli のインストール・テーブル情報から`schema.rs` を生成
```
cargo install diesel_cli --no-default-features --features mysql

diesel print-schema > src/schema.rs
```

```toml:Cargo.toml
[package]
name = "app"
version = "0.1.0"
edition = "2021"

[dependencies]
chrono = "0.4.38"
diesel = { version = "2.2.5", features = ["mysql", "chrono"] }
dotenvy = "0.15.7"
```

```rs:schema.rs
// @generated automatically by Diesel CLI.

diesel::table! {
    articles (id) {
        id -> Integer,
        #[max_length = 255]
        title -> Varchar,
        content -> Text,
        created_at -> Nullable<Datetime>,
        updated_at -> Nullable<Datetime>,
    }
}
```

- select, insert, update, delete の例を記載
- （エラーハンドリングが弱い）

```rs:main.rs
mod schema;

use chrono::NaiveDateTime;
use diesel::{prelude::*, Connection, MysqlConnection, RunQueryDsl};
use dotenvy::dotenv;
use schema::articles::dsl::*;
use std::{env, error::Error};

fn main() -> Result<(), Box<dyn Error>> {
    let conn = &mut establish_connection();

    let results = select_articles(conn);
    println!("{:?}", results);

    let result = select_article_by_id(conn, 3);
    print!("{:?}", result);

    let new_article = Article {
        id: 0,
        title: String::from("diesel title"),
        content: String::from("diesel content"),
        created_at: None,
        updated_at: None,
    };
    let _ = insert_article(conn, &new_article);

    let mut article_to_update = select_article_by_id(conn, 5)?;
    article_to_update.title = String::from("UPDATE TITLE");
    let _ = update_article(conn, &article_to_update);

    let article_to_delete = select_article_by_id(conn, 4)?;
    let _ = delete_article(conn, &article_to_delete)?;

    Ok(())
}

fn select_articles(conn: &mut MysqlConnection) -> QueryResult<Vec<Article>> {
    articles.load::<Article>(conn)
}

fn select_article_by_id(conn: &mut MysqlConnection, article_id: i32) -> QueryResult<Article> {
    articles.filter(id.eq(article_id)).first::<Article>(conn)
}

fn insert_article(conn: &mut MysqlConnection, new_article: &Article) -> QueryResult<usize> {
    diesel::insert_into(articles)
        .values(new_article)
        .execute(conn)
}

fn update_article(conn: &mut MysqlConnection, article: &Article) -> QueryResult<usize> {
    diesel::update(articles.find(article.id)).set(
        (
            title.eq(article.title.to_string()), 
            content.eq(article.content.to_string()),
        )
    ).execute(conn)
}

fn delete_article(conn: &mut MysqlConnection, article: &Article) -> QueryResult<usize> {
    diesel::delete(articles.find(article.id)).execute(conn)
}

fn establish_connection() -> MysqlConnection {
    dotenv().ok();

    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    MysqlConnection::establish(&database_url).unwrap()
}

#[derive(Queryable, Insertable, Debug, PartialEq, Eq)]
#[diesel(table_name = crate::schema::articles)]
struct Article {
    id: i32,
    title: String,
    content: String,
    created_at: Option<NaiveDateTime>,
    updated_at: Option<NaiveDateTime>,
}
```