Skip to main content

Announcing SeaORM 0.12

Β· 8 min read
SeaQL Team
SeaORM 0.12 Banner

πŸŽ‰ We are pleased to announce SeaORM 0.12 today!

We still remember the time when we first introduced SeaORM to the Rust community two years ago. We set out a goal to enable developers to build asynchronous database-driven applications in Rust.

Today, many open-source projects, a handful of startups and many more closed-source projects are using SeaORM. Thank you all who participated and contributed in the making!

SeaORM Star History

New Features πŸŒŸβ€‹

🧭 Seaography: GraphQL integration (preview)​

Seaography example

Seaography is a GraphQL framework built on top of SeaORM. In 0.12, Seaography integration is built into sea-orm. Seaography allows you to build GraphQL resolvers quickly. With just a few commands, you can launch a GraphQL server from SeaORM entities!

While Seaography development is still in an early stage, it is especially useful in prototyping and building internal-use admin panels.

Read the documentation to learn more.

Added macro DerivePartialModel​

#1597 Now you can easily perform custom select to query only the columns you needed

#[derive(DerivePartialModel, FromQueryResult)]
#[sea_orm(entity = "Cake")]
struct PartialCake {
name: String,
#[sea_orm(
from_expr = r#"SimpleExpr::FunctionCall(Func::upper(Expr::col((Cake, cake::Column::Name))))"#
)]
name_upper: String,
}

assert_eq!(
cake::Entity::find()
.into_partial_model::<PartialCake>()
.into_statement(DbBackend::Sqlite)
.to_string(),
r#"SELECT "cake"."name", UPPER("cake"."name") AS "name_upper" FROM "cake""#
);

Added Select::find_with_linked​

#1728, #1743 Similar to find_with_related, you can now select related entities and consolidate the models.

// Consider the following link
pub struct BakedForCustomer;

impl Linked for BakedForCustomer {
type FromEntity = Entity;

type ToEntity = super::customer::Entity;

fn link(&self) -> Vec<RelationDef> {
vec![
super::cakes_bakers::Relation::Baker.def().rev(),
super::cakes_bakers::Relation::Cake.def(),
super::lineitem::Relation::Cake.def().rev(),
super::lineitem::Relation::Order.def(),
super::order::Relation::Customer.def(),
]
}
}

let res: Vec<(baker::Model, Vec<customer::Model>)> = Baker::find()
.find_with_linked(baker::BakedForCustomer)
.order_by_asc(baker::Column::Id)
.all(db)
.await?

Added DeriveValueType derive macro for custom wrapper types​

#1720 So now you can use newtypes easily.

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "custom_value_type")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub number: Integer,
// Postgres only
pub str_vec: StringVec,
}

#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)]
pub struct Integer(i32);

#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)]
pub struct StringVec(pub Vec<String>);

Which saves you the boilerplate of:

impl std::convert::From<StringVec> for Value { .. }

impl TryGetable for StringVec {
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I)
-> Result<Self, TryGetError> { .. }
}

impl ValueType for StringVec {
fn try_from(v: Value) -> Result<Self, ValueTypeErr> { .. }

fn type_name() -> String { "StringVec".to_owned() }

fn array_type() -> ArrayType { ArrayType::String }

fn column_type() -> ColumnType { ColumnType::String(None) }
}

Enhancements πŸ†™β€‹

#1433 Chained AND / OR join ON condition​

Added more macro attributes to DeriveRelation

// Entity file

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
// By default, it's `JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` AND `fruit`.`name` LIKE '%tropical%'`
#[sea_orm(
has_many = "super::fruit::Entity",
on_condition = r#"super::fruit::Column::Name.like("%tropical%")"#
)]
TropicalFruit,
// Specify `condition_type = "any"` to override it, now it becomes
// `JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` OR `fruit`.`name` LIKE '%tropical%'`
#[sea_orm(
has_many = "super::fruit::Entity",
on_condition = r#"super::fruit::Column::Name.like("%tropical%")"#
condition_type = "any",
)]
OrTropicalFruit,
}

#1508 Supports entity with composite primary key of arity 12​

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "primary_key_of_12")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id_1: String,
...
#[sea_orm(primary_key, auto_increment = false)]
pub id_12: bool,
}

#1677 Added UpdateMany::exec_with_returning()​

let models: Vec<Model> = Entity::update_many()
.col_expr(Column::Values, Expr::expr(..))
.exec_with_returning(db)
.await?;

#1511 Added MigratorTrait::migration_table_name() method to configure the name of migration table​

#[async_trait::async_trait]
impl MigratorTrait for Migrator {
// Override the name of migration table
fn migration_table_name() -> sea_orm::DynIden {
Alias::new("override_migration_table_name").into_iden()
}
...
}

