###### tags: `Rust` `Diesel` # Rust Diesel ORM 入門 ディーゼル Dieselとは https://diesel.rs/ https://github.com/diesel-rs/diesel Rustのための安全で拡張性のある ORM 兼 クエリビルダー stable rust で動く ## なんでDiesel作ったの? - Runtime Errorを防ぐため 実行時のエラートラッキングのために、多くの時間が失われた。 Diesel開発チームは、これらの実行時エラーを 不正なデータベース操作をコンパイル時にチェックすることで 取り除く試みをしている。 - パフォーマンス Dieselは、ハイレベルなクエリビルダーを提供している。 ユーザーはSQLの構築から開放され、Rustの問題だけ考えれば良い。 ゼロコスト抽象化に着目することで、データベースへの問い合わせ実行と データの読み込みをC並に早くした。 - 生産性と拡張性 ActiveRecordなど、他のORMと異なる点として、 Dieselは抽象化によってデザインされている。 Dieselを使うことで、再利用可能なコードを書くことができ、 SQLを考えずに使える。 ## シンプルなクエリ例 ```rust= // SELECT * FROM users users::table.load(&connection); // SELECT * FROM posts WHERE user_id=1; Post::belonging_to(user).load(&connection) ``` ## 少ないコードのボイラープレート Dieselは、#[derive] アトリビュートを使って コード生成するので、DBのレコードをstructに割り当てるための コードを書く必要がない ```rust= #[derive(Queryable)] pub struct Download { id: i32, version_id: i32, downloads: i32, counted: i32, date: SystemTime, } ``` # Get Started ## 1. 導入 PostgresSQL 向けの解説なので、Postgresをインストールしておく ※ 公式サイトのプロジェクトのクレート名は diesel_demo である ※ プロジェクトをライブラリとして作成しないと動作しない箇所がある `cargo new --lib diesel_memo` ### cargo.toml ```rust= [dependencies] diesel = { version = "1.0.0", features = ["postgres"] } dotenv = "0.9.0" ``` version : リリース版である 1.0.0 を指定 features : "postgres" dotenv : .envと呼ばれる環境変数を管理するためのツールも導入する ### diesel_cli Dieselが提供する、独立したCLIツール 独立したバイナリであり、プロジェクトのコードには直接作用しない cago.toml には追加できないので、直接インストールする ~~`cargo install diesel_cli`~~ 上の方法は、対応しているDBの全てのドライバをインストールする mysqlclient をインストールしていないとエラーになるので、 postgresだけ使うときは以下のコマンドでインストール というか、dieselを feture = ["postgres"] でdependency に記述しているときは下のコマンド (qiitaにもエラーの記事が乗っていた) `cargo install diesel_cli --no-default-features --features postgres` ### 環境変数を .envファイルに記述 Dieselに、どこにデータベースがあるかを教える必要がある。 Dieselは **DATABASE_URL**という環境変数をデフォルトで使用する 環境変数を汚したくないので、 dotenv クレートを使って cargoプロジェクトのルート直下に **.env** ファイルを作って DATABASE_URL環境変数を設定する `echo DATABASE_URL=postgres://username:password@localhost/dbname > .env` ## 2. disel_cliを使ってデータベースをmigration ここまで、 - cargo.tomlに依存を追加 - diesel_cliをインストールした - .envに環境変数を用意した 次に、以下のコマンドを実行 1. `disel setup` - diesel.toml と migration/ ディレクトリが作成される 2. `disel migration generate create_posts` - "日付_時刻_<migration名>" のフォルダができる - up.sql に、 CREATE TABLE 文を書く ```sql= CREATE TABLE posts ( id SERIAL PRIMARY KEY, title VARCHAR NOT NULL, body TEXT NOT NULL, published BOOLEAN NOT NULL DEFAULT 'f' ) ``` - down.sql に、 DROP TABLE 文を書く ```sql= DROP TABLE posts ``` 3. `diesel migration run` - up.sql が実行される。 - src/schema.rs が追加される 4. `diesel migration redo` - down.sql を実行した後 up.sql を実行する ※ SQLite や MySQLのときは、up/down.sql を文法にあわせて 書き直す ここまでできたら、Rustのコード上からPostgresSQLを操作できる ### 豆知識 postgres に url で接続する `psql postgres://{user}:{password}@{ipv4 or domain or localhost}/{dbname}` ## 3. Rustで diesel を使う 今回は、CRUD操作それぞれについて、別バイナリで作成する Rustでは、 main() を含むファイルを プロジェクト内に複数持てる `cargo build --bin <ファイル名>` でビルドするエントリーポイントを 選択してビルドすることができる 実行ファイルになるプログラムは、基本 src/bin に置く ### DB接続(postgres) DB接続は、共通の操作なので lib.rsに書く lib.rs ```rust= #[macro_use] extern crate diesel; extern crate dotenv; use diesel::prelude::*; use diesel::pg::PgConnection; use dotenv::dotenv; use std::env; pub fn establish_connection() -> PgConnection { dotenv().ok(); let database_url = env::var("DATABASE_URL") .expect("DATABASE_URL must be set"); PgConnection::establish(&database_url) .expect(&format!("Error connecting to {}", database_url)) } ``` - dotenv().ok() : 環境変数を .env ファイルに書いたものに合わせる - std::env::var("DATABASE_URL") : 環境変数をResultで取り出す - PgConnection::establish(&database_url) : postgresへ urlで接続する この関数により、PgConnectionオブジェクトを作成できる ### モデル定義 プログラム内で、DBのレコードの値を扱うには structにレコードを割り当てる、またはその逆を行う そのために、 models.rs ファイルにモデルを定義して importして使うことにする main.rs で使う mod は lib.rs に集めることが慣習とされている まず lib.rsを作成 lib.rs ```rust= pub mod schema; pub mod models; // 自動生成 ``` 次に models.rs を作成 ```rust= #[derive(Queryable)] pub struct Post { pub id: i32, pub title: String, pub body: String, pub published: bool, } ``` #[derive(Queryable)] は、struct を レコードに割り当てるための コード生成を行う ### Queryable構造体のルール - 構造体名は、 database名 から s を抜いたもの 例. posts = Post (大文字は小文字になる) - migrationで作成したschemeと、プロパティの順番が同じであること scheme.rsと同じ並びであることが仮定されているため ### table! マクロ scheme.rs に書いてあるマクロ migration を行うたびに、更新される ## CRUD操作を実装する 1. Read 2. Create 3. Update 4. Delete デモでは、 src/bin/ ディレクトリに - show_posts.rs - write_posts.rs - publish_posts.rs - delete_posts.rs を作成し、それぞれにmain()を実装する。 src/bin は src/lib.rs と異なるクレートと判断されるので、 プロジェクトのcrate名を externして使用できるようにする。 # Read src/bin/ ```rust= extern crate diesel_demo; extern crate diesel; use self::diesel_demo::*; use self::models::*; use self::diesel::prelude::*; fn main() { use diesel_demo::schema::posts::dsl::*; let connection = establish_connection(); let results = posts.filter(published.eq(true)) .limit(5) .load::<Post>(&connection) .expect("Error loading posts"); println!("Displaying {} posts", results.len()); for post in results { println!("{}", post.title); println!("----------\n"); println!("{}", post.body); } } ```