What's new in SeaORM 1.1.7
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_entityto 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
DatabaseConnectiondirectly fromsqlx::PgPool,sqlx::SqlitePoolandsqlx::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_DIRenvironment variable #2419 - [sea-orm-cli] Added
acquire-timeoutoption #2461 - [sea-orm-cli] Added
impl-active-model-behavioroption #2487 - [sea-orm-cli] Added
with-preludeoption #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
sqlxto0.8#2305 #2371 - Upgrade
bigdecimalto0.4#2305 - Upgrade
sea-queryto0.32.0#2305 - Upgrade
sea-query-binderto0.7#2305 - Upgrade
sea-schemato0.16#2305 - Upgrade
ouroborosto0.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
