Rust/ActixWeb で データベース(SeaORM 利用)の準備
IT技術
はじめに
Rust のお勉強、前々回/前回の続きです。
今回はデータベースの準備がメインとなります。
今回の動作環境
毎度の前回からの動作環境変更点。
バージョン(括弧内は前回) | |
Rust | 1.81(1.78) |
actix-web | 4.9.0(4.5.1) |
データベース接続
言わずもがな。
少々乱暴な解釈かもですが、アプリ/API は総じてデータベースのラッパーで、なので CRUD 操作してなんぼなんですよね。
actix の公式サイトでは Diesel というクレートを利用して DB 操作例が記載されてました。
なのでそれ使ってみよー、と思ったですが下記一文(英語)が目に止まりまして.
The current versions of Diesel (v1/v2) does not support asynchronous operations, so it is important to use the web::block function to offload your database operations to the Actix runtime thread-pool.
抜き出してみる。
Diesel does not support asynchronous.
訳してみる。
Diesel は非同期操作をサポートしていない。
oh.
actix、非同期処理が売りな web フレームワークなのに。
まぁはい。切り替えて非同期処理をサポートしてる DB ライブラリ(クレート)を探してみる。
非同期処理をサポートしてる DB ライブラリ(クレート)
調べましたら以下の 2 つがよく利用されている非同期対応の DB ライブラリ のようでした。
sqlx |
|
SeaORM |
|
個人的に ORM は苦手でして。生成/実行される SQL の確認が面倒ですし、N+1 対応とか頭使うしなので。
ですが今回は SeaORM を使ってみることにしました。
決め手はエンティティの自動生成です(自動生成してくれるみたいです)。
今回作る CRUD 処理の概要と準備
何でも良いのですが、オラクルでお馴染みの dept/emp テーブル を作成し、それの CRUD 処理を書いていきます。
理由は特にないです。強いて言えば懐かしさ?
その他使用する RDBMS などまとめ。
- RDBMS
- postgres(16.4)
- テーブル
- 2テーブル
- dept(部署)
- emp(社員)
- リレーション
- dept 1 : N emp
- 2テーブル
- API
- deptのCRUD処理
- empのCRUD処理
また準備として SeaORM のクレートを追加します。
使用する RDBMS、非同期ランタイムによって features に指定する値が異なるようです。
( ドキュメント を参考に設定)
1cargo add sea-orm --features "sqlx-postgres, runtime-tokio-native-tls macros"
準備完了。ではやっていきます。
マイグレーション定義
まずはテーブルを作成します。マイグレーション 機能があるので利用します。
バイナリパッケージが必要とのことでインストール。
1cargo install sea-orm-cli
2
3# 確認
4which sea-orm-cli
5/usr/local/cargo/bin/sea-orm-cli
6
7see-orm-cli --version
8sea-orm-cli 1.0.1
続いて初期化。
1sea-orm-cli migrate init
2
3# 結果
4Initializing migration directory...
5Creating file `./migration/src/lib.rs`
6Creating file `./migration/src/m20220101_000001_create_table.rs`
7Creating file `./migration/src/main.rs`
8Creating file `./migration/Cargo.toml`
9Creating file `./migration/README.md`
10Done!
自動生成された Cargo.toml(./migration/Cargo.toml) に下記を追加する。
1[dependencies.sea-orm-migration]
2version = "1.0.0"
3features = [
4 # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI.
5 # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime.
6 # e.g.
7 # "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature
8 # "sqlx-postgres", # `DATABASE_DRIVER` feature
9 "runtime-tokio-native-tls", # ココと
10 "sqlx-postgres", # ココ追加
11]
自動生成されたマイグレーションファイルはサンプルなので、今回作成するテーブル用のファイルを作成します。
1### deptテーブル
2sea-orm-cli migrate generate dept_table
3
4# 結果
5Generating new migration...
6Creating migration file `./migration/src/m20240830_034748_dept_table.rs`
7Adding migration `m20240830_034748_dept_table` to `./migration/src/lib.rs`
8
9
10### empテーブル
11sea-orm-cli migrate generate emp_table
12
13Generating new migration...
14Creating migration file `./migration/src/m20240830_034805_emp_table.rs`
15Adding migration `m20240830_034805_emp_table` to `./migration/src/lib.rs`
マイグレーションファイルに定義を記載する。
その前に最初に自動生成された m20220101_000001_create_table.rs は削除します。
また、./migration/src/lib.rs に定義された m20220101_000001_create_table の記載を削除します。
これらのせいでマイグレーション実行時エラーになりまして。邪魔なの。
dept
1#[async_trait::async_trait]
2impl MigrationTrait for Migration {
3 async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
4 // Replace the sample below with your own migration scripts
5 // todo!();
6
7 manager
8 .create_table(
9 Table::create()
10 .table(Dept::Table)
11 .if_not_exists()
12 .col(ColumnDef::new(Dept::Deptno).integer().primary_key())
13 .col(ColumnDef::new(Dept::Dname).string().not_null())
14 .col(ColumnDef::new(Dept::Loc).string().not_null())
15 .to_owned(),
16 )
17 .await
18 }
19
20 async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
21 // Replace the sample below with your own migration scripts
22 // todo!();
23
24 manager
25 .drop_table(Table::drop().table(Dept::Table).to_owned())
26 .await
27 }
28}
29
30#[derive(DeriveIden)]
31pub enum Dept {
32 Table,
33 Deptno,
34 Dname,
35 Loc,
36}
emp
1#[async_trait::async_trait]
2impl MigrationTrait for Migration {
3 async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
4 // Replace the sample below with your own migration scripts
5 // todo!();
6
7 manager
8 .create_table(
9 Table::create()
10 .table(Dept::Table)
11 .if_not_exists()
12 .col(ColumnDef::new(Dept::Deptno).integer().primary_key())
13 .col(ColumnDef::new(Dept::Dname).string().not_null())
14 .col(ColumnDef::new(Dept::Loc).string().not_null())
15 .to_owned(),
16 )
17 .await
18 }
19
20 async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
21 // Replace the sample below with your own migration scripts
22 // todo!();
23
24 manager
25 .drop_table(Table::drop().table(Dept::Table).to_owned())
26 .await
27 }
28}
29
30#[derive(DeriveIden)]
31pub enum Dept {
32 Table,
33 Deptno,
34 Dname,
35 Loc,
36}
enum に項目を定義し、それを利用して項目の型や制約の定義をするようです。
では実行します。
マイグレーション実行
1sea-orm-cli migrate -u postgres://(接続文字列)
2
3# 結果
4Applying all pending migrations
5Applying migration 'm20240830_034748_dept_table'
6Migration 'm20240830_034748_dept_table' has been applied
7Applying migration 'm20240830_034805_emp_table'
8Migration 'm20240830_034805_emp_table' has been applied
実行終了。きちんと作成されたか確認します。
データベース確認(psql コマンド)
1 # \dt;
2 List of relations
3 Schema | Name | Type | Owner
4--------+------------------+-------+--------
5 public | dept | table | xxuser
6 public | emp | table | xxuser
7 public | seaql_migrations | table | xxuser
8(3 rows)
できる子。seaql_migrations はバージョン管理ですね。
続いてテーブル定義確認。
1# \d dept;
2 Table "public.dept"
3 Column | Type | Collation | Nullable | Default
4--------+-------------------+-----------+----------+---------
5 deptno | integer | | not null |
6 dname | character varying | | not null |
7 loc | character varying | | not null |
8Indexes:
9 "dept_pkey" PRIMARY KEY, btree (deptno)
10Referenced by:
11 TABLE "emp" CONSTRAINT "fk_deptno" FOREIGN KEY (deptno) REFERENCES dept(deptno) ON UPDATE CASCADE ON DELETE CASCADE
12
13
14# \d emp;
15 Table "public.emp"
16 Column | Type | Collation | Nullable | Default
17----------+-----------------------------+-----------+----------+---------
18 empno | integer | | not null |
19 ename | character varying | | |
20 job | character varying | | |
21 mgr | integer | | |
22 hiredate | timestamp without time zone | | |
23 sal | integer | | |
24 comm | integer | | |
25 deptno | integer | | |
26Indexes:
27 "emp_pkey" PRIMARY KEY, btree (empno)
28Foreign-key constraints:
29 "fk_deptno" FOREIGN KEY (deptno) REFERENCES dept(deptno) ON UPDATE CASCADE ON DELETE CASCADE
うーん、何か一部データ型が思ってたのと違う。キー値にシーケンス設定されてない。emp テーブルの項目に not null 制約定義忘れてる。
さささっとやりすぎました、反省。
もっとよく調べて定義してみます。
データ型の定義メソッドは こちら を参照したら大分見当をつけることができました。
この手の情報はドキュメントに詳細にまとめておいて欲しいですよね、と思いました。
(とか言っといて実はドキュメントがあったケースは多々ありまして。すみません。先に謝ってみる)
修正後
dept
1 manager
2 .create_table(
3 Table::create()
4 .table(Dept::Table)
5 .if_not_exists()
6 .col(
7 ColumnDef::new(Dept::Deptno)
8 .integer()
9 .auto_increment()
10 .primary_key(),
11 )
12 .col(ColumnDef::new(Dept::Dname).string_len(14).not_null())
13 .col(ColumnDef::new(Dept::Loc).string_len(13).not_null())
14 .to_owned(),
15 )
16 .await
emp
1 manager
2 .create_table(
3 Table::create()
4 .table(Emp::Table)
5 .if_not_exists()
6 .col(
7 ColumnDef::new(Emp::Empno)
8 .integer()
9 .auto_increment()
10 .primary_key(),
11 )
12 .col(ColumnDef::new(Emp::Ename).string_len(10).not_null())
13 .col(ColumnDef::new(Emp::Job).string_len(9).not_null())
14 .col(ColumnDef::new(Emp::Mgr).integer())
15 .col(ColumnDef::new(Emp::Hiredate).date_time().not_null())
16 .col(ColumnDef::new(Emp::Sal).decimal_len(7, 2).not_null())
17 .col(ColumnDef::new(Emp::Comm).decimal_len(7, 2))
18 .col(ColumnDef::new(Emp::Deptno).integer().not_null())
19 .foreign_key(
20 ForeignKey::create()
21 .name("fk_deptno")
22 .from(Emp::Table, Emp::Deptno)
23 .to(Dept::Table, Dept::Deptno)
24 .on_delete(ForeignKeyAction::Cascade)
25 .on_update(ForeignKeyAction::Cascade),
26 )
27 .to_owned(),
28 )
29 .await
マイグレーション再実行(リフレッシュ)する。
1sea-orm-cli migrate refresh -u postgres://(接続文字列)
2
3# 結果
4Rolling back all applied migrations
5Rolling back migration 'm20240830_034805_emp_table'
6Migration 'm20240830_034805_emp_table' has been rollbacked
7Rolling back migration 'm20240830_034748_dept_table'
8Migration 'm20240830_034748_dept_table' has been rollbacked
9Applying all pending migrations
10Applying migration 'm20240830_034748_dept_table'
11Migration 'm20240830_034748_dept_table' has been applied
12Applying migration 'm20240830_034805_emp_table'
13Migration 'm20240830_034805_emp_table' has been applied
テーブル定義はどうだろう?
1# \d dept;
2 Table "public.dept"
3 Column | Type | Collation | Nullable | Default
4--------+-----------------------+-----------+----------+--------------------------------------
5 deptno | integer | | not null | nextval('dept_deptno_seq'::regclass)
6 dname | character varying(14) | | not null |
7 loc | character varying(13) | | not null |
8Indexes:
9 "dept_pkey" PRIMARY KEY, btree (deptno)
10Referenced by:
11 TABLE "emp" CONSTRAINT "fk_deptno" FOREIGN KEY (deptno) REFERENCES dept(deptno) ON UPDATE CASCADE ON DELETE CASCADE
12
13# \d emp;
14 Table "public.emp"
15 Column | Type | Collation | Nullable | Default
16----------+-----------------------------+-----------+----------+------------------------------------
17 empno | integer | | not null | nextval('emp_empno_seq'::regclass)
18 ename | character varying(10) | | not null |
19 job | character varying(9) | | not null |
20 mgr | integer | | |
21 hiredate | timestamp without time zone | | not null |
22 sal | numeric(7,2) | | not null |
23 comm | numeric(7,2) | | |
24 deptno | integer | | not null |
25Indexes:
26 "emp_pkey" PRIMARY KEY, btree (empno)
27Foreign-key constraints:
28 "fk_deptno" FOREIGN KEY (deptno) REFERENCES dept(deptno) ON UPDATE CASCADE ON DELETE CASCADE
良い感じ!
ドキュメント 読んでましたら DDL 文の直定義もできるようでした。
なので例えば postgres で PostGIS を使う場合の拡張型を DDL 直記載するで対応できますね。
エンティティ作成
テーブルできましたので、エンティティを生成してみます。
src ディレクトリ配下に entities ディレクトリを作成し、その中にエンティティを配置するよう指定します。
下記コマンドで作成したテーブルから生成させることができるみたいです。便利。
1sea-orm-cli generate entity \
2 -u postgres://(接続文字列) \
3 -o src/entities
4
5# 結果
6Connecting to Postgres ...
7Discovering schema ...
8... discovered.
9Generating dept.rs
10 > Column `deptno`: i32, auto_increment, not_null
11 > Column `dname`: String, not_null
12 > Column `loc`: String, not_null
13Generating emp.rs
14 > Column `empno`: i32, auto_increment, not_null
15 > Column `ename`: String, not_null
16 > Column `job`: String, not_null
17 > Column `mgr`: Option
18 > Column `hiredate`: DateTime, not_null
19 > Column `sal`: Decimal, not_null
20 > Column `comm`: Option
21 > Column `deptno`: i32, not_null
22Writing src/entities/dept.rs
23Writing src/entities/emp.rs
24Writing src/entities/mod.rs
25Writing src/entities/prelude.rs
26... Done.
瞬で実行終わりました。
1% tree src/entities
2src/entities
3├── dept.rs
4├── emp.rs
5├── mod.rs
6└── prelude.rs
できてる。
生成したエンティティの中身。
dept
1//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
2
3use sea_orm::entity::prelude::*;
4
5#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
6#[sea_orm(table_name = "dept")]
7pub struct Model {
8 #[sea_orm(primary_key)]
9 pub deptno: i32,
10 pub dname: String,
11 pub loc: String,
12}
13
14#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
15pub enum Relation {
16 #[sea_orm(has_many = "super::emp::Entity")]
17 Emp,
18}
19
20impl Related for Entity {
21 fn to() -> RelationDef {
22 Relation::Emp.def()
23 }
24}
25
26impl ActiveModelBehavior for ActiveModel {}
emp
1//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
2
3use sea_orm::entity::prelude::*;
4
5#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
6#[sea_orm(table_name = "dept")]
7pub struct Model {
8 #[sea_orm(primary_key)]
9 pub deptno: i32,
10 pub dname: String,
11 pub loc: String,
12}
13
14#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
15pub enum Relation {
16 #[sea_orm(has_many = "super::emp::Entity")]
17 Emp,
18}
19
20impl Related for Entity {
21 fn to() -> RelationDef {
22 Relation::Emp.def()
23 }
24}
25
26impl ActiveModelBehavior for ActiveModel {}
素晴らしいですね。この自動生成されたエンティティを利用して DB とやりとりするのでしょうね。多分。
さ CRUD 処理しますか。
と思ったですが、マイグレーションだけで長くなっちゃいました。
疲れました。
CRUD 処理については次回書きますね。
まとめ
Rust の DB 関連ライブラリは非同期処理対応かを確認すること。
しかし今回はほぼコード書かなかったですね。
最近多いですよね自動生成。
いや結構前からあるですね自動生成。
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
おすすめ記事
immichを知ってほしい
2024.10.31