Rust での DB を扱いを楽にする Diesel を利用してみます。他にも ORM はありそうなのですが、これが一番メジャーっぽいです。
今回は、Postgres の DB と連携させることを目標とします。
サンプルプログラム#
DB の立ち上げを Docker でできるようにしておきました:
GitHub のリポジトリ
準備#
依存関係#
cargo new
で適当なプロジェクトを作成し、 Cargo.toml
には以下のように記述しておきます:
1[dependencies]
2diesel = { version = "1.4.4", features = ["postgres", "chrono"] }
3dotenv = "0.15.0"
4chrono = "0.4"
diesel
の features
には2つ指定しています:
postgres
: PostgreSQL の DB を扱うためchrono
: 日付型のデータを扱うため
Diesel CLI のインストール#
Diesel には CLI ツール が用意されているため、インストールします。 Rust のプロジェクトルートで以下のコマンドを実行します:
1cargo install diesel_cli --no-default-features --features postgres
ここで、もし以下のようなエラーが出た場合:
1note: ld: library not found for -libpq
2
3clang: error: linker command failed with exit code 1 (use -v to see invocation)
libpq のライブラリがインストールされていませんので、各 OS の環境に合わせてインストールします。
Diesel のセットアップ#
Diesel のセットアップをする前に DB を立ち上げておきます。今回のプロジェクトの場合、プロジェクトルートで
1docker-compose up -d db
を実行すると PostgreSQL の DB が立ち上がります。DBeaver などのツールで DB を閲覧できるようになります。
環境変数 DATABASE_URL
は postgres://admin:password@localhost:5432/postgres
です。これを Rust のプロジェクトルートの .env
に書き込んでおきます:
1echo DATABASE_URL=postgres://admin:password@localhost:5432/postgres > .env
次に、以下のコマンドでセットアップができます:
1diesel setup
マイグレーション#
以下のコマンドを実行します:
1diesel migration generate create_table
すると、以下のようにマイグレーション用の SQL ファイルが作成されます:
1$ diesel migration generate create_table
2Creating migrations/2021-12-24-150710_create_table/up.sql
3Creating migrations/2021-12-24-150710_create_table/down.sql
up.sql
を以下の内容にします:
id
はSERIAL
を指定し、自動で採番されるようにします。
1CREATE TABLE posts (
2 id SERIAL PRIMARY KEY,
3 title TEXT NOT NULL,
4 body TEXT NOT NULL,
5 is_published BOOLEAN NOT NULL DEFAULT false,
6 create_time TIMESTAMP NOT NULL DEFAULT current_timestamp,
7 update_time TIMESTAMP NOT NULL DEFAULT current_timestamp
8);
また、down.sql
を以下のようにします:
1DROP TABLE posts;
この状態で以下のコマンド
1diesel migration run
を実行すると、up.sql
の内容が実行され、DB にテーブルが作成されます。
また、src/schema.rs
が以下の内容で作成されます。
1table! {
2 posts (id) {
3 id -> Int4,
4 title -> Text,
5 body -> Text,
6 is_published -> Bool,
7 create_time -> Timestamp,
8 update_time -> Timestamp,
9 }
10}
マイグレーションをロールバックしたい場合は以下のコマンドを実行します:
1diesel mifration revert
このとき、 down.sql
が実行され、テーブルも無事に消え、schema.rs
の内容も消えます。
CRUD 操作#
Rust で CRUD 操作を記述します。
DB の接続#
まずは、 DB とのコネクションを確立するためのメソッドを以下の src/utils.rs
のように記述します:
1use diesel::prelude::*;
2use diesel::pg::PgConnection;
3use dotenv::dotenv;
4use std::env;
5
6pub fn establish_connection() -> PgConnection {
7 dotenv().ok();
8 let database_url = env::var("DATABASE_URL")
9 .expect("DATABASE_URL must be set");
10 PgConnection::establish(&database_url)
11 .expect(&format!("Error connecting to {}", database_url))
12}
モデルの定義#
モデルの構造体を以下のように定義します(models.rs
)。
SELECT
文で取得してきたものを入れる構造体にはQueryable
を付与します。UPDATE
文でテーブルに要素を追加するものにはInsertable
を付与します。table_name="posts"
も指定します。
1use chrono::{DateTime, Utc};
2use crate::schema::posts;
3
4#[derive(Debug, Queryable)]
5pub struct Post {
6 pub id: i32,
7 pub title: String,
8 pub body: String,
9 pub is_published: bool,
10 pub create_time: DateTime<Utc>,
11 pub update_time: DateTime<Utc>,
12}
13
14#[derive(Insertable)]
15#[table_name="posts"]
16pub struct NewPost<'a> {
17 pub title: &'a str,
18 pub body: &'a str,
19}
単一の要素を登録する#
登録するときは以下のように書きます。
1pub fn create_post<'a>(conn: &PgConnection, id: i32, title: &'a str, body: &'a str) -> Post {
2 use crate::schema::posts;
3 let new_post = NewPost {
4 id,
5 title,
6 body,
7 };
8
9 diesel::insert_into(posts::table)
10 .values(&new_post)
11 .get_result(conn)
12 .expect("Error saving new post")
13}
要素を取得する#
こんな感じで書けます。filter
を使用して条件の絞り込み、 limit
で取得上限数を指定します。
1pub fn show_posts(conn: &PgConnection) -> Vec<Post> {
2 use crate::schema::posts::dsl::*;
3 posts.filter(is_published.eq(true))
4 .limit(5)
5 .load::<Post>(conn)
6 .expect("Error loading posts")
7}
要素を更新する#
update
を用いて更新します。引数に更新対象を指定し、set
で更新内容を記述します。
1pub fn publish_post(conn: &PgConnection, id: i32) -> Post {
2 use crate::schema::posts::dsl::{posts, is_published};
3 diesel::update(posts.find(id))
4 .set(is_published.eq(true))
5 .get_result::<Post>(conn)
6 .expect(&format!("Unable to find post {}", id))
7}
要素を削除する#
ここでは LIKE
句でパターンを指定して削除を行う処理を記述します。
1pub fn delete_post(conn: &PgConnection, target: &str) -> usize {
2 use crate::schema::posts::dsl::*;
3 let pattern = format!("%{}%", target);
4 diesel::delete(posts.filter(title.like(pattern)))
5 .execute(conn)
6 .expect("Error deleting posts")
7}