DEV Community


Posted on • Originally published at

What's new in SeaORM 0.10.x

πŸŽ‰ We are pleased to release SeaORM 0.10.0!

Rust 1.65

The long-anticipated Rust 1.65 has been released! Generic associated types (GATs) must be the hottest newly-stabilized feature.

How is GAT useful to SeaORM? Let's take a look at the following:

trait StreamTrait<'a>: Send + Sync {
    type Stream: Stream<Item = Result<QueryResult, DbErr>> + Send;

    fn stream(
        &'a self,
        stmt: Statement,
    ) -> Pin<Box<dyn Future<Output = Result<Self::Stream, DbErr>> + 'a + Send>>;
Enter fullscreen mode Exit fullscreen mode

You can see that the Future has a lifetime 'a, but as a side effect the lifetime is tied to StreamTrait.

With GAT, the lifetime can be elided:

trait StreamTrait: Send + Sync {
    type Stream<'a>: Stream<Item = Result<QueryResult, DbErr>> + Send
        Self: 'a;

    fn stream<'a>(
        &'a self,
        stmt: Statement,
    ) -> Pin<Box<dyn Future<Output = Result<Self::Stream<'a>, DbErr>> + 'a + Send>>;
Enter fullscreen mode Exit fullscreen mode

What benefit does it bring in practice? Consider you have a function that accepts a generic ConnectionTrait and calls stream():

async fn processor<'a, C>(conn: &'a C) -> Result<...>
where C: ConnectionTrait + StreamTrait<'a> {...}
Enter fullscreen mode Exit fullscreen mode

The fact that the lifetime of the connection is tied to the stream can create confusion to the compiler, most likely when you are making transactions:

async fn do_transaction<C>(conn: &C) -> Result<...>
where C: ConnectionTrait + TransactionTrait
    let txn = conn.begin().await?;
Enter fullscreen mode Exit fullscreen mode

But now, with the lifetime of the stream elided, it's much easier to work on streams inside transactions because the two lifetimes are now distinct and the stream's lifetime is implicit:

async fn processor<C>(conn: &C) -> Result<...>
where C: ConnectionTrait + StreamTrait {...}
Enter fullscreen mode Exit fullscreen mode

Big thanks to @nappa85 for the contribution.

Below are some feature highlights 🌟:

Support Array Data Types in Postgres

[#1132] Support model field of type Vec<T>. (by @hf29h8sh321, @ikrivosheev, @tyt2y3, @billy1624)

You can define a vector of types that are already supported by SeaORM in the model.

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "collection")]
pub struct Model {
    pub id: i32,
    pub integers: Vec<i32>,
    pub integers_opt: Option<Vec<i32>>,
    pub floats: Vec<f32>,
    pub doubles: Vec<f64>,
    pub strings: Vec<String>,
Enter fullscreen mode Exit fullscreen mode

Keep in mind that you need to enable the postgres-array feature and this is a Postgres only feature.

sea-orm = { version = "0.10", features = ["postgres-array", ...] }
Enter fullscreen mode Exit fullscreen mode

Better Error Types

[#750, #1002] Error types with parsable database specific error. (by @mohs8421, @tyt2y3)

let mud_cake = cake::ActiveModel {
    id: Set(1),
    name: Set("Moldy Cake".to_owned()),
    price: Set(dec!(10.25)),
    gluten_free: Set(false),
    serial: Set(Uuid::new_v4()),
    bakery_id: Set(None),

// Insert a new cake with its primary key (`id` column) set to 1.
let cake ="could not insert cake");

// Insert the same row again and it failed
// because primary key of each row should be unique.
let error: DbErr = cake
    .expect_err("inserting should fail due to duplicate primary key");

match error {
    DbErr::Exec(RuntimeErr::SqlxError(error)) => match error {
        Error::Database(e) => {
            // We check the error code thrown by the database (MySQL in this case),
            // `23000` means `ER_DUP_KEY`: we have a duplicate key in the table.
            assert_eq!(e.code().unwrap(), "23000");
        _ => panic!("Unexpected sqlx-error kind"),
    _ => panic!("Unexpected Error kind"),
Enter fullscreen mode Exit fullscreen mode

Run Migration on Any Postgres Schema

[#1056] By default migration will be run on the public schema, you can now override it when running migration on the CLI or programmatically. (by @MattGson, @nahuakang, @billy1624)

For CLI, you can specify the target schema with -s / --database_schema option:

  • via sea-orm-cli: sea-orm-cli migrate -u postgres://root:root@localhost/database -s my_schema
  • via SeaORM migrator: cargo run -- -u postgres://root:root@localhost/database -s my_schema

You can also run the migration on the target schema programmatically:

let connect_options = ConnectOptions::new("postgres://root:root@localhost/database".into())
    .set_schema_search_path("my_schema".into()) // Override the default schema

let db = Database::connect(connect_options).await?

migration::Migrator::up(&db, None).await?;
Enter fullscreen mode Exit fullscreen mode

Breaking Changes

enum ColumnType {
    // then
    Enum(String, Vec<String>)

    // now
    Enum {
        /// Name of enum
        name: DynIden,
        /// Variants of enum
        variants: Vec<DynIden>,
Enter fullscreen mode Exit fullscreen mode
  • A new method array_type was added to ValueType:
impl sea_orm::sea_query::ValueType for MyType {
    fn array_type() -> sea_orm::sea_query::ArrayType {
Enter fullscreen mode Exit fullscreen mode
  • ActiveEnum::name() changed return type to DynIden:
#[derive(Debug, Iden)]
#[iden = "category"]
pub struct CategoryEnum;

impl ActiveEnum for Category {
    // then
    fn name() -> String {

    // now
    fn name() -> DynIden {
Enter fullscreen mode Exit fullscreen mode

SeaORM Enhancements

CLI Enhancements

Please check here for the complete changelog.

Integration Examples

SeaORM plays well with the other crates in the async ecosystem. We maintain an array of example projects for building REST, GraphQL and gRPC services. More examples wanted!


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

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


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.11.x.

Top comments (0)