RustのSeaORMというORMを使ってSQLiteにレコード追加したりしてみる
軽く調べてみたところdieselが安パイ的な記事を見かけたものの若干情報が古い&非同期処理対応してないっぽいので今回は見送った
クレートのインストール
Cargo.tomlを編集する
SQLiteを使う場合は↓のようになる(詳細はこちらのドキュメントを参照)
[dependencies] sea-orm = { version = "^0", features = [ "sqlx-sqlite", "runtime-tokio-native-tls", "macros" ] } tokio = {version = "1.27.0", features = ["full"]} # 必須じゃないけど非同期処理使いたいので入れておく
DBにコネクションを貼る
SeaORMを使ってSQLiteのDBにコネクションを貼ってみる
use sea_orm::Database; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let db = Database::connect("sqlite://sample.db?mode=rwc").await?; Ok(()) }
sqlite://sample.db?mode=rwc
でSQLiteのDBファイルのパスとモードを指定している
mode=rwc
というクエリパラメータでレコードの読み書きを許可し、さらにsample.db
ファイルが存在しない場合は新規作成することを表している
※このURLの仕様についてはこちらを参照
Entityとマッピング
ORM特有のDBのレコードとプログラムのオブジェクトを結びつける儀式を行う
Entityの実装
src/entities/users.rs
にusers
テーブルの定義を書いてみよう
use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "users")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, pub age: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {}
そうしたら、main関数でDBにusersテーブルがなければsrc/entities/users.rs
の定義に従って作成するようにする
use sea_orm::*; mod entities; use entities::users::Entity as Users; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let db = Database::connect("sqlite://sample.db?mode=rwc").await?; let backend = db.get_database_backend(); let schema = Schema::new(backend); let statement = backend.build(schema.create_table_from_entity(Users).if_not_exists()); db.execute(statement).await?; Ok(()) }
実装し終わったらcargo run
で実行しsample.db
にusersテーブルができているか確認
※SQLiteのファイルはVSCodeのSQLite Viewerという拡張機能で閲覧している
CRUD操作
Create
レコードの新規作成処理を実装する
use sea_orm::*; mod entities; use entities::users::ActiveModel as UserModel; use entities::users::Entity as Users; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let db = Database::connect("sqlite://sample.db?mode=rwc").await?; let backend = db.get_database_backend(); let schema = Schema::new(backend); let statement = backend.build(schema.create_table_from_entity(Users).if_not_exists()); db.execute(statement).await?; create(&db).await?; Ok(()) } async fn create(db: &DatabaseConnection) -> Result<(), Box<dyn std::error::Error>> { UserModel { name: Set("hoge".to_owned()), age: Set(20), ..Default::default() } .save(db) .await?; Ok(()) }
EntityのActiveModel
にsave()
メソッドが生えているのでこれを使う
上記のコードを実行すると期待通りのレコードが追加されている
Read
今度はレコードを一覧取得する
use entities::users::Model; use sea_orm::*; mod entities; use entities::users::ActiveModel as UserModel; use entities::users::Entity as Users; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let db = Database::connect("sqlite://sample.db?mode=rwc").await?; let backend = db.get_database_backend(); let schema = Schema::new(backend); let statement = backend.build(schema.create_table_from_entity(Users).if_not_exists()); db.execute(statement).await?; create(&db).await?; let users = find_all(&db).await?; println!("users: {}", users.len()); Ok(()) } async fn create(db: &DatabaseConnection) -> Result<(), Box<dyn std::error::Error>> { UserModel { name: Set("hoge".to_owned()), age: Set(20), ..Default::default() } .save(db) .await?; Ok(()) } async fn find_all(db: &DatabaseConnection) -> Result<Vec<Model>, Box<dyn std::error::Error>> { let users = Users::find().all(db).await?; Ok(users) }
Entity
のfind()
でSELECT文を発行し、さらにall()
を実行することでSELECT * FROM...というSQLが実行されて一覧取得するようになっている
子のコードを実行するとレコード数が標準出力される
WHERE句を使う
何らかの条件付きで検索してみる
今回は↓のようなテーブルからname = hoge
というユーザーだけ取得する
use entities::users::Model; use sea_orm::*; mod entities; use entities::users::ActiveModel as UserModel; use entities::users::Column as UserColumn; use entities::users::Entity as Users; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let db = Database::connect("sqlite://sample.db?mode=rwc").await?; let backend = db.get_database_backend(); let schema = Schema::new(backend); let statement = backend.build(schema.create_table_from_entity(Users).if_not_exists()); db.execute(statement).await?; let user = find_by_name("hoge".to_owned(), &db).await?; println!("found user name: {}", user.unwrap().name); Ok(()) } async fn find_by_name( name: String, db: &DatabaseConnection, ) -> Result<Option<Model>, Box<dyn std::error::Error>> { let user = Users::find() .filter(UserColumn::Name.eq(name)) .one(db) .await?; Ok(user) }
Column
にattributeと各種条件(eqとか)があるのでそれを使ってfilter
を作成する
今回はnameが一致したものを一つだけ取り出す条件
Update
name=hogeのレコードのageを30に更新する処理
async fn update( name: String, age: i32, db: &DatabaseConnection, ) -> Result<(), Box<dyn std::error::Error>> { let user = find_by_name(name, db).await?; let mut user: UserModel = user.unwrap().into(); user.age = Set(age); user.update(db).await?; Ok(()) }
実行するとage=30に更新される
Delete
name=hogeのレコードを削除する
async fn delete_by_name( name: String, db: &DatabaseConnection, ) -> Result<(), Box<dyn std::error::Error>> { let user = find_by_name(name, db).await?.unwrap(); user.delete(db).await?; Ok(()) }
実行するとname=hogeのレコードが消えているはず