What's new in SeaORM 1.0-rc.x
This blog post summarizes the new features and enhancements introduced in SeaORM 1.0-rc.x:
New Featuresβ
Refreshed migration schema definitionβ
#2099 We are aware that SeaORM's migration scripts can sometimes look verbose. Thanks to the clever design made by Loco, we've refreshed the schema definition syntax.
An old migration script looks like this:
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Users::Table)
.if_not_exists()
.col(
ColumnDef::new(Users::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Users::Pid).uuid().not_null())
.col(ColumnDef::new(Users::Email).string().not_null().unique_key())
// ...
}
}
Now, using the new schema helpers, you can define the schema with a simplified syntax!
// Remember to import `sea_orm_migration::schema::*`
use sea_orm_migration::{prelude::*, schema::*};
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Users::Table)
.if_not_exists()
.col(pk_auto(Users::Id)) // Primary key with auto-increment
.col(uuid(Users::Pid)) // UUID column
.col(string_uniq(Users::Email)) // String column with unique and not null constraint
.col(string(Users::Password)) // String column
.col(string(Users::ApiKey).unique_key())
.col(string(Users::Name))
.col(string_null(Users::ResetToken)) // Nullable string column
.col(timestamp_null(Users::ResetSentAt)) // Nullable timestamp column
.col(string_null(Users::EmailVerificationToken))
.col(timestamp_null(Users::EmailVerificationSentAt))
.col(timestamp_null(Users::EmailVerifiedAt))
.to_owned(),
)
.await
}
// ...
}
There are three variants for each commonly used column type:
<COLUMN_TYPE>()helper function, e.g.string(), define a non-null string column<COLUMN_TYPE>_null()helper function, e.g.string_null(), define a nullable string column<COLUMN_TYPE>_uniq()helper function, e.g.string_uniq(), define a non-null and unique string column
The new schema helpers can be used by importing sea_orm_migration::schema::*. The migration library is fully backward compatible, so there is no rush to migrate old scripts. The new syntax is recommended for new scripts, and all examples in the SeaORM repository have been updated for demonstration. For advanced use cases, the old SeaQuery syntax can still be used.
Reworked SQLite Type Mappingsβ
sea-orm#2077 sea-query#735 sea-schema#117 We've reworked the type mappings for SQLite across the SeaQL ecosystem, such that SeaQuery and SeaSchema are now reciprocal to each other. Migrations written with SeaQuery can be rediscovered by sea-orm-cli and generate compatible entities! In other words, the roundtrip is complete.
Data types will be mapped to SQLite types with a custom naming scheme following SQLite's affinity rule:
INTEGER:integer,tiny_integer,small_integer,big_integerandbooleanare stored asintegerREAL:float,double,decimalandmoneyare stored asrealBLOB:blobandvarbinary_blobare stored asblobTEXT: all other data types are stored astext, includingstring,char,text,json,uuid,date,time,datetime,timestamp, etc.
To illustrate,
assert_eq!(
Table::create()
.table(Alias::new("strange"))
.col(ColumnDef::new(Alias::new("id")).integer().not_null().auto_increment().primary_key())
.col(ColumnDef::new(Alias::new("int1")).integer())
.col(ColumnDef::new(Alias::new("int2")).tiny_integer())
.col(ColumnDef::new(Alias::new("int3")).small_integer())
.col(ColumnDef::new(Alias::new("int4")).big_integer())
.col(ColumnDef::new(Alias::new("string1")).string())
.col(ColumnDef::new(Alias::new("string2")).string_len(24))
.col(ColumnDef::new(Alias::new("char1")).char())
.col(ColumnDef::new(Alias::new("char2")).char_len(24))
.col(ColumnDef::new(Alias::new("text_col")).text())
.col(ColumnDef::new(Alias::new("json_col")).json())
.col(ColumnDef::new(Alias::new("uuid_col")).uuid())
.col(ColumnDef::new(Alias::new("decimal1")).decimal())
.col(ColumnDef::new(Alias::new("decimal2")).decimal_len(12, 4))
.col(ColumnDef::new(Alias::new("money1")).money())
.col(ColumnDef::new(Alias::new("money2")).money_len(12, 4))
.col(ColumnDef::new(Alias::new("float_col")).float())
.col(ColumnDef::new(Alias::new("double_col")).double())
.col(ColumnDef::new(Alias::new("date_col")).date())
.col(ColumnDef::new(Alias::new("time_col")).time())
.col(ColumnDef::new(Alias::new("datetime_col")).date_time())
.col(ColumnDef::new(Alias::new("boolean_col")).boolean())
.col(ColumnDef::new(Alias::new("binary2")).binary_len(1024))
.col(ColumnDef::new(Alias::new("binary3")).var_binary(1024))
.to_string(SqliteQueryBuilder),
[
r#"CREATE TABLE "strange" ("#,
r#""id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,"#,
r#""int1" integer,"#,
r#""int2" tinyint,"#,
r#""int3" smallint,"#,
r#""int4" bigint,"#,
r#""string1" varchar,"#,
r#""string2" varchar(24),"#,
r#""char1" char,"#,
r#""char2" char(24),"#,
r#""text_col" text,"#,
r#""json_col" json_text,"#,
r#""uuid_col" uuid_text,"#,
r#""decimal1" real,"#,
r#""decimal2" real(12, 4),"#,
r#""money1" real_money,"#,
r#""money2" real_money(12, 4),"#,
r#""float_col" float,"#,
r#""double_col" double,"#,
r#""date_col" date_text,"#,
r#""time_col" time_text,"#,
r#""datetime_col" datetime_text,"#,
r#""boolean_col" boolean,"#,
r#""binary2" blob(1024),"#,
r#""binary3" varbinary_blob(1024)"#,
r#")"#,
]
.join(" ")
);
The full type mapping table is documented here:
| ColumnType | MySQL data type | PostgreSQL data type | SQLite data type |
|---|---|---|---|
| Char | char | char | char |
| String | varchar | varchar | varchar |
| Text | text | text | text |
| TinyInteger | tinyint | smallint | tinyint |
| SmallInteger | smallint | smallint | smallint |
| Integer | int | integer | integer |
| BigInteger | bigint | bigint | integer |
| TinyUnsigned | tinyint unsigned | smallint | tinyint |
| SmallUnsigned | smallint unsigned | smallint | smallint |
| Unsigned | int unsigned | integer | integer |
| BigUnsigned | bigint unsigned | bigint | integer |
| Float | float | real | float |
| Double | double | double precision | double |
| Decimal | decimal | decimal | real |
| DateTime | datetime | timestamp without time zone | datetime_text |
| Timestamp | timestamp | timestamp | timestamp_text |
| TimestampWithTimeZone | timestamp | timestamp with time zone | timestamp_with_timezone_text |
| Time | time | time | time_text |
| Date | date | date | date_text |
| Year | year | N/A | N/A |
| Interval | N/A | interval | N/A |
| Binary | binary | bytea | blob |
| VarBinary | varbinary | bytea | varbinary_blob |
| Bit | bit | bit | N/A |
| VarBit | bit | varbit | N/A |
| Boolean | bool | bool | boolean |
| Money | decimal | money | real_money |
| Json | json | json | json_text |
| JsonBinary | json | jsonb | jsonb_text |
| Uuid | binary(16) | uuid | uuid_text |
| Enum | ENUM(...) | ENUM_NAME | enum_text |
| Array | N/A | DATA_TYPE[] | N/A |
| Cidr | N/A | cidr | N/A |
| Inet | N/A | inet | N/A |
| MacAddr | N/A | macaddr | N/A |
| LTree | N/A | ltree | N/A |
Enhancementsβ
- #2137
DerivePartialModelmacro attributeentitynow supportssyn::Type
#[derive(DerivePartialModel)]
#[sea_orm(entity = "<entity::Model as ModelTrait>::Entity")]
struct EntityNameNotAIdent {
#[sea_orm(from_col = "foo2")]
_foo: i32,
#[sea_orm(from_col = "bar2")]
_bar: String,
}
- #2146 Added
RelationDef::from_alias()
assert_eq!(
cake::Entity::find()
.join_as(
JoinType::LeftJoin,
cake_filling::Relation::Cake.def().rev(),
cf.clone()
)
.join(
JoinType::LeftJoin,
cake_filling::Relation::Filling.def().from_alias(cf)
)
.build(DbBackend::MySql)
.to_string(),
[
"SELECT `cake`.`id`, `cake`.`name` FROM `cake`",
"LEFT JOIN `cake_filling` AS `cf` ON `cake`.`id` = `cf`.`cake_id`",
"LEFT JOIN `filling` ON `cf`.`filling_id` = `filling`.`id`",
]
.join(" ")
);
- #1665 [sea-orm-macro] Qualify traits in
DeriveActiveModelmacro - #2064 [sea-orm-cli] Fix
migrate generateon emptymod.rsfiles
Breaking Changesβ
- #2145 Renamed
ConnectOptions::pool_options()toConnectOptions::sqlx_pool_options() - #2145 Made
sqlx_commonprivate, hidingsqlx_error_to_xxx_err - MySQL
moneytype maps todecimal - MySQL
blobtypes moved toextension::mysql::MySqlType;ColumnDef::blob()now takes no parameters
assert_eq!(
Table::create()
.table(BinaryType::Table)
.col(ColumnDef::new(BinaryType::BinaryLen).binary_len(32))
.col(ColumnDef::new(BinaryType::Binary).binary())
.col(ColumnDef::new(BinaryType::Blob).custom(MySqlType::Blob))
.col(ColumnDef::new(BinaryType::TinyBlob).custom(MySqlType::TinyBlob))
.col(ColumnDef::new(BinaryType::MediumBlob).custom(MySqlType::MediumBlob))
.col(ColumnDef::new(BinaryType::LongBlob).custom(MySqlType::LongBlob))
.to_string(MysqlQueryBuilder),
[
"CREATE TABLE `binary_type` (",
"`binlen` binary(32),",
"`bin` binary(1),",
"`b` blob,",
"`tb` tinyblob,",
"`mb` mediumblob,",
"`lb` longblob",
")",
]
.join(" ")
);
ColumnDef::binary()sets column type asbinarywith default length of1- Removed
BlobSizeenum - Added
StringLento represent length ofvarchar/varbinary
/// Length for var-char/binary; default to 255
pub enum StringLen {
/// String size
N(u32),
Max,
#[default]
None,
}
ValueType::columntype()ofVec<u8>maps toVarBinary(StringLen::None)ValueType::columntype()ofStringmaps toString(StringLen::None)ColumnType::Bitmaps tobitfor PostgresColumnType::BinaryandColumnType::VarBinarymap tobyteafor PostgresValue::DecimalandValue::BigDecimalmap torealfor SQLiteColumnType::Year(Option<MySqlYear>)changed toColumnType::Year
Upgradesβ
- Upgrade
sea-queryto0.31.0-rc.3 - Upgrade
sea-schemato0.15.0-rc.4 - Upgrade
sea-query-binderto0.6.0-rc.1 - #2088 Upgrade
strumto0.26
House Keepingβ
- #2140 Improved Actix example to return 404 not found on unexpected inputs
- #2154 Deprecated Actix v3 example
- #2136 Re-enabled
rocket_okapiexample
Release Planningβ
In the previous release of SeaORM, we stated that we want our next release to be 1.0. We are indeed very close to 1.0 now!
While 0.12 will still be maintained before 1.0 get finalized, you are welcome to try out 1.0-rc.x today! There will still be a few minor but still technically breaking changes:
- #2185 Adding trait const
ARITYtoPrimaryKeyTrait, allowing users to write better generic code - #2186 Associating
ActiveModeltoEntityTrait, allowing users to extend the behaviour of Entities
Now is also the perfect time for you to propose breaking changes that'd have long term impact to SeaORM. After the stablization, we hope that SeaORM can offer a stable API surface that developers can use in production for the years to come.
We'd not have more than 2 major releases in a year, and each major release will be maintained for at least 1 year. It's still tentative, but that's what we have in mind for now. Moreoever, it will actually allow us to ship new features more frequently!
SQL Server Supportβ
We've been planning SQL Server for SeaORM for a while, but it was put aside in 2023 (which I regretted). Anyway SQL Server support is coming soon! It will first be offered as a closed beta to our partners. If you are interested, please join our waiting list.
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 π:
Gold Sponsorsβ
GitHub Sponsorsβ
Rustacean Sticker Pack π¦β
The Rustacean Sticker Pack is the perfect way to express your passion for Rust. Our stickers are made with a premium water-resistant vinyl with a unique matte finish. Stick them on your laptop, notebook, or any gadget to show off your love for Rust!
Moreover, all proceeds contributes directly to the ongoing development of SeaQL projects.
Sticker Pack Contents:
- Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography, FireDBG
- Mascot of SeaQL: Terres the Hermit Crab
- Mascot of Rust: Ferris the Crab
- The Rustacean word
Support SeaQL and get a Sticker Pack!

