## Release Notes: SeaORM 2.0.0-rc.35 *(since 2.0.0-rc.34)* ### 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. ```rust 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`, `default`, `ignore`, `exhaustive`, and custom `active_model` path attributes for fine-grained control when converting "form" or "input" structs into ActiveModels. **`set`** — always set fields not present on the struct: ```rust #[derive(DeriveIntoActiveModel)] #[sea_orm(active_model = "fruit::ActiveModel", set(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: ```rust #[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` field is `None`: ```rust #[derive(DeriveIntoActiveModel)] #[sea_orm(active_model = "fruit::ActiveModel")] struct NewFruit { #[sea_orm(default = "String::from(\"Unnamed\")")] name: Option, } 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>`. **`ignore`** — exclude struct fields from the ActiveModel: ```rust #[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(...)`: ```rust #[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`: ```rust #[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, } 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