
This blog post summarizes the new features and enhancements introduced in SeaORM 1.1
:
- 2024-10-15
1.1.0
- 2024-11-04
1.1.1
- 2024-12-02
1.1.2
- 2024-12-24
1.1.3
- 2025-01-10
1.1.4
- 2025-02-14
1.1.5
- 2025-02-24
1.1.6
- 2025-03-02
1.1.7
New Features
Support Postgres Vector
#2500 The popular pgvector extension enables efficient storage and querying of high-dimensional vector data, supporting applications like similarity search, recommendation systems, and other AI tools.
Thanks to the contribution of @28Smiles, PgVector
is now integrated nicely into the SeaQL ecosystem (under feature flag postgres-vector
).
// Model
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "image_model")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub embedding: PgVector,
}
// Schema
sea_query::Table::create()
.table(image_model::Entity.table_ref())
.col(ColumnDef::new(Column::Id).integer().not_null().auto_increment().primary_key())
.col(ColumnDef::new(Column::Embedding).vector(None).not_null())
..
// Insert
ActiveModel {
id: NotSet,
embedding: Set(PgVector::from(vec![1., 2., 3.])),
}
.insert(db)
.await?
Nested Objects in Relational Queries
We now have a good answer to Why SeaORM does not nest objects for parent-child relation!
The latest improvements to the FromQueryResult
and DerivePartialModel
macros allows you to nest objects easily, simplifying the construction of complex queries.
To illustrate, let's take a look at the Bakery Schema again.
As a simple first example, we'd like to select Cake
with Bakery
:
#[derive(FromQueryResult)]
struct Cake {
id: i32,
name: String,
#[sea_orm(nested)]
bakery: Option<Bakery>,
}
#[derive(FromQueryResult)]
struct Bakery {
#[sea_orm(from_alias = "bakery_id")]
id: i32,
#[sea_orm(from_alias = "bakery_name")]
brand: String,
}
let cake: Cake = cake::Entity::find()
.select_only()
.column(cake::Column::Id)
.column(cake::Column::Name)
.column_as(bakery::Column::Id, "bakery_id")
.column_as(bakery::Column::Name, "bakery_name")
.left_join(bakery::Entity)
.order_by_asc(cake::Column::Id)
.into_model()
.one(db)
.await?
.unwrap();
assert_eq!(
cake,
Cake {
id: 1,
name: "Basque cheesecake".to_string(),
bakery: Some(Bakery {
id: 20,
brand: "Super Baker".to_string(),
})
}
);
Because the tables cake
and bakery
have some duplicate column names, we'd have to do custom selects. select_only
here clears the default select list, and we apply aliases with column_as
. Then, in FromQueryResult
we use from_alias
to map the query result back to the nested struct.
You may wonder if there are ways to not do the alias and mapping? Yes! There's where DerivePartialModel
comes into play. The previous example can be written as:
#[derive(DerivePartialModel)] // FromQueryResult is no longer needed
#[sea_orm(entity = "cake::Entity", from_query_result)]
struct Cake {
id: i32,
name: String,
#[sea_orm(nested)]
bakery: Option<Bakery>,
}
#[derive(DerivePartialModel)]
#[sea_orm(entity = "bakery::Entity", from_query_result)]
struct Bakery {
id: i32,
#[sea_orm(from_col = "Name")]
brand: String,
}
// same as previous example, but without the custom selects
let cake: Cake = cake::Entity::find()
.left_join(bakery::Entity)
.order_by_asc(cake::Column::Id)
.into_partial_model()
.one(db)
.await?
.unwrap();
Under the hood, bakery_
prefix will be added to the column alias in the SQL query.
SELECT
"cake"."id" AS "id",
"cake"."name" AS "name",
"bakery"."id" AS "bakery_id",
"bakery"."name" AS "bakery_brand"
FROM "cake"
LEFT JOIN "bakery" ON "cake"."bakery_id" = "bakery"."id"
ORDER BY "cake"."id" ASC LIMIT 1
Now, let's look at one more advanced three-way join. Our join tree starts from Order:
Order -> Customer
-> LineItem -> Cake
#[derive(Debug, DerivePartialModel, PartialEq)]
#[sea_orm(entity = "order::Entity", from_query_result)]
struct Order {
id: i32,
total: Decimal,
#[sea_orm(nested)]
customer: Customer,
#[sea_orm(nested)]
line: LineItem,
}
#[derive(Debug, DerivePartialModel, PartialEq)]
#[sea_orm(entity = "customer::Entity", from_query_result)]
struct Customer {
name: String,
}
#[derive(Debug, DerivePartialModel, PartialEq)]
#[sea_orm(entity = "lineitem::Entity", from_query_result)]
struct LineItem {
price: Decimal,
quantity: i32,
#[sea_orm(nested)]
cake: Cake,
}
#[derive(Debug, DerivePartialModel, PartialEq)]
#[sea_orm(entity = "cake::Entity", from_query_result)]
struct Cake {
name: String,
}
let items: Vec<Order> = order::Entity::find()
.left_join(customer::Entity)
.left_join(lineitem::Entity)
.join(JoinType::LeftJoin, lineitem::Relation::Cake.def())
.order_by_asc(order::Column::Id)
.order_by_asc(lineitem::Column::Id)
.into_partial_model()
.all(db)
.await?;
assert_eq!(
items,
[
Order {
id: 101,
total: Decimal::from(10),
customer: Customer {
name: "Bob".to_owned()
},
line: LineItem {
cake: Cake {
name: "Cheesecake".to_owned()
},
price: Decimal::from(2),
quantity: 2,
}
},
..
]
);
That's it! Hope you like these new features, and a huge thanks to @Goodjooy for laying the foundation, @jreppnow for implementing the nested logic, and everyone who participated in the discussion.
Bonus: PartialModel -> ActiveModel
#2517
DerivePartialModel
got another extension to derive IntoActiveModel
as well. Absent attributes will be filled with NotSet
. This allows you to have a cake and eat it!
#[derive(DerivePartialModel)]
#[sea_orm(entity = "cake::Entity", into_active_model)]
struct PartialCake {
id: i32,
name: String,
}
let partial_cake = PartialCake {
id: 12,
name: "Lemon Drizzle".to_owned(),
};
// this is now possible:
assert_eq!(
cake::ActiveModel {
..partial_cake.into_active_model()
},
cake::ActiveModel {
id: Set(12),
name: Set("Lemon Drizzle".to_owned()),
..Default::default()
}
);
Three way select
#2518
With PartialModel being so powerful, if you still need to do non-nested selects, there's SelectThree
, an extension to SelectTwo
:
Order -> Lineitem -> Cake
let items: Vec<(order::Model, Option<lineitem::Model>, Option<cake::Model>)> =
order::Entity::find()
.find_also_related(lineitem::Entity)
.and_also_related(cake::Entity)
.order_by_asc(order::Column::Id)
.order_by_asc(lineitem::Column::Id)
.all(db)
.await?;
Insert heterogeneous models
#2433
Insert many now allows active models to have different column sets (it previously panics). Missing columns will be filled with NULL
. This makes seeding data (e.g. with Loco) a seamless operation.
let apple = cake_filling::ActiveModel {
cake_id: ActiveValue::set(2),
filling_id: ActiveValue::NotSet,
};
let orange = cake_filling::ActiveModel {
cake_id: ActiveValue::NotSet,
filling_id: ActiveValue::set(3),
};
assert_eq!(
Insert::<cake_filling::ActiveModel>::new()
.add_many([apple, orange])
.build(DbBackend::Postgres)
.to_string(),
r#"INSERT INTO "cake_filling" ("cake_id", "filling_id") VALUES (2, NULL), (NULL, 3)"#,
);
Improved Seaography Integration
#2403 We've simplified the code by allowing you to register entities into Seaography's GraphQL schema directly within the entity module.
pub mod prelude;
pub mod sea_orm_active_enums;
pub mod baker;
pub mod bakery;
pub mod cake;
pub mod cakes_bakers;
seaography::register_entity_modules!([
baker,
bakery,
cake,
cakes_bakers,
]);
seaography::register_active_enums!([
sea_orm_active_enums::Tea,
]);
Enhancements
- Added
Insert::exec_with_returning_keys
&Insert::exec_with_returning_many
(Postgres only)
assert_eq!(
Entity::insert_many([
ActiveModel { id: NotSet, name: Set("two".into()) },
ActiveModel { id: NotSet, name: Set("three".into()) },
])
.exec_with_returning_many(db)
.await
.unwrap(),
[
Model { id: 2, name: "two".into() },
Model { id: 3, name: "three".into() },
]
);
assert_eq!(
cakes_bakers::Entity::insert_many([
cakes_bakers::ActiveModel {
cake_id: Set(1),
baker_id: Set(2),
},
cakes_bakers::ActiveModel {
cake_id: Set(2),
baker_id: Set(1),
},
])
.exec_with_returning_keys(db)
.await
.unwrap(),
[(1, 2), (2, 1)]
);
- Added
DeleteOne::exec_with_returning
&DeleteMany::exec_with_returning
#2432 - Support complex type path in
DeriveIntoActiveModel
#2517
#[derive(DeriveIntoActiveModel)]
#[sea_orm(active_model = "<fruit::Entity as EntityTrait>::ActiveModel")]
struct Fruit {
cake_id: Option<Option<i32>>,
}
- Added
DatabaseConnection::close_by_ref
#2511
pub async fn close(self) -> Result<(), DbErr> { .. } // existing
pub async fn close_by_ref(&self) -> Result<(), DbErr> { .. } // new
- Added
Schema::json_schema_from_entity
to construct schema metadata for Entities
assert_eq!(
Schema::new(DbBackend::MySql).json_schema_from_entity(lunch_set::Entity),
json! {
"columns": [
{
"name": "id",
"nullable": false,
"type": "integer"
},
{
"name": "name",
"nullable": false,
"type": "string"
},
{
"name": "tea",
"nullable": false,
"type": {
"name": "tea",
"variants": [
"EverydayTea",
"BreakfastTea"
]
}
}
],
"primary_key": [
"id"
]
}
);
- Construct
DatabaseConnection
directly fromsqlx::PgPool
,sqlx::SqlitePool
andsqlx::MySqlPool
#2348
// these are implemented:
impl From<MySqlPool> for SqlxMySqlPoolConnection { .. }
impl From<MySqlPool> for DatabaseConnection { .. }
// so this is now possible:
let db: DatabaseConnection = mysql_pool.into();
- Expose underlying row types (e.g.
sqlx::postgres::PgRow
) #2265 - [sea-orm-migration] Allow modifying the connection in migrations #2397
#[async_std::main]
async fn main() {
cli::run_cli_with_connection(migration::Migrator, |connect_options| async {
let db = Database::connect(connect_options).await?;
if db.get_database_backend() == DatabaseBackend::Sqlite {
register_sqlite_functions(&db).await;
}
Ok(db)
}).await;
}
- [sea-orm-cli] Added
MIGRATION_DIR
environment variable #2419 - [sea-orm-cli] Added
acquire-timeout
option #2461 - [sea-orm-cli] Added
impl-active-model-behavior
option #2487 - [sea-orm-cli] Added
with-prelude
option #2322all
: the default value (current behaviour), will generate prelude.rs and add it to mod.rs / lib.rsall-allow-unused-imports
: will generate prelude.rs and add it to mod.rs, plus adding#![allow(unused_imports)]
in the modulenone
: will not generate prelude.rs
Upgrades
- Upgrade
sqlx
to0.8
#2305 #2371 - Upgrade
bigdecimal
to0.4
#2305 - Upgrade
sea-query
to0.32.0
#2305 - Upgrade
sea-query-binder
to0.7
#2305 - Upgrade
sea-schema
to0.16
#2305 - Upgrade
ouroboros
to0.18
#2353
House Keeping
Release Planning
SeaORM 1.0 is a stable release. As demonstrated, we are able to ship many new features without breaking the API. The 1.x version will be updated until at least October 2025, and we'll decide whether to release a 2.0 version or extend the 1.x life cycle.
SQL Server Support
We've been beta-testing SQL Server for SeaORM for a while. If you are building software for your company, please request early access.
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 GitHub sponsors 😇:
Wait... there's one more thing
"Are we web yet?" is a recurring question in the Rust community, the answer is yes, yes, YES!
If you are looking for a batteries-included full-stack web development framework that is strongly-typed, asynchronous, robust and high-performance, look no further than Rust + Loco + SeaQL. We highly recommend giving Loco a try - "It’s Like Ruby on Rails, but for Rust."
With this final piece of software, my vision for a complete full-stack Rust environment is now realized. After years of development in SeaORM + Seaography, I am excited to introduce it to you:
🖥️ SeaORM Pro: Professional Admin Panel


SeaORM Pro is an admin panel solution allowing you to quickly and easily launch an admin panel for your application - frontend development skills not required, but certainly nice to have!
Features:
- Full CRUD
- Built on React + GraphQL
- Built-in GraphQL resolver
- Customize the UI with simple TOML
Learn More