Skip to main content

What's new in SeaORM 0.10.x

Β· 7 min read
SeaQL Team

πŸŽ‰ 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>>;
}

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
where
Self: 'a;

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

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> {...}

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?;
processor(&txn).await?;
txn.commit().await?;
}

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 {...}

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 {
#[sea_orm(primary_key)]
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>,
}

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", ...] }

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 = mud_cake.save(db).await.expect("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
.into_active_model()
.insert(db)
.await
.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"),
}

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
.to_owned();

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

migration::Migrator::up(&db, None).await?;

Breaking Changes​

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

// now
Enum {
/// Name of enum
name: DynIden,
/// Variants of enum
variants: Vec<DynIden>,
}
...
}
  • 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 {
sea_orm::sea_query::ArrayType::TypeName
}
...
}
  • ActiveEnum::name() changed return type to DynIden:
#[derive(Debug, Iden)]
#[iden = "category"]
pub struct CategoryEnum;

impl ActiveEnum for Category {
// then
fn name() -> String {
"category".to_owned()
}

// now
fn name() -> DynIden {
SeaRc::new(CategoryEnum)
}
...
}

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 πŸ˜‡:

Γ‰mile Fugulin
Dean Sheather
Shane Sveller
Sakti Dwi Cahyono
Henrik Giesel
Jacob Trueb
Marcus Buffett
Unnamed Sponsor
Unnamed Sponsor

Community​

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.