## 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<T>` field is `None`:

```rust
#[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`** — 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<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