#1707 Added DbErr::sql_err() method to parse common database errors​

assert!(matches!(
cake.into_active_model().insert(db).await
.expect_err("Insert a row with duplicated primary key")
.sql_err(),
Some(SqlErr::UniqueConstraintViolation(_))
));

assert!(matches!(
fk_cake.insert(db).await
.expect_err("Insert a row with invalid foreign key")
.sql_err(),
Some(SqlErr::ForeignKeyConstraintViolation(_))
));

#1737 Introduced new ConnAcquireErr​

enum DbErr {
ConnectionAcquire(ConnAcquireErr),
..
}

enum ConnAcquireErr {
Timeout,
ConnectionClosed,
}

#1627 Added DatabaseConnection::ping()​

|db: DatabaseConnection| {
assert!(db.ping().await.is_ok());
db.clone().close().await;
assert!(matches!(db.ping().await, Err(DbErr::ConnectionAcquire)));
}

#1708 Added TryInsert that does not panic on empty inserts​

// now, you can do:
let res = Bakery::insert_many(std::iter::empty())
.on_empty_do_nothing()
.exec(db)
.await;

assert!(matches!(res, Ok(TryInsertResult::Empty)));

#1712 Insert on conflict do nothing to return Ok​

let on = OnConflict::column(Column::Id).do_nothing().to_owned();

// Existing behaviour
let res = Entity::insert_many([..]).on_conflict(on).exec(db).await;
assert!(matches!(res, Err(DbErr::RecordNotInserted)));

// New API; now you can:
let res =
Entity::insert_many([..]).on_conflict(on).do_nothing().exec(db).await;
assert!(matches!(res, Ok(TryInsertResult::Conflicted)));

#1740, #1755 Replacing sea_query::Iden with sea_orm::DeriveIden​

To provide a more consistent interface, sea-query/derive is no longer enabled by sea-orm, as such, Iden no longer works as a derive macro (it's still a trait).

// then:

#[derive(Iden)]
#[iden = "category"]
pub struct CategoryEnum;

#[derive(Iden)]
pub enum Tea {
Table,
#[iden = "AfternoonTea"]
EverydayTea,
}

// now:

#[derive(DeriveIden)]
#[sea_orm(iden = "category")]
pub struct CategoryEnum;

#[derive(DeriveIden)]
pub enum Tea {
Table,
#[sea_orm(iden = "AfternoonTea")]
EverydayTea,
}

New Release Train Ferry πŸš’β€‹

It's been the 12th release of SeaORM! Initially, a major version was released every month. It gradually became 2 to 3 months, and now, it's been 6 months since the last major release. As our userbase grew and some are already using SeaORM in production, we understand the importance of having a stable API surface and feature set.

That's why we are committed to:

  1. Reviewing breaking changes with strict scrutiny
  2. Expanding our test suite to cover all features of our library
  3. Never remove features, and consider deprecation carefully

Today, the architecture of SeaORM is pretty solid and stable, and with the 0.12 release where we paid back a lot of technical debt, we will be able to deliver new features and enhancements without breaking. As our major dependency SQLx is not 1.0 yet, technically we cannot be 1.0.

We are still advancing rapidly, and we will always make a new release as soon as SQLx makes a new release, so that you can upgrade everything at once. As a result, the next major release of SeaORM will come out 6 months from now, or when SQLx makes a new release, whichever is earlier.

Community Survey πŸ“β€‹

SeaQL is an independent open-source organization. Our goal is to enable developers to build data intensive applications in Rust. If you are using SeaORM, please participate in the SeaQL Community Survey!

By completing this survey, you will help us gather insights into how you, the developer, are using our libraries and identify means to improve your developer experience. We will also publish an annual survey report to summarize our findings.

If you are a happy user of SeaORM, consider writing us a testimonial!

A big thank to DigitalOcean who sponsored our server hosting, and JetBrains who sponsored our IDE, and every sponsor on GitHub Sponsor!

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our sponsors πŸ˜‡:

Shane Sveller
Γ‰mile Fugulin
Afonso Barracha
Jacob Trueb
Natsuki Ikeguchi
Marlon Mueller-Soppart
KallyDev
Dean Sheather
Manfred Lee
Roland GorΓ‘cz
IceApinan
René Klačan
Unnamed Sponsor

What's Next for SeaORM? ⛡​

Open-source project is a never-ending work, and we are actively looking for ways to sustain the project. You can support our endeavour by starring & sharing our repositories and becoming a sponsor.

We are considering multiple directions to generate revenue for the organization. If you have any suggestion, or want to join or collaborate with us, please contact us via hello[at]sea-ql.org.

Thank you for your support, and together we can make open-source sustainable.