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_integer
andboolean
are stored asinteger
REAL
:float
,double
,decimal
andmoney
are stored asreal
BLOB
:blob
andvarbinary_blob
are stored asblob
TEXT
: 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
DerivePartialModel
macro attributeentity
now 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
DeriveActiveModel
macro - #2064 [sea-orm-cli] Fix
migrate generate
on emptymod.rs
files
Breaking Changesβ
- #2145 Renamed
ConnectOptions::pool_options()
toConnectOptions::sqlx_pool_options()
- #2145 Made
sqlx_common
private, hidingsqlx_error_to_xxx_err
- MySQL
money
type maps todecimal
- MySQL
blob
types 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 asbinary
with default length of1
- Removed
BlobSize
enum - Added
StringLen
to 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()
ofString
maps toString(StringLen::None)
ColumnType::Bit
maps tobit
for PostgresColumnType::Binary
andColumnType::VarBinary
map tobytea
for PostgresValue::Decimal
andValue::BigDecimal
map toreal
for SQLiteColumnType::Year(Option<MySqlYear>)
changed toColumnType::Year
Upgradesβ
- Upgrade
sea-query
to0.31.0-rc.3
- Upgrade
sea-schema
to0.15.0-rc.4
- Upgrade
sea-query-binder
to0.6.0-rc.1
- #2088 Upgrade
strum
to0.26
House Keepingβ
- #2140 Improved Actix example to return 404 not found on unexpected inputs
- #2154 Deprecated Actix v3 example
- #2136 Re-enabled
rocket_okapi
example
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
ARITY
toPrimaryKeyTrait
, allowing users to write better generic code - #2186 Associating
ActiveModel
toEntityTrait
, 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!