Skip to content

Releases: SeaQL/sea-orm

2.0.0-rc.38

09 Apr 23:26

Choose a tag to compare

Release Notes: SeaORM 2.0.0-rc.38

(since 2.0.0-rc.37)

New Features

find_both_related() β€” required inner join loader (#2997)

A new find_both_related() method returns Vec<(E::Model, F::Model)> (both sides non-optional), as a counterpart to the existing find_also_related() which returns Vec<(E::Model, Option<F::Model>)>. Use this when you know the relation is always populated and want to avoid the Option unwrap:

let results: Vec<(Order, LineItem)> = Order::find()
    .find_both_related(LineItem)
    .all(db)
    .await?;

set_ne / set_ne_and aliases on ActiveValue (#3040)

Shorter aliases for set_if_not_equals and set_if_not_equals_and. The long-form names are kept as compatibility aliases:

// Before
active_model.name.set_if_not_equals("Alice");

// After
active_model.name.set_ne("Alice");

map_sqlx_*_pool_opts on ConnectOptions (#2770)

Three new methods to customise the underlying sqlx::pool::PoolOptions before the connection pool is created β€” one per driver:

ConnectOptions::new(DATABASE_URL)
    .map_sqlx_postgres_pool_opts(|opts| opts.max_connections(20).min_connections(5))
    .to_owned()

BTreeMap / HashMap support in TryGetableFromJson (#3009)

Map types can now be used directly as model fields when the column holds a JSON object:

pub struct Model {
    pub id: i32,
    pub metadata: HashMap<String, serde_json::Value>,
}

Inherited visibility in derive macros (#3029)

DeriveEntityModel, DeriveActiveModel, DeriveModel, and related macros now inherit the pub(crate) / pub(super) / private visibility of the struct they are applied to, instead of always emitting pub items.

Bug Fixes

Schema sync: drop unique constraint on PostgreSQL (#2994)

DROP INDEX fails on PostgreSQL for unique indexes created via column-level UNIQUE because they are backed by a named constraint, not a standalone index. Schema sync now uses ALTER TABLE … DROP CONSTRAINT on PostgreSQL and falls back to DROP INDEX on other backends.

Schema sync: tables in non-default schemas now discovered (#3016)

sync() previously ran schema discovery only against CURRENT_SCHEMA(). Entities with #[sea_orm(schema_name = "other")] were never found, causing every sync to attempt a redundant CREATE TABLE. Discovery now collects all schemas referenced by registered entities and queries each one.

Nested PartialModel null detection for optional fields (#3039)

A nested Option<PartialModel> loaded via a left join could incorrectly fail with Missing value for column 'id' when the nested model itself contained Option<T> fields. The null check now correctly handles arbitrary Option nesting depth.

use_transaction per-migration config was ignored (#3002)

exec_with_connection unconditionally wrapped all PostgreSQL migration operations in a transaction, overriding the per-migration use_transaction setting. The macro has been removed and each call site now uses the correct connection type.

Proc macros failed on long type paths with newlines (#3031)

DeriveActiveModelEx and other derive macros used .replace(' ', "") to strip whitespace, which missed newlines in formatted type paths longer than ~50 characters. Replaced with .split_whitespace().collect().

TIMESTAMPTZ conversion in the Postgres proxy driver (#3004)

from_sqlx_postgres_row_to_proxy_row now correctly converts TIMESTAMPTZ columns, fixing a panic when using the proxy driver with timestamptz fields.

Dependency Updates

  • arrow updated to 58 (#3007)
  • strum updated to 0.28 (#2993)

1.1.20

31 Mar 11:44

Choose a tag to compare

Enhancements

  • Add exists method to PaginatorTrait #730

Bug Fixes

  • Fix conversion of TIMESTAMPTZ values to proxy row in Postgres driver #3005
  • Fix no-default-features compile errors #3000

2.0.0-rc.37

09 Mar 17:24

Choose a tag to compare

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

2.0.0-rc.36

04 Mar 12:50

Choose a tag to compare

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

2.0.0-rc.35

27 Feb 21:43

Choose a tag to compare

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, 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:

#[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 β€” 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 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

2.0.0-rc.34

21 Feb 10:49

Choose a tag to compare

  • 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

2.0.0-rc.32

11 Feb 21:02

Choose a tag to compare

  • 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

26 Jan 00:03

Choose a tag to compare

  • 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 on Option<T> fields (#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 nested model (#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>, // can be null
}

2.0.0-rc.28

11 Jan 23:04

Choose a tag to compare

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

1.1.19

11 Nov 00:47

Choose a tag to compare

Enhancements

  • Add find_linked_recursive method to ModelTrait #2480
  • Skip drop extension type in fresh #2716

Bug Fixes

  • Handle null values in from_sqlx_*_row_to_proxy_row functions #2744