Skip to main content

What's new in SeaORM 0.4.0

ยท 4 min read
SeaQL Team
Chris Tsang

๐ŸŽ‰ We are pleased to release SeaORM 0.4.0 today! Here are some feature highlights ๐ŸŒŸ:

Rust Edition 2021#

[#273] Upgrading SeaORM to Rust Edition 2021 ๐Ÿฆ€โค๐Ÿš!

Contributed by:

Carter Snook


[#252] You can now use Rust enums in model where the values are mapped to a database string, integer or native enum. Learn more here.

#[derive(Debug, Clone, PartialEq, DeriveEntityModel)]#[sea_orm(table_name = "active_enum")]pub struct Model {    #[sea_orm(primary_key)]    pub id: i32,    // Use our custom enum in a model    pub category: Option<Category>,    pub color: Option<Color>,    pub tea: Option<Tea>,}
#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]#[sea_orm(rs_type = "String", db_type = "String(Some(1))")]// An enum serialized into database as a string valuepub enum Category {    #[sea_orm(string_value = "B")]    Big,    #[sea_orm(string_value = "S")]    Small,}
#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]#[sea_orm(rs_type = "i32", db_type = "Integer")]// An enum serialized into database as an integer valuepub enum Color {    #[sea_orm(num_value = 0)]    Black,    #[sea_orm(num_value = 1)]    White,}
#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")]// An enum serialized into database as a database native enumpub enum Tea {    #[sea_orm(string_value = "EverydayTea")]    EverydayTea,    #[sea_orm(string_value = "BreakfastTea")]    BreakfastTea,}
Designed by:

Chris Tsang
Contributed by:

Billy Chan

Supports RETURNING Clause on PostgreSQL#

[#183] When performing insert or update operation on ActiveModel against PostgreSQL, RETURNING clause will be used to perform select in a single SQL statement.

// For PostgreSQLcake::ActiveModel {    name: Set("Apple Pie".to_owned()),    ..Default::default()}.insert(&postgres_db).await?;
assert_eq!(    postgres_db.into_transaction_log(),    vec![Transaction::from_sql_and_values(        DbBackend::Postgres,        r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id", "name""#,        vec!["Apple Pie".into()]    )]);
// For MySQL & SQLitecake::ActiveModel {    name: Set("Apple Pie".to_owned()),    ..Default::default()}.insert(&other_db).await?;
assert_eq!(    other_db.into_transaction_log(),    vec![        Transaction::from_sql_and_values(            DbBackend::MySql,            r#"INSERT INTO `cake` (`name`) VALUES (?)"#,            vec!["Apple Pie".into()]        ),        Transaction::from_sql_and_values(            DbBackend::MySql,            r#"SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = ? LIMIT ?"#,            vec![15.into(), 1u64.into()]        )]);
Proposed by:

Marlon Brandรฃo de Sousa
Contributed by:

Billy Chan

Axum Integration Example#

[#297] Added Axum integration example. More examples wanted!

Contributed by:



Our GitHub Sponsor profile is up! If you feel generous, a small donation will be greatly appreciated.

A big shout out to our first sponsors ๐Ÿ˜‡:

Shane Sveller
Zachary Vander Velden
Unnamed Sponsor


SeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust's future.

Here is the roadmap for SeaORM 0.5.x.

What's new in SeaORM 0.3.0

ยท 4 min read
SeaQL Team
Chris Tsang

๐ŸŽ‰ We are pleased to release SeaORM 0.3.0 today! Here are some feature highlights ๐ŸŒŸ:


[#222] Use database transaction to perform atomic operations

Two transaction APIs are provided:

  • closure style. Will be committed on Ok and rollback on Err.

    // <Fn, A, B> -> Result<A, B>db.transaction::<_, _, DbErr>(|txn| {    Box::pin(async move {        bakery::ActiveModel {            name: Set("SeaSide Bakery".to_owned()),            ..Default::default()        }        .save(txn)        .await?;
            bakery::ActiveModel {            name: Set("Top Bakery".to_owned()),            ..Default::default()        }        .save(txn)        .await?;
            Ok(())    })}).await;
  • RAII style. begin the transaction followed by commit or rollback. If txn goes out of scope, it'd automatically rollback.

    let txn = db.begin().await?;
    // do something with txn

Contributed by:

Marco Napetti
Chris Tsang


[#222] Use async stream on any Select for memory efficiency.

let mut stream = Fruit::find().stream(&db).await?;
while let Some(item) = stream.try_next().await? {    let item: fruit::ActiveModel = item.into();    // do something with item}

Contributed by:

Marco Napetti

API for custom logic on save & delete#

[#210] We redefined the trait methods of ActiveModelBehavior. You can now perform custom validation before and after insert, update, save, delete actions. You can abort an action even after it is done, if you are inside a transaction.

impl ActiveModelBehavior for ActiveModel {    // Override default values    fn new() -> Self {        Self {            serial: Set(Uuid::new_v4()),            ..ActiveModelTrait::default()        }    }
    // Triggered before insert / update    fn before_save(self, insert: bool) -> Result<Self, DbErr> {        if self.price.as_ref() <= &0.0 {            Err(DbErr::Custom(format!(                "[before_save] Invalid Price, insert: {}",                insert            )))        } else {            Ok(self)        }    }
    // Triggered after insert / update    fn after_save(self, insert: bool) -> Result<Self, DbErr> {        Ok(self)    }
    // Triggered before delete    fn before_delete(self) -> Result<Self, DbErr> {        Ok(self)    }
    // Triggered after delete    fn after_delete(self) -> Result<Self, DbErr> {        Ok(self)    }}

Contributed by:

Billy Chan

Generate Entity Models That Derive Serialize / Deserialize#

[#237] You can use sea-orm-cli to generate entity models that also derive serde Serialize / Deserialize traits.

//! SeaORM Entity. Generated by sea-orm-codegen 0.3.0
use sea_orm::entity::prelude:: * ;use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]#[sea_orm(table_name = "cake")]pub struct Model {    #[sea_orm(primary_key)]    pub id: i32,    #[sea_orm(column_type = "Text", nullable)]    pub name: Option<String> ,}
// ...

Contributed by:

Tim Eggert

Introduce DeriveIntoActiveModel macro & IntoActiveValue Trait#

[#240] introduced a new derive macro DeriveIntoActiveModel for implementing IntoActiveModel on structs. This is useful when creating your own struct with only partial fields of a model, for example as a form submission in a REST API.

IntoActiveValue trait allows converting Option<T> into ActiveValue<T> with the method into_active_value.

// Define regular model as usual#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]#[sea_orm(table_name = "users")]pub struct Model {    pub id: Uuid,    pub created_at: DateTimeWithTimeZone,    pub updated_at: DateTimeWithTimeZone,    pub email: String,    pub password: String,    pub full_name: Option<String>,    pub phone: Option<String>,}
// Create a new struct with some fields omitted#[derive(DeriveIntoActiveModel)]pub struct NewUser {    // id, created_at and updated_at are omitted from this struct,    // and will always be `ActiveValue::unset`    pub email: String,    pub password: String,    // Full name is usually optional, but it can be required here    pub full_name: String,    // Option implements `IntoActiveValue`, and when `None` will be `unset`    pub phone: Option<String>,}
#[derive(DeriveIntoActiveModel)]pub struct UpdateUser {    // Option<Option<T>> allows for Some(None) to update the column to be NULL    pub phone: Option<Option<String>>,}

Contributed by:

Ari Seyhun


SeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust's future.

Here is the roadmap for SeaORM 0.4.x.

What's new in SeaORM 0.2.4

ยท 2 min read
SeaQL Team
Chris Tsang

๐ŸŽ‰ We are pleased to release SeaORM 0.2.4 today! Some feature highlights:

Better ergonomic when working with custom select list#

[#208] Use Select::into_values to quickly select a custom column list and destruct as tuple.

use sea_orm::{entity::*, query::*, tests_cfg::cake, DeriveColumn, EnumIter};
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]enum QueryAs {    CakeName,    NumOfCakes,}
let res: Vec<(String, i64)> = cake::Entity::find()    .select_only()    .column_as(cake::Column::Name, QueryAs::CakeName)    .column_as(cake::Column::Id.count(), QueryAs::NumOfCakes)    .group_by(cake::Column::Name)    .into_values::<_, QueryAs>()    .all(&db)    .await?;
assert_eq!(    res,    vec![("Chocolate Forest".to_owned(), 2i64)]);

Contributed by:


Rename column name & column enum variant#

[#209] Rename the column name and enum variant of a model attribute, especially helpful when the column name is a Rust keyword.

mod my_entity {    use sea_orm::entity::prelude::*;
    #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]    #[sea_orm(table_name = "my_entity")]    pub struct Model {        #[sea_orm(primary_key, enum_name = "IdentityColumn", column_name = "id")]        pub id: i32,        #[sea_orm(column_name = "type")]        pub type_: String,    }
assert_eq!(my_entity::Column::IdentityColumn.to_string().as_str(), "id");assert_eq!(my_entity::Column::Type.to_string().as_str(), "type");

Contributed by:

Billy Chan

not on a condition tree#

[#145] Build a complex condition tree with Condition.

use sea_orm::{entity::*, query::*, tests_cfg::cake, sea_query::Expr, DbBackend};
assert_eq!(    cake::Entity::find()        .filter(            Condition::all()                .add(                    Condition::all()                        .not()                        .add(Expr::val(1).eq(1))                        .add(Expr::val(2).eq(2))                )                .add(                    Condition::any()                        .add(Expr::val(3).eq(3))                        .add(Expr::val(4).eq(4))                )        )        .build(DbBackend::Postgres)        .to_string(),    r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE (NOT (1 = 1 AND 2 = 2)) AND (3 = 3 OR 4 = 4)"#);

Contributed by:



SeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust's future.

Here is the roadmap for SeaORM 0.3.x.

Introducing SeaORM

ยท 5 min read
Chris Tsang
SeaQL Team

We are pleased to introduce SeaORM 0.2.2 to the Rust community today. It's our pleasure to have received feedback and contributions from awesome people to SeaQuery and SeaORM since 0.1.0.

Rust is a wonderful language that can be used to build anything. One of the FAQs is "Are We Web Yet?", and if Rocket (or your favourite web framework) is Rust's Rail, then SeaORM is precisely Rust's ActiveRecord.

SeaORM is an async ORM built from the ground up, designed to play well with the async ecosystem, whether it's actix, async-std, tokio or any web framework built on top.

Let's have a quick tour of SeaORM.


Here is how you'd execute multiple queries in parallel:

// execute multiple queries in parallellet cakes_and_fruits: (Vec<cake::Model>, Vec<fruit::Model>) =    futures::try_join!(Cake::find().all(&db), Fruit::find().all(&db))?;


You can use SeaQuery to build complex queries without 'fighting the ORM':

// build subquery with easelet cakes_with_filling: Vec<cake::Model> = cake::Entity::find()    .filter(        Condition::any().add(            cake::Column::Id.in_subquery(                Query::select()                    .column(cake_filling::Column::CakeId)                    .from(cake_filling::Entity)                    .to_owned(),            ),        ),    )    .all(&db)    .await?;

More on SeaQuery


To write unit tests, you can use our mock interface:

// Setup mock connectionlet db = MockDatabase::new(DbBackend::Postgres)    .append_query_results(vec![        vec![            cake::Model {                id: 1,                name: "New York Cheese".to_owned(),            },        ],    ])    .into_connection();
// Perform your application logicassert_eq!(    cake::Entity::find().one(&db).await?,    Some(cake::Model {        id: 1,        name: "New York Cheese".to_owned(),    }));
// Compare it against the expected transaction logassert_eq!(    db.into_transaction_log(),    vec![        Transaction::from_sql_and_values(            DbBackend::Postgres,            r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#,            vec![1u64.into()]        ),    ]);

More on testing

Service Oriented#

Here is an example Rocket handler with pagination:

#[get("/?<page>&<posts_per_page>")]async fn list(    conn: Connection<Db>,    page: Option<usize>,    per_page: Option<usize>,) -> Template {    // Set page number and items per page    let page = page.unwrap_or(1);    let per_page = per_page.unwrap_or(10);
    // Setup paginator    let paginator = Post::find()        .order_by_asc(post::Column::Id)        .paginate(&conn, per_page);    let num_pages = paginator.num_pages().await.unwrap();
    // Fetch paginated posts    let posts = paginator        .fetch_page(page - 1)        .await        .expect("could not retrieve posts");
    Template::render(        "index",        context! {            page: page,            per_page: per_page,            posts: posts,            num_pages: num_pages,        },    )}

Full Rocket example

We are building more examples for other web frameworks too.


SeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust's future.

Core Members#

Chris Tsang
Billy Chan


As a courtesy, here is the list of SeaQL's early contributors (in alphabetic order):

Ari Seyhun
Ayomide Bamidele
Ben Armstead
Bobby Ng
Daniel Lyne
Jonas Rinner
Marco Napetti
Markus Merklinger
Raphaรซl Duchaรฎne
Rรฉmi Kalbe
Sam Samai

Release Model

ยท One min read
Chris Tsang
SeaQL Team

Today we will outline our release plan in the near future.

One of Rust's slogan is Stability Without Stagnation, and SeaQL's take on it, is 'progression without stagnation'.

Before reaching 1.0, we will be releasing every week, incorporating the latest changes and merged pull requests. There will be at most one incompatible release per month, so you will be expecting 0.2 in Sep 2021 and 0.9 in Apr 2022. We will decide by then whether the next release is an incremental 0.10 or a stable 1.0.

After that, a major release will be rolled out every year. So you will probably be expecting a 2.0 in 2023.

All of these is only made possible with a solid infrastructure. While we have a test suite, its coverage will likely never be enough. We urge you to submit test cases to SeaORM if a particular feature is of importance to you.

We hope that a rolling release model will provide momentum to the community and propell us forward in the near future.

Hello World

ยท One min read
Chris Tsang
SeaQL Team

After 8 months of secrecy, SeaORM is now public!

The Rust async ecosystem is definitely thriving, with Tokio announcing Axum a week before.

We are now busy doing the brush ups to head towards our announcement in Sep.

If you stumbled upon us just now, well, hello! We sincerely invite you to be our alpha tester.

Welcome to SeaORM

ยท One min read
Chris Tsang
SeaQL Team

One year ago, when we were writing data processing algorithms in Rust, we needed an async library to interface with a database. Back then, there weren't many choices. So we have to write our own.

December last year, we released SeaQuery, and received welcoming responses from the community. We decided to push the project further and develop a full blown async ORM.

It has been a bumpy ride, as designing an async ORM requires working within and sometimes around Rust's unique type system. After several iterations of experimentation, I think we've attained a balance between static & dynamic and compile-time & run-time that it offers benefits of the Rust language while still be familiar and easy-to-work-with for those who come from other languages.

SeaORM is tentative to be released in Sep 2021 and stabilize in May 2022. We hope that SeaORM will become a go-to choice for working with databases in Rust and that the Rust language will be adopted by more organizations in building applications.

If you are intrigued like I do, please stay in touch and join the community.

Share your thoughts here.