2.0.0-rc.37
ER Diagram Generation (sea-orm-cli generate entity --er-diagram)
sea-orm-cli can now generate a Mermaid ER diagram alongside the entity files. Pass --er-diagram to write entities.mermaid into the output directory:
sea-orm-cli generate entity -u postgres://... -o src/entity --er-diagram
The diagram annotates columns with PK, FK, and UK markers and renders all relations — including many-to-many via junction tables — as Mermaid erDiagram syntax. Example output:
PostgreSQL Statement Timeout (ConnectOptions::statement_timeout)
ConnectOptions now accepts a statement_timeout for PostgreSQL connections. The timeout is set via the connection options at connect time (no extra round-trip) and causes the server to abort any statement that exceeds the duration:
ConnectOptions::new(DATABASE_URL)
.statement_timeout(Duration::from_secs(30))
.to_owned()
Has no effect on MySQL or SQLite connections.
SQLite ?mode= URL Parameter Support (#2987)
The rusqlite driver now parses the ?mode= query parameter from SQLite connection URLs, matching the behaviour of the sqlx SQLite driver:
| Mode | Behaviour |
|---|---|
rwc (default) |
Read-write, create if not exists |
rw |
Read-write, must exist |
ro |
Read-only |
memory |
In-memory database |
// Open an existing database read-only
let db = Database::connect("sqlite:./data.db?mode=ro").await?;
Unsupported parameters or unknown mode values return a DbErr::Conn error.
no-default-features compile errors with mac_address and proxy (#2992)
with-mac_addressfeature: added missingTryGetableimpls,try_from_u64impl, postgres array support, andwith-jsonserde flagproxyfeature: removed an accidental hard dependency onserde_json(now only activated viawith-json)- Fixed
cfgguards on JSON/JSONB proxy row handling to requirewith-json
2.0.0-rc.36
Per-Migration Transaction Control (#2980)
Previously, all Postgres migrations ran inside a single batch transaction, while MySQL and SQLite ran without one. This was an all-or-nothing approach with no way to opt out for individual migrations (e.g. CREATE INDEX CONCURRENTLY on Postgres requires running outside a transaction).
MigrationTrait now has a use_transaction() method to control this per migration:
impl MigrationTrait for Migration {
fn use_transaction(&self) -> Option<bool> {
Some(false) // opt out of automatic transaction
}
}
None(default): follow backend convention — Postgres uses a transaction, MySQL/SQLite do notSome(true): force a transaction on any backendSome(false): disable automatic transaction wrapping
For migrations that opt out, SchemaManager::begin() and SchemaManager::commit() allow manual transaction control within the migration body:
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// DDL in a transaction
let m = manager.begin().await?;
m.create_table(
Table::create()
.table("my_table")
.col(pk_auto("id"))
.col(string("name"))
.to_owned(),
).await?;
m.commit().await?;
// Non-transactional DDL
manager.get_connection()
.execute_unprepared("CREATE INDEX CONCURRENTLY idx_name ON my_table (name)")
.await?;
Ok(())
}
Core changes:
- Added
OwnedTransactionvariant toDatabaseExecutor, enablingSchemaManagerto own a transaction - Added
DatabaseExecutor::is_transaction()for runtime introspection - Each migration is now wrapped individually rather than in a batch
2.0.0-rc.35
SQLite Transaction Modes (#2932)
Added begin_with_options to TransactionTrait, allowing you to specify SQLite transaction modes (DEFERRED, IMMEDIATE, EXCLUSIVE), along with isolation level and access mode for other backends. Works for both sqlx-sqlite and rusqlite.
use sea_orm::{TransactionTrait, TransactionOptions, SqliteTransactionMode};
let txn = db.begin_with_options(TransactionOptions {
sqlite_transaction_mode: Some(SqliteTransactionMode::Immediate),
..Default::default()
}).await?;
Nested transactions correctly fall back to SAVEPOINT regardless of the mode.
Extend DeriveIntoActiveModel (#2961)
DeriveIntoActiveModel now supports set/fill, default, ignore, skip, exhaustive, and custom active_model path attributes for fine-grained control when converting "form" or "input" structs into ActiveModels.
set / fill — always set fields not present on the struct:
#[derive(DeriveIntoActiveModel)]
#[sea_orm(active_model = "fruit::ActiveModel", fill(cake_id = "None"))]
struct NewFruit {
name: String,
// cake_id is not on the struct, but will always be Set(None)
}
NewFruit { name: "Apple".into() }.into_active_model()
// => ActiveModel { id: NotSet, name: Set("Apple"), cake_id: Set(None) }
Multiple set entries can be combined or split across attributes:
#[derive(DeriveIntoActiveModel)]
#[sea_orm(
active_model = "fruit::ActiveModel",
set(name = "String::from(\"cherry\")", cake_id = "None")
)]
struct IdOnlyFruit {
id: i32,
}
IdOnlyFruit { id: 1 }.into_active_model()
// => ActiveModel { id: Set(1), name: Set("cherry"), cake_id: Set(None) }
default — fallback value when an Option<T> field is None:
#[derive(DeriveIntoActiveModel)]
#[sea_orm(active_model = "fruit::ActiveModel")]
struct NewFruit {
#[sea_orm(default = "String::from(\"Unnamed\")")]
name: Option<String>,
}
NewFruit { name: Some("Apple".into()) }.into_active_model()
// => ActiveModel { id: NotSet, name: Set("Apple"), cake_id: NotSet }
NewFruit { name: None }.into_active_model()
// => ActiveModel { id: NotSet, name: Set("Unnamed"), cake_id: NotSet }
Bare #[sea_orm(default)] (without a value) uses Default::default() as the fallback. This also works with custom enum types that implement Into<Option<T>>.
ignore / skip — exclude struct fields from the ActiveModel:
#[derive(DeriveIntoActiveModel)]
#[sea_orm(active_model = "fruit::ActiveModel")]
struct NewFruit {
name: String,
cake_id: i32,
#[sea_orm(ignore)]
_extra: String, // not mapped to ActiveModel
}
exhaustive — require all ActiveModel fields to be either on the struct or in set(...):
#[derive(DeriveIntoActiveModel)]
#[sea_orm(active_model = "fruit::ActiveModel", exhaustive, set(cake_id = "None"))]
struct FullFruit {
id: i32,
name: String,
// cake_id is covered by set(...), so all fields are accounted for
}
Combining everything — set + default + ignore + exhaustive:
#[derive(DeriveIntoActiveModel)]
#[sea_orm(active_model = "fruit::ActiveModel", exhaustive, set(cake_id = "None"))]
struct NewFruit {
id: i32,
#[sea_orm(default = "String::from(\"Unnamed\")")]
name: Option<String>,
}
NewFruit { id: 1, name: Some("Apple".into()) }.into_active_model()
// => ActiveModel { id: Set(1), name: Set("Apple"), cake_id: Set(None) }
NewFruit { id: 2, name: None }.into_active_model()
// => ActiveModel { id: Set(2), name: Set("Unnamed"), cake_id: Set(None) }
IntoSimpleExpr for FunctionCall (#2822)
FunctionCall now implements IntoSimpleExpr, so function calls can be used directly in select expressions and filters without wrapping in SimpleExpr.
Arrow: Support Decimal64 and Fixed-Size Binary (#2957)
- Decimal columns with precision <= 18 now map to Arrow
Decimal64(was alwaysDecimal128) - Precision 19-38 maps to
Decimal128, above 38 toDecimal256 - Added
FixedSizeBinary(N)support via#[sea_orm(arrow_byte_width = N)] - Added
BinaryArray/LargeBinaryArray/FixedSizeBinaryArraytoValue::Bytesconversion
Optional time crate for Migrations (#2865)
Migrations can now use the time crate instead of std::time::SystemTime for timestamps, enabling compilation to WASM targets. Activate with the with-time feature on sea-orm-migration.
OpenTelemetry SpanKind::Client (#2937)
The db_span! macro now emits otel.kind = "client", ensuring database spans are properly recognized as client spans by APM tools (Datadog, Jaeger, etc.).
Fix unique column in schema sync (#2971)
Columns marked with #[sea_orm(unique)] are now correctly handled by the schema sync/diff builder, generating proper unique constraints instead of being silently ignored.
Fix DeriveArrowSchema with split attributes (#2973)
Fixed a compilation error when #[sea_orm(...)] attributes were split across multiple lines on the same field (e.g. #[sea_orm(primary_key)] and #[sea_orm(auto_increment = false)] separately). The macro now properly consumes attributes it doesn't recognize.
Map internal error types properly
Internal errors from the schema builder are now mapped to the correct DbErr variants instead of being lost or mistyped.
Pi Spigot Example
The sea-orm-sync pi spigot example has been refactored into a tutorial-style example with:
- OOP
PiSpigotstruct with state machine pattern (new/step/finalize/to_state/from_state) clapCLI with--digits,--checkpoint, and--dbflags- Comprehensive tests against 1000 known digits of pi, including three-phase checkpoint/resume
- Tutorial README demonstrating how to add SQLite persistence to any program
2.0.0-rc.34
- Don't create index if column is already unique (entity first workflow) (#2950)
- Derive clone for topologies (#2954)
- Added
try_from_u64toDeriveValueType(#2958)
// Test for try_from_u64 attribute with type alias
type UserId = i32;
#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)]
#[sea_orm(try_from_u64)]
pub struct MyUserId(pub UserId);
- Arrow / Parquet support (#2957)
- Added
ArrowSchema,DeriveArrowSchema - Support decimal with different formats
- Support timestamp with different timezone / resolution
- Added parquet example
- Added
2.0.0-rc.32
- permit passing custom derives to Model or ModelEx (#2933)
#[sea_orm::model]
#[derive(TS, ...)]
// Apply attributes specifically to the generated Model struct
#[sea_orm(model_attrs(ts(rename = "Fruit")))]
// Apply attributes specifically to the generated ModelEx struct
#[sea_orm(model_ex_attrs(ts(rename = "FruitEx")))]
struct Model {
// ...
}The code above expands to:
// ...
#[derive(TS, ...)]
#[ts(rename = "Fruit")]
struct Model {
// ...
}
// ...
#[derive(TS, ...)]
#[ts(rename = "FruitEx")]
struct ModelEx {
// ...
}- Add typed column for TextUuid (#2717)
- Fix proxy compile error (#2935)
- Use i64 for COUNT(*) result on MySQL and SQLite (#2944)
- MigratorTrait with self (#2806)
2.0.0-rc.29
- Add missing lifetime hint to
EntityName::table_name(#2907) - [sea-orm-cli] Fix codegen to not generate relations to filtered entities (#2913)
- [sea-orm-cli] Fix enum variants starting with digits (#2905)
- Add wrapper type for storing Uuids as TEXT (#2914)
- Optimize exists;
PaginatorTrait::existsis moved toSelectExt(#2909) - Add tracing spans for database operations (#2885)
- Fix derive enums without per-case rename (#2922)
- Fix DeriveIntoActiveModel (#2926)
#[derive(DeriveIntoActiveModel)]
#[sea_orm(active_model = "<fruit::Entity as EntityTrait>::ActiveModel")]
struct PartialFruit {
cake_id: Option<i32>,
}
assert_eq!(
PartialFruit { cake_id: Some(1) }.into_active_model(),
fruit::ActiveModel {
id: NotSet,
name: NotSet,
cake_id: Set(Some(1)),
}
);- FromQueryResult now supports nullable left join (#2845)
#[derive(FromQueryResult)]
struct CakeWithOptionalBakeryModel {
#[sea_orm(alias = "cake_id")]
id: i32,
#[sea_orm(alias = "cake_name")]
name: String,
#[sea_orm(nested)]
bakery: Option<bakery::Model>,
}2.0.0-rc.28
- Deprecate
do_nothingandon_empty_do_nothing(#2883) - Add
set_if_not_equals_andtoActiveValue(#2888) - Add
sqlx-allfeature insea-orm-migration(#2887) - Add debug log for entity registry (#2900)
- Set
auto_incrementto false for String / Uuid primary key by default (#2881)
1.1.19
- Add
find_linked_recursivemethod to ModelTrait https://github.com/SeaQL/sea-orm/pull/2480 - Skip drop extension type in fresh https://github.com/SeaQL/sea-orm/pull/2716
- Handle null values in
from_sqlx_*_row_to_proxy_rowfunctions https://github.com/SeaQL/sea-orm/pull/2744
1.1.17
- Added
map_sqlx_mysql_opts,map_sqlx_postgres_opts,map_sqlx_sqlite_optstoConnectOptionshttps://github.com/SeaQL/sea-orm/pull/2731
let mut opt = ConnectOptions::new(url);
opt.map_sqlx_postgres_opts(|pg_opt: PgConnectOptions| {
pg_opt.ssl_mode(PgSslMode::Require)
});
- Added
mariadb-use-returningto use returning syntax for MariaDB https://github.com/SeaQL/sea-orm/pull/2710 - Released
sea-orm-rocket0.6 https://github.com/SeaQL/sea-orm/pull/2732
1.1.16
- Fix enum casting in DerivePartialModel https://github.com/SeaQL/sea-orm/pull/2719 https://github.com/SeaQL/sea-orm/pull/2720
#[derive(DerivePartialModel)]
#[sea_orm(entity = "active_enum::Entity", from_query_result, alias = "zzz")]
struct PartialWithEnumAndAlias {
#[sea_orm(from_col = "tea")]
foo: Option<Tea>,
}
let sql = active_enum::Entity::find()
.into_partial_model::<PartialWithEnumAndAlias>()
.into_statement(DbBackend::Postgres)
.sql;
assert_eq!(
sql,
r#"SELECT CAST("zzz"."tea" AS "text") AS "foo" FROM "public"."active_enum""#,
);
- [sea-orm-cli] Use tokio (optional) instead of async-std https://github.com/SeaQL/sea-orm/pull/2721