SeaQL/sea-orm
 Watch   
 Star   
 Fork   
8 days ago
sea-orm

2.0.0-rc.37

New Features

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:

image

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.

Bug Fixes

no-default-features compile errors with mac_address and proxy (#2992)

  • with-mac_address feature: added missing TryGetable impls, try_from_u64 impl, postgres array support, and with-json serde flag
  • proxy feature: removed an accidental hard dependency on serde_json (now only activated via with-json)
  • Fixed cfg guards on JSON/JSONB proxy row handling to require with-json
13 days ago
sea-orm

2.0.0-rc.36

New Features

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 not
  • Some(true): force a transaction on any backend
  • Some(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 OwnedTransaction variant to DatabaseExecutor, enabling SchemaManager to own a transaction
  • Added DatabaseExecutor::is_transaction() for runtime introspection
  • Each migration is now wrapped individually rather than in a batch
18 days ago
sea-orm

2.0.0-rc.35

New Features

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 everythingset + 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 always Decimal128)
  • Precision 19-38 maps to Decimal128, above 38 to Decimal256
  • Added FixedSizeBinary(N) support via #[sea_orm(arrow_byte_width = N)]
  • Added BinaryArray / LargeBinaryArray / FixedSizeBinaryArray to Value::Bytes conversion

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.).

Bug Fixes

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.

Improvements

Pi Spigot Example

The sea-orm-sync pi spigot example has been refactored into a tutorial-style example with:

  • OOP PiSpigot struct with state machine pattern (new / step / finalize / to_state / from_state)
  • clap CLI with --digits, --checkpoint, and --db flags
  • Comprehensive tests against 1000 known digits of pi, including three-phase checkpoint/resume
  • Tutorial README demonstrating how to add SQLite persistence to any program
25 days ago
sea-orm

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_u64 to DeriveValueType (#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
2026-02-12 04:51:46
sea-orm

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)
2026-01-26 07:30:11
sea-orm

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::exists is moved to SelectExt (#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>,
}
2026-01-12 06:49:22
sea-orm

2.0.0-rc.28

  • Deprecate do_nothing and on_empty_do_nothing (#2883)
  • Add set_if_not_equals_and to ActiveValue (#2888)
  • Add sqlx-all feature in sea-orm-migration (#2887)
  • Add debug log for entity registry (#2900)
  • Set auto_increment to false for String / Uuid primary key by default (#2881)
2025-11-11 08:09:11
sea-orm

1.1.19

Enhancements

Bug Fixes

2025-10-09 20:20:40
sea-orm

1.1.17

New Features

let mut opt = ConnectOptions::new(url);
opt.map_sqlx_postgres_opts(|pg_opt: PgConnectOptions| {
    pg_opt.ssl_mode(PgSslMode::Require)
});
2025-09-11 21:58:36
sea-orm

1.1.16

Bug Fixes

#[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""#,
);

Enhancements