$30 off During Our Annual Pro Sale. View Details »

Rust SQLライブラリを比較する

Rust SQLライブラリを比較する

RustのSQLライブラリにはデファクト・スタンダードはありません。
最も有名とされるのはDieselですが、非同期処理に非対応などのデメリットからSeaORM、sqlxなども注目されています(sqlxはORMではありません)。
今回はSeaORM、sqlxの2つのライブラリの使い方や書き方を比較してみたいと思います。サンプルコードをシェアしながら、webアプリケーション開発に必要な要素の使い勝手を比較を行います。

More Decks by PharmaX(旧YOJO Technologies)開発チーム

Other Decks in Technology

Transcript

  1. Rust SQL ライブラリの比較検討 1 Rust SQL ライブラリの比較検討 目的 Rust のSQL

    ライブラリを比較し、ユースケースや場面ごとにどれを使うのがよいのかを検討する軸を学ぶ 比較するライブラリ 各ライブラリの説明 sqlx sqlx はシンプルなSQL ライブラリです。sqlx はOR マッパーでもクエリビルダーでも なく、SQL のコンパイル時のチェックやマイグレーションのみを行い、非同期処理 に対応しています。実装者がSQL を書かないといけないので、OR マッパーにある ようなメリットは享受できませんが、diesel のDSL や多くのメソッドの理解は必要 ないというメリットがあります。 『Web アプリ開発で学ぶRust 言語入門』p.167 非同期処理をサポートしており、tokio などの非同期ランタイムを選択可能 PostgresSQL 、MySQL 、SQLite 、SQLServer などの多数のRDBMS をサポートする async fn find(&self, id: i32) -> anyhow::Result<Product> { let product_table = sqlx::query_as::<_, ProductTable>( r#" select * from sqlx.product where id = $1 "#, ) .bind(id) .fetch_one(&self.pool) .await .map_err(|e| match e { sqlx::Error::RowNotFound => RepositoryError::NotFound(id), _ => RepositoryError::Unexpected(e.to_string()), })?; Ok(Product::from(product_table)) } SeaORM SeaORM は最初から非同期処理をサポートしたORM として開発されたもので、 tokio やaync_std 、actix などの非同期ランタイムを選択して使用できます。SQL 実行 基盤には、(中略)SQL 実行クレートのsqlx を利用しています。使用するデータベ
  2. Rust SQL ライブラリの比較検討 2 ース環境を構築する機能や必要な構造体や列挙型を生成するためのCLI が利用でき ます。 パーフェクトRust p.662 実行基盤にはsqlx

    を使っている 非同期処理をサポートしており、tokio などの主要非同期ランタイムを選択可能 sqlx 同様、多数のRDBMS テーブルの値をマッピングしたModel と呼ばれる構造体を生成してくれる アーキテクチャの文脈では、sqlx はrepository 層に自分で〇〇Table みたいなtable と対応するStruct を 用意していたが、SeaORM はmodel を生成してくれる(後ろで詳述) // find all models let cakes: Vec<cake::Model> = Cake::find().all(db).await?; // find and filter let chocolate: Vec<cake::Model> = Cake::find() .filter(cake::Column::Name.contains("chocolate")) .all(db) .await?; // find one model let cheese: Option<cake::Model> = Cake::find_by_id(1).one(db).await?; let cheese: cake::Model = cheese.unwrap(); // find related models (lazy) let fruits: Vec<fruit::Model> = cheese.find_related(Fruit).all(db).await?; // find related models (eager) let cake_with_fruits: Vec<(cake::Model, Vec<fruit::Model>)> = Cake::find().find_with_related(Fruit).all(db).await?; diesel diesel はRust のOR マッパーかつクエリビルダーです。OR マッパーについては、 (中略)インピーダンスミスマッチと呼ばれるデータベースのレコードとプログラ ミングで扱うデータの構造上の違いによる複雑性を解消するために、データベース のレコードをプログラミング側のオブジェクト(クラスやRust の構造体)にマッピ
  3. Rust SQL ライブラリの比較検討 3 ングしてプログラミング言語側から直感的に操作できるようにすることです。 (中略) diesel はOR マッパーとしての側面に加え、クエリビルダーを名乗っているとおり、 オブジェクトを通じてクエリを組み立てるようにして利用します。

    (中略) diesel は非常に便利で、Rust のSQL ライブラリとしては人気があり、有力な選択肢 です。一方、OR マッパー+クエリビルダーを名乗っている分、Ruby on Rails など のOR マッパーと比べてSQL への意識が必要(マイグレーションは実際SQL で書く 必要があります)だったり、多機能ゆえに複雑だったり、async に非対応などのデ メリットも存在します。 『Web アプリ開発で学ぶRust 言語入門』p.166 use self::models::*; use diesel::prelude::*; use diesel_demo::*; fn main() { use self::schema::posts::dsl::*; let connection = &mut establish_connection(); let results = posts .filter(published.eq(true)) .limit(5) .select(Post::as_select()) .load(connection) .expect("Error loading posts"); println!("Displaying {} posts", results.len()); for post in results { println!("{}", post.title); println!("-----------\n"); println!("{}", post.body); } } レポジトリ https://github.com/akiueno/rust-db-libs-samples 今回扱うテーブル構造 パーフェクトRust の例に従う
  4. Rust SQL ライブラリの比較検討 4 同じテーブル名で以下のようにスキーマで区別する 例えば、sqlx のマイグレーションファイルは下記のようになる CREATE TABLE sqlx.product_category

    { id BIGINT AUTO_INCREMENT NOT null, name varchar(20) not null default '', CONSTRAINT product_category_pk PRIMARY KEY (id) }; スキーマ名はsqlx, seaorm, diesel のようにして区別する postgres=# \dt sqlx.* List of relations Schema | Name | Type | Owner --------+------------------+-------+---------- sqlx | product | table | postgres sqlx | product_category | table | postgres postgres=# \d sqlx.product Table "sqlx.product" Column | Type | Collation | Nullable | Default -------------+-----------------------+-----------+----------+------------------------------------------ id | bigint | | not null | nextval('sqlx.product_id_seq'::regclass) name | character varying(20) | | not null | ''::character varying price | integer | | not null | 0 category_id | bigint | | not null | Indexes: "product_pk" PRIMARY KEY, btree (id) Foreign-key constraints: "product_category_fk" FOREIGN KEY (category_id) REFERENCES sqlx.product_category(id) 上記のようにschema ごとにテーブルの表示などをすることができる 各ライブラリの使い方 sqlx CLI ツールは下記のようにインストールする
  5. Rust SQL ライブラリの比較検討 5 cargo install sqlx-cli migration -r フラグを付けるとリバート用のmigration

    ファイルもできる $ sqlx migrate add -r <name> Creating migrations/20231001154420_<name>.up.sql Creating migrations/20231001154420_<name>.down.sql 普通のmigration ファイルとreversible なファイルは混ぜられない cannot mix reversible migrations with simple migrations. All migrations should be reversible or simple migrations migration ファイル自体はSQL を直接書く必要がある
  6. Rust SQL ライブラリの比較検討 6 query テーブルをバインドするテーブル定義は自分で書かなければならない query_as はジェネリクスの第二引数で受け取った構造体にレコードをバインドして戻り値にすることができ る。 $1

    ($2, $3 )で変数化したところにbind で値を埋めていくことができる seaORM Runtime についてはこちら Database & Async Runtime | SeaORM 🐚 An async & dynamic ORM for Rust First, add sea-orm to the [dependencies] section of your Cargo.toml. https://www.sea-ql.org/SeaORM/docs/0.4.x/install-and-config/database-and-async-runtim e/
  7. Rust SQL ライブラリの比較検討 7 CLI ツールは下記のようにインストールする cargo install sea-orm-cli migration

    CLI で簡単にレコードを扱うための構造体やテーブル同士の関係を表す列挙型を生成してくれる migration 用のフォルダを作成するには、下記のコマンドを実行する sea-orm-cli migrate init -d <name> -d <name> でフォルダ名を指定する フォルダ名を指定してmigration を実行するには下記のようにする sea-orm-cli migrate up -d <name> sea-orm-cli migrate down -d <name> マイグレーション用のフォルダが生成される 下記のようにテーブルの構造を指定していく
  8. Rust SQL ライブラリの比較検討 9 下記のコマンドを実行してマイグレーションを適用する sea-orm-cli migrate up -s seaorm

    -d seaorm-migration model (entity )の作成 model を生成するコマンドを実行する
  9. Rust SQL ライブラリの比較検討 10 sea-orm-cli generate entity -u postgres://docker:password@postgres:5432/postgres -s

    seaorm -o src/models --with-serde both スキーマは-s で指定する 下記のようにスキーマが自動生成され、relation まで
  10. Rust SQL ライブラリの比較検討 11 query Active レコードのようにテーブルに対応するモデル毎にメソッドが自動で提供される デフォルトでは自分で作ったProduct モデルと衝突するので、下記のようにモデル名を変更した その他

    cargo make cargo-make によるプロジェクト・ビルド入門 - Crieit cargo-make によるプロジェクト・ビルド モチベーション cargo はrust のパッケージ管理ツール 兼ビルドツールである. これい自体非常に便利なのだが, Web 標準は無視できない. 特にSeed のよ うなWeb フロントエンド・... https://crieit.net/posts/cargo-make Makefile.toml にコマンドを記載していく migration などのコマンドをまとめて置くと便利 [tasks.migrate_run] command = "sqlx" args = ["migrate", "run", "--database-url", "${DATABASE_URL}"] という記述があった場合 cargo make migrate_run