github SeaQL/sea-orm 2.0.0-rc.35

12 hours ago

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

Don't miss a new sea-orm release

NewReleases is sending notifications on new releases.