SeaORM でテーブル作成とデータ操作

githubSeaORM という Rust 用の ORM を見つけたので軽く試してみました。

sea-orm-cli というツールを使うと、既存のテーブルから Entity 定義を自動生成してくれるようですが、ここでは自前で定義した Entity を基にテーブル作成とデータ操作(INSERT, SELECT)を実施してみました。

今回のソースは こちら

1. はじめに

Cargo.toml へ依存定義を設定します。

sea-orm の features で DB のドライバーと非同期ランタイムを指定する事になります。

今回は MySQL/MariaDB へ接続するため DB ドライバーは sqlx-mysql、 非同期ランタイムは async-std で TLS の Rust 実装を用いる事にしたので runtime-async-std-rustls としています。

Cargo.toml
・・・
[dependencies]
sea-orm = { version = "0.8", features = ["sqlx-mysql", "runtime-async-std-rustls", "macros" ] }
async-std = { version = "1", features = ["attributes"] }

2. Entity 定義

ここでは、以下のようなテーブル内容を想定した Entity 定義を行います。

CREATE TABLE tasks (
  id int(10) unsigned NOT NULL AUTO_INCREMENT,
  subject varchar(255) NOT NULL,
  status enum('ready','completed') NOT NULL,
  PRIMARY KEY (id)
)

Entity 定義は DeriveEntityModel を derive した Model という名の struct を定義すれば良さそうです。

そうすると、DeriveRelation を derive した Relation enumimpl ActiveModelBehavior for ActiveModel の定義が必要となりますが、今回は特に使わないので空実装としておきます。

status カラムを DB の enum 型とするため、DeriveActiveEnum を derive した Status enum を別途定義しています。

あとは、sea_orm でテーブル名やプライマリキー、status カラムの enum 値(DB 側)等の指定を行っています。

task.rs
use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "tasks")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: u32,
    pub subject: String,
    pub status: Status,
}

#[derive(Clone, Debug, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "status")]
pub enum Status {
    #[sea_orm(string_value = "ready")]
    Ready,
    #[sea_orm(string_value = "completed")]
    Completed,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

impl ActiveModelBehavior for ActiveModel {}

3. 処理の実装

DB 接続

Database::connect へ DB の接続文字列 mysql://user:password@host/db を渡す事で DB へ接続します。

ここでは、環境変数から DB の接続文字列を取得するようにしてみました。

let db_uri = env::var("DB_URI")?;
let db = Database::connect(db_uri).await?;

テーブル作成

create_table_from_entity で Entity 定義から Create Table 文を作成できます。

get_database_backend で取得したバックエンドの build を用いる事で、 接続する DB(今回は MySQL/MariaDB)用の Create Table 文を取得できるので execute でテーブル作成を実施します。

let backend = db.get_database_backend();
let schema = Schema::new(backend);

let st = backend.build(&schema.create_table_from_entity(Task));
db.execute(st).await?;

INSERT

INSERT する Entity データの生成に ActiveModel を使い、値を ActiveValue で設定します。

id は自動採番を使うため NotSet としています。(明示的に設定しても可)

ActiveModel の insert を呼び出す事で INSERT を実施します。

let t1 = task::ActiveModel {
    id: ActiveValue::NotSet,
    subject: ActiveValue::Set("task1".to_owned()),
    status: ActiveValue::Set(task::Status::Ready),
};

let r1 = t1.insert(&db).await?;
println!("{:?}", r1);

なお、serde_jsonActiveModel::from_json を使う事で、JSON から生成する事も可能でした。

SELECT

find() で SELECT を実施します。 all を使う事で対象となる全レコードを取得できるようです。

let rows = Task::find().all(&db).await?;
println!("{:?}", rows);

上記処理を合わせた、最終的なコードは以下のようになりました。

main.rs
mod task;

use sea_orm::*;
use std::env;

use task::Entity as Task;

type Error = Box<dyn std::error::Error>;

#[async_std::main]
async fn main() -> Result<(), Error> {
    let db_uri = env::var("DB_URI")?;
    let db = Database::connect(db_uri).await?;

    let backend = db.get_database_backend();
    let schema = Schema::new(backend);

    let st = backend.build(&schema.create_table_from_entity(Task));
    db.execute(st).await?;

    let t1 = task::ActiveModel {
        id: ActiveValue::NotSet,
        subject: ActiveValue::Set("task1".to_owned()),
        status: ActiveValue::Set(task::Status::Ready),
    };

    let r1 = t1.insert(&db).await?;
    println!("{:?}", r1);

    let t2 = task::ActiveModel {
        id: ActiveValue::NotSet,
        subject: ActiveValue::Set("task2".to_owned()),
        status: ActiveValue::Set(task::Status::Completed),
    };

    let r2 = t2.insert(&db).await?;
    println!("{:?}", r2);

    let rows = Task::find().all(&db).await?;
    println!("{:?}", rows);

    Ok(())
}

4. 動作確認

MariaDB へ DB を作成しておきます。

DB 作成
MariaDB [(none)]> CREATE DATABASE sample1;

環境変数へ DB 接続文字列を設定し、実行します。

実行
> set DB_URI=mysql://root:@localhost/sample1
> cargo run
・・・
Model { id: 1, subject: "task1", status: Ready }
Model { id: 2, subject: "task2", status: Completed }
[Model { id: 1, subject: "task1", status: Ready }, Model { id: 2, subject: "task2", status: Completed }]

正常に実行できました。

ついでに、テーブルとレコード内容を確認してみると以下のようになりました。

テーブルとレコード内容
MariaDB [sample1]> SHOW CREATE TABLE tasks \G
*************************** 1. row ***************************
       Table: tasks
Create Table: CREATE TABLE `tasks` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `subject` varchar(255) NOT NULL,
  `status` enum('ready','completed') NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1
1 row in set (0.000 sec)

MariaDB [sample1]> select * from tasks;
+----+---------+-----------+
| id | subject | status    |
+----+---------+-----------+
|  1 | task1   | ready     |
|  2 | task2   | completed |
+----+---------+-----------+
2 rows in set (0.001 sec)