Back to Sea Orm

2.0.0 Rc.41

changelog/2.0.0-rc.41.md

2.0.07.8 KB
Original Source

Release Notes: SeaORM 2.0.0-rc.41

(since 2.0.0-rc.40)

New Features

SelectFourMany with consolidate() (#3054)

SelectFour::consolidate() returns a SelectFourMany; its all() (star topology) rolls the three has-many children up into Vecs — Vec<(E::Model, Vec<F::Model>, Vec<G::Model>, Vec<H::Model>)> — mirroring SelectThreeMany instead of returning flat Option tuples.

rust
let rows = parent::Entity::find()
    .find_also_related(a::Entity)
    .find_also_related(b::Entity)
    .find_also(parent::Entity, c::Entity)
    .consolidate()
    .all(db)
    .await?;

Update without returning (#2965)

ActiveModelTrait::update_without_returning (and UpdateOne / ValidatedUpdateOne::exec_without_returning) run an UPDATE without a RETURNING clause, yielding an UpdateResult — useful on backends without RETURNING or when the model isn't needed. It runs before_save, skips after_save, and returns DbErr::RecordNotUpdated when no row matches.

rust
let res = active_model.update_without_returning(db).await?;

Explicit prefixes for nested result models (#2989)

FromQueryResult and DerivePartialModel accept #[sea_orm(nested(prefix = "..."))], so a model can hold several nested fields of the same type without column clashes:

rust
#[sea_orm(nested(prefix = "mgr_"), alias = "manager")]
manager: Worker,
#[sea_orm(nested(prefix = "csh_"), alias = "cashier")]
cashier: Worker,

Migration CLI custom connection entrypoints (#3035)

sea-orm-migration exposes custom connection entrypoints so applications can customize ConnectOptions, load SQLite extensions, or build the connection from their own config before running migration commands.

rust
run_cli_with_connection(migrator, |mut opt: ConnectOptions| async move {
    opt.sqlx_logging(false); // ...or load extensions, read app config, etc.
    Database::connect(opt).await
})
.await;

Prebuilt sea-orm-cli binaries via cargo-binstall (#2877)

Releases now publish prebuilt sea-orm-cli binaries for Linux (x64/arm64), macOS (x64/arm64), and Windows (x64), and the crate carries cargo-binstall metadata, so cargo binstall sea-orm-cli installs without compiling from source.

Enhancements

Stable serde for first-party value wrappers (#3023)

First-party wrappers now have explicit serde impls instead of relying on derive shape: TextUuid is a JSON string, and the Unix-timestamp wrappers (ChronoUnixTimestamp, ChronoUnixTimestampMillis, TimeUnixTimestamp, TimeUnixTimestampMillis) are JSON i64s matching their stored value.

rust
let json = serde_json::to_string(&TextUuid::from(uuid))?; // "\"<uuid>\""
serde_json::from_str::<TextUuid>("\"not-a-uuid\"").is_err(); // parse-checked

Streaming behind stream cargo feature (#3075)

Streaming APIs (QueryStream, StreamTrait, Select::stream*) are gated behind the stream feature, on by default. default-features = false without stream drops the ouroboros dependency.

toml
# opt out of streaming (and ouroboros) by dropping default features
sea-orm = { version = "2.0.0-rc.41", default-features = false, features = ["macros", "sqlx-postgres", "runtime-tokio-native-tls"] }

Generated migration names (#2580)

Generated migrations implement MigrationName explicitly, keeping names stable when the file/module layout is customized.

Bug Fixes

ActiveModelBehavior runs for junction tables (#3027, #3010)

When establishing a many-to-many relation through the ActiveModel builder, the junction-table rows now run their ActiveModelBehavior hooks (before_save / after_save on inserted via-models, before_delete / after_delete on removed ones) instead of being inserted/deleted without hooks.

Generated entities compile when an enum name collides with a generated identifier (#2880, #2153)

An enum named like a generated item (Model, Entity, ActiveModel, Column, PrimaryKey, Relation) is now imported aliased instead of emitting a conflicting use:

rust
use super::sea_orm_active_enums::Model as ModelEnum;

serde(rename_all) no longer breaks ActiveModel::from_json defaulting (#2855, #2854)

Regression from #2842: with #[serde(rename_all = "...")] (or field-level rename), missing fields errored instead of defaulting to ActiveValue::NotSet. The default-merge is now keyed off each column's json_key(), which respects both rename forms.

rust
#[serde(rename_all = "camelCase")]
pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub first_name: String }

// `id` omitted → NotSet again (previously errored "missing field")
let am = ActiveModel::from_json(json!({ "firstName": "Max" }))?;
assert_eq!(am.id, ActiveValue::NotSet);

Entity loader diamond relations (#3030)

The entity loader distinguishes multiple relations to the same target by their relation definition rather than table reference, fixing diamond graphs where two fields point to the same entity through different foreign keys:

rust
#[sea_orm(belongs_to, from = "sender_id", to = "id")]
pub sender: HasOne<user::Entity>,
#[sea_orm(belongs_to, from = "recipient_id", to = "id")]
pub recipient: HasOne<user::Entity>, // both → user, now loaded independently

Schema-sync indexes for non-default PostgreSQL schemas (#3085, #3084)

On PostgreSQL, indexes from #[sea_orm(indexed)] / #[sea_orm(unique_key)] on a schema_name entity now target the qualified table (ON "sys"."app_user"), fixing relation "..." does not exist and wrong-schema targeting. MySQL/SQLite keep unqualified targets (their SeaQuery index builders reject a qualified one), which also removes a latent panic for #[sea_orm(unique)] columns there.

CLI extra attribute parsing (#3032)

Entity generation handles comma-separated extra attributes and derives with nested commas:

shell
--model-extra-attributes 'serde(rename_all = "camelCase"),ts(export)'

PostgreSQL drop_everything with dependent custom types (#3014)

drop_everything drops PostgreSQL custom types with CASCADE, cleaning up dependent domains and functions while preserving extension-owned types.

Generated delete_by_* for single-column unique keys (#3059)

model_ex generates delete_by_* helpers for one-column unique keys, matching the existing find_by_* behavior for #[sea_orm(unique)] and single-column unique_key fields.

rust
Entity::delete_by_name("alpha").exec(db).await?; // alongside find_by_name

SQLite file URI parameters (#3072)

Rusqlite connections allow SQLite file URI query parameters (e.g. shared in-memory databases), leaving validation to SQLite.

rust
Database::connect("sqlite:file:mydb?mode=memory&cache=shared").await?;

SQLite timestamp with time zone reads (#2878)

Timestamp-with-time-zone values preserve their stored offset instead of normalizing through UTC on read.

Tracing instrumentation no longer records SQL arguments by default (#3047)

Automatic tracing::instrument fields drop SQL arguments; SQL recording stays controlled by the explicit tracing span setting.

Compatibility Notes

  • Serde shape for first-party wrappers is now explicit: TextUuid ↔ JSON string, Unix-timestamp wrappers ↔ JSON integers.
  • SQLite timestamp-with-time-zone reads preserve offsets; PostgreSQL drop_everything is intentionally more destructive for custom-type dependents.
  • The entity loader change adds methods to the public loader traits — generated code updates automatically, but manual trait impls must add them.
  • default-features = false users of streaming must enable stream.
  • Tracing spans no longer carry SQL statements/arguments as automatic instrument fields; re-enable via the explicit tracing span setting if needed.
  • Nested prefixes are opt-in; existing unprefixed #[sea_orm(nested)] fields still compile. Reusing the same non-empty prefix for the same nested type is a compile error (the fields would decode from identical aliases).